Automatically mute if autoplay is blocked.
This commit is contained in:
@@ -352,7 +352,7 @@ See the [fully expanded live configuration example](#config-expanded-live) for h
|
|||||||
| `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. |
|
| `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_play` | `all` | :heavy_multiplication_x: | Whether to automatically play live camera feeds. `never` will never automatically play, `selected` will automatically play when a camera is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case). Some live providers (e.g. `webrtc-card`, `jsmpeg`) do not support the prevention of automatic play on initial load, but should still respect the value of this flag on play-after-pause.|
|
| `auto_play` | `all` | :heavy_multiplication_x: | Whether to automatically play live camera feeds. `never` will never automatically play, `selected` will automatically play when a camera is selected in the carousel, `visible` will automatically play when the browser/tab becomes visible or `all` on any opportunity to automatically play (i.e. either case). Some live providers (e.g. `webrtc-card`, `jsmpeg`) do not support the prevention of automatic play on initial load, but should still respect the value of this flag on play-after-pause.|
|
||||||
| `auto_pause` | `never` | :heavy_multiplication_x: | Whether to automatically pause live camera feeds. `never` will never automatically pause, `unselected` will automatically pause when a camera is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case). **Caution**: Some live providers (e.g. `jsmpeg`) may not offer human-accessible means to resume play if it is paused, unless the `auto_play` option (above) is used.|
|
| `auto_pause` | `never` | :heavy_multiplication_x: | Whether to automatically pause live camera feeds. `never` will never automatically pause, `unselected` will automatically pause when a camera is unselected in the carousel, `hidden` will automatically pause when the browser/tab becomes hidden or `all` on any opportunity to automatically pause (i.e. either case). **Caution**: Some live providers (e.g. `jsmpeg`) may not offer human-accessible means to resume play if it is paused, unless the `auto_play` option (above) is used.|
|
||||||
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute live camera feeds. `never` will never automatically mute, `unselected` will automatically mute when a camera is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case).|
|
| `auto_mute` | `all` | :heavy_multiplication_x: | Whether to automatically mute live camera feeds. `never` will never automatically mute, `unselected` will automatically mute when a camera is unselected in the carousel, `hidden` will automatically mute when the browser/tab becomes hidden or `all` on any opportunity to automatically mute (i.e. either case). Note that if `auto_play` is enabled, the stream may mute itself automatically in order to honor the `auto_play` setting, as some browsers will not auto play media that is unmuted -- that is to say, where necessary, the `auto_play` parameter will take priority over the `auto_mute` parameter.|
|
||||||
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute live camera feeds. `never` will never automatically unmute, `selected` will automatically unmute when a camera is unselected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `all` on any opportunity to automatically unmute (i.e. either case).|
|
| `auto_unmute` | `never` | :heavy_multiplication_x: | Whether to automatically unmute live camera feeds. `never` will never automatically unmute, `selected` will automatically unmute when a camera is unselected in the carousel, `visible` will automatically unmute when the browser/tab becomes visible or `all` on any opportunity to automatically unmute (i.e. either case).|
|
||||||
| `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_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` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab becomes hidden or `all` on any opportunity to lazily unload (i.e. either case). 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. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
|
| `lazy_unload` | `never` | :heavy_multiplication_x: | When to lazily **un**load lazyily-loaded cameras. `never` will never lazily-unload, `unselected` will lazy-unload a camera when it is unselected in the carousel, `hidden` will lazy-unload all cameras when the browser/tab becomes hidden or `all` on any opportunity to lazily unload (i.e. either case). 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. Some live providers (e.g. `webrtc-card`) implement their own lazy unloading independently which may occur regardless of the value of this setting.|
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import {
|
|||||||
} from 'lit';
|
} from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import liveMSEStyle from '../../scss/live-go2rtc.scss';
|
import liveMSEStyle from '../../scss/live-go2rtc.scss';
|
||||||
import { CameraConfig, ExtendedHomeAssistant } from '../../types.js';
|
import {
|
||||||
|
CameraConfig,
|
||||||
|
ExtendedHomeAssistant,
|
||||||
|
FrigateCardMediaPlayer,
|
||||||
|
} from '../../types.js';
|
||||||
import '../image.js';
|
import '../image.js';
|
||||||
import {
|
import {
|
||||||
hideMediaControlsTemporarily,
|
hideMediaControlsTemporarily,
|
||||||
@@ -51,7 +55,7 @@ class FrigateCardGo2RTCPlayer extends VideoRTC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@customElement('frigate-card-live-go2rtc')
|
@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.
|
// Not an reactive property to avoid resetting the video.
|
||||||
public hass?: ExtendedHomeAssistant;
|
public hass?: ExtendedHomeAssistant;
|
||||||
|
|
||||||
@@ -63,8 +67,8 @@ export class FrigateCardGo2RTC extends LitElement {
|
|||||||
|
|
||||||
protected _player?: FrigateCardGo2RTCPlayer;
|
protected _player?: FrigateCardGo2RTCPlayer;
|
||||||
|
|
||||||
public play(): void {
|
public async play(): Promise<void> {
|
||||||
this._player?.video?.play();
|
return this._player?.video?.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
public pause(): void {
|
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 {
|
public seek(seconds: number): void {
|
||||||
if (this._player?.video) {
|
if (this._player?.video) {
|
||||||
this._player.video.currentTime = seconds;
|
this._player.video.currentTime = seconds;
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import '../../patches/ha-camera-stream';
|
|||||||
import '../../patches/ha-hls-player.js';
|
import '../../patches/ha-hls-player.js';
|
||||||
import '../../patches/ha-web-rtc-player.ts';
|
import '../../patches/ha-web-rtc-player.ts';
|
||||||
|
|
||||||
|
|
||||||
@customElement('frigate-card-live-ha')
|
@customElement('frigate-card-live-ha')
|
||||||
export class FrigateCardLiveHA extends LitElement {
|
export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPlayer {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
@@ -20,45 +19,30 @@ export class FrigateCardLiveHA extends LitElement {
|
|||||||
|
|
||||||
protected _playerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
|
protected _playerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._playerRef.value?.play();
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._playerRef.value?.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._playerRef.value?.pause();
|
this._playerRef.value?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
this._playerRef.value?.mute();
|
this._playerRef.value?.mute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmute the video.
|
|
||||||
*/
|
|
||||||
public unmute(): void {
|
public unmute(): void {
|
||||||
this._playerRef.value?.unmute();
|
this._playerRef.value?.unmute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return this._playerRef.value?.isMuted() ?? true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public seek(seconds: number): void {
|
public seek(seconds: number): void {
|
||||||
this._playerRef.value?.seek(seconds);
|
this._playerRef.value?.seek(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Master render method.
|
|
||||||
* @returns A rendered template.
|
|
||||||
*/
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
@@ -79,9 +63,6 @@ export class FrigateCardLiveHA extends LitElement {
|
|||||||
</frigate-card-ha-camera-stream>`;
|
</frigate-card-ha-camera-stream>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get styles.
|
|
||||||
*/
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return unsafeCSS(liveHAStyle);
|
return unsafeCSS(liveHAStyle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { HomeAssistant } from 'custom-card-helpers';
|
|||||||
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import liveImageStyle from '../../scss/live-image.scss';
|
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 { getStateObjOrDispatchError } from './live.js';
|
||||||
import '../image.js';
|
import '../image.js';
|
||||||
|
|
||||||
@customElement('frigate-card-live-image')
|
@customElement('frigate-card-live-image')
|
||||||
export class FrigateCardLiveImage extends LitElement {
|
export class FrigateCardLiveImage extends LitElement implements FrigateCardMediaPlayer {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
@@ -20,46 +20,31 @@ export class FrigateCardLiveImage extends LitElement {
|
|||||||
@state()
|
@state()
|
||||||
protected _playing = true;
|
protected _playing = true;
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._playing = true;
|
this._playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._playing = false;
|
this._playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
// Not implemented.
|
// Not implemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmute the video.
|
|
||||||
*/
|
|
||||||
public unmute(): void {
|
public unmute(): void {
|
||||||
// Not implemented.
|
// Not implemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public seek(_seconds: number): void {
|
public seek(_seconds: number): void {
|
||||||
// Not implemented.
|
// Not implemented.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Master render method.
|
|
||||||
* @returns A rendered template.
|
|
||||||
*/
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
if (!this.hass || !this.liveImageConfig) {
|
if (!this.hass || !this.liveImageConfig) {
|
||||||
return;
|
return;
|
||||||
@@ -80,9 +65,6 @@ export class FrigateCardLiveImage extends LitElement {
|
|||||||
</frigate-card-image>`;
|
</frigate-card-image>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get styles.
|
|
||||||
*/
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return unsafeCSS(liveImageStyle);
|
return unsafeCSS(liveImageStyle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
CameraConfig,
|
CameraConfig,
|
||||||
CardWideConfig,
|
CardWideConfig,
|
||||||
ExtendedHomeAssistant,
|
ExtendedHomeAssistant,
|
||||||
|
FrigateCardMediaPlayer,
|
||||||
JSMPEGConfig,
|
JSMPEGConfig,
|
||||||
} from '../../types.js';
|
} from '../../types.js';
|
||||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.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;
|
const JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS = 1 * 60 * 60;
|
||||||
|
|
||||||
@customElement('frigate-card-live-jsmpeg')
|
@customElement('frigate-card-live-jsmpeg')
|
||||||
export class FrigateCardLiveJSMPEG extends LitElement {
|
export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMediaPlayer {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public cameraConfig?: CameraConfig;
|
public cameraConfig?: CameraConfig;
|
||||||
|
|
||||||
@@ -43,24 +44,14 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
|
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
|
||||||
protected _refreshPlayerTimerID?: number;
|
protected _refreshPlayerTimerID?: number;
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._jsmpegVideoPlayer?.play();
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._jsmpegVideoPlayer?.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
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 {
|
public mute(): void {
|
||||||
const player = this._jsmpegVideoPlayer?.player;
|
const player = this._jsmpegVideoPlayer?.player;
|
||||||
if (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 {
|
public unmute(): void {
|
||||||
const player = this._jsmpegVideoPlayer?.player;
|
const player = this._jsmpegVideoPlayer?.player;
|
||||||
if (player) {
|
if (player) {
|
||||||
@@ -79,9 +66,10 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video (unsupported).
|
return this._jsmpegVideoPlayer ? this._jsmpegVideoPlayer.player.volume === 0 : true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public seek(_seconds: number): void {
|
public seek(_seconds: number): void {
|
||||||
// JSMPEG does not support seeking.
|
// JSMPEG does not support seeking.
|
||||||
|
|||||||
@@ -8,18 +8,25 @@ import {
|
|||||||
CameraConfig,
|
CameraConfig,
|
||||||
CardWideConfig,
|
CardWideConfig,
|
||||||
FrigateCardError,
|
FrigateCardError,
|
||||||
|
FrigateCardMediaPlayer,
|
||||||
WebRTCCardConfig,
|
WebRTCCardConfig,
|
||||||
} from '../../types.js';
|
} from '../../types.js';
|
||||||
import { contentsChanged } from '../../utils/basic.js';
|
import { contentsChanged } from '../../utils/basic.js';
|
||||||
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
import { dispatchMediaLoadedEvent } from '../../utils/media-info.js';
|
||||||
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
|
import { dispatchErrorMessageEvent, renderProgressIndicator } from '../message.js';
|
||||||
import { renderTask } from '../../utils/task.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
|
// Create a wrapper for AlexxIT's WebRTC card
|
||||||
// - https://github.com/AlexxIT/WebRTC
|
// - https://github.com/AlexxIT/WebRTC
|
||||||
@customElement('frigate-card-live-webrtc-card')
|
@customElement('frigate-card-live-webrtc-card')
|
||||||
export class FrigateCardLiveWebRTCCard extends LitElement {
|
export class FrigateCardLiveWebRTCCard
|
||||||
|
extends LitElement
|
||||||
|
implements FrigateCardMediaPlayer
|
||||||
|
{
|
||||||
@property({ attribute: false, hasChanged: contentsChanged })
|
@property({ attribute: false, hasChanged: contentsChanged })
|
||||||
public webRTCConfig?: WebRTCCardConfig;
|
public webRTCConfig?: WebRTCCardConfig;
|
||||||
|
|
||||||
@@ -34,29 +41,14 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
|||||||
// A task to await the load of the WebRTC component.
|
// A task to await the load of the WebRTC component.
|
||||||
protected _webrtcTask = new Task(this, this._getWebRTCCardElement, () => [1]);
|
protected _webrtcTask = new Task(this, this._getWebRTCCardElement, () => [1]);
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._getPlayer()?.play();
|
||||||
*/
|
|
||||||
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.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._getPlayer()?.pause();
|
this._getPlayer()?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
const player = this._getPlayer();
|
const player = this._getPlayer();
|
||||||
if (player) {
|
if (player) {
|
||||||
@@ -64,9 +56,6 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmute the video.
|
|
||||||
*/
|
|
||||||
public unmute(): void {
|
public unmute(): void {
|
||||||
const player = this._getPlayer();
|
const player = this._getPlayer();
|
||||||
if (player) {
|
if (player) {
|
||||||
@@ -74,9 +63,10 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return this._getPlayer()?.muted ?? true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public seek(seconds: number): void {
|
public seek(seconds: number): void {
|
||||||
const player = this._getPlayer();
|
const player = this._getPlayer();
|
||||||
if (player) {
|
if (player) {
|
||||||
|
|||||||
+20
-21
@@ -515,9 +515,8 @@ export class FrigateCardLiveCarousel extends LitElement {
|
|||||||
<frigate-card-live-provider
|
<frigate-card-live-provider
|
||||||
?disabled=${this.liveConfig.lazy_load}
|
?disabled=${this.liveConfig.lazy_load}
|
||||||
.cameraConfig=${cameraConfig}
|
.cameraConfig=${cameraConfig}
|
||||||
.cameraEndpoints=${guard(
|
.cameraEndpoints=${guard([this.cameraManager], () =>
|
||||||
[this.cameraManager],
|
this.cameraManager?.getCameraEndpoints(cameraID),
|
||||||
() => this.cameraManager?.getCameraEndpoints(cameraID),
|
|
||||||
)}
|
)}
|
||||||
.label=${cameraMetadata?.title ?? ''}
|
.label=${cameraMetadata?.title ?? ''}
|
||||||
.liveConfig=${config}
|
.liveConfig=${config}
|
||||||
@@ -664,7 +663,10 @@ export class FrigateCardLiveCarousel extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@customElement('frigate-card-live-provider')
|
@customElement('frigate-card-live-provider')
|
||||||
export class FrigateCardLiveProvider extends LitElement {
|
export class FrigateCardLiveProvider
|
||||||
|
extends LitElement
|
||||||
|
implements FrigateCardMediaPlayer
|
||||||
|
{
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public hass?: ExtendedHomeAssistant;
|
public hass?: ExtendedHomeAssistant;
|
||||||
|
|
||||||
@@ -694,37 +696,34 @@ export class FrigateCardLiveProvider extends LitElement {
|
|||||||
|
|
||||||
protected _providerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
|
protected _providerRef: Ref<Element & FrigateCardMediaPlayer> = createRef();
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
// 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
|
||||||
public play(): void {
|
// auto-play unless the video is muted.
|
||||||
this._providerRef.value?.play();
|
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 {
|
public pause(): void {
|
||||||
this._providerRef.value?.pause();
|
this._providerRef.value?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
this._providerRef.value?.mute();
|
this._providerRef.value?.mute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmute the video.
|
|
||||||
*/
|
|
||||||
public unmute(): void {
|
public unmute(): void {
|
||||||
this._providerRef.value?.unmute();
|
this._providerRef.value?.unmute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return this._providerRef.value?.isMuted() ?? true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public seek(seconds: number): void {
|
public seek(seconds: number): void {
|
||||||
this._providerRef.value?.seek(seconds);
|
this._providerRef.value?.seek(seconds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ customElements.whenDefined('ha-camera-stream').then(() => {
|
|||||||
|
|
||||||
@customElement('frigate-card-ha-camera-stream')
|
@customElement('frigate-card-ha-camera-stream')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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
|
// 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.
|
// to use query rather than the ref directive to find the player.
|
||||||
@query('#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
|
// - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-camera-stream.ts
|
||||||
// ========================================================================================
|
// ========================================================================================
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._player?.play();
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._player?.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._player?.pause();
|
this._player?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
this._player?.mute();
|
this._player?.mute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmute the video.
|
|
||||||
*/
|
|
||||||
public unmute(): void {
|
public unmute(): void {
|
||||||
this._player?.unmute();
|
this._player?.unmute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isMuted(): boolean {
|
||||||
|
return this._player?.isMuted() ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seek the video (unsupported).
|
* Seek the video (unsupported).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,33 +19,28 @@ import {
|
|||||||
hideMediaControlsTemporarily,
|
hideMediaControlsTemporarily,
|
||||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||||
} from '../utils/media.js';
|
} from '../utils/media.js';
|
||||||
|
import { FrigateCardMediaPlayer } from '../types.js';
|
||||||
|
|
||||||
customElements.whenDefined('ha-hls-player').then(() => {
|
customElements.whenDefined('ha-hls-player').then(() => {
|
||||||
@customElement('frigate-card-ha-hls-player')
|
@customElement('frigate-card-ha-hls-player')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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
|
// 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.
|
// to use query rather than the ref directive to find the player.
|
||||||
@query('#video')
|
@query('#video')
|
||||||
protected _video: HTMLVideoElement;
|
protected _video: HTMLVideoElement;
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._video?.play();
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._video?.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._video?.pause();
|
this._video?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
// The muted property is only for the initial muted state. Must explicitly
|
// The muted property is only for the initial muted state. Must explicitly
|
||||||
// set the muted on the video player to make the change dynamic.
|
// 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 {
|
public unmute(): void {
|
||||||
// See note in mute().
|
// See note in mute().
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
@@ -64,9 +56,10 @@ customElements.whenDefined('ha-hls-player').then(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return this._video?.muted() ?? true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public seek(seconds: number): void {
|
public seek(seconds: number): void {
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
hideMediaControlsTemporarily(this._video);
|
hideMediaControlsTemporarily(this._video);
|
||||||
|
|||||||
@@ -23,29 +23,23 @@ import liveHAComponentsStyle from '../scss/live-ha-components.scss';
|
|||||||
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
customElements.whenDefined('ha-web-rtc-player').then(() => {
|
||||||
@customElement('frigate-card-ha-web-rtc-player')
|
@customElement('frigate-card-ha-web-rtc-player')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// 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
|
// 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.
|
// to use query rather than the ref directive to find the player.
|
||||||
@query('#remote-stream')
|
@query('#remote-stream')
|
||||||
protected _video: HTMLVideoElement;
|
protected _video: HTMLVideoElement;
|
||||||
|
|
||||||
/**
|
public async play(): Promise<void> {
|
||||||
* Play the video.
|
return this._video?.play();
|
||||||
*/
|
|
||||||
public play(): void {
|
|
||||||
this._video?.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the video.
|
|
||||||
*/
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
this._video?.pause();
|
this._video?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mute the video.
|
|
||||||
*/
|
|
||||||
public mute(): void {
|
public mute(): void {
|
||||||
// The muted property is only for the initial muted state. Must explicitly
|
// The muted property is only for the initial muted state. Must explicitly
|
||||||
// set the muted on the video player to make the change dynamic.
|
// 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 {
|
public unmute(): void {
|
||||||
// See note in mute().
|
// See note in mute().
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
@@ -64,9 +55,10 @@ customElements.whenDefined('ha-web-rtc-player').then(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isMuted(): boolean {
|
||||||
* Seek the video.
|
return this._video?.muted ?? true;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public seek(seconds: number): void {
|
public seek(seconds: number): void {
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
this._video.currentTime = seconds;
|
this._video.currentTime = seconds;
|
||||||
|
|||||||
+2
-1
@@ -1347,10 +1347,11 @@ export interface StateParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FrigateCardMediaPlayer {
|
export interface FrigateCardMediaPlayer {
|
||||||
play(): void;
|
play(): Promise<void>;
|
||||||
pause(): void;
|
pause(): void;
|
||||||
mute(): void;
|
mute(): void;
|
||||||
unmute(): void;
|
unmute(): void;
|
||||||
|
isMuted(): boolean;
|
||||||
seek(seconds: number): void;
|
seek(seconds: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user