mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add media session management
This commit is contained in:
parent
2c06924792
commit
d7dc66301e
@ -37,11 +37,12 @@ type Action =
|
||||
| { type: "mute" }
|
||||
| { type: "fullscreen" }
|
||||
| { type: "seek"; value: number }
|
||||
| { type: "seekTo"; value: number }
|
||||
| { type: "seekPercent"; value: number }
|
||||
| { type: "volume"; value: number }
|
||||
| { type: "subtitle"; subtitles: Track[]; fonts: Font[] };
|
||||
|
||||
const keyboardReducerAtom = atom<null, Action>(null, (get, set, action) => {
|
||||
export const reducerAtom = atom<null, Action>(null, (get, set, action) => {
|
||||
switch (action.type) {
|
||||
case "play":
|
||||
set(playAtom, !get(playAtom));
|
||||
@ -55,6 +56,9 @@ const keyboardReducerAtom = atom<null, Action>(null, (get, set, action) => {
|
||||
case "seek":
|
||||
set(progressAtom, get(progressAtom) + action.value);
|
||||
break;
|
||||
case "seekTo":
|
||||
set(progressAtom, action.value);
|
||||
break;
|
||||
case "seekPercent":
|
||||
set(progressAtom, (get(durationAtom) * action.value) / 100);
|
||||
break;
|
||||
@ -83,7 +87,7 @@ export const useVideoKeyboard = (
|
||||
previousEpisode?: string,
|
||||
nextEpisode?: string,
|
||||
) => {
|
||||
const reducer = useSetAtom(keyboardReducerAtom);
|
||||
const reducer = useSetAtom(reducerAtom);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
|
92
front/src/player/media-session.tsx
Normal file
92
front/src/player/media-session.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Kyoo - A portable and vast media library solution.
|
||||
* Copyright (c) Kyoo.
|
||||
*
|
||||
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
*
|
||||
* Kyoo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Kyoo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { reducerAtom } from "./keyboard";
|
||||
import { durationAtom, playAtom, progressAtom } from "./state";
|
||||
|
||||
export const MediaSessionManager = ({
|
||||
title,
|
||||
image,
|
||||
previous,
|
||||
next,
|
||||
}: {
|
||||
title?: string;
|
||||
image?: string | null;
|
||||
previous?: string;
|
||||
next?: string;
|
||||
}) => {
|
||||
const [isPlaying, setPlay] = useAtom(playAtom);
|
||||
const progress = useAtomValue(progressAtom);
|
||||
const duration = useAtomValue(durationAtom);
|
||||
const reducer = useSetAtom(reducerAtom);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!("mediaSession" in navigator)) return;
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: title,
|
||||
artwork: image ? [{ src: image }] : undefined,
|
||||
});
|
||||
}, [title, image]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!("mediaSession" in navigator)) return;
|
||||
const actions: [MediaSessionAction, MediaSessionActionHandler | null][] = [
|
||||
["play", () => setPlay(true)],
|
||||
["pause", () => setPlay(false)],
|
||||
["previoustrack", previous ? () => router.push(previous) : null],
|
||||
["nexttrack", next ? () => router.push(next) : null],
|
||||
[
|
||||
"seekbackward",
|
||||
(evt: MediaSessionActionDetails) =>
|
||||
reducer({ type: "seek", value: evt.seekOffset ? -evt.seekOffset : -10 }),
|
||||
],
|
||||
[
|
||||
"seekforward",
|
||||
(evt: MediaSessionActionDetails) =>
|
||||
reducer({ type: "seek", value: evt.seekOffset ? evt.seekOffset : 10 }),
|
||||
],
|
||||
[
|
||||
"seekto",
|
||||
(evt: MediaSessionActionDetails) => reducer({ type: "seekTo", value: evt.seekTime! }),
|
||||
],
|
||||
];
|
||||
|
||||
for (const [action, handler] of actions) {
|
||||
try {
|
||||
navigator.mediaSession.setActionHandler(action, handler);
|
||||
} catch {}
|
||||
}
|
||||
}, [setPlay, reducer, router, previous, next]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!("mediaSession" in navigator)) return;
|
||||
navigator.mediaSession.playbackState = isPlaying ? "playing" : "paused";
|
||||
}, [isPlaying]);
|
||||
useEffect(() => {
|
||||
if (!("mediaSession" in navigator)) return;
|
||||
navigator.mediaSession.setPositionState({ position: progress, duration, playbackRate: 1 });
|
||||
}, [progress, duration]);
|
||||
|
||||
return null;
|
||||
};
|
@ -33,6 +33,7 @@ import Head from "next/head";
|
||||
import { makeTitle } from "~/utils/utils";
|
||||
import { episodeDisplayNumber } from "~/components/episode";
|
||||
import { useVideoKeyboard } from "./keyboard";
|
||||
import { MediaSessionManager } from "./media-session";
|
||||
|
||||
// Callback used to hide the controls when the mouse goes iddle. This is stored globally to clear the old timeout
|
||||
// if the mouse moves again (if this is stored as a state, the whole page is redrawn on mouse move)
|
||||
@ -55,6 +56,13 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
const [menuOpenned, setMenuOpen] = useState(false);
|
||||
const displayControls = showHover || !isPlaying || mouseMoved || menuOpenned;
|
||||
|
||||
const previous =
|
||||
data && !data.isMovie && data.previousEpisode
|
||||
? `/watch/${data.previousEpisode.slug}`
|
||||
: undefined;
|
||||
const next =
|
||||
data && !data.isMovie && data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : undefined;
|
||||
|
||||
const mouseHasMoved = () => {
|
||||
setMouseMoved(true);
|
||||
if (mouseCallback) clearTimeout(mouseCallback);
|
||||
@ -84,16 +92,7 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
}, [setFullscreen]);
|
||||
|
||||
useSubtitleController(playerRef, data?.subtitles, data?.fonts);
|
||||
useVideoKeyboard(
|
||||
data?.subtitles,
|
||||
data?.fonts,
|
||||
data && !data.isMovie && data.previousEpisode
|
||||
? `/watch/${data.previousEpisode.slug}`
|
||||
: undefined,
|
||||
data && !data.isMovie && data.nextEpisode
|
||||
? `/watch/${data.nextEpisode.slug}`
|
||||
: undefined,
|
||||
);
|
||||
useVideoKeyboard(data?.subtitles, data?.fonts, previous, next);
|
||||
|
||||
if (error) return <ErrorPage {...error} />;
|
||||
|
||||
@ -117,6 +116,7 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
<meta name="description" content={data.overview ?? undefined} />
|
||||
</Head>
|
||||
)}
|
||||
<MediaSessionManager title={data?.name} image={data?.thumbnail} next={next} previous={previous} />
|
||||
<style jsx global>{`
|
||||
::cue {
|
||||
background-color: transparent;
|
||||
|
Loading…
x
Reference in New Issue
Block a user