From 1c4ade5e6c380b147aadd18f2ccd9eb02cf45eab Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 28 May 2026 11:28:56 +0200 Subject: [PATCH] chore: format player mode and state --- static/player/mode.ts | 68 ++++++++++++------------- static/player/state.ts | 110 ++++++++++++++++++++--------------------- 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/static/player/mode.ts b/static/player/mode.ts index e7bcbe5..9bd22d6 100644 --- a/static/player/mode.ts +++ b/static/player/mode.ts @@ -1,16 +1,16 @@ -import { state } from './state'; -import { displayTimeFromAbsolute } from './timeline'; -import { showControls } from './controls'; -import { updateSubtitleOptions } from './subtitles'; -import { updateQualityOptions } from './quality'; -import { safeLocalStorage } from './storage'; +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 ''; + if (!src?.token) return ""; let url = `${state.streamURL}?mode=${encodeURIComponent(mode)}&token=${encodeURIComponent(src.token)}`; - if (quality && quality !== 'best') url += `&quality=${encodeURIComponent(quality)}`; + if (quality && quality !== "best") url += `&quality=${encodeURIComponent(quality)}`; return url; }; @@ -34,9 +34,9 @@ const loadVideo = (url: string): void => { export const switchMode = (mode: string): void => { if (!state.availableModes.includes(mode) || mode === state.currentMode) return; state.currentMode = mode; - safeLocalStorage.setItem('player-audio-mode', mode); + safeLocalStorage.setItem("player-audio-mode", mode); const qualitySelect = state.container.querySelector( - '[data-quality-select]' + "[data-quality-select]", ) as HTMLSelectElement | null; loadVideo(streamUrlForMode(mode, qualitySelect?.value)); updateSubtitleOptions(); @@ -49,24 +49,24 @@ export const switchMode = (mode: string): void => { * 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 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')); + 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'); + 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')); + 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'); + sub.disabled = !state.availableModes.includes("sub"); } }; @@ -74,27 +74,27 @@ export const updateModeButtons = (): void => { * 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; + 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'); + dub?.addEventListener("click", () => { + if (state.availableModes.includes("dub")) { + switchMode("dub"); showControls(); } }); - sub?.addEventListener('click', () => { - if (state.availableModes.includes('sub')) { - switchMode('sub'); + sub?.addEventListener("click", () => { + if (state.availableModes.includes("sub")) { + switchMode("sub"); showControls(); } }); - const autoplayBtn = document.querySelector('[data-autoplay]') as HTMLInputElement | null; - autoplayBtn?.addEventListener('change', e => { + 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' + "mal:autoplay-enabled", + (e.target as HTMLInputElement).checked ? "true" : "false", ); showControls(); }); diff --git a/static/player/state.ts b/static/player/state.ts index 4d68bcc..870c690 100644 --- a/static/player/state.ts +++ b/static/player/state.ts @@ -1,6 +1,6 @@ -import type { ModeSource, SkipSegment, SubtitleCue, SubtitleTrack, ActiveSegment } from './types'; -import { q, qs, dataset } from '../q'; -import { safeLocalStorage } from './storage'; +import type { ModeSource, SkipSegment, SubtitleCue, SubtitleTrack, ActiveSegment } from "./types"; +import { q, qs, dataset } from "../q"; +import { safeLocalStorage } from "./storage"; export interface PlayerState { container: HTMLElement; @@ -44,22 +44,22 @@ export interface PlayerState { } const createInitialState = (): PlayerState => ({ - container: document.createElement('div'), - video: document.createElement('video'), - progress: document.createElement('div'), - scrubber: document.createElement('div'), - buffered: document.createElement('div'), - timeDisplay: document.createElement('div'), - durationDisplay: document.createElement('div'), + container: document.createElement("div"), + video: document.createElement("video"), + progress: document.createElement("div"), + scrubber: document.createElement("div"), + buffered: document.createElement("div"), + timeDisplay: document.createElement("div"), + durationDisplay: document.createElement("div"), modeSources: {}, availableModes: [], - currentMode: 'dub', - modeSwitchedFrom: '', - currentEpisode: '1', + currentMode: "dub", + modeSwitchedFrom: "", + currentEpisode: "1", totalEpisodes: 0, malID: 0, - streamURL: '/watch/proxy/stream', - initialStreamToken: '', + streamURL: "/watch/proxy/stream", + initialStreamToken: "", startTimeSeconds: 0, shouldAutoPlay: false, parsedSegments: [], @@ -76,7 +76,7 @@ const createInitialState = (): PlayerState => ({ transitionEpisode: null, completionSent: false, completionAttempts: 0, - lastSavedProgress: { episode: '1', seconds: -1 }, + lastSavedProgress: { episode: "1", seconds: -1 }, episodeGrid: null, episodeList: null, previewPopover: null, @@ -98,7 +98,7 @@ interface RequiredPlayerElements { const findElement = ( container: HTMLElement, selector: string, - elementType: new () => T + elementType: new () => T, ): T | null => { const element = container.querySelector(selector); if (element instanceof elementType) return element; @@ -106,12 +106,12 @@ const findElement = ( }; const requiredPlayerElements = (container: HTMLElement): RequiredPlayerElements | null => { - const video = findElement(container, 'video', HTMLVideoElement); - const progress = findElement(container, '[data-progress]', HTMLElement); - const scrubber = findElement(container, '[data-scrubber]', HTMLElement); - const buffered = findElement(container, '[data-buffered]', HTMLElement); - const timeDisplay = findElement(container, '[data-time]', HTMLElement); - const durationDisplay = findElement(container, '[data-duration]', HTMLElement); + const video = findElement(container, "video", HTMLVideoElement); + const progress = findElement(container, "[data-progress]", HTMLElement); + const scrubber = findElement(container, "[data-scrubber]", HTMLElement); + const buffered = findElement(container, "[data-buffered]", HTMLElement); + const timeDisplay = findElement(container, "[data-time]", HTMLElement); + const durationDisplay = findElement(container, "[data-duration]", HTMLElement); if (!video || !progress || !scrubber || !buffered || !timeDisplay || !durationDisplay) { return null; @@ -136,43 +136,43 @@ export const initState = (c: HTMLElement): boolean => { state.buffered = elements.buffered; state.timeDisplay = elements.timeDisplay; state.durationDisplay = elements.durationDisplay; - state.previewPopover = q(c, '[data-preview-popover]'); - state.previewTime = q(c, '[data-preview-time]'); - state.videoOverlay = q(c, '[data-video-overlay]'); + state.previewPopover = q(c, "[data-preview-popover]"); + state.previewTime = q(c, "[data-preview-time]"); + state.videoOverlay = q(c, "[data-video-overlay]"); // data attributes from server - state.malID = Number.parseInt(dataset(c, 'malId'), 10); - state.currentEpisode = dataset(c, 'currentEpisode') || '1'; - state.totalEpisodes = Number.parseInt(dataset(c, 'totalEpisodes'), 10); - state.streamURL = dataset(c, 'streamUrl') || '/watch/proxy/stream'; - state.initialStreamToken = dataset(c, 'streamToken') || ''; - state.startTimeSeconds = Number.parseFloat(dataset(c, 'startTimeSeconds') || '0') || 0; + state.malID = Number.parseInt(dataset(c, "malId"), 10); + state.currentEpisode = dataset(c, "currentEpisode") || "1"; + state.totalEpisodes = Number.parseInt(dataset(c, "totalEpisodes"), 10); + state.streamURL = dataset(c, "streamUrl") || "/watch/proxy/stream"; + state.initialStreamToken = dataset(c, "streamToken") || ""; + state.startTimeSeconds = Number.parseFloat(dataset(c, "startTimeSeconds") || "0") || 0; // from session: previous page set this when autoplay triggered - state.shouldAutoPlay = sessionStorage.getItem('mal:autoplay-next') === 'true'; - sessionStorage.removeItem('mal:autoplay-next'); + state.shouldAutoPlay = sessionStorage.getItem("mal:autoplay-next") === "true"; + sessionStorage.removeItem("mal:autoplay-next"); // global elements (not inside player container) - state.episodeGrid = qs('[data-episode-grid]'); - state.episodeList = qs('[data-episode-list]'); + state.episodeGrid = qs("[data-episode-grid]"); + state.episodeList = qs("[data-episode-list]"); const safeJsonUnknown = (raw: string | undefined): unknown => { try { - return JSON.parse(raw ?? ''); + return JSON.parse(raw ?? ""); } catch { return null; } }; const isRecord = (v: unknown): v is Record => - typeof v === 'object' && v !== null && !Array.isArray(v); + typeof v === "object" && v !== null && !Array.isArray(v); const isStringArray = (v: unknown): v is string[] => - Array.isArray(v) && v.every(item => typeof item === 'string'); + Array.isArray(v) && v.every((item) => typeof item === "string"); const isSubtitleItemArray = (v: unknown): v is { lang: string; token: string }[] => Array.isArray(v) && v.every( - item => isRecord(item) && typeof item.lang === 'string' && typeof item.token === 'string' + (item) => isRecord(item) && typeof item.lang === "string" && typeof item.token === "string", ); const parseModeSources = (v: unknown): Record => { @@ -180,7 +180,7 @@ export const initState = (c: HTMLElement): boolean => { const out: Record = {}; for (const [key, value] of Object.entries(v)) { if (!isRecord(value)) continue; - if (typeof value.token !== 'string' || value.token === '') continue; + if (typeof value.token !== "string" || value.token === "") continue; if (!isSubtitleItemArray(value.subtitles)) continue; const qualities = value.qualities; out[key] = { @@ -199,10 +199,10 @@ export const initState = (c: HTMLElement): boolean => { const out: SkipSegment[] = []; for (const item of v) { if (!isRecord(item)) continue; - const type = typeof item.type === 'string' ? item.type : ''; - const start = typeof item.start === 'number' ? item.start : Number(item.start); - const end = typeof item.end === 'number' ? item.end : Number(item.end); - const source = typeof item.source === 'string' ? item.source : undefined; + const type = typeof item.type === "string" ? item.type : ""; + const start = typeof item.start === "number" ? item.start : Number(item.start); + const end = typeof item.end === "number" ? item.end : Number(item.end); + const source = typeof item.source === "string" ? item.source : undefined; if (!type || !Number.isFinite(start) || !Number.isFinite(end)) continue; out.push({ type, start, end, source }); } @@ -210,25 +210,25 @@ export const initState = (c: HTMLElement): boolean => { }; // mode sources = { sub: { token, subtitles, qualities }, dub: { ... } } - state.modeSources = parseModeSources(safeJsonUnknown(dataset(c, 'modeSources'))); - state.availableModes = parseAvailableModes(safeJsonUnknown(dataset(c, 'availableModes'))); + state.modeSources = parseModeSources(safeJsonUnknown(dataset(c, "modeSources"))); + state.availableModes = parseAvailableModes(safeJsonUnknown(dataset(c, "availableModes"))); // resolve initial mode: localStorage > backend default > first available > 'dub' - const backendInitialMode = dataset(c, 'initialMode') || 'dub'; - state.modeSwitchedFrom = dataset(c, 'modeSwitchedFrom') || ''; - const storedMode = safeLocalStorage.getItem('player-audio-mode'); + const backendInitialMode = dataset(c, "initialMode") || "dub"; + state.modeSwitchedFrom = dataset(c, "modeSwitchedFrom") || ""; + const storedMode = safeLocalStorage.getItem("player-audio-mode"); const initialMode = storedMode && state.availableModes.includes(storedMode) ? storedMode : backendInitialMode; - const fallbackMode = Object.keys(state.modeSources).find(m => state.modeSources[m]?.token); + const fallbackMode = Object.keys(state.modeSources).find((m) => state.modeSources[m]?.token); state.currentMode = state.modeSources[initialMode]?.token ? initialMode - : (fallbackMode ?? state.availableModes[0] ?? 'dub'); + : (fallbackMode ?? state.availableModes[0] ?? "dub"); // parse skip segments from data attribute - const segments = parseSegments(safeJsonUnknown(dataset(c, 'segments'))); + const segments = parseSegments(safeJsonUnknown(dataset(c, "segments"))); state.parsedSegments = segments - .map(s => ({ ...s, start: Number(s.start) || 0, end: Number(s.end) || 0 })) - .filter(s => s.end > s.start); + .map((s) => ({ ...s, start: Number(s.start) || 0, end: Number(s.end) || 0 })) + .filter((s) => s.end > s.start); return true; };