Add better control over substreams.

This commit is contained in:
Dermot Duffy
2023-05-06 10:42:17 -07:00
parent fd74c1c855
commit 867a118a16
9 changed files with 405 additions and 26 deletions
+28 -17
View File
@@ -21,6 +21,7 @@ import 'web-dialog';
import { z } from 'zod';
import pkg from '../package.json';
import { actionHandler } from './action-handler-directive.js';
import { AutomationsController } from './automations';
import { CameraManagerEngineFactory } from './camera-manager/engine-factory.js';
import { CameraManager } from './camera-manager/manager.js';
import './components/elements.js';
@@ -91,8 +92,13 @@ import { FrigateCardInitializer } from './utils/initializer.js';
import { isValidMediaLoadedInfo } from './utils/media-info.js';
import { MicrophoneController } from './utils/microphone';
import { getActionsFromQueryString } from './utils/querystring.js';
import {
createViewWithNextStream,
createViewWithoutSubstream,
createViewWithSelectedSubstream,
hasSubstream,
} from './utils/substream';
import { View } from './view/view.js';
import { AutomationsController } from './automations';
/** A note on media callbacks:
*
@@ -485,12 +491,9 @@ class FrigateCard extends LitElement {
title: localize('config.menu.buttons.substreams'),
...this._getConfig().menu.buttons.substreams,
type: 'custom:frigate-card-menu-icon',
tap_action: createFrigateCardCustomAction('live_substream_select', {
camera:
override === undefined || override === dependencies[0]
? dependencies[1]
: dependencies[0],
}) as FrigateCardCustomAction,
tap_action: createFrigateCardCustomAction(
hasSubstream(this._view) ? 'live_substream_off' : 'live_substream_on',
) as FrigateCardCustomAction,
});
} else if (dependencies.length > 2) {
const menuItems = Array.from(dependencies, (cameraID) => {
@@ -1478,7 +1481,7 @@ class FrigateCard extends LitElement {
}
protected _cardActionHandler(frigateCardAction: FrigateCardCustomAction): void {
if (!this._view) {
if (!this._view || !this._cameraManager) {
return;
}
@@ -1550,16 +1553,24 @@ class FrigateCard extends LitElement {
});
}
break;
case 'live_substream_select':
const overrides: Map<string, string> =
this._view.context?.live?.overrides ?? new Map();
overrides.set(this._view.camera, frigateCardAction.camera);
this._changeView({
view: this._view.clone().mergeInContext({
live: { overrides: overrides },
}),
});
case 'live_substream_select': {
const view = createViewWithSelectedSubstream(
this._view,
frigateCardAction.camera,
);
view && this._changeView({ view: view });
break;
}
case 'live_substream_off': {
const view = createViewWithoutSubstream(this._view);
view && this._changeView({ view: view });
break;
}
case 'live_substream_on': {
const view = createViewWithNextStream(this._cameraManager, this._view);
view && this._changeView({ view: view });
break;
}
case 'media_player':
this._mediaPlayerAction(
frigateCardAction.media_player,
+2
View File
@@ -219,6 +219,8 @@ const FRIGATE_CARD_GENERAL_ACTIONS = [
'image',
'live',
'menu_toggle',
'live_substream_on',
'live_substream_off',
'microphone_mute',
'microphone_unmute',
'recording',
+14 -7
View File
@@ -28,12 +28,21 @@ export function getCameraID(
* Get all cameras that depend on a given camera.
* @param cameraManager The camera manager.
* @param cameraID ID of the target camera.
* @returns A set of dependent cameraIDs or null.
* @returns A set of dependent cameraIDs or null (since JS sets guarantee order,
* the first item in the set is guaranteed to be the cameraID itself).
*/
export const getAllDependentCameras = (
export function getAllDependentCameras(
cameraManager: CameraManager,
cameraID: string,
): Set<string>;
export function getAllDependentCameras(
cameraManager?: CameraManager,
cameraID?: string,
): Set<string> | null => {
): Set<string> | null;
export function getAllDependentCameras(
cameraManager?: CameraManager,
cameraID?: string,
): Set<string> | null {
if (!cameraManager || !cameraID) {
return null;
}
@@ -45,9 +54,7 @@ export const getAllDependentCameras = (
if (cameraConfig) {
cameraIDs.add(cameraID);
const dependentCameras: Set<string> = new Set();
(cameraConfig.dependencies.cameras || []).forEach((item) =>
dependentCameras.add(item),
);
cameraConfig.dependencies.cameras.forEach((item) => dependentCameras.add(item));
if (cameraConfig.dependencies.all_cameras) {
cameras.forEach((_, key) => dependentCameras.add(key));
}
@@ -62,4 +69,4 @@ export const getAllDependentCameras = (
getDependentCameras(cameraID);
}
return cameraIDs;
};
}
+48
View File
@@ -0,0 +1,48 @@
import { CameraManager } from '../camera-manager/manager';
import { View } from '../view/view';
import { getAllDependentCameras } from './camera';
export const createViewWithSelectedSubstream = (
view: View,
substreamID: string,
): View | null => {
const overrides: Map<string, string> = view.context?.live?.overrides ?? new Map();
overrides.set(view.camera, substreamID);
return view.clone().mergeInContext({
live: { overrides: overrides },
});
};
export const createViewWithoutSubstream = (view: View): View => {
const newView = view.clone();
const overrides: Map<string, string> | undefined = newView.context?.live?.overrides;
if (overrides && overrides.has(view.camera)) {
newView.context?.live?.overrides?.delete(view.camera);
}
return newView;
};
export const hasSubstream = (view: View): boolean => {
const override = view?.context?.live?.overrides?.get(view.camera);
return !!override && override !== view.camera;
};
export const createViewWithNextStream = (
cameraManager: CameraManager,
view: View,
): View => {
const dependencies = [...getAllDependentCameras(cameraManager, view.camera)];
if (dependencies.length <= 1) {
return view.clone();
}
const newView = view.clone();
const overrides: Map<string, string> = newView.context?.live?.overrides ?? new Map();
const currentOverride = overrides.get(newView.camera) ?? newView.camera;
const currentIndex = dependencies.indexOf(currentOverride);
const newIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % dependencies.length;
overrides.set(view.camera, dependencies[newIndex]);
newView.mergeInContext({ live: { overrides: overrides } });
return newView;
};