Move seeking support into the engine.

This commit is contained in:
Dermot Duffy
2023-01-24 19:36:54 -08:00
parent 13816c3025
commit 6b2036676c
12 changed files with 142 additions and 176 deletions
+6 -117
View File
@@ -1,25 +1,16 @@
import add from 'date-fns/add';
import fromUnixTime from 'date-fns/fromUnixTime';
import startOfHour from 'date-fns/startOfHour';
import sub from 'date-fns/sub';
import { ViewContext } from 'view';
import {
CameraConfig,
ClipsOrSnapshotsOrAll,
FrigateCardView,
RecordingSegment,
} from '../types';
import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types';
import { View } from '../view/view';
import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries';
import { CameraManager } from '../camera/manager';
import { getAllDependentCameras } from './camera.js';
import { ViewMedia } from '../view/media';
import { ViewMediaClassifier } from '../view/media-classifier';
import { HomeAssistant } from 'custom-card-helpers';
import { dispatchFrigateCardErrorEvent } from '../components/message';
import { MediaQueriesResults } from '../view/media-queries-results';
import { errorToConsole } from './basic';
import { RecordingSegmentsQueryResults } from '../camera/types';
export const changeViewToRecentEventsForCameraAndDependents = async (
element: HTMLElement,
@@ -168,12 +159,11 @@ export const createViewForRecordings = async (
queryResults.selectBestResult((media) =>
findClosestMediaIndex(media, options.targetTime as Date, cameraIDs),
);
viewerContext = await generateMediaViewerContext(
hass,
cameraManager,
mediaArray,
options.targetTime,
);
viewerContext = {
mediaViewer: {
seek: options.targetTime,
},
};
}
return (
@@ -187,75 +177,6 @@ export const createViewForRecordings = async (
);
};
/**
* Generate the media view context for a set of media children (used to set
* seek times into each media item).
* @param hass The Home Assistant object.
* @param cameraManager The datamanager to use for data access.
* @param media The media.
* @param targetTime The target time.
* @returns The ViewContext.
*/
export const generateMediaViewerContext = async (
hass: HomeAssistant,
cameraManager: CameraManager,
media: ViewMedia[],
targetTime: Date,
): Promise<ViewContext> => {
const seek = new Map();
const hourStart = startOfHour(targetTime);
for (const [index, child] of media.entries()) {
const start = child.getStartTime();
const end = child.getEndTime();
if (!start || !end) {
continue;
}
let seekSeconds: number | null = null;
if (targetTime >= start && targetTime <= end) {
const query = cameraManager.generateDefaultRecordingSegmentsQueries(
child.getCameraID(),
{
start: start,
end: end,
},
)[0];
let segments: RecordingSegmentsQueryResults | null;
try {
segments = (await cameraManager.getRecordingSegments(hass, query)).get(
query,
) ?? null;
} catch (e) {
errorToConsole(e as Error);
// View context is never critical. Ignore errors which will at least
// allow the video to load even if it doesn't seek to the correct
// location.
return {};
}
if (segments) {
seekSeconds = getSeekTimeInSegments(
// Recordings start from the top of the hour.
ViewMediaClassifier.isRecording(child) ? hourStart : start,
targetTime,
segments.segments,
);
}
}
if (seekSeconds !== null) {
seek.set(index, {
seekSeconds: seekSeconds,
seekTime: targetTime.getTime() / 1000,
});
}
}
return seek.size > 0 ? { mediaViewer: { seek: seek } } : {};
};
/**
* Find the closest matching media object.
* @param mediaArray The media. Must be sorted most recent first.
@@ -302,35 +223,3 @@ export const findClosestMediaIndex = (
}
return bestMatch ? bestMatch.index : null;
};
/**
* Get the number of seconds to seek into a video stream consisting of the
* provided segments to reach the target time provided.
* @param startTime The earliest allowable time to seek from.
* @param targetTime Target time.
* @param segments An array of segments dataset items. Must be sorted from oldest to youngest.
* @returns
*/
const getSeekTimeInSegments = (
startTime: Date,
targetTime: Date,
segments: RecordingSegment[],
): number | null => {
if (!segments.length) {
return null;
}
let seekMilliseconds = 0;
// Inspired by: https://github.com/blakeblackshear/frigate/blob/release-0.11.0/web/src/routes/Recording.jsx#L27
for (const segment of segments) {
const segmentStart = fromUnixTime(segment.start_time);
if (segmentStart > targetTime) {
break;
}
const segmentEnd = fromUnixTime(segment.end_time);
const start = segmentStart < startTime ? startTime : segmentStart;
const end = segmentEnd > targetTime ? targetTime : segmentEnd;
seekMilliseconds += end.getTime() - start.getTime();
}
return seekMilliseconds / 1000;
};
+2 -4
View File
@@ -87,12 +87,11 @@ export class TimelineDataSource {
public async refresh(
hass: HomeAssistant,
cameras: Map<string, CameraConfig>,
window: TimelineWindow,
): Promise<void> {
try {
await Promise.all([
this._refreshEvents(hass, cameras, window),
this._refreshEvents(hass, window),
this._refreshRecordings(hass, window),
]);
} catch (e) {
@@ -122,7 +121,6 @@ export class TimelineDataSource {
protected async _refreshEvents(
hass: HomeAssistant,
cameras: Map<string, CameraConfig>,
window: TimelineWindow,
): Promise<void> {
if (
@@ -145,7 +143,7 @@ export class TimelineDataSource {
for (const media of results?.getResults() ?? []) {
const endTime = media.getEndTime();
const startTime = media.getStartTime();
const id = media.getID(cameras.get(media.getCameraID()));
const id = media.getID();
if (id && startTime) {
this._dataset.update({
id: id,