diff --git a/web/src/components/scrubber/ActivityScrubber.tsx b/web/src/components/scrubber/ActivityScrubber.tsx
index 9f43e98c5..22af9b85e 100644
--- a/web/src/components/scrubber/ActivityScrubber.tsx
+++ b/web/src/components/scrubber/ActivityScrubber.tsx
@@ -76,7 +76,7 @@ const domEvents: TimelineEventsWithMissing[] = [
type ActivityScrubberProps = {
className?: string;
items?: TimelineItem[];
- timeBars?: { time: DateType; id?: IdType | undefined }[];
+ timeBars?: { time: DateType; id: IdType }[];
groups?: TimelineGroup[];
options?: TimelineOptions;
} & TimelineEventsHandlers;
@@ -94,6 +94,9 @@ function ActivityScrubber({
timeline: null,
});
const [currentTime, setCurrentTime] = useState(Date.now());
+ const [customTimes, setCustomTimes] = useState<
+ { id: IdType; time: DateType }[]
+ >([]);
const defaultOptions: TimelineOptions = {
width: "100%",
@@ -161,6 +164,41 @@ function ActivityScrubber({
};
}, [containerRef]);
+ // need to keep custom times in sync
+ useEffect(() => {
+ if (!timelineRef.current.timeline || timeBars == undefined) {
+ return;
+ }
+
+ setCustomTimes((prevTimes) => {
+ if (prevTimes.length == 0 && timeBars.length == 0) {
+ return [];
+ }
+
+ prevTimes
+ .filter((x) => timeBars.find((y) => x.id == y.id) == undefined)
+ .forEach((time) => {
+ try {
+ timelineRef.current.timeline?.removeCustomTime(time.id);
+ } catch {}
+ });
+
+ timeBars.forEach((time) => {
+ try {
+ const existing = timelineRef.current.timeline?.getCustomTime(time.id);
+
+ if (existing != time.time) {
+ timelineRef.current.timeline?.setCustomTime(time.time, time.id);
+ }
+ } catch {
+ timelineRef.current.timeline?.addCustomTime(time.time, time.id);
+ }
+ });
+
+ return timeBars;
+ });
+ }, [timeBars, timelineRef]);
+
return (
diff --git a/web/src/utils/historyUtil.ts b/web/src/utils/historyUtil.ts
index c4928fb44..78bfe77cb 100644
--- a/web/src/utils/historyUtil.ts
+++ b/web/src/utils/historyUtil.ts
@@ -111,10 +111,20 @@ export function getTimelineHoursForDay(
const now = new Date();
const data: TimelinePlayback[] = [];
const startDay = new Date(timestamp * 1000);
+ startDay.setHours(23, 59, 59, 999);
+ const dayEnd = startDay.getTime() / 1000;
startDay.setHours(0, 0, 0, 0);
let start = startDay.getTime() / 1000;
let end = 0;
+ const relevantPreviews = allPreviews.filter((preview) => {
+ return (
+ preview.camera == camera &&
+ preview.start >= start &&
+ Math.floor(preview.end - 1) <= dayEnd
+ );
+ });
+
const dayIdx = Object.keys(cards).find((day) => {
if (parseInt(day) > start) {
return false;
@@ -156,12 +166,9 @@ export function getTimelineHoursForDay(
return [];
})
: [];
- const previewCheck = start + 30; // preview can start after the hour
- const relevantPreview = Object.values(allPreviews || []).find(
+ const relevantPreview = relevantPreviews.find(
(preview) =>
- preview.camera == camera &&
- preview.start < previewCheck &&
- preview.end > previewCheck
+ Math.round(preview.start) >= start && Math.floor(preview.end) <= end
);
data.push({
camera,
diff --git a/web/src/views/history/DesktopTimelineView.tsx b/web/src/views/history/DesktopTimelineView.tsx
index 94705feaf..ebb688cdb 100644
--- a/web/src/views/history/DesktopTimelineView.tsx
+++ b/web/src/views/history/DesktopTimelineView.tsx
@@ -42,6 +42,11 @@ export default function DesktopTimelineView({
const [seeking, setSeeking] = useState(false);
const [timeToSeek, setTimeToSeek] = useState
(undefined);
+ const [timelineTime, setTimelineTime] = useState(
+ initialPlayback.timelineItems.length > 0
+ ? initialPlayback.timelineItems[0].timestamp
+ : initialPlayback.range.start
+ );
const annotationOffset = useMemo(() => {
if (!config) {
@@ -54,14 +59,6 @@ export default function DesktopTimelineView({
);
}, [config]);
- const timelineTime = useMemo(() => {
- if (!selectedPlayback || selectedPlayback.timelineItems.length == 0) {
- return selectedPlayback.range.start;
- }
-
- return selectedPlayback.timelineItems.at(0)!!.timestamp;
- }, [selectedPlayback]);
-
const recordingParams = useMemo(() => {
return {
before: selectedPlayback.range.end,
@@ -121,36 +118,6 @@ export default function DesktopTimelineView({
[annotationOffset, recordings, playerRef]
);
- const onScrubTime = useCallback(
- (data: { time: Date }) => {
- if (!selectedPlayback.relevantPreview) {
- return;
- }
-
- if (playerRef.current?.paused() == false) {
- setScrubbing(true);
- playerRef.current?.pause();
- }
-
- const seekTimestamp = data.time.getTime() / 1000;
- const seekTime = seekTimestamp - selectedPlayback.relevantPreview.start;
- setTimeToSeek(Math.round(seekTime));
- },
- [scrubbing, playerRef, selectedPlayback]
- );
-
- const onStopScrubbing = useCallback(
- (data: { time: Date }) => {
- const playbackTime = data.time.getTime() / 1000;
- playerRef.current?.currentTime(
- playbackTime - selectedPlayback.range.start
- );
- setScrubbing(false);
- playerRef.current?.play();
- },
- [selectedPlayback, playerRef]
- );
-
// handle seeking to next frame when seek is finished
useEffect(() => {
if (seeking) {
@@ -165,15 +132,33 @@ export default function DesktopTimelineView({
// handle loading main playback when selected hour changes
useEffect(() => {
- if (!playerRef.current) {
+ if (!playerRef.current || !previewRef.current) {
return;
}
+ setTimelineTime(
+ selectedPlayback.timelineItems.length > 0
+ ? selectedPlayback.timelineItems[0].timestamp
+ : selectedPlayback.range.start
+ );
+
playerRef.current.src({
src: playbackUri,
type: "application/vnd.apple.mpegurl",
});
- }, [playerRef, selectedPlayback]);
+
+ if (selectedPlayback.relevantPreview) {
+ console.log(
+ `found relevant preview with start ${new Date(
+ selectedPlayback.relevantPreview.start * 1000
+ )} for ${new Date(selectedPlayback.range.start * 1000)}`
+ );
+ previewRef.current.src({
+ src: selectedPlayback.relevantPreview.src,
+ type: selectedPlayback.relevantPreview.type,
+ });
+ }
+ }, [playerRef, previewRef, selectedPlayback]);
const timelineStack = useMemo(
() =>
@@ -181,7 +166,7 @@ export default function DesktopTimelineView({
selectedPlayback.camera,
timelineData,
allPreviews,
- timelineTime
+ selectedPlayback.range.start + 60
),
[]
);
@@ -216,9 +201,15 @@ export default function DesktopTimelineView({
seekOptions={{ forward: 10, backward: 5 }}
onReady={(player) => {
playerRef.current = player;
- player.currentTime(
- timelineTime - selectedPlayback.range.start
- );
+
+ if (selectedPlayback.timelineItems.length > 0) {
+ player.currentTime(
+ selectedPlayback.timelineItems[0].timestamp -
+ selectedPlayback.range.start
+ );
+ } else {
+ player.currentTime(0);
+ }
player.on("playing", () => {
onSelectItem(undefined);
});
@@ -285,13 +276,18 @@ export default function DesktopTimelineView({
return (
{
+ if (!timeline.relevantPreview) {
+ return;
+ }
+
+ if (playerRef.current?.paused() == false) {
+ setScrubbing(true);
+ playerRef.current?.pause();
+ }
+
+ const seekTimestamp = data.time.getTime() / 1000;
+ const seekTime =
+ seekTimestamp - timeline.relevantPreview.start;
+ setTimelineTime(seekTimestamp);
+ setTimeToSeek(Math.round(seekTime));
+ }}
+ timechangedHandler={(data) => {
+ const playbackTime = data.time.getTime() / 1000;
+ playerRef.current?.currentTime(
+ playbackTime - timeline.range.start
+ );
+ setScrubbing(false);
+ playerRef.current?.play();
+ }}
doubleClickHandler={() => {
setSelectedPlayback(timeline);
}}