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 @@