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";
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 {

View File

@ -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`
<ha-card>
<div
class="${classMap({
"no-image": !stateObj.attributes.entity_picture,
ratio: true,
class="ratio ${classMap({
"no-image": !this._image,
})}"
>
<div class="image" style="background-image: url(${image})"></div>
<div class="caption">
<div
class="image"
style="background-image: url(${this.hass.hassUrl(picture)})"
></div>
<div
class="caption ${classMap({
unavailable: stateObj.state === UNAVAILABLE,
})}"
>
${this._config!.name ||
computeStateName(this.hass!.states[this._config!.entity])}
<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>
${this._computeSecondaryTitle(stateObj)}
</div>
</div>
${OFF_STATES.includes(stateObj.state)
${OFF_STATES.includes(stateObj.state) ||
!stateObj.attributes.media_duration ||
!stateObj.attributes.media_position
? ""
: html`
<paper-progress
@ -99,36 +122,63 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
></paper-progress>
`}
<div class="controls">
<paper-icon-button
icon="hass:power"
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"}
@click=${this._handleClick}
></paper-icon-button>
${(stateObj.state === "off" &&
!supportsFeature(stateObj, SUPPORT_TURN_ON)) ||
(stateObj.state === "on" &&
!supportsFeature(stateObj, SUPPORT_TURN_OFF))
? ""
: 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">
<paper-icon-button
icon="hass:skip-previous"
.action=${"media_previous_track"}
@click=${this._handleClick}
></paper-icon-button>
<paper-icon-button
class="playPauseButton"
icon=${stateObj.state !== "playing"
? "hass:play"
: supportsFeature(stateObj, SUPPORT_PAUSE)
? "hass:pause"
: "hass:stop"}
.action=${"media_play_pause"}
@click=${this._handleClick}
></paper-icon-button>
<paper-icon-button
icon="hass:skip-next"
.action=${"media_next_track"}
@click=${this._handleClick}
></paper-icon-button>
${!supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
? ""
: html`
<paper-icon-button
icon="hass:skip-previous"
.disabled="${OFF_STATES.includes(stateObj.state)}"
.action=${"media_previous_track"}
@click=${this._handleClick}
></paper-icon-button>
`}
${(stateObj.state !== "playing" &&
!supportsFeature(stateObj, SUPPORTS_PLAY)) ||
stateObj.state === UNAVAILABLE ||
(stateObj.state === "playing" &&
!supportsFeature(stateObj, SUPPORT_PAUSE) &&
!supportsFeature(stateObj, SUPPORT_STOP))
? ""
: html`
<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>
<paper-icon-button
icon="hass:dots-vertical"
@click="${this._handleMoreInfo}"
@click=${this._handleMoreInfo}
></paper-icon-button>
</div>
</ha-card>
@ -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 {

View File

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