From 685952c19264d2a8d53ab18892cfef2aa8780b68 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Feb 2022 20:28:48 -0800 Subject: [PATCH] Show when media is loaded --- .../media-browser/browser-media-player.ts | 52 +++++++------- .../media-browser/ha-bar-media-player.ts | 69 ++++++++++++++----- .../media-browser/ha-panel-media-browser.ts | 13 +++- 3 files changed, 86 insertions(+), 48 deletions(-) diff --git a/src/panels/media-browser/browser-media-player.ts b/src/panels/media-browser/browser-media-player.ts index ac2ed08137..bb36b33332 100644 --- a/src/panels/media-browser/browser-media-player.ts +++ b/src/panels/media-browser/browser-media-player.ts @@ -5,43 +5,43 @@ import { SUPPORT_PAUSE, SUPPORT_PLAY, } from "../../data/media-player"; -import { resolveMediaSource } from "../../data/media_source"; +import { ResolvedMediaSource } from "../../data/media_source"; import { HomeAssistant } from "../../types"; export class BrowserMediaPlayer { - private player?: HTMLAudioElement; + private player: HTMLAudioElement; - private stopped = false; + // We pretend we're playing while still buffering. + private _buffering = true; + + private _removed = false; constructor( public hass: HomeAssistant, public item: MediaPlayerItem, + public resolved: ResolvedMediaSource, private onChange: () => void - ) {} - - public async initialize() { - const resolvedUrl: any = await resolveMediaSource( - this.hass, - this.item.media_content_id - ); - - const player = new Audio(resolvedUrl.url); + ) { + const player = new Audio(this.resolved.url); player.addEventListener("play", this._handleChange); - player.addEventListener("playing", this._handleChange); + player.addEventListener("playing", () => { + this._buffering = false; + this._handleChange(); + }); player.addEventListener("pause", this._handleChange); player.addEventListener("ended", this._handleChange); player.addEventListener("canplaythrough", () => { - if (this.stopped) { + if (this._removed) { return; } - this.player = player; player.play(); this.onChange(); }); + this.player = player; } private _handleChange = () => { - if (!this.stopped) { + if (!this._removed) { this.onChange(); } }; @@ -58,8 +58,8 @@ export class BrowserMediaPlayer { } } - public stop() { - this.stopped = true; + public remove() { + this._removed = true; // @ts-ignore this.onChange = undefined; if (this.player) { @@ -68,9 +68,7 @@ export class BrowserMediaPlayer { } public get isPlaying(): boolean { - return ( - this.player !== undefined && !this.player.paused && !this.player.ended - ); + return this._buffering || (!this.player.paused && !this.player.ended); } static idleStateObj(): MediaPlayerEntity { @@ -88,19 +86,19 @@ export class BrowserMediaPlayer { toStateObj(): MediaPlayerEntity { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement const base = BrowserMediaPlayer.idleStateObj(); - if (!this.player) { - return base; - } base.state = this.isPlaying ? "playing" : "paused"; base.attributes = { media_title: this.item.title, - media_duration: this.player.duration, - media_position: this.player.currentTime, - media_position_updated_at: base.last_updated, entity_picture: this.item.thumbnail, // eslint-disable-next-line no-bitwise supported_features: SUPPORT_PLAY | SUPPORT_PAUSE, }; + + if (this.player.duration) { + base.attributes.media_duration = this.player.duration; + base.attributes.media_position = this.player.currentTime; + base.attributes.media_position_updated_at = base.last_updated; + } return base; } } diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 3f76564e1e..7ac9b47100 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -20,6 +20,7 @@ import { } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; @@ -27,6 +28,7 @@ import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; import { supportsFeature } from "../../common/entity/supports-feature"; import "../../components/ha-button-menu"; +import "../../components/ha-circular-progress"; import "../../components/ha-icon-button"; import { UNAVAILABLE_STATES } from "../../data/entity"; import { @@ -43,6 +45,7 @@ import { SUPPORT_PLAY, SUPPORT_STOP, } from "../../data/media-player"; +import { ResolvedMediaSource } from "../../data/media_source"; import type { HomeAssistant } from "../../types"; import "../lovelace/components/hui-marquee"; import { BrowserMediaPlayer } from "./browser-media-player"; @@ -54,7 +57,7 @@ declare global { } @customElement("ha-bar-media-player") -class BarMediaPlayer extends LitElement { +export class BarMediaPlayer extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public entityId!: string; @@ -68,6 +71,8 @@ class BarMediaPlayer extends LitElement { @state() private _marqueeActive = false; + @state() private _newMediaExpected = false; + @state() private _browserPlayer?: BrowserMediaPlayer; private _progressInterval?: number; @@ -98,27 +103,42 @@ class BarMediaPlayer extends LitElement { clearInterval(this._progressInterval); this._progressInterval = undefined; } - - if (this._browserPlayer) { - this._browserPlayer.stop(); - this._browserPlayer = undefined; - } + this._tearDownBrowserPlayer(); } - public async playItem(item: MediaPlayerItem) { + public showResolvingNewMediaPicked() { + this._tearDownBrowserPlayer(); + this._newMediaExpected = true; + } + + public playItem(item: MediaPlayerItem, resolved: ResolvedMediaSource) { if (this.entityId !== BROWSER_PLAYER) { throw Error("Only browser supported"); } - if (this._browserPlayer) { - this._browserPlayer.stop(); - } - this._browserPlayer = new BrowserMediaPlayer(this.hass, item, () => - this.requestUpdate("_browserPlayer") + this._tearDownBrowserPlayer(); + this._browserPlayer = new BrowserMediaPlayer( + this.hass, + item, + resolved, + () => this.requestUpdate("_browserPlayer") ); - await this._browserPlayer.initialize(); + this._newMediaExpected = false; } protected render(): TemplateResult { + if (this._newMediaExpected) { + return html` +
+ ${until( + // Only show spinner after 500ms + new Promise((resolve) => setTimeout(resolve, 500)).then( + () => html`` + ) + )} +
+ `; + } + const isBrowser = this.entityId === BROWSER_PLAYER; const stateObj = this._stateObj; const controls = !stateObj @@ -274,13 +294,19 @@ class BarMediaPlayer extends LitElement { public willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); + if (changedProps.has("entityId")) { + this._tearDownBrowserPlayer(); + } + if (!changedProps.has("hass") || this.entityId === BROWSER_PLAYER) { + return; + } + // Reset new media expected if media player state changes + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; if ( - changedProps.has("entityId") && - this.entityId !== BROWSER_PLAYER && - this._browserPlayer + !oldHass || + oldHass.states[this.entityId] !== this.hass.states[this.entityId] ) { - this._browserPlayer?.stop(); - this._browserPlayer = undefined; + this._newMediaExpected = false; } } @@ -329,6 +355,13 @@ class BarMediaPlayer extends LitElement { return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined; } + private _tearDownBrowserPlayer() { + if (this._browserPlayer) { + this._browserPlayer.remove(); + this._browserPlayer = undefined; + } + } + private _openMoreInfo() { if (this._browserPlayer) { return; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 9f3fa2ca1d..b64b07cb2e 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -37,6 +37,7 @@ import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant, Route } from "../../types"; import "./ha-bar-media-player"; +import type { BarMediaPlayer } from "./ha-bar-media-player"; import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog"; import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { @@ -79,6 +80,8 @@ class PanelMediaBrowser extends LitElement { @query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse; + @query("ha-bar-media-player") private _player!: BarMediaPlayer; + protected render(): TemplateResult { return html` @@ -235,7 +238,9 @@ class PanelMediaBrowser extends LitElement { ev: HASSDomEvent ): Promise { const item = ev.detail.item; + if (this._entityId !== BROWSER_PLAYER) { + this._player.showResolvingNewMediaPicked(); this.hass!.callService("media_player", "play_media", { entity_id: this._entityId, media_content_id: item.media_content_id, @@ -244,6 +249,8 @@ class PanelMediaBrowser extends LitElement { return; } + // We won't cancel current media being played if we're going to + // open a camera. if (isCameraMediaSource(item.media_content_id)) { fireEvent(this, "hass-more-info", { entityId: getEntityIdFromCameraMediaSource(item.media_content_id), @@ -251,15 +258,15 @@ class PanelMediaBrowser extends LitElement { return; } + this._player.showResolvingNewMediaPicked(); + const resolvedUrl = await resolveMediaSource( this.hass, item.media_content_id ); if (resolvedUrl.mime_type.startsWith("audio/")) { - await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem( - item - ); + this._player.playItem(item, resolvedUrl); return; }