Merge pull request #544 from dermotduffy/auto-pause

Implement `auto_play`, `auto_pause`, `auto_mute` and `auto_unmute`
This commit is contained in:
Dermot Duffy
2022-05-08 08:59:39 -07:00
committed by GitHub
13 changed files with 325 additions and 75 deletions
+9 -4
View File
@@ -223,9 +223,12 @@ live:
| Option | Default | Overridable | Description |
| - | - | - | - |
| `preload` | `false` | :heavy_multiplication_x: | Whether or not to preload the live view. Preloading causes the live view to render in the background regardless of what view is actually shown, so it's instantly available when requested. This consumes additional network/CPU resources continually. |
| `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute live cameras. Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead. |
| `auto_play` | `all` | :heavy_multiplication_x: | Whether to automatically play live camera feeds. `never` will never automatically play, `selected` will automatically play when a camera is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case). Some live providers (e.g. `webrtc-card`, `jsmpeg`) do not support the prevention of automatic play on initial load, but should still respect the value of this flag on play-after-pause.|
| `auto_pause` | `never` | :heavy_multiplication_x: | Whether to automatically pause live camera feeds. `never` will never automatically pause, `unselected` will automatically pause when a camera is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case). **Caution**: Some live providers (e.g. `jsmpeg`) may not offer human-accessible means to resume play if it is paused, unless the `auto_play` option (above) is used.|
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute live camera feeds. `never` will never automatically mute, `unselected` will automatically mute when a camera is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case).|
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute live camera feeds. `never` will never automatically unmute, `selected` will automatically unmute when a camera is unselected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `all` on any opportunity to automatically unmute (i.e. either case).|
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load cameras in the camera carousel. Setting this will `false` will cause all cameras to load simultaneously when the `live` carousel is opened (or cause all cameras to load continually if both `lazy_load` and `preload` are `true`). This will result in a smoother carousel experience at a cost of (potentially) a substantial amount of continually streamed data. |
| `lazy_unload` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab is hidden or `all` on any opportunity to lazily unload (i.e. either case). This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. This option has no effect if `lazy_load` is false. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
| `lazy_unload` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab becomes hidden or `all` on any opportunity to lazily unload (i.e. either case). This will cause a reloading delay on revisiting that camera in the carousel but will save the streaming network resources that are otherwise consumed. This option has no effect if `lazy_load` is false. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. |
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between live cameras. Accepted values: `slide` or `none`. |
| `actions` | | :white_check_mark: | Actions to use for the `live` view. See [actions](#actions) below.|
@@ -325,8 +328,10 @@ event_viewer:
| Option | Default | Overridable | Description |
| - | - | - | - |
| `auto_play` | `true` | :heavy_multiplication_x: | Whether or not to autoplay clips.|
| `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute clips. Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead. |
| `auto_play` | `all` | :heavy_multiplication_x: | Whether to automatically play events. `never` will never automatically play, `selected` will automatically play when an event is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case).|
| `auto_pause` | `all` | :heavy_multiplication_x: | Whether to automatically pause events. `never` will never automatically pause, `unselected` will automatically pause when an event is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case).|
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute events. `never` will never automatically mute, `unselected` will automatically mute when an event is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case).|
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute events. `never` will never automatically unmute, `selected` will automatically unmute when an event is selected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `all` on any opportunity to automatically unmute (i.e. either case). Note that some browsers will not allow automated unmute until the user has interacted with the page in some way -- if the user has not then the browser may pause the media instead.|
| `lazy_load` | `true` | :heavy_multiplication_x: | Whether or not to lazily load media in the event viewer carousel. Setting this will false will fetch all media immediately which may make the carousel experience smoother at a cost of (potentially) a substantial number of simultaneous media fetches on load. |
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the event viewer carousel can be dragged left or right, via touch/swipe and mouse dragging. |
| `transition_effect` | `slide` | :heavy_multiplication_x: | Effect to apply as a transition between event media. Accepted values: `slide` or `none`. |
+4 -1
View File
@@ -9,7 +9,7 @@ import styles from 'rollup-plugin-styles';
import image from '@rollup/plugin-image';
import replace from '@rollup/plugin-replace';
const dev = process.env.ROLLUP_WATCH;
const dev = process.env.ROLLUP_WATCH === 'true' || process.env.ROLLUP_WATCH === '1';
const serveopts = {
contentBase: ['./dist'],
@@ -45,7 +45,10 @@ const plugins = [
exclude: 'node_modules/**',
}),
replace({
preventAssignment: true,
values: {
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'),
}
}),
dev && serve(serveopts),
!dev && terser(),
+76 -12
View File
@@ -1,23 +1,33 @@
import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel';
import { FrigateCardMediaPlayer } from '../../types.js';
import {
AutoMuteCondition,
AutoPauseCondition,
AutoPlayCondition,
AutoUnmuteCondition,
FrigateCardMediaPlayer,
} from '../../types.js';
export type AutoMediaPluginOptionsType = {
playerSelector: string;
autoPlayWhenVisible?: boolean;
autoUnmuteWhenVisible?: boolean;
// Note: Neither play nor unmute will activate on selection. The caller is
// expected to call the `play()` or `unmute()` methods manually when the media
// is actually loaded (and not just when the slide is visible -- the browser
// cannot play media that is not actually loaded yet, e.g. lazy loading).
autoPlayCondition?: AutoPlayCondition;
autoUnmuteCondition?: AutoUnmuteCondition;
autoPauseCondition?: AutoPauseCondition;
autoMuteCondition?: AutoMuteCondition;
};
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {
autoPlayWhenVisible: true,
autoUnmuteWhenVisible: true,
};
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {};
export type AutoMediaPluginType = EmblaPluginType<AutoMediaPluginOptionsType> & {
play: () => void;
pause: () => void;
mute: () => void;
unmute: () => void;
}
};
/**
* An Embla plugin to take automated actions on media (e.g. pause, unmute, etc).
@@ -43,9 +53,19 @@ export function AutoMediaPlugin(
// slide is selected, so only pause (and not play/unmute) based on carousel
// events.
carousel.on('destroy', pause);
if (
options.autoPauseCondition &&
['all', 'unselected'].includes(options.autoPauseCondition)
) {
carousel.on('select', pausePrevious);
}
carousel.on('destroy', mute);
if (
options.autoMuteCondition &&
['all', 'unselected'].includes(options.autoMuteCondition)
) {
carousel.on('select', mutePrevious);
}
document.addEventListener('visibilitychange', visibilityHandler);
}
@@ -55,9 +75,19 @@ export function AutoMediaPlugin(
*/
function destroy(): void {
carousel.off('destroy', pause);
if (
options.autoPauseCondition &&
['all', 'unselected'].includes(options.autoPauseCondition)
) {
carousel.off('select', pausePrevious);
}
carousel.off('destroy', mute);
if (
options.autoMuteCondition &&
['all', 'unselected'].includes(options.autoMuteCondition)
) {
carousel.off('select', mutePrevious);
}
document.removeEventListener('visibilitychange', visibilityHandler);
}
@@ -67,13 +97,29 @@ export function AutoMediaPlugin(
*/
function visibilityHandler(): void {
if (document.visibilityState == 'hidden') {
pause();
mute();
if (
options.autoPauseCondition &&
['all', 'hidden'].includes(options.autoPauseCondition)
) {
pauseAll();
}
if (
options.autoMuteCondition &&
['all', 'hidden'].includes(options.autoMuteCondition)
) {
muteAll();
}
} else if (document.visibilityState == 'visible') {
if (options.autoPlayWhenVisible) {
if (
options.autoPlayCondition &&
['all', 'visible'].includes(options.autoPlayCondition)
) {
play();
}
if (options.autoUnmuteWhenVisible) {
if (
options.autoUnmuteCondition &&
['all', 'visible'].includes(options.autoUnmuteCondition)
) {
unmute();
}
}
@@ -109,6 +155,15 @@ export function AutoMediaPlugin(
getPlayer(slides[carousel.previousScrollSnap()])?.pause();
}
/**
* Pause all slides.
*/
function pauseAll(): void {
for (const slide of slides) {
getPlayer(slide)?.pause();
}
}
/**
* Unmute the current slide.
*/
@@ -130,6 +185,15 @@ export function AutoMediaPlugin(
getPlayer(slides[carousel.previousScrollSnap()])?.mute();
}
/**
* Mute all slides.
*/
function muteAll(): void {
for (const slide of slides) {
getPlayer(slide)?.mute();
}
}
const self: AutoMediaPluginType = {
name: 'AutoMediaPlugin',
options,
+43 -6
View File
@@ -242,13 +242,23 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
| AutoMediaPluginType
| undefined;
if (automedia) {
// If this has changed to preloaded then pause & mute, otherwise play
// and potentially unmute (depending on configuration).
// If this has changed to preloaded (i.e. is now loaded but in the
// background) take the appropriate play/pause/mute/unmute actions.
if (this.preloaded) {
if (
this.liveConfig?.auto_pause &&
['all', 'unselected'].includes(this.liveConfig.auto_pause)
) {
automedia.pause();
}
if (
this.liveConfig?.auto_mute &&
['all', 'unselected'].includes(this.liveConfig.auto_mute)
) {
automedia.mute();
}
} else {
automedia.play();
this._autoPlayHandler();
this._autoUnmuteHandler();
}
}
@@ -299,16 +309,42 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
}),
AutoMediaPlugin({
playerSelector: 'frigate-card-live-provider',
autoUnmuteWhenVisible: !!this.liveConfig?.auto_unmute,
...(this.liveConfig?.auto_play && {
autoPlayCondition: this.liveConfig.auto_play,
}),
...(this.liveConfig?.auto_pause && {
autoPauseCondition: this.liveConfig.auto_pause,
}),
...(this.liveConfig?.auto_mute && {
autoMuteCondition: this.liveConfig.auto_mute,
}),
...(this.liveConfig?.auto_unmute && {
autoUnmuteCondition: this.liveConfig.auto_unmute,
}),
}),
];
}
/**
* Unmute the media on the selected slide.
* Play the media on the loaded slide.
*/
protected _autoPlayHandler(): void {
if (
this.liveConfig?.auto_play &&
['all', 'selected'].includes(this.liveConfig.auto_play)
) {
super._autoPlayHandler();
}
}
/**
* Unmute the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (this.liveConfig?.auto_unmute) {
if (
this.liveConfig?.auto_unmute &&
['all', 'selected'].includes(this.liveConfig.auto_unmute)
) {
super._autoUnmuteHandler();
}
}
@@ -978,6 +1014,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
// The media carousel may automatically pause when the browser tab is
// inactive, JSMPEG does not need to also do so independently.
pauseWhenHidden: false,
autoplay: false,
protocols: [],
audio: false,
videoBufferSize: 1024 * 1024 * 4,
+3
View File
@@ -79,6 +79,9 @@ export class FrigateCardSurround extends LitElement {
...(this.targetView && { view: this.targetView }),
target: parent,
childIndex: null,
// Don't carry over history of this 'empty' view.
previous: null,
})
.dispatchChangeEvent(this);
}
+25 -6
View File
@@ -206,23 +206,32 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}
/**
* Play the media on the selected slide.
* Play the media on the loaded slide.
*/
protected _autoPlayHandler(): void {
if (this.viewerConfig?.auto_play) {
if (
this.viewerConfig?.auto_play &&
['all', 'selected'].includes(this.viewerConfig.auto_play)
) {
super._autoPlayHandler();
}
}
/**
* Unmute the media on the selected slide.
* Unmute the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (this.viewerConfig?.auto_unmute) {
if (
this.viewerConfig?.auto_unmute &&
['all', 'selected'].includes(this.viewerConfig.auto_unmute)
) {
super._autoUnmuteHandler();
}
}
/**
* Destroy the carousel.
*/
protected _destroyCarousel(): void {
super._destroyCarousel();
@@ -280,8 +289,18 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}),
AutoMediaPlugin({
playerSelector: 'frigate-card-ha-hls-player',
autoPlayWhenVisible: !!this.viewerConfig?.auto_play,
autoUnmuteWhenVisible: !!this.viewerConfig?.auto_unmute,
...(this.viewerConfig?.auto_play && {
autoPlayCondition: this.viewerConfig.auto_play,
}),
...(this.viewerConfig?.auto_pause && {
autoPauseCondition: this.viewerConfig.auto_pause,
}),
...(this.viewerConfig?.auto_mute && {
autoMuteCondition: this.viewerConfig.auto_mute,
}),
...(this.viewerConfig?.auto_unmute && {
autoUnmuteCondition: this.viewerConfig.auto_unmute,
}),
}),
];
}
+11
View File
@@ -35,6 +35,8 @@ import {
CONF_VIEW_DEFAULT,
CONF_VIEW_TIMEOUT_SECONDS,
CONF_VIEW_UPDATE_ENTITIES,
CONF_LIVE_AUTO_UNMUTE,
CONF_EVENT_VIEWER_AUTO_UNMUTE,
} from './const';
import {
BUTTON_SIZE_MIN,
@@ -592,4 +594,13 @@ const UPGRADES = [
upgrade(CONF_LIVE_LAZY_UNLOAD, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_LIVE_AUTO_UNMUTE, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_EVENT_VIEWER_AUTO_PLAY, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
upgrade(CONF_EVENT_VIEWER_AUTO_UNMUTE, (val) =>
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
),
];
+5
View File
@@ -44,6 +44,8 @@ export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE =
export const CONF_EVENT_VIEWER = 'event_viewer' as const;
export const CONF_EVENT_VIEWER_AUTO_PLAY = `${CONF_EVENT_VIEWER}.auto_play` as const;
export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` as const;
export const CONF_EVENT_VIEWER_AUTO_MUTE = `${CONF_EVENT_VIEWER}.auto_mute` as const;
export const CONF_EVENT_VIEWER_AUTO_UNMUTE = `${CONF_EVENT_VIEWER}.auto_unmute` as const;
export const CONF_EVENT_VIEWER_DRAGGABLE = `${CONF_EVENT_VIEWER}.draggable` as const;
export const CONF_EVENT_VIEWER_LAZY_LOAD = `${CONF_EVENT_VIEWER}.lazy_load` as const;
@@ -67,6 +69,9 @@ export const CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
`${CONF_EVENT_VIEWER}.controls.title.duration_seconds` as const;
export const CONF_LIVE = 'live' as const;
export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const;
export const CONF_LIVE_AUTO_PAUSE = `${CONF_LIVE}.auto_pause` as const;
export const CONF_LIVE_AUTO_MUTE = `${CONF_LIVE}.auto_mute` as const;
export const CONF_LIVE_AUTO_UNMUTE = `${CONF_LIVE}.auto_unmute` as const;
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE =
`${CONF_LIVE}.controls.next_previous.style` as const;
+56 -12
View File
@@ -34,6 +34,8 @@ import {
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_CONTROLS,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS,
CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE,
CONF_EVENT_VIEWER_AUTO_MUTE,
CONF_EVENT_VIEWER_AUTO_PAUSE,
CONF_EVENT_VIEWER_AUTO_PLAY,
CONF_EVENT_VIEWER_AUTO_UNMUTE,
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
@@ -50,6 +52,9 @@ import {
CONF_IMAGE_MODE,
CONF_IMAGE_REFRESH_SECONDS,
CONF_IMAGE_URL,
CONF_LIVE_AUTO_MUTE,
CONF_LIVE_AUTO_PAUSE,
CONF_LIVE_AUTO_PLAY,
CONF_LIVE_AUTO_UNMUTE,
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
@@ -349,12 +354,29 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'auto', label: localize('config.view.dark_modes.auto') },
];
protected _lazyUnloadConditions: EditorSelectOption[] = [
protected _mediaActionNegativeConditions: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'all', label: localize('config.live.lazy_unload_conditions.all') },
{ value: 'unselected', label: localize('config.live.lazy_unload_conditions.unselected') },
{ value: 'hidden', label: localize('config.live.lazy_unload_conditions.hidden') },
{ value: 'never', label: localize('config.live.lazy_unload_conditions.never') },
{ value: 'all', label: localize('config.common.media_action_conditions.all') },
{
value: 'unselected',
label: localize('config.common.media_action_conditions.unselected'),
},
{ value: 'hidden', label: localize('config.common.media_action_conditions.hidden') },
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
];
protected _mediaActionPositiveConditions: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'all', label: localize('config.common.media_action_conditions.all') },
{
value: 'selected',
label: localize('config.common.media_action_conditions.selected'),
},
{
value: 'visible',
label: localize('config.common.media_action_conditions.visible'),
},
{ value: 'never', label: localize('config.common.media_action_conditions.never') },
];
public setConfig(config: RawFrigateCardConfig): void {
@@ -988,7 +1010,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderMenuButton('timeline')}
${this._renderMenuButton('media_player')}
</div>
</div>
`
: ''}
${this._renderOptionSetHeader('live')}
@@ -1000,9 +1021,24 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
${this._renderOptionSelector(
CONF_LIVE_LAZY_UNLOAD,
this._lazyUnloadConditions,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_PLAY,
this._mediaActionPositiveConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_PAUSE,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_MUTE,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_LIVE_AUTO_UNMUTE,
this._mediaActionPositiveConditions,
)}
${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)}
${this._renderOptionSelector(
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
this._liveNextPreviousControlStyles,
@@ -1065,13 +1101,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('event_viewer')}
${this._expandedMenus[MENU_OPTIONS] === 'event_viewer'
? html` <div class="values">
${this._renderSwitch(
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_PLAY,
defaults.event_viewer.auto_play,
this._mediaActionPositiveConditions,
)}
${this._renderSwitch(
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_PAUSE,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_MUTE,
this._mediaActionNegativeConditions,
)}
${this._renderOptionSelector(
CONF_EVENT_VIEWER_AUTO_UNMUTE,
defaults.event_viewer.auto_unmute,
this._mediaActionPositiveConditions,
)}
${this._renderSwitch(
CONF_EVENT_VIEWER_DRAGGABLE,
+15 -6
View File
@@ -33,6 +33,16 @@
},
"zone": "Frigate zone"
},
"common": {
"media_action_conditions": {
"all": "All opportunities",
"unselected": "On unselection",
"selected": "On selection",
"hidden": "On browser/tab hiding",
"visible": "On browser/tab visibility",
"never": "Never"
}
},
"view": {
"camera_select": "View for newly selected cameras",
"dark_mode": "Dark mode",
@@ -68,6 +78,8 @@
},
"event_viewer": {
"auto_play": "Automatically play media",
"auto_pause": "Automatically pause media",
"auto_mute": "Automatically mute media",
"auto_unmute": "Automatically unmute media",
"draggable": "Event Viewer can be dragged/swiped",
"lazy_load": "Event Viewer media is lazily loaded in carousel",
@@ -117,12 +129,9 @@
"draggable": "Live cameras view can be dragged/swiped",
"lazy_load": "Live cameras are lazily loaded",
"lazy_unload": "Live cameras are lazily unloaded",
"lazy_unload_conditions": {
"all": "All opportunities",
"unselected": "On unselection",
"hidden": "On browser/tab hiding",
"never": "Never"
},
"auto_play": "Automatically play live cameras",
"auto_pause": "Automatically pause live cameras",
"auto_mute": "Automatically mute live cameras",
"auto_unmute": "Automatically unmute live cameras",
"transition_effect": "Live camera transition effect",
"controls": {
+2 -2
View File
@@ -63,14 +63,14 @@ customElements.whenDefined('ha-camera-stream').then(() => {
* Mute the video.
*/
public mute(): void {
this.muted = true;
this._playerRef.value?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this.muted = false;
this._playerRef.value?.unmute();
}
/**
+10 -4
View File
@@ -38,14 +38,21 @@ customElements.whenDefined('ha-hls-player').then(() => {
* Mute the video.
*/
public mute(): void {
this.muted = true;
// The muted property is only for the initial muted state. Must explicitly
// set the muted on the video player to make the change dynamic.
if (this._videoRef.value) {
this._videoRef.value.muted = true;
}
}
/**
* Unmute the video.
*/
public unmute(): void {
this.muted = false;
// See note in mute().
if (this._videoRef.value) {
this._videoRef.value.muted = false;
}
}
// =====================================================================================
@@ -54,8 +61,7 @@ customElements.whenDefined('ha-hls-player').then(() => {
// =====================================================================================
protected render(): TemplateResult {
if (this._error) {
// Use native Frigate card error handling, and attach the entityid to
// clarify which camera the error refers to.
// Use native Frigate card error handling.
return dispatchErrorMessageEvent(this, this._error);
}
return html`
+53 -9
View File
@@ -64,8 +64,24 @@ export const FRIGATE_MENU_PRIORITY_MAX = 100;
const LIVE_PROVIDERS = ['auto', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const;
export type LiveProvider = typeof LIVE_PROVIDERS[number];
const LAZY_UNLOAD_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const;
export type LazyUnloadCondition = typeof LAZY_UNLOAD_CONDITIONS[number];
const MEDIA_ACTION_NEGATIVE_CONDITIONS = [
'all',
'unselected',
'hidden',
'never',
] as const;
export type LazyUnloadCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
export type AutoMuteCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
export type AutoPauseCondition = typeof MEDIA_ACTION_NEGATIVE_CONDITIONS[number];
const MEDIA_ACTION_POSITIVE_CONDITIONS = [
'all',
'selected',
'visible',
'never',
] as const;
export type AutoUnmuteCondition = typeof MEDIA_ACTION_POSITIVE_CONDITIONS[number];
export type AutoPlayCondition = typeof MEDIA_ACTION_POSITIVE_CONDITIONS[number];
export class FrigateCardError extends Error {}
@@ -560,7 +576,10 @@ export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
* Live view configuration section.
*/
const liveConfigDefault = {
auto_unmute: false,
auto_play: 'all' as const,
auto_pause: 'never' as const,
auto_mute: 'all' as const,
auto_unmute: 'never' as const,
preload: false,
lazy_load: true,
lazy_unload: 'never' as const,
@@ -665,10 +684,23 @@ const liveOverridableConfigSchema = z
const liveConfigSchema = liveOverridableConfigSchema
.extend({
// Non-overrideable parameters.
auto_unmute: z.boolean().default(liveConfigDefault.auto_unmute),
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(liveConfigDefault.auto_play),
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.auto_pause),
auto_mute: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(liveConfigDefault.auto_unmute),
preload: z.boolean().default(liveConfigDefault.preload),
lazy_load: z.boolean().default(liveConfigDefault.lazy_load),
lazy_unload: z.enum(LAZY_UNLOAD_CONDITIONS).default(liveConfigDefault.lazy_unload),
lazy_unload: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(liveConfigDefault.lazy_unload),
draggable: z.boolean().default(liveConfigDefault.draggable),
transition_effect: transitionEffectConfigSchema.default(
liveConfigDefault.transition_effect,
@@ -750,8 +782,10 @@ export type MenuConfig = z.infer<typeof menuConfigSchema>;
* Event viewer configuration section (clip, snapshot).
*/
const viewerConfigDefault = {
auto_play: true,
auto_unmute: false,
auto_play: 'all' as const,
auto_pause: 'all' as const,
auto_mute: 'all' as const,
auto_unmute: 'never' as const,
lazy_load: true,
draggable: true,
transition_effect: 'slide' as const,
@@ -786,8 +820,18 @@ export type ViewerNextPreviousControlConfig = z.infer<
const viewerConfigSchema = z
.object({
auto_play: z.boolean().default(viewerConfigDefault.auto_play),
auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute),
auto_play: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(viewerConfigDefault.auto_play),
auto_pause: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(viewerConfigDefault.auto_pause),
auto_mute: z
.enum(MEDIA_ACTION_NEGATIVE_CONDITIONS)
.default(viewerConfigDefault.auto_mute),
auto_unmute: z
.enum(MEDIA_ACTION_POSITIVE_CONDITIONS)
.default(viewerConfigDefault.auto_unmute),
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
draggable: z.boolean().default(viewerConfigDefault.draggable),
transition_effect: transitionEffectConfigSchema.default(