import { isYoutubeMediaPath } from './youtube-playback'; import { normalizeYoutubeLangCode } from '../../core/services/youtube/labels'; export type YoutubePrimarySubtitleNotificationTimer = ReturnType | { id: number }; type SubtitleTrackEntry = { id: number | null; type: string; lang: string; external: boolean; }; function parseTrackId(value: unknown): number | null { if (typeof value === 'number' && Number.isInteger(value)) { return value; } if (typeof value === 'string') { const parsed = Number(value.trim()); return Number.isInteger(parsed) ? parsed : null; } return null; } function normalizeTrack(entry: unknown): SubtitleTrackEntry | null { if (!entry || typeof entry !== 'object') { return null; } const track = entry as Record; return { id: parseTrackId(track.id), type: String(track.type || '').trim(), lang: String(track.lang || '').trim(), external: track.external === true, }; } export function clearYoutubePrimarySubtitleNotificationTimer( timer: YoutubePrimarySubtitleNotificationTimer | null, ): void { if (!timer) { return; } if (typeof timer === 'object' && timer !== null && 'id' in timer) { clearTimeout((timer as { id: number }).id); return; } clearTimeout(timer); } function buildPreferredLanguageSet(values: string[]): Set { const normalized = values .map((value) => normalizeYoutubeLangCode(value)) .filter((value) => value.length > 0); return new Set(normalized); } function matchesPreferredLanguage(language: string, preferred: Set): boolean { if (preferred.size === 0) { return false; } const normalized = normalizeYoutubeLangCode(language); if (!normalized) { return false; } if (preferred.has(normalized)) { return true; } const base = normalized.split('-')[0] || normalized; return preferred.has(base); } function hasSelectedPrimarySubtitle( sid: number | null, trackList: unknown[] | null, preferredLanguages: Set, ): boolean { if (sid === null || !Array.isArray(trackList)) { return false; } const activeTrack = trackList.map(normalizeTrack).find((track) => track?.type === 'sub' && track.id === sid) ?? null; if (!activeTrack) { return false; } if (activeTrack.external) { return true; } return matchesPreferredLanguage(activeTrack.lang, preferredLanguages); } export function createYoutubePrimarySubtitleNotificationRuntime(deps: { getPrimarySubtitleLanguages: () => string[]; notifyFailure: (message: string) => void; schedule: (fn: () => void, delayMs: number) => YoutubePrimarySubtitleNotificationTimer; clearSchedule: (timer: YoutubePrimarySubtitleNotificationTimer | null) => void; delayMs?: number; }) { const delayMs = deps.delayMs ?? 5000; let currentMediaPath: string | null = null; let currentSid: number | null = null; let currentTrackList: unknown[] | null = null; let pendingTimer: YoutubePrimarySubtitleNotificationTimer | null = null; let lastReportedMediaPath: string | null = null; const clearPendingTimer = (): void => { deps.clearSchedule(pendingTimer); pendingTimer = null; }; const maybeReportFailure = (): void => { const mediaPath = currentMediaPath?.trim() || ''; if (!mediaPath || !isYoutubeMediaPath(mediaPath)) { return; } if (lastReportedMediaPath === mediaPath) { return; } const preferredLanguages = buildPreferredLanguageSet(deps.getPrimarySubtitleLanguages()); if (preferredLanguages.size === 0) { return; } if (hasSelectedPrimarySubtitle(currentSid, currentTrackList, preferredLanguages)) { return; } lastReportedMediaPath = mediaPath; deps.notifyFailure('Primary subtitle failed to download or load. Try again from the subtitle modal.'); }; const schedulePendingCheck = (): void => { clearPendingTimer(); const mediaPath = currentMediaPath?.trim() || ''; if (!mediaPath || !isYoutubeMediaPath(mediaPath)) { return; } pendingTimer = deps.schedule(() => { pendingTimer = null; maybeReportFailure(); }, delayMs); }; return { handleMediaPathChange: (path: string | null): void => { const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : null; if (currentMediaPath !== normalizedPath) { lastReportedMediaPath = null; } currentMediaPath = normalizedPath; currentSid = null; currentTrackList = null; schedulePendingCheck(); }, handleSubtitleTrackChange: (sid: number | null): void => { currentSid = sid; const preferredLanguages = buildPreferredLanguageSet(deps.getPrimarySubtitleLanguages()); if (hasSelectedPrimarySubtitle(currentSid, currentTrackList, preferredLanguages)) { clearPendingTimer(); } }, handleSubtitleTrackListChange: (trackList: unknown[] | null): void => { currentTrackList = trackList; const preferredLanguages = buildPreferredLanguageSet(deps.getPrimarySubtitleLanguages()); if (hasSelectedPrimarySubtitle(currentSid, currentTrackList, preferredLanguages)) { clearPendingTimer(); } }, }; }