// ─── Video Engine V1 — on-device progress (resume) ─────────────────────────── // Saves playback position on the student's device so they can resume later. // Bounded to the 50 most-recent videos (LRU) to stay light. const KEY_PREFIX = "docdoe_video_progress_"; const INDEX_KEY = "docdoe_video_progress_index"; const MAX_ENTRIES = 50; export type SavedVideoProgress = { video_id: string; current_segment_index: number; current_segment_elapsed: number; // seconds into the current segment completed_segment_ids: string[]; updated_at: string; }; function canUseStorage(): boolean { return typeof window !== "undefined" && !!window.localStorage; } function readIndex(): string[] { if (!canUseStorage()) return []; try { const raw = window.localStorage.getItem(INDEX_KEY); const parsed = raw ? (JSON.parse(raw) as unknown) : []; return Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === "string") : []; } catch { return []; } } function writeIndex(ids: string[]): void { if (!canUseStorage()) return; try { window.localStorage.setItem(INDEX_KEY, JSON.stringify(ids)); } catch { /* storage full / blocked — ignore */ } } function touch(videoId: string): void { let index = readIndex().filter((id) => id !== videoId); index.unshift(videoId); // Evict least-recently-used beyond the cap. if (index.length > MAX_ENTRIES) { const evicted = index.slice(MAX_ENTRIES); index = index.slice(0, MAX_ENTRIES); for (const id of evicted) { try { window.localStorage.removeItem(`${KEY_PREFIX}${id}`); } catch { /* ignore */ } } } writeIndex(index); } export function loadVideoProgress(videoId: string): SavedVideoProgress | null { if (!canUseStorage()) return null; try { const raw = window.localStorage.getItem(`${KEY_PREFIX}${videoId}`); if (!raw) return null; return JSON.parse(raw) as SavedVideoProgress; } catch { return null; } } export function saveVideoProgress(progress: SavedVideoProgress): void { if (!canUseStorage()) return; try { window.localStorage.setItem( `${KEY_PREFIX}${progress.video_id}`, JSON.stringify({ ...progress, updated_at: new Date().toISOString() }), ); touch(progress.video_id); } catch { /* storage full / blocked — degrade silently */ } } export function clearVideoProgress(videoId: string): void { if (!canUseStorage()) return; try { window.localStorage.removeItem(`${KEY_PREFIX}${videoId}`); writeIndex(readIndex().filter((id) => id !== videoId)); } catch { /* ignore */ } }