diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 0ea37fda5..66124631b 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -6,7 +6,7 @@ import { useState, } from "react"; import Hls from "hls.js"; -import { isAndroid, isDesktop, isMobile } from "react-device-detect"; +import { isAndroid, isDesktop, isIOS, isMobile } from "react-device-detect"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"; import VideoControls from "./VideoControls"; import { VideoResolutionType } from "@/types/live"; @@ -28,24 +28,28 @@ type HlsVideoPlayerProps = { visible: boolean; currentSource: string; hotKeys: boolean; + fullscreen: boolean; onClipEnded?: () => void; onPlayerLoaded?: () => void; onTimeUpdate?: (time: number) => void; onPlaying?: () => void; setFullResolution?: React.Dispatch>; onUploadFrame?: (playTime: number) => Promise | undefined; + setFullscreen?: (full: boolean) => void; }; export default function HlsVideoPlayer({ videoRef, visible, currentSource, hotKeys, + fullscreen, onClipEnded, onPlayerLoaded, onTimeUpdate, onPlaying, setFullResolution, onUploadFrame, + setFullscreen, }: HlsVideoPlayerProps) { const { data: config } = useSWR("config"); @@ -153,6 +157,7 @@ export default function HlsVideoPlayer({ seek: true, playbackRate: true, plusUpload: config?.plus?.enabled == true, + fullscreen: !isIOS, }} setControlsOpen={setControlsOpen} setMuted={setMuted} @@ -196,6 +201,8 @@ export default function HlsVideoPlayer({ } } }} + fullscreen={fullscreen} + setFullscreen={setFullscreen} /> void; setMuted?: (muted: boolean) => void; onPlayPause: (play: boolean) => void; onSeek: (diff: number) => void; onSetPlaybackRate: (rate: number) => void; onUploadFrame?: () => void; + setFullscreen?: (full: boolean) => void; }; export default function VideoControls({ className, @@ -75,12 +80,14 @@ export default function VideoControls({ playbackRates = PLAYBACK_RATE_DEFAULT, playbackRate, hotKeys = true, + fullscreen, setControlsOpen, setMuted, onPlayPause, onSeek, onSetPlaybackRate, onUploadFrame, + setFullscreen, }: VideoControlsProps) { const onReplay = useCallback( (e: React.MouseEvent) => { @@ -248,6 +255,14 @@ export default function VideoControls({ onUploadFrame={onUploadFrame} /> )} + {features.fullscreen && setFullscreen && ( +
setFullscreen(!fullscreen)} + > + {fullscreen ? : } +
+ )} ); } diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index e3e677530..e67468e6c 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -24,10 +24,12 @@ type DynamicVideoPlayerProps = { startTimestamp?: number; isScrubbing: boolean; hotKeys: boolean; + fullscreen: boolean; onControllerReady: (controller: DynamicVideoController) => void; onTimestampUpdate?: (timestamp: number) => void; onClipEnded?: () => void; setFullResolution: React.Dispatch>; + setFullscreen: (full: boolean) => void; }; export default function DynamicVideoPlayer({ className, @@ -37,10 +39,12 @@ export default function DynamicVideoPlayer({ startTimestamp, isScrubbing, hotKeys, + fullscreen, onControllerReady, onTimestampUpdate, onClipEnded, setFullResolution, + setFullscreen, }: DynamicVideoPlayerProps) { const apiHost = useApiHost(); const { data: config } = useSWR("config"); @@ -184,6 +188,7 @@ export default function DynamicVideoPlayer({ visible={!(isScrubbing || isLoading)} currentSource={source} hotKeys={hotKeys} + fullscreen={fullscreen} onTimeUpdate={onTimeUpdate} onPlayerLoaded={onPlayerLoaded} onClipEnded={onClipEnded} @@ -201,6 +206,7 @@ export default function DynamicVideoPlayer({ }} setFullResolution={setFullResolution} onUploadFrame={onUploadFrameToPlus} + setFullscreen={setFullscreen} /> (null); + const mainLayoutRef = useRef(null); const previewRefs = useRef<{ [camera: string]: PreviewController }>({}); const [playbackStart, setPlaybackStart] = useState(startTime); @@ -208,7 +210,36 @@ export function RecordingView({ [currentTime], ); - // motion timeline data + // fullscreen + + const [fullscreen, setFullscreen] = useState(false); + + const onToggleFullscreen = useCallback( + (full: boolean) => { + if (full) { + mainLayoutRef.current?.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }, + [mainLayoutRef], + ); + + useEffect(() => { + if (mainLayoutRef.current == null) { + return; + } + const fsListener = () => { + setFullscreen(document.fullscreenElement != null); + }; + document.addEventListener("fullscreenchange", fsListener); + + return () => { + document.removeEventListener("fullscreenchange", fsListener); + }; + }, [mainLayoutRef]); + + // layout const getCameraAspect = useCallback( (cam: string) => { @@ -372,19 +403,40 @@ export function RecordingView({
-
+
{ setPlayerTime(timestamp); setCurrentTime(timestamp); @@ -413,11 +466,17 @@ export function RecordingView({ }} isScrubbing={scrubbing || exportMode == "timeline"} setFullResolution={setFullResolution} + setFullscreen={onToggleFullscreen} />
{isDesktop && (
{allCameras.map((cam) => { if (cam == mainCamera) {