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