mirror of
https://github.com/p-stream/p-stream.git
synced 2026-05-14 14:11:29 +00:00
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.
This commit is contained in:
parent
4ced25623f
commit
c460c15966
5 changed files with 67 additions and 15 deletions
|
|
@ -841,11 +841,11 @@
|
||||||
"errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",
|
"errorNetwork": "Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",
|
||||||
"errorNotSupported": "The media or media provider object is not supported."
|
"errorNotSupported": "The media or media provider object is not supported."
|
||||||
},
|
},
|
||||||
"autoResumeText": "There was an error trying to play the media 😖. Automatically trying the next source...",
|
"autoResumeText": "There was an error trying to play the media 😖. Automatically trying the other sources...",
|
||||||
"copyDebugInfo": "Copy debug info",
|
"copyDebugInfo": "Copy debug info",
|
||||||
"debugInfo": "Check console for more details.",
|
"debugInfo": "Check console for more details.",
|
||||||
"homeButton": "Go home",
|
"homeButton": "Go home",
|
||||||
"resumeButton": "Try next source",
|
"resumeButton": "Try next sources",
|
||||||
"text": "There was an error trying to play the media 😖. Please try again or try a different source!",
|
"text": "There was an error trying to play the media 😖. Please try again or try a different source!",
|
||||||
"title": "Failed to play video!"
|
"title": "Failed to play video!"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from "@/backend/helpers/providerApi";
|
} from "@/backend/helpers/providerApi";
|
||||||
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
|
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
|
||||||
import { getProviders } from "@/backend/providers/providers";
|
import { getProviders } from "@/backend/providers/providers";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
import { usePreferencesStore } from "@/stores/preferences";
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
|
|
||||||
export interface ScrapingItems {
|
export interface ScrapingItems {
|
||||||
|
|
@ -170,10 +171,15 @@ export function useScrape() {
|
||||||
async (media: ScrapeMedia, startFromSourceId?: string) => {
|
async (media: ScrapeMedia, startFromSourceId?: string) => {
|
||||||
const providerInstance = getProviders();
|
const providerInstance = getProviders();
|
||||||
const allSources = providerInstance.listSources();
|
const allSources = providerInstance.listSources();
|
||||||
|
const failedSources = usePlayerStore.getState().failedSources;
|
||||||
|
|
||||||
// Start with all available sources (filtered by disabled ones)
|
// Start with all available sources (filtered by disabled and failed ones)
|
||||||
let baseSourceOrder = allSources
|
let baseSourceOrder = allSources
|
||||||
.filter((source) => !disabledSources.includes(source.id))
|
.filter(
|
||||||
|
(source) =>
|
||||||
|
!disabledSources.includes(source.id) &&
|
||||||
|
!failedSources.includes(source.id),
|
||||||
|
)
|
||||||
.map((source) => source.id);
|
.map((source) => source.id);
|
||||||
|
|
||||||
// Apply custom source ordering if enabled
|
// Apply custom source ordering if enabled
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,10 @@ export function RealPlayerView() {
|
||||||
let startAt: number | undefined;
|
let startAt: number | undefined;
|
||||||
if (startAtParam) startAt = parseTimestamp(startAtParam) ?? undefined;
|
if (startAtParam) startAt = parseTimestamp(startAtParam) ?? undefined;
|
||||||
|
|
||||||
|
// Clear failed sources when we successfully find a working source
|
||||||
|
const playerStore = usePlayerStore.getState();
|
||||||
|
playerStore.clearFailedSources();
|
||||||
|
|
||||||
playMedia(
|
playMedia(
|
||||||
convertRunoutputToSource(out),
|
convertRunoutputToSource(out),
|
||||||
convertProviderCaption(out.stream.captions),
|
convertProviderCaption(out.stream.captions),
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export interface PlaybackErrorPartProps {
|
||||||
export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const playbackError = usePlayerStore((s) => s.interface.error);
|
const playbackError = usePlayerStore((s) => s.interface.error);
|
||||||
|
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
||||||
|
const addFailedSource = usePlayerStore((s) => s.addFailedSource);
|
||||||
const modal = useModal("error");
|
const modal = useModal("error");
|
||||||
const settingsRouter = useOverlayRouter("settings");
|
const settingsRouter = useOverlayRouter("settings");
|
||||||
const hasOpenedSettings = useRef(false);
|
const hasOpenedSettings = useRef(false);
|
||||||
|
|
@ -33,21 +35,24 @@ export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
||||||
(s) => s.enableAutoResumeOnPlaybackError,
|
(s) => s.enableAutoResumeOnPlaybackError,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Automatically open the settings overlay when a playback error occurs (unless auto-resume is enabled)
|
// Mark the failed source and handle UI when a playback error occurs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (playbackError && currentSourceId) {
|
||||||
playbackError &&
|
// Mark this source as failed
|
||||||
!hasOpenedSettings.current &&
|
addFailedSource(currentSourceId);
|
||||||
!enableAutoResumeOnPlaybackError
|
|
||||||
) {
|
if (!hasOpenedSettings.current && !enableAutoResumeOnPlaybackError) {
|
||||||
hasOpenedSettings.current = true;
|
hasOpenedSettings.current = true;
|
||||||
// Reset the last successful source when a playback error occurs
|
// Reset the last successful source when a playback error occurs
|
||||||
setLastSuccessfulSource(null);
|
setLastSuccessfulSource(null);
|
||||||
settingsRouter.open();
|
settingsRouter.open();
|
||||||
settingsRouter.navigate("/source");
|
settingsRouter.navigate("/source");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
playbackError,
|
playbackError,
|
||||||
|
currentSourceId,
|
||||||
|
addFailedSource,
|
||||||
settingsRouter,
|
settingsRouter,
|
||||||
setLastSuccessfulSource,
|
setLastSuccessfulSource,
|
||||||
enableAutoResumeOnPlaybackError,
|
enableAutoResumeOnPlaybackError,
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ export interface SourceSlice {
|
||||||
asTrack: boolean;
|
asTrack: boolean;
|
||||||
};
|
};
|
||||||
meta: PlayerMeta | null;
|
meta: PlayerMeta | null;
|
||||||
|
failedSources: string[];
|
||||||
setStatus(status: PlayerStatus): void;
|
setStatus(status: PlayerStatus): void;
|
||||||
setSource(
|
setSource(
|
||||||
stream: SourceSliceSource,
|
stream: SourceSliceSource,
|
||||||
|
|
@ -104,6 +105,9 @@ export interface SourceSlice {
|
||||||
redisplaySource(startAt: number): void;
|
redisplaySource(startAt: number): void;
|
||||||
setCaptionAsTrack(asTrack: boolean): void;
|
setCaptionAsTrack(asTrack: boolean): void;
|
||||||
addExternalSubtitles(): Promise<void>;
|
addExternalSubtitles(): Promise<void>;
|
||||||
|
addFailedSource(sourceId: string): void;
|
||||||
|
clearFailedSources(): void;
|
||||||
|
reset(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia {
|
export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia {
|
||||||
|
|
@ -141,6 +145,7 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||||
currentAudioTrack: null,
|
currentAudioTrack: null,
|
||||||
status: playerStatus.IDLE,
|
status: playerStatus.IDLE,
|
||||||
meta: null,
|
meta: null,
|
||||||
|
failedSources: [],
|
||||||
caption: {
|
caption: {
|
||||||
selected: null,
|
selected: null,
|
||||||
asTrack: false,
|
asTrack: false,
|
||||||
|
|
@ -256,6 +261,38 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||||
s.caption.asTrack = asTrack;
|
s.caption.asTrack = asTrack;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addFailedSource(sourceId: string) {
|
||||||
|
set((s) => {
|
||||||
|
if (!s.failedSources.includes(sourceId)) {
|
||||||
|
s.failedSources = [...s.failedSources, sourceId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearFailedSources() {
|
||||||
|
set((s) => {
|
||||||
|
s.failedSources = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
set((s) => {
|
||||||
|
s.source = null;
|
||||||
|
s.sourceId = null;
|
||||||
|
s.embedId = null;
|
||||||
|
s.qualities = [];
|
||||||
|
s.audioTracks = [];
|
||||||
|
s.captionList = [];
|
||||||
|
s.isLoadingExternalSubtitles = false;
|
||||||
|
s.currentQuality = null;
|
||||||
|
s.currentAudioTrack = null;
|
||||||
|
s.status = playerStatus.IDLE;
|
||||||
|
s.meta = null;
|
||||||
|
s.failedSources = [];
|
||||||
|
s.caption = {
|
||||||
|
selected: null,
|
||||||
|
asTrack: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
async addExternalSubtitles() {
|
async addExternalSubtitles() {
|
||||||
const store = get();
|
const store = get();
|
||||||
if (!store.meta) return;
|
if (!store.meta) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue