diff --git a/src/card.ts b/src/card.ts index d8da19c4..4a9ec670 100644 --- a/src/card.ts +++ b/src/card.ts @@ -955,9 +955,9 @@ export class FrigateCard extends LitElement { throw new Error(localize('error.invalid_configuration')); } - const configUpgradeable = isConfigUpgradeable(inputConfig); const parseResult = frigateCardConfigSchema.safeParse(inputConfig); if (!parseResult.success) { + const configUpgradeable = isConfigUpgradeable(inputConfig); const hint = this._getParseErrorPaths(parseResult.error); let upgradeMessage = ''; if (configUpgradeable && getLovelace().mode !== 'yaml') { diff --git a/src/components/live/live-image.ts b/src/components/live/live-image.ts index c9a1eb3d..2699b548 100644 --- a/src/components/live/live-image.ts +++ b/src/components/live/live-image.ts @@ -2,7 +2,7 @@ import { HomeAssistant } from 'custom-card-helpers'; import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import liveFrigateStyle from '../../scss/live-frigate.scss'; -import { CameraConfig } from '../../types.js'; +import { CameraConfig, LiveImageConfig } from '../../types.js'; import { getStateObjOrDispatchError } from './live.js'; import '../image.js'; @@ -14,6 +14,9 @@ export class FrigateCardLiveImage extends LitElement { @property({ attribute: false }) public cameraConfig?: CameraConfig; + @property({ attribute: false }) + public liveImageConfig?: LiveImageConfig; + @state() protected _playing = true; @@ -58,7 +61,7 @@ export class FrigateCardLiveImage extends LitElement { * @returns A rendered template. */ protected render(): TemplateResult | void { - if (!this.hass) { + if (!this.hass || !this.liveImageConfig) { return; } @@ -67,7 +70,7 @@ export class FrigateCardLiveImage extends LitElement { return html` { if (provider === 'image') { // Only count the media has loaded if the required provider is diff --git a/src/const.ts b/src/const.ts index 7f4d9e4d..5468ea2e 100644 --- a/src/const.ts +++ b/src/const.ts @@ -139,6 +139,8 @@ export const CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS = export const CONF_LIVE_CONTROLS_TITLE_MODE = `${CONF_LIVE}.controls.title.mode` as const; export const CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS = `${CONF_LIVE}.controls.title.duration_seconds` as const; +export const CONF_LIVE_IMAGE_REFRESH_SECONDS = + `${CONF_LIVE}.image.refresh_seconds` as const; export const CONF_LIVE_LAYOUT_FIT = `${CONF_LIVE}.layout.fit` as const; export const CONF_LIVE_LAYOUT_POSITION_X = `${CONF_LIVE}.layout.position.x` as const; export const CONF_LIVE_LAYOUT_POSITION_Y = `${CONF_LIVE}.layout.position.y` as const; diff --git a/src/editor.ts b/src/editor.ts index cf6a5a39..b4eb6a38 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -62,6 +62,7 @@ import { CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS, CONF_LIVE_CONTROLS_TITLE_MODE, CONF_LIVE_DRAGGABLE, + CONF_LIVE_IMAGE_REFRESH_SECONDS, CONF_LIVE_LAYOUT_FIT, CONF_LIVE_LAYOUT_POSITION_X, CONF_LIVE_LAYOUT_POSITION_Y, @@ -156,6 +157,7 @@ const MENU_LIVE_CONTROLS_NEXT_PREVIOUS = 'live.controls.next_previous'; const MENU_LIVE_CONTROLS_THUMBNAILS = 'live.controls.thumbnails'; const MENU_LIVE_CONTROLS_TIMELINE = 'live.controls.timeline'; const MENU_LIVE_CONTROLS_TITLE = 'live.controls.title'; +const MENU_LIVE_IMAGE = 'live.image'; const MENU_LIVE_LAYOUT = 'live.layout'; const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls'; const MENU_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS = 'media_viewer.controls.next_previous'; @@ -1110,7 +1112,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'image', label: localize('config.cameras.live_providers.image'), - }, + }, { value: 'frigate-jsmpeg', label: localize('config.cameras.live_providers.frigate-jsmpeg'), @@ -1594,6 +1596,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_LAYOUT_POSITION_X, CONF_LIVE_LAYOUT_POSITION_Y, )} + ${this._putInSubmenu( + MENU_LIVE_IMAGE, + true, + 'config.live.image.editor_label', + { name: 'mdi:image-sync' }, + html` ${this._renderNumberInput(CONF_LIVE_IMAGE_REFRESH_SECONDS)} `, + )} ` : ''} diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 7f9c3dd4..9ac4fdb8 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -166,6 +166,10 @@ "editor_label": "Live Controls" }, "draggable": "Live cameras view can be dragged/swiped", + "image": { + "editor_label": "Live Image Options", + "refresh_seconds": "Number of seconds after which to refresh live image (0=never)" + }, "layout": "Live Layout", "lazy_load": "Live cameras are lazily loaded", "lazy_unload": "Live cameras are lazily unloaded", diff --git a/src/types.ts b/src/types.ts index dd5f7d59..aaf68b98 100644 --- a/src/types.ts +++ b/src/types.ts @@ -739,6 +739,11 @@ export type TitleControlConfig = z.infer; /** * Live view configuration section. */ + +const liveImageConfigDefault = { + refresh_seconds: 1, +}; + const liveConfigDefault = { auto_play: 'all' as const, auto_pause: 'never' as const, @@ -750,6 +755,7 @@ const liveConfigDefault = { draggable: true, transition_effect: 'slide' as const, show_image_during_load: true, + image: liveImageConfigDefault, controls: { next_previous: { size: 48, @@ -771,6 +777,11 @@ const liveConfigDefault = { }, }; +const liveImageConfigSchema = z.object({ + refresh_seconds: z.number().min(0).default(liveConfigDefault.image.refresh_seconds), +}); +export type LiveImageConfig = z.infer; + const webrtcCardConfigSchema = webrtcCardCameraConfigSchema.passthrough().optional(); export type WebRTCCardConfig = z.infer; @@ -799,8 +810,9 @@ export type JSMPEGConfig = z.infer; const liveOverridableConfigSchema = z .object({ - webrtc_card: webrtcCardConfigSchema, + image: liveImageConfigSchema.default(liveConfigDefault.image), jsmpeg: jsmpegConfigSchema, + webrtc_card: webrtcCardConfigSchema, controls: z .object({ next_previous: nextPreviousControlConfigSchema