From 66588ee1c695f8e3ce9a67db4a9c33f1f746399e Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Thu, 5 May 2022 21:37:51 -0700 Subject: [PATCH 1/6] Initial attempt at auto_pause . --- src/components/embla-plugins/automedia.ts | 45 ++++++++++++++++++----- src/components/live.ts | 3 ++ src/components/viewer.ts | 3 ++ src/const.ts | 2 + src/editor.ts | 26 +++++++++---- src/localize/languages/en.json | 16 +++++--- src/types.ts | 11 ++++-- 7 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/components/embla-plugins/automedia.ts b/src/components/embla-plugins/automedia.ts index ce407027..02c1d833 100644 --- a/src/components/embla-plugins/automedia.ts +++ b/src/components/embla-plugins/automedia.ts @@ -1,10 +1,11 @@ import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel'; -import { FrigateCardMediaPlayer } from '../../types.js'; +import { AutoPauseCondition, FrigateCardMediaPlayer } from '../../types.js'; export type AutoMediaPluginOptionsType = { playerSelector: string; autoPlayWhenVisible?: boolean; autoUnmuteWhenVisible?: boolean; + autoPauseCondition?: AutoPauseCondition; }; export const defaultOptions: Partial = { @@ -17,12 +18,12 @@ export type AutoMediaPluginType = EmblaPluginType & pause: () => void; mute: () => void; unmute: () => void; -} +}; /** * An Embla plugin to take automated actions on media (e.g. pause, unmute, etc). - * @param userOptions - * @returns + * @param userOptions + * @returns */ export function AutoMediaPlugin( userOptions?: AutoMediaPluginOptionsType, @@ -43,7 +44,12 @@ export function AutoMediaPlugin( // slide is selected, so only pause (and not play/unmute) based on carousel // events. carousel.on('destroy', pause); - carousel.on('select', pausePrevious); + if ( + options.autoPauseCondition && + ['all', 'unselected'].includes(options.autoPauseCondition) + ) { + carousel.on('select', pausePrevious); + } carousel.on('destroy', mute); carousel.on('select', mutePrevious); @@ -55,7 +61,12 @@ export function AutoMediaPlugin( */ function destroy(): void { carousel.off('destroy', pause); - carousel.off('select', pausePrevious); + if ( + options.autoPauseCondition && + ['all', 'unselected'].includes(options.autoPauseCondition) + ) { + carousel.off('select', pausePrevious); + } carousel.off('destroy', mute); carousel.off('select', mutePrevious); @@ -65,14 +76,19 @@ export function AutoMediaPlugin( /** * Handle document visibility changes. */ - function visibilityHandler(): void { + function visibilityHandler(): void { if (document.visibilityState == 'hidden') { - pause(); + if ( + options.autoPauseCondition && + ['all', 'hidden'].includes(options.autoPauseCondition) + ) { + pauseAll(); + } mute(); } else if (document.visibilityState == 'visible') { if (options.autoPlayWhenVisible) { play(); - } + } if (options.autoUnmuteWhenVisible) { unmute(); } @@ -109,6 +125,15 @@ export function AutoMediaPlugin( getPlayer(slides[carousel.previousScrollSnap()])?.pause(); } + /** + * Pause the previous slide. + */ + function pauseAll(): void { + for (const slide of slides) { + getPlayer(slide)?.pause(); + } + } + /** * Unmute the current slide. */ @@ -126,7 +151,7 @@ export function AutoMediaPlugin( /** * Mute the previous slide. */ - function mutePrevious(): void { + function mutePrevious(): void { getPlayer(slides[carousel.previousScrollSnap()])?.mute(); } diff --git a/src/components/live.ts b/src/components/live.ts index d718a904..382d4dfd 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -300,6 +300,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { AutoMediaPlugin({ playerSelector: 'frigate-card-live-provider', autoUnmuteWhenVisible: !!this.liveConfig?.auto_unmute, + ...(this.liveConfig?.auto_pause && { + autoPauseCondition: this.liveConfig.auto_pause, + }), }), ]; } diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 823c33d7..7fae9c64 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -282,6 +282,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { playerSelector: 'frigate-card-ha-hls-player', autoPlayWhenVisible: !!this.viewerConfig?.auto_play, autoUnmuteWhenVisible: !!this.viewerConfig?.auto_unmute, + ...(this.viewerConfig?.auto_pause && { + autoPauseCondition: this.viewerConfig.auto_pause, + }), }), ]; } diff --git a/src/const.ts b/src/const.ts index d29dc511..98c5ee1b 100644 --- a/src/const.ts +++ b/src/const.ts @@ -43,6 +43,7 @@ export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE = `${CONF_EVENT_GALLERY}.controls.thumbnails.size` as const; export const CONF_EVENT_VIEWER = 'event_viewer' as const; +export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` as const; export const CONF_EVENT_VIEWER_AUTO_PLAY = `${CONF_EVENT_VIEWER}.auto_play` 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; @@ -67,6 +68,7 @@ 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_PAUSE = `${CONF_LIVE}.auto_pause` 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; diff --git a/src/editor.ts b/src/editor.ts index 19d977a9..73f14f37 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -34,6 +34,7 @@ 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_PAUSE, CONF_EVENT_VIEWER_AUTO_PLAY, CONF_EVENT_VIEWER_AUTO_UNMUTE, CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE, @@ -50,6 +51,7 @@ import { CONF_IMAGE_MODE, CONF_IMAGE_REFRESH_SECONDS, CONF_IMAGE_URL, + CONF_LIVE_AUTO_PAUSE, CONF_LIVE_AUTO_UNMUTE, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE, @@ -349,12 +351,15 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'auto', label: localize('config.view.dark_modes.auto') }, ]; - protected _lazyUnloadConditions: EditorSelectOption[] = [ + protected _mediaActionConditions: 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') }, ]; public setConfig(config: RawFrigateCardConfig): void { @@ -988,7 +993,6 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderMenuButton('timeline')} ${this._renderMenuButton('media_player')} - ` : ''} ${this._renderOptionSetHeader('live')} @@ -1000,7 +1004,11 @@ 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._mediaActionConditions, + )} + ${this._renderOptionSelector( + CONF_LIVE_AUTO_PAUSE, + this._mediaActionConditions, )} ${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)} ${this._renderOptionSelector( @@ -1073,6 +1081,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_EVENT_VIEWER_AUTO_UNMUTE, defaults.event_viewer.auto_unmute, )} + ${this._renderOptionSelector( + CONF_EVENT_VIEWER_AUTO_PAUSE, + this._mediaActionConditions, + )} ${this._renderSwitch( CONF_EVENT_VIEWER_DRAGGABLE, defaults.event_viewer.draggable, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 07732abf..15cc3fd9 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -33,6 +33,14 @@ }, "zone": "Frigate zone" }, + "common": { + "media_action_conditions": { + "all": "All opportunities", + "unselected": "On unselection", + "hidden": "On browser/tab hiding", + "never": "Never" + } + }, "view": { "camera_select": "View for newly selected cameras", "dark_mode": "Dark mode", @@ -68,6 +76,7 @@ }, "event_viewer": { "auto_play": "Automatically play media", + "auto_pause": "Automatically pause 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 +126,7 @@ "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_pause": "Automatically pause live cameras", "auto_unmute": "Automatically unmute live cameras", "transition_effect": "Live camera transition effect", "controls": { diff --git a/src/types.ts b/src/types.ts index 8c598e08..b1d6d19d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,8 +64,9 @@ 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_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const; +export type LazyUnloadCondition = typeof MEDIA_ACTION_CONDITIONS[number]; +export type AutoPauseCondition = typeof MEDIA_ACTION_CONDITIONS[number]; export class FrigateCardError extends Error {} @@ -560,6 +561,7 @@ export type TitleControlConfig = z.infer; * Live view configuration section. */ const liveConfigDefault = { + auto_pause: 'all' as const, auto_unmute: false, preload: false, lazy_load: true, @@ -665,10 +667,11 @@ const liveOverridableConfigSchema = z const liveConfigSchema = liveOverridableConfigSchema .extend({ // Non-overrideable parameters. + auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_pause), auto_unmute: z.boolean().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_CONDITIONS).default(liveConfigDefault.lazy_unload), draggable: z.boolean().default(liveConfigDefault.draggable), transition_effect: transitionEffectConfigSchema.default( liveConfigDefault.transition_effect, @@ -750,6 +753,7 @@ export type MenuConfig = z.infer; * Event viewer configuration section (clip, snapshot). */ const viewerConfigDefault = { + auto_pause: 'all' as const, auto_play: true, auto_unmute: false, lazy_load: true, @@ -786,6 +790,7 @@ export type ViewerNextPreviousControlConfig = z.infer< const viewerConfigSchema = z .object({ + auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_pause), auto_play: z.boolean().default(viewerConfigDefault.auto_play), auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute), lazy_load: z.boolean().default(viewerConfigDefault.lazy_load), From d552cf1a0dcb52c77aee3d94e8c2f2d4e504096d Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 7 May 2022 16:04:50 -0700 Subject: [PATCH 2/6] Implement auto_mute . --- src/components/embla-plugins/automedia.ts | 35 +++++++++++++++++++---- src/components/live.ts | 3 ++ src/components/viewer.ts | 3 ++ src/const.ts | 2 ++ src/editor.ts | 18 +++++++++--- src/localize/languages/en.json | 2 ++ src/patches/ha-camera-stream.ts | 4 +-- src/patches/ha-hls-player.ts | 14 ++++++--- src/types.ts | 5 ++++ 9 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/components/embla-plugins/automedia.ts b/src/components/embla-plugins/automedia.ts index 02c1d833..b1b61f1a 100644 --- a/src/components/embla-plugins/automedia.ts +++ b/src/components/embla-plugins/automedia.ts @@ -1,11 +1,12 @@ import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel'; -import { AutoPauseCondition, FrigateCardMediaPlayer } from '../../types.js'; +import { AutoMuteCondition, AutoPauseCondition, FrigateCardMediaPlayer } from '../../types.js'; export type AutoMediaPluginOptionsType = { playerSelector: string; autoPlayWhenVisible?: boolean; autoUnmuteWhenVisible?: boolean; autoPauseCondition?: AutoPauseCondition; + autoMuteCondition?: AutoMuteCondition; }; export const defaultOptions: Partial = { @@ -51,7 +52,12 @@ export function AutoMediaPlugin( carousel.on('select', pausePrevious); } carousel.on('destroy', mute); - carousel.on('select', mutePrevious); + if ( + options.autoMuteCondition && + ['all', 'unselected'].includes(options.autoMuteCondition) + ) { + carousel.on('select', mutePrevious); + } document.addEventListener('visibilitychange', visibilityHandler); } @@ -68,7 +74,12 @@ export function AutoMediaPlugin( carousel.off('select', pausePrevious); } carousel.off('destroy', mute); - carousel.off('select', mutePrevious); + if ( + options.autoMuteCondition && + ['all', 'unselected'].includes(options.autoMuteCondition) + ) { + carousel.off('select', mutePrevious); + } document.removeEventListener('visibilitychange', visibilityHandler); } @@ -84,7 +95,12 @@ export function AutoMediaPlugin( ) { pauseAll(); } - mute(); + if ( + options.autoMuteCondition && + ['all', 'hidden'].includes(options.autoMuteCondition) + ) { + muteAll(); + } } else if (document.visibilityState == 'visible') { if (options.autoPlayWhenVisible) { play(); @@ -126,7 +142,7 @@ export function AutoMediaPlugin( } /** - * Pause the previous slide. + * Pause all slides. */ function pauseAll(): void { for (const slide of slides) { @@ -155,6 +171,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, diff --git a/src/components/live.ts b/src/components/live.ts index 382d4dfd..b657562c 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -303,6 +303,9 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { ...(this.liveConfig?.auto_pause && { autoPauseCondition: this.liveConfig.auto_pause, }), + ...(this.liveConfig?.auto_mute && { + autoMuteCondition: this.liveConfig.auto_mute, + }), }), ]; } diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 7fae9c64..159c4672 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -285,6 +285,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel { ...(this.viewerConfig?.auto_pause && { autoPauseCondition: this.viewerConfig.auto_pause, }), + ...(this.viewerConfig?.auto_mute && { + autoMuteCondition: this.viewerConfig.auto_mute, + }), }), ]; } diff --git a/src/const.ts b/src/const.ts index 98c5ee1b..19071855 100644 --- a/src/const.ts +++ b/src/const.ts @@ -45,6 +45,7 @@ export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE = export const CONF_EVENT_VIEWER = 'event_viewer' as const; export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` as const; export const CONF_EVENT_VIEWER_AUTO_PLAY = `${CONF_EVENT_VIEWER}.auto_play` 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; @@ -69,6 +70,7 @@ export const CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS = export const CONF_LIVE = 'live' 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; diff --git a/src/editor.ts b/src/editor.ts index 73f14f37..6b4b7340 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -34,6 +34,7 @@ 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, @@ -51,6 +52,7 @@ import { CONF_IMAGE_MODE, CONF_IMAGE_REFRESH_SECONDS, CONF_IMAGE_URL, + CONF_LIVE_AUTO_MUTE, CONF_LIVE_AUTO_PAUSE, CONF_LIVE_AUTO_UNMUTE, CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE, @@ -1010,6 +1012,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_AUTO_PAUSE, this._mediaActionConditions, )} + ${this._renderOptionSelector( + CONF_LIVE_AUTO_MUTE, + this._mediaActionConditions, + )} ${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)} ${this._renderOptionSelector( CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE, @@ -1077,14 +1083,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_EVENT_VIEWER_AUTO_PLAY, defaults.event_viewer.auto_play, )} - ${this._renderSwitch( - CONF_EVENT_VIEWER_AUTO_UNMUTE, - defaults.event_viewer.auto_unmute, - )} ${this._renderOptionSelector( CONF_EVENT_VIEWER_AUTO_PAUSE, this._mediaActionConditions, )} + ${this._renderOptionSelector( + CONF_EVENT_VIEWER_AUTO_MUTE, + this._mediaActionConditions, + )} + ${this._renderSwitch( + CONF_EVENT_VIEWER_AUTO_UNMUTE, + defaults.event_viewer.auto_unmute, + )} ${this._renderSwitch( CONF_EVENT_VIEWER_DRAGGABLE, defaults.event_viewer.draggable, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 15cc3fd9..c2dfacb0 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -77,6 +77,7 @@ "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", @@ -127,6 +128,7 @@ "lazy_load": "Live cameras are lazily loaded", "lazy_unload": "Live cameras are lazily unloaded", "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": { diff --git a/src/patches/ha-camera-stream.ts b/src/patches/ha-camera-stream.ts index 00c26155..ebfca95e 100644 --- a/src/patches/ha-camera-stream.ts +++ b/src/patches/ha-camera-stream.ts @@ -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(); } /** diff --git a/src/patches/ha-hls-player.ts b/src/patches/ha-hls-player.ts index 95fdc1f7..7c328f9f 100644 --- a/src/patches/ha-hls-player.ts +++ b/src/patches/ha-hls-player.ts @@ -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` diff --git a/src/types.ts b/src/types.ts index b1d6d19d..4069aee5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -66,6 +66,7 @@ export type LiveProvider = typeof LIVE_PROVIDERS[number]; const MEDIA_ACTION_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const; export type LazyUnloadCondition = typeof MEDIA_ACTION_CONDITIONS[number]; +export type AutoMuteCondition = typeof MEDIA_ACTION_CONDITIONS[number]; export type AutoPauseCondition = typeof MEDIA_ACTION_CONDITIONS[number]; export class FrigateCardError extends Error {} @@ -562,6 +563,7 @@ export type TitleControlConfig = z.infer; */ const liveConfigDefault = { auto_pause: 'all' as const, + auto_mute: 'all' as const, auto_unmute: false, preload: false, lazy_load: true, @@ -668,6 +670,7 @@ const liveConfigSchema = liveOverridableConfigSchema .extend({ // Non-overrideable parameters. auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_pause), + auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_mute), auto_unmute: z.boolean().default(liveConfigDefault.auto_unmute), preload: z.boolean().default(liveConfigDefault.preload), lazy_load: z.boolean().default(liveConfigDefault.lazy_load), @@ -755,6 +758,7 @@ export type MenuConfig = z.infer; const viewerConfigDefault = { auto_pause: 'all' as const, auto_play: true, + auto_mute: 'all' as const, auto_unmute: false, lazy_load: true, draggable: true, @@ -792,6 +796,7 @@ const viewerConfigSchema = z .object({ auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_pause), auto_play: z.boolean().default(viewerConfigDefault.auto_play), + auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_mute), auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute), lazy_load: z.boolean().default(viewerConfigDefault.lazy_load), draggable: z.boolean().default(viewerConfigDefault.draggable), From f6cdf2f01d273af0addd1ba8dd727f6ba7eb215b Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 7 May 2022 20:07:09 -0700 Subject: [PATCH 3/6] Add auto_play and auto_unmute for consistency. --- README.md | 13 +++-- src/components/embla-plugins/automedia.ts | 33 ++++++++---- src/components/live.ts | 29 ++++++++-- src/components/viewer.ts | 27 +++++++--- src/config-mgmt.ts | 11 ++++ src/const.ts | 3 +- src/editor.ts | 44 +++++++++++---- src/localize/languages/en.json | 3 ++ src/types.ts | 66 +++++++++++++++++------ 9 files changed, 176 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index b9b63596..4defebaf 100644 --- a/README.md +++ b/README.md @@ -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 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 hidden 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`. | diff --git a/src/components/embla-plugins/automedia.ts b/src/components/embla-plugins/automedia.ts index b1b61f1a..37041eb3 100644 --- a/src/components/embla-plugins/automedia.ts +++ b/src/components/embla-plugins/automedia.ts @@ -1,18 +1,25 @@ import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel'; -import { AutoMuteCondition, AutoPauseCondition, 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 methods manually when the media is actually loaded + // (not just the slide shown). + autoPlayCondition?: AutoPlayCondition; + autoUnmuteCondition?: AutoUnmuteCondition; autoPauseCondition?: AutoPauseCondition; autoMuteCondition?: AutoMuteCondition; }; -export const defaultOptions: Partial = { - autoPlayWhenVisible: true, - autoUnmuteWhenVisible: true, -}; +export const defaultOptions: Partial = {}; export type AutoMediaPluginType = EmblaPluginType & { play: () => void; @@ -102,10 +109,16 @@ export function AutoMediaPlugin( 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(); } } @@ -174,7 +187,7 @@ export function AutoMediaPlugin( /** * Mute all slides. */ - function muteAll(): void { + function muteAll(): void { for (const slide of slides) { getPlayer(slide)?.mute(); } diff --git a/src/components/live.ts b/src/components/live.ts index b657562c..77346f9a 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -299,22 +299,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 _autoUnmuteHandler(): void { - if (this.liveConfig?.auto_unmute) { + 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 && + ['all', 'selected'].includes(this.liveConfig.auto_unmute) + ) { super._autoUnmuteHandler(); } } @@ -984,6 +1004,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, diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 159c4672..28ec2abc 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -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) { + protected _autoPlayHandler(): void { + 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,14 +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, + }), }), ]; } diff --git a/src/config-mgmt.ts b/src/config-mgmt.ts index 14d79c79..cc6eb774 100644 --- a/src/config-mgmt.ts +++ b/src/config-mgmt.ts @@ -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, + ), ]; diff --git a/src/const.ts b/src/const.ts index 19071855..cc75daa0 100644 --- a/src/const.ts +++ b/src/const.ts @@ -43,8 +43,8 @@ export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SIZE = `${CONF_EVENT_GALLERY}.controls.thumbnails.size` as const; export const CONF_EVENT_VIEWER = 'event_viewer' as const; -export const CONF_EVENT_VIEWER_AUTO_PAUSE = `${CONF_EVENT_VIEWER}.auto_pause` 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; @@ -69,6 +69,7 @@ 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; diff --git a/src/editor.ts b/src/editor.ts index 6b4b7340..739439f0 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -54,6 +54,7 @@ import { 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, @@ -353,7 +354,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'auto', label: localize('config.view.dark_modes.auto') }, ]; - protected _mediaActionConditions: EditorSelectOption[] = [ + protected _mediaActionNegativeConditions: EditorSelectOption[] = [ { value: '', label: '' }, { value: 'all', label: localize('config.common.media_action_conditions.all') }, { @@ -364,6 +365,20 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { 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 { // Note: This does not use Zod to parse the configuration, so it may be // partially or completely invalid. It's more useful to have a partially @@ -1006,17 +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._mediaActionConditions, + this._mediaActionNegativeConditions, + )} + ${this._renderOptionSelector( + CONF_LIVE_AUTO_PLAY, + this._mediaActionPositiveConditions, )} ${this._renderOptionSelector( CONF_LIVE_AUTO_PAUSE, - this._mediaActionConditions, + this._mediaActionNegativeConditions, )} ${this._renderOptionSelector( CONF_LIVE_AUTO_MUTE, - this._mediaActionConditions, + 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, @@ -1079,21 +1101,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderOptionSetHeader('event_viewer')} ${this._expandedMenus[MENU_OPTIONS] === 'event_viewer' ? html`
- ${this._renderSwitch( + ${this._renderOptionSelector( CONF_EVENT_VIEWER_AUTO_PLAY, - defaults.event_viewer.auto_play, + this._mediaActionPositiveConditions, )} ${this._renderOptionSelector( CONF_EVENT_VIEWER_AUTO_PAUSE, - this._mediaActionConditions, + this._mediaActionNegativeConditions, )} ${this._renderOptionSelector( CONF_EVENT_VIEWER_AUTO_MUTE, - this._mediaActionConditions, + this._mediaActionNegativeConditions, )} - ${this._renderSwitch( + ${this._renderOptionSelector( CONF_EVENT_VIEWER_AUTO_UNMUTE, - defaults.event_viewer.auto_unmute, + this._mediaActionPositiveConditions, )} ${this._renderSwitch( CONF_EVENT_VIEWER_DRAGGABLE, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index c2dfacb0..b9223c0e 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -37,7 +37,9 @@ "media_action_conditions": { "all": "All opportunities", "unselected": "On unselection", + "selected": "On selection", "hidden": "On browser/tab hiding", + "visible": "On browser/tab visibility", "never": "Never" } }, @@ -127,6 +129,7 @@ "draggable": "Live cameras view can be dragged/swiped", "lazy_load": "Live cameras are lazily loaded", "lazy_unload": "Live cameras are lazily unloaded", + "auto_play": "Automatically play live cameras", "auto_pause": "Automatically pause live cameras", "auto_mute": "Automatically mute live cameras", "auto_unmute": "Automatically unmute live cameras", diff --git a/src/types.ts b/src/types.ts index 4069aee5..f66ea1f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,10 +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 MEDIA_ACTION_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const; -export type LazyUnloadCondition = typeof MEDIA_ACTION_CONDITIONS[number]; -export type AutoMuteCondition = typeof MEDIA_ACTION_CONDITIONS[number]; -export type AutoPauseCondition = typeof MEDIA_ACTION_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 {} @@ -562,9 +576,10 @@ export type TitleControlConfig = z.infer; * Live view configuration section. */ const liveConfigDefault = { - auto_pause: 'all' as const, + auto_play: 'all' as const, + auto_pause: 'never' as const, auto_mute: 'all' as const, - auto_unmute: false, + auto_unmute: 'never' as const, preload: false, lazy_load: true, lazy_unload: 'never' as const, @@ -669,12 +684,23 @@ const liveOverridableConfigSchema = z const liveConfigSchema = liveOverridableConfigSchema .extend({ // Non-overrideable parameters. - auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_pause), - auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(liveConfigDefault.auto_mute), - 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(MEDIA_ACTION_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, @@ -756,10 +782,10 @@ export type MenuConfig = z.infer; * Event viewer configuration section (clip, snapshot). */ const viewerConfigDefault = { + auto_play: 'all' as const, auto_pause: 'all' as const, - auto_play: true, auto_mute: 'all' as const, - auto_unmute: false, + auto_unmute: 'never' as const, lazy_load: true, draggable: true, transition_effect: 'slide' as const, @@ -794,10 +820,18 @@ export type ViewerNextPreviousControlConfig = z.infer< const viewerConfigSchema = z .object({ - auto_pause: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_pause), - auto_play: z.boolean().default(viewerConfigDefault.auto_play), - auto_mute: z.enum(MEDIA_ACTION_CONDITIONS).default(viewerConfigDefault.auto_mute), - 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( From cfb5f04438648779a7ff5f7cdf4441bf818b3328 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 8 May 2022 07:54:42 -0700 Subject: [PATCH 4/6] Fix live camera preload behavior. --- README.md | 2 +- src/components/live.ts | 22 ++++++++++++++++------ src/components/surround-thumbnails.ts | 3 +++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4defebaf..573c6ff3 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ live: | `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 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 hidden or `all` on any opportunity to automatically unmute (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 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. | diff --git a/src/components/live.ts b/src/components/live.ts index 77346f9a..ab32b155 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -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) { - automedia.pause(); - automedia.mute(); + 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(); } } @@ -330,7 +340,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel { /** * Unmute the media on the loaded slide. */ - protected _autoUnmuteHandler(): void { + protected _autoUnmuteHandler(): void { if ( this.liveConfig?.auto_unmute && ['all', 'selected'].includes(this.liveConfig.auto_unmute) diff --git a/src/components/surround-thumbnails.ts b/src/components/surround-thumbnails.ts index ce708446..f0d62a2f 100644 --- a/src/components/surround-thumbnails.ts +++ b/src/components/surround-thumbnails.ts @@ -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); } From 0f11a938fb9f52e3d8242dad3a69f20e864a40f4 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 8 May 2022 08:24:09 -0700 Subject: [PATCH 5/6] Fix rollup warnings. --- rollup.config.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 509c1388..d6d1f407 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -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({ - 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), + preventAssignment: true, + values: { + 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), + } }), dev && serve(serveopts), !dev && terser(), From 00ba9bed4f1c33bb2f0b0bd5d38d6fdbce7ded21 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 8 May 2022 08:49:41 -0700 Subject: [PATCH 6/6] Minor codereview fixes. --- README.md | 2 +- src/components/embla-plugins/automedia.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 573c6ff3..c2da62c6 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ 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_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 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_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).| diff --git a/src/components/embla-plugins/automedia.ts b/src/components/embla-plugins/automedia.ts index 37041eb3..5a19b6be 100644 --- a/src/components/embla-plugins/automedia.ts +++ b/src/components/embla-plugins/automedia.ts @@ -11,8 +11,9 @@ export type AutoMediaPluginOptionsType = { playerSelector: string; // Note: Neither play nor unmute will activate on selection. The caller is - // expected to call the methods manually when the media is actually loaded - // (not just the slide shown). + // 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;