import { MutableRefObject, ReactNode, useEffect, useRef, useState, } from "react"; import Hls from "hls.js"; import { isAndroid, isDesktop, isMobile } from "react-device-detect"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import VideoControls from "./VideoControls"; // Android native hls does not seek correctly const USE_NATIVE_HLS = !isAndroid; const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const; const unsupportedErrorCodes = [ MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, MediaError.MEDIA_ERR_DECODE, ]; type HlsVideoPlayerProps = { children?: ReactNode; videoRef: MutableRefObject; visible: boolean; currentSource: string; hotKeys: boolean; onClipEnded?: () => void; onPlayerLoaded?: () => void; onTimeUpdate?: (time: number) => void; onPlaying?: () => void; }; export default function HlsVideoPlayer({ children, videoRef, visible, currentSource, hotKeys, onClipEnded, onPlayerLoaded, onTimeUpdate, onPlaying, }: HlsVideoPlayerProps) { // playback const hlsRef = useRef(); const [useHlsCompat, setUseHlsCompat] = useState(false); const [loadedMetadata, setLoadedMetadata] = useState(false); useEffect(() => { if (!videoRef.current) { return; } if (USE_NATIVE_HLS && videoRef.current.canPlayType(HLS_MIME_TYPE)) { return; } else if (Hls.isSupported()) { setUseHlsCompat(true); } }, [videoRef]); useEffect(() => { if (!videoRef.current) { return; } const currentPlaybackRate = videoRef.current.playbackRate; if (!useHlsCompat) { videoRef.current.src = currentSource; videoRef.current.load(); return; } if (!hlsRef.current) { hlsRef.current = new Hls(); hlsRef.current.attachMedia(videoRef.current); } hlsRef.current.loadSource(currentSource); videoRef.current.playbackRate = currentPlaybackRate; }, [videoRef, hlsRef, useHlsCompat, currentSource]); // controls const [isPlaying, setIsPlaying] = useState(true); const [mobileCtrlTimeout, setMobileCtrlTimeout] = useState(); const [controls, setControls] = useState(isMobile); const [controlsOpen, setControlsOpen] = useState(false); return ( ); }