feat(frontend): drive seek bar with rAF for smooth playback progress
This commit is contained in:
@@ -19,11 +19,11 @@
|
|||||||
class="group relative h-1.5 flex-1 cursor-pointer rounded-full bg-[var(--mf-track)] transition-[height] hover:h-2"
|
class="group relative h-1.5 flex-1 cursor-pointer rounded-full bg-[var(--mf-track)] transition-[height] hover:h-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-y-0 left-0 rounded-full bg-[var(--mf-accent)] transition-[width] duration-75 ease-linear group-hover:brightness-110"
|
class="absolute inset-y-0 left-0 rounded-full bg-[var(--mf-accent)] group-hover:brightness-110"
|
||||||
style="width:{player.progressPercent}%"
|
style="width:{player.progressPercent}%"
|
||||||
></div>
|
></div>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 shadow-[0_1px_4px_rgba(0,0,0,0.5)] transition-[left,opacity] duration-75 ease-linear group-hover:opacity-100"
|
class="pointer-events-none absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 shadow-[0_1px_4px_rgba(0,0,0,0.5)] transition-opacity duration-75 group-hover:opacity-100"
|
||||||
style="left:{player.progressPercent}%"
|
style="left:{player.progressPercent}%"
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class PlayerState {
|
|||||||
#audio: HTMLAudioElement | null = null;
|
#audio: HTMLAudioElement | null = null;
|
||||||
#loadToken = 0;
|
#loadToken = 0;
|
||||||
#accentCache = new Map<string, Accent>();
|
#accentCache = new Map<string, Accent>();
|
||||||
|
#rafId: number | null = null;
|
||||||
|
|
||||||
current = $derived(this.tracks.find((t) => t.id === this.currentId) ?? EMPTY);
|
current = $derived(this.tracks.find((t) => t.id === this.currentId) ?? EMPTY);
|
||||||
accent = $derived(this.accentColor ?? accentForHue(this.current.hue));
|
accent = $derived(this.accentColor ?? accentForHue(this.current.hue));
|
||||||
@@ -79,15 +80,22 @@ class PlayerState {
|
|||||||
const audio = new Audio();
|
const audio = new Audio();
|
||||||
audio.preload = 'auto';
|
audio.preload = 'auto';
|
||||||
audio.volume = this.volume / 100;
|
audio.volume = this.volume / 100;
|
||||||
audio.addEventListener('timeupdate', () => {
|
|
||||||
this.progress = audio.currentTime;
|
|
||||||
});
|
|
||||||
audio.addEventListener('durationchange', () => this.#syncDuration(audio.duration));
|
audio.addEventListener('durationchange', () => this.#syncDuration(audio.duration));
|
||||||
audio.addEventListener('loadedmetadata', () => this.#syncDuration(audio.duration));
|
audio.addEventListener('loadedmetadata', () => this.#syncDuration(audio.duration));
|
||||||
audio.addEventListener('play', () => (this.playing = true));
|
audio.addEventListener('play', () => {
|
||||||
audio.addEventListener('pause', () => (this.playing = false));
|
this.playing = true;
|
||||||
audio.addEventListener('ended', () => this.next());
|
this.#startProgressLoop();
|
||||||
|
});
|
||||||
|
audio.addEventListener('pause', () => {
|
||||||
|
this.playing = false;
|
||||||
|
this.#stopProgressLoop();
|
||||||
|
});
|
||||||
|
audio.addEventListener('ended', () => {
|
||||||
|
this.#stopProgressLoop();
|
||||||
|
this.next();
|
||||||
|
});
|
||||||
audio.addEventListener('error', () => {
|
audio.addEventListener('error', () => {
|
||||||
|
this.#stopProgressLoop();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
this.error = 'No se pudo reproducir la pista.';
|
this.error = 'No se pudo reproducir la pista.';
|
||||||
@@ -96,6 +104,24 @@ class PlayerState {
|
|||||||
return audio;
|
return audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tickProgress = () => {
|
||||||
|
const audio = this.#audio;
|
||||||
|
if (!audio) return;
|
||||||
|
this.progress = audio.currentTime;
|
||||||
|
this.#rafId = requestAnimationFrame(this.#tickProgress);
|
||||||
|
};
|
||||||
|
|
||||||
|
#startProgressLoop() {
|
||||||
|
if (this.#rafId !== null) return;
|
||||||
|
this.#rafId = requestAnimationFrame(this.#tickProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
#stopProgressLoop() {
|
||||||
|
if (this.#rafId === null) return;
|
||||||
|
cancelAnimationFrame(this.#rafId);
|
||||||
|
this.#rafId = null;
|
||||||
|
}
|
||||||
|
|
||||||
#syncDuration(value: number) {
|
#syncDuration(value: number) {
|
||||||
if (!Number.isFinite(value) || value <= 0) return;
|
if (!Number.isFinite(value) || value <= 0) return;
|
||||||
const id = this.currentId;
|
const id = this.currentId;
|
||||||
|
|||||||
Reference in New Issue
Block a user