From 4c2c54229b67595df5462c492a027bb7248ae147 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 28 May 2026 11:28:46 +0200 Subject: [PATCH] chore: format player progress quality keyboard --- static/player/keyboard.ts | 32 ++++++++++++++++---------------- static/player/progress.ts | 26 +++++++++++++------------- static/player/quality.ts | 36 ++++++++++++++++++------------------ 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/static/player/keyboard.ts b/static/player/keyboard.ts index ac1f5c1..04dda74 100644 --- a/static/player/keyboard.ts +++ b/static/player/keyboard.ts @@ -1,5 +1,5 @@ -import { state } from './state'; -import { absoluteTimeFromRatio, getBounds } from './timeline'; +import { state } from "./state"; +import { absoluteTimeFromRatio, getBounds } from "./timeline"; import { showControls, toggleMute, @@ -7,54 +7,54 @@ import { toggleFullscreen, seekBy, setVolume, -} from './controls'; -import { saveProgress } from './progress'; +} from "./controls"; +import { saveProgress } from "./progress"; /** * Sets up keyboard shortcuts for player control. * Ignores input/textarea to allow typing. */ export const setupKeyboard = (): void => { - document.addEventListener('keydown', e => { + document.addEventListener("keydown", (e) => { const target = e.target as HTMLElement; // ignore when typing in form fields - if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) + if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) return; switch (e.code) { - case 'Space': - case 'KeyK': + case "Space": + case "KeyK": e.preventDefault(); togglePlayPause(); showControls(); void saveProgress(); break; - case 'ArrowLeft': - case 'KeyJ': + case "ArrowLeft": + case "KeyJ": e.preventDefault(); seekBy(-10); break; - case 'ArrowRight': - case 'KeyL': + case "ArrowRight": + case "KeyL": e.preventDefault(); seekBy(10); break; - case 'ArrowUp': + case "ArrowUp": e.preventDefault(); setVolume(state.video.volume + 0.05); showControls(); break; - case 'ArrowDown': + case "ArrowDown": e.preventDefault(); setVolume(state.video.volume - 0.05); showControls(); break; - case 'KeyM': + case "KeyM": e.preventDefault(); toggleMute(); showControls(); break; - case 'KeyF': + case "KeyF": e.preventDefault(); toggleFullscreen(); showControls(); diff --git a/static/player/progress.ts b/static/player/progress.ts index 5b993e3..bf80f6d 100644 --- a/static/player/progress.ts +++ b/static/player/progress.ts @@ -1,5 +1,5 @@ -import { state } from './state'; -import { displayTimeFromAbsolute } from './timeline'; +import { state } from "./state"; +import { displayTimeFromAbsolute } from "./timeline"; // builds JSON payload for progress API const buildPayload = (episode: number, seconds: number) => @@ -12,7 +12,7 @@ const buildPayload = (episode: number, seconds: number) => // sends progress via beacon (survives page unload) const sendBeacon = (payload: string) => { if (!navigator.sendBeacon) return false; - navigator.sendBeacon('/api/watch-progress', new Blob([payload], { type: 'application/json' })); + navigator.sendBeacon("/api/watch-progress", new Blob([payload], { type: "application/json" })); return true; }; @@ -41,9 +41,9 @@ export const saveProgress = async (): Promise => { const payload = buildPayload(episode, safeTime); try { - const res = await fetch('/api/watch-progress', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + const res = await fetch("/api/watch-progress", { + method: "POST", + headers: { "Content-Type": "application/json" }, body: payload, }); if (!res.ok) return; @@ -87,9 +87,9 @@ export const markEpisodeTransition = (episodeNumber: number): void => { const payload = buildPayload(episodeNumber, 0); // beacon falls back to fetch with keepalive if (!sendBeacon(payload)) { - fetch('/api/watch-progress', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + fetch("/api/watch-progress", { + method: "POST", + headers: { "Content-Type": "application/json" }, keepalive: true, body: payload, }).catch(() => undefined); @@ -101,25 +101,25 @@ export const markEpisodeTransition = (episodeNumber: number): void => { */ export const setupProgress = (): void => { // periodic save during playback - state.video.addEventListener('timeupdate', () => { + state.video.addEventListener("timeupdate", () => { scheduleProgressSave(); }); // immediate save on pause - state.video.addEventListener('pause', () => { + state.video.addEventListener("pause", () => { window.clearTimeout(state.progressSaveTimer); state.progressSaveTimer = undefined; saveProgress(); }); // save after scrubbing - window.addEventListener('mouseup', () => { + window.addEventListener("mouseup", () => { state.isScrubbing = false; saveProgress(); }); // save on page close - window.addEventListener('beforeunload', () => { + window.addEventListener("beforeunload", () => { if (state.transitionEpisode !== null || state.completionSent || !state.malID) { return; } diff --git a/static/player/quality.ts b/static/player/quality.ts index 2acc633..63bf4dc 100644 --- a/static/player/quality.ts +++ b/static/player/quality.ts @@ -1,13 +1,13 @@ -import { state } from './state'; -import { displayTimeFromAbsolute } from './timeline'; -import { safeLocalStorage } from './storage'; +import { state } from "./state"; +import { displayTimeFromAbsolute } from "./timeline"; +import { safeLocalStorage } from "./storage"; // same as mode.ts - could be extracted to shared util 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; }; @@ -30,7 +30,7 @@ const loadVideo = (url: string): void => { export const switchQuality = (quality: string): void => { const url = streamUrlForMode(state.currentMode, quality); if (!url) return; - safeLocalStorage.setItem('mal:preferred-quality', quality); + safeLocalStorage.setItem("mal:preferred-quality", quality); loadVideo(url); }; @@ -39,38 +39,38 @@ export const switchQuality = (quality: string): void => { * Shows/hides dropdown based on availability. */ export const updateQualityOptions = (): void => { - const select = state.container.querySelector('[data-quality-select]') as HTMLSelectElement | null; + const select = state.container.querySelector("[data-quality-select]") as HTMLSelectElement | null; if (!select) return; const qualities = state.modeSources[state.currentMode]?.qualities ?? []; - select.innerHTML = ''; + select.innerHTML = ""; - const best = document.createElement('option'); - best.value = 'best'; - best.textContent = 'Auto / Best'; + const best = document.createElement("option"); + best.value = "best"; + best.textContent = "Auto / Best"; select.appendChild(best); - qualities.forEach(q => { - const opt = document.createElement('option'); + qualities.forEach((q) => { + const opt = document.createElement("option"); opt.value = q; opt.textContent = q; select.appendChild(opt); }); // restore saved preference - const preferred = safeLocalStorage.getItem('mal:preferred-quality') || 'best'; - select.value = qualities.includes(preferred) ? preferred : 'best'; + const preferred = safeLocalStorage.getItem("mal:preferred-quality") || "best"; + select.value = qualities.includes(preferred) ? preferred : "best"; // hide if no quality options const wrapper = select.parentElement; - wrapper?.classList.toggle('hidden', qualities.length === 0); + wrapper?.classList.toggle("hidden", qualities.length === 0); }; /** * Binds quality select change handler. */ export const setupQuality = (): void => { - const select = state.container.querySelector('[data-quality-select]') as HTMLSelectElement | null; - select?.addEventListener('change', e => { + const select = state.container.querySelector("[data-quality-select]") as HTMLSelectElement | null; + select?.addEventListener("change", (e) => { switchQuality((e.target as HTMLSelectElement).value); }); };