pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Track and skip failed sources during playback

Introduces a mechanism to track failed sources in the player store. When a playback error occurs, the current source is marked as failed and subsequent attempts will skip these sources. Failed sources are cleared when a working source is found. UI text is updated to reflect the new behavior.

Pas c460c159 4ced2562

+67 -15
+2 -2
src/assets/locales/en.json
··· 841 841 "errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.", 842 842 "errorNotSupported": "The media or media provider object is not supported." 843 843 }, 844 - "autoResumeText": "There was an error trying to play the media 😖. Automatically trying the next source...", 844 + "autoResumeText": "There was an error trying to play the media 😖. Automatically trying the other sources...", 845 845 "copyDebugInfo": "Copy debug info", 846 846 "debugInfo": "Check console for more details.", 847 847 "homeButton": "Go home", 848 - "resumeButton": "Try next source", 848 + "resumeButton": "Try next sources", 849 849 "text": "There was an error trying to play the media 😖. Please try again or try a different source!", 850 850 "title": "Failed to play video!" 851 851 },
+8 -2
src/hooks/useProviderScrape.tsx
··· 10 10 } from "@/backend/helpers/providerApi"; 11 11 import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers"; 12 12 import { getProviders } from "@/backend/providers/providers"; 13 + import { usePlayerStore } from "@/stores/player/store"; 13 14 import { usePreferencesStore } from "@/stores/preferences"; 14 15 15 16 export interface ScrapingItems { ··· 170 171 async (media: ScrapeMedia, startFromSourceId?: string) => { 171 172 const providerInstance = getProviders(); 172 173 const allSources = providerInstance.listSources(); 174 + const failedSources = usePlayerStore.getState().failedSources; 173 175 174 - // Start with all available sources (filtered by disabled ones) 176 + // Start with all available sources (filtered by disabled and failed ones) 175 177 let baseSourceOrder = allSources 176 - .filter((source) => !disabledSources.includes(source.id)) 178 + .filter( 179 + (source) => 180 + !disabledSources.includes(source.id) && 181 + !failedSources.includes(source.id), 182 + ) 177 183 .map((source) => source.id); 178 184 179 185 // Apply custom source ordering if enabled
+4
src/pages/PlayerView.tsx
··· 182 182 let startAt: number | undefined; 183 183 if (startAtParam) startAt = parseTimestamp(startAtParam) ?? undefined; 184 184 185 + // Clear failed sources when we successfully find a working source 186 + const playerStore = usePlayerStore.getState(); 187 + playerStore.clearFailedSources(); 188 + 185 189 playMedia( 186 190 convertRunoutputToSource(out), 187 191 convertProviderCaption(out.stream.captions),
+16 -11
src/pages/parts/player/PlaybackErrorPart.tsx
··· 22 22 export function PlaybackErrorPart(props: PlaybackErrorPartProps) { 23 23 const { t } = useTranslation(); 24 24 const playbackError = usePlayerStore((s) => s.interface.error); 25 + const currentSourceId = usePlayerStore((s) => s.sourceId); 26 + const addFailedSource = usePlayerStore((s) => s.addFailedSource); 25 27 const modal = useModal("error"); 26 28 const settingsRouter = useOverlayRouter("settings"); 27 29 const hasOpenedSettings = useRef(false); ··· 33 35 (s) => s.enableAutoResumeOnPlaybackError, 34 36 ); 35 37 36 - // Automatically open the settings overlay when a playback error occurs (unless auto-resume is enabled) 38 + // Mark the failed source and handle UI when a playback error occurs 37 39 useEffect(() => { 38 - if ( 39 - playbackError && 40 - !hasOpenedSettings.current && 41 - !enableAutoResumeOnPlaybackError 42 - ) { 43 - hasOpenedSettings.current = true; 44 - // Reset the last successful source when a playback error occurs 45 - setLastSuccessfulSource(null); 46 - settingsRouter.open(); 47 - settingsRouter.navigate("/source"); 40 + if (playbackError && currentSourceId) { 41 + // Mark this source as failed 42 + addFailedSource(currentSourceId); 43 + 44 + if (!hasOpenedSettings.current && !enableAutoResumeOnPlaybackError) { 45 + hasOpenedSettings.current = true; 46 + // Reset the last successful source when a playback error occurs 47 + setLastSuccessfulSource(null); 48 + settingsRouter.open(); 49 + settingsRouter.navigate("/source"); 50 + } 48 51 } 49 52 }, [ 50 53 playbackError, 54 + currentSourceId, 55 + addFailedSource, 51 56 settingsRouter, 52 57 setLastSuccessfulSource, 53 58 enableAutoResumeOnPlaybackError,
+37
src/stores/player/slices/source.ts
··· 89 89 asTrack: boolean; 90 90 }; 91 91 meta: PlayerMeta | null; 92 + failedSources: string[]; 92 93 setStatus(status: PlayerStatus): void; 93 94 setSource( 94 95 stream: SourceSliceSource, ··· 104 105 redisplaySource(startAt: number): void; 105 106 setCaptionAsTrack(asTrack: boolean): void; 106 107 addExternalSubtitles(): Promise<void>; 108 + addFailedSource(sourceId: string): void; 109 + clearFailedSources(): void; 110 + reset(): void; 107 111 } 108 112 109 113 export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia { ··· 141 145 currentAudioTrack: null, 142 146 status: playerStatus.IDLE, 143 147 meta: null, 148 + failedSources: [], 144 149 caption: { 145 150 selected: null, 146 151 asTrack: false, ··· 254 259 setCaptionAsTrack(asTrack: boolean) { 255 260 set((s) => { 256 261 s.caption.asTrack = asTrack; 262 + }); 263 + }, 264 + addFailedSource(sourceId: string) { 265 + set((s) => { 266 + if (!s.failedSources.includes(sourceId)) { 267 + s.failedSources = [...s.failedSources, sourceId]; 268 + } 269 + }); 270 + }, 271 + clearFailedSources() { 272 + set((s) => { 273 + s.failedSources = []; 274 + }); 275 + }, 276 + reset() { 277 + set((s) => { 278 + s.source = null; 279 + s.sourceId = null; 280 + s.embedId = null; 281 + s.qualities = []; 282 + s.audioTracks = []; 283 + s.captionList = []; 284 + s.isLoadingExternalSubtitles = false; 285 + s.currentQuality = null; 286 + s.currentAudioTrack = null; 287 + s.status = playerStatus.IDLE; 288 + s.meta = null; 289 + s.failedSources = []; 290 + s.caption = { 291 + selected: null, 292 + asTrack: false, 293 + }; 257 294 }); 258 295 }, 259 296 async addExternalSubtitles() {