Improve media selection logic.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { CameraConfig } from '../types';
|
import { CameraConfig } from '../types';
|
||||||
|
import { ViewMedia } from '../view/media';
|
||||||
import { RecordingSegmentsCache, RequestCache } from './cache';
|
import { RecordingSegmentsCache, RequestCache } from './cache';
|
||||||
import { CameraManagerEngine } from './engine';
|
import { CameraManagerEngine } from './engine';
|
||||||
import { FrigateCameraManagerEngine } from './frigate/engine-frigate';
|
import { FrigateCameraManagerEngine } from './frigate/engine-frigate';
|
||||||
@@ -65,6 +66,18 @@ export class CameraManagerEngineFactory {
|
|||||||
return output.size ? output : null;
|
return output.size ? output : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getEngineForMedia(
|
||||||
|
cameras: Map<string, CameraConfig>,
|
||||||
|
media: ViewMedia,
|
||||||
|
): CameraManagerEngine | null {
|
||||||
|
const cameraID = media.getCameraID();
|
||||||
|
if (!cameraID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const engines = this.getEnginesForCameraIDs(cameras, new Set([cameraID]));
|
||||||
|
return engines ? ([...engines.keys()][0] ?? null) : null;
|
||||||
|
}
|
||||||
|
|
||||||
public getAllEngines(
|
public getAllEngines(
|
||||||
cameras: Map<string, CameraConfig>,
|
cameras: Map<string, CameraConfig>,
|
||||||
): CameraManagerEngine[] | null {
|
): CameraManagerEngine[] | null {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
RecordingQueryResultsMap,
|
RecordingQueryResultsMap,
|
||||||
RecordingSegmentsQuery,
|
RecordingSegmentsQuery,
|
||||||
RecordingSegmentsQueryResultsMap,
|
RecordingSegmentsQueryResultsMap,
|
||||||
|
CameraManagerEngineCapabilities,
|
||||||
|
CameraManagerMediaCapabilities,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
|
export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000;
|
||||||
@@ -89,4 +91,7 @@ export interface CameraManagerEngine {
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
cameras: Map<string, CameraConfig>,
|
cameras: Map<string, CameraConfig>,
|
||||||
): Promise<MediaMetadata | null>;
|
): Promise<MediaMetadata | null>;
|
||||||
|
|
||||||
|
getCapabilities(): CameraManagerEngineCapabilities | null;
|
||||||
|
getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
} from '../engine';
|
} from '../engine';
|
||||||
import { DateRange } from '../range';
|
import { DateRange } from '../range';
|
||||||
import {
|
import {
|
||||||
|
CameraManagerEngineCapabilities,
|
||||||
|
CameraManagerMediaCapabilities,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
Engine,
|
Engine,
|
||||||
EventQuery,
|
EventQuery,
|
||||||
@@ -760,4 +762,17 @@ export class FrigateCameraManagerEngine implements CameraManagerEngine {
|
|||||||
}
|
}
|
||||||
return seekMilliseconds / 1000;
|
return seekMilliseconds / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCapabilities(): CameraManagerEngineCapabilities {
|
||||||
|
return {
|
||||||
|
canFavoriteEvents: true,
|
||||||
|
canFavoriteRecordings: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities {
|
||||||
|
return {
|
||||||
|
canFavorite: ViewMediaClassifier.isEvent(media)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { HomeAssistant } from 'custom-card-helpers';
|
|||||||
import { CameraConfig } from '../types.js';
|
import { CameraConfig } from '../types.js';
|
||||||
import { allPromises, arrayify, setify } from '../utils/basic.js';
|
import { allPromises, arrayify, setify } from '../utils/basic.js';
|
||||||
import {
|
import {
|
||||||
|
CameraManagerCapabilities,
|
||||||
|
CameraManagerMediaCapabilities,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
EventQuery,
|
EventQuery,
|
||||||
EventQueryResults,
|
EventQueryResults,
|
||||||
@@ -312,6 +314,26 @@ export class CameraManager {
|
|||||||
return engine.getMediaDownloadPath(cameraConfig, media);
|
return engine.getMediaDownloadPath(cameraConfig, media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCapabilities(): CameraManagerCapabilities | null {
|
||||||
|
const engines = this._engineFactory.getAllEngines(this._cameras);
|
||||||
|
if (!engines) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canFavoriteEvents: engines.some((engine) => engine.getCapabilities()?.canFavoriteEvents),
|
||||||
|
canFavoriteRecordings: engines.some((engine) => engine.getCapabilities()?.canFavoriteRecordings),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null {
|
||||||
|
const engine = this._engineFactory.getEngineForMedia(this._cameras, media);
|
||||||
|
if (!engine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return engine.getMediaCapabilities(media);
|
||||||
|
}
|
||||||
|
|
||||||
public async favoriteMedia(
|
public async favoriteMedia(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
media: ViewMedia,
|
media: ViewMedia,
|
||||||
|
|||||||
@@ -74,6 +74,17 @@ export interface MediaMetadata {
|
|||||||
days?: Set<string>;
|
days?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BaseCapabilities {
|
||||||
|
canFavoriteEvents: boolean;
|
||||||
|
canFavoriteRecordings: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CameraManagerCapabilities = BaseCapabilities;
|
||||||
|
export type CameraManagerEngineCapabilities = BaseCapabilities;
|
||||||
|
export interface CameraManagerMediaCapabilities {
|
||||||
|
canFavorite: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// ===========
|
// ===========
|
||||||
// Event Query
|
// Event Query
|
||||||
// ===========
|
// ===========
|
||||||
@@ -107,6 +118,8 @@ export interface EventQueryResults extends QueryResults {
|
|||||||
|
|
||||||
export interface RecordingQuery extends MediaQuery {
|
export interface RecordingQuery extends MediaQuery {
|
||||||
type: QueryType.Recording;
|
type: QueryType.Recording;
|
||||||
|
|
||||||
|
favorite?: boolean;
|
||||||
}
|
}
|
||||||
export type PartialRecordingQuery = Partial<RecordingQuery>;
|
export type PartialRecordingQuery = Partial<RecordingQuery>;
|
||||||
|
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ export interface MediaFilterCoreWhenSelection {
|
|||||||
custom?: DateRange;
|
custom?: DateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MediaFilterControl {
|
export type MediaFilterControls = {
|
||||||
mediaType = 'mediaType',
|
mediaType?: boolean;
|
||||||
when = 'when',
|
when?: boolean;
|
||||||
camera = 'camera',
|
camera?: boolean;
|
||||||
what = 'what',
|
what?: boolean;
|
||||||
where = 'where',
|
where?: boolean;
|
||||||
favorite = 'favorite',
|
favorite?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
@customElement('frigate-card-media-filter-core')
|
@customElement('frigate-card-media-filter-core')
|
||||||
export class FrigateCardMediaFilterCore extends LitElement {
|
export class FrigateCardMediaFilterCore extends LitElement {
|
||||||
@@ -86,7 +86,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
|
|||||||
public defaults?: MediaFilterCoreSelection;
|
public defaults?: MediaFilterCoreSelection;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public controls?: Record<MediaFilterControl, boolean>;
|
public controls?: MediaFilterControls;
|
||||||
|
|
||||||
protected _cameraOptions?: ValueLabel<string>[];
|
protected _cameraOptions?: ValueLabel<string>[];
|
||||||
protected _whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[];
|
protected _whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[];
|
||||||
|
|||||||
@@ -16,29 +16,34 @@ import {
|
|||||||
unsafeCSS,
|
unsafeCSS,
|
||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { CameraManager } from '../camera/manager';
|
import {
|
||||||
|
CameraManager,
|
||||||
|
} from '../camera/manager';
|
||||||
import { DateRange } from '../camera/range';
|
import { DateRange } from '../camera/range';
|
||||||
import { CameraConfig, ExtendedHomeAssistant } from '../types';
|
import { CameraConfig, ExtendedHomeAssistant } from '../types';
|
||||||
import { getCameraTitle } from '../utils/camera';
|
import { getCameraTitle } from '../utils/camera';
|
||||||
import { View } from '../view/view';
|
import { View } from '../view/view';
|
||||||
import {
|
import {
|
||||||
|
MediaFilterControls,
|
||||||
MediaFilterCoreFavoriteSelection,
|
MediaFilterCoreFavoriteSelection,
|
||||||
MediaFilterCoreSelection,
|
MediaFilterCoreSelection,
|
||||||
MediaFilterCoreWhen,
|
MediaFilterCoreWhen,
|
||||||
MediaFilterCoreWhenSelection,
|
MediaFilterCoreWhenSelection,
|
||||||
|
MediaFilterMediaType,
|
||||||
ValueLabel,
|
ValueLabel,
|
||||||
} from './media-filter-core';
|
} from './media-filter-core';
|
||||||
import './surround.js';
|
import './surround.js';
|
||||||
import './timeline-core.js';
|
import './timeline-core.js';
|
||||||
import { EventQuery, QueryType } from '../camera/types';
|
import { EventQuery, QueryType, RecordingQuery } from '../camera/types';
|
||||||
import { EventMediaQueries } from '../view/media-queries';
|
import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries';
|
||||||
import { createViewForEvents } from '../utils/media-to-view.js';
|
import { createViewForEvents, createViewForRecordings } from '../utils/media-to-view.js';
|
||||||
import { HomeAssistant } from 'custom-card-helpers';
|
import { HomeAssistant } from 'custom-card-helpers';
|
||||||
import { prettifyTitle } from '../utils/basic';
|
import { prettifyTitle } from '../utils/basic';
|
||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import parse from 'date-fns/parse';
|
import parse from 'date-fns/parse';
|
||||||
import endOfMonth from 'date-fns/endOfMonth';
|
import endOfMonth from 'date-fns/endOfMonth';
|
||||||
import mediaFilterStyle from '../scss/media-filter.scss';
|
import mediaFilterStyle from '../scss/media-filter.scss';
|
||||||
|
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
||||||
|
|
||||||
@customElement('frigate-card-media-filter')
|
@customElement('frigate-card-media-filter')
|
||||||
export class FrigateCardMediaFilter extends LitElement {
|
export class FrigateCardMediaFilter extends LitElement {
|
||||||
@@ -96,13 +101,24 @@ export class FrigateCardMediaFilter extends LitElement {
|
|||||||
protected async _mediaFilterHandler(
|
protected async _mediaFilterHandler(
|
||||||
ev: CustomEvent<MediaFilterCoreSelection>,
|
ev: CustomEvent<MediaFilterCoreSelection>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.cameras || !this.cameraManager || !this.hass || !this.view) {
|
const mediaFilter = ev.detail;
|
||||||
|
if (
|
||||||
|
!this.cameras ||
|
||||||
|
!this.cameraManager ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.view ||
|
||||||
|
!mediaFilter.mediaType
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mediaFilter = ev.detail;
|
|
||||||
const convertedTime = this._convertWhenToDateRange(mediaFilter.when);
|
const convertedTime = this._convertWhenToDateRange(mediaFilter.when);
|
||||||
const convertedFavorite = this._convertFavoriteToBoolean(mediaFilter.favorite);
|
const convertedFavorite = this._convertFavoriteToBoolean(mediaFilter.favorite);
|
||||||
|
|
||||||
|
if (
|
||||||
|
mediaFilter.mediaType === MediaFilterMediaType.Clips ||
|
||||||
|
mediaFilter.mediaType === MediaFilterMediaType.Snapshots
|
||||||
|
) {
|
||||||
const query: EventQuery = {
|
const query: EventQuery = {
|
||||||
type: QueryType.Event,
|
type: QueryType.Event,
|
||||||
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()),
|
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()),
|
||||||
@@ -111,6 +127,8 @@ export class FrigateCardMediaFilter extends LitElement {
|
|||||||
...(convertedFavorite !== null && { favorite: convertedFavorite }),
|
...(convertedFavorite !== null && { favorite: convertedFavorite }),
|
||||||
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
|
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
|
||||||
...(this.mediaLimit && { limit: this.mediaLimit }),
|
...(this.mediaLimit && { limit: this.mediaLimit }),
|
||||||
|
...(mediaFilter.mediaType === MediaFilterMediaType.Clips && { hasClip: true }),
|
||||||
|
...(mediaFilter.mediaType === MediaFilterMediaType.Snapshots && { hasSnapshot: true })
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -125,6 +143,26 @@ export class FrigateCardMediaFilter extends LitElement {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
)?.dispatchChangeEvent(this);
|
)?.dispatchChangeEvent(this);
|
||||||
|
} else if (mediaFilter.mediaType === MediaFilterMediaType.Recordings) {
|
||||||
|
const query: RecordingQuery = {
|
||||||
|
type: QueryType.Recording,
|
||||||
|
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()),
|
||||||
|
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
await createViewForRecordings(
|
||||||
|
this,
|
||||||
|
this.hass,
|
||||||
|
this.cameraManager,
|
||||||
|
this.cameras,
|
||||||
|
this.view,
|
||||||
|
{
|
||||||
|
query: new RecordingMediaQueries([query]),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)?.dispatchChangeEvent(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
@@ -147,12 +185,32 @@ export class FrigateCardMediaFilter extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
|
const areEvents = !!(
|
||||||
|
this.view?.query && MediaQueriesClassifier.areEventQueries(this.view.query)
|
||||||
|
);
|
||||||
|
const areRecordings = !!(
|
||||||
|
this.view?.query && MediaQueriesClassifier.areRecordingQueries(this.view.query)
|
||||||
|
);
|
||||||
|
const managerCapabilities = this.cameraManager?.getCapabilities();
|
||||||
|
|
||||||
|
// Which media controls are shown depends on the view.
|
||||||
|
const controls: MediaFilterControls = {
|
||||||
|
what: areEvents,
|
||||||
|
where: areEvents,
|
||||||
|
favorite: areEvents
|
||||||
|
? !!managerCapabilities?.canFavoriteEvents
|
||||||
|
: areRecordings
|
||||||
|
? !!managerCapabilities?.canFavoriteRecordings
|
||||||
|
: false,
|
||||||
|
};
|
||||||
|
|
||||||
return html` <frigate-card-media-filter-core
|
return html` <frigate-card-media-filter-core
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.whenOptions=${this._mediaMetadataController?.whenOptions}
|
.whenOptions=${this._mediaMetadataController?.whenOptions}
|
||||||
.cameraOptions=${this._cameraOptions}
|
.cameraOptions=${this._cameraOptions}
|
||||||
.whatOptions=${this._mediaMetadataController?.whatOptions}
|
.whatOptions=${this._mediaMetadataController?.whatOptions}
|
||||||
.whereOptions=${this._mediaMetadataController?.whereOptions}
|
.whereOptions=${this._mediaMetadataController?.whereOptions}
|
||||||
|
.controls=${controls}
|
||||||
@frigate-card:media-filter-core:change=${this._mediaFilterHandler.bind(this)}
|
@frigate-card:media-filter-core:change=${this._mediaFilterHandler.bind(this)}
|
||||||
>
|
>
|
||||||
</frigate-card-media-filter-core>`;
|
</frigate-card-media-filter-core>`;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import { CSSResult, html, LitElement, PropertyValues, TemplateResult, unsafeCSS } from 'lit';
|
import {
|
||||||
|
CSSResult,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
unsafeCSS,
|
||||||
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { localize } from '../localize/localize.js';
|
import { localize } from '../localize/localize.js';
|
||||||
@@ -282,6 +289,12 @@ export class FrigateCardThumbnail extends LitElement {
|
|||||||
// Only show timeline control if the recording has a start & end time.
|
// Only show timeline control if the recording has a start & end time.
|
||||||
(this.media.getStartTime() && this.media.getEndTime()));
|
(this.media.getStartTime() && this.media.getEndTime()));
|
||||||
|
|
||||||
|
const shouldShowFavoriteControl =
|
||||||
|
this.show_favorite_control &&
|
||||||
|
this.media &&
|
||||||
|
this.hass &&
|
||||||
|
this.cameraManager?.getMediaCapabilities(this.media)?.canFavorite;
|
||||||
|
|
||||||
return html` ${ViewMediaClassifier.isEvent(this.media)
|
return html` ${ViewMediaClassifier.isEvent(this.media)
|
||||||
? html`<frigate-card-thumbnail-feature-event
|
? html`<frigate-card-thumbnail-feature-event
|
||||||
aria-label="${title ?? ''}"
|
aria-label="${title ?? ''}"
|
||||||
@@ -299,7 +312,7 @@ export class FrigateCardThumbnail extends LitElement {
|
|||||||
.date=${this.media.getStartTime() ?? undefined}
|
.date=${this.media.getStartTime() ?? undefined}
|
||||||
></frigate-card-thumbnail-feature-recording>`
|
></frigate-card-thumbnail-feature-recording>`
|
||||||
: html``}
|
: html``}
|
||||||
${this.show_favorite_control && this.media && this.hass
|
${shouldShowFavoriteControl
|
||||||
? html` <ha-icon
|
? html` <ha-icon
|
||||||
class="${classMap(starClasses)}"
|
class="${classMap(starClasses)}"
|
||||||
icon=${this.media.isFavorite() ? 'mdi:star' : 'mdi:star-outline'}
|
icon=${this.media.isFavorite() ? 'mdi:star' : 'mdi:star-outline'}
|
||||||
|
|||||||
+57
-19
@@ -3,7 +3,11 @@ import sub from 'date-fns/sub';
|
|||||||
import { ViewContext } from 'view';
|
import { ViewContext } from 'view';
|
||||||
import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types';
|
import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types';
|
||||||
import { View } from '../view/view';
|
import { View } from '../view/view';
|
||||||
import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries';
|
import {
|
||||||
|
EventMediaQueries,
|
||||||
|
MediaQueries,
|
||||||
|
RecordingMediaQueries,
|
||||||
|
} from '../view/media-queries';
|
||||||
import { CameraManager } from '../camera/manager';
|
import { CameraManager } from '../camera/manager';
|
||||||
import { getAllDependentCameras } from './camera.js';
|
import { getAllDependentCameras } from './camera.js';
|
||||||
import { ViewMedia } from '../view/media';
|
import { ViewMedia } from '../view/media';
|
||||||
@@ -46,13 +50,13 @@ export const createViewForEvents = async (
|
|||||||
},
|
},
|
||||||
): Promise<View | null> => {
|
): Promise<View | null> => {
|
||||||
let query: EventMediaQueries;
|
let query: EventMediaQueries;
|
||||||
if (options?.query) {
|
|
||||||
query = options.query;
|
|
||||||
} else {
|
|
||||||
const cameraIDs: Set<string> = options?.cameraIDs
|
const cameraIDs: Set<string> = options?.cameraIDs
|
||||||
? options.cameraIDs
|
? options.cameraIDs
|
||||||
: new Set(getAllDependentCameras(cameras, view.camera));
|
: new Set(getAllDependentCameras(cameras, view.camera));
|
||||||
|
|
||||||
|
if (options?.query) {
|
||||||
|
query = options.query;
|
||||||
|
} else {
|
||||||
const eventQueries = cameraManager.generateDefaultEventQueries(cameraIDs, {
|
const eventQueries = cameraManager.generateDefaultEventQueries(cameraIDs, {
|
||||||
...(options?.limit && { limit: options.limit }),
|
...(options?.limit && { limit: options.limit }),
|
||||||
...(options?.mediaType === 'clips' && { hasClip: true }),
|
...(options?.mediaType === 'clips' && { hasClip: true }),
|
||||||
@@ -64,20 +68,21 @@ export const createViewForEvents = async (
|
|||||||
query = new EventMediaQueries(eventQueries);
|
query = new EventMediaQueries(eventQueries);
|
||||||
}
|
}
|
||||||
|
|
||||||
let queryResults: MediaQueriesResults | null;
|
if (!query) {
|
||||||
try {
|
|
||||||
queryResults = await cameraManager.executeMediaQueries(hass, query);
|
|
||||||
} catch (e) {
|
|
||||||
errorToConsole(e as Error);
|
|
||||||
dispatchFrigateCardErrorEvent(element, e as Error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view?.evolve({
|
return executeMediaQueryForView(
|
||||||
view: options?.targetView,
|
element,
|
||||||
query: query,
|
hass,
|
||||||
queryResults: queryResults,
|
cameraManager,
|
||||||
});
|
view,
|
||||||
|
query,
|
||||||
|
options?.targetView ?? 'clips',
|
||||||
|
{
|
||||||
|
cameraIDs: cameraIDs,
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,6 +134,7 @@ export const createViewForRecordings = async (
|
|||||||
cameras: Map<string, CameraConfig>,
|
cameras: Map<string, CameraConfig>,
|
||||||
view: View,
|
view: View,
|
||||||
options?: {
|
options?: {
|
||||||
|
query?: RecordingMediaQueries;
|
||||||
cameraIDs?: Set<string>;
|
cameraIDs?: Set<string>;
|
||||||
targetView?: 'recording' | 'recordings';
|
targetView?: 'recording' | 'recordings';
|
||||||
targetTime?: Date;
|
targetTime?: Date;
|
||||||
@@ -140,6 +146,10 @@ export const createViewForRecordings = async (
|
|||||||
? options.cameraIDs
|
? options.cameraIDs
|
||||||
: new Set(getAllDependentCameras(cameras, view.camera));
|
: new Set(getAllDependentCameras(cameras, view.camera));
|
||||||
|
|
||||||
|
let query: RecordingMediaQueries;
|
||||||
|
if (options?.query) {
|
||||||
|
query = options.query;
|
||||||
|
} else {
|
||||||
const recordingQueries = cameraManager.generateDefaultRecordingQueries(cameraIDs, {
|
const recordingQueries = cameraManager.generateDefaultRecordingQueries(cameraIDs, {
|
||||||
...(options?.start && { start: options.start }),
|
...(options?.start && { start: options.start }),
|
||||||
...(options?.end && { end: options.end }),
|
...(options?.end && { end: options.end }),
|
||||||
@@ -149,7 +159,35 @@ export const createViewForRecordings = async (
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = new RecordingMediaQueries(recordingQueries);
|
query = new RecordingMediaQueries(recordingQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeMediaQueryForView(
|
||||||
|
element,
|
||||||
|
hass,
|
||||||
|
cameraManager,
|
||||||
|
view,
|
||||||
|
query,
|
||||||
|
options?.targetView ?? 'recordings',
|
||||||
|
{
|
||||||
|
cameraIDs: cameraIDs,
|
||||||
|
...(options?.targetTime && { targetTime: options.targetTime }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeMediaQueryForView = async (
|
||||||
|
element: HTMLElement,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
cameraManager: CameraManager,
|
||||||
|
view: View,
|
||||||
|
query: MediaQueries,
|
||||||
|
targetView: FrigateCardView,
|
||||||
|
options?: {
|
||||||
|
cameraIDs: Set<string>;
|
||||||
|
targetTime?: Date;
|
||||||
|
},
|
||||||
|
): Promise<View | null> => {
|
||||||
let queryResults: MediaQueriesResults | null;
|
let queryResults: MediaQueriesResults | null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -162,9 +200,9 @@ export const createViewForRecordings = async (
|
|||||||
|
|
||||||
let viewerContext: ViewContext | undefined = {};
|
let viewerContext: ViewContext | undefined = {};
|
||||||
const mediaArray = queryResults?.getResults();
|
const mediaArray = queryResults?.getResults();
|
||||||
if (queryResults && mediaArray && options?.targetTime) {
|
if (queryResults && mediaArray && options?.targetTime && options.cameraIDs) {
|
||||||
queryResults.selectBestResult((media) =>
|
queryResults.selectBestResult((media) =>
|
||||||
findClosestMediaIndex(media, options.targetTime as Date, cameraIDs),
|
findClosestMediaIndex(media, options.targetTime as Date, options.cameraIDs),
|
||||||
);
|
);
|
||||||
viewerContext = {
|
viewerContext = {
|
||||||
mediaViewer: {
|
mediaViewer: {
|
||||||
@@ -176,7 +214,7 @@ export const createViewForRecordings = async (
|
|||||||
return (
|
return (
|
||||||
view
|
view
|
||||||
?.evolve({
|
?.evolve({
|
||||||
view: options?.targetView ? options.targetView : 'recording',
|
view: targetView,
|
||||||
query: query,
|
query: query,
|
||||||
queryResults: queryResults,
|
queryResults: queryResults,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// - TODO: getRecordingTitle should use getCameraTitle but need hass.
|
// - TODO: getRecordingTitle should use getCameraTitle but need hass.
|
||||||
// - TODO: Take MediaQueries wrappers out of the camera manager.
|
// - TODO: Take MediaQueries wrappers out of the camera manager.
|
||||||
// - TODO: View a media in the gallery from September, then notice timeline missing the item.
|
// - TODO: View a media in the gallery from September, then notice timeline missing the item.
|
||||||
|
// - TODO: Debug statement in card wide config.
|
||||||
|
|
||||||
// Gallery:
|
// Gallery:
|
||||||
// - TODO: Event gallery show_details default does not work.
|
// - TODO: Event gallery show_details default does not work.
|
||||||
|
|||||||
Reference in New Issue
Block a user