ha-frontend/src/cards/ha-media_player-card.html

338 lines
8.3 KiB
HTML

<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../../bower_components/paper-material/paper-material.html">
<link rel='import' href='../../bower_components/paper-icon-button/paper-icon-button.html'>
<link rel='import' href='../../bower_components/paper-progress/paper-progress.html'>
<link rel='import' href='../util/media-player-model.html'>
<dom-module id='ha-media_player-card'>
<template>
<style include="paper-material iron-flex iron-flex-alignment iron-positioning">
:host {
display: block;
position: relative;
font-size: 0px;
border-radius: 2px;
overflow: hidden;
}
.banner {
position: relative;
background-color: white;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.banner:before {
display: block;
content: "";
width: 100%;
/* removed .25% from 16:9 ratio to fix YT black bars */
padding-top: 56%;
transition: padding-top .8s;
}
.banner.no-cover {
background-position: center center;
background-image: url(/static/images/card_media_player_bg.png);
background-repeat: no-repeat;
background-color: var(--primary-color);
}
.banner.content-type-music:before {
padding-top: 100%;
}
.banner.no-cover:before {
padding-top: 88px;
}
.banner > .cover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
background-position: center center;
background-size: cover;
transition: opacity .8s;
opacity: 1;
}
.banner.is-off > .cover {
opacity: 0;
}
.banner > .caption {
@apply(--paper-font-caption);
position: absolute;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, var(--dark-secondary-opacity));
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
color: white;
transition: background-color .5s;
}
.banner.is-off > .caption {
background-color: initial;
}
.banner > .caption .title {
@apply(--paper-font-common-nowrap);
font-size: 1.2em;
margin: 8px 0 4px;
}
.progress {
width: 100%;
--paper-progress-active-color: var(--accent-color);
--paper-progress-container-color: #FFF;
}
.controls {
position: relative;
@apply(--paper-font-body1);
padding: 8px;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
background-color: white;
}
.controls paper-icon-button {
width: 44px;
height: 44px;
}
paper-icon-button {
opacity: var(--dark-primary-opacity);
}
paper-icon-button[disabled] {
opacity: var(--dark-disabled-opacity);
}
paper-icon-button.primary {
width: 56px !important;
height: 56px !important;
background-color: var(--primary-color);
color: white;
border-radius: 50%;
padding: 8px;
transition: background-color .5s;
}
paper-icon-button.primary[disabled] {
background-color: rgba(0, 0, 0, var(--dark-disabled-opacity));
}
[invisible] {
visibility: hidden !important;
}
</style>
<div class$='[[computeBannerClasses(playerObj)]]'>
<div class='cover' id='cover'></div>
<div class='caption'>
[[computeStateName(stateObj)]]
<div class='title'>[[playerObj.primaryText]]</div>
[[playerObj.secondaryText]]<br />
</div>
</div>
<paper-progress
max='[[stateObj.attributes.media_duration]]'
value='[[playbackPosition]]'
hidden$='[[computeHideProgress(playerObj)]]'
class='progress'
></paper-progress>
<div class='controls layout horizontal justified'>
<paper-icon-button
icon='mdi:power'
on-tap='handleTogglePower'
invisible$='[[computeHidePowerButton(playerObj)]]'
class='self-center secondary'
></paper-icon-button>
<div>
<paper-icon-button
icon='mdi:skip-previous'
invisible$='[[!playerObj.supportsPreviousTrack]]'
disabled='[[playerObj.isOff]]'
on-tap='handlePrevious'
></paper-icon-button>
<paper-icon-button
class='primary'
icon='[[computePlaybackControlIcon(playerObj)]]'
invisible$='[[!computePlaybackControlIcon(playerObj)]]'
disabled='[[playerObj.isOff]]'
on-tap='handlePlaybackControl'
></paper-icon-button>
<paper-icon-button
icon='mdi:skip-next'
invisible$='[[!playerObj.supportsNextTrack]]'
disabled='[[playerObj.isOff]]'
on-tap='handleNext'
></paper-icon-button>
</div>
<paper-icon-button
icon='mdi:dots-vertical'
on-tap='handleOpenMoreInfo'
class='self-center secondary'
></paper-icon-button>
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'ha-media_player-card',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
},
playerObj: {
type: Object,
computed: 'computePlayerObj(hass, stateObj)',
observer: 'playerObjChanged',
},
playbackControlIcon: {
type: String,
computed: 'computePlaybackControlIcon(playerObj)',
},
playbackPosition: {
type: Number,
},
/**
* The z-depth of the card, from 0-5.
*/
elevation: {
type: Number,
value: 1,
reflectToAttribute: true,
},
},
created: function () {
this.updatePlaybackPosition = this.updatePlaybackPosition.bind(this);
},
playerObjChanged: function (playerObj) {
var picture = playerObj.stateObj.attributes.entity_picture;
if (picture) {
this.$.cover.style.backgroundImage = 'url(' + picture + ')';
} else {
this.$.cover.style.backgroundImage = '';
}
if (playerObj.isPlaying) {
if (!this._positionTracking) {
this._positionTracking = setInterval(this.updatePlaybackPosition, 1000);
}
this.updatePlaybackPosition();
} else if (this._positionTracking) {
clearInterval(this._positionTracking);
this._positionTracking = null;
this.playbackPosition = 0;
}
},
updatePlaybackPosition: function () {
this.playbackPosition = this.playerObj.currentProgress;
},
computeBannerClasses: function (playerObj) {
var cls = 'banner';
if (playerObj.isOff || playerObj.isIdle) {
cls += ' is-off no-cover';
} else if (!playerObj.stateObj.attributes.entity_picture) {
cls += ' no-cover';
} else if (playerObj.stateObj.attributes.media_content_type === 'music') {
cls += ' content-type-music';
}
return cls;
},
computeHideProgress: function (playerObj) {
return !playerObj.showProgress;
},
computeHidePowerButton: function (playerObj) {
return playerObj.isOff ? !playerObj.supportsTurnOn : !playerObj.supportsTurnOff;
},
computePlayerObj: function (hass, stateObj) {
return new window.MediaPlayerEntity(hass, stateObj);
},
computePlaybackControlIcon: function (playerObj) {
if (playerObj.isPlaying) {
return playerObj.supportsPause ? 'mdi:pause' : 'mdi:stop';
} else if (playerObj.isPaused || playerObj.isOff || playerObj.isIdle) {
return playerObj.supportsPlay ? 'mdi:play' : null;
}
return '';
},
computeStateName: function (stateObj) {
return window.hassUtil.computeStateName(stateObj);
},
handleNext: function (ev) {
ev.stopPropagation();
this.playerObj.nextTrack();
},
handleOpenMoreInfo: function (ev) {
ev.stopPropagation();
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
},
handlePlaybackControl: function (ev) {
ev.stopPropagation();
this.playerObj.mediaPlayPause();
},
handlePrevious: function (ev) {
ev.stopPropagation();
this.playerObj.previousTrack();
},
handleTogglePower: function (ev) {
ev.stopPropagation();
this.playerObj.togglePower();
},
});
</script>