Initial version of substream support.

This commit is contained in:
Dermot Duffy
2023-02-24 20:41:11 -08:00
parent f8c824b135
commit 812c3e1018
20 changed files with 295 additions and 147 deletions
+43 -20
View File
@@ -56,6 +56,18 @@ import { dispatchMessageEvent, dispatchErrorMessageEvent } from '../message.js';
import { HassEntity } from 'home-assistant-js-websocket';
import { CameraEndpoints } from '../../camera-manager/types.js';
interface LiveViewContext {
// A cameraID override (used for dependencies/substreams to force a different
// camera to be live rather than the camera selected in the view).
overrides?: Map<string, string>;
}
declare module 'view' {
interface ViewContext {
live?: LiveViewContext;
}
}
/**
* Get the state object or dispatch an error. Used in `ha` and `image` live
* providers.
@@ -339,7 +351,7 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getSelectedCameraIndex(): number {
const cameraIDs = this.cameraManager?.getCameraIDs();
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (!cameraIDs || !this.view) {
return 0;
}
@@ -362,7 +374,7 @@ export class FrigateCardLiveCarousel extends LitElement {
* @returns A list of EmblaOptionsTypes.
*/
protected _getPlugins(): EmblaCarouselPlugins {
const cameras = this.cameraManager?.getCameraIDs();
const cameras = this.cameraManager?.getStore().getVisibleCameraIDs();
return [
// Only enable wheel plugin if there is more than one camera.
...(cameras && cameras.size > 1
@@ -420,18 +432,27 @@ export class FrigateCardLiveCarousel extends LitElement {
* name to slide number.
*/
protected _getSlides(): [TemplateResult[], Record<string, number>] {
const cameras = this.cameraManager?.getCameras();
if (!cameras) {
const visibleCameras = this.cameraManager?.getStore().getVisibleCameras();
if (!visibleCameras) {
return [[], {}];
}
const slides: TemplateResult[] = [];
const cameraToSlide: Record<string, number> = {};
for (const [camera, cameraConfig] of cameras) {
const slide = this._renderLive(camera, cameraConfig, slides.length);
for (const [cameraID, cameraConfig] of visibleCameras) {
const liveCameraID =
this.view?.context?.live?.overrides?.get(cameraID) ?? cameraID;
const liveCameraConfig =
cameraID === liveCameraID
? cameraConfig
: this.cameraManager?.getStore().getCameraConfig(liveCameraID);
const slide = liveCameraConfig
? this._renderLive(liveCameraID, liveCameraConfig, slides.length)
: null;
if (slide) {
cameraToSlide[camera] = slides.length;
cameraToSlide[cameraID] = slides.length;
slides.push(slide);
}
}
@@ -442,7 +463,7 @@ export class FrigateCardLiveCarousel extends LitElement {
* Handle the user selecting a new slide in the carousel.
*/
protected _setViewHandler(ev: CustomEvent<CarouselSelect>): void {
const cameras = this.cameraManager?.getCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (cameras && ev.detail.index !== this._getSelectedCameraIndex()) {
this._setViewCameraID(Array.from(cameras.keys())[ev.detail.index]);
}
@@ -515,7 +536,7 @@ export class FrigateCardLiveCarousel extends LitElement {
<frigate-card-live-provider
?disabled=${this.liveConfig.lazy_load}
.cameraConfig=${cameraConfig}
.cameraEndpoints=${guard([this.cameraManager], () =>
.cameraEndpoints=${guard([this.cameraManager, cameraID], () =>
this.cameraManager?.getCameraEndpoints(cameraID),
)}
.label=${cameraMetadata?.title ?? ''}
@@ -535,7 +556,7 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getCameraIDsOfNeighbors(): [string | null, string | null] {
const cameras = this.cameraManager?.getCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (!cameras || !this.view || !this.hass) {
return [null, null];
}
@@ -557,15 +578,13 @@ export class FrigateCardLiveCarousel extends LitElement {
* @returns A template to display to the user.
*/
protected render(): TemplateResult | void {
if (!this.liveConfig || !this.view || !this.hass || !this.cameraManager) {
return;
}
const [slides, cameraToSlide] = this._getSlides();
this._cameraToSlide = cameraToSlide;
if (
!slides.length ||
!this.liveConfig ||
!this.view ||
!this.hass ||
!this.cameraManager
) {
if (!slides.length) {
return;
}
@@ -577,15 +596,19 @@ export class FrigateCardLiveCarousel extends LitElement {
const [prevID, nextID] = this._getCameraIDsOfNeighbors();
const overrideCameraID = (cameraID: string): string => {
return this.view?.context?.live?.overrides?.get(cameraID) ?? cameraID;
};
const cameraMetadataPrevious = prevID
? this.cameraManager.getCameraMetadata(this.hass, prevID)
? this.cameraManager.getCameraMetadata(this.hass, overrideCameraID(prevID))
: null;
const cameraMetadataCurrent = this.cameraManager.getCameraMetadata(
this.hass,
this.view.camera,
overrideCameraID(this.view.camera),
);
const cameraMetadataNext = nextID
? this.cameraManager.getCameraMetadata(this.hass, nextID)
? this.cameraManager.getCameraMetadata(this.hass, overrideCameraID(nextID))
: null;
// Notes on the below:
+7 -10
View File
@@ -31,10 +31,7 @@ import { MediaQueriesClassifier } from '../view/media-queries-classifier';
import { View } from '../view/view';
import { CameraManager } from '../camera-manager/manager';
import { HomeAssistant } from 'custom-card-helpers';
import {
MediaMetadata,
QueryType,
} from '../camera-manager/types';
import { DataQuery, MediaMetadata, QueryType } from '../camera-manager/types';
import format from 'date-fns/format';
import endOfMonth from 'date-fns/endOfMonth';
import isEqual from 'lodash-es/isEqual';
@@ -168,7 +165,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ev: CustomEvent<{ value: unknown }>,
): Promise<void> {
const cameras = this.cameraManager?.getCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (!this.hass || !cameras || !this.cameraManager || !this.view) {
return;
}
@@ -269,7 +266,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('cameraManager')) {
const cameras = this.cameraManager?.getCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (cameras) {
this._cameraOptions = Array.from(cameras.keys()).map((cameraID) => ({
value: cameraID,
@@ -321,7 +318,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
protected _getDefaultsFromView(): MediaFilterCoreDefaults | null {
const queries = this.view?.query?.getQueries();
const cameras = this.cameraManager?.getCameras();
const cameras = this.cameraManager?.getStore().getVisibleCameras();
if (!this.view || !queries || !cameras) {
return null;
}
@@ -333,12 +330,12 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
let favorite: MediaFilterCoreFavoriteSelection | undefined;
const cameraIDSets = uniqWith(
queries.map((query) => query.cameraIDs),
queries.map((query: DataQuery) => query.cameraIDs),
isEqual,
);
// Special note: If all cameras are selected, this is the same as no
// Special note: If all visible cameras are selected, this is the same as no
// selector at all.
if (cameraIDSets.length === 1 && queries[0].cameraIDs.size !== cameras.size) {
if (cameraIDSets.length === 1 && isEqual(queries[0].cameraIDs, cameras)) {
cameraIDs = [...queries[0].cameraIDs];
}
+8 -5
View File
@@ -22,6 +22,7 @@ import { ThumbnailCarouselTap } from './thumbnail-carousel.js';
import './surround-basic.js';
import { changeViewToRecentEventsForCameraAndDependents } from '../utils/media-to-view';
import { getAllDependentCameras } from '../utils/camera.js';
import type { DataQuery } from '../camera-manager/types';
interface ThumbnailViewContext {
// Whether or not to fetch thumbnails.
@@ -120,7 +121,7 @@ export class FrigateCardSurround extends LitElement {
// user is scrubbing video).
if (
changedProperties.has('view') &&
View.isMediaChange(changedProperties.get('view'), this.view)
View.isMajorMediaChange(changedProperties.get('view'), this.view)
) {
this._cameraIDsForTimeline = this._getCameraIDsForTimeline() ?? undefined;
}
@@ -138,18 +139,20 @@ export class FrigateCardSurround extends LitElement {
}
protected _getCameraIDsForTimeline(): Set<string> | null {
const cameras = this.cameraManager?.getCameras();
if (!this.view || !cameras) {
if (!this.view) {
return null;
}
if (this.view?.is('live')) {
return getAllDependentCameras(cameras, this.view.camera);
return getAllDependentCameras(
this.cameraManager,
this.view.camera,
);
}
if (this.view.isViewerView()) {
return new Set(
this.view.query
?.getQueries()
?.map((query) => [...query.cameraIDs])
?.map((query: DataQuery) => [...query.cameraIDs])
.flat(),
);
}
+5 -15
View File
@@ -238,7 +238,7 @@ export class FrigateCardTimelineCore extends LitElement {
const item = request.detail.item;
const media = this._timelineSource?.dataset.get(item)?.media;
const cameraConfig = media
? this.cameraManager?.getCameraConfig(media.getCameraID()) ?? undefined
? this.cameraManager?.getStore().getCameraConfigForMedia(media) ?? undefined
: undefined;
request.detail.hass = this.hass;
@@ -291,15 +291,9 @@ export class FrigateCardTimelineCore extends LitElement {
* @returns A set of camera ids (may be empty).
*/
protected _getTimelineCameraIDs(): Set<string> | null {
return this.cameraIDs ?? this._getAllCameraIDs();
}
/**
* Get all the keys of all cameras.
* @returns A set of camera ids (may be empty).
*/
protected _getAllCameraIDs(): Set<string> | null {
return this.cameraManager?.getCameraIDs() ?? null;
return (
this.cameraIDs ?? this.cameraManager?.getStore().getVisibleCameraIDs() ?? null
);
}
/**
@@ -1042,11 +1036,7 @@ export class FrigateCardTimelineCore extends LitElement {
changedProps.has('cameraIDs')
) {
const cameraIDs = this._getTimelineCameraIDs();
if (
cameraIDs &&
this.cameraManager &&
this.timelineConfig
) {
if (cameraIDs && this.cameraManager && this.timelineConfig) {
this._timelineSource = new TimelineDataSource(
this.cameraManager,
cameraIDs,