Add hls support on the web

This commit is contained in:
Zoe Roux 2022-12-31 00:07:47 +09:00
parent 4b92b8a38e
commit 67da1563be
2 changed files with 70 additions and 55 deletions

View File

@ -18,20 +18,13 @@
* 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 { useEffect, useLayoutEffect, useRef } from "react";
import { memo, useEffect, useLayoutEffect, useRef } from "react";
import NativeVideo, { VideoProperties as VideoProps } from "./video";
import { bakedAtom } from "../jotai-utils";
import { Platform } from "react-native";
enum PlayMode {
Direct,
Transmux,
}
const playModeAtom = atom<PlayMode>(PlayMode.Direct);
export const playAtom = atom(true);
export const loadAtom = atom(false);
@ -70,7 +63,9 @@ export const [privateFullscreen, fullscreenAtom] = bakedAtom(
export const subtitleAtom = atom<Track | null>(null);
export const Video = ({
const MemoVideo = memo(NativeVideo);
export const Video = memo(function _Video({
links,
setError,
fonts,
@ -78,7 +73,7 @@ export const Video = ({
}: {
links?: WatchItem["link"];
setError: (error: string | undefined) => void;
} & VideoProps) => {
} & Partial<VideoProps>) {
const ref = useRef<NativeVideo | null>(null);
const isPlaying = useAtomValue(playAtom);
const setLoad = useSetAtom(loadAtom);
@ -110,37 +105,13 @@ export const Video = ({
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;
return (
<NativeVideo
<MemoVideo
ref={ref}
{...props}
source={{ uri: links.direct }}
// @ts-ignore Web only
source={{ uri: links.direct, transmux: links.transmux }}
paused={!isPlaying}
muted={isMuted}
volume={volume}
@ -170,4 +141,4 @@ export const Video = ({
// },
/>
);
};
});

View File

@ -19,25 +19,39 @@
*/
import { Font, Track } from "@kyoo/models";
import { forwardRef, RefObject, useEffect, useImperativeHandle, useRef } from "react";
import { VideoProperties } from "react-native-video";
import { useAtomValue } from "jotai";
import {
forwardRef,
RefObject,
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef,
} from "react";
import { VideoProps } from "react-native-video";
import { atom, useAtom, useAtomValue } from "jotai";
import { useYoshiki } from "yoshiki";
import SubtitleOctopus from "libass-wasm";
import { subtitleAtom } from "./state";
// import Hls from "hls.js";
// let hls: Hls | null = null;
// TODO fallback via links and hls.
import Hls from "hls.js";
declare module "react-native-video" {
interface VideoProperties {
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 },
forwaredRef,
) {
@ -56,7 +70,7 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
useEffect(() => {
if (paused) ref.current?.pause();
else ref.current?.play();
else ref.current?.play().catch(() => {});
}, [paused]);
useEffect(() => {
if (!ref.current || !volume) return;
@ -67,10 +81,33 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
const subtitle = useAtomValue(subtitleAtom);
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 (
<video
ref={ref}
src={typeof source === "number" ? undefined : source.uri}
muted={muted}
autoPlay={!paused}
onCanPlay={() => onBuffer?.call(null, { isBuffering: false })}
@ -89,11 +126,18 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProperties>(fun
seekableDuration: 0,
});
}}
onError={() =>
onError?.call(null, {
error: { "": "", errorString: ref.current?.error?.message ?? "Unknown error" },
})
}
onError={() => {
if (
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%" })}
/>
);