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
This commit is contained in:
Nicolas Mowen 2024-03-15 06:52:38 -06:00 committed by GitHub
parent d882cb0f63
commit c66f552280
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 63 additions and 18 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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 (
<div className={`relative ${className ?? ""} cursor-pointer`}>
<div className={`w-full relative ${isScrubbing ? "hidden" : "visible"}`}>
<div
className={`w-full relative ${isScrubbing || isLoading ? "hidden" : "visible"}`}
>
<HlsVideoPlayer
className={` ${wideVideo ? "" : "aspect-video"}`}
className={`${wideVideo ? "" : "aspect-video"}`}
videoRef={playerRef}
currentSource={source}
onTimeUpdate={onTimeUpdate}
onPlayerLoaded={onPlayerLoaded}
onClipEnded={onClipEnded}
onPlaying={() => setIsLoading(false)}
>
{config && focusedItem && (
<TimelineEventOverlay
@ -169,10 +180,11 @@ export default function DynamicVideoPlayer({
</HlsVideoPlayer>
</div>
<PreviewPlayer
className={`${isScrubbing ? "visible" : "hidden"} ${className ?? ""}`}
className={`${isScrubbing || isLoading ? "visible" : "hidden"} ${className ?? ""}`}
camera={camera}
timeRange={timeRange}
cameraPreviews={cameraPreviews}
startTime={startTimestamp}
onControllerReady={(previewController) => {
setPreviewController(previewController);
}}

View File

@ -301,6 +301,7 @@ export function MobileRecordingView({
relevantPreviews,
allCameras,
}: MobileRecordingViewProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const navigate = useNavigate();
const contentRef = useRef<HTMLDivElement | null>(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({
<div>
<DynamicVideoPlayer
className={`w-full ${grow}`}
camera={playbackCamera}
timeRange={currentTimeRange}
cameraPreviews={relevantPreviews || []}