Files
party-player/public/player/yt.js
simonandClaude Opus 5 fc105d4e5e 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. 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
2026-08-10 21:03:37 +02:00

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,
};