Merge pull request #4850 from zsarnett/media-card-updates

Update Media Card to check for Supported Features
This commit is contained in:
Bram Kragten 2020-02-17 21:16:06 +01:00 committed by GitHub
commit af3626b215
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 153 additions and 54 deletions

View File

@ -3,8 +3,18 @@ import { HomeAssistant } from "../types";
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise"; import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
export const SUPPORT_PAUSE = 1; 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_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 SUPPORTS_PLAY = 16384;
export const SUPPORT_SELECT_SOUND_MODE = 65536;
export const OFF_STATES = ["off", "idle"]; export const OFF_STATES = ["off", "idle"];
export interface MediaPlayerThumbnail { export interface MediaPlayerThumbnail {

View File

@ -16,12 +16,23 @@ import "../../../components/ha-card";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { supportsFeature } from "../../../common/entity/supports-feature"; 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 { hasConfigOrEntityChanged } from "../common/has-changed";
import { HomeAssistant, MediaEntity } from "../../../types"; import { HomeAssistant, MediaEntity } from "../../../types";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { MediaControlCardConfig } from "./types"; import { MediaControlCardConfig } from "./types";
import { UNAVAILABLE } from "../../../data/entity";
@customElement("hui-media-control-card") @customElement("hui-media-control-card")
export class HuiMediaControlCard extends LitElement implements LovelaceCard { export class HuiMediaControlCard extends LitElement implements LovelaceCard {
@ -38,6 +49,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() private _config?: MediaControlCardConfig; @property() private _config?: MediaControlCardConfig;
@property() private _image?: string;
public getCardSize(): number { public getCardSize(): number {
return 3; return 3;
@ -68,28 +80,39 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
> >
`; `;
} }
const image =
stateObj.attributes.entity_picture || const picture = this._image || "../static/images/card_media_player_bg.png";
"../static/images/card_media_player_bg.png";
return html` return html`
<ha-card> <ha-card>
<div <div
class="${classMap({ class="ratio ${classMap({
"no-image": !stateObj.attributes.entity_picture, "no-image": !this._image,
ratio: true,
})}" })}"
> >
<div class="image" style="background-image: url(${image})"></div> <div
<div class="caption"> class="image"
style="background-image: url(${this.hass.hassUrl(picture)})"
></div>
<div
class="caption ${classMap({
unavailable: stateObj.state === UNAVAILABLE,
})}"
>
${this._config!.name || ${this._config!.name ||
computeStateName(this.hass!.states[this._config!.entity])} computeStateName(this.hass!.states[this._config!.entity])}
<div class="title"> <div class="title">
${this._computeMediaTitle(stateObj)} ${stateObj.attributes.media_title ||
this.hass.localize(`state.media_player.${stateObj.state}`) ||
this.hass.localize(`state.default.${stateObj.state}`) ||
stateObj.state}
</div> </div>
${this._computeSecondaryTitle(stateObj)}
</div> </div>
</div> </div>
${OFF_STATES.includes(stateObj.state) ${OFF_STATES.includes(stateObj.state) ||
!stateObj.attributes.media_duration ||
!stateObj.attributes.media_position
? "" ? ""
: html` : html`
<paper-progress <paper-progress
@ -99,36 +122,63 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
></paper-progress> ></paper-progress>
`} `}
<div class="controls"> <div class="controls">
<paper-icon-button ${(stateObj.state === "off" &&
icon="hass:power" !supportsFeature(stateObj, SUPPORT_TURN_ON)) ||
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"} (stateObj.state === "on" &&
@click=${this._handleClick} !supportsFeature(stateObj, SUPPORT_TURN_OFF))
></paper-icon-button> ? ""
: html`
<paper-icon-button
icon="hass:power"
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"}
@click=${this._handleClick}
></paper-icon-button>
`}
<div class="playback-controls"> <div class="playback-controls">
<paper-icon-button ${!supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
icon="hass:skip-previous" ? ""
.action=${"media_previous_track"} : html`
@click=${this._handleClick} <paper-icon-button
></paper-icon-button> icon="hass:skip-previous"
<paper-icon-button .disabled="${OFF_STATES.includes(stateObj.state)}"
class="playPauseButton" .action=${"media_previous_track"}
icon=${stateObj.state !== "playing" @click=${this._handleClick}
? "hass:play" ></paper-icon-button>
: supportsFeature(stateObj, SUPPORT_PAUSE) `}
? "hass:pause" ${(stateObj.state !== "playing" &&
: "hass:stop"} !supportsFeature(stateObj, SUPPORTS_PLAY)) ||
.action=${"media_play_pause"} stateObj.state === UNAVAILABLE ||
@click=${this._handleClick} (stateObj.state === "playing" &&
></paper-icon-button> !supportsFeature(stateObj, SUPPORT_PAUSE) &&
<paper-icon-button !supportsFeature(stateObj, SUPPORT_STOP))
icon="hass:skip-next" ? ""
.action=${"media_next_track"} : html`
@click=${this._handleClick} <paper-icon-button
></paper-icon-button> class="playPauseButton"
.disabled="${OFF_STATES.includes(stateObj.state)}"
.icon=${stateObj.state !== "playing"
? "hass:play"
: supportsFeature(stateObj, SUPPORT_PAUSE)
? "hass:pause"
: "hass:stop"}
.action=${"media_play_pause"}
@click=${this._handleClick}
></paper-icon-button>
`}
${!supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
? ""
: html`
<paper-icon-button
icon="hass:skip-next"
.disabled="${OFF_STATES.includes(stateObj.state)}"
.action=${"media_next_track"}
@click=${this._handleClick}
></paper-icon-button>
`}
</div> </div>
<paper-icon-button <paper-icon-button
icon="hass:dots-vertical" icon="hass:dots-vertical"
@click="${this._handleMoreInfo}" @click=${this._handleMoreInfo}
></paper-icon-button> ></paper-icon-button>
</div> </div>
</ha-card> </ha-card>
@ -158,41 +208,66 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
) { ) {
applyThemesOnElement(this, this.hass.themes, this._config.theme); 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 { private _computeSecondaryTitle(stateObj: HassEntity): string {
let prefix; let secondaryTitle: string;
let suffix;
switch (stateObj.attributes.media_content_type) { switch (stateObj.attributes.media_content_type) {
case "music": case "music":
prefix = stateObj.attributes.media_artist; secondaryTitle = stateObj.attributes.media_artist;
suffix = stateObj.attributes.media_title; break;
case "playlist":
secondaryTitle = stateObj.attributes.media_playlist;
break; break;
case "tvshow": case "tvshow":
prefix = stateObj.attributes.media_series_title; secondaryTitle = stateObj.attributes.media_series_title;
suffix = stateObj.attributes.media_title; if (stateObj.attributes.media_season) {
secondaryTitle += " S" + stateObj.attributes.media_season;
if (stateObj.attributes.media_episode) {
secondaryTitle += "E" + stateObj.attributes.media_episode;
}
}
break; break;
default: default:
prefix = secondaryTitle = stateObj.attributes.app_name
stateObj.attributes.media_title || ? stateObj.attributes.app_name
stateObj.attributes.app_name || : "";
this.hass!.localize(`state.media_player.${stateObj.state}`) ||
this.hass!.localize(`state.default.${stateObj.state}`) ||
stateObj.state;
suffix = "";
} }
return prefix && suffix ? `${prefix}: ${suffix}` : prefix || suffix || ""; return secondaryTitle;
} }
private _handleMoreInfo() { private _handleMoreInfo(): void {
fireEvent(this, "hass-more-info", { fireEvent(this, "hass-more-info", {
entityId: this._config!.entity, entityId: this._config!.entity,
}); });
} }
private _handleClick(e: MouseEvent) { private _handleClick(e: MouseEvent): void {
this.hass!.callService("media_player", (e.currentTarget! as any).action, { this.hass!.callService("media_player", (e.currentTarget! as any).action, {
entity_id: this._config!.entity, entity_id: this._config!.entity,
}); });
@ -205,6 +280,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
width: 100%; width: 100%;
height: 0; height: 0;
padding-bottom: 56.25%; padding-bottom: 56.25%;
transition: padding-bottom 0.8s;
} }
.image { .image {
@ -252,6 +328,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
width: 44px; width: 44px;
height: 44px; height: 44px;
} }
paper-icon-button { paper-icon-button {
opacity: var(--dark-primary-opacity); opacity: var(--dark-primary-opacity);
} }
@ -270,6 +347,10 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
transition: background-color 0.5s; transition: background-color 0.5s;
} }
.playPauseButton[disabled] {
background-color: rgba(0, 0, 0, var(--dark-disabled-opacity));
}
.caption { .caption {
position: absolute; position: absolute;
left: 0; left: 0;
@ -283,9 +364,16 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
transition: background-color 0.5s; transition: background-color 0.5s;
} }
.caption.unavailable {
background-color: rgba(0, 0, 0, var(--dark-secondary-opacity));
}
.title { .title {
font-size: 1.2em; font-size: 1.2em;
margin: 8px 0 4px; margin: 8px 0 4px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
} }
.progress { .progress {

View File

@ -204,6 +204,7 @@ export type MediaEntity = HassEntityBase & {
attributes: HassEntityAttributeBase & { attributes: HassEntityAttributeBase & {
media_duration: number; media_duration: number;
media_position: number; media_position: number;
media_title: string;
}; };
}; };