mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 12:46:35 +00:00
Merge pull request #4850 from zsarnett/media-card-updates
Update Media Card to check for Supported Features
This commit is contained in:
commit
af3626b215
@ -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 {
|
||||||
|
@ -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(${this.hass.hassUrl(picture)})"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="caption ${classMap({
|
||||||
|
unavailable: stateObj.state === UNAVAILABLE,
|
||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
<div class="image" style="background-image: url(${image})"></div>
|
|
||||||
<div class="caption">
|
|
||||||
${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>
|
||||||
|
${this._computeSecondaryTitle(stateObj)}
|
||||||
</div>
|
</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,20 +122,41 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
></paper-progress>
|
></paper-progress>
|
||||||
`}
|
`}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
${(stateObj.state === "off" &&
|
||||||
|
!supportsFeature(stateObj, SUPPORT_TURN_ON)) ||
|
||||||
|
(stateObj.state === "on" &&
|
||||||
|
!supportsFeature(stateObj, SUPPORT_TURN_OFF))
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:power"
|
icon="hass:power"
|
||||||
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"}
|
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
|
`}
|
||||||
<div class="playback-controls">
|
<div class="playback-controls">
|
||||||
|
${!supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:skip-previous"
|
icon="hass:skip-previous"
|
||||||
|
.disabled="${OFF_STATES.includes(stateObj.state)}"
|
||||||
.action=${"media_previous_track"}
|
.action=${"media_previous_track"}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
></paper-icon-button>
|
></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
|
<paper-icon-button
|
||||||
class="playPauseButton"
|
class="playPauseButton"
|
||||||
icon=${stateObj.state !== "playing"
|
.disabled="${OFF_STATES.includes(stateObj.state)}"
|
||||||
|
.icon=${stateObj.state !== "playing"
|
||||||
? "hass:play"
|
? "hass:play"
|
||||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||||
? "hass:pause"
|
? "hass:pause"
|
||||||
@ -120,15 +164,21 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
.action=${"media_play_pause"}
|
.action=${"media_play_pause"}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
|
`}
|
||||||
|
${!supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:skip-next"
|
icon="hass:skip-next"
|
||||||
|
.disabled="${OFF_STATES.includes(stateObj.state)}"
|
||||||
.action=${"media_next_track"}
|
.action=${"media_next_track"}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
></paper-icon-button>
|
></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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeMediaTitle(stateObj: HassEntity): string {
|
if (newImage.substr(0, 1) !== "/") {
|
||||||
let prefix;
|
this._image = newImage;
|
||||||
let suffix;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMediaPlayerThumbnailWithCache(this.hass, this._config.entity).then(
|
||||||
|
({ content_type, content }) => {
|
||||||
|
this._image = `data:${content_type};base64,${content}`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeSecondaryTitle(stateObj: HassEntity): string {
|
||||||
|
let secondaryTitle: string;
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user