mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
wip start cast
This commit is contained in:
parent
de3fda6a1a
commit
a7f0bd5a91
@ -21,34 +21,34 @@
|
||||
"tsdoc": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@jellyfin/libass-wasm": "^4.1.1",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.8.7",
|
||||
"@mui/icons-material": "^5.10.9",
|
||||
"@mui/material": "^5.10.10",
|
||||
"hls.js": "^1.2.4",
|
||||
"jotai": "^1.8.4",
|
||||
"next": "12.2.2",
|
||||
"next-translate": "^1.5.0",
|
||||
"jotai": "^1.8.6",
|
||||
"next": "12.3.1",
|
||||
"next-translate": "^1.6.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-query": "^4.0.0-beta.23",
|
||||
"superjson": "^1.9.1",
|
||||
"zod": "^3.18.0"
|
||||
"superjson": "^1.10.1",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chromecast-caf-sender": "^1.0.5",
|
||||
"@types/node": "18.0.3",
|
||||
"@types/react": "18.0.15",
|
||||
"@types/node": "18.11.2",
|
||||
"@types/react": "18.0.21",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"eslint": "8.19.0",
|
||||
"eslint-config-next": "12.2.2",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-jsdoc": "^0.3.38",
|
||||
"typescript": "4.7.4"
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
||||
|
@ -38,9 +38,8 @@ if (typeof window === "undefined") {
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps) => {
|
||||
const [queryClient] = useState(() => createQueryClient());
|
||||
const { queryState, ...props } = superjson.deserialize<any>(pageProps ?? {});
|
||||
const { queryState, ...props } = superjson.deserialize<any>(pageProps ?? { json: {} });
|
||||
const getLayout = (Component as QueryPage).getLayout ?? ((page) => page);
|
||||
const castEnabled = true;
|
||||
|
||||
useMobileHover();
|
||||
|
||||
@ -75,7 +74,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<Box >
|
||||
{getLayout(<Component {...props} />)}
|
||||
{castEnabled && <CastProvider />}
|
||||
<CastProvider />
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</Hydrate>
|
||||
|
@ -18,10 +18,18 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import Script from "next/script";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// @ts-ignore
|
||||
const CastController = dynamic(() => import("./state").then((x) => x.CastController), {
|
||||
loading: () => null,
|
||||
});
|
||||
|
||||
export const CastProvider = () => {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
window.__onGCastApiAvailable = (isAvailable) => {
|
||||
if (!isAvailable) return;
|
||||
@ -34,9 +42,13 @@ export const CastProvider = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Script
|
||||
src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"
|
||||
strategy="lazyOnload"
|
||||
/>
|
||||
<>
|
||||
<Script
|
||||
src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"
|
||||
strategy="lazyOnload"
|
||||
onReady={() => setLoaded(true)}
|
||||
/>
|
||||
{loaded && <CastController />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -20,16 +20,21 @@
|
||||
|
||||
import { Pause, PlayArrow, SkipNext, SkipPrevious } from "@mui/icons-material";
|
||||
import { Box, IconButton, Paper, Tooltip, Typography } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { useRouter } from "next/router";
|
||||
import { Image } from "~/components/poster";
|
||||
import { ProgressText, VolumeSlider } from "~/player/components/left-buttons";
|
||||
import { ProgressBar } from "../components/progress-bar";
|
||||
import { mediaAtom } from "./state";
|
||||
|
||||
export const CastMiniPlayer = () => {
|
||||
const { t } = useTranslation("player");
|
||||
const router = useRouter();
|
||||
|
||||
const [media, setMedia] = useAtom(mediaAtom);
|
||||
console.log(media)
|
||||
|
||||
const name = "Ansatsu Kyoushitsu";
|
||||
const episodeName = "S1:E1 Assassination Time";
|
||||
const thumbnail = "/api/show/ansatsu-kyoushitsu/thumbnail";
|
||||
|
@ -19,8 +19,9 @@
|
||||
*/
|
||||
|
||||
import { atom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { bakedAtom } from "~/utils/jotai-utils";
|
||||
import { stopAtom } from "../state";
|
||||
|
||||
export type Media = {
|
||||
name: string;
|
||||
@ -44,19 +45,35 @@ export const [_playAtom, playAtom] = bakedAtom<boolean, never>(true, (get) => {
|
||||
controller.playOrPause();
|
||||
});
|
||||
export const [_durationAtom, durationAtom] = bakedAtom(1, (get, _, value) => {
|
||||
const { controller } = get(playerAtom);
|
||||
const { player, controller } = get(playerAtom);
|
||||
player.currentTime = value;
|
||||
controller.seek();
|
||||
});
|
||||
|
||||
export const [_mediaAtom, mediaAtom] = bakedAtom<Media | null, string>(null, (get, _, value) => {});
|
||||
export const [_mediaAtom, mediaAtom] = bakedAtom<Media | null, string>(
|
||||
null,
|
||||
async (_, _2, value) => {
|
||||
const session = cast.framework.CastContext.getInstance().getCurrentSession();
|
||||
if (!session) return;
|
||||
const mediaInfo = new chrome.cast.media.MediaInfo(
|
||||
value,
|
||||
process.env.KYOO_URL ?? "http://localhost:5000",
|
||||
);
|
||||
session.loadMedia(new chrome.cast.media.LoadRequest(mediaInfo));
|
||||
},
|
||||
);
|
||||
|
||||
export const useCastController = () => {
|
||||
const { player, controller } = useAtomValue(playerAtom);
|
||||
const setPlay = useSetAtom(_playAtom);
|
||||
const setDuration = useSetAtom(_durationAtom);
|
||||
const setMedia = useSetAtom(_mediaAtom);
|
||||
const stopPlayer = useAtomValue(stopAtom);
|
||||
const media = useAtomValue(mediaAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const context = cast.framework.CastContext.getInstance();
|
||||
|
||||
const eventListeners: [
|
||||
cast.framework.RemotePlayerEventType,
|
||||
(event: cast.framework.RemotePlayerChangedEvent<any>) => void,
|
||||
@ -69,9 +86,23 @@ export const useCastController = () => {
|
||||
],
|
||||
];
|
||||
|
||||
const sessionStateHandler = (event: cast.framework.SessionStateEventData) => {
|
||||
if (event.sessionState === cast.framework.SessionState.SESSION_STARTED) {
|
||||
stopPlayer[0]();
|
||||
setMedia(media);
|
||||
}
|
||||
};
|
||||
|
||||
context.addEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED, sessionStateHandler);
|
||||
for (const [key, handler] of eventListeners) controller.addEventListener(key, handler);
|
||||
return () => {
|
||||
context.removeEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED, sessionStateHandler);
|
||||
for (const [key, handler] of eventListeners) controller.removeEventListener(key, handler);
|
||||
};
|
||||
}, [player, controller, setPlay, setDuration, setMedia]);
|
||||
}, [player, controller, setPlay, setDuration, setMedia, stopPlayer, media]);
|
||||
};
|
||||
|
||||
export const CastController = (props: any) => {
|
||||
useCastController();
|
||||
return <div></div>;
|
||||
};
|
||||
|
@ -84,7 +84,7 @@ export const LeftButtons = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
<VolumeSlider color="white" />
|
||||
<ProgressText />
|
||||
<ProgressText sx={{ color: "white" }} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -27,7 +27,13 @@ import { useState, useEffect, PointerEvent as ReactPointerEvent } from "react";
|
||||
import { Box, styled } from "@mui/material";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { Hover, LoadingIndicator } from "./components/hover";
|
||||
import { fullscreenAtom, playAtom, useSubtitleController, useVideoController } from "./state";
|
||||
import {
|
||||
fullscreenAtom,
|
||||
playAtom,
|
||||
stopAtom,
|
||||
useSubtitleController,
|
||||
useVideoController,
|
||||
} from "./state";
|
||||
import { useRouter } from "next/router";
|
||||
import Head from "next/head";
|
||||
import { makeTitle } from "~/utils/utils";
|
||||
@ -43,13 +49,15 @@ let mouseCallback: NodeJS.Timeout;
|
||||
|
||||
const query = (slug: string): QueryIdentifier<WatchItem> => ({
|
||||
path: ["watch", slug],
|
||||
// @ts-ignore
|
||||
parser: WatchItemP,
|
||||
});
|
||||
|
||||
const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
const { data, error } = useFetch(query(slug));
|
||||
const { playerRef, videoProps, onVideoClick } = useVideoController(data?.link);
|
||||
const { playerRef, videoProps, onVideoClick } = useVideoController(slug, data?.link);
|
||||
const setFullscreen = useSetAtom(fullscreenAtom);
|
||||
const setStopCallback = useSetAtom(stopAtom);
|
||||
const router = useRouter();
|
||||
|
||||
const [isPlaying, setPlay] = useAtom(playAtom);
|
||||
@ -96,6 +104,16 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
useSubtitleController(playerRef, data?.subtitles, data?.fonts);
|
||||
useVideoKeyboard(data?.subtitles, data?.fonts, previous, next);
|
||||
|
||||
useEffect(() => {
|
||||
setStopCallback([ () => {
|
||||
console.log("toto")
|
||||
router.push(data ? (data.isMovie ? `/movie/${data.slug}` : `/show/${data.showSlug}`) : "/");
|
||||
}]);
|
||||
return () => {
|
||||
setStopCallback([() => {}]);
|
||||
};
|
||||
}, [setStopCallback, data, router]);
|
||||
|
||||
if (error) return <ErrorPage {...error} />;
|
||||
|
||||
return (
|
||||
|
@ -84,10 +84,13 @@ export const [_, fullscreenAtom] = bakedAtom(false, async (_, set, value, baker)
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
export const mediaAtom = atom<string | null>(null);
|
||||
// The tuple is only used to prevent jotai from thinking the function is a read func.
|
||||
export const stopAtom = atom<[() => void]>([() => {}]);
|
||||
|
||||
let hls: Hls | null = null;
|
||||
|
||||
export const useVideoController = (links?: { direct: string; transmux: string }) => {
|
||||
export const useVideoController = (slug: string, links?: { direct: string; transmux: string }) => {
|
||||
const player = useRef<HTMLVideoElement>(null);
|
||||
const setPlayer = useSetAtom(playerAtom);
|
||||
const setPlay = useSetAtom(_playAtom);
|
||||
@ -99,6 +102,7 @@ export const useVideoController = (links?: { direct: string; transmux: string })
|
||||
const setVolume = useSetAtom(_volumeAtom);
|
||||
const setMuted = useSetAtom(_mutedAtom);
|
||||
const setFullscreen = useSetAtom(fullscreenAtom);
|
||||
const setMedia = useSetAtom(mediaAtom);
|
||||
const [playMode, setPlayMode] = useAtom(playModeAtom);
|
||||
|
||||
setPlayer(player);
|
||||
@ -110,7 +114,8 @@ export const useVideoController = (links?: { direct: string; transmux: string })
|
||||
|
||||
useEffect(() => {
|
||||
setPlayMode(PlayMode.Direct);
|
||||
}, [links, setPlayMode]);
|
||||
setMedia(slug);
|
||||
}, [slug, links, setPlayMode, setMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
const src = playMode === PlayMode.Direct ? links?.direct : links?.transmux;
|
||||
|
794
front/yarn.lock
794
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user