mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-28 16:53:29 +00:00
add lastSuccessfulSource feature
to sort the last successful source to the top
This commit is contained in:
parent
627d66eced
commit
d575d71108
13 changed files with 251 additions and 8 deletions
|
|
@ -1107,7 +1107,10 @@
|
||||||
"embedOrderEnableLabel": "Custom embed order",
|
"embedOrderEnableLabel": "Custom embed order",
|
||||||
"manualSource": "Manual source selection",
|
"manualSource": "Manual source selection",
|
||||||
"manualSourceDescription": "Require picking a source before scraping. Disables automatic source selection and opens the source picker when starting playback.",
|
"manualSourceDescription": "Require picking a source before scraping. Disables automatic source selection and opens the source picker when starting playback.",
|
||||||
"manualSourceLabel": "Manual source selection"
|
"manualSourceLabel": "Manual source selection",
|
||||||
|
"lastSuccessfulSource": "Last successful source",
|
||||||
|
"lastSuccessfulSourceDescription": "Automatically prioritize the source that successfully provided content for the previous episode. This helps ensure continuity when watching series.",
|
||||||
|
"lastSuccessfulSourceEnableLabel": "Enable last successful source"
|
||||||
},
|
},
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ export interface SettingsInput {
|
||||||
forceCompactEpisodeView?: boolean;
|
forceCompactEpisodeView?: boolean;
|
||||||
sourceOrder?: string[] | null;
|
sourceOrder?: string[] | null;
|
||||||
enableSourceOrder?: boolean;
|
enableSourceOrder?: boolean;
|
||||||
|
lastSuccessfulSource?: string | null;
|
||||||
|
enableLastSuccessfulSource?: boolean;
|
||||||
disabledSources?: string[] | null;
|
disabledSources?: string[] | null;
|
||||||
embedOrder?: string[] | null;
|
embedOrder?: string[] | null;
|
||||||
enableEmbedOrder?: boolean;
|
enableEmbedOrder?: boolean;
|
||||||
|
|
@ -52,6 +54,8 @@ export interface SettingsResponse {
|
||||||
forceCompactEpisodeView?: boolean;
|
forceCompactEpisodeView?: boolean;
|
||||||
sourceOrder?: string[] | null;
|
sourceOrder?: string[] | null;
|
||||||
enableSourceOrder?: boolean;
|
enableSourceOrder?: boolean;
|
||||||
|
lastSuccessfulSource?: string | null;
|
||||||
|
enableLastSuccessfulSource?: boolean;
|
||||||
disabledSources?: string[] | null;
|
disabledSources?: string[] | null;
|
||||||
embedOrder?: string[] | null;
|
embedOrder?: string[] | null;
|
||||||
enableEmbedOrder?: boolean;
|
enableEmbedOrder?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -106,12 +106,16 @@ export function NextEpisodeButton(props: {
|
||||||
const time = usePlayerStore((s) => s.progress.time);
|
const time = usePlayerStore((s) => s.progress.time);
|
||||||
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
|
const enableAutoplay = usePreferencesStore((s) => s.enableAutoplay);
|
||||||
const enableSkipCredits = usePreferencesStore((s) => s.enableSkipCredits);
|
const enableSkipCredits = usePreferencesStore((s) => s.enableSkipCredits);
|
||||||
|
const setLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setLastSuccessfulSource,
|
||||||
|
);
|
||||||
const showingState = shouldShowNextEpisodeButton(time, duration);
|
const showingState = shouldShowNextEpisodeButton(time, duration);
|
||||||
const status = usePlayerStore((s) => s.status);
|
const status = usePlayerStore((s) => s.status);
|
||||||
const setShouldStartFromBeginning = usePlayerStore(
|
const setShouldStartFromBeginning = usePlayerStore(
|
||||||
(s) => s.setShouldStartFromBeginning,
|
(s) => s.setShouldStartFromBeginning,
|
||||||
);
|
);
|
||||||
const updateItem = useProgressStore((s) => s.updateItem);
|
const updateItem = useProgressStore((s) => s.updateItem);
|
||||||
|
const sourceId = usePlayerStore((s) => s.sourceId);
|
||||||
|
|
||||||
const isLastEpisode =
|
const isLastEpisode =
|
||||||
!meta?.episode?.number || !meta?.episodes?.at(-1)?.number
|
!meta?.episode?.number || !meta?.episodes?.at(-1)?.number
|
||||||
|
|
@ -147,6 +151,12 @@ export function NextEpisodeButton(props: {
|
||||||
|
|
||||||
const loadNextEpisode = useCallback(() => {
|
const loadNextEpisode = useCallback(() => {
|
||||||
if (!meta || !nextEp) return;
|
if (!meta || !nextEp) return;
|
||||||
|
|
||||||
|
// Store the current source as the last successful source
|
||||||
|
if (sourceId) {
|
||||||
|
setLastSuccessfulSource(sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
const metaCopy = { ...meta };
|
const metaCopy = { ...meta };
|
||||||
metaCopy.episode = nextEp;
|
metaCopy.episode = nextEp;
|
||||||
metaCopy.season =
|
metaCopy.season =
|
||||||
|
|
@ -173,6 +183,8 @@ export function NextEpisodeButton(props: {
|
||||||
updateItem,
|
updateItem,
|
||||||
isLastEpisode,
|
isLastEpisode,
|
||||||
nextSeason,
|
nextSeason,
|
||||||
|
sourceId,
|
||||||
|
setLastSuccessfulSource,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const startCurrentEpisodeFromBeginning = useCallback(() => {
|
const startCurrentEpisodeFromBeginning = useCallback(() => {
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,12 @@ export function SourceSelectionView({
|
||||||
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
||||||
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
||||||
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
||||||
|
const lastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const enableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.enableLastSuccessfulSource,
|
||||||
|
);
|
||||||
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
||||||
|
|
||||||
const sources = useMemo(() => {
|
const sources = useMemo(() => {
|
||||||
|
|
@ -154,13 +160,34 @@ export function SourceSelectionView({
|
||||||
.filter((v) => !disabledSources.includes(v.id));
|
.filter((v) => !disabledSources.includes(v.id));
|
||||||
|
|
||||||
if (!enableSourceOrder || preferredSourceOrder.length === 0) {
|
if (!enableSourceOrder || preferredSourceOrder.length === 0) {
|
||||||
|
// Even without custom source order, prioritize last successful source if enabled
|
||||||
|
if (enableLastSuccessfulSource && lastSuccessfulSource) {
|
||||||
|
const lastSourceIndex = allSources.findIndex(
|
||||||
|
(s) => s.id === lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
if (lastSourceIndex !== -1) {
|
||||||
|
const lastSource = allSources.splice(lastSourceIndex, 1)[0];
|
||||||
|
return [lastSource, ...allSources];
|
||||||
|
}
|
||||||
|
}
|
||||||
return allSources;
|
return allSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort sources according to preferred order
|
// Sort sources according to preferred order, but prioritize last successful source
|
||||||
const orderedSources = [];
|
const orderedSources = [];
|
||||||
const remainingSources = [...allSources];
|
const remainingSources = [...allSources];
|
||||||
|
|
||||||
|
// First, add the last successful source if it exists, is available, and the feature is enabled
|
||||||
|
if (enableLastSuccessfulSource && lastSuccessfulSource) {
|
||||||
|
const lastSourceIndex = remainingSources.findIndex(
|
||||||
|
(s) => s.id === lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
if (lastSourceIndex !== -1) {
|
||||||
|
orderedSources.push(remainingSources[lastSourceIndex]);
|
||||||
|
remainingSources.splice(lastSourceIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add sources in preferred order
|
// Add sources in preferred order
|
||||||
for (const sourceId of preferredSourceOrder) {
|
for (const sourceId of preferredSourceOrder) {
|
||||||
const sourceIndex = remainingSources.findIndex((s) => s.id === sourceId);
|
const sourceIndex = remainingSources.findIndex((s) => s.id === sourceId);
|
||||||
|
|
@ -174,7 +201,14 @@ export function SourceSelectionView({
|
||||||
orderedSources.push(...remainingSources);
|
orderedSources.push(...remainingSources);
|
||||||
|
|
||||||
return orderedSources;
|
return orderedSources;
|
||||||
}, [metaType, preferredSourceOrder, enableSourceOrder, disabledSources]);
|
}, [
|
||||||
|
metaType,
|
||||||
|
preferredSourceOrder,
|
||||||
|
enableSourceOrder,
|
||||||
|
disabledSources,
|
||||||
|
lastSuccessfulSource,
|
||||||
|
enableLastSuccessfulSource,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
|
||||||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||||
import { PlayerMeta } from "@/stores/player/slices/source";
|
import { PlayerMeta } from "@/stores/player/slices/source";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
import { useProgressStore } from "@/stores/progress";
|
import { useProgressStore } from "@/stores/progress";
|
||||||
|
|
||||||
interface SkipEpisodeButtonProps {
|
interface SkipEpisodeButtonProps {
|
||||||
|
|
@ -19,13 +20,19 @@ export function SkipEpisodeButton(props: SkipEpisodeButtonProps) {
|
||||||
(s) => s.setShouldStartFromBeginning,
|
(s) => s.setShouldStartFromBeginning,
|
||||||
);
|
);
|
||||||
const updateItem = useProgressStore((s) => s.updateItem);
|
const updateItem = useProgressStore((s) => s.updateItem);
|
||||||
|
const sourceId = usePlayerStore((s) => s.sourceId);
|
||||||
|
const setLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setLastSuccessfulSource,
|
||||||
|
);
|
||||||
const nextEp = meta?.episodes?.find(
|
const nextEp = meta?.episodes?.find(
|
||||||
(v) => v.number === (meta?.episode?.number ?? 0) + 1,
|
(v) => v.number === (meta?.episode?.number ?? 0) + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadNextEpisode = useCallback(() => {
|
const loadNextEpisode = useCallback(() => {
|
||||||
if (!meta || !nextEp) return;
|
if (!meta || !nextEp) return;
|
||||||
|
if (sourceId) {
|
||||||
|
setLastSuccessfulSource(sourceId);
|
||||||
|
}
|
||||||
const metaCopy = { ...meta };
|
const metaCopy = { ...meta };
|
||||||
metaCopy.episode = nextEp;
|
metaCopy.episode = nextEp;
|
||||||
setShouldStartFromBeginning(true);
|
setShouldStartFromBeginning(true);
|
||||||
|
|
@ -43,6 +50,8 @@ export function SkipEpisodeButton(props: SkipEpisodeButtonProps) {
|
||||||
props,
|
props,
|
||||||
setShouldStartFromBeginning,
|
setShouldStartFromBeginning,
|
||||||
updateItem,
|
updateItem,
|
||||||
|
sourceId,
|
||||||
|
setLastSuccessfulSource,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Don't show button if not in control, not a show, or no next episode
|
// Don't show button if not in control, not a show, or no next episode
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ export function useAuthData() {
|
||||||
const setEnableSourceOrder = usePreferencesStore(
|
const setEnableSourceOrder = usePreferencesStore(
|
||||||
(s) => s.setEnableSourceOrder,
|
(s) => s.setEnableSourceOrder,
|
||||||
);
|
);
|
||||||
|
const setLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setLastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const setEnableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setEnableLastSuccessfulSource,
|
||||||
|
);
|
||||||
const setDisabledSources = usePreferencesStore((s) => s.setDisabledSources);
|
const setDisabledSources = usePreferencesStore((s) => s.setDisabledSources);
|
||||||
const setEmbedOrder = usePreferencesStore((s) => s.setEmbedOrder);
|
const setEmbedOrder = usePreferencesStore((s) => s.setEmbedOrder);
|
||||||
const setEnableEmbedOrder = usePreferencesStore((s) => s.setEnableEmbedOrder);
|
const setEnableEmbedOrder = usePreferencesStore((s) => s.setEnableEmbedOrder);
|
||||||
|
|
@ -193,6 +199,14 @@ export function useAuthData() {
|
||||||
setEnableSourceOrder(settings.enableSourceOrder);
|
setEnableSourceOrder(settings.enableSourceOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.lastSuccessfulSource !== undefined) {
|
||||||
|
setLastSuccessfulSource(settings.lastSuccessfulSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.enableLastSuccessfulSource !== undefined) {
|
||||||
|
setEnableLastSuccessfulSource(settings.enableLastSuccessfulSource);
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.disabledSources !== undefined) {
|
if (settings.disabledSources !== undefined) {
|
||||||
setDisabledSources(settings.disabledSources ?? []);
|
setDisabledSources(settings.disabledSources ?? []);
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +279,8 @@ export function useAuthData() {
|
||||||
setForceCompactEpisodeView,
|
setForceCompactEpisodeView,
|
||||||
setSourceOrder,
|
setSourceOrder,
|
||||||
setEnableSourceOrder,
|
setEnableSourceOrder,
|
||||||
|
setLastSuccessfulSource,
|
||||||
|
setEnableLastSuccessfulSource,
|
||||||
setDisabledSources,
|
setDisabledSources,
|
||||||
setEmbedOrder,
|
setEmbedOrder,
|
||||||
setEnableEmbedOrder,
|
setEnableEmbedOrder,
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,12 @@ export function useScrape() {
|
||||||
|
|
||||||
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
||||||
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
||||||
|
const lastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const enableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.enableLastSuccessfulSource,
|
||||||
|
);
|
||||||
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
||||||
const preferredEmbedOrder = usePreferencesStore((s) => s.embedOrder);
|
const preferredEmbedOrder = usePreferencesStore((s) => s.embedOrder);
|
||||||
const enableEmbedOrder = usePreferencesStore((s) => s.enableEmbedOrder);
|
const enableEmbedOrder = usePreferencesStore((s) => s.enableEmbedOrder);
|
||||||
|
|
@ -162,11 +168,29 @@ export function useScrape() {
|
||||||
|
|
||||||
const startScraping = useCallback(
|
const startScraping = useCallback(
|
||||||
async (media: ScrapeMedia) => {
|
async (media: ScrapeMedia) => {
|
||||||
// Filter out disabled sources from the source order
|
// Create source order that prioritizes last successful source
|
||||||
const filteredSourceOrder = enableSourceOrder
|
let filteredSourceOrder = enableSourceOrder
|
||||||
? preferredSourceOrder.filter((id) => !disabledSources.includes(id))
|
? preferredSourceOrder.filter((id) => !disabledSources.includes(id))
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
// If we have a last successful source and the feature is enabled, prioritize it
|
||||||
|
if (enableLastSuccessfulSource && lastSuccessfulSource) {
|
||||||
|
// Get all available sources (either from custom order or default)
|
||||||
|
const availableSources = filteredSourceOrder || [];
|
||||||
|
|
||||||
|
// If the last successful source is not disabled and exists in available sources,
|
||||||
|
// move it to the front
|
||||||
|
if (
|
||||||
|
!disabledSources.includes(lastSuccessfulSource) &&
|
||||||
|
availableSources.includes(lastSuccessfulSource)
|
||||||
|
) {
|
||||||
|
filteredSourceOrder = [
|
||||||
|
lastSuccessfulSource,
|
||||||
|
...availableSources.filter((id) => id !== lastSuccessfulSource),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out disabled embeds from the embed order
|
// Filter out disabled embeds from the embed order
|
||||||
const filteredEmbedOrder = enableEmbedOrder
|
const filteredEmbedOrder = enableEmbedOrder
|
||||||
? preferredEmbedOrder.filter((id) => !disabledEmbeds.includes(id))
|
? preferredEmbedOrder.filter((id) => !disabledEmbeds.includes(id))
|
||||||
|
|
@ -223,6 +247,8 @@ export function useScrape() {
|
||||||
startScrape,
|
startScrape,
|
||||||
preferredSourceOrder,
|
preferredSourceOrder,
|
||||||
enableSourceOrder,
|
enableSourceOrder,
|
||||||
|
lastSuccessfulSource,
|
||||||
|
enableLastSuccessfulSource,
|
||||||
disabledSources,
|
disabledSources,
|
||||||
preferredEmbedOrder,
|
preferredEmbedOrder,
|
||||||
enableEmbedOrder,
|
enableEmbedOrder,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ export function useSettingsState(
|
||||||
enableDetailsModal: boolean,
|
enableDetailsModal: boolean,
|
||||||
sourceOrder: string[],
|
sourceOrder: string[],
|
||||||
enableSourceOrder: boolean,
|
enableSourceOrder: boolean,
|
||||||
|
lastSuccessfulSource: string | null,
|
||||||
|
enableLastSuccessfulSource: boolean,
|
||||||
disabledSources: string[],
|
disabledSources: string[],
|
||||||
embedOrder: string[],
|
embedOrder: string[],
|
||||||
enableEmbedOrder: boolean,
|
enableEmbedOrder: boolean,
|
||||||
|
|
@ -164,6 +166,18 @@ export function useSettingsState(
|
||||||
resetEnableSourceOrder,
|
resetEnableSourceOrder,
|
||||||
enableSourceOrderChanged,
|
enableSourceOrderChanged,
|
||||||
] = useDerived(enableSourceOrder);
|
] = useDerived(enableSourceOrder);
|
||||||
|
const [
|
||||||
|
lastSuccessfulSourceState,
|
||||||
|
setLastSuccessfulSourceState,
|
||||||
|
resetLastSuccessfulSource,
|
||||||
|
lastSuccessfulSourceChanged,
|
||||||
|
] = useDerived(lastSuccessfulSource);
|
||||||
|
const [
|
||||||
|
enableLastSuccessfulSourceState,
|
||||||
|
setEnableLastSuccessfulSourceState,
|
||||||
|
resetEnableLastSuccessfulSource,
|
||||||
|
enableLastSuccessfulSourceChanged,
|
||||||
|
] = useDerived(enableLastSuccessfulSource);
|
||||||
const [
|
const [
|
||||||
disabledSourcesState,
|
disabledSourcesState,
|
||||||
setDisabledSourcesState,
|
setDisabledSourcesState,
|
||||||
|
|
@ -259,6 +273,8 @@ export function useSettingsState(
|
||||||
resetEnableImageLogos();
|
resetEnableImageLogos();
|
||||||
resetSourceOrder();
|
resetSourceOrder();
|
||||||
resetEnableSourceOrder();
|
resetEnableSourceOrder();
|
||||||
|
resetLastSuccessfulSource();
|
||||||
|
resetEnableLastSuccessfulSource();
|
||||||
resetDisabledSources();
|
resetDisabledSources();
|
||||||
resetEmbedOrder();
|
resetEmbedOrder();
|
||||||
resetEnableEmbedOrder();
|
resetEnableEmbedOrder();
|
||||||
|
|
@ -293,6 +309,8 @@ export function useSettingsState(
|
||||||
enableImageLogosChanged ||
|
enableImageLogosChanged ||
|
||||||
sourceOrderChanged ||
|
sourceOrderChanged ||
|
||||||
enableSourceOrderChanged ||
|
enableSourceOrderChanged ||
|
||||||
|
lastSuccessfulSourceChanged ||
|
||||||
|
enableLastSuccessfulSourceChanged ||
|
||||||
disabledSourcesChanged ||
|
disabledSourcesChanged ||
|
||||||
embedOrderChanged ||
|
embedOrderChanged ||
|
||||||
enableEmbedOrderChanged ||
|
enableEmbedOrderChanged ||
|
||||||
|
|
@ -400,6 +418,16 @@ export function useSettingsState(
|
||||||
set: setEnableSourceOrderState,
|
set: setEnableSourceOrderState,
|
||||||
changed: enableSourceOrderChanged,
|
changed: enableSourceOrderChanged,
|
||||||
},
|
},
|
||||||
|
lastSuccessfulSource: {
|
||||||
|
state: lastSuccessfulSourceState,
|
||||||
|
set: setLastSuccessfulSourceState,
|
||||||
|
changed: lastSuccessfulSourceChanged,
|
||||||
|
},
|
||||||
|
enableLastSuccessfulSource: {
|
||||||
|
state: enableLastSuccessfulSourceState,
|
||||||
|
set: setEnableLastSuccessfulSourceState,
|
||||||
|
changed: enableLastSuccessfulSourceChanged,
|
||||||
|
},
|
||||||
proxyTmdb: {
|
proxyTmdb: {
|
||||||
state: proxyTmdbState,
|
state: proxyTmdbState,
|
||||||
set: setProxyTmdbState,
|
set: setProxyTmdbState,
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,20 @@ export function RealPlayerView() {
|
||||||
const manualSourceSelection = usePreferencesStore(
|
const manualSourceSelection = usePreferencesStore(
|
||||||
(s) => s.manualSourceSelection,
|
(s) => s.manualSourceSelection,
|
||||||
);
|
);
|
||||||
|
const setLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setLastSuccessfulSource,
|
||||||
|
);
|
||||||
const router = useOverlayRouter("settings");
|
const router = useOverlayRouter("settings");
|
||||||
const openedWatchPartyRef = useRef<boolean>(false);
|
const openedWatchPartyRef = useRef<boolean>(false);
|
||||||
const progressItems = useProgressStore((s) => s.items);
|
const progressItems = useProgressStore((s) => s.items);
|
||||||
|
|
||||||
|
// Reset last successful source when leaving the player
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setLastSuccessfulSource(null);
|
||||||
|
};
|
||||||
|
}, [setLastSuccessfulSource]);
|
||||||
|
|
||||||
const paramsData = JSON.stringify({
|
const paramsData = JSON.stringify({
|
||||||
media: params.media,
|
media: params.media,
|
||||||
season: params.season,
|
season: params.season,
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,20 @@ export function SettingsPage() {
|
||||||
(s) => s.setEnableSourceOrder,
|
(s) => s.setEnableSourceOrder,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const lastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const setLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setLastSuccessfulSource,
|
||||||
|
);
|
||||||
|
|
||||||
|
const enableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.enableLastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const setEnableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.setEnableLastSuccessfulSource,
|
||||||
|
);
|
||||||
|
|
||||||
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
const disabledSources = usePreferencesStore((s) => s.disabledSources);
|
||||||
const setDisabledSources = usePreferencesStore((s) => s.setDisabledSources);
|
const setDisabledSources = usePreferencesStore((s) => s.setDisabledSources);
|
||||||
|
|
||||||
|
|
@ -406,6 +420,8 @@ export function SettingsPage() {
|
||||||
enableDetailsModal,
|
enableDetailsModal,
|
||||||
sourceOrder,
|
sourceOrder,
|
||||||
enableSourceOrder,
|
enableSourceOrder,
|
||||||
|
lastSuccessfulSource,
|
||||||
|
enableLastSuccessfulSource,
|
||||||
disabledSources,
|
disabledSources,
|
||||||
embedOrder,
|
embedOrder,
|
||||||
enableEmbedOrder,
|
enableEmbedOrder,
|
||||||
|
|
@ -475,6 +491,8 @@ export function SettingsPage() {
|
||||||
state.enableImageLogos.changed ||
|
state.enableImageLogos.changed ||
|
||||||
state.sourceOrder.changed ||
|
state.sourceOrder.changed ||
|
||||||
state.enableSourceOrder.changed ||
|
state.enableSourceOrder.changed ||
|
||||||
|
state.lastSuccessfulSource.changed ||
|
||||||
|
state.enableLastSuccessfulSource.changed ||
|
||||||
state.disabledSources.changed ||
|
state.disabledSources.changed ||
|
||||||
state.proxyTmdb.changed ||
|
state.proxyTmdb.changed ||
|
||||||
state.enableCarouselView.changed ||
|
state.enableCarouselView.changed ||
|
||||||
|
|
@ -500,6 +518,8 @@ export function SettingsPage() {
|
||||||
enableImageLogos: state.enableImageLogos.state,
|
enableImageLogos: state.enableImageLogos.state,
|
||||||
sourceOrder: state.sourceOrder.state,
|
sourceOrder: state.sourceOrder.state,
|
||||||
enableSourceOrder: state.enableSourceOrder.state,
|
enableSourceOrder: state.enableSourceOrder.state,
|
||||||
|
lastSuccessfulSource: state.lastSuccessfulSource.state,
|
||||||
|
enableLastSuccessfulSource: state.enableLastSuccessfulSource.state,
|
||||||
disabledSources: state.disabledSources.state,
|
disabledSources: state.disabledSources.state,
|
||||||
proxyTmdb: state.proxyTmdb.state,
|
proxyTmdb: state.proxyTmdb.state,
|
||||||
enableCarouselView: state.enableCarouselView.state,
|
enableCarouselView: state.enableCarouselView.state,
|
||||||
|
|
@ -537,6 +557,8 @@ export function SettingsPage() {
|
||||||
setEnableImageLogos(state.enableImageLogos.state);
|
setEnableImageLogos(state.enableImageLogos.state);
|
||||||
setSourceOrder(state.sourceOrder.state);
|
setSourceOrder(state.sourceOrder.state);
|
||||||
setEnableSourceOrder(state.enableSourceOrder.state);
|
setEnableSourceOrder(state.enableSourceOrder.state);
|
||||||
|
setLastSuccessfulSource(state.lastSuccessfulSource.state);
|
||||||
|
setEnableLastSuccessfulSource(state.enableLastSuccessfulSource.state);
|
||||||
setDisabledSources(state.disabledSources.state);
|
setDisabledSources(state.disabledSources.state);
|
||||||
setAppLanguage(state.appLanguage.state);
|
setAppLanguage(state.appLanguage.state);
|
||||||
setTheme(state.theme.state);
|
setTheme(state.theme.state);
|
||||||
|
|
@ -584,6 +606,8 @@ export function SettingsPage() {
|
||||||
setEnableImageLogos,
|
setEnableImageLogos,
|
||||||
setSourceOrder,
|
setSourceOrder,
|
||||||
setEnableSourceOrder,
|
setEnableSourceOrder,
|
||||||
|
setLastSuccessfulSource,
|
||||||
|
setEnableLastSuccessfulSource,
|
||||||
setDisabledSources,
|
setDisabledSources,
|
||||||
setAppLanguage,
|
setAppLanguage,
|
||||||
setTheme,
|
setTheme,
|
||||||
|
|
@ -650,6 +674,8 @@ export function SettingsPage() {
|
||||||
setSourceOrder={state.sourceOrder.set}
|
setSourceOrder={state.sourceOrder.set}
|
||||||
enableSourceOrder={state.enableSourceOrder.state}
|
enableSourceOrder={state.enableSourceOrder.state}
|
||||||
setenableSourceOrder={state.enableSourceOrder.set}
|
setenableSourceOrder={state.enableSourceOrder.set}
|
||||||
|
enableLastSuccessfulSource={state.enableLastSuccessfulSource.state}
|
||||||
|
setEnableLastSuccessfulSource={state.enableLastSuccessfulSource.set}
|
||||||
disabledSources={state.disabledSources.state}
|
disabledSources={state.disabledSources.state}
|
||||||
setDisabledSources={state.disabledSources.set}
|
setDisabledSources={state.disabledSources.set}
|
||||||
enableLowPerformanceMode={state.enableLowPerformanceMode.state}
|
enableLowPerformanceMode={state.enableLowPerformanceMode.state}
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,12 @@ export function SourceSelectPart(props: { media: ScrapeMedia }) {
|
||||||
const routerId = "manualSourceSelect";
|
const routerId = "manualSourceSelect";
|
||||||
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
||||||
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
||||||
|
const lastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
const enableLastSuccessfulSource = usePreferencesStore(
|
||||||
|
(s) => s.enableLastSuccessfulSource,
|
||||||
|
);
|
||||||
|
|
||||||
const sources = useMemo(() => {
|
const sources = useMemo(() => {
|
||||||
const metaType = props.media.type;
|
const metaType = props.media.type;
|
||||||
|
|
@ -138,13 +144,34 @@ export function SourceSelectPart(props: { media: ScrapeMedia }) {
|
||||||
.filter((v) => v.mediaTypes?.includes(metaType));
|
.filter((v) => v.mediaTypes?.includes(metaType));
|
||||||
|
|
||||||
if (!enableSourceOrder || preferredSourceOrder.length === 0) {
|
if (!enableSourceOrder || preferredSourceOrder.length === 0) {
|
||||||
|
// Even without custom source order, prioritize last successful source if enabled
|
||||||
|
if (enableLastSuccessfulSource && lastSuccessfulSource) {
|
||||||
|
const lastSourceIndex = allSources.findIndex(
|
||||||
|
(s) => s.id === lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
if (lastSourceIndex !== -1) {
|
||||||
|
const lastSource = allSources.splice(lastSourceIndex, 1)[0];
|
||||||
|
return [lastSource, ...allSources];
|
||||||
|
}
|
||||||
|
}
|
||||||
return allSources;
|
return allSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort sources according to preferred order
|
// Sort sources according to preferred order, but prioritize last successful source
|
||||||
const orderedSources = [];
|
const orderedSources = [];
|
||||||
const remainingSources = [...allSources];
|
const remainingSources = [...allSources];
|
||||||
|
|
||||||
|
// First, add the last successful source if it exists, is available, and the feature is enabled
|
||||||
|
if (enableLastSuccessfulSource && lastSuccessfulSource) {
|
||||||
|
const lastSourceIndex = remainingSources.findIndex(
|
||||||
|
(s) => s.id === lastSuccessfulSource,
|
||||||
|
);
|
||||||
|
if (lastSourceIndex !== -1) {
|
||||||
|
orderedSources.push(remainingSources[lastSourceIndex]);
|
||||||
|
remainingSources.splice(lastSourceIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add sources in preferred order
|
// Add sources in preferred order
|
||||||
for (const sourceId of preferredSourceOrder) {
|
for (const sourceId of preferredSourceOrder) {
|
||||||
const sourceIndex = remainingSources.findIndex((s) => s.id === sourceId);
|
const sourceIndex = remainingSources.findIndex((s) => s.id === sourceId);
|
||||||
|
|
@ -158,7 +185,13 @@ export function SourceSelectPart(props: { media: ScrapeMedia }) {
|
||||||
orderedSources.push(...remainingSources);
|
orderedSources.push(...remainingSources);
|
||||||
|
|
||||||
return orderedSources;
|
return orderedSources;
|
||||||
}, [props.media.type, preferredSourceOrder, enableSourceOrder]);
|
}, [
|
||||||
|
props.media.type,
|
||||||
|
preferredSourceOrder,
|
||||||
|
enableSourceOrder,
|
||||||
|
lastSuccessfulSource,
|
||||||
|
enableLastSuccessfulSource,
|
||||||
|
]);
|
||||||
|
|
||||||
if (selectedSourceId) {
|
if (selectedSourceId) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ export function PreferencesPart(props: {
|
||||||
setSourceOrder: (v: string[]) => void;
|
setSourceOrder: (v: string[]) => void;
|
||||||
enableSourceOrder: boolean;
|
enableSourceOrder: boolean;
|
||||||
setenableSourceOrder: (v: boolean) => void;
|
setenableSourceOrder: (v: boolean) => void;
|
||||||
|
enableLastSuccessfulSource: boolean;
|
||||||
|
setEnableLastSuccessfulSource: (v: boolean) => void;
|
||||||
disabledSources: string[];
|
disabledSources: string[];
|
||||||
setDisabledSources: (v: string[]) => void;
|
setDisabledSources: (v: string[]) => void;
|
||||||
enableLowPerformanceMode: boolean;
|
enableLowPerformanceMode: boolean;
|
||||||
|
|
@ -267,6 +269,30 @@ export function PreferencesPart(props: {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Last Successful Source Preference */}
|
||||||
|
<div>
|
||||||
|
<p className="text-white font-bold mb-3">
|
||||||
|
{t("settings.preferences.lastSuccessfulSource")}
|
||||||
|
</p>
|
||||||
|
<p className="max-w-[25rem] font-medium">
|
||||||
|
{t("settings.preferences.lastSuccessfulSourceDescription")}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
onClick={() =>
|
||||||
|
props.setEnableLastSuccessfulSource(
|
||||||
|
!props.enableLastSuccessfulSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg"
|
||||||
|
>
|
||||||
|
<Toggle enabled={props.enableLastSuccessfulSource} />
|
||||||
|
<p className="flex-1 text-white font-bold">
|
||||||
|
{t("settings.preferences.lastSuccessfulSourceEnableLabel")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-white font-bold">
|
<p className="text-white font-bold">
|
||||||
{t("settings.preferences.sourceOrder")}
|
{t("settings.preferences.sourceOrder")}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ export interface PreferencesStore {
|
||||||
forceCompactEpisodeView: boolean;
|
forceCompactEpisodeView: boolean;
|
||||||
sourceOrder: string[];
|
sourceOrder: string[];
|
||||||
enableSourceOrder: boolean;
|
enableSourceOrder: boolean;
|
||||||
|
lastSuccessfulSource: string | null;
|
||||||
|
enableLastSuccessfulSource: boolean;
|
||||||
disabledSources: string[];
|
disabledSources: string[];
|
||||||
embedOrder: string[];
|
embedOrder: string[];
|
||||||
enableEmbedOrder: boolean;
|
enableEmbedOrder: boolean;
|
||||||
|
|
@ -39,6 +41,8 @@ export interface PreferencesStore {
|
||||||
setForceCompactEpisodeView(v: boolean): void;
|
setForceCompactEpisodeView(v: boolean): void;
|
||||||
setSourceOrder(v: string[]): void;
|
setSourceOrder(v: string[]): void;
|
||||||
setEnableSourceOrder(v: boolean): void;
|
setEnableSourceOrder(v: boolean): void;
|
||||||
|
setLastSuccessfulSource(v: string | null): void;
|
||||||
|
setEnableLastSuccessfulSource(v: boolean): void;
|
||||||
setDisabledSources(v: string[]): void;
|
setDisabledSources(v: string[]): void;
|
||||||
setEmbedOrder(v: string[]): void;
|
setEmbedOrder(v: string[]): void;
|
||||||
setEnableEmbedOrder(v: boolean): void;
|
setEnableEmbedOrder(v: boolean): void;
|
||||||
|
|
@ -68,6 +72,8 @@ export const usePreferencesStore = create(
|
||||||
forceCompactEpisodeView: false,
|
forceCompactEpisodeView: false,
|
||||||
sourceOrder: [],
|
sourceOrder: [],
|
||||||
enableSourceOrder: false,
|
enableSourceOrder: false,
|
||||||
|
lastSuccessfulSource: null,
|
||||||
|
enableLastSuccessfulSource: true,
|
||||||
disabledSources: [],
|
disabledSources: [],
|
||||||
embedOrder: [],
|
embedOrder: [],
|
||||||
enableEmbedOrder: false,
|
enableEmbedOrder: false,
|
||||||
|
|
@ -136,6 +142,16 @@ export const usePreferencesStore = create(
|
||||||
s.enableSourceOrder = v;
|
s.enableSourceOrder = v;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setLastSuccessfulSource(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.lastSuccessfulSource = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setEnableLastSuccessfulSource(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.enableLastSuccessfulSource = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
setDisabledSources(v) {
|
setDisabledSources(v) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.disabledSources = v;
|
s.disabledSources = v;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue