Refactor JSMPEG signing/creation logic.

This commit is contained in:
Dermot Duffy
2021-10-22 21:38:04 -07:00
parent 9f39d23cfc
commit 7b2b829848
3 changed files with 112 additions and 81 deletions
+6 -4
View File
@@ -733,9 +733,10 @@ export class FrigateCard extends LitElement {
> >
</frigate-card-viewer>` </frigate-card-viewer>`
: ``} : ``}
<!-- Note the subtle difference in condition below vs the other views in order ${
to always render the live view for live_preload mode --> // Note the subtle difference in condition below vs the other views in order
${(!this._message && this._view.is('live')) || this.config.live_preload // to always render the live view for live_preload mode.
(!this._message && this._view.is('live')) || this.config.live_preload
? html` ? html`
<frigate-card-live <frigate-card-live
.hass=${this._hass} .hass=${this._hass}
@@ -749,7 +750,8 @@ export class FrigateCard extends LitElement {
> >
</frigate-card-live> </frigate-card-live>
` `
: ``} : ``
}
${this.config.elements ${this.config.elements
? html` ? html`
<frigate-card-elements <frigate-card-elements
+64 -37
View File
@@ -21,6 +21,12 @@ import JSMpeg from '@cycjimmy/jsmpeg-player';
import liveStyle from '../scss/live.scss'; import liveStyle from '../scss/live.scss';
// Number of seconds a signed URL is valid for.
const URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
// Number of seconds before the expiry to trigger a refresh.
const URL_SIGN_REFRESH_THRESHOLD_SECONDS = 1 * 60 * 60;
@customElement('frigate-card-live') @customElement('frigate-card-live')
export class FrigateCardLive extends LitElement { export class FrigateCardLive extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
@@ -169,8 +175,9 @@ export class FrigateCardLiveJSMPEG extends LitElement {
protected hass!: HomeAssistant & ExtendedHomeAssistant; protected hass!: HomeAssistant & ExtendedHomeAssistant;
protected _jsmpegCanvasElement?: HTMLCanvasElement; protected _jsmpegCanvasElement?: HTMLCanvasElement;
protected _jsmpegVideoPlayer?; protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
protected _jsmpegURL?: string | null; protected _jsmpegURL?: string | null;
protected _refreshPlayerTimerID?: number;
protected async _getURL(): Promise<string | null> { protected async _getURL(): Promise<string | null> {
if (!this.hass) { if (!this.hass) {
@@ -180,7 +187,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
const request = { const request = {
type: 'auth/sign_path', type: 'auth/sign_path',
path: `/api/frigate/${this.clientId}` + `/jsmpeg/${this.cameraName}`, 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. // Sign the path so it includes an authSig parameter.
let response; let response;
@@ -194,22 +201,17 @@ export class FrigateCardLiveJSMPEG extends LitElement {
return url.replace(/^http/i, 'ws'); return url.replace(/^http/i, 'ws');
} }
protected async _createJSMPEGPlayer(): Promise<void> { protected _createJSMPEGPlayer(): JSMpeg.VideoElement {
let videoDecoded = false; let videoDecoded = false;
return new Promise<void>((resolve) => { return new JSMpeg.VideoElement(
this._jsmpegVideoPlayer = new JSMpeg.VideoElement(
this, this,
this._jsmpegURL, this._jsmpegURL,
{ {
preserveDrawingBuffer: true, preserveDrawingBuffer: true,
canvas: this._jsmpegCanvasElement, canvas: this._jsmpegCanvasElement,
hooks: { 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: () => { play: () => {
dispatchPlayEvent(this); dispatchPlayEvent(this);
resolve();
}, },
pause: () => { pause: () => {
dispatchPauseEvent(this); dispatchPauseEvent(this);
@@ -220,6 +222,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
protocols: [], protocols: [],
audio: false, audio: false,
videoBufferSize: 1024 * 1024 * 4, videoBufferSize: 1024 * 1024 * 4,
reconnectInterval: 10,
onVideoDecode: () => { onVideoDecode: () => {
// This is the only callback that is called after the dimensions // This is the only callback that is called after the dimensions
// are available. It's called on every frame decode, so just // are available. It's called on every frame decode, so just
@@ -231,41 +234,65 @@ export class FrigateCardLiveJSMPEG extends LitElement {
}, },
}, },
); );
}); }
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<void> {
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 { protected render(): TemplateResult | void {
if (!this._jsmpegCanvasElement) { if (
this._jsmpegCanvasElement = document.createElement('canvas'); this._jsmpegURL === undefined ||
this._jsmpegCanvasElement.className = 'media'; !this._jsmpegVideoPlayer ||
} !this._jsmpegCanvasElement
) {
if (this._jsmpegURL === undefined) { return html`${until(this._refreshPlayer(), renderProgressIndicator())}`;
return html`${until(
(async () => {
this._jsmpegURL = await this._getURL();
this.requestUpdate();
})(),
renderProgressIndicator(),
)}`;
} }
if (!this._jsmpegURL) { if (!this._jsmpegURL) {
return dispatchErrorMessageEvent( return dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_sign'));
this,
'Could not retrieve or sign JSMPEG websocket path',
);
} }
if (!this._jsmpegVideoPlayer || !this._jsmpegCanvasElement) {
if (!this._jsmpegVideoPlayer) { return dispatchErrorMessageEvent(this, localize('error.jsmpeg_no_player'));
return html`${until(
(async () => {
await this._createJSMPEGPlayer();
this.requestUpdate();
})(),
renderProgressIndicator(),
)}`;
} }
return html`${this._jsmpegCanvasElement}`; return html`${this._jsmpegCanvasElement}`;
} }
+3 -1
View File
@@ -101,6 +101,8 @@
"missing_webrtc": "WebRTC component not found", "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", "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", "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"
} }
} }