diff --git a/public/locales/en-US.json b/public/locales/en-US.json
index 0e8228ac..f1274b2a 100644
--- a/public/locales/en-US.json
+++ b/public/locales/en-US.json
@@ -264,6 +264,10 @@
"label": "Automatic theater mode",
"title": "Automatically enables theater mode when you load a video"
},
+ "automaticallyDisableClosedCaptions": {
+ "label": "Automatically disable closed captions",
+ "title": "Automatically disables closed captions when you load a video"
+ },
"copyTimestampUrlButton": {
"label": "Copy video URL with timestamp button",
"title": "Copies video URL with timestamp (?t=123)"
diff --git a/public/locales/en-US.json.d.ts b/public/locales/en-US.json.d.ts
index 4e126f35..27e9c06f 100644
--- a/public/locales/en-US.json.d.ts
+++ b/public/locales/en-US.json.d.ts
@@ -183,6 +183,10 @@ interface EnUS {
label: "Automatic theater mode";
title: "Automatically enables theater mode when you load a video";
};
+ automaticallyDisableClosedCaptions: {
+ label: "Automatically disable closed captions";
+ title: "Automatically disables closed captions when you load a video";
+ };
copyTimestampUrlButton: {
label: "Copy video URL with timestamp button";
title: "Copies video URL with timestamp (?t=123)";
diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx
index 4164e317..59a95b63 100644
--- a/src/components/Settings/Settings.tsx
+++ b/src/components/Settings/Settings.tsx
@@ -769,6 +769,14 @@ export default function Settings() {
onChange={setCheckboxOption("enable_hide_official_artist_videos_from_home_page")}
title={t("settings.sections.miscellaneous.features.hideOfficialArtistVideosFromHomePage.title")}
/>
+
diff --git a/src/features/automaticallyDisableClosedCaptions/index.ts b/src/features/automaticallyDisableClosedCaptions/index.ts
new file mode 100644
index 00000000..761fea9f
--- /dev/null
+++ b/src/features/automaticallyDisableClosedCaptions/index.ts
@@ -0,0 +1,31 @@
+import type { YouTubePlayerDiv } from "@/src/types";
+import { isWatchPage, waitForAllElements, waitForSpecificMessage } from "@/src/utils/utilities";
+let captionsWhereEnabled = false;
+export async function enableAutomaticallyDisableClosedCaptions() {
+ const {
+ data: {
+ options: { enable_automatically_disable_closed_captions }
+ }
+ } = await waitForSpecificMessage("options", "request_data", "content");
+ if (!enable_automatically_disable_closed_captions) return;
+ await waitForAllElements(["div#player", "div#player-wide-container", "div#video-container", "div#player-container"]);
+ // Get the player element
+ const playerContainer = isWatchPage() ? document.querySelector("div#movie_player") : null;
+ const subtitlesButton = document.querySelector("button.ytp-subtitles-button");
+ // If player element is not available, return
+ if (!playerContainer || !subtitlesButton) return;
+ captionsWhereEnabled = subtitlesButton.getAttribute("aria-pressed") === "true";
+ // Disable captions
+ playerContainer.unloadModule("captions");
+}
+export async function disableAutomaticallyDisableClosedCaptions() {
+ await waitForAllElements(["div#player", "div#player-wide-container", "div#video-container", "div#player-container"]);
+ // Get the player element
+ const playerContainer = isWatchPage() ? document.querySelector("div#movie_player") : null;
+ // If player element is not available, return
+ if (!playerContainer) return;
+ // If captions weren't enabled, return
+ if (!captionsWhereEnabled) return;
+ // Re-enable captions
+ playerContainer.loadModule("captions");
+}
diff --git a/src/global.d.ts b/src/global.d.ts
index 1158478f..3e76a27d 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -55,6 +55,8 @@ declare module "node_modules/@types/youtube-player/dist/types" {
viewerLivestreamJoinMediaTime: number;
}
interface YouTubePlayer {
+ unloadModule(moduleName: string): void;
+ loadModule(moduleName: string): void;
getProgressState(): ProgressState;
getVideoBytesLoaded(): Promise;
getVideoData(): Promise;
diff --git a/src/i18n/constants.ts b/src/i18n/constants.ts
index 803d1ac7..f67d438b 100644
--- a/src/i18n/constants.ts
+++ b/src/i18n/constants.ts
@@ -1,50 +1,50 @@
export const availableLocales = [
- "ca-ES",
- "cs-CZ",
- "de-DE",
- "en-GB",
- "en-US",
- "es-ES",
- "fa-IR",
- "fr-FR",
- "he-IL",
- "hi-IN",
- "it-IT",
- "ja-JP",
- "ko-KR",
- "pl-PL",
- "pt-BR",
- "ru-RU",
- "sv-SE",
- "tr-TR",
- "uk-UA",
- "vi-VN",
- "zh-CN",
- "zh-TW"
+ "ca-ES",
+ "cs-CZ",
+ "de-DE",
+ "en-GB",
+ "en-US",
+ "es-ES",
+ "fa-IR",
+ "fr-FR",
+ "he-IL",
+ "hi-IN",
+ "it-IT",
+ "ja-JP",
+ "ko-KR",
+ "pl-PL",
+ "pt-BR",
+ "ru-RU",
+ "sv-SE",
+ "tr-TR",
+ "uk-UA",
+ "vi-VN",
+ "zh-CN",
+ "zh-TW"
] as const;
export const localePercentages: Record = {
- "en-US": 100,
- "ca-ES": 0,
- "cs-CZ": 0,
- "de-DE": 27,
- "en-GB": 1,
- "es-ES": 48,
- "fa-IR": 0,
- "fr-FR": 51,
- "he-IL": 0,
- "hi-IN": 0,
- "it-IT": 100,
- "ja-JP": 92,
- "ko-KR": 99,
- "pl-PL": 0,
- "pt-BR": 56,
- "ru-RU": 90,
- "sv-SE": 99,
- "tr-TR": 63,
- "uk-UA": 18,
- "vi-VN": 0,
- "zh-CN": 100,
- "zh-TW": 97
+ "en-US": 100,
+ "ca-ES": 0,
+ "cs-CZ": 0,
+ "de-DE": 27,
+ "en-GB": 1,
+ "es-ES": 48,
+ "fa-IR": 0,
+ "fr-FR": 51,
+ "he-IL": 0,
+ "hi-IN": 0,
+ "it-IT": 100,
+ "ja-JP": 92,
+ "ko-KR": 99,
+ "pl-PL": 0,
+ "pt-BR": 56,
+ "ru-RU": 90,
+ "sv-SE": 99,
+ "tr-TR": 63,
+ "uk-UA": 18,
+ "vi-VN": 0,
+ "zh-CN": 100,
+ "zh-TW": 97
};
export const localeDirection: Record = {
"ca-ES": "ltr",
diff --git a/src/pages/content/index.ts b/src/pages/content/index.ts
index c3cf1f69..983beab0 100644
--- a/src/pages/content/index.ts
+++ b/src/pages/content/index.ts
@@ -407,6 +407,11 @@ const storageChangeHandler = async (changes: StorageChanges, areaName: string) =
scrollWheelSpeedControlEnabled: newValue
});
},
+ enable_automatically_disable_closed_captions: (__oldValue, newValue) => {
+ sendExtensionOnlyMessage("automaticallyDisableClosedCaptionsChange", {
+ automaticallyDisableClosedCaptionsEnabled: newValue
+ });
+ },
enable_scroll_wheel_volume_control: (__oldValue, newValue) => {
sendExtensionOnlyMessage("scrollWheelVolumeControlChange", {
scrollWheelVolumeControlEnabled: newValue
diff --git a/src/pages/embedded/index.ts b/src/pages/embedded/index.ts
index 71932b0d..f7af1058 100644
--- a/src/pages/embedded/index.ts
+++ b/src/pages/embedded/index.ts
@@ -1,6 +1,10 @@
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
import { deepDarkPresets } from "@/src/deepDarkPresets";
import { type FeatureFuncRecord, featureButtonFunctions } from "@/src/features";
+import {
+ disableAutomaticallyDisableClosedCaptions,
+ enableAutomaticallyDisableClosedCaptions
+} from "@/src/features/automaticallyDisableClosedCaptions";
import { enableAutomaticTheaterMode } from "@/src/features/automaticTheaterMode";
import { featuresInControls } from "@/src/features/buttonPlacement";
import { getFeatureButton, updateFeatureButtonIcon, updateFeatureButtonTitle } from "@/src/features/buttonPlacement/utils";
@@ -186,7 +190,8 @@ const enableFeatures = () => {
adjustSpeedOnScrollWheel(),
enableHideTranslateComment(),
enableHideEndScreenCards(),
- enablePlaylistLength()
+ enablePlaylistLength(),
+ enableAutomaticallyDisableClosedCaptions()
]);
// Enable feature menu before calling button functions
await enableFeatureMenu();
@@ -650,6 +655,17 @@ window.addEventListener("DOMContentLoaded", function () {
else await removeHideEndScreenCardsButton();
break;
}
+ case "automaticallyDisableClosedCaptionsChange": {
+ const {
+ data: { automaticallyDisableClosedCaptionsEnabled }
+ } = message;
+ if (automaticallyDisableClosedCaptionsEnabled) {
+ await enableAutomaticallyDisableClosedCaptions();
+ } else {
+ await disableAutomaticallyDisableClosedCaptions();
+ }
+ break;
+ }
case "hideLiveStreamChatChange": {
const {
data: { hideLiveStreamChatEnabled }
diff --git a/src/types/index.ts b/src/types/index.ts
index f8cd7638..25805377 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -416,6 +416,10 @@ export type ExtensionSendOnlyMessageMappings = {
{ volumeBoostAmount: number; volumeBoostEnabled: boolean; volumeBoostMode: VolumeBoostMode }
>;
volumeBoostChange: DataResponseMessage<"volumeBoostChange", { volumeBoostEnabled: boolean; volumeBoostMode: VolumeBoostMode }>;
+ automaticallyDisableClosedCaptionsChange: DataResponseMessage<
+ "automaticallyDisableClosedCaptionsChange",
+ { automaticallyDisableClosedCaptionsEnabled: boolean }
+ >;
};
export type FilterMessagesBySource = {
[K in keyof T]: Extract;
@@ -455,6 +459,7 @@ export type configuration = {
enable_automatic_theater_mode: boolean;
enable_automatically_set_quality: boolean;
enable_copy_timestamp_url_button: boolean;
+ enable_automatically_disable_closed_captions: boolean;
enable_custom_css: boolean;
enable_deep_dark_theme: boolean;
enable_forced_playback_speed: boolean;
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 218dafd2..23bad81d 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -111,7 +111,8 @@ export const defaultConfiguration = {
volume_adjustment_steps: 5,
volume_boost_amount: 5,
volume_boost_mode: "global",
- youtube_data_api_v3_key: ""
+ youtube_data_api_v3_key: "",
+ enable_automatically_disable_closed_captions: false
} satisfies configuration;
export const configurationImportSchema: TypeToPartialZodSchema<
configuration,
@@ -207,7 +208,8 @@ export const configurationImportSchema: TypeToPartialZodSchema<
volume_adjustment_steps: z.number().min(1).max(100).optional(),
volume_boost_amount: z.number().optional(),
volume_boost_mode: z.enum(volumeBoostModes).optional(),
- youtube_data_api_v3_key: z.string().optional()
+ youtube_data_api_v3_key: z.string().optional(),
+ enable_automatically_disable_closed_captions: z.boolean().optional()
});
export const DEV_MODE = process.env.__DEV__ === "true";
export const ENABLE_SOURCE_MAP = DEV_MODE === true ? "inline" : false;