mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-29 04:06:35 +00:00
Media More Info: Convert to Lit Element (#6619)
* lit element * Remove Properties * review comments * This should be somewhat better.
This commit is contained in:
parent
21644ec889
commit
39f24c41ad
@ -21,6 +21,11 @@ export interface MediaPlayerThumbnail {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ControlButton {
|
||||||
|
icon: string;
|
||||||
|
action: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const getCurrentProgress = (stateObj: HassEntity): number => {
|
export const getCurrentProgress = (stateObj: HassEntity): number => {
|
||||||
let progress = stateObj.attributes.media_position;
|
let progress = stateObj.attributes.media_position;
|
||||||
|
|
||||||
|
@ -1,421 +0,0 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
|
||||||
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
|
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
|
||||||
import "../../../components/ha-paper-dropdown-menu";
|
|
||||||
import "../../../components/ha-paper-slider";
|
|
||||||
import "../../../components/ha-icon";
|
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
import HassMediaPlayerEntity from "../../../util/hass-media-player-model";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class MoreInfoMediaPlayer extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex iron-flex-alignment"></style>
|
|
||||||
<style>
|
|
||||||
.media-state {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon-button[highlight] {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
max-height: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.5s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-volume_level .volume {
|
|
||||||
max-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon.source-input {
|
|
||||||
padding: 7px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-paper-dropdown-menu.source-input {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class$="[[computeClassNames(stateObj)]]">
|
|
||||||
<div class="layout horizontal">
|
|
||||||
<div class="flex">
|
|
||||||
<ha-icon-button
|
|
||||||
icon="hass:power"
|
|
||||||
highlight$="[[playerObj.isOff]]"
|
|
||||||
on-click="handleTogglePower"
|
|
||||||
hidden$="[[computeHidePowerButton(playerObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<template
|
|
||||||
is="dom-if"
|
|
||||||
if="[[computeShowPlaybackControls(playerObj)]]"
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
icon="hass:skip-previous"
|
|
||||||
on-click="handlePrevious"
|
|
||||||
hidden$="[[!playerObj.supportsPreviousTrack]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
icon="[[computePlaybackControlIcon(playerObj)]]"
|
|
||||||
on-click="handlePlaybackControl"
|
|
||||||
hidden$="[[!computePlaybackControlIcon(playerObj)]]"
|
|
||||||
highlight=""
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
icon="hass:skip-next"
|
|
||||||
on-click="handleNext"
|
|
||||||
hidden$="[[!playerObj.supportsNextTrack]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- VOLUME -->
|
|
||||||
<div
|
|
||||||
class="volume_buttons center horizontal layout"
|
|
||||||
hidden$="[[computeHideVolumeButtons(playerObj)]]"
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
on-click="handleVolumeTap"
|
|
||||||
icon="hass:volume-off"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
id="volumeDown"
|
|
||||||
disabled$="[[playerObj.isMuted]]"
|
|
||||||
on-mousedown="handleVolumeDown"
|
|
||||||
on-touchstart="handleVolumeDown"
|
|
||||||
on-touchend="handleVolumeTouchEnd"
|
|
||||||
icon="hass:volume-medium"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
id="volumeUp"
|
|
||||||
disabled$="[[playerObj.isMuted]]"
|
|
||||||
on-mousedown="handleVolumeUp"
|
|
||||||
on-touchstart="handleVolumeUp"
|
|
||||||
on-touchend="handleVolumeTouchEnd"
|
|
||||||
icon="hass:volume-high"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="volume center horizontal layout"
|
|
||||||
hidden$="[[!playerObj.supportsVolumeSet]]"
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
on-click="handleVolumeTap"
|
|
||||||
hidden$="[[playerObj.supportsVolumeButtons]]"
|
|
||||||
icon="[[computeMuteVolumeIcon(playerObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-paper-slider
|
|
||||||
disabled$="[[playerObj.isMuted]]"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
value="[[playerObj.volumeSliderValue]]"
|
|
||||||
on-change="volumeSliderChanged"
|
|
||||||
class="flex"
|
|
||||||
ignore-bar-touch=""
|
|
||||||
dir="{{rtl}}"
|
|
||||||
>
|
|
||||||
</ha-paper-slider>
|
|
||||||
</div>
|
|
||||||
<!-- SOURCE PICKER -->
|
|
||||||
<div
|
|
||||||
class="controls layout horizontal justified"
|
|
||||||
hidden$="[[computeHideSelectSource(playerObj)]]"
|
|
||||||
>
|
|
||||||
<ha-icon class="source-input" icon="hass:login-variant"></ha-icon>
|
|
||||||
<ha-paper-dropdown-menu
|
|
||||||
class="flex source-input"
|
|
||||||
dynamic-align=""
|
|
||||||
label-float=""
|
|
||||||
label="[[localize('ui.card.media_player.source')]]"
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
attr-for-selected="item-name"
|
|
||||||
selected="[[playerObj.source]]"
|
|
||||||
on-selected-changed="handleSourceChanged"
|
|
||||||
>
|
|
||||||
<template is="dom-repeat" items="[[playerObj.sourceList]]">
|
|
||||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
|
||||||
</template>
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>
|
|
||||||
</div>
|
|
||||||
<!-- SOUND MODE PICKER -->
|
|
||||||
<template is="dom-if" if="[[!computeHideSelectSoundMode(playerObj)]]">
|
|
||||||
<div class="controls layout horizontal justified">
|
|
||||||
<ha-icon class="source-input" icon="hass:music-note"></ha-icon>
|
|
||||||
<ha-paper-dropdown-menu
|
|
||||||
class="flex source-input"
|
|
||||||
dynamic-align
|
|
||||||
label-float
|
|
||||||
label="[[localize('ui.card.media_player.sound_mode')]]"
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
attr-for-selected="item-name"
|
|
||||||
selected="[[playerObj.soundMode]]"
|
|
||||||
on-selected-changed="handleSoundModeChanged"
|
|
||||||
>
|
|
||||||
<template is="dom-repeat" items="[[playerObj.soundModeList]]">
|
|
||||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
|
||||||
</template>
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!-- TTS -->
|
|
||||||
<div
|
|
||||||
hidden$="[[computeHideTTS(ttsLoaded, playerObj)]]"
|
|
||||||
class="layout horizontal end"
|
|
||||||
>
|
|
||||||
<paper-input
|
|
||||||
id="ttsInput"
|
|
||||||
label="[[localize('ui.card.media_player.text_to_speak')]]"
|
|
||||||
class="flex"
|
|
||||||
value="{{ttsMessage}}"
|
|
||||||
on-keydown="ttsCheckForEnter"
|
|
||||||
></paper-input>
|
|
||||||
<ha-icon-button icon="hass:send" on-click="sendTTS"></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: Object,
|
|
||||||
playerObj: {
|
|
||||||
type: Object,
|
|
||||||
computed: "computePlayerObj(hass, stateObj)",
|
|
||||||
observer: "playerObjChanged",
|
|
||||||
},
|
|
||||||
|
|
||||||
ttsLoaded: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "computeTTSLoaded(hass)",
|
|
||||||
},
|
|
||||||
|
|
||||||
ttsMessage: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
rtl: {
|
|
||||||
type: String,
|
|
||||||
computed: "_computeRTLDirection(hass)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computePlayerObj(hass, stateObj) {
|
|
||||||
return new HassMediaPlayerEntity(hass, stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
playerObjChanged(newVal, oldVal) {
|
|
||||||
if (oldVal) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.fire("iron-resize");
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClassNames(stateObj) {
|
|
||||||
return attributeClassNames(stateObj, ["volume_level"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeMuteVolumeIcon(playerObj) {
|
|
||||||
return playerObj.isMuted ? "hass:volume-off" : "hass:volume-high";
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHideVolumeButtons(playerObj) {
|
|
||||||
return !playerObj.supportsVolumeButtons || playerObj.isOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeShowPlaybackControls(playerObj) {
|
|
||||||
return !playerObj.isOff && playerObj.hasMediaControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
computePlaybackControlIcon(playerObj) {
|
|
||||||
if (playerObj.isPlaying) {
|
|
||||||
return playerObj.supportsPause ? "hass:pause" : "hass:stop";
|
|
||||||
}
|
|
||||||
if (playerObj.hasMediaControl || playerObj.isOff || playerObj.isIdle) {
|
|
||||||
if (
|
|
||||||
playerObj.hasMediaControl &&
|
|
||||||
playerObj.supportsPause &&
|
|
||||||
!playerObj.isPaused
|
|
||||||
) {
|
|
||||||
return "hass:play-pause";
|
|
||||||
}
|
|
||||||
return playerObj.supportsPlay ? "hass:play" : null;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHidePowerButton(playerObj) {
|
|
||||||
return playerObj.isOff
|
|
||||||
? !playerObj.supportsTurnOn
|
|
||||||
: !playerObj.supportsTurnOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHideSelectSource(playerObj) {
|
|
||||||
return (
|
|
||||||
playerObj.isOff ||
|
|
||||||
!playerObj.supportsSelectSource ||
|
|
||||||
!playerObj.sourceList
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHideSelectSoundMode(playerObj) {
|
|
||||||
return (
|
|
||||||
playerObj.isOff ||
|
|
||||||
!playerObj.supportsSelectSoundMode ||
|
|
||||||
!playerObj.soundModeList
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeHideTTS(ttsLoaded, playerObj) {
|
|
||||||
return !ttsLoaded || !playerObj.supportsPlayMedia;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeTTSLoaded(hass) {
|
|
||||||
return isComponentLoaded(hass, "tts");
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTogglePower() {
|
|
||||||
this.playerObj.togglePower();
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePrevious() {
|
|
||||||
this.playerObj.previousTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePlaybackControl() {
|
|
||||||
this.playerObj.mediaPlayPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNext() {
|
|
||||||
this.playerObj.nextTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSourceChanged(ev) {
|
|
||||||
if (!this.playerObj) return;
|
|
||||||
|
|
||||||
const oldVal = this.playerObj.source;
|
|
||||||
const newVal = ev.detail.value;
|
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
|
||||||
|
|
||||||
this.playerObj.selectSource(newVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSoundModeChanged(ev) {
|
|
||||||
if (!this.playerObj) return;
|
|
||||||
|
|
||||||
const oldVal = this.playerObj.soundMode;
|
|
||||||
const newVal = ev.detail.value;
|
|
||||||
|
|
||||||
if (!newVal || oldVal === newVal) return;
|
|
||||||
|
|
||||||
this.playerObj.selectSoundMode(newVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeTap() {
|
|
||||||
if (!this.playerObj.supportsVolumeMute) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.playerObj.volumeMute(!this.playerObj.isMuted);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeTouchEnd(ev) {
|
|
||||||
/* when touch ends, we must prevent this from
|
|
||||||
* becoming a mousedown, up, click by emulation */
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeUp() {
|
|
||||||
const obj = this.$.volumeUp;
|
|
||||||
this.handleVolumeWorker("volume_up", obj, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeDown() {
|
|
||||||
const obj = this.$.volumeDown;
|
|
||||||
this.handleVolumeWorker("volume_down", obj, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleVolumeWorker(service, obj, force) {
|
|
||||||
if (force || (obj !== undefined && obj.pointerDown)) {
|
|
||||||
this.playerObj.callService(service);
|
|
||||||
setTimeout(() => this.handleVolumeWorker(service, obj, false), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeSliderChanged(ev) {
|
|
||||||
const volPercentage = parseFloat(ev.target.value);
|
|
||||||
const volume = volPercentage > 0 ? volPercentage / 100 : 0;
|
|
||||||
this.playerObj.setVolume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
ttsCheckForEnter(ev) {
|
|
||||||
if (ev.keyCode === 13) this.sendTTS();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTTS() {
|
|
||||||
const services = this.hass.services.tts;
|
|
||||||
const serviceKeys = Object.keys(services).sort();
|
|
||||||
let service;
|
|
||||||
let i;
|
|
||||||
|
|
||||||
for (i = 0; i < serviceKeys.length; i++) {
|
|
||||||
if (serviceKeys[i].indexOf("_say") !== -1) {
|
|
||||||
service = serviceKeys[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hass.callService("tts", service, {
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
message: this.ttsMessage,
|
|
||||||
});
|
|
||||||
this.ttsMessage = "";
|
|
||||||
this.$.ttsInput.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeRTLDirection(hass) {
|
|
||||||
return computeRTLDirection(hass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-media_player", MoreInfoMediaPlayer);
|
|
381
src/dialogs/more-info/controls/more-info-media_player.ts
Normal file
381
src/dialogs/more-info/controls/more-info-media_player.ts
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "@polymer/paper-input/paper-input";
|
||||||
|
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
customElement,
|
||||||
|
query,
|
||||||
|
} from "lit-element";
|
||||||
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
|
import { HomeAssistant, MediaEntity } from "../../../types";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import { UNAVAILABLE_STATES, UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
||||||
|
import {
|
||||||
|
SUPPORT_TURN_ON,
|
||||||
|
SUPPORT_TURN_OFF,
|
||||||
|
SUPPORTS_PLAY,
|
||||||
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
|
SUPPORT_PAUSE,
|
||||||
|
SUPPORT_STOP,
|
||||||
|
SUPPORT_NEXT_TRACK,
|
||||||
|
SUPPORT_VOLUME_MUTE,
|
||||||
|
SUPPORT_VOLUME_SET,
|
||||||
|
SUPPORT_VOLUME_BUTTONS,
|
||||||
|
SUPPORT_SELECT_SOURCE,
|
||||||
|
SUPPORT_SELECT_SOUND_MODE,
|
||||||
|
SUPPORT_PLAY_MEDIA,
|
||||||
|
ControlButton,
|
||||||
|
} from "../../../data/media-player";
|
||||||
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
|
|
||||||
|
import "../../../components/ha-paper-dropdown-menu";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-slider";
|
||||||
|
import "../../../components/ha-icon";
|
||||||
|
|
||||||
|
@customElement("more-info-media_player")
|
||||||
|
class MoreInfoMediaPlayer extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: MediaEntity;
|
||||||
|
|
||||||
|
@query("#ttsInput") private _ttsInput?: HTMLInputElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls = this._getControls();
|
||||||
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${!controls
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<div class="controls">
|
||||||
|
${controls!.map(
|
||||||
|
(control) => html`
|
||||||
|
<ha-icon-button
|
||||||
|
action=${control.action}
|
||||||
|
.icon=${control.icon}
|
||||||
|
@click=${this._handleClick}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
${(supportsFeature(stateObj, SUPPORT_VOLUME_SET) ||
|
||||||
|
supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)) &&
|
||||||
|
![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state)
|
||||||
|
? html`
|
||||||
|
<div class="volume">
|
||||||
|
${supportsFeature(stateObj, SUPPORT_VOLUME_MUTE)
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
.icon=${stateObj.attributes.is_volume_muted
|
||||||
|
? "hass:volume-off"
|
||||||
|
: "hass:volume-high"}
|
||||||
|
@click=${this._toggleMute}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${supportsFeature(stateObj, SUPPORT_VOLUME_SET)
|
||||||
|
? html`
|
||||||
|
<ha-slider
|
||||||
|
id="input"
|
||||||
|
pin
|
||||||
|
ignore-bar-touch
|
||||||
|
.dir=${computeRTLDirection(this.hass!)}
|
||||||
|
.value=${Number(stateObj.attributes.volume_level) * 100}
|
||||||
|
@change=${this._selectedValueChanged}
|
||||||
|
></ha-slider>
|
||||||
|
`
|
||||||
|
: supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
action="volume_down"
|
||||||
|
icon="hass:volume-minus"
|
||||||
|
@click=${this._handleClick}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
action="volume_up"
|
||||||
|
icon="hass:volume-plus"
|
||||||
|
@click=${this._handleClick}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${stateObj.state !== "off" &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_SELECT_SOURCE) &&
|
||||||
|
stateObj.attributes.source_list?.length
|
||||||
|
? html`
|
||||||
|
<div class="source-input">
|
||||||
|
<ha-icon class="source-input" icon="hass:login-variant"></ha-icon>
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
.label=${this.hass.localize("ui.card.media_player.source")}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
attr-for-selected="item-name"
|
||||||
|
.selected=${stateObj.attributes.source!}
|
||||||
|
@iron-select=${this._handleSourceChanged}
|
||||||
|
>
|
||||||
|
${stateObj.attributes.source_list!.map(
|
||||||
|
(source) =>
|
||||||
|
html`
|
||||||
|
<paper-item .itemName=${source}>${source}</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${supportsFeature(stateObj, SUPPORT_SELECT_SOUND_MODE) &&
|
||||||
|
stateObj.attributes.sound_mode_list?.length
|
||||||
|
? html`
|
||||||
|
<div class="sound-input">
|
||||||
|
<ha-icon icon="hass:music-note"></ha-icon>
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
dynamic-align
|
||||||
|
label-float
|
||||||
|
.label=${this.hass.localize("ui.card.media_player.sound_mode")}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
attr-for-selected="item-name"
|
||||||
|
.selected=${stateObj.attributes.sound_mode!}
|
||||||
|
@iron-select=${this._handleSoundModeChanged}
|
||||||
|
>
|
||||||
|
${stateObj.attributes.sound_mode_list.map(
|
||||||
|
(mode) => html`
|
||||||
|
<paper-item itemName=${mode}>${mode}</paper-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${isComponentLoaded(this.hass, "tts") &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_PLAY_MEDIA)
|
||||||
|
? html`
|
||||||
|
<div class="tts">
|
||||||
|
<paper-input
|
||||||
|
id="ttsInput"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.text_to_speak"
|
||||||
|
)}
|
||||||
|
@keydown=${this._ttsCheckForEnter}
|
||||||
|
></paper-input>
|
||||||
|
<ha-icon-button icon="hass:send" @click=${
|
||||||
|
this._sendTTS
|
||||||
|
}></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
ha-icon-button[action="turn_off"],
|
||||||
|
ha-icon-button[action="turn_on"],
|
||||||
|
ha-slider,
|
||||||
|
#ttsInput {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume,
|
||||||
|
.controls,
|
||||||
|
.source-input,
|
||||||
|
.sound-input,
|
||||||
|
.tts {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-input ha-icon,
|
||||||
|
.sound-input ha-icon {
|
||||||
|
padding: 7px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-input ha-paper-dropdown-menu,
|
||||||
|
.sound-input ha-paper-dropdown-menu {
|
||||||
|
margin-left: 10px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getControls(): ControlButton[] | undefined {
|
||||||
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = stateObj.state;
|
||||||
|
|
||||||
|
if (UNAVAILABLE_STATES.includes(state)) {
|
||||||
|
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_TURN_OFF)) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:power",
|
||||||
|
action: "turn_off",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _handleClick(e: MouseEvent): void {
|
||||||
|
this.hass!.callService(
|
||||||
|
"media_player",
|
||||||
|
(e.currentTarget! as HTMLElement).getAttribute("action")!,
|
||||||
|
{
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleMute() {
|
||||||
|
this.hass!.callService("media_player", "volume_mute", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
is_volume_muted: !this.stateObj!.attributes.is_volume_muted,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selectedValueChanged(e: Event): void {
|
||||||
|
this.hass!.callService("media_player", "volume_set", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
volume_level:
|
||||||
|
Number((e.currentTarget! as HTMLElement).getAttribute("value")!) / 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSourceChanged(e: CustomEvent) {
|
||||||
|
const newVal = e.detail.value;
|
||||||
|
|
||||||
|
if (!newVal || this.stateObj!.attributes.source === newVal) return;
|
||||||
|
|
||||||
|
this.hass.callService("media_player", "select_source", {
|
||||||
|
source: newVal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSoundModeChanged(e: CustomEvent) {
|
||||||
|
const newVal = e.detail.value;
|
||||||
|
|
||||||
|
if (!newVal || this.stateObj?.attributes.sound_mode === newVal) return;
|
||||||
|
|
||||||
|
this.hass.callService("media_player", "select_sound_mode", {
|
||||||
|
sound_mode: newVal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _ttsCheckForEnter(e: KeyboardEvent) {
|
||||||
|
if (e.keyCode === 13) this._sendTTS();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sendTTS() {
|
||||||
|
const ttsInput = this._ttsInput;
|
||||||
|
if (!ttsInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const services = this.hass.services.tts;
|
||||||
|
const serviceKeys = Object.keys(services).sort();
|
||||||
|
|
||||||
|
const service = serviceKeys.find((key) => key.indexOf("_say") !== -1);
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hass.callService("tts", service, {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
message: ttsInput.value,
|
||||||
|
});
|
||||||
|
ttsInput.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-media_player": MoreInfoMediaPlayer;
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,7 @@ import {
|
|||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
SUPPORT_TURN_OFF,
|
SUPPORT_TURN_OFF,
|
||||||
SUPPORT_TURN_ON,
|
SUPPORT_TURN_ON,
|
||||||
|
ControlButton,
|
||||||
} from "../../../data/media-player";
|
} from "../../../data/media-player";
|
||||||
import type { HomeAssistant, MediaEntity } from "../../../types";
|
import type { HomeAssistant, MediaEntity } from "../../../types";
|
||||||
import { contrast } from "../common/color/contrast";
|
import { contrast } from "../common/color/contrast";
|
||||||
@ -157,11 +158,6 @@ const customGenerator = (colors: Swatch[]) => {
|
|||||||
return [foregroundColor, backgroundColor.hex];
|
return [foregroundColor, backgroundColor.hex];
|
||||||
};
|
};
|
||||||
|
|
||||||
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> {
|
||||||
|
@ -287,6 +287,12 @@ export type MediaEntity = HassEntityBase & {
|
|||||||
media_title: string;
|
media_title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
entity_picture_local?: string;
|
entity_picture_local?: string;
|
||||||
|
is_volume_muted?: boolean;
|
||||||
|
volume_level?: number;
|
||||||
|
source?: string;
|
||||||
|
source_list?: string[];
|
||||||
|
sound_mode?: string;
|
||||||
|
sound_mode_list?: string[];
|
||||||
};
|
};
|
||||||
state:
|
state:
|
||||||
| "playing"
|
| "playing"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user