Allow camera capabilities to be configured.

This commit is contained in:
Dermot Duffy
2024-03-26 21:00:15 -07:00
parent 0c7260d95a
commit 95c44adcf1
63 changed files with 2308 additions and 1447 deletions
+15 -10
View File
@@ -182,11 +182,11 @@ export class MediaFilterController {
return null;
};
const visibleCameraIDs = cameraManager.getStore().getVisibleCameraIDs();
if (!visibleCameraIDs.size || !values.mediaType) {
const cameraIDs =
getArrayValueAsSet(values.camera) ?? this._getAllCameraIDs(cameraManager);
if (!cameraIDs.size || !values.mediaType) {
return;
}
const cameraIDs = getArrayValueAsSet(values.camera) ?? visibleCameraIDs;
const when = this._getWhen(values.when);
const favorite = values.favorite
@@ -280,10 +280,16 @@ export class MediaFilterController {
this._host.requestUpdate();
}
protected _getAllCameraIDs(cameraManager: CameraManager): Set<string> {
return cameraManager.getStore().getCameraIDsWithCapability({
anyCapabilities: ['clips', 'snapshots', 'recordings'],
});
}
public computeInitialDefaultsFromView(cameraManager: CameraManager, view: View): void {
const queries = view.query?.getQueries();
const visibleCameraIDs = cameraManager.getStore().getVisibleCameraIDs();
if (!queries || !visibleCameraIDs.size) {
const allCameraIDs = this._getAllCameraIDs(cameraManager);
if (!queries || !allCameraIDs.size) {
return;
}
@@ -300,7 +306,7 @@ export class MediaFilterController {
);
// Special note: If all visible cameras are selected, this is the same as no
// selector at all.
if (cameraIDSets.length === 1 && !isEqual(queries[0].cameraIDs, visibleCameraIDs)) {
if (cameraIDSets.length === 1 && !isEqual(queries[0].cameraIDs, allCameraIDs)) {
cameraIDs = [...queries[0].cameraIDs];
}
@@ -375,8 +381,7 @@ export class MediaFilterController {
}
public computeCameraOptions(cameraManager: CameraManager): void {
const cameras = cameraManager.getStore().getVisibleCameraIDs();
this._cameraOptions = [...cameras].map((cameraID) => ({
this._cameraOptions = [...this._getAllCameraIDs(cameraManager)].map((cameraID) => ({
value: cameraID,
label: cameraManager.getCameraMetadata(cameraID)?.title ?? cameraID,
}));
@@ -451,9 +456,9 @@ export class MediaFilterController {
events: events,
recordings: recordings,
favorites: events
? !!managerCapabilities?.canFavoriteEvents
? managerCapabilities?.has('favorite-events')
: recordings
? !!managerCapabilities?.canFavoriteRecordings
? managerCapabilities?.has('favorite-recordings')
: false,
};
}
+22 -16
View File
@@ -24,6 +24,7 @@ import { getEntityIcon, getEntityTitle } from '../utils/ha';
import { hasUsablePTZ } from '../utils/ptz';
import { hasSubstream } from '../utils/substream';
import { View } from '../view/view';
import { getCameraIDsForViewName } from '../view/view-to-cameras';
export interface MenuButtonControllerOptions {
currentMediaLoadedInfo?: MediaLoadedInfo | null;
@@ -62,16 +63,12 @@ export class MenuButtonController {
view: View,
options?: MenuButtonControllerOptions,
): MenuItem[] {
const visibleCameraIDs = cameraManager.getStore().getVisibleCameraIDs();
const selectedCameraID = view.camera;
const substreamAwareCameraID =
view.context?.live?.overrides?.get(selectedCameraID) ?? selectedCameraID;
const selectedCameraConfig = cameraManager
.getStore()
.getCameraConfig(selectedCameraID);
const allSelectedCameraIDs = cameraManager
.getStore()
.getAllDependentCameras(selectedCameraID);
const substreamAwareCameraCapabilities =
cameraManager.getCameraCapabilities(substreamAwareCameraID);
@@ -98,9 +95,12 @@ export class MenuButtonController {
) as FrigateCardCustomAction,
});
if (visibleCameraIDs.size) {
// Show all cameras in the menu rather than just cameras that support the
// current view for a less surprising UX.
const menuCameraIDs = cameraManager.getStore().getCameraIDsWithCapability('menu');
if (menuCameraIDs.size) {
const menuItems = Array.from(
cameraManager.getStore().getCameraConfigEntries(visibleCameraIDs),
cameraManager.getStore().getCameraConfigEntries(menuCameraIDs),
([cameraID, config]) => {
const action = createFrigateCardCameraAction('camera_select', cameraID);
const metadata = cameraManager.getCameraMetadata(cameraID);
@@ -126,10 +126,15 @@ export class MenuButtonController {
});
}
if (selectedCameraID && allSelectedCameraIDs && view.is('live')) {
const dependencies = [...allSelectedCameraIDs];
const substreamCameraIDs = cameraManager
.getStore()
.getAllDependentCameras(selectedCameraID, 'substream');
if (dependencies.length === 2) {
if (selectedCameraID && substreamCameraIDs && view.is('live')) {
const substreams = [...substreamCameraIDs].filter((cameraID) => cameraID !== selectedCameraID);
const streams = [selectedCameraID, ...substreams];
if (streams.length === 2) {
// If there are only two dependencies (the main camera, and 1 other)
// then use a button not a menu to toggle.
buttons.push({
@@ -145,14 +150,14 @@ export class MenuButtonController {
hasSubstream(view) ? 'live_substream_off' : 'live_substream_on',
) as FrigateCardCustomAction,
});
} else if (dependencies.length > 2) {
const menuItems = Array.from(dependencies, (cameraID) => {
} else if (streams.length > 2) {
const menuItems = Array.from(streams, (streamID) => {
const action = createFrigateCardCameraAction(
'live_substream_select',
cameraID,
streamID,
);
const metadata = cameraManager.getCameraMetadata(cameraID) ?? undefined;
const cameraConfig = cameraManager.getStore().getCameraConfig(cameraID);
const metadata = cameraManager.getCameraMetadata(streamID) ?? undefined;
const cameraConfig = cameraManager.getStore().getCameraConfig(streamID);
return {
enabled: true,
icon: metadata?.icon,
@@ -161,7 +166,7 @@ export class MenuButtonController {
title: metadata?.title,
selected:
(view.context?.live?.overrides?.get(selectedCameraID) ??
selectedCameraID) === cameraID,
selectedCameraID) === streamID,
...(action && { tap_action: action }),
};
});
@@ -409,7 +414,8 @@ export class MenuButtonController {
});
}
if (view.supportsMultipleDisplayModes() && visibleCameraIDs.size > 1) {
const viewCameraIDs = getCameraIDsForViewName(cameraManager, view.view);
if (view.supportsMultipleDisplayModes() && viewCameraIDs.size > 1) {
const isGrid = view.isGrid();
buttons.push({
icon: isGrid ? 'mdi:grid-off' : 'mdi:grid',
+1 -1
View File
@@ -137,7 +137,7 @@ export class PTZController {
const ptzCapabilities = this._cameraManager.getCameraCapabilities(
this._cameraID,
)?.ptz;
)?.getPTZCapabilities();
const defaultActions: FrigateCardPTZActions = {};
const panTilt = ptzCapabilities?.panTilt;
+4 -3
View File
@@ -4,14 +4,15 @@ import { DataSet } from 'vis-data';
import { IdType, TimelineItem, TimelineWindow } from 'vis-timeline/esnext';
import { CameraManager } from '../camera-manager/manager';
import {
compressRanges,
ExpiringMemoryRangeSet,
MemoryRangeSet,
compressRanges,
} from '../camera-manager/range';
import { EventQuery, RecordingQuery, RecordingSegment } from '../camera-manager/types';
import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera-manager/utils';
import { capEndDate } from '../camera-manager/utils/cap-end-date';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/utils/range-to-cache-friendly';
import { ClipsOrSnapshotsOrAll } from '../types';
import { ModifyInterface, errorToConsole } from '../utils/basic.js';
import { errorToConsole, ModifyInterface } from '../utils/basic.js';
import { ViewMedia } from '../view/media';
// Allow timeline freshness to be at least this number of seconds out of date