From c66f552280ceffcd0af4592b034a70f70ad8a185 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 15 Mar 2024 06:52:38 -0600 Subject: [PATCH] Improve Recordings loading (#10462) * Show skeleton until video players finishes loading * Clean up android logic * Ensure mobile view video is consistent * Cleanup * Only show when not scrubbing * Don't use loading * Start preview at correct time too * Fix react race condition * Be wait for seek to show video player --- web/src/components/player/HlsVideoPlayer.tsx | 3 +++ web/src/components/player/PreviewPlayer.tsx | 27 +++++++++---------- .../player/dynamic/DynamicVideoController.ts | 16 ++++++++++- .../player/dynamic/DynamicVideoPlayer.tsx | 18 ++++++++++--- web/src/views/events/RecordingView.tsx | 17 ++++++++++++ 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index 6d6df7268..0ede0ea3c 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -42,6 +42,7 @@ type HlsVideoPlayerProps = { onClipEnded?: () => void; onPlayerLoaded?: () => void; onTimeUpdate?: (time: number) => void; + onPlaying?: () => void; }; export default function HlsVideoPlayer({ className, @@ -51,6 +52,7 @@ export default function HlsVideoPlayer({ onClipEnded, onPlayerLoaded, onTimeUpdate, + onPlaying, }: HlsVideoPlayerProps) { // playback @@ -183,6 +185,7 @@ export default function HlsVideoPlayer({ setMobileCtrlTimeout(setTimeout(() => setControls(false), 4000)); } }} + onPlaying={onPlaying} onPause={() => { setIsPlaying(false); diff --git a/web/src/components/player/PreviewPlayer.tsx b/web/src/components/player/PreviewPlayer.tsx index 9206f7e9b..2539faaa2 100644 --- a/web/src/components/player/PreviewPlayer.tsx +++ b/web/src/components/player/PreviewPlayer.tsx @@ -272,26 +272,25 @@ class PreviewVideoController extends PreviewController { } if (this.timeToSeek) { - if ( - Math.round(this.previewRef.current.currentTime + this.preview.start) != - Math.round(this.timeToSeek) - ) { - if (isAndroid) { - const currentTs = - this.previewRef.current.currentTime + this.preview.start; - const diff = this.timeToSeek - currentTs; + const diff = + Math.round(this.timeToSeek) - + Math.round(this.previewRef.current.currentTime + this.preview.start); + if (Math.abs(diff) > 1) { + let seekTime; + if (isAndroid) { if (diff < 30) { - this.previewRef.current.currentTime = - this.previewRef.current.currentTime + diff / 2; + seekTime = Math.round( + this.previewRef.current.currentTime + diff / 2, + ); } else { - this.previewRef.current.currentTime = - this.timeToSeek - this.preview.start; + seekTime = Math.round(this.timeToSeek - this.preview.start); } } else { - this.previewRef.current.currentTime = - this.timeToSeek - this.preview.start; + seekTime = this.timeToSeek - this.preview.start; } + + this.previewRef.current.currentTime = seekTime; } else { this.seeking = false; this.timeToSeek = undefined; diff --git a/web/src/components/player/dynamic/DynamicVideoController.ts b/web/src/components/player/dynamic/DynamicVideoController.ts index 2b3956db7..d30976447 100644 --- a/web/src/components/player/dynamic/DynamicVideoController.ts +++ b/web/src/components/player/dynamic/DynamicVideoController.ts @@ -81,13 +81,27 @@ export class DynamicVideoController { this.playerController.currentTime = seekSeconds; if (play) { - this.playerController.play(); + this.waitAndPlay(); } else { this.playerController.pause(); } } } + waitAndPlay() { + return new Promise((resolve) => { + const onSeekedHandler = () => { + this.playerController.removeEventListener("seeked", onSeekedHandler); + this.playerController.play(); + resolve(undefined); + }; + + this.playerController.addEventListener("seeked", onSeekedHandler, { + once: true, + }); + }); + } + seekToTimelineItem(timeline: Timeline) { this.playerController.pause(); this.seekToTimestamp(timeline.timestamp + this.annotationOffset); diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index da169a838..3c656a4a7 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -90,12 +90,19 @@ export default function DynamicVideoPlayer({ // initial state + const [isLoading, setIsLoading] = useState(false); const [source, setSource] = useState( `${apiHost}vod/${camera}/start/${timeRange.start}/end/${timeRange.end}/master.m3u8`, ); // start at correct time + useEffect(() => { + if (isScrubbing) { + setIsLoading(true); + } + }, [isScrubbing]); + const onPlayerLoaded = useCallback(() => { if (!controller || !startTimestamp) { return; @@ -140,6 +147,7 @@ export default function DynamicVideoPlayer({ setSource( `${apiHost}vod/${camera}/start/${timeRange.start}/end/${timeRange.end}/master.m3u8`, ); + setIsLoading(true); controller.newPlayback({ recordings: recordings ?? [], @@ -151,14 +159,17 @@ export default function DynamicVideoPlayer({ return (
-
+
setIsLoading(false)} > {config && focusedItem && (
{ setPreviewController(previewController); }} diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index ff5bfbac6..60b8e58a8 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -301,6 +301,7 @@ export function MobileRecordingView({ relevantPreviews, allCameras, }: MobileRecordingViewProps) { + const { data: config } = useSWR("config"); const navigate = useNavigate(); const contentRef = useRef(null); @@ -310,6 +311,21 @@ export function MobileRecordingView({ const [playbackCamera, setPlaybackCamera] = useState(startCamera); const [playbackStart, setPlaybackStart] = useState(startTime); + const grow = useMemo(() => { + if (!config) { + return "aspect-video"; + } + + const aspectRatio = + config.cameras[playbackCamera].detect.width / + config.cameras[playbackCamera].detect.height; + if (aspectRatio > 2) { + return "aspect-wide"; + } else { + return "aspect-video"; + } + }, [config, playbackCamera]); + // timeline time const timeRange = useMemo(() => getChunkedTimeDay(startTime), [startTime]); @@ -453,6 +469,7 @@ export function MobileRecordingView({