mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Break apart mobile and desktop timeline views
This commit is contained in:
parent
944afd7c3a
commit
6807400314
@ -11,7 +11,13 @@ import {
|
|||||||
getTimelineIcon,
|
getTimelineIcon,
|
||||||
} from "@/utils/timelineUtil";
|
} from "@/utils/timelineUtil";
|
||||||
import { renderToStaticMarkup } from "react-dom/server";
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import Player from "video.js/dist/types/player";
|
import Player from "video.js/dist/types/player";
|
||||||
|
|
||||||
@ -177,6 +183,83 @@ export default function HistoryTimelineView({
|
|||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<MobileView
|
||||||
|
config={config}
|
||||||
|
playerRef={playerRef}
|
||||||
|
previewRef={previewRef}
|
||||||
|
playback={playback}
|
||||||
|
playbackUri={playbackUri}
|
||||||
|
playbackTimes={playbackTimes}
|
||||||
|
timelineTime={timelineTime}
|
||||||
|
hasRelevantPreview={hasRelevantPreview}
|
||||||
|
scrubbing={scrubbing}
|
||||||
|
focusedItem={focusedItem}
|
||||||
|
setFocusedItem={setFocusedItem}
|
||||||
|
setSeeking={setSeeking}
|
||||||
|
onSelectItem={onSelectItem}
|
||||||
|
onScrubTime={onScrubTime}
|
||||||
|
onStopScrubbing={onStopScrubbing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DesktopView
|
||||||
|
config={config}
|
||||||
|
playerRef={playerRef}
|
||||||
|
previewRef={previewRef}
|
||||||
|
playback={playback}
|
||||||
|
playbackUri={playbackUri}
|
||||||
|
playbackTimes={playbackTimes}
|
||||||
|
timelineTime={timelineTime}
|
||||||
|
hasRelevantPreview={hasRelevantPreview}
|
||||||
|
scrubbing={scrubbing}
|
||||||
|
focusedItem={focusedItem}
|
||||||
|
setFocusedItem={setFocusedItem}
|
||||||
|
setSeeking={setSeeking}
|
||||||
|
onSelectItem={onSelectItem}
|
||||||
|
onScrubTime={onScrubTime}
|
||||||
|
onStopScrubbing={onStopScrubbing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type DesktopViewProps = {
|
||||||
|
config: FrigateConfig;
|
||||||
|
playerRef: React.MutableRefObject<Player | undefined>;
|
||||||
|
previewRef: React.MutableRefObject<Player | undefined>;
|
||||||
|
playback: TimelinePlayback;
|
||||||
|
playbackUri: string;
|
||||||
|
playbackTimes: { start: number; end: number };
|
||||||
|
timelineTime: number;
|
||||||
|
hasRelevantPreview: boolean;
|
||||||
|
scrubbing: boolean;
|
||||||
|
focusedItem: Timeline | undefined;
|
||||||
|
setFocusedItem: (item: Timeline | undefined) => void;
|
||||||
|
setSeeking: (seeking: boolean) => void;
|
||||||
|
onSelectItem: ({ items }: { items: number[] }) => void;
|
||||||
|
onScrubTime: ({ time }: { time: Date }) => void;
|
||||||
|
onStopScrubbing: ({ time }: { time: Date }) => void;
|
||||||
|
};
|
||||||
|
function DesktopView({
|
||||||
|
config,
|
||||||
|
playerRef,
|
||||||
|
previewRef,
|
||||||
|
playback,
|
||||||
|
playbackUri,
|
||||||
|
playbackTimes,
|
||||||
|
timelineTime,
|
||||||
|
hasRelevantPreview,
|
||||||
|
scrubbing,
|
||||||
|
focusedItem,
|
||||||
|
setFocusedItem,
|
||||||
|
setSeeking,
|
||||||
|
onSelectItem,
|
||||||
|
onScrubTime,
|
||||||
|
onStopScrubbing,
|
||||||
|
}: DesktopViewProps) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<>
|
<>
|
||||||
@ -254,18 +337,141 @@ export default function HistoryTimelineView({
|
|||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
options={{
|
options={{
|
||||||
...(isMobile && {
|
|
||||||
start: new Date(
|
|
||||||
Math.max(playbackTimes.start, timelineTime - 300) * 1000
|
|
||||||
),
|
|
||||||
end: new Date(
|
|
||||||
Math.min(playbackTimes.end, timelineTime + 300) * 1000
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
snap: null,
|
snap: null,
|
||||||
min: new Date(playbackTimes.start * 1000),
|
min: new Date(playbackTimes.start * 1000),
|
||||||
max: new Date(playbackTimes.end * 1000),
|
max: new Date(playbackTimes.end * 1000),
|
||||||
timeAxis: isMobile ? { scale: "minute", step: 5 } : {},
|
}}
|
||||||
|
timechangeHandler={onScrubTime}
|
||||||
|
timechangedHandler={onStopScrubbing}
|
||||||
|
selectHandler={onSelectItem}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type MobileViewProps = {
|
||||||
|
config: FrigateConfig;
|
||||||
|
playerRef: React.MutableRefObject<Player | undefined>;
|
||||||
|
previewRef: React.MutableRefObject<Player | undefined>;
|
||||||
|
playback: TimelinePlayback;
|
||||||
|
playbackUri: string;
|
||||||
|
playbackTimes: { start: number; end: number };
|
||||||
|
timelineTime: number;
|
||||||
|
hasRelevantPreview: boolean;
|
||||||
|
scrubbing: boolean;
|
||||||
|
focusedItem: Timeline | undefined;
|
||||||
|
setFocusedItem: (item: Timeline | undefined) => void;
|
||||||
|
setSeeking: (seeking: boolean) => void;
|
||||||
|
onSelectItem: ({ items }: { items: number[] }) => void;
|
||||||
|
onScrubTime: ({ time }: { time: Date }) => void;
|
||||||
|
onStopScrubbing: ({ time }: { time: Date }) => void;
|
||||||
|
};
|
||||||
|
function MobileView({
|
||||||
|
config,
|
||||||
|
playerRef,
|
||||||
|
previewRef,
|
||||||
|
playback,
|
||||||
|
playbackUri,
|
||||||
|
playbackTimes,
|
||||||
|
timelineTime,
|
||||||
|
hasRelevantPreview,
|
||||||
|
scrubbing,
|
||||||
|
focusedItem,
|
||||||
|
setFocusedItem,
|
||||||
|
setSeeking,
|
||||||
|
onSelectItem,
|
||||||
|
onScrubTime,
|
||||||
|
onStopScrubbing,
|
||||||
|
}: MobileViewProps) {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`relative ${
|
||||||
|
hasRelevantPreview && scrubbing ? "hidden" : "visible"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<VideoPlayer
|
||||||
|
options={{
|
||||||
|
preload: "auto",
|
||||||
|
autoplay: true,
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: playbackUri,
|
||||||
|
type: "application/vnd.apple.mpegurl",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
seekOptions={{ forward: 10, backward: 5 }}
|
||||||
|
onReady={(player) => {
|
||||||
|
playerRef.current = player;
|
||||||
|
player.currentTime(timelineTime - playbackTimes.start);
|
||||||
|
player.on("playing", () => {
|
||||||
|
setFocusedItem(undefined);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onDispose={() => {
|
||||||
|
playerRef.current = undefined;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{config && focusedItem ? (
|
||||||
|
<TimelineEventOverlay
|
||||||
|
timeline={focusedItem}
|
||||||
|
cameraConfig={config.cameras[playback.camera]}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
</VideoPlayer>
|
||||||
|
</div>
|
||||||
|
{hasRelevantPreview && (
|
||||||
|
<div className={`${scrubbing ? "visible" : "hidden"}`}>
|
||||||
|
<VideoPlayer
|
||||||
|
options={{
|
||||||
|
preload: "auto",
|
||||||
|
autoplay: false,
|
||||||
|
controls: false,
|
||||||
|
muted: true,
|
||||||
|
loadingSpinner: false,
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: `${playback.relevantPreview?.src}`,
|
||||||
|
type: "video/mp4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
seekOptions={{}}
|
||||||
|
onReady={(player) => {
|
||||||
|
previewRef.current = player;
|
||||||
|
player.on("seeked", () => setSeeking(false));
|
||||||
|
}}
|
||||||
|
onDispose={() => {
|
||||||
|
previewRef.current = undefined;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
<div className="m-1">
|
||||||
|
{playback != undefined && (
|
||||||
|
<ActivityScrubber
|
||||||
|
items={timelineItemsToScrubber(playback.timelineItems)}
|
||||||
|
timeBars={
|
||||||
|
hasRelevantPreview
|
||||||
|
? [{ time: new Date(timelineTime * 1000), id: "playback" }]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
options={{
|
||||||
|
start: new Date(
|
||||||
|
Math.max(playbackTimes.start, timelineTime - 300) * 1000
|
||||||
|
),
|
||||||
|
end: new Date(
|
||||||
|
Math.min(playbackTimes.end, timelineTime + 300) * 1000
|
||||||
|
),
|
||||||
|
snap: null,
|
||||||
|
min: new Date(playbackTimes.start * 1000),
|
||||||
|
max: new Date(playbackTimes.end * 1000),
|
||||||
|
timeAxis: { scale: "minute", step: 5 },
|
||||||
}}
|
}}
|
||||||
timechangeHandler={onScrubTime}
|
timechangeHandler={onScrubTime}
|
||||||
timechangedHandler={onStopScrubbing}
|
timechangedHandler={onStopScrubbing}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user