From 7aaead6c67af050f4552ea22babb472ab7525764 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Tue, 16 Jun 2026 10:38:08 +0200 Subject: [PATCH] refactor: group media state --- static/player/skip/editor.ts | 12 ++++++------ static/player/skip/index.ts | 10 +++++----- static/player/skip/segments.ts | 12 ++++++------ static/player/subtitles/index.ts | 32 ++++++++++++++++---------------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/static/player/skip/editor.ts b/static/player/skip/editor.ts index 6397349..e533545 100644 --- a/static/player/skip/editor.ts +++ b/static/player/skip/editor.ts @@ -166,14 +166,14 @@ export const setupSegmentEditor = (): void => { resetBtn?.addEventListener("click", reset); markStartBtn?.addEventListener("click", () => { - startTime = Math.max(0, state.video.currentTime); + startTime = Math.max(0, state.elements.video.currentTime); if (endTime != null && startTime >= endTime) endTime = null; setError(null); updateLabels(); showControls(); }); markEndBtn?.addEventListener("click", () => { - endTime = Math.max(0, state.video.currentTime); + endTime = Math.max(0, state.elements.video.currentTime); if (startTime != null && endTime <= startTime) { setError("End must be after start."); return; @@ -202,8 +202,8 @@ export const setupSegmentEditor = (): void => { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - mal_id: state.malID, - episode: Number.parseInt(state.currentEpisode, 10), + mal_id: state.episode.malID, + episode: Number.parseInt(state.episode.current, 10), skip_type: skipType, start_time: startTime, end_time: endTime, @@ -219,12 +219,12 @@ export const setupSegmentEditor = (): void => { // Update local segments immediately so UI reflects the saved data. const normalizedType = skipType === "ed" ? "ending" : "opening"; - state.parsedSegments = (state.parsedSegments || []).filter((s) => { + state.skip.parsedSegments = state.skip.parsedSegments.filter((s) => { const t = (s.type || "").toLowerCase(); if (normalizedType === "ending") return t !== "ed" && t !== "ending" && t !== "outro"; return t !== "op" && t !== "opening" && t !== "intro"; }); - state.parsedSegments.push({ + state.skip.parsedSegments.push({ type: normalizedType, start: startTime, end: endTime, diff --git a/static/player/skip/index.ts b/static/player/skip/index.ts index b8a235a..a5ae254 100644 --- a/static/player/skip/index.ts +++ b/static/player/skip/index.ts @@ -12,17 +12,17 @@ const skipLabel = (type: string): string => (type === "ed" ? "Skip outro" : "Ski * Called on timeupdate. Shows button when in active segment. */ export const updateSkipButton = (currentTime: number): void => { - const btn = state.container.querySelector("[data-skip]") as HTMLButtonElement | null; + const btn = state.elements.container.querySelector("[data-skip]") as HTMLButtonElement | null; const displayTime = displayTimeFromAbsolute(currentTime); // find segment that contains current time (with delay buffer) - const segment = state.activeSegments.find((s) => { + const segment = state.skip.activeSegments.find((s) => { const delay = Math.min(1, Math.max(0.25, (s.end - s.start) * 0.02)); return displayTime >= s.start + delay && displayTime < s.end; }); if (!segment) { - state.activeSkipSegment = null; + state.skip.activeSegment = null; btn?.classList.add("hidden"); return; } @@ -30,13 +30,13 @@ export const updateSkipButton = (currentTime: number): void => { // auto-skip: jump to end if enabled const autoSkip = safeLocalStorage.getItem("mal:autoskip-enabled") === "true"; if (autoSkip && displayTime >= segment.start && displayTime < segment.end) { - state.video.currentTime = absoluteTimeFromDisplay(segment.end + 0.01); + state.elements.video.currentTime = absoluteTimeFromDisplay(segment.end + 0.01); void saveProgress(); return; } // show skip button - state.activeSkipSegment = segment; + state.skip.activeSegment = segment; if (btn) { btn.textContent = skipLabel(segment.type); btn.title = skipLabel(segment.type); diff --git a/static/player/skip/segments.ts b/static/player/skip/segments.ts index b8ebec1..11a627f 100644 --- a/static/player/skip/segments.ts +++ b/static/player/skip/segments.ts @@ -11,9 +11,9 @@ const MIN_OUTRO_START_RATIO = 0.5; // outro must start at least 50% in * Validates intro/outro positioning. */ export const resolveActiveSegments = (): void => { - const bounds = state.video.duration; + const bounds = state.elements.video.duration; if (bounds <= 0) { - state.activeSegments = []; + state.skip.activeSegments = []; return; } @@ -24,7 +24,7 @@ export const resolveActiveSegments = (): void => { return null; }; - state.activeSegments = state.parsedSegments.filter((s) => { + state.skip.activeSegments = state.skip.parsedSegments.filter((s) => { const t = normalizeType(s.type); if (!t) return false; const isOverride = (s.source || "").toLowerCase() === "override"; @@ -54,14 +54,14 @@ export const resolveActiveSegments = (): void => { * Renders segment markers on the timeline progress bar. */ export const renderSegments = (): void => { - const track = state.container.querySelector("[data-segments]") as HTMLElement | null; + const track = state.elements.container.querySelector("[data-segments]") as HTMLElement | null; if (!track) return; track.innerHTML = ""; - const bounds = state.video.duration; + const bounds = state.elements.video.duration; if (bounds <= 0) return; - state.activeSegments.forEach((s) => { + state.skip.activeSegments.forEach((s) => { const bar = document.createElement("div"); bar.className = "absolute opacity-95"; bar.style.backgroundColor = "var(--player-segment)"; diff --git a/static/player/subtitles/index.ts b/static/player/subtitles/index.ts index 79c9f66..1a148fd 100644 --- a/static/player/subtitles/index.ts +++ b/static/player/subtitles/index.ts @@ -7,7 +7,7 @@ const proxyUrl = (token: string) => `/watch/proxy/subtitle?token=${encodeURIComp // builds subtitle track list from current mode's source const subtitlesForMode = (): SubtitleTrack[] => { - const src = state.modeSources[state.currentMode]; + const src = state.playback.modeSources[state.playback.currentMode]; if (!src?.subtitles) return []; return src.subtitles .map((t) => ({ @@ -19,7 +19,7 @@ const subtitlesForMode = (): SubtitleTrack[] => { }; const hideSubtitleText = (): void => { - const el = state.container.querySelector("[data-subtitle-text]") as HTMLElement | null; + const el = state.elements.container.querySelector("[data-subtitle-text]") as HTMLElement | null; if (!el) return; el.textContent = ""; el.classList.remove("block"); @@ -42,11 +42,11 @@ const loadSubtitle = async (url: string): Promise => { * Shows/hides dropdown based on availability. */ export const updateSubtitleOptions = (): void => { - const select = state.container.querySelector( + const select = state.elements.container.querySelector( "[data-subtitle-select]", ) as HTMLSelectElement | null; if (!select) return; - state.currentSubtitleTracks = subtitlesForMode(); + state.subtitles.tracks = subtitlesForMode(); select.innerHTML = ""; const none = document.createElement("option"); @@ -55,7 +55,7 @@ export const updateSubtitleOptions = (): void => { select.appendChild(none); select.value = "none"; - state.currentSubtitleTracks.forEach((t, i) => { + state.subtitles.tracks.forEach((t, i) => { const opt = document.createElement("option"); opt.value = String(i); opt.textContent = t.label; @@ -63,8 +63,8 @@ export const updateSubtitleOptions = (): void => { }); const wrapper = select.parentElement; - wrapper?.classList.toggle("hidden", state.currentSubtitleTracks.length === 0); - state.activeSubtitles = []; + wrapper?.classList.toggle("hidden", state.subtitles.tracks.length === 0); + state.subtitles.activeCues = []; hideSubtitleText(); }; @@ -73,20 +73,20 @@ export const updateSubtitleOptions = (): void => { * Finds active cue and shows/hides overlay. */ export const updateSubtitleRender = (time: number): void => { - const el = state.container.querySelector("[data-subtitle-text]") as HTMLElement | null; + const el = state.elements.container.querySelector("[data-subtitle-text]") as HTMLElement | null; if (!el) return; - if (!state.activeSubtitles.length) { + if (!state.subtitles.activeCues.length) { hideSubtitleText(); return; } // binary search: cues are sorted by start time let lo = 0; - let hi = state.activeSubtitles.length - 1; + let hi = state.subtitles.activeCues.length - 1; let cue: SubtitleCue | undefined; while (lo <= hi) { const mid = (lo + hi) >> 1; - const c = state.activeSubtitles[mid]; + const c = state.subtitles.activeCues[mid]; if (time < c.start) { hi = mid - 1; continue; @@ -112,22 +112,22 @@ export const updateSubtitleRender = (time: number): void => { * Loads and parses selected VTT track. */ export const setupSubtitles = (): void => { - const select = state.container.querySelector( + const select = state.elements.container.querySelector( "[data-subtitle-select]", ) as HTMLSelectElement | null; select?.addEventListener("change", async () => { if (select.value === "none") { - state.activeSubtitles = []; + state.subtitles.activeCues = []; hideSubtitleText(); return; } - const track = state.currentSubtitleTracks[Number(select.value)]; + const track = state.subtitles.tracks[Number(select.value)]; if (!track) { - state.activeSubtitles = []; + state.subtitles.activeCues = []; return; } const cues = await loadSubtitle(track.url); cues.sort((a, b) => a.start - b.start); - state.activeSubtitles = cues; + state.subtitles.activeCues = cues; }); };