From 02a179b1d8e681ee5b338d2b954a94ee7dcc502a Mon Sep 17 00:00:00 2001
From: Pas <74743263+Pasithea0@users.noreply.github.com>
Date: Sat, 10 Jan 2026 18:29:00 -0700
Subject: [PATCH] add Find Next Source button
replace edit order button
---
src/assets/locales/en.json | 2 +-
.../atoms/settings/SourceSelectingView.tsx | 36 ++++++++++++++-----
src/hooks/useProviderScrape.tsx | 8 ++++-
src/pages/PlayerView.tsx | 35 ++++++++++++++++--
src/stores/player/slices/source.ts | 9 +++++
5 files changed, 76 insertions(+), 14 deletions(-)
diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json
index e683c815..7e4d0df3 100644
--- a/src/assets/locales/en.json
+++ b/src/assets/locales/en.json
@@ -823,7 +823,7 @@
},
"title": "Sources",
"unknownOption": "Unknown",
- "editOrder": "Edit order"
+ "findNextSource": "Find next source"
},
"subtitles": {
"customChoice": "Drop or upload file",
diff --git a/src/components/player/atoms/settings/SourceSelectingView.tsx b/src/components/player/atoms/settings/SourceSelectingView.tsx
index 5e9938fb..86ba15c8 100644
--- a/src/components/player/atoms/settings/SourceSelectingView.tsx
+++ b/src/components/player/atoms/settings/SourceSelectingView.tsx
@@ -10,6 +10,7 @@ import {
import { Menu } from "@/components/player/internals/ContextMenu";
import { SelectableLink } from "@/components/player/internals/ContextMenu/Links";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
+import { playerStatus } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store";
import { usePreferencesStore } from "@/stores/preferences";
@@ -156,6 +157,8 @@ export function SourceSelectionView({
const router = useOverlayRouter(id);
const metaType = usePlayerStore((s) => s.meta?.type);
const currentSourceId = usePlayerStore((s) => s.sourceId);
+ const setResumeFromSourceId = usePlayerStore((s) => s.setResumeFromSourceId);
+ const setStatus = usePlayerStore((s) => s.setStatus);
const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
const lastSuccessfulSource = usePreferencesStore(
@@ -164,6 +167,9 @@ export function SourceSelectionView({
const enableLastSuccessfulSource = usePreferencesStore(
(s) => s.enableLastSuccessfulSource,
);
+ const manualSourceSelection = usePreferencesStore(
+ (s) => s.manualSourceSelection,
+ );
const sources = useMemo(() => {
if (!metaType) return [];
@@ -221,20 +227,32 @@ export function SourceSelectionView({
enableLastSuccessfulSource,
]);
+ const handleFindNextSource = () => {
+ if (!currentSourceId) return;
+ // Set the resume source ID in the store
+ setResumeFromSourceId(currentSourceId);
+ // Close the settings overlay
+ router.close();
+ // Set status to SCRAPING to trigger scraping from next source
+ setStatus(playerStatus.SCRAPING);
+ };
+
return (
<>
router.navigate("/")}
rightSide={
-
+
+ {currentSourceId && !manualSourceSelection && (
+
+ )}
+
}
>
{t("player.menus.sources.title")}
diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx
index 39b2b561..383e01f5 100644
--- a/src/hooks/useProviderScrape.tsx
+++ b/src/hooks/useProviderScrape.tsx
@@ -211,7 +211,12 @@ export function useScrape() {
}
// If we have a last successful source and the feature is enabled, prioritize it
- if (enableLastSuccessfulSource && lastSuccessfulSource) {
+ // BUT only if we're not resuming from a specific source (to preserve custom order)
+ if (
+ enableLastSuccessfulSource &&
+ lastSuccessfulSource &&
+ !startFromSourceId
+ ) {
const lastSourceIndex = baseSourceOrder.indexOf(lastSuccessfulSource);
if (lastSourceIndex !== -1) {
baseSourceOrder = [
@@ -222,6 +227,7 @@ export function useScrape() {
}
// If starting from a specific source ID, filter the order to start AFTER that source
+ // This preserves the custom order while starting from the next source
let filteredSourceOrder = baseSourceOrder;
if (startFromSourceId) {
const startIndex = filteredSourceOrder.indexOf(startFromSourceId);
diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx
index 43558fe3..76c0eed3 100644
--- a/src/pages/PlayerView.tsx
+++ b/src/pages/PlayerView.tsx
@@ -47,6 +47,10 @@ export function RealPlayerView() {
const [resumeFromSourceId, setResumeFromSourceId] = useState(
null,
);
+ const storeResumeFromSourceId = usePlayerStore((s) => s.resumeFromSourceId);
+ const setResumeFromSourceIdInStore = usePlayerStore(
+ (s) => s.setResumeFromSourceId,
+ );
const [startAtParam] = useQueryParam("t");
const {
status,
@@ -77,6 +81,14 @@ export function RealPlayerView() {
};
}, [setLastSuccessfulSource]);
+ // Reset resume from source ID when leaving the player
+ useEffect(() => {
+ return () => {
+ setResumeFromSourceId(null);
+ setResumeFromSourceIdInStore(null);
+ };
+ }, [setResumeFromSourceIdInStore]);
+
const paramsData = JSON.stringify({
media: params.media,
season: params.season,
@@ -169,14 +181,28 @@ export function RealPlayerView() {
(startFromSourceId: string) => {
// Set resume source first
setResumeFromSourceId(startFromSourceId);
+ setResumeFromSourceIdInStore(startFromSourceId);
// Then change status in next tick to ensure re-render
setTimeout(() => {
setStatus(playerStatus.SCRAPING);
}, 0);
},
- [setStatus],
+ [setStatus, setResumeFromSourceIdInStore],
);
+ // Sync store value to local state when it changes (e.g., from settings)
+ // or when status changes to SCRAPING
+ useEffect(() => {
+ if (storeResumeFromSourceId && status === playerStatus.SCRAPING) {
+ if (
+ !resumeFromSourceId ||
+ resumeFromSourceId !== storeResumeFromSourceId
+ ) {
+ setResumeFromSourceId(storeResumeFromSourceId);
+ }
+ }
+ }, [storeResumeFromSourceId, resumeFromSourceId, status]);
+
const playAfterScrape = useCallback(
(out: RunOutput | null) => {
if (!out) return;
@@ -223,9 +249,11 @@ export function RealPlayerView() {
) : (
{
setErrorData({
sourceOrder,
@@ -234,6 +262,7 @@ export function RealPlayerView() {
setScrapeNotFound();
// Clear resume state after scraping
setResumeFromSourceId(null);
+ setResumeFromSourceIdInStore(null);
}}
onGetStream={playAfterScrape}
/>
diff --git a/src/stores/player/slices/source.ts b/src/stores/player/slices/source.ts
index dab67ce2..b8147616 100644
--- a/src/stores/player/slices/source.ts
+++ b/src/stores/player/slices/source.ts
@@ -105,6 +105,7 @@ export interface SourceSlice {
meta: PlayerMeta | null;
failedSourcesPerMedia: Record; // mediaKey -> array of failed sourceIds
failedEmbedsPerMedia: Record>; // mediaKey -> sourceId -> array of failed embedIds
+ resumeFromSourceId: string | null;
setStatus(status: PlayerStatus): void;
setSource(
stream: SourceSliceSource,
@@ -129,6 +130,7 @@ export interface SourceSlice {
addFailedEmbed(sourceId: string, embedId: string): void;
clearFailedSources(mediaKey?: string): void;
clearFailedEmbeds(mediaKey?: string): void;
+ setResumeFromSourceId(sourceId: string | null): void;
reset(): void;
}
@@ -190,6 +192,7 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
meta: null,
failedSourcesPerMedia: {},
failedEmbedsPerMedia: {},
+ resumeFromSourceId: null,
caption: {
selected: null,
asTrack: false,
@@ -387,6 +390,11 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
}
});
},
+ setResumeFromSourceId(sourceId: string | null) {
+ set((s) => {
+ s.resumeFromSourceId = sourceId;
+ });
+ },
reset() {
set((s) => {
s.source = null;
@@ -402,6 +410,7 @@ export const createSourceSlice: MakeSlice = (set, get) => ({
s.meta = null;
s.failedSourcesPerMedia = {};
s.failedEmbedsPerMedia = {};
+ s.resumeFromSourceId = null;
this.clearTranslateTask();
s.caption = {
selected: null,