Improve handling of non-Frigate cameras.

This commit is contained in:
Dermot Duffy
2022-01-14 21:31:16 -08:00
parent 64af74ae26
commit 73caa5d52a
8 changed files with 87 additions and 45 deletions
+2 -2
View File
@@ -96,7 +96,7 @@ cameras:
| `title` | Autodetected from `camera_entity` if that is specified. | :heavy_multiplication_x: | A friendly name for this camera to use in the card. | | `title` | Autodetected from `camera_entity` if that is specified. | :heavy_multiplication_x: | A friendly name for this camera to use in the card. |
| `icon` | Autodetected from `camera_entity` if that is specified. | :heavy_multiplication_x: | The icon to use for this camera in the camera menu and in the next & previous controls when using the `icon` style. | | `icon` | Autodetected from `camera_entity` if that is specified. | :heavy_multiplication_x: | The icon to use for this camera in the camera menu and in the next & previous controls when using the `icon` style. |
| `webrtc` | | :heavy_multiplication_x: | The WebRTC entity/URL to use for this camera. See below. | | `webrtc` | | :heavy_multiplication_x: | The WebRTC entity/URL to use for this camera. See below. |
| `id` | `camera_entity`, or `camera_name` if set (in that preference order). | :heavy_multiplication_x: | An optional identifier to use throughout the card configuration to refer unambiguously to this camera. See [camera IDs](#camera-ids). | | `id` | `camera_entity`, `webrtc.entity` or `camera_name` if set (in that preference order). | :heavy_multiplication_x: | An optional identifier to use throughout the card configuration to refer unambiguously to this camera. See [camera IDs](#camera-ids). |
#### Camera WebRTC configuration #### Camera WebRTC configuration
@@ -118,7 +118,7 @@ See [Using WebRTC](#webrtc) below for more details on how to use WebRTC with thi
#### Camera IDs: Refering to cameras in card configuration #### Camera IDs: Refering to cameras in card configuration
Each camera configured in the card has a single identifier (`id`). For a given camera, this will be one of the camera {`id`, `camera_entity` or `camera_name`} parameters for that camera -- in that order of precedence. These ids may be used in conditions or custom actions to refer to a given camera unambiguously. | Each camera configured in the card has a single identifier (`id`). For a given camera, this will be one of the camera {`id`, `camera_entity`, `webrtc.entity` or `camera_name`} parameters for that camera -- in that order of precedence. These ids may be used in conditions or custom actions to refer to a given camera unambiguously. |
#### Example #### Example
+13 -1
View File
@@ -147,13 +147,25 @@ export class BrowseMediaUtil {
* Get the parameters to search for media related to the current view. * Get the parameters to search for media related to the current view.
* @returns A BrowseMediaQueryParameters object. * @returns A BrowseMediaQueryParameters object.
*/ */
static getBrowseMediaQueryParametersFromView( static getBrowseMediaQueryParametersOrDispatchError(
node: HTMLElement,
view: View, view: View,
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
): BrowseMediaQueryParameters | undefined { ): BrowseMediaQueryParameters | undefined {
if (!view.isClipRelatedView() && !view.isSnapshotRelatedView()) { if (!view.isClipRelatedView() && !view.isSnapshotRelatedView()) {
return undefined; return undefined;
} }
// Verify there is a camera name, otherwise getBrowseMediaQueryParameters()
// will return undefined.
if (!cameraConfig.camera_name) {
dispatchErrorMessageEvent(
node,
localize('error.no_camera_name') + `: ${JSON.stringify(cameraConfig)}`,
);
return undefined;
}
return BrowseMediaUtil.getBrowseMediaQueryParameters( return BrowseMediaUtil.getBrowseMediaQueryParameters(
view.isClipRelatedView() ? 'clips' : 'snapshots', view.isClipRelatedView() ? 'clips' : 'snapshots',
cameraConfig, cameraConfig,
+20 -20
View File
@@ -432,18 +432,24 @@ export class FrigateCard extends LitElement {
} }
} }
if (config.camera_name) { const id =
const id = config.id || config.camera_entity || config.camera_name; config.id || config.camera_entity || config.webrtc?.entity || config.camera_name;
if (cameras.has(id)) {
if (!id) {
this._setMessageAndUpdate({ this._setMessageAndUpdate({
message: localize('error.duplicate_camera_id'), message: localize('error.no_camera_id') + `: ${JSON.stringify(config)}`,
type: 'error',
});
errorFree = false;
} else if (cameras.has(id)) {
this._setMessageAndUpdate({
message: localize('error.duplicate_camera_id') + `: ${JSON.stringify(config)}`,
type: 'error', type: 'error',
}); });
errorFree = false; errorFree = false;
} else { } else {
cameras.set(id, config); cameras.set(id, config);
} }
}
}; };
if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) { if (this._getConfig().cameras && Array.isArray(this._getConfig().cameras)) {
@@ -506,11 +512,6 @@ export class FrigateCard extends LitElement {
// Pass. // Pass.
} }
// Fallback: Guess from the entity_id.
if (entity.includes('.')) {
return entity.split('.', 2)[1];
}
return null; return null;
} }
@@ -842,6 +843,9 @@ export class FrigateCard extends LitElement {
if (!cameraConfig || !cameraConfig.frigate_url || !this._view) { if (!cameraConfig || !cameraConfig.frigate_url || !this._view) {
return null; return null;
} }
if (!cameraConfig.camera_name) {
return cameraConfig.frigate_url;
}
if (this._view.isViewerView() || this._view.isGalleryView()) { if (this._view.isViewerView() || this._view.isGalleryView()) {
return `${cameraConfig.frigate_url}/events?camera=${cameraConfig.camera_name}`; return `${cameraConfig.frigate_url}/events?camera=${cameraConfig.camera_name}`;
} }
@@ -1015,12 +1019,14 @@ export class FrigateCard extends LitElement {
// Do not artifically constrain aspect ratio if: // Do not artifically constrain aspect ratio if:
// - It's fullscreen. // - It's fullscreen.
// - Aspect ratio enforcement is disabled. // - Aspect ratio enforcement is disabled.
// - Or aspect ratio enforcement is dynamic and it's a media view (i.e. not the gallery). // - Aspect ratio enforcement is dynamic and it's a media view (i.e. not the gallery).
// - There is a message to display to the user.
return !( return !(
(screenfull.isEnabled && screenfull.isFullscreen) || (screenfull.isEnabled && screenfull.isFullscreen) ||
aspectRatioMode == 'unconstrained' || aspectRatioMode == 'unconstrained' ||
(aspectRatioMode == 'dynamic' && this._view?.isMediaView()) (aspectRatioMode == 'dynamic' && this._view?.isMediaView() ||
this._message != null)
); );
} }
@@ -1187,10 +1193,7 @@ export class FrigateCard extends LitElement {
? html` <frigate-card-gallery ? html` <frigate-card-gallery
.hass=${this._hass} .hass=${this._hass}
.view=${this._view} .view=${this._view}
.browseMediaQueryParameters=${BrowseMediaUtil.getBrowseMediaQueryParametersFromView( .cameraConfig=${cameraConfig}
this._view,
cameraConfig,
)}
class="${classMap(galleryClasses)}" class="${classMap(galleryClasses)}"
> >
</frigate-card-gallery>` </frigate-card-gallery>`
@@ -1199,10 +1202,7 @@ export class FrigateCard extends LitElement {
? html` <frigate-card-viewer ? html` <frigate-card-viewer
.hass=${this._hass} .hass=${this._hass}
.view=${this._view} .view=${this._view}
.browseMediaQueryParameters=${BrowseMediaUtil.getBrowseMediaQueryParametersFromView( .cameraConfig=${cameraConfig}
this._view,
cameraConfig,
)}
.viewerConfig=${this._getConfig().event_viewer} .viewerConfig=${this._getConfig().event_viewer}
.resolvedMediaCache=${this._resolvedMediaCache} .resolvedMediaCache=${this._resolvedMediaCache}
class="${classMap(viewerClasses)}" class="${classMap(viewerClasses)}"
+14 -4
View File
@@ -4,7 +4,7 @@ import { HomeAssistant } from 'custom-card-helpers';
import { customElement, property, state } from 'lit/decorators.js'; import { customElement, property, state } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
import type { BrowseMediaQueryParameters, ExtendedHomeAssistant } from '../types.js'; import type { CameraConfig, ExtendedHomeAssistant } from '../types.js';
import { BrowseMediaUtil } from '../browse-media-util.js'; import { BrowseMediaUtil } from '../browse-media-util.js';
import { View } from '../view.js'; import { View } from '../view.js';
import { renderProgressIndicator } from './message.js'; import { renderProgressIndicator } from './message.js';
@@ -23,23 +23,33 @@ export class FrigateCardGallery extends LitElement {
protected view?: Readonly<View>; protected view?: Readonly<View>;
@property({ attribute: false }) @property({ attribute: false })
protected browseMediaQueryParameters?: BrowseMediaQueryParameters; protected cameraConfig?: CameraConfig;
/** /**
* Master render method. * Master render method.
* @returns A rendered template. * @returns A rendered template.
*/ */
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if (!this.hass || !this.view || !this.browseMediaQueryParameters) { if (!this.hass || !this.view || !this.cameraConfig) {
return; return;
} }
if (!this.view.target) { if (!this.view.target) {
const browseMediaQueryParameters =
BrowseMediaUtil.getBrowseMediaQueryParametersOrDispatchError(
this,
this.view,
this.cameraConfig,
);
if (!browseMediaQueryParameters) {
return;
}
BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange( BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange(
this, this,
this.hass, this.hass,
this.view, this.view,
this.browseMediaQueryParameters, browseMediaQueryParameters,
); );
return renderProgressIndicator(); return renderProgressIndicator();
} }
+11 -2
View File
@@ -1,4 +1,5 @@
// TODO readme // TODO autodetect live provider from cameras configuration, or allow explicit setting.
// TODO verify README links worked correctly (e.g. basic cameras configuration)
import { import {
CSSResultGroup, CSSResultGroup,
@@ -371,7 +372,8 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
const config = getOverriddenConfig( const config = getOverriddenConfig(
this.liveConfig, this.liveConfig,
this.liveOverrides, this.liveOverrides,
conditionState) as LiveConfig; conditionState,
) as LiveConfig;
return html` <div class="embla__slide"> return html` <div class="embla__slide">
<frigate-card-live-provider <frigate-card-live-provider
@@ -832,6 +834,13 @@ export class FrigateCardLiveJSMPEG extends LitElement {
this._jsmpegCanvasElement = document.createElement('canvas'); this._jsmpegCanvasElement = document.createElement('canvas');
this._jsmpegCanvasElement.className = 'media'; this._jsmpegCanvasElement.className = 'media';
if (!this.cameraConfig?.camera_name) {
return dispatchErrorMessageEvent(
this,
localize('error.no_camera_name') + `: ${JSON.stringify(this.cameraConfig)}`,
);
}
const url = await this._getURL(); const url = await this._getURL();
if (url) { if (url) {
this._jsmpegVideoPlayer = this._createJSMPEGPlayer(url); this._jsmpegVideoPlayer = this._createJSMPEGPlayer(url);
+16 -8
View File
@@ -18,6 +18,7 @@ import type {
BrowseMediaNeighbors, BrowseMediaNeighbors,
BrowseMediaQueryParameters, BrowseMediaQueryParameters,
BrowseMediaSource, BrowseMediaSource,
CameraConfig,
ExtendedHomeAssistant, ExtendedHomeAssistant,
MediaShowInfo, MediaShowInfo,
ViewerConfig, ViewerConfig,
@@ -30,10 +31,7 @@ import {
} from './thumbnail-carousel.js'; } from './thumbnail-carousel.js';
import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js'; import { ResolvedMediaCache, ResolvedMediaUtil } from '../resolved-media.js';
import { View } from '../view.js'; import { View } from '../view.js';
import { import { createMediaShowInfo, dispatchErrorMessageEvent } from '../common.js';
createMediaShowInfo,
dispatchErrorMessageEvent,
} from '../common.js';
import { renderProgressIndicator } from '../components/message.js'; import { renderProgressIndicator } from '../components/message.js';
import './next-prev-control.js'; import './next-prev-control.js';
@@ -53,7 +51,7 @@ export class FrigateCardViewer extends LitElement {
protected viewerConfig?: ViewerConfig; protected viewerConfig?: ViewerConfig;
@property({ attribute: false }) @property({ attribute: false })
protected browseMediaQueryParameters?: BrowseMediaQueryParameters; protected cameraConfig?: CameraConfig;
@property({ attribute: false }) @property({ attribute: false })
protected resolvedMediaCache?: ResolvedMediaCache; protected resolvedMediaCache?: ResolvedMediaCache;
@@ -63,7 +61,17 @@ export class FrigateCardViewer extends LitElement {
* @returns A rendered template. * @returns A rendered template.
*/ */
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if (!this.hass || !this.view || !this.browseMediaQueryParameters) { if (!this.hass || !this.view || !this.cameraConfig) {
return;
}
const browseMediaQueryParameters =
BrowseMediaUtil.getBrowseMediaQueryParametersOrDispatchError(
this,
this.view,
this.cameraConfig,
);
if (!browseMediaQueryParameters) {
return; return;
} }
@@ -72,7 +80,7 @@ export class FrigateCardViewer extends LitElement {
this, this,
this.hass, this.hass,
this.view, this.view,
this.browseMediaQueryParameters, browseMediaQueryParameters,
); );
return renderProgressIndicator(); return renderProgressIndicator();
} }
@@ -82,7 +90,7 @@ export class FrigateCardViewer extends LitElement {
.viewerConfig=${this.viewerConfig} .viewerConfig=${this.viewerConfig}
.resolvedMediaCache=${this.resolvedMediaCache} .resolvedMediaCache=${this.resolvedMediaCache}
.hass=${this.hass} .hass=${this.hass}
.browseMediaQueryParameters=${this.browseMediaQueryParameters} .browseMediaQueryParameters=${browseMediaQueryParameters}
> >
</frigate-card-viewer-core>`; </frigate-card-viewer-core>`;
} }
+4 -2
View File
@@ -171,12 +171,14 @@
"upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor", "upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor",
"webrtc_missing": "WebRTC component not found", "webrtc_missing": "WebRTC component not found",
"webrtc_reported_error": "WebRTC component reported an error", "webrtc_reported_error": "WebRTC component reported an error",
"no_cameras": "No valid cameras found, you must configure at least one camera with either a camera_entity or camera_name", "no_cameras": "No valid cameras found, you must configure at least one camera entry",
"duplicate_camera_id": "Duplicate Frigate camera, use the 'id' parameter to uniquely identify cameras with the same 'camera_entity' or 'camera_name'", "no_camera_id": "Could not determine camera id for the following camera, may need to set 'id' parameter manually",
"duplicate_camera_id": "Duplicate Frigate camera id for the following camera, use the 'id' parameter to uniquely identify cameras",
"could_not_render_elements": "Could not render picture elements", "could_not_render_elements": "Could not render picture elements",
"invalid_elements_config": "Invalid picture elements configuration", "invalid_elements_config": "Invalid picture elements configuration",
"jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path", "jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path",
"jsmpeg_no_player": "Could not start JSMPEG player", "jsmpeg_no_player": "Could not start JSMPEG player",
"no_camera_name": "Could not determine Frigate camera name for camera, please specify either 'camera_entity' or 'camera_name' for the following camera",
"download_no_media": "No media to download", "download_no_media": "No media to download",
"download_no_event_id": "Could not extract Frigate event id from media", "download_no_event_id": "Could not extract Frigate event id from media",
"download_sign_failed": "Could not sign media URL for download" "download_sign_failed": "Could not sign media URL for download"
+1
View File
@@ -19,4 +19,5 @@
span { span {
padding: 10px; padding: 10px;
word-break: break-word;
} }