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.
|
// 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">
|
||||||
|
|||||||
Reference in New Issue
Block a user