cc_load_policy is only a request: the viewer's "always show captions" preference overrides it, and auto-generated tracks ignore it. Unloading the caption module is the part that sticks, and it has to run again after every load because a new video brings the module back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9v2Xoa6eoeGYpK6gp95k9
88 lines
2.4 KiB
JavaScript
88 lines
2.4 KiB
JavaScript
// Thin wrapper around the YouTube IFrame Player API.
|
|
|
|
let apiReady = null;
|
|
|
|
/** Load the IFrame API once; resolves when window.YT is usable. */
|
|
export function loadApi() {
|
|
if (apiReady) return apiReady;
|
|
|
|
apiReady = new Promise((resolve) => {
|
|
if (window.YT?.Player) return resolve(window.YT);
|
|
window.onYouTubeIframeAPIReady = () => resolve(window.YT);
|
|
const script = document.createElement('script');
|
|
script.src = 'https://www.youtube.com/iframe_api';
|
|
document.head.appendChild(script);
|
|
});
|
|
|
|
return apiReady;
|
|
}
|
|
|
|
const PLAYER_VARS = {
|
|
controls: 0,
|
|
disablekb: 1,
|
|
modestbranding: 1,
|
|
rel: 0,
|
|
playsinline: 1,
|
|
iv_load_policy: 3,
|
|
fs: 0,
|
|
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.
|
|
*/
|
|
export async function createPlayer(elementId, { onStateChange } = {}) {
|
|
const YT = await loadApi();
|
|
|
|
return new Promise((resolve) => {
|
|
const player = new YT.Player(elementId, {
|
|
width: '100%',
|
|
height: '100%',
|
|
playerVars: { ...PLAYER_VARS, origin: location.origin },
|
|
events: {
|
|
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);
|
|
},
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
export const STATE = {
|
|
UNSTARTED: -1,
|
|
ENDED: 0,
|
|
PLAYING: 1,
|
|
PAUSED: 2,
|
|
BUFFERING: 3,
|
|
CUED: 5,
|
|
};
|