Improve handling of non-Frigate cameras.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)}"
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -19,4 +19,5 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user