mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Add hls support on the web
This commit is contained in:
parent
4b92b8a38e
commit
67da1563be
@ -18,20 +18,13 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Font, Track, WatchItem } from "@kyoo/models";
|
import { Track, WatchItem } from "@kyoo/models";
|
||||||
import { atom, useAtomValue, useSetAtom } from "jotai";
|
import { atom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
import { memo, useEffect, useLayoutEffect, useRef } from "react";
|
||||||
import NativeVideo, { VideoProperties as VideoProps } from "./video";
|
import NativeVideo, { VideoProperties as VideoProps } from "./video";
|
||||||
import { bakedAtom } from "../jotai-utils";
|
import { bakedAtom } from "../jotai-utils";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
enum PlayMode {
|
|
||||||
Direct,
|
|
||||||
Transmux,
|
|
||||||
}
|
|
||||||
|
|
||||||
const playModeAtom = atom<PlayMode>(PlayMode.Direct);
|
|
||||||
|
|
||||||
export const playAtom = atom(true);
|
export const playAtom = atom(true);
|
||||||
export const loadAtom = atom(false);
|
export const loadAtom = atom(false);
|
||||||
|
|
||||||
@ -70,7 +63,9 @@ export const [privateFullscreen, fullscreenAtom] = bakedAtom(
|
|||||||
|
|
||||||
export const subtitleAtom = atom<Track | null>(null);
|
export const subtitleAtom = atom<Track | null>(null);
|
||||||
|
|
||||||
export const Video = ({
|
const MemoVideo = memo(NativeVideo);
|
||||||
|
|
||||||
|
export const Video = memo(function _Video({
|
||||||
links,
|
links,
|
||||||
setError,
|
setError,
|
||||||
fonts,
|
fonts,
|
||||||
@ -78,7 +73,7 @@ export const Video = ({
|
|||||||
}: {
|
}: {
|
||||||
links?: WatchItem["link"];
|
links?: WatchItem["link"];
|
||||||
setError: (error: string | undefined) => void;
|
setError: (error: string | undefined) => void;
|
||||||
} & VideoProps) => {
|
} & Partial<VideoProps>) {
|
||||||
const ref = useRef<NativeVideo | null>(null);
|
const ref = useRef<NativeVideo | null>(null);
|
||||||
const isPlaying = useAtomValue(playAtom);
|
const isPlaying = useAtomValue(playAtom);
|
||||||
const setLoad = useSetAtom(loadAtom);
|
const setLoad = useSetAtom(loadAtom);
|
||||||
@ -110,37 +105,13 @@ export const Video = ({
|
|||||||
|
|
||||||
const subtitle = useAtomValue(subtitleAtom);
|
const subtitle = useAtomValue(subtitleAtom);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// setPlayMode(PlayMode.Direct);
|
|
||||||
// }, [links, setPlayMode]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const src = playMode === PlayMode.Direct ? links?.direct : links?.transmux;
|
|
||||||
|
|
||||||
// if (!player?.current || !src) return;
|
|
||||||
// if (
|
|
||||||
// playMode == PlayMode.Direct ||
|
|
||||||
// player.current.canPlayType("application/vnd.apple.mpegurl")
|
|
||||||
// ) {
|
|
||||||
// player.current.src = src;
|
|
||||||
// } else {
|
|
||||||
// if (hls === null) hls = new Hls();
|
|
||||||
// hls.loadSource(src);
|
|
||||||
// hls.attachMedia(player.current);
|
|
||||||
// hls.on(Hls.Events.MANIFEST_LOADED, async () => {
|
|
||||||
// try {
|
|
||||||
// await player.current?.play();
|
|
||||||
// } catch {}
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }, [playMode, links, player]);
|
|
||||||
|
|
||||||
if (!links) return null;
|
if (!links) return null;
|
||||||
return (
|
return (
|
||||||
<NativeVideo
|
<MemoVideo
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
source={{ uri: links.direct }}
|
// @ts-ignore Web only
|
||||||
|
source={{ uri: links.direct, transmux: links.transmux }}
|
||||||
paused={!isPlaying}
|
paused={!isPlaying}
|
||||||
muted={isMuted}
|
muted={isMuted}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
@ -170,4 +141,4 @@ export const Video = ({
|
|||||||
// },
|
// },
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -19,25 +19,39 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Font, Track } from "@kyoo/models";
|
import { Font, Track } from "@kyoo/models";
|
||||||
import { forwardRef, RefObject, useEffect, useImperativeHandle, useRef } from "react";
|
import {
|
||||||
import { VideoProperties } from "react-native-video";
|
forwardRef,
|
||||||
import { useAtomValue } from "jotai";
|
RefObject,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
import { VideoProps } from "react-native-video";
|
||||||
|
import { atom, useAtom, useAtomValue } from "jotai";
|
||||||
import { useYoshiki } from "yoshiki";
|
import { useYoshiki } from "yoshiki";
|
||||||
import SubtitleOctopus from "libass-wasm";
|
import SubtitleOctopus from "libass-wasm";
|
||||||
import { subtitleAtom } from "./state";
|
import { subtitleAtom } from "./state";
|
||||||
// import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
|
|
||||||
// let hls: Hls | null = null;
|
|
||||||
|
|
||||||
// TODO fallback via links and hls.
|
|
||||||
|
|
||||||
declare module "react-native-video" {
|
declare module "react-native-video" {
|
||||||
interface VideoProperties {
|
interface VideoProperties {
|
||||||
fonts?: Font[];
|
fonts?: Font[];
|
||||||
}
|
}
|
||||||
|
export type VideoProps = Omit<VideoProperties, "source"> & {
|
||||||
|
source: { uri?: string; transmux?: string };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(function _Video(
|
enum PlayMode {
|
||||||
|
Direct,
|
||||||
|
Transmux,
|
||||||
|
}
|
||||||
|
|
||||||
|
const playModeAtom = atom<PlayMode>(PlayMode.Direct);
|
||||||
|
let hls: Hls | null = null;
|
||||||
|
|
||||||
|
const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function _Video(
|
||||||
{ source, paused, muted, volume, onBuffer, onLoad, onProgress, onError, fonts },
|
{ source, paused, muted, volume, onBuffer, onLoad, onProgress, onError, fonts },
|
||||||
forwaredRef,
|
forwaredRef,
|
||||||
) {
|
) {
|
||||||
@ -56,7 +70,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (paused) ref.current?.pause();
|
if (paused) ref.current?.pause();
|
||||||
else ref.current?.play();
|
else ref.current?.play().catch(() => {});
|
||||||
}, [paused]);
|
}, [paused]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current || !volume) return;
|
if (!ref.current || !volume) return;
|
||||||
@ -67,10 +81,33 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
|
|||||||
const subtitle = useAtomValue(subtitleAtom);
|
const subtitle = useAtomValue(subtitleAtom);
|
||||||
useSubtitle(ref, subtitle, fonts);
|
useSubtitle(ref, subtitle, fonts);
|
||||||
|
|
||||||
|
const [playMode, setPlayMode] = useAtom(playModeAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
setPlayMode(PlayMode.Direct);
|
||||||
|
}, [source.uri, setPlayMode]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
console.log("toto");
|
||||||
|
const src = playMode === PlayMode.Direct ? source?.uri : source?.transmux;
|
||||||
|
|
||||||
|
if (!ref?.current || !src) return;
|
||||||
|
if (playMode == PlayMode.Direct || ref.current.canPlayType("application/vnd.apple.mpegurl")) {
|
||||||
|
ref.current.src = src;
|
||||||
|
} else {
|
||||||
|
if (hls === null) hls = new Hls();
|
||||||
|
hls.loadSource(src);
|
||||||
|
hls.attachMedia(ref.current);
|
||||||
|
hls.on(Hls.Events.MANIFEST_LOADED, async () => {
|
||||||
|
try {
|
||||||
|
await ref.current?.play();
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [playMode, source?.uri, source?.transmux]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={ref}
|
ref={ref}
|
||||||
src={typeof source === "number" ? undefined : source.uri}
|
|
||||||
muted={muted}
|
muted={muted}
|
||||||
autoPlay={!paused}
|
autoPlay={!paused}
|
||||||
onCanPlay={() => onBuffer?.call(null, { isBuffering: false })}
|
onCanPlay={() => onBuffer?.call(null, { isBuffering: false })}
|
||||||
@ -89,11 +126,18 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
|
|||||||
seekableDuration: 0,
|
seekableDuration: 0,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onError={() =>
|
onError={() => {
|
||||||
onError?.call(null, {
|
if (
|
||||||
error: { "": "", errorString: ref.current?.error?.message ?? "Unknown error" },
|
ref?.current?.error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED &&
|
||||||
})
|
playMode !== PlayMode.Transmux
|
||||||
}
|
)
|
||||||
|
setPlayMode(PlayMode.Transmux);
|
||||||
|
else {
|
||||||
|
onError?.call(null, {
|
||||||
|
error: { "": "", errorString: ref.current?.error?.message ?? "Unknown error" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...css({ width: "100%", height: "100%" })}
|
{...css({ width: "100%", height: "100%" })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user