diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 3aaf16e093..ddf024ac98 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -3,8 +3,18 @@ import { HomeAssistant } from "../types"; import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; export const SUPPORT_PAUSE = 1; +export const SUPPORT_VOLUME_SET = 4; +export const SUPPORT_VOLUME_MUTE = 8; +export const SUPPORT_PREVIOUS_TRACK = 16; export const SUPPORT_NEXT_TRACK = 32; +export const SUPPORT_TURN_ON = 128; +export const SUPPORT_TURN_OFF = 256; +export const SUPPORT_PLAY_MEDIA = 512; +export const SUPPORT_VOLUME_BUTTONS = 1024; +export const SUPPORT_SELECT_SOURCE = 2048; +export const SUPPORT_STOP = 4096; export const SUPPORTS_PLAY = 16384; +export const SUPPORT_SELECT_SOUND_MODE = 65536; export const OFF_STATES = ["off", "idle"]; export interface MediaPlayerThumbnail { diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index c5035a9c58..6a319b4122 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -16,12 +16,23 @@ import "../../../components/ha-card"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { supportsFeature } from "../../../common/entity/supports-feature"; -import { OFF_STATES, SUPPORT_PAUSE } from "../../../data/media-player"; +import { + OFF_STATES, + SUPPORT_PAUSE, + SUPPORT_TURN_ON, + SUPPORT_TURN_OFF, + SUPPORT_PREVIOUS_TRACK, + SUPPORT_NEXT_TRACK, + SUPPORTS_PLAY, + fetchMediaPlayerThumbnailWithCache, + SUPPORT_STOP, +} from "../../../data/media-player"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { HomeAssistant, MediaEntity } from "../../../types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { fireEvent } from "../../../common/dom/fire_event"; import { MediaControlCardConfig } from "./types"; +import { UNAVAILABLE } from "../../../data/entity"; @customElement("hui-media-control-card") export class HuiMediaControlCard extends LitElement implements LovelaceCard { @@ -38,6 +49,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { @property() public hass?: HomeAssistant; @property() private _config?: MediaControlCardConfig; + @property() private _image?: string; public getCardSize(): number { return 3; @@ -68,28 +80,39 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { > `; } - const image = - stateObj.attributes.entity_picture || - "../static/images/card_media_player_bg.png"; + + const picture = this._image || "../static/images/card_media_player_bg.png"; return html`
-
-
+
+
${this._config!.name || computeStateName(this.hass!.states[this._config!.entity])}
- ${this._computeMediaTitle(stateObj)} + ${stateObj.attributes.media_title || + this.hass.localize(`state.media_player.${stateObj.state}`) || + this.hass.localize(`state.default.${stateObj.state}`) || + stateObj.state}
+ ${this._computeSecondaryTitle(stateObj)}
- ${OFF_STATES.includes(stateObj.state) + ${OFF_STATES.includes(stateObj.state) || + !stateObj.attributes.media_duration || + !stateObj.attributes.media_position ? "" : html` `}
- + ${(stateObj.state === "off" && + !supportsFeature(stateObj, SUPPORT_TURN_ON)) || + (stateObj.state === "on" && + !supportsFeature(stateObj, SUPPORT_TURN_OFF)) + ? "" + : html` + + `}
- - - + ${!supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK) + ? "" + : html` + + `} + ${(stateObj.state !== "playing" && + !supportsFeature(stateObj, SUPPORTS_PLAY)) || + stateObj.state === UNAVAILABLE || + (stateObj.state === "playing" && + !supportsFeature(stateObj, SUPPORT_PAUSE) && + !supportsFeature(stateObj, SUPPORT_STOP)) + ? "" + : html` + + `} + ${!supportsFeature(stateObj, SUPPORT_NEXT_TRACK) + ? "" + : html` + + `}
@@ -158,41 +208,66 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { ) { applyThemesOnElement(this, this.hass.themes, this._config.theme); } + + const oldImage = oldHass + ? oldHass.states[this._config.entity].attributes.entity_picture + : undefined; + const newImage = this.hass.states[this._config.entity].attributes + .entity_picture; + + if (!newImage || newImage === oldImage) { + this._image = newImage; + return; + } + + if (newImage.substr(0, 1) !== "/") { + this._image = newImage; + return; + } + + fetchMediaPlayerThumbnailWithCache(this.hass, this._config.entity).then( + ({ content_type, content }) => { + this._image = `data:${content_type};base64,${content}`; + } + ); } - private _computeMediaTitle(stateObj: HassEntity): string { - let prefix; - let suffix; + private _computeSecondaryTitle(stateObj: HassEntity): string { + let secondaryTitle: string; switch (stateObj.attributes.media_content_type) { case "music": - prefix = stateObj.attributes.media_artist; - suffix = stateObj.attributes.media_title; + secondaryTitle = stateObj.attributes.media_artist; + break; + case "playlist": + secondaryTitle = stateObj.attributes.media_playlist; break; case "tvshow": - prefix = stateObj.attributes.media_series_title; - suffix = stateObj.attributes.media_title; + secondaryTitle = stateObj.attributes.media_series_title; + if (stateObj.attributes.media_season) { + secondaryTitle += " S" + stateObj.attributes.media_season; + + if (stateObj.attributes.media_episode) { + secondaryTitle += "E" + stateObj.attributes.media_episode; + } + } break; default: - prefix = - stateObj.attributes.media_title || - stateObj.attributes.app_name || - this.hass!.localize(`state.media_player.${stateObj.state}`) || - this.hass!.localize(`state.default.${stateObj.state}`) || - stateObj.state; - suffix = ""; + secondaryTitle = stateObj.attributes.app_name + ? stateObj.attributes.app_name + : ""; } - return prefix && suffix ? `${prefix}: ${suffix}` : prefix || suffix || ""; + return secondaryTitle; } - private _handleMoreInfo() { + private _handleMoreInfo(): void { fireEvent(this, "hass-more-info", { entityId: this._config!.entity, }); } - private _handleClick(e: MouseEvent) { + private _handleClick(e: MouseEvent): void { this.hass!.callService("media_player", (e.currentTarget! as any).action, { entity_id: this._config!.entity, }); @@ -205,6 +280,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { width: 100%; height: 0; padding-bottom: 56.25%; + transition: padding-bottom 0.8s; } .image { @@ -252,6 +328,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { width: 44px; height: 44px; } + paper-icon-button { opacity: var(--dark-primary-opacity); } @@ -270,6 +347,10 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { transition: background-color 0.5s; } + .playPauseButton[disabled] { + background-color: rgba(0, 0, 0, var(--dark-disabled-opacity)); + } + .caption { position: absolute; left: 0; @@ -283,9 +364,16 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { transition: background-color 0.5s; } + .caption.unavailable { + background-color: rgba(0, 0, 0, var(--dark-secondary-opacity)); + } + .title { font-size: 1.2em; margin: 8px 0 4px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .progress { diff --git a/src/types.ts b/src/types.ts index 9cef34e771..1e8626bec6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -204,6 +204,7 @@ export type MediaEntity = HassEntityBase & { attributes: HassEntityAttributeBase & { media_duration: number; media_position: number; + media_title: string; }; };