Move all live provider options into cameras.

This commit is contained in:
Dermot Duffy
2023-02-26 18:45:37 -08:00
parent 29c0279e56
commit bf2952b228
13 changed files with 355 additions and 265 deletions
+3 -6
View File
@@ -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 liveImageStyle from '../../scss/live-image.scss';
import { CameraConfig, FrigateCardMediaPlayer, LiveImageConfig } from '../../types.js';
import { CameraConfig, FrigateCardMediaPlayer } from '../../types.js';
import { getStateObjOrDispatchError } from './live.js';
import '../image.js';
@@ -14,9 +14,6 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
@property({ attribute: false })
public cameraConfig?: CameraConfig;
@property({ attribute: false })
public liveImageConfig?: LiveImageConfig;
@state()
protected _playing = true;
@@ -46,7 +43,7 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
}
protected render(): TemplateResult | void {
if (!this.hass || !this.liveImageConfig) {
if (!this.hass || !this.cameraConfig) {
return;
}
@@ -55,7 +52,7 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
return html` <frigate-card-image
.imageConfig=${{
mode: 'camera' as const,
refresh_seconds: this._playing ? this.liveImageConfig.refresh_seconds : 0,
refresh_seconds: this._playing ? this.cameraConfig.image.refresh_seconds : 0,
// Don't need to pass layout options as FrigateCardLiveProvider has
// already taken care of this for us.
}}
+1 -6
View File
@@ -10,11 +10,9 @@ import {
CardWideConfig,
ExtendedHomeAssistant,
FrigateCardMediaPlayer,
JSMPEGConfig,
} from '../../types.js';
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
import { dispatchErrorMessageEvent } from '../message.js';
import { contentsChanged } from '../../utils/basic.js';
import { CameraEndpoints } from '../../camera-manager/types.js';
import { getEndpointAddressOrDispatchError } from '../../utils/endpoint.js';
@@ -32,9 +30,6 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
@property({ attribute: false })
public cameraEndpoints?: CameraEndpoints;
@property({ attribute: false, hasChanged: contentsChanged })
public jsmpegConfig?: JSMPEGConfig;
@property({ attribute: false })
public cardWideConfig?: CardWideConfig;
@@ -99,7 +94,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
videoBufferSize: 1024 * 1024 * 4,
// Override with user-specified options.
...this.jsmpegConfig?.options,
...this.cameraConfig?.jsmpeg?.options,
// Don't allow the player to internally reconnect, as it may re-use a
// URL with a (newly) invalid signature, e.g. during a Home Assistant
+2 -18
View File
@@ -9,9 +9,7 @@ import {
CardWideConfig,
FrigateCardError,
FrigateCardMediaPlayer,
WebRTCCardConfig,
} from '../../types.js';
import { contentsChanged } from '../../utils/basic.js';
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
import { renderTask } from '../../utils/task.js';
@@ -27,9 +25,6 @@ export class FrigateCardLiveWebRTCCard
extends LitElement
implements FrigateCardMediaPlayer
{
@property({ attribute: false, hasChanged: contentsChanged })
public webRTCConfig?: WebRTCCardConfig;
@property({ attribute: false })
public cameraConfig?: CameraConfig;
@@ -95,23 +90,12 @@ export class FrigateCardLiveWebRTCCard
protected _createWebRTC(): HTMLElement | null {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const webrtcElement = this._webrtcTask.value;
if (webrtcElement && this.hass) {
if (webrtcElement && this.hass && this.cameraConfig?.webrtc_card) {
const webrtc = new webrtcElement() as HTMLElement & {
hass: HomeAssistant;
setConfig: (config: Record<string, unknown>) => void;
};
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_card?.url;
}
if (!config.entity) {
config.entity = this.cameraConfig?.webrtc_card?.entity;
}
webrtc.setConfig(config);
webrtc.setConfig(this.cameraConfig.webrtc_card);
webrtc.hass = this.hass;
return webrtc;
}
-3
View File
@@ -861,7 +861,6 @@ export class FrigateCardLiveProvider
${ref(this._providerRef)}
.hass=${this.hass}
.cameraConfig=${this.cameraConfig}
.liveImageConfig=${this.liveConfig.image}
@frigate-card:media:loaded=${() => {
if (provider === 'image') {
// Only count the media has loaded if the required provider is
@@ -898,7 +897,6 @@ export class FrigateCardLiveProvider
class=${classMap(providerClasses)}
.hass=${this.hass}
.cameraConfig=${this.cameraConfig}
.webRTCConfig=${this.liveConfig.webrtc_card}
.cardWideConfig=${this.cardWideConfig}
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
>
@@ -910,7 +908,6 @@ export class FrigateCardLiveProvider
.hass=${this.hass}
.cameraConfig=${this.cameraConfig}
.cameraEndpoints=${this.cameraEndpoints}
.jsmpegConfig=${this.liveConfig.jsmpeg}
.cardWideConfig=${this.cardWideConfig}
@frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)}
>
+76 -2
View File
@@ -13,7 +13,6 @@ import {
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
CONF_LIVE_LAZY_UNLOAD,
CONF_LIVE_PRELOAD,
CONF_LIVE_WEBRTC_CARD,
CONF_MEDIA_GALLERY,
CONF_MEDIA_VIEWER,
CONF_MENU,
@@ -617,6 +616,80 @@ const transformFrigateUIAction = (data: unknown): boolean => {
return false;
};
/**
* Move live provider options exclusively into camera configs.
* @returns An upgrade function.
*/
const upgradeCameraOptionsFromLiveToMultipleCameras = (): ((
obj: RawFrigateCardConfig,
) => boolean) => {
return function (obj: RawFrigateCardConfig): boolean {
const cameras = getConfigValue(obj, CONF_CAMERAS) as
| RawFrigateCardConfigArray
| undefined;
if (cameras === undefined) {
return false;
}
const webrtcCardConfig = getConfigValue(obj, 'live.webrtc_card') as
| RawFrigateCardConfigArray
| undefined;
const imageConfig = getConfigValue(obj, 'live.image') as
| RawFrigateCardConfigArray
| undefined;
const jsmpegConfig = getConfigValue(obj, 'live.jsmpeg') as
| RawFrigateCardConfigArray
| undefined;
if (!webrtcCardConfig && !imageConfig && !jsmpegConfig) {
return false;
}
if (webrtcCardConfig) {
cameras.forEach((camera) => {
if (
camera.live_provider === 'webrtc_card' &&
(camera.webrtc_card === undefined || typeof camera.webrtc_card === 'object')
) {
camera.webrtc_card = { ...webrtcCardConfig, ...camera.webrtc_card };
}
});
}
if (imageConfig) {
cameras.forEach((camera) => {
if (
camera.live_provider === 'image' &&
(camera.image === undefined || typeof camera.image === 'object')
) {
camera.image = { ...imageConfig, ...camera.image };
}
});
}
if (jsmpegConfig) {
cameras.forEach((camera) => {
if (
camera.live_provider === 'jsmpeg' &&
(camera.jsmpeg === undefined || typeof camera.jsmpeg === 'object')
) {
camera.jsmpeg = { ...jsmpegConfig, ...camera.jsmpeg };
}
});
}
setConfigValue(obj, CONF_CAMERAS, cameras);
deleteConfigValue(obj, 'live.webrtc_card');
deleteConfigValue(obj, 'live.image');
deleteConfigValue(obj, 'live.jsmpeg');
// Note: This upgrade is imperfect. There could be override conditions being
// set that this upgrade cannot understand, e.g. if in fullscreen mode then
// refresh a live image more frequently. Such functionality is not possible
// after this change, since camera configs cannot be overrided.
return true;
};
};
const UPGRADES = [
// v1.2.1 -> v2.0.0
upgradeMoveTo('frigate_url', 'frigate.url'),
@@ -656,7 +729,7 @@ const UPGRADES = [
),
),
upgradeArrayValue(CONF_CAMERAS, upgradeMoveTo('webrtc', 'webrtc_card')),
upgradeMoveToWithOverrides('live.webrtc', CONF_LIVE_WEBRTC_CARD),
upgradeMoveToWithOverrides('live.webrtc', 'live.webrtc_card'),
upgradeMoveToWithOverrides('image.src', CONF_IMAGE_URL),
// v3.0.0 -> v4.0.0-rc.1
@@ -741,4 +814,5 @@ const UPGRADES = [
val === 'frigate-jsmpeg' ? 'jsmpeg' : val,
),
),
upgradeCameraOptionsFromLiveToMultipleCameras(),
];
+4 -4
View File
@@ -13,9 +13,12 @@ export const CONF_CAMERAS_ARRAY_FRIGATE_LABEL =
export const CONF_CAMERAS_ARRAY_FRIGATE_URL = `${CONF_CAMERAS}.#.frigate.url` as const;
export const CONF_CAMERAS_ARRAY_FRIGATE_ZONE = `${CONF_CAMERAS}.#.frigate.zone` as const;
export const CONF_CAMERAS_ARRAY_GO2RTC_MODES = `${CONF_CAMERAS}.#.go2rtc.modes` as const;
export const CONF_CAMERAS_ARRAY_GO2RTC_STREAM = `${CONF_CAMERAS}.#.go2rtc.stream` as const;
export const CONF_CAMERAS_ARRAY_GO2RTC_STREAM =
`${CONF_CAMERAS}.#.go2rtc.stream` as const;
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_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 =
@@ -147,8 +150,6 @@ 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;
@@ -159,7 +160,6 @@ export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const;
export const CONF_LIVE_TRANSITION_EFFECT = `${CONF_LIVE}.transition_effect` as const;
export const CONF_LIVE_SHOW_IMAGE_DURING_LOAD =
`${CONF_LIVE}.show_image_during_load` as const;
export const CONF_LIVE_WEBRTC_CARD = `${CONF_LIVE}.webrtc_card` as const;
const CONF_IMAGE = 'image' as const;
export const CONF_IMAGE_LAYOUT_FIT = `${CONF_IMAGE}.layout.fit` as const;
+103 -81
View File
@@ -23,6 +23,7 @@ import {
CONF_CAMERAS_ARRAY_FRIGATE_ZONE,
CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
CONF_CAMERAS_ARRAY_TITLE,
CONF_CAMERAS_ARRAY_TRIGGERS_ENTITIES,
@@ -63,7 +64,6 @@ 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,
@@ -160,15 +160,17 @@ const MENU_CAMERAS = 'cameras';
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
const MENU_CAMERAS_GO2RTC = 'cameras.go2rtc';
const MENU_CAMERAS_IMAGE = 'cameras.image';
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
const MENU_CAMERAS_WEBRTC = 'cameras.webrtc';
const MENU_CAMERAS_WEBRTC_CARD = 'cameras.webrtc_card';
const MENU_CAMERAS_LIVE_PROVIDER = 'cameras.live_provider';
const MENU_CAMERAS_ENGINE = 'cameras.engine';
const MENU_IMAGE_LAYOUT = 'image.layout';
const MENU_LIVE_CONTROLS = 'live.controls';
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_GALLERY_CONTROLS_THUMBNAILS = 'media_gallery.controls.thumbnails';
const MENU_MEDIA_GALLERY_CONTROLS_FILTER = 'media_gallery.controls.filter';
@@ -1157,12 +1159,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
* @returns A rendered template.
*/
protected _renderTitleControls(
domain: string,
menuDomain: string,
configPathMode: string,
configPathDurationSeconds: string,
): TemplateResult | void {
return this._putInSubmenu(
domain,
menuDomain,
true,
'config.common.controls.title.editor_label',
{ name: 'mdi:subtitles' },
@@ -1344,40 +1346,94 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
getArrayConfigPath(CONF_CAMERAS_ARRAY_ID, cameraIndex),
)}
${this._renderSwitch(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_HIDE,
cameraIndex,
),
getArrayConfigPath(CONF_CAMERAS_ARRAY_HIDE, cameraIndex),
this._defaults.cameras.hide,
)}
${this._putInSubmenu(
MENU_CAMERAS_FRIGATE,
cameraIndex,
'config.cameras.frigate.editor_label',
{ path: FRIGATE_ICON_SVG_PATH },
html`
MENU_CAMERAS_ENGINE,
true,
'config.cameras.engines.editor_label',
{ name: 'mdi:engine' },
html`${this._putInSubmenu(
MENU_CAMERAS_FRIGATE,
cameraIndex,
'config.cameras.frigate.editor_label',
{ path: FRIGATE_ICON_SVG_PATH },
html`
${this._renderStringInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
cameraIndex,
),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_URL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_LABEL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_ZONE, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
cameraIndex,
),
)}
`,
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_LIVE_PROVIDER,
true,
'config.cameras.live_provider_options.editor_label',
{ name: 'mdi:cctv' },
html` ${this._putInSubmenu(
MENU_CAMERAS_GO2RTC,
cameraIndex,
'config.cameras.go2rtc.editor_label',
{ name: 'mdi:alpha-g-circle' },
html`${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_GO2RTC_MODES, cameraIndex),
this._go2rtcModes,
{
multiple: true,
label: localize('config.cameras.go2rtc.modes.editor_label'),
},
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_GO2RTC_STREAM, cameraIndex),
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_IMAGE,
true,
'config.cameras.image.editor_label',
{ name: 'mdi:image' },
html` ${this._renderNumberInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
cameraIndex,
),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_URL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_LABEL, cameraIndex),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_FRIGATE_ZONE, cameraIndex),
)}
${this._renderStringInput(
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_WEBRTC_CARD,
cameraIndex,
'config.cameras.webrtc_card.editor_label',
{ name: 'mdi:webrtc' },
html`${this._renderEntitySelector(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY,
cameraIndex,
),
'camera',
)}
`,
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)}`,
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_DEPENDENCIES,
@@ -1423,36 +1479,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
},
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_GO2RTC,
cameraIndex,
'config.cameras.go2rtc.editor_label',
{ name: 'mdi:alpha-g-circle' },
html`${this._renderOptionSelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_GO2RTC_MODES, cameraIndex),
this._go2rtcModes,
{
multiple: true,
label: localize('config.cameras.go2rtc.modes.editor_label')
}
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_GO2RTC_STREAM, cameraIndex),
)}`,
)}
${this._putInSubmenu(
MENU_CAMERAS_WEBRTC,
cameraIndex,
'config.cameras.webrtc_card.editor_label',
{ name: 'mdi:webrtc' },
html`${this._renderEntitySelector(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_ENTITY, cameraIndex),
'camera',
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, cameraIndex),
)}`,
)}
</div>`
: ``}
</div>
@@ -1467,20 +1493,23 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
*/
protected _renderStringInput(
configPath: string,
type?:
| 'number'
| 'text'
| 'search'
| 'tel'
| 'url'
| 'email'
| 'password'
| 'date'
| 'month'
| 'week'
| 'time'
| 'datetime-local'
| 'color',
params?: {
label?: string;
type?:
| 'number'
| 'text'
| 'search'
| 'tel'
| 'url'
| 'email'
| 'password'
| 'date'
| 'month'
| 'week'
| 'time'
| 'datetime-local'
| 'color';
},
): TemplateResult | void {
if (!this._config) {
return;
@@ -1489,8 +1518,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
return html`
<ha-selector
.hass=${this.hass}
.selector=${{ text: { type: type || 'text' } }}
.label=${this._getLabel(configPath)}
.selector=${{ text: { type: params?.type || 'text' } }}
.label=${params?.label ?? this._getLabel(configPath)}
.value=${getConfigValue(this._config, configPath, '')}
.required=${false}
@value-changed=${(ev) => this._valueChangedHandler(configPath, ev)}
@@ -1711,13 +1740,6 @@ 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-outline' },
html` ${this._renderNumberInput(CONF_LIVE_IMAGE_REFRESH_SECONDS)} `,
)}
</div>
`
: ''}
+10 -4
View File
@@ -15,6 +15,9 @@
"cameras": "Show events for specific cameras with this camera",
"editor_label": "Dependency Options"
},
"engines": {
"editor_label": "Camera engine options"
},
"frigate": {
"camera_name": "Frigate camera name (Autodetected from entity)",
"client_id": "Frigate client id (For >1 Frigate server)",
@@ -36,8 +39,15 @@
},
"hide": "Hide camera from UI",
"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)"
},
"id": "Unique id for this camera in this card",
"live_provider": "Live view provider for this camera",
"live_provider_options": {
"editor_label": "Live provider options"
},
"live_providers": {
"auto": "Automatic",
"jsmpeg": "JSMpeg",
@@ -184,10 +194,6 @@
"editor_label": "Live Controls"
},
"draggable": "Live cameras view can be dragged/swiped",
"image": {
"editor_label": "Image Live Provider 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",
+10
View File
@@ -15,6 +15,9 @@
"cameras": "Mostra eventi per telecamere specifiche con questa telecamera",
"editor_label": "Opzioni di dipendenza"
},
"engines": {
"editor_label": ""
},
"frigate": {
"camera_name": "Nome della telecamera frigate (autodificato dall'entità)",
"client_id": "ID client Frigate (per > 1 Frigate server)",
@@ -36,8 +39,15 @@
},
"hide": "",
"icon": "Icona per questa telecamera (Autoidentificato dall'entità)",
"image": {
"editor_label": "",
"refresh_seconds": ""
},
"id": "ID univoco per questa telecamera in questa carta",
"live_provider": "Provider di visualizzazione dal vivo per questa telecamera",
"live_provider_options": {
"editor_label": ""
},
"live_providers": {
"auto": "Automatica",
"jsmpeg": "JSMpeg",
+10
View File
@@ -15,6 +15,9 @@
"cameras": "Mostrar eventos para câmeras específicas nesta câmera",
"editor_label": "Opções de dependência"
},
"engines": {
"editor_label": ""
},
"frigate": {
"camera_name": "Nome da câmera do Frigate (detectado automaticamente pela entidade)",
"client_id": "ID do cliente do Frigate (para >1 servidor Frigate)",
@@ -36,8 +39,15 @@
},
"hide": "",
"icon": "Ícone para esta câmera (detectado automaticamente pela entidade)",
"image": {
"editor_label": "",
"refresh_seconds": ""
},
"id": "ID exclusivo para esta câmera nesse cartão",
"live_provider": "Provedor de visualização ao vivo para esta câmera",
"live_provider_options": {
"editor_label": ""
},
"live_providers": {
"auto": "Automatico",
"jsmpeg": "JSMpeg",
+5 -5
View File
@@ -7,6 +7,7 @@ import {
import { getArrayConfigPath, getConfigValue, setConfigValue } from './config-mgmt.js';
import {
CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY,
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
CONF_MEDIA_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
CONF_MEDIA_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_MEDIA_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
@@ -18,7 +19,6 @@ import {
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
CONF_LIVE_CONTROLS_TITLE_MODE,
CONF_LIVE_DRAGGABLE,
CONF_LIVE_IMAGE_REFRESH_SECONDS,
CONF_LIVE_LAZY_UNLOAD,
CONF_LIVE_SHOW_IMAGE_DURING_LOAD,
CONF_LIVE_TRANSITION_EFFECT,
@@ -116,10 +116,6 @@ const LOW_PROFILE_DEFAULTS = {
[CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL]: false,
[CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS]: false,
// Refresh the live camera image every 10 seconds (same as stock Home
// Assistant Picture Glance).
[CONF_LIVE_IMAGE_REFRESH_SECONDS]: 10,
// Disable all optional performance related features.
[CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR]: false,
@@ -136,6 +132,10 @@ const LOW_PROFILE_DEFAULTS = {
const LOW_PROFILE_CAMERA_DEFAULTS = {
[CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY]: false,
// Refresh the live camera image every 10 seconds (same as stock Home
// Assistant Picture Glance).
[CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS]: 10,
};
/**
+69 -67
View File
@@ -392,6 +392,51 @@ const customSchema = z
})
.passthrough();
/**
* Live provider options
*/
const go2rtcConfigSchema = z.object({
modes: z.enum(['webrtc', 'mse', 'mp4', 'mjpeg']).array().optional(),
stream: z.string().optional(),
});
export type Go2rtcConfig = z.infer<typeof go2rtcConfigSchema>;
const liveImageConfigSchema = z.object({
refresh_seconds: z.number().min(0).default(1),
});
export type LiveImageConfig = z.infer<typeof liveImageConfigSchema>;
const webrtcCardConfigSchema = z
.object({
entity: z.string().optional(),
url: z.string().optional(),
})
.passthrough();
export type WebRTCCardConfig = z.infer<typeof webrtcCardConfigSchema>;
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<typeof jsmpegConfigSchema>;
/**
* Camera configuration section
*/
@@ -405,6 +450,9 @@ const cameraConfigDefault = {
all_cameras: false,
cameras: [],
},
image: {
refresh_seconds: 1,
},
hide: false,
triggers: {
motion: false,
@@ -412,14 +460,10 @@ const cameraConfigDefault = {
entities: [],
},
};
const webrtcCardCameraConfigSchema = z.object({
entity: z.string().optional(),
url: z.string().optional(),
});
const cameraConfigSchema = z
.object({
camera_entity: z.string().optional(),
live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider),
// Used for presentation in the UI (autodetected from the entity if
// specified).
@@ -433,29 +477,6 @@ const cameraConfigSchema = z
// this card.
id: z.string().optional(),
engine: z.enum(ENGINES).default('auto'),
frigate: z
.object({
// No URL validation to allow relative URLs within HA (e.g. Frigate addon).
url: z.string().optional(),
client_id: z.string().default(cameraConfigDefault.frigate.client_id),
camera_name: z.string().optional(),
label: z.string().optional(),
zone: z.string().optional(),
})
.default(cameraConfigDefault.frigate),
go2rtc: z
.object({
modes: z.enum(['webrtc', 'mse', 'mp4', 'mjpeg']).array().optional(),
stream: z.string().optional(),
})
.optional(),
// Camera identifiers for WebRTC.
webrtc_card: webrtcCardCameraConfigSchema.optional(),
dependencies: z
.object({
all_cameras: z.boolean().default(cameraConfigDefault.dependencies.all_cameras),
@@ -470,6 +491,26 @@ const cameraConfigSchema = z
entities: z.string().array().default(cameraConfigDefault.triggers.entities),
})
.default(cameraConfigDefault.triggers),
// Engine options.
engine: z.enum(ENGINES).default('auto'),
frigate: z
.object({
// No URL validation to allow relative URLs within HA (e.g. Frigate addon).
url: z.string().optional(),
client_id: z.string().default(cameraConfigDefault.frigate.client_id),
camera_name: z.string().optional(),
label: z.string().optional(),
zone: z.string().optional(),
})
.default(cameraConfigDefault.frigate),
// Live provider options.
live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider),
go2rtc: go2rtcConfigSchema.optional(),
image: liveImageConfigSchema.default(cameraConfigDefault.image),
jsmpeg: jsmpegConfigSchema.optional(),
webrtc_card: webrtcCardConfigSchema.optional(),
})
.default(cameraConfigDefault);
export type CameraConfig = z.infer<typeof cameraConfigSchema>;
@@ -795,10 +836,6 @@ export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
* Live view configuration section.
*/
const liveImageConfigDefault = {
refresh_seconds: 1,
};
const liveThumbnailControlsDefaults = {
...thumbnailControlsDefaults,
media: 'all' as const,
@@ -815,7 +852,6 @@ const liveConfigDefault = {
draggable: true,
transition_effect: 'slide' as const,
show_image_during_load: true,
image: liveImageConfigDefault,
controls: {
next_previous: {
size: 48,
@@ -836,42 +872,8 @@ const livethumbnailsControlSchema = thumbnailsControlSchema.extend({
.default(liveConfigDefault.controls.thumbnails.media),
});
const liveImageConfigSchema = z.object({
refresh_seconds: z.number().min(0).default(liveConfigDefault.image.refresh_seconds),
});
export type LiveImageConfig = z.infer<typeof liveImageConfigSchema>;
const webrtcCardConfigSchema = webrtcCardCameraConfigSchema.passthrough().optional();
export type WebRTCCardConfig = z.infer<typeof webrtcCardConfigSchema>;
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(),
})
.optional();
export type JSMPEGConfig = z.infer<typeof jsmpegConfigSchema>;
const liveOverridableConfigSchema = z
.object({
image: liveImageConfigSchema.default(liveConfigDefault.image),
jsmpeg: jsmpegConfigSchema,
webrtc_card: webrtcCardConfigSchema,
controls: z
.object({
next_previous: nextPreviousControlConfigSchema