Add ability to set playback speed on motion playback (#10628)

* Allow control of playback rate on motion page

* Apply playback rate
This commit is contained in:
Nicolas Mowen 2024-03-23 10:24:57 -06:00 committed by GitHub
parent 76a114a3cd
commit 63bf986e08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 26 additions and 14 deletions

View File

@ -160,6 +160,7 @@ export default function HlsVideoPlayer({
show={controls} show={controls}
controlsOpen={controlsOpen} controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen} setControlsOpen={setControlsOpen}
playbackRate={videoRef.current?.playbackRate ?? 1}
onPlayPause={(play) => { onPlayPause={(play) => {
if (!videoRef.current) { if (!videoRef.current) {
return; return;
@ -180,6 +181,9 @@ export default function HlsVideoPlayer({
videoRef.current.currentTime = Math.max(0, currentTime + diff); videoRef.current.currentTime = Math.max(0, currentTime + diff);
}} }}
onSetPlaybackRate={(rate) =>
videoRef.current ? (videoRef.current.playbackRate = rate) : null
}
/> />
{children} {children}
</div> </div>

View File

@ -30,6 +30,7 @@ const CONTROLS_DEFAULT: VideoControls = {
seek: true, seek: true,
playbackRate: true, playbackRate: true,
}; };
const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16];
type VideoControlsProps = { type VideoControlsProps = {
className?: string; className?: string;
@ -38,9 +39,12 @@ type VideoControlsProps = {
isPlaying: boolean; isPlaying: boolean;
show: boolean; show: boolean;
controlsOpen?: boolean; controlsOpen?: boolean;
playbackRates?: number[];
playbackRate: number;
setControlsOpen?: (open: boolean) => void; setControlsOpen?: (open: boolean) => void;
onPlayPause: (play: boolean) => void; onPlayPause: (play: boolean) => void;
onSeek: (diff: number) => void; onSeek: (diff: number) => void;
onSetPlaybackRate: (rate: number) => void;
}; };
export default function VideoControls({ export default function VideoControls({
className, className,
@ -49,18 +53,13 @@ export default function VideoControls({
isPlaying, isPlaying,
show, show,
controlsOpen, controlsOpen,
playbackRates = PLAYBACK_RATE_DEFAULT,
playbackRate,
setControlsOpen, setControlsOpen,
onPlayPause, onPlayPause,
onSeek, onSeek,
onSetPlaybackRate,
}: VideoControlsProps) { }: VideoControlsProps) {
const playbackRates = useMemo(() => {
if (isSafari) {
return [0.5, 1, 2];
} else {
return [0.5, 1, 2, 4, 8, 16];
}
}, []);
const onReplay = useCallback( const onReplay = useCallback(
(e: React.MouseEvent<SVGElement>) => { (e: React.MouseEvent<SVGElement>) => {
e.stopPropagation(); e.stopPropagation();
@ -177,7 +176,7 @@ export default function VideoControls({
{features.seek && ( {features.seek && (
<MdForward10 className="size-5 cursor-pointer" onClick={onSkip} /> <MdForward10 className="size-5 cursor-pointer" onClick={onSkip} />
)} )}
{video && features.playbackRate && ( {features.playbackRate && (
<DropdownMenu <DropdownMenu
open={controlsOpen == true} open={controlsOpen == true}
onOpenChange={(open) => { onOpenChange={(open) => {
@ -186,10 +185,10 @@ export default function VideoControls({
} }
}} }}
> >
<DropdownMenuTrigger>{`${video.playbackRate}x`}</DropdownMenuTrigger> <DropdownMenuTrigger>{`${playbackRate}x`}</DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuRadioGroup <DropdownMenuRadioGroup
onValueChange={(rate) => (video.playbackRate = parseFloat(rate))} onValueChange={(rate) => onSetPlaybackRate(parseFloat(rate))}
> >
{playbackRates.map((rate) => ( {playbackRates.map((rate) => (
<DropdownMenuRadioItem key={rate} value={rate.toString()}> <DropdownMenuRadioItem key={rate} value={rate.toString()}>

View File

@ -716,11 +716,15 @@ function MotionReview({
// playback // playback
const [playbackRate, setPlaybackRate] = useState(8);
const [controlsOpen, setControlsOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!playing) { if (!playing) {
return; return;
} }
const interval = 500 / playbackRate;
const startTime = currentTime; const startTime = currentTime;
let counter = 0; let counter = 0;
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
@ -732,14 +736,14 @@ function MotionReview({
} }
setCurrentTime(startTime + counter); setCurrentTime(startTime + counter);
}, 60); }, interval);
return () => { return () => {
clearInterval(intervalId); clearInterval(intervalId);
}; };
// do not render when current time changes // do not render when current time changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [playing]); }, [playing, playbackRate]);
if (!relevantPreviews) { if (!relevantPreviews) {
return <ActivityIndicator />; return <ActivityIndicator />;
@ -815,9 +819,13 @@ function MotionReview({
features={{ features={{
volume: false, volume: false,
seek: true, seek: true,
playbackRate: false, playbackRate: true,
}} }}
isPlaying={playing} isPlaying={playing}
playbackRates={[4, 8, 12, 16]}
playbackRate={playbackRate}
controlsOpen={controlsOpen}
setControlsOpen={setControlsOpen}
onPlayPause={setPlaying} onPlayPause={setPlaying}
onSeek={(diff) => { onSeek={(diff) => {
const wasPlaying = playing; const wasPlaying = playing;
@ -832,6 +840,7 @@ function MotionReview({
setTimeout(() => setPlaying(true), 100); setTimeout(() => setPlaying(true), 100);
} }
}} }}
onSetPlaybackRate={setPlaybackRate}
show={currentTime < timeRange.before - 4} show={currentTime < timeRange.before - 4}
/> />
</> </>