Merge pull request #313 from dermotduffy/unmute
Add auto unmute support
This commit is contained in:
@@ -191,6 +191,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_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute live cameras.|
|
||||
| `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` | `false` | :heavy_multiplication_x: | Whether or not to lazily **un**load lazyily-loaded cameras in the camera carousel, or just leave the camera paused. Setting this to `true` will cause cameras to be entirely unloaded when they are no longer visible (either because the carousel has scrolled past them, or because the document has been marked hidden/inactive by the browser). 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. |
|
||||
| `draggable` | `true` | :heavy_multiplication_x: | Whether or not the live carousel can be dragged left or right, via touch/swipe and mouse dragging. |
|
||||
@@ -299,6 +300,7 @@ event_viewer:
|
||||
| Option | Default | Overridable | Description |
|
||||
| - | - | - | - |
|
||||
| `autoplay_clip` | `true` | :heavy_multiplication_x: | Whether or not to autoplay clips.|
|
||||
| `auto_unmute` | `false` | :heavy_multiplication_x: | Whether or not to automatically unmute clips.|
|
||||
| `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. |
|
||||
| `controls` | | :heavy_multiplication_x: | Configuration for the event viewer. See below. |
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmute 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -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_AUTOPLAY_CLIP =
|
||||
`${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_LAZY_LOAD = `${CONF_EVENT_VIEWER}.lazy_load` as const;
|
||||
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;
|
||||
|
||||
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 =
|
||||
`${CONF_LIVE}.controls.next_previous.style` as const;
|
||||
export const CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE =
|
||||
|
||||
+20
-10
@@ -29,6 +29,7 @@ import {
|
||||
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
|
||||
CONF_EVENT_GALLERY_MIN_COLUMNS,
|
||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||
CONF_EVENT_VIEWER_AUTO_UNMUTE,
|
||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||
CONF_EVENT_VIEWER_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
CONF_EVENT_VIEWER_CONTROLS_THUMBNAILS_MODE,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
CONF_EVENT_VIEWER_LAZY_LOAD,
|
||||
CONF_IMAGE_REFRESH_SECONDS,
|
||||
CONF_IMAGE_SRC,
|
||||
CONF_LIVE_AUTO_UNMUTE,
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
|
||||
@@ -240,8 +242,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
protected _titleModes = new Map([
|
||||
['', ''],
|
||||
['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',
|
||||
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_LAZY_LOAD, defaults.live.lazy_load)}
|
||||
${this._renderSwitch(CONF_LIVE_LAZY_UNLOAD, defaults.live.lazy_unload)}
|
||||
${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)}
|
||||
${this._renderDropdown(
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
this._liveNextPreviousControlStyles,
|
||||
@@ -771,14 +780,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
this._thumbnailMedias,
|
||||
)}
|
||||
${this._renderStringInput(CONF_LIVE_CONTROLS_THUMBNAILS_SIZE)}
|
||||
${this._renderDropdown(
|
||||
CONF_LIVE_CONTROLS_TITLE_MODE,
|
||||
this._titleModes,
|
||||
)}
|
||||
${this._renderStringInput(
|
||||
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
||||
'number',
|
||||
)}
|
||||
${this._renderDropdown(CONF_LIVE_CONTROLS_TITLE_MODE, this._titleModes)}
|
||||
${this._renderStringInput(
|
||||
CONF_LIVE_CONTROLS_TITLE_DURATION_SECONDS,
|
||||
'number',
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
@@ -801,6 +807,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
CONF_EVENT_VIEWER_AUTOPLAY_CLIP,
|
||||
defaults.event_viewer.autoplay_clip,
|
||||
)}
|
||||
${this._renderSwitch(
|
||||
CONF_EVENT_VIEWER_AUTO_UNMUTE,
|
||||
defaults.event_viewer.auto_unmute,
|
||||
)}
|
||||
${this._renderSwitch(
|
||||
CONF_EVENT_VIEWER_DRAGGABLE,
|
||||
defaults.event_viewer.draggable,
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
},
|
||||
"event_viewer": {
|
||||
"autoplay_clip": "Autoplay clips",
|
||||
"auto_unmute": "Automatically unmute media",
|
||||
"draggable": "Event Viewer can be dragged/swiped",
|
||||
"lazy_load": "Event Viewer media is lazily loaded in carousel",
|
||||
"controls": {
|
||||
@@ -93,6 +94,7 @@
|
||||
"draggable": "Live cameras view can be dragged/swiped",
|
||||
"lazy_load": "Live cameras are lazily loaded",
|
||||
"lazy_unload": "Live cameras are lazily unloaded",
|
||||
"auto_unmute": "Automatically unmute live cameras",
|
||||
"controls": {
|
||||
"next_previous": {
|
||||
"style": "Live view next & previous control style",
|
||||
|
||||
@@ -56,6 +56,20 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
||||
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.
|
||||
* @returns A rendered template.
|
||||
@@ -82,7 +96,6 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
||||
? html`
|
||||
<frigate-card-ha-hls-player
|
||||
${ref(this._playerRef)}
|
||||
autoplay
|
||||
playsinline
|
||||
.allowExoPlayer=${this.allowExoPlayer}
|
||||
.muted=${this.muted}
|
||||
|
||||
@@ -34,6 +34,20 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
||||
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:
|
||||
// - 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`
|
||||
<video
|
||||
${ref(this._videoRef)}
|
||||
?autoplay=${this.autoPlay}
|
||||
.muted=${this.muted}
|
||||
?playsinline=${this.playsInline}
|
||||
?controls=${this.controls}
|
||||
|
||||
@@ -455,6 +455,7 @@ export type TitleControlConfig = z.infer<typeof titleControlConfigSchema>;
|
||||
* Live view configuration section.
|
||||
*/
|
||||
const liveConfigDefault = {
|
||||
auto_unmute: false,
|
||||
preload: false,
|
||||
lazy_load: true,
|
||||
lazy_unload: false,
|
||||
@@ -550,6 +551,7 @@ const liveOverridableConfigSchema = z
|
||||
const liveConfigSchema = liveOverridableConfigSchema
|
||||
.extend({
|
||||
// Non-overrideable parameters.
|
||||
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.boolean().default(liveConfigDefault.lazy_unload),
|
||||
@@ -603,6 +605,7 @@ export type MenuConfig = z.infer<typeof menuConfigSchema>;
|
||||
*/
|
||||
const viewerConfigDefault = {
|
||||
autoplay_clip: true,
|
||||
auto_unmute: false,
|
||||
lazy_load: true,
|
||||
draggable: true,
|
||||
controls: {
|
||||
@@ -633,6 +636,7 @@ export type ViewerNextPreviousControlConfig = z.infer<
|
||||
const viewerConfigSchema = z
|
||||
.object({
|
||||
autoplay_clip: z.boolean().default(viewerConfigDefault.autoplay_clip),
|
||||
auto_unmute: z.boolean().default(viewerConfigDefault.auto_unmute),
|
||||
lazy_load: z.boolean().default(viewerConfigDefault.lazy_load),
|
||||
draggable: z.boolean().default(viewerConfigDefault.draggable),
|
||||
controls: z
|
||||
@@ -823,6 +827,8 @@ export interface StateParameters {
|
||||
export interface FrigateCardMediaPlayer {
|
||||
play(): void;
|
||||
pause(): void;
|
||||
mute(): void;
|
||||
unmute(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user