mirror of
https://github.com/p-stream/p-stream.git
synced 2026-01-11 20:10:32 +00:00
add Find Next Source button
replace edit order button
This commit is contained in:
parent
45e5abd00e
commit
02a179b1d8
5 changed files with 76 additions and 14 deletions
|
|
@ -823,7 +823,7 @@
|
||||||
},
|
},
|
||||||
"title": "Sources",
|
"title": "Sources",
|
||||||
"unknownOption": "Unknown",
|
"unknownOption": "Unknown",
|
||||||
"editOrder": "Edit order"
|
"findNextSource": "Find next source"
|
||||||
},
|
},
|
||||||
"subtitles": {
|
"subtitles": {
|
||||||
"customChoice": "Drop or upload file",
|
"customChoice": "Drop or upload file",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import { Menu } from "@/components/player/internals/ContextMenu";
|
import { Menu } from "@/components/player/internals/ContextMenu";
|
||||||
import { SelectableLink } from "@/components/player/internals/ContextMenu/Links";
|
import { SelectableLink } from "@/components/player/internals/ContextMenu/Links";
|
||||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||||
|
import { playerStatus } from "@/stores/player/slices/source";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
import { usePreferencesStore } from "@/stores/preferences";
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
|
|
||||||
|
|
@ -156,6 +157,8 @@ export function SourceSelectionView({
|
||||||
const router = useOverlayRouter(id);
|
const router = useOverlayRouter(id);
|
||||||
const metaType = usePlayerStore((s) => s.meta?.type);
|
const metaType = usePlayerStore((s) => s.meta?.type);
|
||||||
const currentSourceId = usePlayerStore((s) => s.sourceId);
|
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 preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder);
|
||||||
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder);
|
||||||
const lastSuccessfulSource = usePreferencesStore(
|
const lastSuccessfulSource = usePreferencesStore(
|
||||||
|
|
@ -164,6 +167,9 @@ export function SourceSelectionView({
|
||||||
const enableLastSuccessfulSource = usePreferencesStore(
|
const enableLastSuccessfulSource = usePreferencesStore(
|
||||||
(s) => s.enableLastSuccessfulSource,
|
(s) => s.enableLastSuccessfulSource,
|
||||||
);
|
);
|
||||||
|
const manualSourceSelection = usePreferencesStore(
|
||||||
|
(s) => s.manualSourceSelection,
|
||||||
|
);
|
||||||
|
|
||||||
const sources = useMemo(() => {
|
const sources = useMemo(() => {
|
||||||
if (!metaType) return [];
|
if (!metaType) return [];
|
||||||
|
|
@ -221,20 +227,32 @@ export function SourceSelectionView({
|
||||||
enableLastSuccessfulSource,
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Menu.BackLink
|
<Menu.BackLink
|
||||||
onClick={() => router.navigate("/")}
|
onClick={() => router.navigate("/")}
|
||||||
rightSide={
|
rightSide={
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
type="button"
|
{currentSourceId && !manualSourceSelection && (
|
||||||
onClick={() => {
|
<button
|
||||||
window.location.href = "/settings#source-order";
|
type="button"
|
||||||
}}
|
onClick={handleFindNextSource}
|
||||||
className="-mr-2 -my-1 px-2 p-[0.4em] rounded tabbable hover:bg-video-context-light hover:bg-opacity-10"
|
className="-mr-2 -my-1 px-2 p-[0.4em] rounded tabbable hover:bg-video-context-light hover:bg-opacity-10"
|
||||||
>
|
>
|
||||||
{t("player.menus.sources.editOrder")}
|
{t("player.menus.sources.findNextSource")}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("player.menus.sources.title")}
|
{t("player.menus.sources.title")}
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,12 @@ export function useScrape() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a last successful source and the feature is enabled, prioritize it
|
// 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);
|
const lastSourceIndex = baseSourceOrder.indexOf(lastSuccessfulSource);
|
||||||
if (lastSourceIndex !== -1) {
|
if (lastSourceIndex !== -1) {
|
||||||
baseSourceOrder = [
|
baseSourceOrder = [
|
||||||
|
|
@ -222,6 +227,7 @@ export function useScrape() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If starting from a specific source ID, filter the order to start AFTER that source
|
// 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;
|
let filteredSourceOrder = baseSourceOrder;
|
||||||
if (startFromSourceId) {
|
if (startFromSourceId) {
|
||||||
const startIndex = filteredSourceOrder.indexOf(startFromSourceId);
|
const startIndex = filteredSourceOrder.indexOf(startFromSourceId);
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ export function RealPlayerView() {
|
||||||
const [resumeFromSourceId, setResumeFromSourceId] = useState<string | null>(
|
const [resumeFromSourceId, setResumeFromSourceId] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const storeResumeFromSourceId = usePlayerStore((s) => s.resumeFromSourceId);
|
||||||
|
const setResumeFromSourceIdInStore = usePlayerStore(
|
||||||
|
(s) => s.setResumeFromSourceId,
|
||||||
|
);
|
||||||
const [startAtParam] = useQueryParam("t");
|
const [startAtParam] = useQueryParam("t");
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
|
|
@ -77,6 +81,14 @@ export function RealPlayerView() {
|
||||||
};
|
};
|
||||||
}, [setLastSuccessfulSource]);
|
}, [setLastSuccessfulSource]);
|
||||||
|
|
||||||
|
// Reset resume from source ID when leaving the player
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setResumeFromSourceId(null);
|
||||||
|
setResumeFromSourceIdInStore(null);
|
||||||
|
};
|
||||||
|
}, [setResumeFromSourceIdInStore]);
|
||||||
|
|
||||||
const paramsData = JSON.stringify({
|
const paramsData = JSON.stringify({
|
||||||
media: params.media,
|
media: params.media,
|
||||||
season: params.season,
|
season: params.season,
|
||||||
|
|
@ -169,14 +181,28 @@ export function RealPlayerView() {
|
||||||
(startFromSourceId: string) => {
|
(startFromSourceId: string) => {
|
||||||
// Set resume source first
|
// Set resume source first
|
||||||
setResumeFromSourceId(startFromSourceId);
|
setResumeFromSourceId(startFromSourceId);
|
||||||
|
setResumeFromSourceIdInStore(startFromSourceId);
|
||||||
// Then change status in next tick to ensure re-render
|
// Then change status in next tick to ensure re-render
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setStatus(playerStatus.SCRAPING);
|
setStatus(playerStatus.SCRAPING);
|
||||||
}, 0);
|
}, 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(
|
const playAfterScrape = useCallback(
|
||||||
(out: RunOutput | null) => {
|
(out: RunOutput | null) => {
|
||||||
if (!out) return;
|
if (!out) return;
|
||||||
|
|
@ -223,9 +249,11 @@ export function RealPlayerView() {
|
||||||
<SourceSelectPart media={scrapeMedia} />
|
<SourceSelectPart media={scrapeMedia} />
|
||||||
) : (
|
) : (
|
||||||
<ScrapingPart
|
<ScrapingPart
|
||||||
key={`scraping-${resumeFromSourceId || "default"}`}
|
key={`scraping-${resumeFromSourceId || storeResumeFromSourceId || "default"}`}
|
||||||
media={scrapeMedia}
|
media={scrapeMedia}
|
||||||
startFromSourceId={resumeFromSourceId || undefined}
|
startFromSourceId={
|
||||||
|
resumeFromSourceId || storeResumeFromSourceId || undefined
|
||||||
|
}
|
||||||
onResult={(sources, sourceOrder) => {
|
onResult={(sources, sourceOrder) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
sourceOrder,
|
sourceOrder,
|
||||||
|
|
@ -234,6 +262,7 @@ export function RealPlayerView() {
|
||||||
setScrapeNotFound();
|
setScrapeNotFound();
|
||||||
// Clear resume state after scraping
|
// Clear resume state after scraping
|
||||||
setResumeFromSourceId(null);
|
setResumeFromSourceId(null);
|
||||||
|
setResumeFromSourceIdInStore(null);
|
||||||
}}
|
}}
|
||||||
onGetStream={playAfterScrape}
|
onGetStream={playAfterScrape}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ export interface SourceSlice {
|
||||||
meta: PlayerMeta | null;
|
meta: PlayerMeta | null;
|
||||||
failedSourcesPerMedia: Record<string, string[]>; // mediaKey -> array of failed sourceIds
|
failedSourcesPerMedia: Record<string, string[]>; // mediaKey -> array of failed sourceIds
|
||||||
failedEmbedsPerMedia: Record<string, Record<string, string[]>>; // mediaKey -> sourceId -> array of failed embedIds
|
failedEmbedsPerMedia: Record<string, Record<string, string[]>>; // mediaKey -> sourceId -> array of failed embedIds
|
||||||
|
resumeFromSourceId: string | null;
|
||||||
setStatus(status: PlayerStatus): void;
|
setStatus(status: PlayerStatus): void;
|
||||||
setSource(
|
setSource(
|
||||||
stream: SourceSliceSource,
|
stream: SourceSliceSource,
|
||||||
|
|
@ -129,6 +130,7 @@ export interface SourceSlice {
|
||||||
addFailedEmbed(sourceId: string, embedId: string): void;
|
addFailedEmbed(sourceId: string, embedId: string): void;
|
||||||
clearFailedSources(mediaKey?: string): void;
|
clearFailedSources(mediaKey?: string): void;
|
||||||
clearFailedEmbeds(mediaKey?: string): void;
|
clearFailedEmbeds(mediaKey?: string): void;
|
||||||
|
setResumeFromSourceId(sourceId: string | null): void;
|
||||||
reset(): void;
|
reset(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,6 +192,7 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||||
meta: null,
|
meta: null,
|
||||||
failedSourcesPerMedia: {},
|
failedSourcesPerMedia: {},
|
||||||
failedEmbedsPerMedia: {},
|
failedEmbedsPerMedia: {},
|
||||||
|
resumeFromSourceId: null,
|
||||||
caption: {
|
caption: {
|
||||||
selected: null,
|
selected: null,
|
||||||
asTrack: false,
|
asTrack: false,
|
||||||
|
|
@ -387,6 +390,11 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setResumeFromSourceId(sourceId: string | null) {
|
||||||
|
set((s) => {
|
||||||
|
s.resumeFromSourceId = sourceId;
|
||||||
|
});
|
||||||
|
},
|
||||||
reset() {
|
reset() {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.source = null;
|
s.source = null;
|
||||||
|
|
@ -402,6 +410,7 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||||
s.meta = null;
|
s.meta = null;
|
||||||
s.failedSourcesPerMedia = {};
|
s.failedSourcesPerMedia = {};
|
||||||
s.failedEmbedsPerMedia = {};
|
s.failedEmbedsPerMedia = {};
|
||||||
|
s.resumeFromSourceId = null;
|
||||||
this.clearTranslateTask();
|
this.clearTranslateTask();
|
||||||
s.caption = {
|
s.caption = {
|
||||||
selected: null,
|
selected: null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue