Allow playback on motion screen

This commit is contained in:
Nicolas Mowen 2024-03-22 08:55:15 -06:00
parent ed5cf4df87
commit 8b89a54e50
3 changed files with 93 additions and 40 deletions

View File

@ -10,7 +10,7 @@ import Hls from "hls.js";
import { isDesktop, isMobile } from "react-device-detect";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import VideoControls from "./PlayerControls";
import VideoControls from "./VideoControls";
const HLS_MIME_TYPE = "application/vnd.apple.mpegurl" as const;
const unsupportedErrorCodes = [
@ -210,6 +210,26 @@ export default function HlsVideoPlayer({
show={controls}
controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen}
onPlayPause={(play) => {
if (!videoRef.current) {
return;
}
if (play) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}}
onSeek={(diff) => {
const currentTime = videoRef.current?.currentTime;
if (!videoRef.current || !currentTime) {
return;
}
videoRef.current.currentTime = Math.max(0, currentTime + diff);
}}
/>
{children}
</div>

View File

@ -32,12 +32,14 @@ const CONTROLS_DEFAULT: VideoControls = {
type VideoControlsProps = {
className?: string;
video: HTMLVideoElement | null;
video?: HTMLVideoElement | null;
features?: VideoControls;
isPlaying: boolean;
show: boolean;
controlsOpen: boolean;
setControlsOpen: (open: boolean) => void;
controlsOpen?: boolean;
setControlsOpen?: (open: boolean) => void;
onPlayPause: (play: boolean) => void;
onSeek: (diff: number) => void;
};
export default function VideoControls({
className,
@ -47,6 +49,8 @@ export default function VideoControls({
show,
controlsOpen,
setControlsOpen,
onPlayPause,
onSeek,
}: VideoControlsProps) {
const playbackRates = useMemo(() => {
if (isSafari) {
@ -59,48 +63,25 @@ export default function VideoControls({
const onReplay = useCallback(
(e: React.MouseEvent<SVGElement>) => {
e.stopPropagation();
const currentTime = video?.currentTime;
if (!video || !currentTime) {
return;
}
video.currentTime = Math.max(0, currentTime - 10);
onSeek(-10);
},
[video],
[onSeek],
);
const onSkip = useCallback(
(e: React.MouseEvent<SVGElement>) => {
e.stopPropagation();
const currentTime = video?.currentTime;
if (!video || !currentTime) {
return;
}
video.currentTime = currentTime + 10;
onSeek(10);
},
[video],
[onSeek],
);
const onTogglePlay = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
if (!video) {
return;
}
if (isPlaying) {
video.pause();
} else {
video.play();
}
onPlayPause(!isPlaying);
},
[isPlaying, video],
[isPlaying, onPlayPause],
);
// volume control
@ -119,7 +100,7 @@ export default function VideoControls({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [video?.volume, video?.muted]);
if (!video || !show) {
if (!show) {
return;
}
@ -127,7 +108,7 @@ export default function VideoControls({
<div
className={`px-4 py-2 flex justify-between items-center gap-8 text-white z-50 bg-black bg-opacity-60 rounded-lg ${className ?? ""}`}
>
{features.volume && (
{video && features.volume && (
<div className="flex justify-normal items-center gap-2">
<VolumeIcon
className="size-5"
@ -161,17 +142,19 @@ export default function VideoControls({
{features.seek && (
<MdForward10 className="size-5 cursor-pointer" onClick={onSkip} />
)}
{features.playbackRate && (
{video && features.playbackRate && (
<DropdownMenu
open={controlsOpen}
open={controlsOpen == true}
onOpenChange={(open) => {
setControlsOpen(open);
if (setControlsOpen) {
setControlsOpen(open);
}
}}
>
<DropdownMenuTrigger>{`${video.playbackRate}x`}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup
onValueChange={(rate) => (video.playbackRate = parseInt(rate))}
onValueChange={(rate) => (video.playbackRate = parseFloat(rate))}
>
{playbackRates.map((rate) => (
<DropdownMenuRadioItem key={rate} value={rate.toString()}>

View File

@ -38,6 +38,7 @@ import PreviewPlayer, {
} from "@/components/player/PreviewPlayer";
import SummaryTimeline from "@/components/timeline/SummaryTimeline";
import { RecordingStartingPoint } from "@/types/record";
import VideoControls from "@/components/player/VideoControls";
type EventViewProps = {
reviews?: ReviewSegment[];
@ -678,6 +679,7 @@ function MotionReview({
);
const [scrubbing, setScrubbing] = useState(false);
const [playing, setPlaying] = useState(false);
// move to next clip
@ -704,6 +706,33 @@ function MotionReview({
});
}, [currentTime, currentTimeRange, timeRangeSegments]);
// playback
useEffect(() => {
if (!playing) {
return;
}
const startTime = currentTime;
let counter = 0;
const intervalId = setInterval(() => {
counter += 0.5;
if (startTime + counter >= timeRange.before) {
setPlaying(false);
return;
}
setCurrentTime(startTime + counter);
}, 60);
return () => {
clearInterval(intervalId);
};
// do not render when current time changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playing]);
if (!relevantPreviews) {
return <ActivityIndicator />;
}
@ -762,9 +791,30 @@ function MotionReview({
motion_events={motionData ?? []}
severityType="significant_motion"
contentRef={contentRef}
onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)}
onHandlebarDraggingChange={(scrubbing) => {
if (playing && scrubbing) {
setPlaying(false);
}
setScrubbing(scrubbing);
}}
/>
</div>
<VideoControls
className="absolute bottom-16 left-1/2 -translate-x-1/2 bg-secondary"
features={{
volume: false,
seek: true,
playbackRate: false,
}}
isPlaying={playing}
onPlayPause={setPlaying}
onSeek={(diff) => {
setCurrentTime(currentTime + diff);
}}
show={currentTime < timeRange.before - 4}
/>
</>
);
}