fix(ui): stop audio restarting by removing conditional <audio> mount and normalised src comparison
Two bugs caused the start/stop loop:
1. The <audio> element was wrapped in {#if audioStore.audioUrl}, so whenever
any reactive state changed (e.g. currentTime ticking), Svelte could destroy
and recreate the element, firing onpause and then the URL effect restarting
playback.
2. Comparing audioEl.src !== url is unreliable — browsers normalise the src
property to a full absolute URL, causing false mismatches every tick.
Fix: make <audio> always present in the DOM (display:none), and track the
loaded URL in a plain local variable (loadedUrl) instead of reading audioEl.src.
This commit is contained in:
@@ -17,14 +17,20 @@
|
||||
});
|
||||
|
||||
// When audioUrl changes, load the new source.
|
||||
// Use a local variable to track which URL is currently loaded so we never
|
||||
// compare against audioEl.src (browsers normalise it, causing false mismatches).
|
||||
let loadedUrl = '';
|
||||
$effect(() => {
|
||||
if (!audioEl) return;
|
||||
const url = audioStore.audioUrl;
|
||||
if (url && audioEl.src !== url) {
|
||||
if (url && url !== loadedUrl) {
|
||||
loadedUrl = url;
|
||||
audioEl.src = url;
|
||||
audioEl.load();
|
||||
audioEl.playbackRate = audioStore.speed;
|
||||
audioEl.play().catch(() => {});
|
||||
} else if (!url) {
|
||||
loadedUrl = '';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,19 +113,19 @@
|
||||
<title>libnovel</title>
|
||||
</svelte:head>
|
||||
|
||||
<!-- Hidden persistent audio element — lives outside {#key} so it never unmounts -->
|
||||
{#if audioStore.audioUrl}
|
||||
<audio
|
||||
bind:this={audioEl}
|
||||
bind:currentTime={audioStore.currentTime}
|
||||
bind:duration={audioStore.duration}
|
||||
onplay={() => (audioStore.isPlaying = true)}
|
||||
onpause={() => (audioStore.isPlaying = false)}
|
||||
onended={() => (audioStore.isPlaying = false)}
|
||||
preload="metadata"
|
||||
style="display:none"
|
||||
></audio>
|
||||
{/if}
|
||||
<!-- Persistent audio element — always in the DOM, never conditionally unmounted.
|
||||
Conditional rendering ({#if}) would destroy/recreate it when reactive state
|
||||
changes (e.g. currentTime ticking), triggering onpause and restarting audio. -->
|
||||
<audio
|
||||
bind:this={audioEl}
|
||||
bind:currentTime={audioStore.currentTime}
|
||||
bind:duration={audioStore.duration}
|
||||
onplay={() => (audioStore.isPlaying = true)}
|
||||
onpause={() => (audioStore.isPlaying = false)}
|
||||
onended={() => (audioStore.isPlaying = false)}
|
||||
preload="metadata"
|
||||
style="display:none"
|
||||
></audio>
|
||||
|
||||
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active}>
|
||||
<header class="border-b border-zinc-700 bg-zinc-900 sticky top-0 z-50">
|
||||
|
||||
Reference in New Issue
Block a user