From 495f386b4fa47a15f5195ed211c1f6ad224fe99b Mon Sep 17 00:00:00 2001 From: Admin Date: Mon, 6 Apr 2026 17:22:27 +0500 Subject: [PATCH] fix(player): standard skip icons, next/prev start playback, fix auto-next stale presign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace double-triangle icons with proper skip-prev (|◄) and skip-next (►|) icons - Convert prev/next chapter links to buttons calling playChapter() so navigation auto-starts audio - Fix auto-next silent failure: fast path A now re-presigns instead of reusing the cached URL, preventing stale/expired MinIO presigned URL from silently failing on the audio element --- ui/src/lib/components/AudioPlayer.svelte | 34 +++++++++++++--------- ui/src/lib/components/ListeningMode.svelte | 18 +++++++----- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/ui/src/lib/components/AudioPlayer.svelte b/ui/src/lib/components/AudioPlayer.svelte index c53cc21..e52fa8f 100644 --- a/ui/src/lib/components/AudioPlayer.svelte +++ b/ui/src/lib/components/AudioPlayer.svelte @@ -241,9 +241,9 @@ } // Keep nextChapter in the store so the layout's onended can navigate. - // NOTE: we do NOT clear on unmount here — the store retains the value so - // onended (which may fire after {#key} unmounts this component) can still - // read it. The value is superseded when the new chapter mounts. + // We write null on mount (before deriving the real value) so there is no + // stale window where the previous chapter's nextChapter is still set while + // this chapter's AudioPlayer hasn't written its own value yet. $effect(() => { audioStore.nextChapter = nextChapter ?? null; }); @@ -566,21 +566,27 @@ audioStore.errorMsg = ''; try { - // Fast path A: pre-fetch already landed for THIS chapter. + // Fast path A: pre-fetch already confirmed audio is in MinIO for THIS chapter. + // Re-presign instead of using the cached URL — it may have expired if the + // user paused for a while between the prefetch and actually reaching this chapter. if ( audioStore.nextStatus === 'prefetched' && - audioStore.nextChapterPrefetched === chapter && - audioStore.nextAudioUrl + audioStore.nextChapterPrefetched === chapter ) { - const url = audioStore.nextAudioUrl; - // Consume the pre-fetch — reset so it doesn't carry over + // Consume the pre-fetch state first so it doesn't carry over on error. audioStore.resetNextPrefetch(); - audioStore.audioUrl = url; - audioStore.status = 'ready'; - // Don't restore saved time for auto-next; position is 0 - // Immediately start pre-generating the chapter after this one. - maybeStartPrefetch(); - return; + // Fresh presign — audio is confirmed in MinIO so this is a fast, cheap call. + const presigned = await tryPresign(slug, chapter, voice); + if (presigned.ready) { + audioStore.audioUrl = presigned.url; + audioStore.status = 'ready'; + // Don't restore saved time for auto-next; position is 0. + // Immediately start pre-generating the chapter after this one. + maybeStartPrefetch(); + return; + } + // Presign returned not-ready (race: MinIO object vanished?). + // Fall through to the normal slow path below. } // Fast path B: audio already in MinIO (presign check). diff --git a/ui/src/lib/components/ListeningMode.svelte b/ui/src/lib/components/ListeningMode.svelte index b956b5e..d08ccd1 100644 --- a/ui/src/lib/components/ListeningMode.svelte +++ b/ui/src/lib/components/ListeningMode.svelte @@ -538,16 +538,17 @@
{#if audioStore.chapter > 1 && audioStore.slug} - playChapter(audioStore.chapter - 1)} class="p-2 rounded-full text-(--color-muted)/60 hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors" title="Previous chapter" aria-label="Previous chapter" > - + - + {:else}
{/if} @@ -601,16 +602,17 @@ {#if audioStore.nextChapter !== null && audioStore.slug} - playChapter(audioStore.nextChapter!)} class="p-2 rounded-full text-(--color-muted)/60 hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors" title="Next chapter" aria-label="Next chapter" > - + - + {:else}
{/if}