Add auto unmute support.

This commit is contained in:
Dermot Duffy
2022-02-01 20:40:34 -08:00
parent f46a15d76e
commit 632569b1ef
10 changed files with 260 additions and 70 deletions
+51 -18
View File
@@ -3,15 +3,20 @@ import { FrigateCardMediaPlayer } from '../../types.js';
export type AutoMediaPluginOptionsType = {
playerSelector: string;
autoplayWhenVisible?: boolean;
autoPlayWhenVisible?: boolean;
autoUnmuteWhenVisible?: boolean;
};
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {
autoplayWhenVisible: true,
autoPlayWhenVisible: true,
autoUnmuteWhenVisible: true,
};
export type AutoMediaPluginType = EmblaPluginType<AutoMediaPluginOptionsType> & {
play: () => void;
pause: () => void;
mute: () => void;
unmute: () => void;
}
/**
@@ -35,9 +40,12 @@ export function AutoMediaPlugin(
slides = carousel.slideNodes();
// Frigate card media autoplays when the media loads not necessarily when the
// slide is selected, so only pause based on carousel events.
carousel.on('destroy', pauseAllHandler);
// slide is selected, so only pause (and not play/unmute) based on carousel
// events.
carousel.on('destroy', pause);
carousel.on('select', pausePrevious);
carousel.on('destroy', mute);
carousel.on('select', mutePrevious);
document.addEventListener('visibilitychange', visibilityHandler);
}
@@ -46,8 +54,10 @@ export function AutoMediaPlugin(
* Destroy the plugin.
*/
function destroy(): void {
carousel.off('destroy', pauseAllHandler);
carousel.off('destroy', pause);
carousel.off('select', pausePrevious);
carousel.off('destroy', mute);
carousel.off('select', mutePrevious);
document.removeEventListener('visibilitychange', visibilityHandler);
}
@@ -58,8 +68,14 @@ export function AutoMediaPlugin(
function visibilityHandler(): void {
if (document.visibilityState == 'hidden') {
pause();
} else if (document.visibilityState == 'visible' && options.autoplayWhenVisible) {
play();
mute();
} else if (document.visibilityState == 'visible') {
if (options.autoPlayWhenVisible) {
play();
}
if (options.autoUnmuteWhenVisible) {
unmute();
}
}
}
@@ -73,39 +89,56 @@ export function AutoMediaPlugin(
}
/**
* Pause all slides.
*/
function pauseAllHandler(): void {
slides.forEach((slide) => getPlayer(slide)?.pause());
}
/**
* Autoplay the current slide.
* Play the current slide.
*/
function play(): void {
getPlayer(slides[carousel.selectedScrollSnap()])?.play();
}
/**
* Autopause the current slide.
* Pause the current slide.
*/
function pause(): void {
function pause(): void {
getPlayer(slides[carousel.selectedScrollSnap()])?.pause();
}
/**
* Autopause the previous slide.
* Pause the previous slide.
*/
function pausePrevious(): void {
getPlayer(slides[carousel.previousScrollSnap()])?.pause();
}
/**
* Unmute the current slide.
*/
function unmute(): void {
getPlayer(slides[carousel.selectedScrollSnap()])?.unmute();
}
/**
* Mute the current slide.
*/
function mute(): void {
getPlayer(slides[carousel.selectedScrollSnap()])?.mute();
}
/**
* Mute the previous slide.
*/
function mutePrevious(): void {
getPlayer(slides[carousel.previousScrollSnap()])?.mute();
}
const self: AutoMediaPluginType = {
name: 'AutoMediaPlugin',
options,
init,
destroy,
play,
pause,
mute,
unmute,
};
return self;
}
+105 -12
View File
@@ -15,6 +15,7 @@ import {
MediaShowInfo,
WebRTCConfig,
FrigateCardError,
FrigateCardMediaPlayer,
LiveOverrides,
LiveProvider,
frigateCardConfigDefaults,
@@ -27,7 +28,7 @@ import { Task } from '@lit-labs/task';
import { customElement, property, state } from 'lit/decorators.js';
import { until } from 'lit/directives/until.js';
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
import { AutoMediaPlugin, AutoMediaPluginType } from './embla-plugins/automedia.js';
import { BrowseMediaUtil } from '../browse-media-util.js';
import { ConditionState, getOverriddenConfig } from '../card-condition.js';
import { FrigateCardMediaCarousel } from './media-carousel.js';
@@ -105,7 +106,7 @@ export class FrigateCardLive extends LitElement {
protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void {
this._savedMediaShowInfo = e.detail;
if (this._preloaded) {
// If live is being pre-loaded, don't let the event propogate upwards yet
// If live is being pre-loaded, don't let the event propagate upwards yet
// as the media is not really being shown.
e.stopPropagation();
}
@@ -251,15 +252,16 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
*/
updated(changedProperties: PropertyValues): void {
if (
changedProperties.has('cameras') ||
changedProperties.has('liveConfig') ||
changedProperties.has('preloaded')
this._carousel &&
(changedProperties.has('cameras') || changedProperties.has('liveConfig'))
) {
// All of these properties may fundamentally change the contents/size of
// the DOM, and the carousel should be reset when they change.
this._destroyCarousel();
}
super.updated(changedProperties);
if (changedProperties.has('view')) {
const oldView = changedProperties.get('view') as View | undefined;
if (
@@ -275,7 +277,22 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
}
}
super.updated(changedProperties);
if (changedProperties.has('preloaded')) {
const automedia = this._plugins['AutoMediaPlugin'] as
| AutoMediaPluginType
| undefined;
if (automedia) {
// If this has changed to preloaded then pause & mute, otherwise play
// and potentially unmute (depending on configuration).
if (this.preloaded) {
automedia.pause();
automedia.mute();
} else {
automedia.play();
this._autoUnmuteHandler();
}
}
}
}
/**
@@ -310,10 +327,20 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
}),
AutoMediaPlugin({
playerSelector: 'frigate-card-live-provider',
autoUnmuteWhenVisible: !!this.liveConfig?.auto_unmute,
}),
];
}
/**
* Unmute the media on the selected slide.
*/
protected _autoUnmuteHandler(): void {
if (this.liveConfig?.auto_unmute) {
super._autoUnmuteHandler();
}
}
/**
* Returns the number of slides to lazily load. 0 means all slides are lazy
* loaded, 1 means that 1 slide on each side of the currently selected slide
@@ -519,9 +546,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
<frigate-card-title-control
${ref(this._titleControlRef)}
.config=${config.controls.title}
.text="${title
? `${localize('common.live')}: ${title}`
: ''}"
.text="${title ? `${localize('common.live')}: ${title}` : ''}"
.fitInto=${this as HTMLElement}
>
</frigate-card-title-control>
@@ -549,9 +574,7 @@ export class FrigateCardLiveProvider extends LitElement {
@property({ attribute: false })
public label = '';
protected _providerRef: Ref<
FrigateCardLiveFrigate | FrigateCardLiveJSMPEG | FrigateCardLiveWebRTC
> = createRef();
protected _providerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
/**
* Play the video.
@@ -567,6 +590,20 @@ export class FrigateCardLiveProvider extends LitElement {
this._providerRef.value?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
this._providerRef.value?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this._providerRef.value?.unmute();
}
protected _getResolvedProvider(): LiveProvider {
if (this.cameraConfig?.live_provider === 'auto') {
if (this.cameraConfig?.webrtc?.entity || this.cameraConfig?.webrtc?.url) {
@@ -649,6 +686,20 @@ export class FrigateCardLiveFrigate extends LitElement {
this._playerRef.value?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
this._playerRef.value?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this._playerRef.value?.unmute();
}
/**
* Master render method.
* @returns A rendered template.
@@ -718,6 +769,26 @@ export class FrigateCardLiveWebRTC extends LitElement {
this._getPlayer()?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
const player = this._getPlayer();
if (player) {
player.muted = true;
}
}
/**
* Unmute the video.
*/
public unmute(): void {
const player = this._getPlayer();
if (player) {
player.muted = false;
}
}
/**
* Get the underlying video player.
* @returns The player or `null` if not found.
@@ -848,6 +919,28 @@ export class FrigateCardLiveJSMPEG extends LitElement {
this._jsmpegVideoPlayer?.stop();
}
/**
* Mute the video (included for completeness, JSMPEG live disables audio as
* Frigate does not encode it).
*/
public mute(): void {
const player = this._jsmpegVideoPlayer?.player;
if (player) {
player.volume = 0;
}
}
/**
* Unmute the video (included for completeness, JSMPEG live disables audio as
* Frigate does not encode it).
*/
public unmute(): void {
const player = this._jsmpegVideoPlayer?.player;
if (player) {
player.volume = 1;
}
}
/**
* Get a signed player URL.
* @returns A URL or null.
+32 -23
View File
@@ -3,7 +3,10 @@ import { EmblaCarouselType } from 'embla-carousel';
import { createRef, Ref } from 'lit/directives/ref';
import { customElement } from 'lit/decorators.js';
import { AutoMediaPluginType } from './embla-plugins/automedia.js';
import { FrigateCardCarousel } from './carousel.js';
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import { FrigateCardTitleControl } from './title-control.js';
import type { MediaShowInfo } from '../types.js';
import {
dispatchExistingMediaShowInfoAsEvent,
@@ -14,10 +17,6 @@ import './next-prev-control.js';
import mediaCarouselStyle from '../scss/media-carousel.scss';
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import { FrigateCardTitleControl } from './title-control.js';
import { AutoMediaPluginType } from './embla-plugins/automedia.js';
const getEmptyImageSrc = (width: number, height: number) =>
`data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
@@ -30,12 +29,21 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
protected _titleTimerID: number | null = null;
/**
* Play the media on the selected slide. May be overridden to control when
* autoplay should happen.
*/
protected _autoplayHandler(): void {
(this._plugins['MediaAutoPlayPause'] as AutoMediaPluginType | undefined)?.play();
protected _autoPlayHandler(): void {
(this._plugins['AutoMediaPlugin'] as AutoMediaPluginType | undefined)?.play();
}
/**
* Play the media on the selected slide. May be overridden to control when
* autoplay should happen.
*/
protected _autoUnmuteHandler(): void {
(this._plugins['AutoMediaPlugin'] as AutoMediaPluginType | undefined)?.unmute();
}
/**
@@ -69,7 +77,8 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
*/
connectedCallback(): void {
super.connectedCallback();
this.addEventListener('frigate-card:media-show', this._autoplayHandler);
this.addEventListener('frigate-card:media-show', this._autoPlayHandler);
this.addEventListener('frigate-card:media-show', this._autoUnmuteHandler);
this.addEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.addEventListener('frigate-card:media-show', this._titleHandler);
}
@@ -79,7 +88,8 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
*/
disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener('frigate-card:media-show', this._autoplayHandler);
this.removeEventListener('frigate-card:media-show', this._autoPlayHandler);
this.removeEventListener('frigate-card:media-show', this._autoUnmuteHandler);
this.removeEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.removeEventListener('frigate-card:media-show', this._titleHandler);
}
@@ -128,15 +138,15 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
if (!this._carousel) {
return;
}
const slides = this._carousel.slideNodes();
const heights = this._carousel.slidesInView(true).map((index) => {
return slides[index].getBoundingClientRect().height;
});
const targetHeight = Math.max(...heights);
if (targetHeight > 0) {
this._carousel.containerNode().style.maxHeight = `${targetHeight}px`;
} else {
this._carousel.containerNode().style.removeProperty('max-height');
const slide = this._carousel?.selectedScrollSnap()
if (slide !== undefined) {
const slides = this._carousel.slideNodes();
const height = slides[slide].getBoundingClientRect().height;
if (height > 0) {
this._carousel.containerNode().style.maxHeight = `${height}px`;
} else {
this._carousel.containerNode().style.removeProperty('max-height');
}
}
};
@@ -195,11 +205,10 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
return;
}
this._carousel.slidesInView(true).forEach((slideIndex) => {
if (slideIndex in this._mediaShowInfo) {
dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
}
});
const slideIndex = this._carousel.selectedScrollSnap();
if (slideIndex in this._mediaShowInfo) {
dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
}
}
/**
@@ -232,7 +241,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
// rejected upstream (empty 1x1 images will be rejected here).
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
this._mediaShowInfo[slideIndex] = mediaShowInfo;
if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) {
if (this._carousel && this._carousel?.selectedScrollSnap() == slideIndex) {
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
}
+14 -5
View File
@@ -267,12 +267,20 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}
/**
* Play the media on the selected slide. May be overridden to control when
* autoplay should happen.
* Play the media on the selected slide.
*/
protected _autoplayHandler(): void {
protected _autoPlayHandler(): void {
if (this.viewerConfig?.autoplay_clip) {
super._autoplayHandler();
super._autoPlayHandler();
}
}
/**
* Unmute the media on the selected slide.
*/
protected _autoUnmuteHandler(): void {
if (this.viewerConfig?.auto_unmute) {
super._autoUnmuteHandler();
}
}
@@ -322,7 +330,8 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
? [
AutoMediaPlugin({
playerSelector: 'frigate-card-ha-hls-player',
autoplayWhenVisible: !!this.viewerConfig?.autoplay_clip,
autoPlayWhenVisible: !!this.viewerConfig?.autoplay_clip,
autoUnmuteWhenVisible: !!this.viewerConfig?.auto_unmute,
}),
]
: []),