// 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, // no burned-in captions over the visuals }; /** * 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: () => resolve(player), onStateChange: (event) => onStateChange?.(event.data, player), }, }); }); } export const STATE = { UNSTARTED: -1, ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5, };