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