Add 'dependent camera' support.

This commit is contained in:
Dermot Duffy
2022-04-23 11:22:04 -07:00
parent 58813c156e
commit fb9d5684ec
11 changed files with 363 additions and 164 deletions
+19 -14
View File
@@ -11,11 +11,7 @@ import {
import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property } from 'lit/decorators.js';
import {
CameraConfig,
GalleryConfig,
frigateCardConfigDefaults,
} from '../types.js';
import { CameraConfig, GalleryConfig, frigateCardConfigDefaults } from '../types.js';
import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js';
import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js';
@@ -35,28 +31,37 @@ export class FrigateCardGallery extends LitElement {
protected view?: Readonly<View>;
@property({ attribute: false })
protected cameraConfig?: CameraConfig;
protected galleryConfig?: GalleryConfig;
@property({ attribute: false })
protected galleryConfig?: GalleryConfig;
protected cameras?: Map<string, CameraConfig>;
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
if (!this.hass || !this.view || !this.cameraConfig || !this.view.isGalleryView()) {
const mediaType = this.view?.getMediaType();
if (
!this.hass ||
!this.view ||
!this.cameras ||
!this.view.isGalleryView() ||
!mediaType
) {
return;
}
if (!this.view.target) {
const browseMediaQueryParameters = BrowseMediaUtil.setMediaTypeFromView(
BrowseMediaUtil.getBrowseMediaQueryParametersBaseOrDispatchError(
const browseMediaQueryParameters =
BrowseMediaUtil.getFullDependentBrowseMediaQueryParametersOrDispatchError(
this,
this.cameraConfig,
),
this.view,
);
this.hass,
this.cameras,
this.view.camera,
mediaType,
);
if (!browseMediaQueryParameters) {
return;
}
+10 -9
View File
@@ -19,7 +19,6 @@ import {
LiveProvider,
TransitionEffect,
frigateCardConfigDefaults,
BrowseMediaQueryParameters,
} from '../types.js';
import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel';
import { HomeAssistant } from 'custom-card-helpers';
@@ -127,16 +126,18 @@ export class FrigateCardLive extends LitElement {
this.conditionState,
) as LiveConfig;
const browseMediaParamsBase = BrowseMediaUtil.getBrowseMediaQueryParametersBase(
this.cameras.get(this.view.camera),
);
if (!browseMediaParamsBase) {
const browseMediaParams =
BrowseMediaUtil.getFullDependentBrowseMediaQueryParametersOrDispatchError(
this,
this.hass,
this.cameras,
this.view.camera,
config.controls.thumbnails.media,
);
if (!browseMediaParams) {
return;
}
const browseMediaParams: BrowseMediaQueryParameters = {
...browseMediaParamsBase,
mediaType: config.controls.thumbnails.media,
}
// Note use of liveConfig and not config below -- the carousel will
// independently override the liveconfig to reflect the camera in the
+9 -4
View File
@@ -34,7 +34,9 @@ export class FrigateCardSurround extends LitElement {
protected targetView?: FrigateCardView;
@property({ attribute: false })
protected browseMediaParams?: BrowseMediaQueryParameters;
protected browseMediaParams?:
| BrowseMediaQueryParameters
| BrowseMediaQueryParameters[];
// A task to await the load of the WebRTC component.
protected _browseTask = new Task(this, this._fetchMedia.bind(this), () => [
@@ -53,22 +55,25 @@ export class FrigateCardSurround extends LitElement {
| HomeAssistant
| Readonly<View>
| BrowseMediaQueryParameters
| BrowseMediaQueryParameters[]
| undefined
)[]): Promise<void> {
hass = hass as HomeAssistant;
view = view as Readonly<View>;
browseMediaParams = browseMediaParams as BrowseMediaQueryParameters;
browseMediaParams = browseMediaParams as
| BrowseMediaQueryParameters
| BrowseMediaQueryParameters[];
if (!hass || !view || view.target || !browseMediaParams) {
return;
}
let parent: FrigateBrowseMediaSource | null;
try {
parent = await BrowseMediaUtil.browseMediaQuery(hass, browseMediaParams);
parent = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(hass, browseMediaParams);
} catch (e) {
return dispatchErrorMessageEvent(this, (e as Error).message);
}
if (BrowseMediaUtil.getFirstTrueMediaChildIndex(parent) != null) {
if (BrowseMediaUtil.getFirstTrueMediaChildIndex(parent) !== null) {
this.view
?.evolve({
...(this.targetView && { view: this.targetView }),
+44 -55
View File
@@ -25,10 +25,9 @@ import { isEqual } from 'lodash-es';
import { BrowseMediaUtil } from '../browse-media-util';
import {
BrowseMediaQueryParameters,
CameraConfig,
FrigateBrowseMediaSource,
MEDIA_CLASS_PLAYLIST,
MEDIA_TYPE_PLAYLIST,
TimelineConfig,
FrigateEvent,
frigateCardConfigDefaults,
@@ -278,49 +277,47 @@ class TimelineEventManager {
}
this._dateFetch = new Date();
const fetchCameraEvents = async (
camera: string,
fetchMedia: 'clips' | 'snapshots',
): Promise<void> => {
const cameraConfig = cameras.get(camera);
if (!cameraConfig || !this._dateStart || !this._dateEnd) {
return;
}
const browseMediaQueryParametersBase =
BrowseMediaUtil.getBrowseMediaQueryParametersBase(cameraConfig);
if (!browseMediaQueryParametersBase) {
return;
}
try {
this._addMediaSource(
camera,
media,
await BrowseMediaUtil.browseMediaQuery(hass, {
...browseMediaQueryParametersBase,
const params: BrowseMediaQueryParameters[] = [];
cameras.forEach((cameraConfig, cameraID) => {
(media === 'all' ? ['clips', 'snapshots'] : [media]).forEach((mediaType) => {
if (this._dateEnd && this._dateStart) {
const param = BrowseMediaUtil.getBrowseMediaQueryParameters(
hass,
cameraID,
cameraConfig,
{
// Events are always fetched for the maximum extent of the managed
// range. This is because events may change at any point in time
// (e.g. a long-running event that ends).
before: this._dateEnd.getTime() / 1000,
after: this._dateStart.getTime() / 1000,
unlimited: true,
mediaType: mediaType as 'clips' | 'snapshots',
},
);
if (param) {
params.push(param);
}
}
});
});
// Events are always fetched for the maximum extent of the managed
// range. This is because events may change at any point in time
// (e.g. a long-running event that ends).
before: this._dateEnd.getTime() / 1000,
after: this._dateStart.getTime() / 1000,
unlimited: true,
mediaType: fetchMedia,
}),
);
} catch (e) {
return dispatchErrorMessageEvent(element, (e as Error).message);
}
};
if (!params.length) {
return;
}
const promises: Promise<void>[] = [];
(media === 'all' ? ['clips', 'snapshots'] : [media]).forEach((mediaType) =>
promises.push(
...Array.from(cameras.keys()).map((camera) =>
fetchCameraEvents(camera, mediaType as 'clips' | 'snapshots'),
),
),
);
await Promise.all(promises);
let results: Map<BrowseMediaQueryParameters, FrigateBrowseMediaSource>;
try {
results = await BrowseMediaUtil.multipleBrowseMediaQuery(hass, params);
} catch (e) {
return dispatchErrorMessageEvent(element, (e as Error).message);
}
for (const [query, result] of results.entries()) {
if (query.cameraID) {
this._addMediaSource(query.cameraID, media, result);
}
}
}
}
@@ -574,18 +571,10 @@ export class FrigateCardTimelineCore extends LitElement {
return null;
}
const target = {
title: `Timeline events`,
media_class: MEDIA_CLASS_PLAYLIST,
media_content_type: MEDIA_TYPE_PLAYLIST,
media_content_id: '',
can_play: false,
can_expand: true,
children_media_class: MEDIA_CLASS_PLAYLIST,
thumbnail: null,
children: children,
};
const target = BrowseMediaUtil.createEventParentForChildren(
'Timeline events',
children,
);
return {
target: target,
childIndex: childIndex < 0 ? null : childIndex,
+19 -17
View File
@@ -17,7 +17,7 @@ import { ref } from 'lit/directives/ref.js';
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
import type {
BrowseMediaNeighbors,
BrowseMediaQueryParametersBase,
BrowseMediaQueryParameters,
FrigateBrowseMediaSource,
CameraConfig,
MediaShowInfo,
@@ -54,7 +54,7 @@ export class FrigateCardViewer extends LitElement {
protected viewerConfig?: ViewerConfig;
@property({ attribute: false })
protected cameraConfig?: CameraConfig;
protected cameras?: Map<string, CameraConfig>;
@property({ attribute: false })
protected resolvedMediaCache?: ResolvedMediaCache;
@@ -64,21 +64,19 @@ export class FrigateCardViewer extends LitElement {
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
if (!this.hass || !this.view || !this.cameraConfig || !this.viewerConfig) {
if (!this.hass || !this.view || !this.cameras || !this.viewerConfig) {
return;
}
const browseMediaQueryParametersBase =
BrowseMediaUtil.getBrowseMediaQueryParametersBaseOrDispatchError(
const browseMediaQueryParameters =
BrowseMediaUtil.getFullDependentBrowseMediaQueryParametersOrDispatchError(
this,
this.cameraConfig,
this.hass,
this.cameras,
this.view.camera,
);
if (!this.view.target) {
const browseMediaQueryParameters = BrowseMediaUtil.setMediaTypeFromView(
browseMediaQueryParametersBase,
this.view,
);
if (!browseMediaQueryParameters) {
return;
}
@@ -101,7 +99,7 @@ export class FrigateCardViewer extends LitElement {
.hass=${this.hass}
.view=${this.view}
.viewerConfig=${this.viewerConfig}
.browseMediaQueryParametersBase=${browseMediaQueryParametersBase}
.browseMediaQueryParameters=${browseMediaQueryParameters}
.resolvedMediaCache=${this.resolvedMediaCache}
>
</frigate-card-viewer-carousel>
@@ -133,7 +131,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
protected viewerConfig?: ViewerConfig;
@property({ attribute: false })
protected browseMediaQueryParametersBase?: BrowseMediaQueryParametersBase;
protected browseMediaQueryParameters?: BrowseMediaQueryParameters[] | null;
@property({ attribute: false })
protected resolvedMediaCache?: ResolvedMediaCache;
@@ -338,7 +336,7 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
!this.view.target ||
!this.view.target.children ||
!this.view.target.children.length ||
!this.browseMediaQueryParametersBase
!this.browseMediaQueryParameters
) {
return null;
}
@@ -379,13 +377,17 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
let clips: FrigateBrowseMediaSource | null;
try {
clips = await BrowseMediaUtil.browseMediaQuery(this.hass, {
...this.browseMediaQueryParametersBase,
const params = BrowseMediaUtil.overrideMultiBrowseMediaQueryParameters(
this.browseMediaQueryParameters,
{
mediaType: 'clips',
before: latest,
after: earliest,
});
},
);
try {
clips = await BrowseMediaUtil.multipleBrowseMediaQueryMerged(this.hass, params);
} catch (e) {
// This is best effort.
return null;