p-stream/src/pages/PlayerView.tsx
Pas fb3bc161ce Merge: Add Watch Party
commit 6034a1ebeaa97a97552dc249f97cc935dcbd1cd6
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 20:02:36 2025 -0600

    update stuff

commit 91d1370668a3e05fed3687ffef697a96c28a0b2c
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 19:47:34 2025 -0600

    Update Downloads.tsx

commit 1c25c88175abebe761d27194f22eec9fd3bcf2e1
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 19:46:27 2025 -0600

    clean some more stuff

commit d6c24e76348c135f3c1ae0444491ff0b302eca2e
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 19:38:07 2025 -0600

    clean this again

commit 6511de68a1b1397e4884dfc6e6f0599497b9afee
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 19:30:08 2025 -0600

    clean up a bit

commit 467358c1f50c1555b42f9ae8d22f955ebc9bba1b
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 17:45:04 2025 -0600

    validate content

commit 59d4a1665b32bdf4ca453859816a2245b184b025
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 17:35:22 2025 -0600

    add auto join link

commit 6f3c334d2157f1c82f9d26e9a7db49371e6a2b5e
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 17:08:35 2025 -0600

    watch partyyyy

commit 1497377692fba86ea1ef40191dbaa648abc38e2e
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 13:56:04 2025 -0600

    add watch party view stuff

commit 80003f78d0adf6071d4f2c6b6a6d8fdcb2aa204a
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 13:14:07 2025 -0600

    init sending webhooks

    testing on a discord hook

commit f5293c2eae5d5a12be6153ab37e50214289e20b6
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 12:31:41 2025 -0600

    remove duplicate class

commit 7871162e6b2c580eb2bcb9c8abc5ecc19d706a06
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 12:25:49 2025 -0600

    update legacy button

commit a2948f3aa1b7481a3ac5b69e2e4dab71552816de
Author: Pas <74743263+Pasithea0@users.noreply.github.com>
Date:   Sat May 17 12:21:52 2025 -0600

    move watchparty to new atoms menu
2025-05-17 20:04:40 -06:00

160 lines
4.8 KiB
TypeScript

import { RunOutput } from "@movie-web/providers";
import { useCallback, useEffect, useRef, useState } from "react";
import {
Navigate,
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
import { useAsync } from "react-use";
import { usePlayer } from "@/components/player/hooks/usePlayer";
import { usePlayerMeta } from "@/components/player/hooks/usePlayerMeta";
import { convertProviderCaption } from "@/components/player/utils/captions";
import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape";
import { useQueryParam } from "@/hooks/useQueryParams";
import { MetaPart } from "@/pages/parts/player/MetaPart";
import { PlaybackErrorPart } from "@/pages/parts/player/PlaybackErrorPart";
import { PlayerPart } from "@/pages/parts/player/PlayerPart";
import { ScrapeErrorPart } from "@/pages/parts/player/ScrapeErrorPart";
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
import { useLastNonPlayerLink } from "@/stores/history";
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
import { needsOnboarding } from "@/utils/onboarding";
import { parseTimestamp } from "@/utils/timestamp";
export function RealPlayerView() {
const navigate = useNavigate();
const params = useParams<{
media: string;
episode?: string;
season?: string;
}>();
const [errorData, setErrorData] = useState<{
sources: Record<string, ScrapingSegment>;
sourceOrder: ScrapingItems[];
} | null>(null);
const [startAtParam] = useQueryParam("t");
const {
status,
playMedia,
reset,
setScrapeNotFound,
shouldStartFromBeginning,
setShouldStartFromBeginning,
} = usePlayer();
const { setPlayerMeta, scrapeMedia } = usePlayerMeta();
const backUrl = useLastNonPlayerLink();
const router = useOverlayRouter("settings");
const openedWatchPartyRef = useRef<boolean>(false);
const paramsData = JSON.stringify({
media: params.media,
season: params.season,
episode: params.episode,
});
useEffect(() => {
reset();
// Reset watch party state when media changes
openedWatchPartyRef.current = false;
}, [paramsData, reset]);
// Auto-open watch party menu if URL contains watchparty parameter
useEffect(() => {
if (openedWatchPartyRef.current) return;
if (status === playerStatus.PLAYING) {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("watchparty")) {
setTimeout(() => {
router.navigate("/watchparty");
openedWatchPartyRef.current = true;
}, 1000);
}
}
}, [status, router]);
const metaChange = useCallback(
(meta: PlayerMeta) => {
if (meta?.type === "show")
navigate(
`/media/${params.media}/${meta.season?.tmdbId}/${meta.episode?.tmdbId}`,
);
else navigate(`/media/${params.media}`);
},
[navigate, params],
);
const playAfterScrape = useCallback(
(out: RunOutput | null) => {
if (!out) return;
let startAt: number | undefined;
if (startAtParam) startAt = parseTimestamp(startAtParam) ?? undefined;
playMedia(
convertRunoutputToSource(out),
convertProviderCaption(out.stream.captions),
out.sourceId,
shouldStartFromBeginning ? 0 : startAt,
);
setShouldStartFromBeginning(false);
},
[
playMedia,
startAtParam,
shouldStartFromBeginning,
setShouldStartFromBeginning,
],
);
return (
<PlayerPart backUrl={backUrl} onMetaChange={metaChange}>
{status === playerStatus.IDLE ? (
<MetaPart onGetMeta={setPlayerMeta} />
) : null}
{status === playerStatus.SCRAPING && scrapeMedia ? (
<ScrapingPart
media={scrapeMedia}
onResult={(sources, sourceOrder) => {
setErrorData({
sourceOrder,
sources,
});
setScrapeNotFound();
}}
onGetStream={playAfterScrape}
/>
) : null}
{status === playerStatus.SCRAPE_NOT_FOUND && errorData ? (
<ScrapeErrorPart data={errorData} />
) : null}
{status === playerStatus.PLAYBACK_ERROR ? <PlaybackErrorPart /> : null}
</PlayerPart>
);
}
export function PlayerView() {
const loc = useLocation();
const { loading, error, value } = useAsync(() => {
return needsOnboarding();
});
if (error) throw new Error("Failed to detect onboarding");
if (loading) return null;
if (value)
return (
<Navigate
replace
to={{
pathname: "/onboarding",
search: `redirect=${encodeURIComponent(loc.pathname)}`,
}}
/>
);
return <RealPlayerView />;
}
export default PlayerView;