From 6d2d31f709b93471139a052b288c2c2428ca2915 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 26 Feb 2023 19:47:33 -0800 Subject: [PATCH] Allow the user to specify image URL for image live provider. --- README.md | 4 +- src/components/live/live-image.ts | 3 +- src/components/live/live.ts | 14 ++++--- src/const.ts | 1 + src/editor.ts | 18 ++++++--- src/localize/languages/en.json | 3 +- src/localize/languages/it.json | 3 +- src/localize/languages/pt-BR.json | 3 +- src/types.ts | 63 +++++++++++++++++-------------- 9 files changed, 66 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index fb50de7d..8c5a00d2 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,6 @@ cameras: See [Using the WebRTC Card](#webrtc) below for more details on how to use the WebRTC Card live provider. - #### Live Provider: Image Configuration All configuration is under: @@ -207,6 +206,7 @@ cameras: | Option | Default | Overridable | Description | | - | - | - | - | | `refresh_seconds` | 1 | :heavy_multiplication_x: | The image will be refreshed at least every `refresh_seconds`. `0` implies no refreshing. | +| `url` | | :heavy_multiplication_x: | **Advanced**: A static image URL to be fetched in lieu of the Home Assistant image for the given camera. This may be useful for advanced configurations where the camera image is being provided by some non-Home Assistant system. This will also set the temporary loading image used when `show_image_during_load` is set to true under the `live` configuration. | #### Live Provider: JSMPEG Configuration @@ -3367,7 +3367,7 @@ This could be for any number of reasons. Chromecast devices can be quite picky o ### Javascript console shows `[Violation] Added non-passive event listener to a scroll-blocking [...] event` -This card heavily uses [Embla Carousel](https://www.embla-carousel.com/) -- a light-weight performant carousel library -- to show media. This carousel library uses non-passive event-listeners in a considered and performant way, but one that still causes occasional and unhelpful Chrome warnings. These warnings can be safely ignored in this instance, and cannot easily be fixed in the underlying library as it heavily relies on non-passive event listeners ([see this bug comment for explanation](https://github.com/davidjerleke/embla-carousel/issues/62#issuecomment-628569509)). +This card uses [visjs](https://github.com/visjs/vis-timeline) -- a timeline library -- to show camera timelines. This library currently uses non-passive event-listeners. These warnings can be safely ignored in this instance and cannot easily be fixed in the underlying library. ## Development diff --git a/src/components/live/live-image.ts b/src/components/live/live-image.ts index f4b523a0..236a5596 100644 --- a/src/components/live/live-image.ts +++ b/src/components/live/live-image.ts @@ -51,8 +51,9 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia return html` { - if (ev.name === 'NotAllowedError' && !this.isMuted()) { - this.mute(); - this._providerRef.value?.play().catch(); - } - }); + if (this._providerRef.value?.play) { + this._providerRef.value?.play().catch((ev) => { + if (ev.name === 'NotAllowedError' && !this.isMuted()) { + this.mute(); + this._providerRef.value?.play().catch(); + } + }); + } } public pause(): void { diff --git a/src/const.ts b/src/const.ts index 4a3440f9..d67b3f7d 100644 --- a/src/const.ts +++ b/src/const.ts @@ -19,6 +19,7 @@ export const CONF_CAMERAS_ARRAY_HIDE = `${CONF_CAMERAS}.#.hide` as const; export const CONF_CAMERAS_ARRAY_ID = `${CONF_CAMERAS}.#.id` as const; export const CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS = `${CONF_CAMERAS}.#.image.refresh_seconds` as const; +export const CONF_CAMERAS_ARRAY_IMAGE_URL = `${CONF_CAMERAS}.#.image.url` as const; export const CONF_CAMERAS_ARRAY_TITLE = `${CONF_CAMERAS}.#.title` as const; export const CONF_CAMERAS_ARRAY_ICON = `${CONF_CAMERAS}.#.icon` as const; export const CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY = diff --git a/src/editor.ts b/src/editor.ts index 69b0f388..82be6d77 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -132,6 +132,7 @@ import { CONF_CAMERAS_ARRAY_GO2RTC_MODES, CONF_CAMERAS_ARRAY_GO2RTC_STREAM, CONF_CAMERAS_ARRAY_HIDE, + CONF_CAMERAS_ARRAY_IMAGE_URL, } from './const.js'; import { localize } from './localize/localize.js'; import frigate_card_editor_style from './scss/editor.scss'; @@ -1411,12 +1412,17 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor true, 'config.cameras.image.editor_label', { name: 'mdi:image' }, - html` ${this._renderNumberInput( - getArrayConfigPath( - CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS, - cameraIndex, - ), - )}`, + html` + ${this._renderNumberInput( + getArrayConfigPath( + CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS, + cameraIndex, + ), + )} + ${this._renderStringInput( + getArrayConfigPath(CONF_CAMERAS_ARRAY_IMAGE_URL, cameraIndex), + )} + `, )} ${this._putInSubmenu( MENU_CAMERAS_WEBRTC_CARD, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 6b07176b..c332fec9 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -41,7 +41,8 @@ "icon": "Icon for this camera (Autodetected from entity)", "image": { "editor_label": "Image Options", - "refresh_seconds": "Number of seconds after which to refresh live image (0=never)" + "refresh_seconds": "Number of seconds after which to refresh live image (0=never)", + "url": "Image URL to use instead of camera entity snapshot" }, "id": "Unique id for this camera in this card", "live_provider": "Live view provider for this camera", diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index cb7a7202..d6659bc5 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -41,7 +41,8 @@ "icon": "Icona per questa telecamera (Autoidentificato dall'entità)", "image": { "editor_label": "", - "refresh_seconds": "" + "refresh_seconds": "", + "url": "" }, "id": "ID univoco per questa telecamera in questa carta", "live_provider": "Provider di visualizzazione dal vivo per questa telecamera", diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 3e78a333..79ed6eec 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -41,7 +41,8 @@ "icon": "Ícone para esta câmera (detectado automaticamente pela entidade)", "image": { "editor_label": "", - "refresh_seconds": "" + "refresh_seconds": "", + "url": "" }, "id": "ID exclusivo para esta câmera nesse cartão", "live_provider": "Provedor de visualização ao vivo para esta câmera", diff --git a/src/types.ts b/src/types.ts index 8e3ac361..9be25cf7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -392,6 +392,18 @@ const customSchema = z }) .passthrough(); +/** + * Image config base options. Image config schema base is used both for the + * `image` live provider and the `image` card view. + */ +const imageBaseConfigDefault = { + refresh_seconds: 1, +}; +const imageBaseConfigSchema = z.object({ + url: z.string().optional(), + refresh_seconds: z.number().min(0).default(imageBaseConfigDefault.refresh_seconds), +}); + /** * Live provider options */ @@ -402,9 +414,7 @@ const go2rtcConfigSchema = z.object({ }); export type Go2rtcConfig = z.infer; -const liveImageConfigSchema = z.object({ - refresh_seconds: z.number().min(0).default(1), -}); +const liveImageConfigSchema = imageBaseConfigSchema; export type LiveImageConfig = z.infer; const webrtcCardConfigSchema = z @@ -415,26 +425,25 @@ const webrtcCardConfigSchema = z .passthrough(); export type WebRTCCardConfig = z.infer; -const jsmpegConfigSchema = z - .object({ - options: z - .object({ - // https://github.com/phoboslab/jsmpeg#usage - audio: z.boolean().optional(), - video: z.boolean().optional(), - pauseWhenHidden: z.boolean().optional(), - disableGl: z.boolean().optional(), - disableWebAssembly: z.boolean().optional(), - preserveDrawingBuffer: z.boolean().optional(), - progressive: z.boolean().optional(), - throttled: z.boolean().optional(), - chunkSize: z.number().optional(), - maxAudioLag: z.number().optional(), - videoBufferSize: z.number().optional(), - audioBufferSize: z.number().optional(), - }) - .optional(), - }); +const jsmpegConfigSchema = z.object({ + options: z + .object({ + // https://github.com/phoboslab/jsmpeg#usage + audio: z.boolean().optional(), + video: z.boolean().optional(), + pauseWhenHidden: z.boolean().optional(), + disableGl: z.boolean().optional(), + disableWebAssembly: z.boolean().optional(), + preserveDrawingBuffer: z.boolean().optional(), + progressive: z.boolean().optional(), + throttled: z.boolean().optional(), + chunkSize: z.number().optional(), + maxAudioLag: z.number().optional(), + videoBufferSize: z.number().optional(), + audioBufferSize: z.number().optional(), + }) + .optional(), +}); export type JSMPEGConfig = z.infer; /** @@ -718,13 +727,11 @@ const viewConfigSchema = z const IMAGE_MODES = ['screensaver', 'camera', 'url'] as const; const imageConfigDefault = { mode: 'url' as const, - refresh_seconds: 0, + ...imageBaseConfigDefault, }; -const imageConfigSchema = z - .object({ +const imageConfigSchema = imageBaseConfigSchema + .extend({ mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode), - url: z.string().optional(), - refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds), layout: mediaLayoutConfigSchema.optional(), }) .merge(actionsSchema)