From 7b2b829848b10516e582e7cb6426247fc86be64c Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 22 Oct 2021 21:38:04 -0700 Subject: [PATCH 1/4] Refactor JSMPEG signing/creation logic. --- src/card.ts | 36 ++++---- src/components/live.ts | 153 +++++++++++++++++++-------------- src/localize/languages/en.json | 4 +- 3 files changed, 112 insertions(+), 81 deletions(-) diff --git a/src/card.ts b/src/card.ts index 99eca1f2..b2ad7790 100644 --- a/src/card.ts +++ b/src/card.ts @@ -733,23 +733,25 @@ export class FrigateCard extends LitElement { > ` : ``} - - ${(!this._message && this._view.is('live')) || this.config.live_preload - ? html` - - - ` - : ``} + ${ + // Note the subtle difference in condition below vs the other views in order + // to always render the live view for live_preload mode. + (!this._message && this._view.is('live')) || this.config.live_preload + ? html` + + + ` + : `` + } ${this.config.elements ? html` { if (!this.hass) { @@ -180,7 +187,7 @@ export class FrigateCardLiveJSMPEG extends LitElement { const request = { type: 'auth/sign_path', path: `/api/frigate/${this.clientId}` + `/jsmpeg/${this.cameraName}`, - expires: 60 * 15, + expires: URL_SIGN_EXPIRY_SECONDS, }; // Sign the path so it includes an authSig parameter. let response; @@ -194,78 +201,98 @@ export class FrigateCardLiveJSMPEG extends LitElement { return url.replace(/^http/i, 'ws'); } - protected async _createJSMPEGPlayer(): Promise { + protected _createJSMPEGPlayer(): JSMpeg.VideoElement { let videoDecoded = false; - return new Promise((resolve) => { - this._jsmpegVideoPlayer = new JSMpeg.VideoElement( - this, - this._jsmpegURL, - { - preserveDrawingBuffer: true, - canvas: this._jsmpegCanvasElement, - hooks: { - // Don't resolve the promise until it's playing to minimize the - // amount of time the canvas is empty (and show the spinner - // instead). - play: () => { - dispatchPlayEvent(this); - resolve(); - }, - pause: () => { - dispatchPauseEvent(this); - }, + return new JSMpeg.VideoElement( + this, + this._jsmpegURL, + { + preserveDrawingBuffer: true, + canvas: this._jsmpegCanvasElement, + hooks: { + play: () => { + dispatchPlayEvent(this); + }, + pause: () => { + dispatchPauseEvent(this); }, }, - { - protocols: [], - audio: false, - videoBufferSize: 1024 * 1024 * 4, - onVideoDecode: () => { - // This is the only callback that is called after the dimensions - // are available. It's called on every frame decode, so just - // ignore any subsequent calls. - if (!videoDecoded && this._jsmpegCanvasElement) { - videoDecoded = true; - dispatchMediaLoadEvent(this, this._jsmpegCanvasElement); - } - }, + }, + { + protocols: [], + audio: false, + videoBufferSize: 1024 * 1024 * 4, + reconnectInterval: 10, + onVideoDecode: () => { + // This is the only callback that is called after the dimensions + // are available. It's called on every frame decode, so just + // ignore any subsequent calls. + if (!videoDecoded && this._jsmpegCanvasElement) { + videoDecoded = true; + dispatchMediaLoadEvent(this, this._jsmpegCanvasElement); + } }, - ); - }); + }, + ); + } + + protected _resetPlayer(): void { + if (this._refreshPlayerTimerID) { + window.clearTimeout(this._refreshPlayerTimerID); + this._refreshPlayerTimerID = undefined; + } + if (this._jsmpegVideoPlayer) { + this._jsmpegVideoPlayer.destroy(); + this._jsmpegVideoPlayer = undefined; + } + if (this._jsmpegCanvasElement) { + this._jsmpegCanvasElement.remove(); + this._jsmpegCanvasElement = undefined; + } + this._jsmpegURL = undefined; + } + + connectedCallback(): void { + super.connectedCallback(); + this.requestUpdate(); + } + + disconnectedCallback(): void { + this._resetPlayer(); + super.disconnectedCallback(); + } + + protected async _refreshPlayer(): Promise { + this._resetPlayer(); + + this._jsmpegCanvasElement = document.createElement('canvas'); + this._jsmpegCanvasElement.className = 'media'; + + this._jsmpegURL = await this._getURL(); + if (this._jsmpegURL) { + this._refreshPlayerTimerID = window.setTimeout(() => { + this._refreshPlayer(); + }, (URL_SIGN_EXPIRY_SECONDS - URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000); + + this._jsmpegVideoPlayer = this._createJSMPEGPlayer(); + } + this.requestUpdate(); } protected render(): TemplateResult | void { - if (!this._jsmpegCanvasElement) { - this._jsmpegCanvasElement = document.createElement('canvas'); - this._jsmpegCanvasElement.className = 'media'; - } - - if (this._jsmpegURL === undefined) { - return html`${until( - (async () => { - this._jsmpegURL = await this._getURL(); - this.requestUpdate(); - })(), - renderProgressIndicator(), - )}`; + if ( + this._jsmpegURL === undefined || + !this._jsmpegVideoPlayer || + !this._jsmpegCanvasElement + ) { + return html`${until(this._refreshPlayer(), renderProgressIndicator())}`; } if (!this._jsmpegURL) { - return dispatchErrorMessageEvent( - this, - 'Could not retrieve or sign JSMPEG websocket path', - ); + return dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_sign')); } - - if (!this._jsmpegVideoPlayer) { - return html`${until( - (async () => { - await this._createJSMPEGPlayer(); - this.requestUpdate(); - })(), - renderProgressIndicator(), - )}`; + if (!this._jsmpegVideoPlayer || !this._jsmpegCanvasElement) { + return dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_player')); } - return html`${this._jsmpegCanvasElement}`; } diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index d7126823..7e988568 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -101,6 +101,8 @@ "missing_webrtc": "WebRTC component not found", "no_frigate_camera_name": "Cannot autodetect Frigate camera name, you need to either set camera_entity and / or frigate_camera_name", "could_not_render_elements": "Could not render picture elements", - "invalid_elements_config": "Invalid picture elements configuration" + "invalid_elements_config": "Invalid picture elements configuration", + "jsmpeg_no_sign": "Could not retrieve or sign JSMPEG websocket path", + "jsmpeg_no_player": "Could not start JSMPEG player" } } From a6cc7d43d6a5ec47e4d6017cecb8a4cb16570c65 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 22 Oct 2021 22:06:09 -0700 Subject: [PATCH 2/4] Don't adjust reconnection timeout. --- src/components/live.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/live.ts b/src/components/live.ts index be939c62..10d710a2 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -222,7 +222,6 @@ export class FrigateCardLiveJSMPEG extends LitElement { protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4, - reconnectInterval: 10, onVideoDecode: () => { // This is the only callback that is called after the dimensions // are available. It's called on every frame decode, so just From 9e543dbb5ad1944fc90741be5899f4a11771ef62 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 22 Oct 2021 22:15:06 -0700 Subject: [PATCH 3/4] Don't start the timer until the last moment. --- src/components/live.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/live.ts b/src/components/live.ts index 10d710a2..908f25af 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -269,11 +269,11 @@ export class FrigateCardLiveJSMPEG extends LitElement { this._jsmpegURL = await this._getURL(); if (this._jsmpegURL) { + this._jsmpegVideoPlayer = this._createJSMPEGPlayer(); + this._refreshPlayerTimerID = window.setTimeout(() => { this._refreshPlayer(); }, (URL_SIGN_EXPIRY_SECONDS - URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000); - - this._jsmpegVideoPlayer = this._createJSMPEGPlayer(); } this.requestUpdate(); } From 2ed6b98278f93e8f3b0b92c4897aafbaf1560654 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 23 Oct 2021 10:05:27 -0700 Subject: [PATCH 4/4] Fixes for when player is left in the background. --- src/components/live.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/live.ts b/src/components/live.ts index 908f25af..0fc2cce1 100644 --- a/src/components/live.ts +++ b/src/components/live.ts @@ -219,6 +219,7 @@ export class FrigateCardLiveJSMPEG extends LitElement { }, }, { + pauseWhenHidden: false, protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4, @@ -253,11 +254,15 @@ export class FrigateCardLiveJSMPEG extends LitElement { connectedCallback(): void { super.connectedCallback(); - this.requestUpdate(); + if (this.isConnected) { + this.requestUpdate(); + } } disconnectedCallback(): void { - this._resetPlayer(); + if (!this.isConnected) { + this._resetPlayer(); + } super.disconnectedCallback(); }