chore: format player controls

This commit is contained in:
2026-05-28 11:29:52 +02:00
committed by Milas Holsting
parent ea63544998
commit 5a11343a19

View File

@@ -1,23 +1,23 @@
import { state } from './state'; import { state } from "./state";
import { saveProgress } from './progress'; import { saveProgress } from "./progress";
import { safeLocalStorage } from './storage'; import { safeLocalStorage } from "./storage";
export const formatTime = (seconds: number): string => { export const formatTime = (seconds: number): string => {
if (!Number.isFinite(seconds) || seconds < 0) return '00:00'; if (!Number.isFinite(seconds) || seconds < 0) return "00:00";
const mins = Math.floor(seconds / 60); const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60); const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
}; };
/** /**
* Shows the controls overlay and schedules auto-hide after 2s if playing. * Shows the controls overlay and schedules auto-hide after 2s if playing.
*/ */
export const showControls = (): void => { export const showControls = (): void => {
state.container.classList.add('show-controls'); state.container.classList.add("show-controls");
window.clearTimeout(state.playerControlsTimeout); window.clearTimeout(state.playerControlsTimeout);
state.playerControlsTimeout = window.setTimeout(() => { state.playerControlsTimeout = window.setTimeout(() => {
if (!state.isScrubbing && !state.video.paused) { if (!state.isScrubbing && !state.video.paused) {
state.container.classList.remove('show-controls'); state.container.classList.remove("show-controls");
} }
}, 2000); }, 2000);
}; };
@@ -27,7 +27,7 @@ export const seekBy = (delta: number): void => {
if (state.video.duration <= 0) return; if (state.video.duration <= 0) return;
state.video.currentTime = Math.max( state.video.currentTime = Math.max(
0, 0,
Math.min(state.video.duration, state.video.currentTime + delta) Math.min(state.video.duration, state.video.currentTime + delta),
); );
showControls(); showControls();
}; };
@@ -73,13 +73,13 @@ export const syncVolumeUI = (): void => {
const value = state.video.muted ? 0 : Math.round(state.video.volume * 100); const value = state.video.muted ? 0 : Math.round(state.video.volume * 100);
if (volumeRange) { if (volumeRange) {
volumeRange.value = String(value); volumeRange.value = String(value);
volumeRange.style.setProperty('--volume-percent', `${value}%`); volumeRange.style.setProperty("--volume-percent", `${value}%`);
} }
if (volumeUnderline) volumeUnderline.style.height = `${value}%`; if (volumeUnderline) volumeUnderline.style.height = `${value}%`;
updateMuteIcons(state.video.muted || state.video.volume === 0); updateMuteIcons(state.video.muted || state.video.volume === 0);
}; };
const VOLUME_STORAGE_KEY = 'player-volume'; const VOLUME_STORAGE_KEY = "player-volume";
const parseStoredVolume = (raw: string | null): number | null => { const parseStoredVolume = (raw: string | null): number | null => {
if (!raw) return null; if (!raw) return null;
@@ -132,35 +132,35 @@ const getControls = (): Controls => {
if (controlsCache) return controlsCache; if (controlsCache) return controlsCache;
const c = state.container; const c = state.container;
controlsCache = { controlsCache = {
playPause: c.querySelector('[data-play-pause]'), playPause: c.querySelector("[data-play-pause]"),
muteBtn: c.querySelector('[data-mute]'), muteBtn: c.querySelector("[data-mute]"),
volumePanel: c.querySelector('[data-volume-panel]'), volumePanel: c.querySelector("[data-volume-panel]"),
volumeRange: c.querySelector('[data-volume-range]'), volumeRange: c.querySelector("[data-volume-range]"),
volumeUnderline: c.querySelector('[data-volume-underline]'), volumeUnderline: c.querySelector("[data-volume-underline]"),
backwardBtn: c.querySelector('[data-backward]'), backwardBtn: c.querySelector("[data-backward]"),
forwardBtn: c.querySelector('[data-forward]'), forwardBtn: c.querySelector("[data-forward]"),
fullscreenBtn: c.querySelector('[data-fullscreen]'), fullscreenBtn: c.querySelector("[data-fullscreen]"),
iconPlay: c.querySelector('[data-icon-play]'), iconPlay: c.querySelector("[data-icon-play]"),
iconPause: c.querySelector('[data-icon-pause]'), iconPause: c.querySelector("[data-icon-pause]"),
iconVolume: c.querySelector('[data-icon-volume]'), iconVolume: c.querySelector("[data-icon-volume]"),
iconMuted: c.querySelector('[data-icon-muted]'), iconMuted: c.querySelector("[data-icon-muted]"),
skipSegmentBtn: c.querySelector('[data-skip]'), skipSegmentBtn: c.querySelector("[data-skip]"),
subtitleText: c.querySelector('[data-subtitle-text]'), subtitleText: c.querySelector("[data-subtitle-text]"),
autoplayBtn: document.querySelector('[data-autoplay]'), autoplayBtn: document.querySelector("[data-autoplay]"),
}; };
return controlsCache; return controlsCache;
}; };
const updatePlayPauseIcons = (isPlaying: boolean): void => { const updatePlayPauseIcons = (isPlaying: boolean): void => {
const { iconPlay, iconPause } = getControls(); const { iconPlay, iconPause } = getControls();
iconPlay?.classList.toggle('hidden', isPlaying); iconPlay?.classList.toggle("hidden", isPlaying);
iconPause?.classList.toggle('hidden', !isPlaying); iconPause?.classList.toggle("hidden", !isPlaying);
}; };
const updateMuteIcons = (isMuted: boolean): void => { const updateMuteIcons = (isMuted: boolean): void => {
const { iconVolume, iconMuted } = getControls(); const { iconVolume, iconMuted } = getControls();
iconVolume?.classList.toggle('hidden', isMuted); iconVolume?.classList.toggle("hidden", isMuted);
iconMuted?.classList.toggle('hidden', !isMuted); iconMuted?.classList.toggle("hidden", !isMuted);
}; };
/** /**
@@ -182,69 +182,69 @@ export const setupControls = (): void => {
} = getControls(); } = getControls();
// play/pause on button and video click // play/pause on button and video click
playPause?.addEventListener('click', () => { playPause?.addEventListener("click", () => {
togglePlayPause(); togglePlayPause();
showControls(); showControls();
}); });
state.video.addEventListener('click', () => { state.video.addEventListener("click", () => {
togglePlayPause(); togglePlayPause();
showControls(); showControls();
}); });
muteBtn?.addEventListener('click', () => { muteBtn?.addEventListener("click", () => {
toggleMute(); toggleMute();
showControls(); showControls();
}); });
// volume slider // volume slider
volumeRange?.addEventListener('input', () => { volumeRange?.addEventListener("input", () => {
const value = Number(volumeRange.value) / 100; const value = Number(volumeRange.value) / 100;
setVolume(value); setVolume(value);
showControls(); showControls();
}); });
// dragging class for visual feedback // dragging class for visual feedback
volumeRange?.addEventListener('pointerdown', () => volumePanel?.classList.add('is-dragging')); volumeRange?.addEventListener("pointerdown", () => volumePanel?.classList.add("is-dragging"));
window.addEventListener('pointerup', () => volumePanel?.classList.remove('is-dragging')); window.addEventListener("pointerup", () => volumePanel?.classList.remove("is-dragging"));
backwardBtn?.addEventListener('click', () => seekBy(-10)); backwardBtn?.addEventListener("click", () => seekBy(-10));
forwardBtn?.addEventListener('click', () => seekBy(10)); forwardBtn?.addEventListener("click", () => seekBy(10));
fullscreenBtn?.addEventListener('click', () => { fullscreenBtn?.addEventListener("click", () => {
toggleFullscreen(); toggleFullscreen();
showControls(); showControls();
}); });
// skip intro/outro button // skip intro/outro button
skipSegmentBtn?.addEventListener('click', () => { skipSegmentBtn?.addEventListener("click", () => {
if (!state.activeSkipSegment) return; if (!state.activeSkipSegment) return;
state.video.currentTime = state.activeSkipSegment.end + 0.01; state.video.currentTime = state.activeSkipSegment.end + 0.01;
showControls(); showControls();
}); });
// fullscreen change handler // fullscreen change handler
document.addEventListener('fullscreenchange', () => { document.addEventListener("fullscreenchange", () => {
state.isFullscreen = !!document.fullscreenElement; state.isFullscreen = !!document.fullscreenElement;
state.container.classList.toggle('fullscreen', state.isFullscreen); state.container.classList.toggle("fullscreen", state.isFullscreen);
if (state.isFullscreen) showControls(); if (state.isFullscreen) showControls();
}); });
// icon sync on state changes // icon sync on state changes
state.video.addEventListener('play', () => { state.video.addEventListener("play", () => {
updatePlayPauseIcons(true); updatePlayPauseIcons(true);
showControls(); showControls();
}); });
state.video.addEventListener('pause', () => { state.video.addEventListener("pause", () => {
updatePlayPauseIcons(false); updatePlayPauseIcons(false);
showControls(); showControls();
void saveProgress(); void saveProgress();
}); });
state.video.addEventListener('volumechange', () => { state.video.addEventListener("volumechange", () => {
syncVolumeUI(); syncVolumeUI();
schedulePersistVolume(); schedulePersistVolume();
}); });
// mouse move in container shows controls // mouse move in container shows controls
state.container.addEventListener('mousemove', showControls); state.container.addEventListener("mousemove", showControls);
// initial sync — check actual video state since inline script may have started playback // initial sync — check actual video state since inline script may have started playback
updatePlayPauseIcons(!state.video.paused); updatePlayPauseIcons(!state.video.paused);