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