Automatically mute if autoplay is blocked.

This commit is contained in:
Dermot Duffy
2023-02-18 14:28:59 -08:00
parent bae115bf5d
commit 732099b6d0
11 changed files with 103 additions and 174 deletions
+12 -4
View File
@@ -8,7 +8,11 @@ import {
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import liveMSEStyle from '../../scss/live-go2rtc.scss';
import { CameraConfig, ExtendedHomeAssistant } from '../../types.js';
import {
CameraConfig,
ExtendedHomeAssistant,
FrigateCardMediaPlayer,
} from '../../types.js';
import '../image.js';
import {
hideMediaControlsTemporarily,
@@ -51,7 +55,7 @@ class FrigateCardGo2RTCPlayer extends VideoRTC {
}
@customElement('frigate-card-live-go2rtc')
export class FrigateCardGo2RTC extends LitElement {
export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPlayer {
// Not an reactive property to avoid resetting the video.
public hass?: ExtendedHomeAssistant;
@@ -63,8 +67,8 @@ export class FrigateCardGo2RTC extends LitElement {
protected _player?: FrigateCardGo2RTCPlayer;
public play(): void {
this._player?.video?.play();
public async play(): Promise<void> {
return this._player?.video?.play();
}
public pause(): void {
@@ -83,6 +87,10 @@ export class FrigateCardGo2RTC extends LitElement {
}
}
public isMuted(): boolean {
return this._player?.video.muted ?? true;
}
public seek(seconds: number): void {
if (this._player?.video) {
this._player.video.currentTime = seconds;
+7 -26
View File
@@ -9,9 +9,8 @@ import '../../patches/ha-camera-stream';
import '../../patches/ha-hls-player.js';
import '../../patches/ha-web-rtc-player.ts';
@customElement('frigate-card-live-ha')
export class FrigateCardLiveHA extends LitElement {
export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPlayer {
@property({ attribute: false })
public hass?: HomeAssistant;
@@ -20,45 +19,30 @@ export class FrigateCardLiveHA extends LitElement {
protected _playerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
/**
* Play the video.
*/
public play(): void {
this._playerRef.value?.play();
public async play(): Promise<void> {
return this._playerRef.value?.play();
}
/**
* Pause the video.
*/
public pause(): void {
this._playerRef.value?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
this._playerRef.value?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this._playerRef.value?.unmute();
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return this._playerRef.value?.isMuted() ?? true;
}
public seek(seconds: number): void {
this._playerRef.value?.seek(seconds);
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
if (!this.hass) {
return;
@@ -79,9 +63,6 @@ export class FrigateCardLiveHA extends LitElement {
</frigate-card-ha-camera-stream>`;
}
/**
* Get styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(liveHAStyle);
}
+7 -25
View File
@@ -2,12 +2,12 @@ import { HomeAssistant } from 'custom-card-helpers';
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import liveImageStyle from '../../scss/live-image.scss';
import { CameraConfig, LiveImageConfig } from '../../types.js';
import { CameraConfig, FrigateCardMediaPlayer, LiveImageConfig } from '../../types.js';
import { getStateObjOrDispatchError } from './live.js';
import '../image.js';
@customElement('frigate-card-live-image')
export class FrigateCardLiveImage extends LitElement {
export class FrigateCardLiveImage extends LitElement implements FrigateCardMediaPlayer {
@property({ attribute: false })
public hass?: HomeAssistant;
@@ -20,46 +20,31 @@ export class FrigateCardLiveImage extends LitElement {
@state()
protected _playing = true;
/**
* Play the video.
*/
public play(): void {
public async play(): Promise<void> {
this._playing = true;
}
/**
* Pause the video.
*/
public pause(): void {
this._playing = false;
}
/**
* Mute the video.
*/
public mute(): void {
// Not implemented.
}
/**
* Unmute the video.
*/
public unmute(): void {
// Not implemented.
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public seek(_seconds: number): void {
// Not implemented.
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
if (!this.hass || !this.liveImageConfig) {
return;
@@ -80,9 +65,6 @@ export class FrigateCardLiveImage extends LitElement {
</frigate-card-image>`;
}
/**
* Get styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(liveImageStyle);
}
+8 -20
View File
@@ -9,6 +9,7 @@ import {
CameraConfig,
CardWideConfig,
ExtendedHomeAssistant,
FrigateCardMediaPlayer,
JSMPEGConfig,
} from '../../types.js';
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
@@ -24,7 +25,7 @@ const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
const JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS = 1 * 60 * 60;
@customElement('frigate-card-live-jsmpeg')
export class FrigateCardLiveJSMPEG extends LitElement {
export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMediaPlayer {
@property({ attribute: false })
public cameraConfig?: CameraConfig;
@@ -43,24 +44,14 @@ export class FrigateCardLiveJSMPEG extends LitElement {
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
protected _refreshPlayerTimerID?: number;
/**
* Play the video.
*/
public play(): void {
this._jsmpegVideoPlayer?.play();
public async play(): Promise<void> {
return this._jsmpegVideoPlayer?.play();
}
/**
* Pause the video.
*/
public pause(): void {
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) {
@@ -68,10 +59,6 @@ export class FrigateCardLiveJSMPEG extends LitElement {
}
}
/**
* 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) {
@@ -79,9 +66,10 @@ export class FrigateCardLiveJSMPEG extends LitElement {
}
}
/**
* Seek the video (unsupported).
*/
public isMuted(): boolean {
return this._jsmpegVideoPlayer ? this._jsmpegVideoPlayer.player.volume === 0 : true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public seek(_seconds: number): void {
// JSMPEG does not support seeking.
+15 -25
View File
@@ -8,18 +8,25 @@ import {
CameraConfig,
CardWideConfig,
FrigateCardError,
FrigateCardMediaPlayer,
WebRTCCardConfig,
} from '../../types.js';
import { contentsChanged } from '../../utils/basic.js';
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
import { renderTask } from '../../utils/task.js';
import { hideMediaControlsTemporarily, MEDIA_LOAD_CONTROLS_HIDE_SECONDS } from '../../utils/media.js';
import {
hideMediaControlsTemporarily,
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
} from '../../utils/media.js';
// Create a wrapper for AlexxIT's WebRTC card
// - https://github.com/AlexxIT/WebRTC
@customElement('frigate-card-live-webrtc-card')
export class FrigateCardLiveWebRTCCard extends LitElement {
export class FrigateCardLiveWebRTCCard
extends LitElement
implements FrigateCardMediaPlayer
{
@property({ attribute: false, hasChanged: contentsChanged })
public webRTCConfig?: WebRTCCardConfig;
@@ -34,29 +41,14 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
// A task to await the load of the WebRTC component.
protected _webrtcTask = new Task(this, this._getWebRTCCardElement, () => [1]);
/**
* Play the video.
*/
public play(): void {
this._getPlayer()
?.play()
.catch(() => {
// WebRTC appears to generate additional spurious load events, which may
// result in loads after a play() call, which causes the browser to spam
// the logs unless the promise rejection is handled here.
});
public async play(): Promise<void> {
return this._getPlayer()?.play();
}
/**
* Pause the video.
*/
public pause(): void {
this._getPlayer()?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
const player = this._getPlayer();
if (player) {
@@ -64,9 +56,6 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
}
}
/**
* Unmute the video.
*/
public unmute(): void {
const player = this._getPlayer();
if (player) {
@@ -74,9 +63,10 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
}
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return this._getPlayer()?.muted ?? true;
}
public seek(seconds: number): void {
const player = this._getPlayer();
if (player) {
+20 -21
View File
@@ -515,9 +515,8 @@ export class FrigateCardLiveCarousel extends LitElement {
<frigate-card-live-provider
?disabled=${this.liveConfig.lazy_load}
.cameraConfig=${cameraConfig}
.cameraEndpoints=${guard(
[this.cameraManager],
() => this.cameraManager?.getCameraEndpoints(cameraID),
.cameraEndpoints=${guard([this.cameraManager], () =>
this.cameraManager?.getCameraEndpoints(cameraID),
)}
.label=${cameraMetadata?.title ?? ''}
.liveConfig=${config}
@@ -664,7 +663,10 @@ export class FrigateCardLiveCarousel extends LitElement {
}
@customElement('frigate-card-live-provider')
export class FrigateCardLiveProvider extends LitElement {
export class FrigateCardLiveProvider
extends LitElement
implements FrigateCardMediaPlayer
{
@property({ attribute: false })
public hass?: ExtendedHomeAssistant;
@@ -694,37 +696,34 @@ export class FrigateCardLiveProvider extends LitElement {
protected _providerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
/**
* Play the video.
*/
public play(): void {
this._providerRef.value?.play();
public async play(): Promise<void> {
// If the play call fails, and the media is not already muted, mute it first
// and then try again. This works around some browsers that prevent
// auto-play unless the video is muted.
this._providerRef.value?.play().catch((ev) => {
if (ev.name === 'NotAllowedError' && !this.isMuted()) {
this.mute();
this._providerRef.value?.play().catch();
}
});
}
/**
* Pause the video.
*/
public pause(): void {
this._providerRef.value?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
this._providerRef.value?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this._providerRef.value?.unmute();
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return this._providerRef.value?.isMuted() ?? true;
}
public seek(seconds: number): void {
this._providerRef.value?.seek(seconds);
}
+10 -15
View File
@@ -41,7 +41,10 @@ customElements.whenDefined('ha-camera-stream').then(() => {
@customElement('frigate-card-ha-camera-stream')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FrigateCardHaCameraStream extends customElements.get('ha-camera-stream') {
class FrigateCardHaCameraStream
extends customElements.get('ha-camera-stream')
implements FrigateCardMediaPlayer
{
// Due to an obscure behavior when this card is casted, this element needs
// to use query rather than the ref directive to find the player.
@query('#player')
@@ -52,34 +55,26 @@ customElements.whenDefined('ha-camera-stream').then(() => {
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-camera-stream.ts
// ========================================================================================
/**
* Play the video.
*/
public play(): void {
this._player?.play();
public async play(): Promise<void> {
return this._player?.play();
}
/**
* Pause the video.
*/
public pause(): void {
this._player?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
this._player?.mute();
}
/**
* Unmute the video.
*/
public unmute(): void {
this._player?.unmute();
}
public isMuted(): boolean {
return this._player?.isMuted() ?? true;
}
/**
* Seek the video (unsupported).
*/
+11 -18
View File
@@ -19,33 +19,28 @@ import {
hideMediaControlsTemporarily,
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
} from '../utils/media.js';
import { FrigateCardMediaPlayer } from '../types.js';
customElements.whenDefined('ha-hls-player').then(() => {
@customElement('frigate-card-ha-hls-player')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FrigateCardHaHlsPlayer extends customElements.get('ha-hls-player') {
class FrigateCardHaHlsPlayer
extends customElements.get('ha-hls-player')
implements FrigateCardMediaPlayer
{
// Due to an obscure behavior when this card is casted, this element needs
// to use query rather than the ref directive to find the player.
@query('#video')
protected _video: HTMLVideoElement;
/**
* Play the video.
*/
public play(): void {
this._video?.play();
public async play(): Promise<void> {
return this._video?.play();
}
/**
* Pause the video.
*/
public pause(): void {
this._video?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
// The muted property is only for the initial muted state. Must explicitly
// set the muted on the video player to make the change dynamic.
@@ -54,9 +49,6 @@ customElements.whenDefined('ha-hls-player').then(() => {
}
}
/**
* Unmute the video.
*/
public unmute(): void {
// See note in mute().
if (this._video) {
@@ -64,9 +56,10 @@ customElements.whenDefined('ha-hls-player').then(() => {
}
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return this._video?.muted() ?? true;
}
public seek(seconds: number): void {
if (this._video) {
hideMediaControlsTemporarily(this._video);
+10 -18
View File
@@ -23,29 +23,23 @@ import liveHAComponentsStyle from '../scss/live-ha-components.scss';
customElements.whenDefined('ha-web-rtc-player').then(() => {
@customElement('frigate-card-ha-web-rtc-player')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class FrigateCardHaWebRtcPlayer extends customElements.get('ha-web-rtc-player') {
class FrigateCardHaWebRtcPlayer
extends customElements.get('ha-web-rtc-player')
implements FrigateCardMediaPlayer
{
// Due to an obscure behavior when this card is casted, this element needs
// to use query rather than the ref directive to find the player.
@query('#remote-stream')
protected _video: HTMLVideoElement;
/**
* Play the video.
*/
public play(): void {
this._video?.play();
public async play(): Promise<void> {
return this._video?.play();
}
/**
* Pause the video.
*/
public pause(): void {
this._video?.pause();
}
/**
* Mute the video.
*/
public mute(): void {
// The muted property is only for the initial muted state. Must explicitly
// set the muted on the video player to make the change dynamic.
@@ -54,9 +48,6 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
}
}
/**
* Unmute the video.
*/
public unmute(): void {
// See note in mute().
if (this._video) {
@@ -64,9 +55,10 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
}
}
/**
* Seek the video.
*/
public isMuted(): boolean {
return this._video?.muted ?? true;
}
public seek(seconds: number): void {
if (this._video) {
this._video.currentTime = seconds;
+2 -1
View File
@@ -1347,10 +1347,11 @@ export interface StateParameters {
}
export interface FrigateCardMediaPlayer {
play(): void;
play(): Promise<void>;
pause(): void;
mute(): void;
unmute(): void;
isMuted(): boolean;
seek(seconds: number): void;
}