Add auto unmute support.
This commit is contained in:
@@ -3,15 +3,20 @@ import { FrigateCardMediaPlayer } from '../../types.js';
|
|||||||
|
|
||||||
export type AutoMediaPluginOptionsType = {
|
export type AutoMediaPluginOptionsType = {
|
||||||
playerSelector: string;
|
playerSelector: string;
|
||||||
autoplayWhenVisible?: boolean;
|
autoPlayWhenVisible?: boolean;
|
||||||
|
autoUnmuteWhenVisible?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {
|
export const defaultOptions: Partial<AutoMediaPluginOptionsType> = {
|
||||||
autoplayWhenVisible: true,
|
autoPlayWhenVisible: true,
|
||||||
|
autoUnmuteWhenVisible: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AutoMediaPluginType = EmblaPluginType<AutoMediaPluginOptionsType> & {
|
export type AutoMediaPluginType = EmblaPluginType<AutoMediaPluginOptionsType> & {
|
||||||
play: () => void;
|
play: () => void;
|
||||||
|
pause: () => void;
|
||||||
|
mute: () => void;
|
||||||
|
unmute: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,9 +40,12 @@ export function AutoMediaPlugin(
|
|||||||
slides = carousel.slideNodes();
|
slides = carousel.slideNodes();
|
||||||
|
|
||||||
// Frigate card media autoplays when the media loads not necessarily when the
|
// Frigate card media autoplays when the media loads not necessarily when the
|
||||||
// slide is selected, so only pause based on carousel events.
|
// slide is selected, so only pause (and not play/unmute) based on carousel
|
||||||
carousel.on('destroy', pauseAllHandler);
|
// events.
|
||||||
|
carousel.on('destroy', pause);
|
||||||
carousel.on('select', pausePrevious);
|
carousel.on('select', pausePrevious);
|
||||||
|
carousel.on('destroy', mute);
|
||||||
|
carousel.on('select', mutePrevious);
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', visibilityHandler);
|
document.addEventListener('visibilitychange', visibilityHandler);
|
||||||
}
|
}
|
||||||
@@ -46,8 +54,10 @@ export function AutoMediaPlugin(
|
|||||||
* Destroy the plugin.
|
* Destroy the plugin.
|
||||||
*/
|
*/
|
||||||
function destroy(): void {
|
function destroy(): void {
|
||||||
carousel.off('destroy', pauseAllHandler);
|
carousel.off('destroy', pause);
|
||||||
carousel.off('select', pausePrevious);
|
carousel.off('select', pausePrevious);
|
||||||
|
carousel.off('destroy', mute);
|
||||||
|
carousel.off('select', mutePrevious);
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', visibilityHandler);
|
document.removeEventListener('visibilitychange', visibilityHandler);
|
||||||
}
|
}
|
||||||
@@ -58,9 +68,15 @@ export function AutoMediaPlugin(
|
|||||||
function visibilityHandler(): void {
|
function visibilityHandler(): void {
|
||||||
if (document.visibilityState == 'hidden') {
|
if (document.visibilityState == 'hidden') {
|
||||||
pause();
|
pause();
|
||||||
} else if (document.visibilityState == 'visible' && options.autoplayWhenVisible) {
|
mute();
|
||||||
|
} else if (document.visibilityState == 'visible') {
|
||||||
|
if (options.autoPlayWhenVisible) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
if (options.autoUnmuteWhenVisible) {
|
||||||
|
unmute();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,39 +89,56 @@ export function AutoMediaPlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause all slides.
|
* Play the current slide.
|
||||||
*/
|
|
||||||
function pauseAllHandler(): void {
|
|
||||||
slides.forEach((slide) => getPlayer(slide)?.pause());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Autoplay the current slide.
|
|
||||||
*/
|
*/
|
||||||
function play(): void {
|
function play(): void {
|
||||||
getPlayer(slides[carousel.selectedScrollSnap()])?.play();
|
getPlayer(slides[carousel.selectedScrollSnap()])?.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Autopause the current slide.
|
* Pause the current slide.
|
||||||
*/
|
*/
|
||||||
function pause(): void {
|
function pause(): void {
|
||||||
getPlayer(slides[carousel.selectedScrollSnap()])?.pause();
|
getPlayer(slides[carousel.selectedScrollSnap()])?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Autopause the previous slide.
|
* Pause the previous slide.
|
||||||
*/
|
*/
|
||||||
function pausePrevious(): void {
|
function pausePrevious(): void {
|
||||||
getPlayer(slides[carousel.previousScrollSnap()])?.pause();
|
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 = {
|
const self: AutoMediaPluginType = {
|
||||||
name: 'AutoMediaPlugin',
|
name: 'AutoMediaPlugin',
|
||||||
options,
|
options,
|
||||||
init,
|
init,
|
||||||
destroy,
|
destroy,
|
||||||
play,
|
play,
|
||||||
|
pause,
|
||||||
|
mute,
|
||||||
|
unmute,
|
||||||
};
|
};
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-12
@@ -15,6 +15,7 @@ import {
|
|||||||
MediaShowInfo,
|
MediaShowInfo,
|
||||||
WebRTCConfig,
|
WebRTCConfig,
|
||||||
FrigateCardError,
|
FrigateCardError,
|
||||||
|
FrigateCardMediaPlayer,
|
||||||
LiveOverrides,
|
LiveOverrides,
|
||||||
LiveProvider,
|
LiveProvider,
|
||||||
frigateCardConfigDefaults,
|
frigateCardConfigDefaults,
|
||||||
@@ -27,7 +28,7 @@ import { Task } from '@lit-labs/task';
|
|||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import { until } from 'lit/directives/until.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 { BrowseMediaUtil } from '../browse-media-util.js';
|
||||||
import { ConditionState, getOverriddenConfig } from '../card-condition.js';
|
import { ConditionState, getOverriddenConfig } from '../card-condition.js';
|
||||||
import { FrigateCardMediaCarousel } from './media-carousel.js';
|
import { FrigateCardMediaCarousel } from './media-carousel.js';
|
||||||
@@ -105,7 +106,7 @@ export class FrigateCardLive extends LitElement {
|
|||||||
protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void {
|
protected _mediaShowHandler(e: CustomEvent<MediaShowInfo>): void {
|
||||||
this._savedMediaShowInfo = e.detail;
|
this._savedMediaShowInfo = e.detail;
|
||||||
if (this._preloaded) {
|
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.
|
// as the media is not really being shown.
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -251,15 +252,16 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
|||||||
*/
|
*/
|
||||||
updated(changedProperties: PropertyValues): void {
|
updated(changedProperties: PropertyValues): void {
|
||||||
if (
|
if (
|
||||||
changedProperties.has('cameras') ||
|
this._carousel &&
|
||||||
changedProperties.has('liveConfig') ||
|
(changedProperties.has('cameras') || changedProperties.has('liveConfig'))
|
||||||
changedProperties.has('preloaded')
|
|
||||||
) {
|
) {
|
||||||
// All of these properties may fundamentally change the contents/size of
|
// All of these properties may fundamentally change the contents/size of
|
||||||
// the DOM, and the carousel should be reset when they change.
|
// the DOM, and the carousel should be reset when they change.
|
||||||
this._destroyCarousel();
|
this._destroyCarousel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
if (changedProperties.has('view')) {
|
if (changedProperties.has('view')) {
|
||||||
const oldView = changedProperties.get('view') as View | undefined;
|
const oldView = changedProperties.get('view') as View | undefined;
|
||||||
if (
|
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({
|
AutoMediaPlugin({
|
||||||
playerSelector: 'frigate-card-live-provider',
|
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
|
* 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
|
* 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
|
<frigate-card-title-control
|
||||||
${ref(this._titleControlRef)}
|
${ref(this._titleControlRef)}
|
||||||
.config=${config.controls.title}
|
.config=${config.controls.title}
|
||||||
.text="${title
|
.text="${title ? `${localize('common.live')}: ${title}` : ''}"
|
||||||
? `${localize('common.live')}: ${title}`
|
|
||||||
: ''}"
|
|
||||||
.fitInto=${this as HTMLElement}
|
.fitInto=${this as HTMLElement}
|
||||||
>
|
>
|
||||||
</frigate-card-title-control>
|
</frigate-card-title-control>
|
||||||
@@ -549,9 +574,7 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public label = '';
|
public label = '';
|
||||||
|
|
||||||
protected _providerRef: Ref<
|
protected _providerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
|
||||||
FrigateCardLiveFrigate | FrigateCardLiveJSMPEG | FrigateCardLiveWebRTC
|
|
||||||
> = createRef();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the video.
|
* Play the video.
|
||||||
@@ -567,6 +590,20 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
this._providerRef.value?.pause();
|
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 {
|
protected _getResolvedProvider(): LiveProvider {
|
||||||
if (this.cameraConfig?.live_provider === 'auto') {
|
if (this.cameraConfig?.live_provider === 'auto') {
|
||||||
if (this.cameraConfig?.webrtc?.entity || this.cameraConfig?.webrtc?.url) {
|
if (this.cameraConfig?.webrtc?.entity || this.cameraConfig?.webrtc?.url) {
|
||||||
@@ -649,6 +686,20 @@ export class FrigateCardLiveFrigate extends LitElement {
|
|||||||
this._playerRef.value?.pause();
|
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.
|
* Master render method.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
@@ -718,6 +769,26 @@ export class FrigateCardLiveWebRTC extends LitElement {
|
|||||||
this._getPlayer()?.pause();
|
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.
|
* Get the underlying video player.
|
||||||
* @returns The player or `null` if not found.
|
* @returns The player or `null` if not found.
|
||||||
@@ -848,6 +919,28 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
this._jsmpegVideoPlayer?.stop();
|
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.
|
* Get a signed player URL.
|
||||||
* @returns A URL or null.
|
* @returns A URL or null.
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { EmblaCarouselType } from 'embla-carousel';
|
|||||||
import { createRef, Ref } from 'lit/directives/ref';
|
import { createRef, Ref } from 'lit/directives/ref';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import { AutoMediaPluginType } from './embla-plugins/automedia.js';
|
||||||
import { FrigateCardCarousel } from './carousel.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 type { MediaShowInfo } from '../types.js';
|
||||||
import {
|
import {
|
||||||
dispatchExistingMediaShowInfoAsEvent,
|
dispatchExistingMediaShowInfoAsEvent,
|
||||||
@@ -14,10 +17,6 @@ import './next-prev-control.js';
|
|||||||
|
|
||||||
import mediaCarouselStyle from '../scss/media-carousel.scss';
|
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) =>
|
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`;
|
`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);
|
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
||||||
@@ -30,12 +29,21 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||||
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
||||||
protected _titleTimerID: number | null = null;
|
protected _titleTimerID: number | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the media on the selected slide. May be overridden to control when
|
* Play the media on the selected slide. May be overridden to control when
|
||||||
* autoplay should happen.
|
* autoplay should happen.
|
||||||
*/
|
*/
|
||||||
protected _autoplayHandler(): void {
|
protected _autoPlayHandler(): void {
|
||||||
(this._plugins['MediaAutoPlayPause'] as AutoMediaPluginType | undefined)?.play();
|
(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 {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
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._adaptiveHeightHandler);
|
||||||
this.addEventListener('frigate-card:media-show', this._titleHandler);
|
this.addEventListener('frigate-card:media-show', this._titleHandler);
|
||||||
}
|
}
|
||||||
@@ -79,7 +88,8 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
*/
|
*/
|
||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
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._adaptiveHeightHandler);
|
||||||
this.removeEventListener('frigate-card:media-show', this._titleHandler);
|
this.removeEventListener('frigate-card:media-show', this._titleHandler);
|
||||||
}
|
}
|
||||||
@@ -128,16 +138,16 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
if (!this._carousel) {
|
if (!this._carousel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const slide = this._carousel?.selectedScrollSnap()
|
||||||
|
if (slide !== undefined) {
|
||||||
const slides = this._carousel.slideNodes();
|
const slides = this._carousel.slideNodes();
|
||||||
const heights = this._carousel.slidesInView(true).map((index) => {
|
const height = slides[slide].getBoundingClientRect().height;
|
||||||
return slides[index].getBoundingClientRect().height;
|
if (height > 0) {
|
||||||
});
|
this._carousel.containerNode().style.maxHeight = `${height}px`;
|
||||||
const targetHeight = Math.max(...heights);
|
|
||||||
if (targetHeight > 0) {
|
|
||||||
this._carousel.containerNode().style.maxHeight = `${targetHeight}px`;
|
|
||||||
} else {
|
} else {
|
||||||
this._carousel.containerNode().style.removeProperty('max-height');
|
this._carousel.containerNode().style.removeProperty('max-height');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hack: This method attempts to measure the height of the slides in view in
|
// Hack: This method attempts to measure the height of the slides in view in
|
||||||
@@ -195,11 +205,10 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._carousel.slidesInView(true).forEach((slideIndex) => {
|
const slideIndex = this._carousel.selectedScrollSnap();
|
||||||
if (slideIndex in this._mediaShowInfo) {
|
if (slideIndex in this._mediaShowInfo) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
|
dispatchExistingMediaShowInfoAsEvent(this, this._mediaShowInfo[slideIndex]);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,7 +241,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
|||||||
// rejected upstream (empty 1x1 images will be rejected here).
|
// rejected upstream (empty 1x1 images will be rejected here).
|
||||||
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
|
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
|
||||||
this._mediaShowInfo[slideIndex] = mediaShowInfo;
|
this._mediaShowInfo[slideIndex] = mediaShowInfo;
|
||||||
if (this._carousel && this._carousel?.slidesInView(true).includes(slideIndex)) {
|
if (this._carousel && this._carousel?.selectedScrollSnap() == slideIndex) {
|
||||||
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -267,12 +267,20 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the media on the selected slide. May be overridden to control when
|
* Play the media on the selected slide.
|
||||||
* autoplay should happen.
|
|
||||||
*/
|
*/
|
||||||
protected _autoplayHandler(): void {
|
protected _autoPlayHandler(): void {
|
||||||
if (this.viewerConfig?.autoplay_clip) {
|
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({
|
AutoMediaPlugin({
|
||||||
playerSelector: 'frigate-card-ha-hls-player',
|
playerSelector: 'frigate-card-ha-hls-player',
|
||||||
autoplayWhenVisible: !!this.viewerConfig?.autoplay_clip,
|
autoPlayWhenVisible: !!this.viewerConfig?.autoplay_clip,
|
||||||
|
autoUnmuteWhenVisible: !!this.viewerConfig?.auto_unmute,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const CONF_EVENT_GALLERY_MIN_COLUMNS = `${CONF_EVENT_GALLERY}.min_columns
|
|||||||
export const CONF_EVENT_VIEWER = 'event_viewer' as const;
|
export const CONF_EVENT_VIEWER = 'event_viewer' as const;
|
||||||
export const CONF_EVENT_VIEWER_AUTOPLAY_CLIP =
|
export const CONF_EVENT_VIEWER_AUTOPLAY_CLIP =
|
||||||
`${CONF_EVENT_VIEWER}.autoplay_clip` as const;
|
`${CONF_EVENT_VIEWER}.autoplay_clip` 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_DRAGGABLE = `${CONF_EVENT_VIEWER}.draggable` as const;
|
||||||
export const CONF_EVENT_VIEWER_LAZY_LOAD = `${CONF_EVENT_VIEWER}.lazy_load` as const;
|
export const CONF_EVENT_VIEWER_LAZY_LOAD = `${CONF_EVENT_VIEWER}.lazy_load` as const;
|
||||||
export const CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE =
|
export const CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE =
|
||||||
@@ -49,6 +50,7 @@ export const CONF_EVENT_VIEWER_CONTROLS_TITLE_DURATION_SECONDS =
|
|||||||
`${CONF_EVENT_VIEWER}.controls.title.duration_seconds` as const;
|
`${CONF_EVENT_VIEWER}.controls.title.duration_seconds` as const;
|
||||||
|
|
||||||
export const CONF_LIVE = 'live' as const;
|
export const CONF_LIVE = 'live' as const;
|
||||||
|
export const CONF_LIVE_AUTO_UNMUTE = `${CONF_LIVE}.auto_unmute` as const;
|
||||||
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE =
|
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE =
|
||||||
`${CONF_LIVE}.controls.next_previous.style` as const;
|
`${CONF_LIVE}.controls.next_previous.style` as const;
|
||||||
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE =
|
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE =
|
||||||
|
|||||||
+16
-6
@@ -29,6 +29,7 @@ import {
|
|||||||
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
||||||
CONF_EVENT_GALLERY_MIN_COLUMNS,
|
CONF_EVENT_GALLERY_MIN_COLUMNS,
|
||||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||||
|
CONF_EVENT_VIEWER_AUTO_UNMUTE,
|
||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
||||||
@@ -39,6 +40,7 @@ import {
|
|||||||
CONF_EVENT_VIEWER_LAZY_LOAD,
|
CONF_EVENT_VIEWER_LAZY_LOAD,
|
||||||
CONF_IMAGE_REFRESH_SECONDS,
|
CONF_IMAGE_REFRESH_SECONDS,
|
||||||
CONF_IMAGE_SRC,
|
CONF_IMAGE_SRC,
|
||||||
|
CONF_LIVE_AUTO_UNMUTE,
|
||||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
||||||
@@ -240,8 +242,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
protected _titleModes = new Map([
|
protected _titleModes = new Map([
|
||||||
['', ''],
|
['', ''],
|
||||||
['none', localize('config.event_viewer.controls.title.modes.none')],
|
['none', localize('config.event_viewer.controls.title.modes.none')],
|
||||||
['popup-top-left', localize('config.event_viewer.controls.title.modes.popup-top-left')],
|
[
|
||||||
['popup-top-right', localize('config.event_viewer.controls.title.modes.popup-top-right')],
|
'popup-top-left',
|
||||||
|
localize('config.event_viewer.controls.title.modes.popup-top-left'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'popup-top-right',
|
||||||
|
localize('config.event_viewer.controls.title.modes.popup-top-right'),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'popup-bottom-left',
|
'popup-bottom-left',
|
||||||
localize('config.event_viewer.controls.title.modes.popup-bottom-left'),
|
localize('config.event_viewer.controls.title.modes.popup-bottom-left'),
|
||||||
@@ -757,6 +765,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderSwitch(CONF_LIVE_DRAGGABLE, defaults.live.draggable)}
|
${this._renderSwitch(CONF_LIVE_DRAGGABLE, defaults.live.draggable)}
|
||||||
${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
|
${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
|
||||||
${this._renderSwitch(CONF_LIVE_LAZY_UNLOAD, defaults.live.lazy_unload)}
|
${this._renderSwitch(CONF_LIVE_LAZY_UNLOAD, defaults.live.lazy_unload)}
|
||||||
|
${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)}
|
||||||
${this._renderDropdown(
|
${this._renderDropdown(
|
||||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||||
this._liveNextPreviousControlStyles,
|
this._liveNextPreviousControlStyles,
|
||||||
@@ -771,10 +780,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
this._thumbnailMedias,
|
this._thumbnailMedias,
|
||||||
)}
|
)}
|
||||||
${this._renderStringInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE)}
|
${this._renderStringInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE)}
|
||||||
${this._renderDropdown(
|
${this._renderDropdown(CONF_LIVE_CONTROLS_TITLE_MODE, this._titleModes)}
|
||||||
CONF_LIVE_CONTROLS_TITLE_MODE,
|
|
||||||
this._titleModes,
|
|
||||||
)}
|
|
||||||
${this._renderStringInput(
|
${this._renderStringInput(
|
||||||
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
||||||
'number',
|
'number',
|
||||||
@@ -801,6 +807,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||||
defaults.event_viewer.autoplay_clip,
|
defaults.event_viewer.autoplay_clip,
|
||||||
)}
|
)}
|
||||||
|
${this._renderSwitch(
|
||||||
|
CONF_EVENT_VIEWER_AUTO_UNMUTE,
|
||||||
|
defaults.event_viewer.auto_unmute,
|
||||||
|
)}
|
||||||
${this._renderSwitch(
|
${this._renderSwitch(
|
||||||
CONF_EVENT_VIEWER_DRAGGABLE,
|
CONF_EVENT_VIEWER_DRAGGABLE,
|
||||||
defaults.event_viewer.draggable,
|
defaults.event_viewer.draggable,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
},
|
},
|
||||||
"event_viewer": {
|
"event_viewer": {
|
||||||
"autoplay_clip": "Autoplay clips",
|
"autoplay_clip": "Autoplay clips",
|
||||||
|
"auto_unmute": "Automatically unmute media",
|
||||||
"draggable": "Event Viewer can be dragged/swiped",
|
"draggable": "Event Viewer can be dragged/swiped",
|
||||||
"lazy_load": "Event Viewer media is lazily loaded in carousel",
|
"lazy_load": "Event Viewer media is lazily loaded in carousel",
|
||||||
"controls": {
|
"controls": {
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
"draggable": "Live cameras view can be dragged/swiped",
|
"draggable": "Live cameras view can be dragged/swiped",
|
||||||
"lazy_load": "Live cameras are lazily loaded",
|
"lazy_load": "Live cameras are lazily loaded",
|
||||||
"lazy_unload": "Live cameras are lazily unloaded",
|
"lazy_unload": "Live cameras are lazily unloaded",
|
||||||
|
"auto_unmute": "Automatically unmute live cameras",
|
||||||
"controls": {
|
"controls": {
|
||||||
"next_previous": {
|
"next_previous": {
|
||||||
"style": "Live view next & previous control style",
|
"style": "Live view next & previous control style",
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
this._playerRef.value?.pause();
|
this._playerRef.value?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mute the video.
|
||||||
|
*/
|
||||||
|
public mute(): void {
|
||||||
|
this.muted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmute the video.
|
||||||
|
*/
|
||||||
|
public unmute(): void {
|
||||||
|
this.muted = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Master render method.
|
* Master render method.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
@@ -82,7 +96,6 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
? html`
|
? html`
|
||||||
<frigate-card-ha-hls-player
|
<frigate-card-ha-hls-player
|
||||||
${ref(this._playerRef)}
|
${ref(this._playerRef)}
|
||||||
autoplay
|
|
||||||
playsinline
|
playsinline
|
||||||
.allowExoPlayer=${this.allowExoPlayer}
|
.allowExoPlayer=${this.allowExoPlayer}
|
||||||
.muted=${this.muted}
|
.muted=${this.muted}
|
||||||
|
|||||||
@@ -34,6 +34,20 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
this._videoRef.value?.pause();
|
this._videoRef.value?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mute the video.
|
||||||
|
*/
|
||||||
|
public mute(): void {
|
||||||
|
this.muted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unmute the video.
|
||||||
|
*/
|
||||||
|
public unmute(): void {
|
||||||
|
this.muted = false;
|
||||||
|
}
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// Minor modifications from:
|
// Minor modifications from:
|
||||||
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-hls-player.ts
|
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-hls-player.ts
|
||||||
@@ -42,7 +56,6 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
return html`
|
return html`
|
||||||
<video
|
<video
|
||||||
${ref(this._videoRef)}
|
${ref(this._videoRef)}
|
||||||
?autoplay=${this.autoPlay}
|
|
||||||
.muted=${this.muted}
|
.muted=${this.muted}
|
||||||
?playsinline=${this.playsInline}
|
?playsinline=${this.playsInline}
|
||||||
?controls=${this.controls}
|
?controls=${this.controls}
|
||||||
|
|||||||
@@ -455,6 +455,7 @@ export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
|
|||||||
* Live view configuration section.
|
* Live view configuration section.
|
||||||
*/
|
*/
|
||||||
const liveConfigDefault = {
|
const liveConfigDefault = {
|
||||||
|
auto_unmute: false,
|
||||||
preload: false,
|
preload: false,
|
||||||
lazy_load: true,
|
lazy_load: true,
|
||||||
lazy_unload: false,
|
lazy_unload: false,
|
||||||
@@ -504,6 +505,7 @@ export type JSMPEGConfig = z.infer<typeof jsmpegConfigSchema>;
|
|||||||
|
|
||||||
const liveOverridableConfigSchema = z
|
const liveOverridableConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
auto_unmute: z.boolean().default(liveConfigDefault.auto_unmute),
|
||||||
webrtc: webrtcConfigSchema,
|
webrtc: webrtcConfigSchema,
|
||||||
jsmpeg: jsmpegConfigSchema,
|
jsmpeg: jsmpegConfigSchema,
|
||||||
controls: z
|
controls: z
|
||||||
@@ -603,6 +605,7 @@ export type MenuConfig = z.infer<typeof menuConfigSchema>;
|
|||||||
*/
|
*/
|
||||||
const viewerConfigDefault = {
|
const viewerConfigDefault = {
|
||||||
autoplay_clip: true,
|
autoplay_clip: true,
|
||||||
|
auto_unmute: true,
|
||||||
lazy_load: true,
|
lazy_load: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
controls: {
|
controls: {
|
||||||
@@ -633,6 +636,7 @@ export type ViewerNextPreviousControlConfig = z.infer<
|
|||||||
const viewerConfigSchema = z
|
const viewerConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
autoplay_clip: z.boolean().default(viewerConfigDefault.autoplay_clip),
|
autoplay_clip: z.boolean().default(viewerConfigDefault.autoplay_clip),
|
||||||
|
auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute),
|
||||||
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
|
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
|
||||||
draggable: z.boolean().default(viewerConfigDefault.draggable),
|
draggable: z.boolean().default(viewerConfigDefault.draggable),
|
||||||
controls: z
|
controls: z
|
||||||
@@ -823,6 +827,8 @@ export interface StateParameters {
|
|||||||
export interface FrigateCardMediaPlayer {
|
export interface FrigateCardMediaPlayer {
|
||||||
play(): void;
|
play(): void;
|
||||||
pause(): void;
|
pause(): void;
|
||||||
|
mute(): void;
|
||||||
|
unmute(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user