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;