mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
Extract media controls into method (#5141)
* Extract media controls into method * address comments * lint * Moooorre fixes * Fix margin * Update demos * Very narrow idle players show play button * Lint * More stuff * Marquee on steroids
This commit is contained in:
parent
c7a5f63e33
commit
9b220cc6ce
@ -1,23 +1,36 @@
|
|||||||
import { getEntity } from "../../../src/fake_data/entity";
|
import { getEntity } from "../../../src/fake_data/entity";
|
||||||
|
|
||||||
export const createMediaPlayerEntities = () => [
|
export const createMediaPlayerEntities = () => [
|
||||||
getEntity("media_player", "bedroom", "playing", {
|
getEntity("media_player", "music_paused", "paused", {
|
||||||
media_content_type: "movie",
|
friendly_name: "Pausing The Music",
|
||||||
media_title: "Epic sax guy 10 hours",
|
|
||||||
app_name: "YouTube",
|
|
||||||
friendly_name: "Skip, no pause",
|
|
||||||
supported_features: 32,
|
|
||||||
}),
|
|
||||||
getEntity("media_player", "family_room", "paused", {
|
|
||||||
friendly_name: "Paused, music",
|
|
||||||
media_content_type: "music",
|
media_content_type: "music",
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
supported_features: 16417,
|
supported_features: 64063,
|
||||||
entity_picture: "/images/album_cover.jpg",
|
entity_picture: "/images/album_cover.jpg",
|
||||||
|
media_duration: 300,
|
||||||
|
media_position: 50,
|
||||||
|
media_position_updated_at: new Date(
|
||||||
|
// 23 seconds in
|
||||||
|
new Date().getTime() - 23000
|
||||||
|
).toISOString(),
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "family_room_no_play", "paused", {
|
getEntity("media_player", "music_playing", "playing", {
|
||||||
friendly_name: "Paused, no play",
|
friendly_name: "Playing The Music",
|
||||||
|
media_content_type: "music",
|
||||||
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
|
media_artist: "Technohead",
|
||||||
|
supported_features: 64063,
|
||||||
|
entity_picture: "/images/album_cover.jpg",
|
||||||
|
media_duration: 300,
|
||||||
|
media_position: 0,
|
||||||
|
media_position_updated_at: new Date(
|
||||||
|
// 23 seconds in
|
||||||
|
new Date().getTime() - 23000
|
||||||
|
).toISOString(),
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "stream_playing", "playing", {
|
||||||
|
friendly_name: "Playing the Stream",
|
||||||
media_content_type: "movie",
|
media_content_type: "movie",
|
||||||
media_title: "Epic sax guy 10 hours",
|
media_title: "Epic sax guy 10 hours",
|
||||||
app_name: "YouTube",
|
app_name: "YouTube",
|
||||||
@ -31,25 +44,19 @@ export const createMediaPlayerEntities = () => [
|
|||||||
app_name: "Netflix",
|
app_name: "Netflix",
|
||||||
supported_features: 1,
|
supported_features: 1,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "lounge_room", "idle", {
|
getEntity("media_player", "sonos_idle", "idle", {
|
||||||
friendly_name: "Screen casting",
|
friendly_name: "Sonos Idle",
|
||||||
media_content_type: "music",
|
supported_features: 64063,
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
|
||||||
media_artist: "Technohead",
|
|
||||||
supported_features: 1,
|
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "theater", "off", {
|
getEntity("media_player", "theater", "off", {
|
||||||
friendly_name: "Chromcast Idle",
|
friendly_name: "TV Off",
|
||||||
media_content_type: "movie",
|
supported_features: 161,
|
||||||
media_title: "Epic sax guy 10 hours",
|
|
||||||
app_name: "YouTube",
|
|
||||||
supported_features: 33,
|
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "android_cast", "playing", {
|
getEntity("media_player", "android_cast", "playing", {
|
||||||
friendly_name: "Player Off",
|
friendly_name: "Casting App",
|
||||||
media_title: "Android Screen Casting",
|
media_title: "Android Screen Casting",
|
||||||
app_name: "Screen Mirroring",
|
app_name: "Screen Mirroring",
|
||||||
supported_features: 21437,
|
// supported_features: 21437,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "unavailable", "unavailable", {
|
getEntity("media_player", "unavailable", "unavailable", {
|
||||||
friendly_name: "Player Unavailable",
|
friendly_name: "Player Unavailable",
|
||||||
|
@ -7,24 +7,24 @@ import { createMediaPlayerEntities } from "../data/media_players";
|
|||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "Skip, no pause",
|
heading: "Paused music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.bedroom
|
entity: media_player.music_paused
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Paused, music",
|
heading: "Playing music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.family_room
|
entity: media_player.music_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Paused, no play",
|
heading: "Playing stream",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.family_room_no_play
|
entity: media_player.stream_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -42,10 +42,10 @@ const CONFIGS = [
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Chromcast Idle",
|
heading: "Sonos Idle",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.lounge_room
|
entity: media_player.sonos_idle
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -11,17 +11,17 @@ const CONFIGS = [
|
|||||||
config: `
|
config: `
|
||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
- entity: media_player.bedroom
|
- entity: media_player.music_paused
|
||||||
name: Skip, no pause
|
name: Paused music
|
||||||
- entity: media_player.family_room
|
- entity: media_player.music_playing
|
||||||
name: Paused, music
|
name: Playing music
|
||||||
- entity: media_player.family_room_no_play
|
- entity: media_player.stream_playing
|
||||||
name: Paused, no play
|
name: Paused, no play
|
||||||
- entity: media_player.living_room
|
- entity: media_player.living_room
|
||||||
name: Pause, No skip, tvshow
|
name: Pause, No skip, tvshow
|
||||||
- entity: media_player.android_cast
|
- entity: media_player.android_cast
|
||||||
name: Screen casting
|
name: Screen casting
|
||||||
- entity: media_player.lounge_room
|
- entity: media_player.sonos_idle
|
||||||
name: Chromcast Idle
|
name: Chromcast Idle
|
||||||
- entity: media_player.theater
|
- entity: media_player.theater
|
||||||
name: Player Off
|
name: Player Off
|
||||||
|
@ -83,9 +83,7 @@ export const domainIcon = (domain: string, state?: string): string => {
|
|||||||
return state && state === "unlocked" ? "hass:lock-open" : "hass:lock";
|
return state && state === "unlocked" ? "hass:lock-open" : "hass:lock";
|
||||||
|
|
||||||
case "media_player":
|
case "media_player":
|
||||||
return state && state !== "off" && state !== "idle"
|
return state && state === "playing" ? "hass:cast-connected" : "hass:cast";
|
||||||
? "hass:cast-connected"
|
|
||||||
: "hass:cast";
|
|
||||||
|
|
||||||
case "zwave":
|
case "zwave":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -14,7 +14,6 @@ export const SUPPORT_SELECT_SOURCE = 2048;
|
|||||||
export const SUPPORT_STOP = 4096;
|
export const SUPPORT_STOP = 4096;
|
||||||
export const SUPPORTS_PLAY = 16384;
|
export const SUPPORTS_PLAY = 16384;
|
||||||
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||||
export const OFF_STATES = ["off", "idle"];
|
|
||||||
export const CONTRAST_RATIO = 3.5;
|
export const CONTRAST_RATIO = 3.5;
|
||||||
|
|
||||||
export interface MediaPlayerThumbnail {
|
export interface MediaPlayerThumbnail {
|
||||||
@ -56,9 +55,7 @@ export const computeMediaDescription = (stateObj: HassEntity): string => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
secondaryTitle = stateObj.attributes.app_name
|
secondaryTitle = stateObj.attributes.app_name || "";
|
||||||
? stateObj.attributes.app_name
|
|
||||||
: "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return secondaryTitle;
|
return secondaryTitle;
|
||||||
|
@ -33,7 +33,6 @@ import { findEntities } from "../common/find-entites";
|
|||||||
import { LovelaceConfig } from "../../../data/lovelace";
|
import { LovelaceConfig } from "../../../data/lovelace";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
||||||
import {
|
import {
|
||||||
OFF_STATES,
|
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_TURN_ON,
|
SUPPORT_TURN_ON,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
@ -49,6 +48,8 @@ import {
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../components/hui-marquee";
|
import "../components/hui-marquee";
|
||||||
|
// tslint:disable-next-line: no-duplicate-imports
|
||||||
|
import { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
|
||||||
|
|
||||||
function getContrastRatio(
|
function getContrastRatio(
|
||||||
rgb1: [number, number, number],
|
rgb1: [number, number, number],
|
||||||
@ -57,6 +58,11 @@ function getContrastRatio(
|
|||||||
return Math.round((contrast(rgb1, rgb2) + Number.EPSILON) * 100) / 100;
|
return Math.round((contrast(rgb1, rgb2) + Number.EPSILON) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ControlButton {
|
||||||
|
icon: string;
|
||||||
|
action: string;
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("hui-media-control-card")
|
@customElement("hui-media-control-card")
|
||||||
export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
@ -118,7 +124,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return;
|
return;
|
||||||
@ -130,7 +136,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
stateObj.state === "playing"
|
stateObj.state === "playing"
|
||||||
) {
|
) {
|
||||||
this._progressInterval = window.setInterval(
|
this._progressInterval = window.setInterval(
|
||||||
() => this._updateProgressBar(stateObj),
|
() => this._updateProgressBar(),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -147,7 +153,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
@ -162,7 +168,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imageStyle = {
|
const imageStyle = {
|
||||||
"background-image": `url(${this.hass.hassUrl(this._image)})`,
|
"background-image": this._image
|
||||||
|
? `url(${this.hass.hassUrl(this._image)})`
|
||||||
|
: "none",
|
||||||
width: `${this._cardHeight}px`,
|
width: `${this._cardHeight}px`,
|
||||||
"background-color": this._backgroundColor || "",
|
"background-color": this._backgroundColor || "",
|
||||||
};
|
};
|
||||||
@ -172,12 +180,19 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
width: `${this._cardHeight}px`,
|
width: `${this._cardHeight}px`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isOffState = OFF_STATES.includes(stateObj.state);
|
const state = stateObj.state;
|
||||||
|
|
||||||
|
const isOffState = state === "off";
|
||||||
const isUnavailable =
|
const isUnavailable =
|
||||||
stateObj.state === UNAVAILABLE ||
|
state === UNAVAILABLE ||
|
||||||
stateObj.state === UNKNOWN ||
|
state === UNKNOWN ||
|
||||||
(stateObj.state === "off" && !supportsFeature(stateObj, SUPPORT_TURN_ON));
|
(state === "off" && !supportsFeature(stateObj, SUPPORT_TURN_ON));
|
||||||
const hasNoImage = !this._image;
|
const hasNoImage = !this._image;
|
||||||
|
const controls = this._getControls();
|
||||||
|
const showControls =
|
||||||
|
controls && (!this._veryNarrow || isOffState || state === "idle");
|
||||||
|
|
||||||
|
const mediaDescription = computeMediaDescription(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
@ -215,7 +230,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
"no-image": hasNoImage,
|
"no-image": hasNoImage,
|
||||||
narrow: this._narrow && !this._veryNarrow,
|
narrow: this._narrow && !this._veryNarrow,
|
||||||
off: isOffState || isUnavailable,
|
off: isOffState || isUnavailable,
|
||||||
"no-progress": !this._showProgressBar && !this._veryNarrow,
|
"no-progress": this._veryNarrow || !this._showProgressBar,
|
||||||
|
"no-controls": !showControls,
|
||||||
})}"
|
})}"
|
||||||
style=${styleMap({ color: this._foregroundColor || "" })}
|
style=${styleMap({ color: this._foregroundColor || "" })}
|
||||||
>
|
>
|
||||||
@ -246,98 +262,35 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
: `${this._cardHeight - 40}px`,
|
: `${this._cardHeight - 40}px`,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${isOffState
|
${!mediaDescription && !stateObj.attributes.media_title
|
||||||
? ""
|
? ""
|
||||||
: html`
|
: html`
|
||||||
<div class="media-info">
|
<div class="media-info">
|
||||||
<div class="title">
|
|
||||||
<hui-marquee
|
<hui-marquee
|
||||||
.text=${stateObj.attributes.media_title ||
|
.text=${stateObj.attributes.media_title ||
|
||||||
computeMediaDescription(stateObj)}
|
mediaDescription}
|
||||||
.active=${this._marqueeActive}
|
.active=${this._marqueeActive}
|
||||||
@mouseover=${this._marqueeMouseOver}
|
@mouseover=${this._marqueeMouseOver}
|
||||||
@mouseleave=${this._marqueeMouseLeave}
|
@mouseleave=${this._marqueeMouseLeave}
|
||||||
></hui-marquee>
|
></hui-marquee>
|
||||||
</div>
|
|
||||||
${!stateObj.attributes.media_title
|
${!stateObj.attributes.media_title
|
||||||
? ""
|
? ""
|
||||||
: computeMediaDescription(stateObj)}
|
: mediaDescription}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
${this._veryNarrow && !isOffState
|
${!showControls
|
||||||
? ""
|
? ""
|
||||||
: html`
|
: html`
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div>
|
${controls!.map(
|
||||||
${(stateObj.state === "off" &&
|
(control) => html`
|
||||||
!supportsFeature(stateObj, SUPPORT_TURN_ON)) ||
|
|
||||||
!isOffState
|
|
||||||
? ""
|
|
||||||
: html`
|
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:power"
|
.icon=${control.icon}
|
||||||
.action=${stateObj.state === "off"
|
action=${control.action}
|
||||||
? "turn_on"
|
|
||||||
: "turn_off"}
|
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
`}
|
`
|
||||||
</div>
|
)}
|
||||||
${isOffState
|
|
||||||
? ""
|
|
||||||
: html`
|
|
||||||
<div class="playback-controls">
|
|
||||||
${!supportsFeature(
|
|
||||||
stateObj,
|
|
||||||
SUPPORT_PREVIOUS_TRACK
|
|
||||||
)
|
|
||||||
? ""
|
|
||||||
: html`
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:skip-previous"
|
|
||||||
.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"
|
|
||||||
.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"
|
|
||||||
.action=${"media_next_track"}
|
|
||||||
@click=${this._handleClick}
|
|
||||||
></paper-icon-button>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@ -346,7 +299,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
: html`
|
: html`
|
||||||
<paper-progress
|
<paper-progress
|
||||||
.max=${stateObj.attributes.media_duration}
|
.max=${stateObj.attributes.media_duration}
|
||||||
class="progress"
|
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
"--paper-progress-active-color":
|
"--paper-progress-active-color":
|
||||||
this._foregroundColor || "var(--accent-color)",
|
this._foregroundColor || "var(--accent-color)",
|
||||||
@ -354,8 +306,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
? "pointer"
|
? "pointer"
|
||||||
: "initial",
|
: "initial",
|
||||||
})}
|
})}
|
||||||
@click=${(e: MouseEvent) =>
|
@click=${this._handleSeek}
|
||||||
this._handleSeek(e, stateObj)}
|
|
||||||
></paper-progress>
|
></paper-progress>
|
||||||
`}
|
`}
|
||||||
`}
|
`}
|
||||||
@ -374,13 +325,20 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
if (!this._config || !this.hass || !changedProps.has("hass")) {
|
if (!this._config || !this.hass || !changedProps.has("hass")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
|
if (this._progressInterval) {
|
||||||
|
clearInterval(this._progressInterval);
|
||||||
|
this._progressInterval = undefined;
|
||||||
|
}
|
||||||
|
this._foregroundColor = undefined;
|
||||||
|
this._backgroundColor = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,13 +356,15 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updateProgressBar();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this._progressInterval &&
|
!this._progressInterval &&
|
||||||
this._showProgressBar &&
|
this._showProgressBar &&
|
||||||
stateObj.state === "playing"
|
stateObj.state === "playing"
|
||||||
) {
|
) {
|
||||||
this._progressInterval = window.setInterval(
|
this._progressInterval = window.setInterval(
|
||||||
() => this._updateProgressBar(stateObj),
|
() => this._updateProgressBar(),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
@ -427,16 +387,86 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
if (this._image !== oldImage) {
|
if (this._image !== oldImage) {
|
||||||
this._setColors();
|
this._setColors();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getControls(): ControlButton[] | undefined {
|
||||||
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = stateObj.state;
|
||||||
|
|
||||||
|
if (state === UNAVAILABLE || state === UNKNOWN) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === "off") {
|
||||||
|
return supportsFeature(stateObj, SUPPORT_TURN_ON)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: "hass:power",
|
||||||
|
action: "turn_on",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === "idle") {
|
||||||
|
return supportsFeature(stateObj, SUPPORTS_PLAY)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: "hass:play",
|
||||||
|
action: "media_play",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons: ControlButton[] = [];
|
||||||
|
|
||||||
|
if (supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:skip-previous",
|
||||||
|
action: "media_previous_track",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(state === "playing" &&
|
||||||
|
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||||
|
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
||||||
|
(state === "paused" && supportsFeature(stateObj, SUPPORTS_PLAY))
|
||||||
|
) {
|
||||||
|
buttons.push({
|
||||||
|
icon:
|
||||||
|
state !== "playing"
|
||||||
|
? "hass:play"
|
||||||
|
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||||
|
? "hass:pause"
|
||||||
|
: "hass:stop",
|
||||||
|
action: "media_play_pause",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsFeature(stateObj, SUPPORT_NEXT_TRACK)) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:skip-next",
|
||||||
|
action: "media_next_track",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons.length > 0 ? buttons : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private get _image() {
|
private get _image() {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -449,21 +479,20 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _showProgressBar() {
|
private get _showProgressBar() {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config || this._narrow) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
const stateObj = this._stateObj;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!OFF_STATES.includes(stateObj.state) &&
|
(stateObj.state === "playing" || stateObj.state === "paused") &&
|
||||||
stateObj.attributes.media_duration &&
|
"media_duration" in stateObj.attributes &&
|
||||||
stateObj.attributes.media_position &&
|
"media_position" in stateObj.attributes
|
||||||
!this._narrow
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +519,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
debounce(() => this._measureCard(), 250, false)
|
debounce(() => this._measureCard(), 250, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
this._resizeObserver.observe(this);
|
this._resizeObserver.observe(this.shadowRoot!.querySelector("ha-card")!);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleMoreInfo(): void {
|
private _handleMoreInfo(): void {
|
||||||
@ -500,18 +529,28 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleClick(e: MouseEvent): void {
|
private _handleClick(e: MouseEvent): void {
|
||||||
this.hass!.callService("media_player", (e.currentTarget! as any).action, {
|
this.hass!.callService(
|
||||||
|
"media_player",
|
||||||
|
(e.currentTarget! as PaperIconButtonElement).getAttribute("action")!,
|
||||||
|
{
|
||||||
entity_id: this._config!.entity,
|
entity_id: this._config!.entity,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateProgressBar(stateObj: MediaEntity): void {
|
private _updateProgressBar(): void {
|
||||||
if (this._progressBar) {
|
if (this._progressBar) {
|
||||||
this._progressBar.value = getCurrentProgress(stateObj);
|
this._progressBar.value = getCurrentProgress(this._stateObj!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleSeek(e: MouseEvent, stateObj: MediaEntity): void {
|
private get _stateObj(): MediaEntity | undefined {
|
||||||
|
return this.hass!.states[this._config!.entity] as MediaEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSeek(e: MouseEvent): void {
|
||||||
|
const stateObj = this._stateObj!;
|
||||||
|
|
||||||
if (!supportsFeature(stateObj, SUPPORT_SEEK)) {
|
if (!supportsFeature(stateObj, SUPPORT_SEEK)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -710,9 +749,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
height: 44px;
|
height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playPauseButton {
|
paper-icon-button[action="media_play"],
|
||||||
width: 56px !important;
|
paper-icon-button[action="media_play_pause"],
|
||||||
height: 56px !important;
|
paper-icon-button[action="turn_on"] {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-info {
|
.top-info {
|
||||||
@ -742,19 +783,16 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hui-marquee {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.title-controls {
|
.title-controls {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
paper-progress {
|
||||||
font-size: 1.2em;
|
|
||||||
margin: 0px 0 4px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: var(--paper-progress-height, 4px);
|
height: var(--paper-progress-height, 4px);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
@ -775,11 +813,6 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
height: 55px;
|
height: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.off.player,
|
|
||||||
.narrow.player {
|
|
||||||
padding-bottom: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.narrow .controls,
|
.narrow .controls,
|
||||||
.no-progress .controls {
|
.no-progress .controls {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
@ -790,12 +823,14 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.narrow .playPauseButton {
|
.narrow paper-icon-button[action="media_play"],
|
||||||
width: 50px !important;
|
.narrow paper-icon-button[action="media_play_pause"],
|
||||||
height: 50px !important;
|
.narrow paper-icon-button[action="turn_on"] {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-progress.player {
|
.no-progress.player:not(.no-controls) {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -8,13 +8,25 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
property,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
|
||||||
|
|
||||||
@customElement("hui-marquee")
|
@customElement("hui-marquee")
|
||||||
class HuiMarquee extends LitElement {
|
class HuiMarquee extends LitElement {
|
||||||
@property() public text?: string;
|
@property() public text?: string;
|
||||||
@property() public active?: boolean;
|
@property({ type: Boolean }) public active?: boolean;
|
||||||
@property() private _animating = false;
|
@property({ reflect: true, type: Boolean, attribute: "animating" })
|
||||||
|
private _animating = false;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
this.addEventListener("mouseover", () => this.classList.add("hovering"), {
|
||||||
|
// Capture because we need to run before a parent sets active on us.
|
||||||
|
// Hovering will disable the overflow, allowing us to calc if we overflow.
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
this.addEventListener("mouseout", () => this.classList.remove("hovering"));
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues): void {
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
||||||
@ -33,12 +45,7 @@ class HuiMarquee extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div class="marquee-inner" @animationiteration=${this._onIteration}>
|
||||||
class="marquee-inner ${classMap({
|
|
||||||
animating: this._animating,
|
|
||||||
})}"
|
|
||||||
@animationiteration=${this._onIteration}
|
|
||||||
>
|
|
||||||
<span>${this.text}</span>
|
<span>${this.text}</span>
|
||||||
${this._animating
|
${this._animating
|
||||||
? html`
|
? html`
|
||||||
@ -61,15 +68,29 @@ class HuiMarquee extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 25px;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marquee-inner {
|
.marquee-inner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
animation: marquee 10s linear infinite paused;
|
animation: marquee 10s linear infinite paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animating {
|
:host(.hovering) .marquee-inner {
|
||||||
|
text-overflow: initial;
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([animating]) .marquee-inner {
|
||||||
|
left: initial;
|
||||||
|
right: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([animating]) > div {
|
||||||
animation-play-state: running;
|
animation-play-state: running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
|
|||||||
import {
|
import {
|
||||||
SUPPORTS_PLAY,
|
SUPPORTS_PLAY,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
OFF_STATES,
|
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
} from "../../../data/media-player";
|
} from "../../../data/media-player";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
@ -68,7 +67,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow {
|
|||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.secondaryText=${this._computeMediaTitle(stateObj)}
|
.secondaryText=${this._computeMediaTitle(stateObj)}
|
||||||
>
|
>
|
||||||
${OFF_STATES.includes(stateObj.state)
|
${stateObj.state === "off" || stateObj.state === "idle"
|
||||||
? html`
|
? html`
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
${this.hass!.localize(`state.media_player.${stateObj.state}`) ||
|
${this.hass!.localize(`state.media_player.${stateObj.state}`) ||
|
||||||
|
@ -210,6 +210,7 @@ export type MediaEntity = HassEntityBase & {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
entity_picture_local?: string;
|
entity_picture_local?: string;
|
||||||
};
|
};
|
||||||
|
state: "playing" | "paused" | "idle" | "off" | "unavailable" | "unknown";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InputSelectEntity = HassEntityBase & {
|
export type InputSelectEntity = HassEntityBase & {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user