From 9820bd0cb3adf3984505d7ac377a451e08e1e271 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 9 Dec 2024 22:33:18 -0800 Subject: [PATCH] fix: Correctly display MJPEG streams --- src/patches/ha-camera-stream.ts | 112 +++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/src/patches/ha-camera-stream.ts b/src/patches/ha-camera-stream.ts index 3c5f4a91..3e9ae2a8 100644 --- a/src/patches/ha-camera-stream.ts +++ b/src/patches/ha-camera-stream.ts @@ -9,11 +9,14 @@ // available as compilation time. // ==================================================================== -import { css, CSSResultGroup, html, unsafeCSS, TemplateResult } from 'lit'; +import { css, CSSResultGroup, html, nothing, PropertyValues, unsafeCSS } from 'lit'; import { customElement } from 'lit/decorators.js'; import { query } from 'lit/decorators/query.js'; -import { FrigateCardMediaPlayer } from '../types.js'; -import { dispatchMediaLoadedEvent } from '../utils/media-info.js'; +import { FrigateCardMediaPlayer, MediaLoadedInfo } from '../types.js'; +import { + createMediaLoadedInfo, + dispatchExistingMediaLoadedInfoAsEvent, +} from '../utils/media-info.js'; import './ha-hls-player'; import './ha-web-rtc-player'; import liveHAComponentsStyle from '../scss/live-ha-components.scss'; @@ -30,6 +33,8 @@ customElements.whenDefined('ha-camera-stream').then(() => { const STREAM_TYPE_HLS = 'hls'; const STREAM_TYPE_WEB_RTC = 'web_rtc'; + const STREAM_TYPE_MJPEG = 'mjpeg'; + type StreamType = STREAM_TYPE_HLS | STREAM_TYPE_WEB_RTC | STREAM_TYPE_MJPEG; @customElement('frigate-card-ha-camera-stream') // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -42,6 +47,9 @@ customElements.whenDefined('ha-camera-stream').then(() => { @query('#player') protected _player: FrigateCardMediaPlayer; + protected _mediaLoadedInfoPerStream: Record = {}; + protected _mediaLoadedInfoDispatched: MediaLoadedInfo | null = null; + // ======================================================================================== // Minor modifications from: // - https://github.com/home-assistant/frontend/blob/dev/src/components/ha-camera-stream.ts @@ -85,45 +93,59 @@ customElements.whenDefined('ha-camera-stream').then(() => { return this._player ? await this._player.getScreenshotURL() : null; } - /** - * Master render method. - * @returns A rendered template. - */ - protected render(): TemplateResult { + protected _storeMediaLoadedInfoHandler( + stream: StreamType, + ev: CustomEvent, + ) { + this._storeMediaLoadedInfo(stream, ev.detail); + ev.stopPropagation(); + } + + protected _storeMediaLoadedInfo( + stream: StreamType, + mediaLoadedInfo: MediaLoadedInfo, + ) { + this._mediaLoadedInfoPerStream[stream] = mediaLoadedInfo; + } + + protected _renderStream(stream: Stream) { if (!this.stateObj) { - return html``; + return nothing; } - - if (this._shouldRenderMJPEG) { + if (stream.type === STREAM_TYPE_MJPEG) { return html` { - dispatchMediaLoadedEvent(this, ev, { - player: this, - technology: ['mjpeg'], - }); - }} + @load=${(ev) => + this._storeMediaLoadedInfo( + STREAM_TYPE_MJPEG, + createMediaLoadedInfo(ev, { player: this, technology: ['mjpeg'] }), + )} .src=${typeof this._connected == 'undefined' || this._connected ? computeMJPEGStreamUrl(this.stateObj) - : ''} + : this._posterUrl || ''} /> `; } - if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) { - return this._url - ? html` ` - : html``; + + if (stream.type === STREAM_TYPE_HLS) { + return html` + this._storeMediaLoadedInfoHandler(STREAM_TYPE_HLS, ev)} + @streams=${this._handleHlsStreams} + class=${stream.visible ? '' : 'hidden'} + >`; } - if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) { + + if (stream.type === STREAM_TYPE_WEB_RTC) { return html` { .controls=${this.controls} .hass=${this.hass} .entityid=${this.stateObj.entity_id} + .posterUrl=${this._posterUrl} + @frigate-card:media:loaded=${(ev) => + this._storeMediaLoadedInfoHandler(STREAM_TYPE_WEB_RTC, ev)} + @streams=${this._handleWebRtcStreams} + class=${stream.visible ? '' : 'hidden'} >`; } + + return nothing; + } + + public updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + const streams = this._streams( + this._capabilities?.frontend_stream_types, + this._hlsStreams, + this._webRtcStreams, + ); + + const visibleStream = streams.find((stream) => stream.visible) ?? null; + if (visibleStream) { + const mediaLoadedInfo = this._mediaLoadedInfoPerStream[visibleStream.type]; + if (mediaLoadedInfo && mediaLoadedInfo !== this._mediaLoadedInfoDispatched) { + this._mediaLoadedInfoDispatched = mediaLoadedInfo; + dispatchExistingMediaLoadedInfoAsEvent(this, mediaLoadedInfo); + } + } } static get styles(): CSSResultGroup {