mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-30 19:18:48 +00:00
track failed sources and disable multiple
This commit is contained in:
parent
4f6e56fd22
commit
58594ae4b5
3 changed files with 117 additions and 23 deletions
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "@/backend/helpers/providerApi";
|
||||
import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers";
|
||||
import { getProviders } from "@/backend/providers/providers";
|
||||
import { getMediaKey } from "@/stores/player/slices/source";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
|
||||
|
|
@ -172,8 +173,26 @@ export function useScrape() {
|
|||
const providerInstance = getProviders();
|
||||
const allSources = providerInstance.listSources();
|
||||
const playerState = usePlayerStore.getState();
|
||||
const failedSources = playerState.failedSources;
|
||||
const failedEmbeds = playerState.failedEmbeds;
|
||||
|
||||
// Get media-specific failed sources/embeds
|
||||
// Try to get media key from player state first, fallback to deriving from ScrapeMedia
|
||||
let mediaKey = getMediaKey(playerState.meta);
|
||||
if (!mediaKey) {
|
||||
// Derive media key from ScrapeMedia if meta is not set yet
|
||||
if (media.type === "movie") {
|
||||
mediaKey = `movie-${media.tmdbId}`;
|
||||
} else if (media.type === "show" && media.season && media.episode) {
|
||||
mediaKey = `show-${media.tmdbId}-${media.season.tmdbId}-${media.episode.tmdbId}`;
|
||||
} else if (media.type === "show") {
|
||||
mediaKey = `show-${media.tmdbId}`;
|
||||
}
|
||||
}
|
||||
const failedSources = mediaKey
|
||||
? playerState.failedSourcesPerMedia[mediaKey] || []
|
||||
: [];
|
||||
const failedEmbeds = mediaKey
|
||||
? playerState.failedEmbedsPerMedia[mediaKey] || {}
|
||||
: {};
|
||||
|
||||
// Start with all available sources (filtered by disabled and failed ones)
|
||||
let baseSourceOrder = allSources
|
||||
|
|
@ -222,7 +241,7 @@ export function useScrape() {
|
|||
}
|
||||
}
|
||||
|
||||
// Collect all failed embed IDs across all sources
|
||||
// Collect all failed embed IDs across all sources for current media
|
||||
const allFailedEmbedIds = Object.values(failedEmbeds).flat();
|
||||
|
||||
// Filter out disabled and failed embeds from the embed order
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Paragraph } from "@/components/text/Paragraph";
|
|||
import { Title } from "@/components/text/Title";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
|
||||
import { getMediaKey } from "@/stores/player/slices/source";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
|
||||
|
|
@ -24,9 +25,10 @@ export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
|||
const playbackError = usePlayerStore((s) => s.interface.error);
|
||||
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
||||
const currentEmbedId = usePlayerStore((s) => s.embedId);
|
||||
const meta = usePlayerStore((s) => s.meta);
|
||||
const failedEmbedsPerMedia = usePlayerStore((s) => s.failedEmbedsPerMedia);
|
||||
const addFailedSource = usePlayerStore((s) => s.addFailedSource);
|
||||
const addFailedEmbed = usePlayerStore((s) => s.addFailedEmbed);
|
||||
const failedEmbeds = usePlayerStore((s) => s.failedEmbeds);
|
||||
const modal = useModal("error");
|
||||
const settingsRouter = useOverlayRouter("settings");
|
||||
const hasOpenedSettings = useRef(false);
|
||||
|
|
@ -54,6 +56,11 @@ export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
|||
|
||||
// Check if all embeds for this source have now failed
|
||||
// If so, disable the entire source
|
||||
const mediaKey = getMediaKey(meta);
|
||||
const failedEmbeds =
|
||||
mediaKey && failedEmbedsPerMedia[mediaKey]
|
||||
? failedEmbedsPerMedia[mediaKey]
|
||||
: {};
|
||||
const failedEmbedsForSource = failedEmbeds[currentSourceId] || [];
|
||||
// For now, we'll assume if we have 2+ failed embeds for a source, disable it
|
||||
// This is a simple heuristic - we could make it more sophisticated
|
||||
|
|
@ -78,7 +85,8 @@ export function PlaybackErrorPart(props: PlaybackErrorPartProps) {
|
|||
playbackError,
|
||||
currentSourceId,
|
||||
currentEmbedId,
|
||||
failedEmbeds,
|
||||
meta,
|
||||
failedEmbedsPerMedia,
|
||||
addFailedSource,
|
||||
addFailedEmbed,
|
||||
settingsRouter,
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ export interface SourceSlice {
|
|||
asTrack: boolean;
|
||||
};
|
||||
meta: PlayerMeta | null;
|
||||
failedSources: string[];
|
||||
failedEmbeds: Record<string, string[]>; // sourceId -> array of failed embedIds
|
||||
failedSourcesPerMedia: Record<string, string[]>; // mediaKey -> array of failed sourceIds
|
||||
failedEmbedsPerMedia: Record<string, Record<string, string[]>>; // mediaKey -> sourceId -> array of failed embedIds
|
||||
setStatus(status: PlayerStatus): void;
|
||||
setSource(
|
||||
stream: SourceSliceSource,
|
||||
|
|
@ -108,11 +108,32 @@ export interface SourceSlice {
|
|||
addExternalSubtitles(): Promise<void>;
|
||||
addFailedSource(sourceId: string): void;
|
||||
addFailedEmbed(sourceId: string, embedId: string): void;
|
||||
clearFailedSources(): void;
|
||||
clearFailedEmbeds(): void;
|
||||
clearFailedSources(mediaKey?: string): void;
|
||||
clearFailedEmbeds(mediaKey?: string): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique media key for tracking failed sources per media.
|
||||
* For movies: `${type}-${tmdbId}`
|
||||
* For shows: `${type}-${tmdbId}-${season.tmdbId}-${episode.tmdbId}`
|
||||
*/
|
||||
export function getMediaKey(meta: PlayerMeta | null): string | null {
|
||||
if (!meta) return null;
|
||||
|
||||
if (meta.type === "movie") {
|
||||
return `${meta.type}-${meta.tmdbId}`;
|
||||
}
|
||||
|
||||
// For shows, include season and episode IDs for per-episode tracking
|
||||
if (meta.type === "show" && meta.season && meta.episode) {
|
||||
return `${meta.type}-${meta.tmdbId}-${meta.season.tmdbId}-${meta.episode.tmdbId}`;
|
||||
}
|
||||
|
||||
// Fallback if show data is incomplete
|
||||
return `${meta.type}-${meta.tmdbId}`;
|
||||
}
|
||||
|
||||
export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia {
|
||||
if (meta.type === "show") {
|
||||
if (!meta.episode || !meta.season) throw new Error("missing show data");
|
||||
|
|
@ -148,8 +169,8 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
currentAudioTrack: null,
|
||||
status: playerStatus.IDLE,
|
||||
meta: null,
|
||||
failedSources: [],
|
||||
failedEmbeds: {},
|
||||
failedSourcesPerMedia: {},
|
||||
failedEmbedsPerMedia: {},
|
||||
caption: {
|
||||
selected: null,
|
||||
asTrack: false,
|
||||
|
|
@ -172,12 +193,26 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
});
|
||||
},
|
||||
setMeta(meta, newStatus) {
|
||||
const store = get();
|
||||
const oldMediaKey = getMediaKey(store.meta);
|
||||
const newMediaKey = getMediaKey(meta);
|
||||
|
||||
set((s) => {
|
||||
s.meta = meta;
|
||||
s.embedId = null;
|
||||
s.sourceId = null;
|
||||
s.interface.hideNextEpisodeBtn = false;
|
||||
if (newStatus) s.status = newStatus;
|
||||
|
||||
// Clear failed sources/embeds for the new media when media changes
|
||||
// Since we're doing per-episode tracking, we clear whenever media key changes
|
||||
// Only clear if we're actually switching to different media (not just setting meta for the first time)
|
||||
if (newMediaKey && oldMediaKey && oldMediaKey !== newMediaKey) {
|
||||
// Clear failed sources/embeds for the new media (if any exist from previous session)
|
||||
// This ensures a fresh start for each media/episode
|
||||
delete s.failedSourcesPerMedia[newMediaKey];
|
||||
delete s.failedEmbedsPerMedia[newMediaKey];
|
||||
}
|
||||
});
|
||||
},
|
||||
setCaption(caption) {
|
||||
|
|
@ -267,30 +302,62 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
});
|
||||
},
|
||||
addFailedSource(sourceId: string) {
|
||||
const store = get();
|
||||
const mediaKey = getMediaKey(store.meta);
|
||||
if (!mediaKey) return; // Skip tracking if no media is set
|
||||
|
||||
set((s) => {
|
||||
if (!s.failedSources.includes(sourceId)) {
|
||||
s.failedSources = [...s.failedSources, sourceId];
|
||||
if (!s.failedSourcesPerMedia[mediaKey]) {
|
||||
s.failedSourcesPerMedia[mediaKey] = [];
|
||||
}
|
||||
if (!s.failedSourcesPerMedia[mediaKey].includes(sourceId)) {
|
||||
s.failedSourcesPerMedia[mediaKey] = [
|
||||
...s.failedSourcesPerMedia[mediaKey],
|
||||
sourceId,
|
||||
];
|
||||
}
|
||||
});
|
||||
},
|
||||
addFailedEmbed(sourceId: string, embedId: string) {
|
||||
const store = get();
|
||||
const mediaKey = getMediaKey(store.meta);
|
||||
if (!mediaKey) return; // Skip tracking if no media is set
|
||||
|
||||
set((s) => {
|
||||
if (!s.failedEmbeds[sourceId]) {
|
||||
s.failedEmbeds[sourceId] = [];
|
||||
if (!s.failedEmbedsPerMedia[mediaKey]) {
|
||||
s.failedEmbedsPerMedia[mediaKey] = {};
|
||||
}
|
||||
if (!s.failedEmbeds[sourceId].includes(embedId)) {
|
||||
s.failedEmbeds[sourceId] = [...s.failedEmbeds[sourceId], embedId];
|
||||
if (!s.failedEmbedsPerMedia[mediaKey][sourceId]) {
|
||||
s.failedEmbedsPerMedia[mediaKey][sourceId] = [];
|
||||
}
|
||||
if (!s.failedEmbedsPerMedia[mediaKey][sourceId].includes(embedId)) {
|
||||
s.failedEmbedsPerMedia[mediaKey][sourceId] = [
|
||||
...s.failedEmbedsPerMedia[mediaKey][sourceId],
|
||||
embedId,
|
||||
];
|
||||
}
|
||||
});
|
||||
},
|
||||
clearFailedSources() {
|
||||
clearFailedSources(mediaKey?: string) {
|
||||
set((s) => {
|
||||
s.failedSources = [];
|
||||
if (mediaKey) {
|
||||
// Clear for specific media
|
||||
delete s.failedSourcesPerMedia[mediaKey];
|
||||
} else {
|
||||
// Clear all
|
||||
s.failedSourcesPerMedia = {};
|
||||
}
|
||||
});
|
||||
},
|
||||
clearFailedEmbeds() {
|
||||
clearFailedEmbeds(mediaKey?: string) {
|
||||
set((s) => {
|
||||
s.failedEmbeds = {};
|
||||
if (mediaKey) {
|
||||
// Clear for specific media
|
||||
delete s.failedEmbedsPerMedia[mediaKey];
|
||||
} else {
|
||||
// Clear all
|
||||
s.failedEmbedsPerMedia = {};
|
||||
}
|
||||
});
|
||||
},
|
||||
reset() {
|
||||
|
|
@ -306,8 +373,8 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
s.currentAudioTrack = null;
|
||||
s.status = playerStatus.IDLE;
|
||||
s.meta = null;
|
||||
s.failedSources = [];
|
||||
s.failedEmbeds = {};
|
||||
s.failedSourcesPerMedia = {};
|
||||
s.failedEmbedsPerMedia = {};
|
||||
s.caption = {
|
||||
selected: null,
|
||||
asTrack: false,
|
||||
|
|
|
|||
Loading…
Reference in a new issue