Add 'dependent camera' support.
This commit is contained in:
+19
-14
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user