import { state } from './state'; import { displayTimeFromAbsolute } from './timeline'; import { showControls } from './controls'; import { updateSubtitleOptions } from './subtitles'; import { updateQualityOptions } from './quality'; import { safeLocalStorage } from './storage'; // builds stream URL with mode, token, and optional quality param const streamUrlForMode = (mode: string, quality?: string): string => { const src = state.modeSources[mode]; if (!src?.token) return ''; let url = `${state.streamURL}?mode=${encodeURIComponent(mode)}&token=${encodeURIComponent(src.token)}`; if (quality && quality !== 'best') url += `&quality=${encodeURIComponent(quality)}`; return url; }; // switches video src while preserving playback position const loadVideo = (url: string): void => { if (!url) return; const wasPlaying = !state.video.paused; const prevTime = displayTimeFromAbsolute(state.video.currentTime); state.video.src = url; state.video.load(); state.pendingSeekTime = prevTime; // restored in loadedmetadata handler if (wasPlaying) { state.video.play().catch(() => undefined); } }; /** * Switches between sub/dub mode. * Saves preference to localStorage, reloads video src. */ export const switchMode = (mode: string): void => { if (!state.availableModes.includes(mode) || mode === state.currentMode) return; state.currentMode = mode; safeLocalStorage.setItem('player-audio-mode', mode); const qualitySelect = state.container.querySelector( '[data-quality-select]' ) as HTMLSelectElement | null; loadVideo(streamUrlForMode(mode, qualitySelect?.value)); updateSubtitleOptions(); updateQualityOptions(); updateModeButtons(); }; /** * Updates dub/sub button styling based on current mode. * Disables unavailable modes. */ export const updateModeButtons = (): void => { const dub = state.container.querySelector('[data-mode-dub]') as HTMLButtonElement | null; const sub = state.container.querySelector('[data-mode-sub]') as HTMLButtonElement | null; const m = state.currentMode; dub?.classList.toggle('text-accent', m === 'dub'); dub?.classList.toggle('text-foreground', m !== 'dub'); dub?.classList.toggle('opacity-50', !state.availableModes.includes('dub')); dub?.classList.toggle('cursor-not-allowed', !state.availableModes.includes('dub')); if (dub) { dub.disabled = !state.availableModes.includes('dub'); } sub?.classList.toggle('text-accent', m === 'sub'); sub?.classList.toggle('text-foreground', m !== 'sub'); sub?.classList.toggle('opacity-50', !state.availableModes.includes('sub')); sub?.classList.toggle('cursor-not-allowed', !state.availableModes.includes('sub')); if (sub) { sub.disabled = !state.availableModes.includes('sub'); } }; /** * Binds click handlers for mode buttons and autoplay toggle. */ export const setupMode = (): void => { const dub = state.container.querySelector('[data-mode-dub]') as HTMLButtonElement | null; const sub = state.container.querySelector('[data-mode-sub]') as HTMLButtonElement | null; dub?.addEventListener('click', () => { if (state.availableModes.includes('dub')) { switchMode('dub'); showControls(); } }); sub?.addEventListener('click', () => { if (state.availableModes.includes('sub')) { switchMode('sub'); showControls(); } }); const autoplayBtn = document.querySelector('[data-autoplay]') as HTMLInputElement | null; autoplayBtn?.addEventListener('change', e => { safeLocalStorage.setItem( 'mal:autoplay-enabled', (e.target as HTMLInputElement).checked ? 'true' : 'false' ); showControls(); }); };