import type { CliArgs, CliCommandSource } from '../../cli/args'; type LaunchResult = { ok: boolean; mpvPath?: string; }; export type YoutubePlaybackRuntimeDeps = { platform: NodeJS.Platform; directPlaybackFormat: string; mpvYtdlFormat: string; autoLaunchTimeoutMs: number; connectTimeoutMs: number; getSocketPath: () => string; getMpvConnected: () => boolean; invalidatePendingAutoplayReadyFallbacks: () => void; setAppOwnedFlowInFlight: (next: boolean) => void; ensureYoutubePlaybackRuntimeReady: () => Promise; resolveYoutubePlaybackUrl: (url: string, format: string) => Promise; launchWindowsMpv: (playbackUrl: string, args: string[]) => Promise; waitForYoutubeMpvConnected: (timeoutMs: number) => Promise; prepareYoutubePlaybackInMpv: (request: { url: string }) => Promise; runYoutubePlaybackFlow: (request: { url: string; mode: NonNullable; }) => Promise; logInfo: (message: string) => void; logWarn: (message: string) => void; schedule: (callback: () => void, delayMs: number) => ReturnType; clearScheduled: (timer: ReturnType) => void; }; export function createYoutubePlaybackRuntime(deps: YoutubePlaybackRuntimeDeps) { let quitOnDisconnectArmed = false; let quitOnDisconnectArmTimer: ReturnType | null = null; let playbackFlowGeneration = 0; const clearYoutubePlayQuitOnDisconnectArmTimer = (): void => { if (quitOnDisconnectArmTimer) { deps.clearScheduled(quitOnDisconnectArmTimer); quitOnDisconnectArmTimer = null; } }; const runYoutubePlaybackFlow = async (request: { url: string; mode: NonNullable; source: CliCommandSource; }): Promise => { const flowGeneration = ++playbackFlowGeneration; deps.invalidatePendingAutoplayReadyFallbacks(); deps.setAppOwnedFlowInFlight(true); let flowCompleted = false; try { clearYoutubePlayQuitOnDisconnectArmTimer(); quitOnDisconnectArmed = false; await deps.ensureYoutubePlaybackRuntimeReady(); let playbackUrl = request.url; let launchedWindowsMpv = false; if (deps.platform === 'win32') { try { playbackUrl = await deps.resolveYoutubePlaybackUrl( request.url, deps.directPlaybackFormat, ); deps.logInfo('Resolved direct YouTube playback URL for Windows MPV startup.'); } catch (error) { deps.logWarn( `Failed to resolve direct YouTube playback URL; falling back to page URL: ${ error instanceof Error ? error.message : String(error) }`, ); } } if (deps.platform === 'win32' && !deps.getMpvConnected()) { const socketPath = deps.getSocketPath(); const launchResult = await deps.launchWindowsMpv(playbackUrl, [ '--pause=yes', '--ytdl=yes', `--ytdl-format=${deps.mpvYtdlFormat}`, '--sub-auto=no', '--sub-file-paths=.;subs;subtitles', '--sid=auto', '--secondary-sid=auto', '--secondary-sub-visibility=no', '--alang=ja,jp,jpn,japanese,en,eng,english,enus,en-us', '--slang=ja,jp,jpn,japanese,en,eng,english,enus,en-us', `--input-ipc-server=${socketPath}`, ]); launchedWindowsMpv = launchResult.ok; if (launchResult.ok && launchResult.mpvPath) { deps.logInfo( `Bootstrapping Windows mpv for YouTube playback via ${launchResult.mpvPath}`, ); } if (!launchResult.ok) { deps.logWarn('Unable to bootstrap Windows mpv for YouTube playback.'); } } const connected = await deps.waitForYoutubeMpvConnected( launchedWindowsMpv ? deps.autoLaunchTimeoutMs : deps.connectTimeoutMs, ); if (!connected) { throw new Error( launchedWindowsMpv ? 'MPV not connected after auto-launch. Ensure mpv is installed and can open the requested YouTube URL.' : 'MPV not connected. Start mpv with the SubMiner profile or retry after mpv finishes starting.', ); } if (request.source === 'initial') { quitOnDisconnectArmTimer = deps.schedule(() => { if (playbackFlowGeneration !== flowGeneration) { return; } quitOnDisconnectArmed = true; quitOnDisconnectArmTimer = null; }, 3000); } const mediaReady = await deps.prepareYoutubePlaybackInMpv({ url: playbackUrl }); if (!mediaReady) { throw new Error('Timed out waiting for mpv to load the requested YouTube URL.'); } await deps.runYoutubePlaybackFlow({ url: request.url, mode: request.mode, }); flowCompleted = true; deps.logInfo(`YouTube playback flow completed from ${request.source}.`); } finally { if (playbackFlowGeneration === flowGeneration) { if (!flowCompleted) { clearYoutubePlayQuitOnDisconnectArmTimer(); quitOnDisconnectArmed = false; } deps.setAppOwnedFlowInFlight(false); } } }; return { clearYoutubePlayQuitOnDisconnectArmTimer, getQuitOnDisconnectArmed: (): boolean => quitOnDisconnectArmed, runYoutubePlaybackFlow, }; }