p-stream/src/components/player/internals/CastingInternal.tsx

146 lines
4.4 KiB
TypeScript

import { useCallback, useEffect, useRef } from "react";
import { mediaItemTypeToMediaType } from "@/backend/metadata/tmdb";
import { makeVideoElementDisplayInterface } from "@/components/player/display/base";
import { makeChromecastDisplayInterface } from "@/components/player/display/chromecast";
import { useChromecastAvailable } from "@/hooks/useChromecastAvailable";
import { usePlayerStore } from "@/stores/player/store";
export function CastingInternal() {
const setInstance = usePlayerStore((s) => s.casting.setInstance);
const setController = usePlayerStore((s) => s.casting.setController);
const setPlayer = usePlayerStore((s) => s.casting.setPlayer);
const setIsCasting = usePlayerStore((s) => s.casting.setIsCasting);
const isCasting = usePlayerStore((s) => s.interface.isCasting);
const caption = usePlayerStore((s) => s.caption?.selected);
const setDisplay = usePlayerStore((s) => s.setDisplay);
const redisplaySource = usePlayerStore((s) => s.redisplaySource);
const available = useChromecastAvailable();
const display = usePlayerStore((s) => s.display);
const controller = usePlayerStore((s) => s.casting.controller);
const player = usePlayerStore((s) => s.casting.player);
const instance = usePlayerStore((s) => s.casting.instance);
const time = usePlayerStore((s) => s.progress.time);
const metaTitle = usePlayerStore((s) => s.meta?.title);
const metaType = usePlayerStore((s) => s.meta?.type);
const dataRef = useRef({
controller,
player,
instance,
time,
metaTitle,
metaType,
caption,
});
useEffect(() => {
dataRef.current = {
controller,
player,
instance,
time,
metaTitle,
metaType,
caption,
};
}, [controller, player, instance, time, metaTitle, metaType, caption]);
useEffect(() => {
if (isCasting) {
if (
dataRef.current.controller &&
dataRef.current.instance &&
dataRef.current.player
) {
const newDisplay = makeChromecastDisplayInterface({
controller: dataRef.current.controller,
instance: dataRef.current.instance,
player: dataRef.current.player,
});
newDisplay.setMeta({
title: dataRef.current.metaTitle ?? "",
type: mediaItemTypeToMediaType(dataRef.current.metaType ?? "movie"),
});
newDisplay.setCaption(dataRef.current.caption);
setDisplay(newDisplay);
redisplaySource(dataRef.current.time ?? 0);
}
} else {
const newDisplay = makeVideoElementDisplayInterface();
setDisplay(newDisplay);
redisplaySource(dataRef.current.time ?? 0);
}
}, [isCasting, setDisplay, redisplaySource]);
useEffect(() => {
display?.setMeta({
title: dataRef.current.metaTitle ?? "",
type: mediaItemTypeToMediaType(dataRef.current.metaType ?? "movie"),
});
}, [metaTitle, metaType, display]);
const connectionChanged = useCallback(
(e: cast.framework.RemotePlayerChangedEvent) => {
if (e.field === "isConnected") {
setIsCasting(e.value);
}
},
[setIsCasting],
);
useEffect(() => {
if (!available || !window.cast || !window.chrome || !window.chrome.cast)
return;
if (!chrome.cast || !chrome.cast.media) {
console.error(
"Chrome Cast API not fully initialized: chrome.cast.media is undefined",
);
return;
}
try {
const ins = cast.framework.CastContext.getInstance();
setInstance(ins);
const receiverAppId = chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID;
const autoJoinPolicy = chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED;
ins.setOptions({
receiverApplicationId: receiverAppId,
autoJoinPolicy,
});
const newPlayer = new cast.framework.RemotePlayer();
setPlayer(newPlayer);
const newControlller = new cast.framework.RemotePlayerController(
newPlayer,
);
setController(newControlller);
newControlller.addEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged,
);
return () => {
newControlller.removeEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
connectionChanged,
);
};
} catch (error) {
console.error("Error initializing Chromecast:", error);
}
}, [
available,
setPlayer,
setController,
setInstance,
setIsCasting,
connectionChanged,
]);
return null;
}