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:
Admin
2026-03-04 15:53:49 +05:00
parent 034e670795
commit 901b18ee13

View File

@@ -17,14 +17,20 @@
}); });
// When audioUrl changes, load the new source. // 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(() => { $effect(() => {
if (!audioEl) return; if (!audioEl) return;
const url = audioStore.audioUrl; const url = audioStore.audioUrl;
if (url && audioEl.src !== url) { if (url && url !== loadedUrl) {
loadedUrl = url;
audioEl.src = url; audioEl.src = url;
audioEl.load(); audioEl.load();
audioEl.playbackRate = audioStore.speed; audioEl.playbackRate = audioStore.speed;
audioEl.play().catch(() => {}); audioEl.play().catch(() => {});
} else if (!url) {
loadedUrl = '';
} }
}); });
@@ -107,19 +113,19 @@
<title>libnovel</title> <title>libnovel</title>
</svelte:head> </svelte:head>
<!-- Hidden persistent audio element — lives outside {#key} so it never unmounts --> <!-- Persistent audio element — always in the DOM, never conditionally unmounted.
{#if audioStore.audioUrl} Conditional rendering ({#if}) would destroy/recreate it when reactive state
<audio changes (e.g. currentTime ticking), triggering onpause and restarting audio. -->
bind:this={audioEl} <audio
bind:currentTime={audioStore.currentTime} bind:this={audioEl}
bind:duration={audioStore.duration} bind:currentTime={audioStore.currentTime}
onplay={() => (audioStore.isPlaying = true)} bind:duration={audioStore.duration}
onpause={() => (audioStore.isPlaying = false)} onplay={() => (audioStore.isPlaying = true)}
onended={() => (audioStore.isPlaying = false)} onpause={() => (audioStore.isPlaying = false)}
preload="metadata" onended={() => (audioStore.isPlaying = false)}
style="display:none" preload="metadata"
></audio> style="display:none"
{/if} ></audio>
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active}> <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"> <header class="border-b border-zinc-700 bg-zinc-900 sticky top-0 z-50">