diff --git a/src/components/ha-camera-stream.ts b/src/components/ha-camera-stream.ts index 61b3d89d96..e4eda4b3d8 100644 --- a/src/components/ha-camera-stream.ts +++ b/src/components/ha-camera-stream.ts @@ -12,6 +12,8 @@ import { import { fireEvent } from "../common/dom/fire_event"; import { computeStateName } from "../common/entity/compute_state_name"; import { supportsFeature } from "../common/entity/supports-feature"; +import { nextRender } from "../common/util/render-status"; +import { getExternalConfig } from "../external_app/external_config"; import { CAMERA_SUPPORT_STREAM, computeMJPEGStreamUrl, @@ -37,6 +39,8 @@ class HaCameraStream extends LitElement { private _hlsPolyfillInstance?: Hls; + private _useExoPlayer = false; + public connectedCallback() { super.connectedCallback(); this._attached = true; @@ -125,22 +129,33 @@ class HaCameraStream extends LitElement { return this.shadowRoot!.querySelector("video")!; } + private async _getUseExoPlayer(): Promise { + if (!this.hass!.auth.external) { + return false; + } + const externalConfig = await getExternalConfig(this.hass!.auth.external); + return externalConfig && externalConfig.hasExoPlayer; + } + private async _startHls(): Promise { // eslint-disable-next-line - const Hls = ((await import( - /* webpackChunkName: "hls.js" */ "hls.js" - )) as any).default as HLSModule; - let hlsSupported = Hls.isSupported(); + let hls; const videoEl = this._videoEl; + this._useExoPlayer = await this._getUseExoPlayer(); + if (!this._useExoPlayer) { + hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any) + .default as HLSModule; + let hlsSupported = hls.isSupported(); - if (!hlsSupported) { - hlsSupported = - videoEl.canPlayType("application/vnd.apple.mpegurl") !== ""; - } + if (!hlsSupported) { + hlsSupported = + videoEl.canPlayType("application/vnd.apple.mpegurl") !== ""; + } - if (!hlsSupported) { - this._forceMJPEG = this.stateObj!.entity_id; - return; + if (!hlsSupported) { + this._forceMJPEG = this.stateObj!.entity_id; + return; + } } try { @@ -149,8 +164,10 @@ class HaCameraStream extends LitElement { this.stateObj!.entity_id ); - if (Hls.isSupported()) { - this._renderHLSPolyfill(videoEl, Hls, url); + if (this._useExoPlayer) { + this._renderHLSExoPlayer(url); + } else if (hls.isSupported()) { + this._renderHLSPolyfill(videoEl, hls, url); } else { this._renderHLSNative(videoEl, url); } @@ -163,6 +180,29 @@ class HaCameraStream extends LitElement { } } + private async _renderHLSExoPlayer(url: string) { + window.addEventListener("resize", this._resizeExoPlayer); + this.updateComplete.then(() => nextRender()).then(this._resizeExoPlayer); + this._videoEl.style.visibility = "hidden"; + await this.hass!.auth.external!.sendMessage({ + type: "exoplayer/play_hls", + payload: new URL(url, window.location.href).toString(), + }); + } + + private _resizeExoPlayer = () => { + const rect = this._videoEl.getBoundingClientRect(); + this.hass!.auth.external!.fireMessage({ + type: "exoplayer/resize", + payload: { + left: rect.left, + top: rect.top, + right: rect.right, + bottom: rect.bottom, + }, + }); + }; + private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) { videoEl.src = url; await new Promise((resolve) => @@ -194,11 +234,15 @@ class HaCameraStream extends LitElement { fireEvent(this, "iron-resize"); } - private _destroyPolyfill(): void { + private _destroyPolyfill() { if (this._hlsPolyfillInstance) { this._hlsPolyfillInstance.destroy(); this._hlsPolyfillInstance = undefined; } + if (this._useExoPlayer) { + window.removeEventListener("resize", this._resizeExoPlayer); + this.hass!.auth.external!.fireMessage({ type: "exoplayer/stop" }); + } } static get styles(): CSSResult { diff --git a/src/external_app/external_config.ts b/src/external_app/external_config.ts index d911ea2593..27728deaf0 100644 --- a/src/external_app/external_config.ts +++ b/src/external_app/external_config.ts @@ -3,6 +3,7 @@ import { ExternalMessaging } from "./external_messaging"; export interface ExternalConfig { hasSettingsScreen: boolean; canWriteTag: boolean; + hasExoPlayer: boolean; } export const getExternalConfig = (