From 8d2981313890b303a1d05ba518bbc8075772a642 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sat, 11 Jul 2026 15:17:36 +0200 Subject: [PATCH 1/2] fix(player): show an error state when media fails to load isLoading was cleared only by loadedmetadata, and the media elements had no error handler - a blocked, dead, or unreachable stream URL left the player spinning forever. Both media elements now handle error by replacing the spinner with a visible error overlay; the state resets on the next episode switch. Found while gating all outbound requests (#290): a refused stream URL looked identical to a slow one. --- src/ui/PodcastView/EpisodePlayer.svelte | 41 ++++++++++++++++++++++++ src/ui/PodcastView/EpisodePlayer.test.ts | 33 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/ui/PodcastView/EpisodePlayer.svelte b/src/ui/PodcastView/EpisodePlayer.svelte index f7d5793..a99c1d2 100644 --- a/src/ui/PodcastView/EpisodePlayer.svelte +++ b/src/ui/PodcastView/EpisodePlayer.svelte @@ -62,6 +62,7 @@ let isHoveringArtwork: boolean = false; let isLoading: boolean = true; + let loadError: string | null = null; let playerVolume: number = 1; let mediaElement: HTMLMediaElement | null = null; // The currentEpisode subscription fires synchronously on subscribe with the @@ -177,10 +178,19 @@ function onMetadataLoaded() { isLoading = false; + loadError = null; restorePlaybackTime(); } + // A blocked, dead, or unreachable media URL never fires loadedmetadata, so + // without this the loading overlay spins forever (found while gating all + // outbound requests: a refused stream URL looked identical to a slow one). + function onMediaError() { + isLoading = false; + loadError = "Could not load this episode's media."; + } + function restorePlaybackTime() { const playedEps = $playedEpisodes; const currentEp = $currentEpisode; @@ -339,6 +349,7 @@ // the src swap persist the new episode under its key with the old/zero // time and clobber its saved resume position (issue #33). isLoading = true; + loadError = null; lastPositionSaveMs = Number.NEGATIVE_INFINITY; segmentStopTimeWithoutProgressSave = null; @@ -504,6 +515,7 @@ bind:volume={playerVolume} on:ended={onEpisodeEnded} on:loadedmetadata={onMetadataLoaded} + on:error={onMediaError} on:timeupdate={onTimeUpdate} on:pause={onPause} on:play|preventDefault @@ -516,6 +528,11 @@
+ {:else if loadError} + {:else}