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),