Allow the user to specify image URL for image live provider.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -51,8 +51,9 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
|
||||
|
||||
return html` <frigate-card-image
|
||||
.imageConfig=${{
|
||||
mode: 'camera' as const,
|
||||
mode: this.cameraConfig.image.url ? 'url' as const : 'camera' as const,
|
||||
refresh_seconds: this._playing ? this.cameraConfig.image.refresh_seconds : 0,
|
||||
url: this.cameraConfig.image.url,
|
||||
// Don't need to pass layout options as FrigateCardLiveProvider has
|
||||
// already taken care of this for us.
|
||||
}}
|
||||
|
||||
@@ -723,12 +723,14 @@ export class FrigateCardLiveProvider
|
||||
// If the play call fails, and the media is not already muted, mute it first
|
||||
// and then try again. This works around some browsers that prevent
|
||||
// auto-play unless the video is muted.
|
||||
this._providerRef.value?.play().catch((ev) => {
|
||||
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 {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
+12
-6
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+35
-28
@@ -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<typeof go2rtcConfigSchema>;
|
||||
|
||||
const liveImageConfigSchema = z.object({
|
||||
refresh_seconds: z.number().min(0).default(1),
|
||||
});
|
||||
const liveImageConfigSchema = imageBaseConfigSchema;
|
||||
export type LiveImageConfig = z.infer<typeof liveImageConfigSchema>;
|
||||
|
||||
const webrtcCardConfigSchema = z
|
||||
@@ -415,26 +425,25 @@ const webrtcCardConfigSchema = z
|
||||
.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(),
|
||||
});
|
||||
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>;
|
||||
|
||||
/**
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user