diff --git a/src/components/live.ts b/src/components/live.ts
index 98cc2bc6..7572d882 100644
--- a/src/components/live.ts
+++ b/src/components/live.ts
@@ -1,3 +1,4 @@
+// TODO double media load event for webrtc
// TODO webrtc entities in camera section?
// TODO conditional elements based on camera name (requires event changed to propagate upwards)
// TODO Remove media load event warning
@@ -5,7 +6,7 @@
// TODO search for TODOs
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
-import type {
+import {
BrowseMediaSource,
ExtendedHomeAssistant,
CameraConfig,
@@ -14,6 +15,7 @@ import type {
MediaShowInfo,
WebRTCConfig,
StateParameters,
+ FrigateCardError,
} from '../types.js';
import { EmblaOptionsType } from 'embla-carousel';
import { HomeAssistant } from 'custom-card-helpers';
@@ -420,6 +422,7 @@ export class FrigateCardLiveProvider extends LitElement {
: this.liveConfig.provider == 'webrtc'
? html`
`
@@ -482,23 +485,35 @@ export class FrigateCardLiveWebRTC extends LitElement {
@property({ attribute: false })
protected webRTCConfig?: WebRTCConfig;
+ @property({ attribute: false })
+ protected cameraConfig?: CameraConfig;
+
protected hass?: HomeAssistant & ExtendedHomeAssistant;
- protected _webRTCElement: HTMLElement | null = null;
- protected _callbacksAdded = false;
/**
* Create the WebRTC element. May throw.
*/
- protected _createWebRTC(): TemplateResult | void {
+ protected _createWebRTC(): HTMLElement | undefined {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const webrtcElement = customElements.get('webrtc-camera') as any;
if (webrtcElement) {
const webrtc = new webrtcElement();
- webrtc.setConfig(this.webRTCConfig);
+ const config = {...this.webRTCConfig};
+
+ // If the live WebRTC configuration does not specify a URL/entity to use,
+ // then take values from the camera configuration instead (if there are
+ // any).
+ if (!config.url) {
+ config.url = this.cameraConfig?.webrtc?.url;
+ }
+ if (!config.entity) {
+ config.entity = this.cameraConfig?.webrtc?.entity;
+ }
+ webrtc.setConfig(config);
webrtc.hass = this.hass;
- this._webRTCElement = webrtc;
+ return webrtc;
} else {
- throw new Error(localize('error.missing_webrtc'));
+ throw new FrigateCardError(localize('error.webrtc_missing'));
}
}
@@ -510,24 +525,23 @@ export class FrigateCardLiveWebRTC extends LitElement {
if (!this.hass) {
return;
}
- if (!this._webRTCElement) {
- try {
- this._createWebRTC();
- } catch (e) {
- return dispatchErrorMessageEvent(this, (e as Error).message);
- }
+ let webrtcElement: HTMLElement | undefined;
+ try {
+ webrtcElement = this._createWebRTC();
+ } catch (e) {
+ return dispatchErrorMessageEvent(
+ this,
+ e instanceof FrigateCardError
+ ? (e as FrigateCardError).message
+ : localize('error.webrtc_reported_error') + ': ' + (e as Error).message);
}
- return html`${this._webRTCElement}`;
+ return html`${webrtcElement}`;
}
/**
* Updated lifecycle callback.
*/
public updated(): void {
- if (this._callbacksAdded) {
- return;
- }
-
// Extract the video component after it has been rendered and generate the
// media load event.
this.updateComplete.then(() => {
@@ -555,7 +569,6 @@ export class FrigateCardLiveWebRTC extends LitElement {
}
dispatchPauseEvent(this);
};
- this._callbacksAdded = true;
}
});
}
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index 688732b8..90ddbcb5 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -158,7 +158,8 @@
"invalid_configuration": "Invalid configuration",
"invalid_configuration_no_hint": "No location hint available (bad or missing type?)",
"upgrade_available": "An automated card configuration upgrade is available, please visit the visual card editor",
- "missing_webrtc": "WebRTC component not found",
+ "webrtc_missing": "WebRTC component not found",
+ "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",
"duplicate_camera_id": "Duplicate Frigate camera, use the 'id' parameter to uniquely identify cameras with the same 'camera_entity' or 'camera_name'",
"could_not_render_elements": "Could not render picture elements",
diff --git a/src/types.ts b/src/types.ts
index 5ab82a2f..0e11174c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -59,6 +59,8 @@ const FRIGATE_MENU_MODES = [
] as const;
const LIVE_PROVIDERS = ['frigate', 'frigate-jsmpeg', 'webrtc'] as const;
+export class FrigateCardError extends Error {}
+
/**
* Action Types (for "Picture Elements" / Menu)
*/
@@ -349,11 +351,16 @@ const pictureElementsSchema = pictureElementSchema.array().optional();
export type PictureElements = z.infer;
/**
- * Frigate configuration section.
+ * Camera configuration section
*/
export const cameraConfigDefault = {
client_id: 'frigate' as const,
};
+const webrtcCameraConfigSchema = z
+ .object({
+ entity: z.string().optional(),
+ url: z.string().optional(),
+ });
const cameraConfigDefaultSchema = z
.object({
// No URL validation to allow relative URLs within HA (e.g. Frigate addon).
@@ -372,6 +379,9 @@ const cameraConfigDefaultSchema = z
// Optional identifier to separate different camera configurations used in
// this card.
id: z.string().optional(),
+
+ // Camera identifiers for WebRTC.
+ webrtc: webrtcCameraConfigSchema.optional(),
})
.default(cameraConfigDefault);
export type CameraConfig = z.infer;
@@ -459,13 +469,8 @@ const liveConfigDefault = {
},
},
};
-const webrtcConfigSchema = z
- .object({
- entity: z.string().optional(),
- url: z.string().optional(),
- })
- .passthrough()
- .optional();
+
+const webrtcConfigSchema = webrtcCameraConfigSchema.passthrough().optional();
export type WebRTCConfig = z.infer;
const jsmpegConfigSchema = z