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"
}
}