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.
// 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">