diff --git a/public/player/yt.js b/public/player/yt.js index 49ffb62..a29cdbf 100644 --- a/public/player/yt.js +++ b/public/player/yt.js @@ -25,9 +25,28 @@ const PLAYER_VARS = { playsinline: 1, iv_load_policy: 3, fs: 0, - cc_load_policy: 0, // no burned-in captions over the visuals + cc_load_policy: 0, // asks for no captions; see hideCaptions for the rest }; +/** + * Turn captions off for real. + * + * cc_load_policy is only a request: the viewer's "always show captions" + * preference overrides it, and auto-generated tracks ignore it outright. + * Unloading the module is the part that sticks. Two names because the module + * is 'captions' on the HTML5 player and 'cc' on the older one, and loading a + * new video resets both — so this has to run again after every load. + */ +export function hideCaptions(player) { + for (const module of ['captions', 'cc']) { + try { + player.unloadModule(module); + } catch { + // Module was never loaded for this video; nothing to turn off. + } + } +} + /** * Create a player in the given element id. Resolves once the player is ready. * `onStateChange` receives the raw YT state code. @@ -41,8 +60,18 @@ export async function createPlayer(elementId, { onStateChange } = {}) { height: '100%', playerVars: { ...PLAYER_VARS, origin: location.origin }, events: { - onReady: () => resolve(player), - onStateChange: (event) => onStateChange?.(event.data, player), + onReady: () => { + hideCaptions(player); + resolve(player); + }, + onStateChange: (event) => { + // The caption module comes back with each new video, and only once + // the video is actually running, so re-clear it here. + if (event.data === STATE.PLAYING || event.data === STATE.CUED) { + hideCaptions(player); + } + onStateChange?.(event.data, player); + }, }, }); });