mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Merge pull request #158 from balloob/refactor-media-player
Initial refactor media player
This commit is contained in:
commit
2ea195a5d2
@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "1cc966bcef26a859d053bd5c46769a99"
|
||||
VERSION = "d0b5982b1bc41a96b8a4f49aaa92af5d"
|
||||
|
@ -16984,7 +16984,7 @@ window.hass.uiUtil.domainIcon = function(domain, state) {
|
||||
case "media_player":
|
||||
var icon = "hardware:cast";
|
||||
|
||||
if (state && state !== "idle") {
|
||||
if (state && state !== "off" && state !== 'idle') {
|
||||
icon += "-connected";
|
||||
}
|
||||
|
||||
@ -22312,8 +22312,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<div class="horizontal justified layout">
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
<div class="state">
|
||||
<div class="main-text">[[computePrimaryText(stateObj)]]</div>
|
||||
<div class="secondary-text">[[computeSecondaryText(stateObj)]]</div>
|
||||
<div class="main-text">[[computePrimaryText(stateObj, isPlaying)]]</div>
|
||||
<div class="secondary-text">[[computeSecondaryText(stateObj, isPlaying)]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -22321,6 +22321,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var PLAYING_STATES = ['playing', 'paused'];
|
||||
Polymer({
|
||||
is: 'state-card-media_player',
|
||||
|
||||
@ -22328,14 +22329,41 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsPlaying(stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computePrimaryText: function(stateObj) {
|
||||
return stateObj.attributes.media_title || stateObj.stateDisplay;
|
||||
computeIsPlaying: function(stateObj) {
|
||||
return PLAYING_STATES.indexOf(stateObj.state) !== -1;
|
||||
},
|
||||
|
||||
computeSecondaryText: function(stateObj) {
|
||||
return stateObj.attributes.media_title ? stateObj.stateDisplay : '';
|
||||
computePrimaryText: function(stateObj, isPlaying) {
|
||||
return isPlaying ? stateObj.attributes.media_title : stateObj.stateDisplay;
|
||||
},
|
||||
|
||||
computeSecondaryText: function(stateObj, isPlaying) {
|
||||
var text;
|
||||
|
||||
if (stateObj.attributes.media_content_type == 'music') {
|
||||
return stateObj.attributes.media_artist;
|
||||
|
||||
} else if (stateObj.attributes.media_content_type == 'tvshow') {
|
||||
text = stateObj.attributes.media_series_title;
|
||||
|
||||
if (stateObj.attributes.media_season && stateObj.attributes.media_episode) {
|
||||
text += ' S' + stateObj.attributes.media_season + 'E' + stateObj.attributes.media_episode;
|
||||
}
|
||||
return text;
|
||||
|
||||
} else if (stateObj.attributes.app_name) {
|
||||
return stateObj.attributes.app_name;
|
||||
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
@ -22353,6 +22381,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
width: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -25819,8 +25848,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* Accent the power button because the user should use that first */
|
||||
paper-icon-button[focus] {
|
||||
paper-icon-button[highlight] {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
@ -25832,7 +25860,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
.has-media_volume .volume {
|
||||
.has-volume_level .volume {
|
||||
max-height: 40px;
|
||||
}
|
||||
</style>
|
||||
@ -25840,19 +25868,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
<div class$="[[computeClassNames(stateObj)]]">
|
||||
<div class="layout horizontal">
|
||||
<div class="flex">
|
||||
<paper-icon-button icon="power-settings-new" focus$="[[isIdle]]" on-tap="handleTogglePower"></paper-icon-button>
|
||||
<paper-icon-button icon="power-settings-new" highlight$="[[isOff]]" on-tap="handleTogglePower" hidden$="[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]"></paper-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<template is="dom-if" if="[[!isIdle]]">
|
||||
<paper-icon-button icon="av:skip-previous" on-tap="handlePrevious"></paper-icon-button>
|
||||
<paper-icon-button icon="[[computePlayPauseIcon(stateObj)]]" focus$="" on-tap="handlePlayPause"></paper-icon-button>
|
||||
<paper-icon-button icon="av:skip-next" on-tap="handleNext"></paper-icon-button>
|
||||
<template is="dom-if" if="[[!isOff]]">
|
||||
<paper-icon-button icon="av:skip-previous" on-tap="handlePrevious" hidden$="[[!supportsPreviousTrack]]"></paper-icon-button>
|
||||
<paper-icon-button icon="[[computePlaybackControlIcon(stateObj)]]" on-tap="handlePlaybackControl" highlight=""></paper-icon-button>
|
||||
<paper-icon-button icon="av:skip-next" on-tap="handleNext" hidden$="[[!supportsNextTrack]]"></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="volume center horizontal layout">
|
||||
<div class="volume center horizontal layout" hidden$="[[!supportsVolumeSet]]">
|
||||
<paper-icon-button on-tap="handleVolumeTap" icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button>
|
||||
<paper-slider hidden="[[isMuted]]" min="0" max="100" value="{{volumeSliderValue}}" on-change="volumeSliderChanged" class="flex">
|
||||
<paper-slider disabled$="[[isMuted]]" min="0" max="100" value="[[volumeSliderValue]]" on-change="volumeSliderChanged" class="flex">
|
||||
</paper-slider>
|
||||
</div>
|
||||
</div>
|
||||
@ -25863,7 +25891,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var ATTRIBUTE_CLASSES = ['media_volume'];
|
||||
var ATTRIBUTE_CLASSES = ['volume_level'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-media_player',
|
||||
@ -25874,9 +25902,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
isIdle: {
|
||||
isOff: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsIdle(stateObj)',
|
||||
value: false,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isMuted: {
|
||||
@ -25887,13 +25920,58 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
volumeSliderValue: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
}
|
||||
},
|
||||
|
||||
supportsPause: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeSet: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeMute: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsPreviousTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsNextTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOff: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
this.volumeSliderValue = newVal.attributes.media_volume * 100;
|
||||
this.isMuted = newVal.attributes.media_is_volume_muted;
|
||||
this.isOff = newVal.state == 'off';
|
||||
this.isPlaying = newVal.state == 'playing';
|
||||
this.volumeSliderValue = newVal.attributes.volume_level * 100;
|
||||
this.isMuted = newVal.attributes.is_volume_muted;
|
||||
this.supportsPause = (newVal.attributes.supported_media_commands & 1) !== 0;
|
||||
this.supportsVolumeSet = (newVal.attributes.supported_media_commands & 4) !== 0;
|
||||
this.supportsVolumeMute = (newVal.attributes.supported_media_commands & 8) !== 0;
|
||||
this.supportsPreviousTrack = (newVal.attributes.supported_media_commands & 16) !== 0;
|
||||
this.supportsNextTrack = (newVal.attributes.supported_media_commands & 32) !== 0;
|
||||
this.supportsTurnOn = (newVal.attributes.supported_media_commands & 128) !== 0;
|
||||
this.supportsTurnOff = (newVal.attributes.supported_media_commands & 256) !== 0;
|
||||
}
|
||||
|
||||
this.debounce('more-info-volume-animation-finish', function() {
|
||||
@ -25905,35 +25983,37 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
|
||||
},
|
||||
|
||||
computeMediaState: function(stateObj) {
|
||||
return stateObj.state == 'idle' ? 'idle' : stateObj.attributes.media_state;
|
||||
},
|
||||
|
||||
computeIsIdle: function(stateObj) {
|
||||
return stateObj.state == 'idle';
|
||||
},
|
||||
|
||||
computePowerButtonCaption: function(isIdle) {
|
||||
return isIdle ? 'Turn on' : 'Turn off';
|
||||
computeIsOff: function(stateObj) {
|
||||
return stateObj.state == 'off';
|
||||
},
|
||||
|
||||
computeMuteVolumeIcon: function(isMuted) {
|
||||
return isMuted ? 'av:volume-off' : 'av:volume-up';
|
||||
},
|
||||
|
||||
computePlayPauseIcon: function(stateObj) {
|
||||
return stateObj.attributes.media_state == 'playing' ? 'av:pause' : 'av:play-arrow';
|
||||
computePlaybackControlIcon: function(stateObj) {
|
||||
if (this.isPlaying) {
|
||||
return this.supportsPause ? 'av:pause' : 'av:stop';
|
||||
}
|
||||
return 'av:play-arrow';
|
||||
},
|
||||
|
||||
computeHidePowerButton: function(isOff, supportsTurnOn, supportsTurnOff) {
|
||||
return isOff && !supportsTurnOn || !isOff && supportsTurnOff;
|
||||
},
|
||||
|
||||
handleTogglePower: function() {
|
||||
this.callService(this.isIdle ? 'turn_on' : 'turn_off');
|
||||
this.callService(this.isOff ? 'turn_on' : 'turn_off');
|
||||
},
|
||||
|
||||
handlePrevious: function() {
|
||||
this.callService('media_prev_track');
|
||||
this.callService('media_previous_track');
|
||||
},
|
||||
|
||||
handlePlayPause: function() {
|
||||
handlePlaybackControl: function() {
|
||||
if (this.isPlaying && !this.supportsPause) {
|
||||
alert('This case is not supported yet');
|
||||
}
|
||||
this.callService('media_play_pause');
|
||||
},
|
||||
|
||||
@ -25942,14 +26022,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||
},
|
||||
|
||||
handleVolumeTap: function() {
|
||||
this.callService('volume_mute', { mute: !this.isMuted });
|
||||
if (!this.supportsVolumeMute) {
|
||||
return;
|
||||
}
|
||||
this.callService('volume_mute', { is_volume_muted: !this.isMuted });
|
||||
},
|
||||
|
||||
volumeSliderChanged: function(ev) {
|
||||
var volPercentage = parseFloat(ev.target.value);
|
||||
var vol = volPercentage > 0 ? volPercentage / 100 : 0;
|
||||
|
||||
this.callService('volume_set', { volume: vol });
|
||||
this.callService('volume_set', { volume_level: vol });
|
||||
},
|
||||
|
||||
callService: function(service, data) {
|
||||
|
@ -33,8 +33,8 @@
|
||||
<div class='horizontal justified layout'>
|
||||
<state-info state-obj="[[stateObj]]"></state-info>
|
||||
<div class='state'>
|
||||
<div class='main-text'>[[computePrimaryText(stateObj)]]</div>
|
||||
<div class='secondary-text'>[[computeSecondaryText(stateObj)]]</div>
|
||||
<div class='main-text'>[[computePrimaryText(stateObj, isPlaying)]]</div>
|
||||
<div class='secondary-text'>[[computeSecondaryText(stateObj, isPlaying)]]</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -42,6 +42,7 @@
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var PLAYING_STATES = ['playing', 'paused'];
|
||||
Polymer({
|
||||
is: 'state-card-media_player',
|
||||
|
||||
@ -49,14 +50,41 @@
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsPlaying(stateObj)',
|
||||
},
|
||||
},
|
||||
|
||||
computePrimaryText: function(stateObj) {
|
||||
return stateObj.attributes.media_title || stateObj.stateDisplay;
|
||||
computeIsPlaying: function(stateObj) {
|
||||
return PLAYING_STATES.indexOf(stateObj.state) !== -1;
|
||||
},
|
||||
|
||||
computeSecondaryText: function(stateObj) {
|
||||
return stateObj.attributes.media_title ? stateObj.stateDisplay : '';
|
||||
computePrimaryText: function(stateObj, isPlaying) {
|
||||
return isPlaying ? stateObj.attributes.media_title : stateObj.stateDisplay;
|
||||
},
|
||||
|
||||
computeSecondaryText: function(stateObj, isPlaying) {
|
||||
var text;
|
||||
|
||||
if (stateObj.attributes.media_content_type == 'music') {
|
||||
return stateObj.attributes.media_artist;
|
||||
|
||||
} else if (stateObj.attributes.media_content_type == 'tvshow') {
|
||||
text = stateObj.attributes.media_series_title;
|
||||
|
||||
if (stateObj.attributes.media_season && stateObj.attributes.media_episode) {
|
||||
text += ' S' + stateObj.attributes.media_season + 'E' + stateObj.attributes.media_episode;
|
||||
}
|
||||
return text;
|
||||
|
||||
} else if (stateObj.attributes.app_name) {
|
||||
return stateObj.attributes.app_name;
|
||||
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
@ -15,6 +15,7 @@
|
||||
width: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -8,8 +8,7 @@
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* Accent the power button because the user should use that first */
|
||||
paper-icon-button[focus] {
|
||||
paper-icon-button[highlight] {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
@ -21,7 +20,7 @@
|
||||
transition: max-height .5s ease-in;
|
||||
}
|
||||
|
||||
.has-media_volume .volume {
|
||||
.has-volume_level .volume {
|
||||
max-height: 40px;
|
||||
}
|
||||
</style>
|
||||
@ -29,25 +28,26 @@
|
||||
<div class$='[[computeClassNames(stateObj)]]'>
|
||||
<div class='layout horizontal'>
|
||||
<div class='flex'>
|
||||
<paper-icon-button icon='power-settings-new' focus$='[[isIdle]]'
|
||||
on-tap='handleTogglePower'></paper-icon-button>
|
||||
<paper-icon-button icon='power-settings-new' highlight$='[[isOff]]'
|
||||
on-tap='handleTogglePower'
|
||||
hidden$='[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]'></paper-icon-button>
|
||||
</div>
|
||||
<div>
|
||||
<template is='dom-if' if='[[!isIdle]]'>
|
||||
<paper-icon-button icon='av:skip-previous'
|
||||
on-tap='handlePrevious'></paper-icon-button>
|
||||
<paper-icon-button icon='[[computePlayPauseIcon(stateObj)]]' focus$
|
||||
on-tap='handlePlayPause'></paper-icon-button>
|
||||
<paper-icon-button icon='av:skip-next'
|
||||
on-tap='handleNext'></paper-icon-button>
|
||||
<template is='dom-if' if='[[!isOff]]'>
|
||||
<paper-icon-button icon='av:skip-previous' on-tap='handlePrevious'
|
||||
hidden$='[[!supportsPreviousTrack]]'></paper-icon-button>
|
||||
<paper-icon-button icon='[[computePlaybackControlIcon(stateObj)]]'
|
||||
on-tap='handlePlaybackControl' highlight></paper-icon-button>
|
||||
<paper-icon-button icon='av:skip-next' on-tap='handleNext'
|
||||
hidden$='[[!supportsNextTrack]]'></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class='volume center horizontal layout'>
|
||||
<div class='volume center horizontal layout' hidden$='[[!supportsVolumeSet]]'>
|
||||
<paper-icon-button on-tap="handleVolumeTap"
|
||||
icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button>
|
||||
<paper-slider hidden='[[isMuted]]'
|
||||
min='0' max='100' value='{{volumeSliderValue}}'
|
||||
<paper-slider disabled$='[[isMuted]]'
|
||||
min='0' max='100' value='[[volumeSliderValue]]'
|
||||
on-change='volumeSliderChanged' class='flex'>
|
||||
</paper-slider>
|
||||
</div>
|
||||
@ -59,7 +59,7 @@
|
||||
(function() {
|
||||
var serviceActions = window.hass.serviceActions;
|
||||
var uiUtil = window.hass.uiUtil;
|
||||
var ATTRIBUTE_CLASSES = ['media_volume'];
|
||||
var ATTRIBUTE_CLASSES = ['volume_level'];
|
||||
|
||||
Polymer({
|
||||
is: 'more-info-media_player',
|
||||
@ -70,9 +70,14 @@
|
||||
observer: 'stateObjChanged',
|
||||
},
|
||||
|
||||
isIdle: {
|
||||
isOff: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsIdle(stateObj)',
|
||||
value: false,
|
||||
},
|
||||
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isMuted: {
|
||||
@ -83,13 +88,58 @@
|
||||
volumeSliderValue: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
}
|
||||
},
|
||||
|
||||
supportsPause: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeSet: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsVolumeMute: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsPreviousTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsNextTrack: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOn: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
supportsTurnOff: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
stateObjChanged: function(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
this.volumeSliderValue = newVal.attributes.media_volume * 100;
|
||||
this.isMuted = newVal.attributes.media_is_volume_muted;
|
||||
this.isOff = newVal.state == 'off';
|
||||
this.isPlaying = newVal.state == 'playing';
|
||||
this.volumeSliderValue = newVal.attributes.volume_level * 100;
|
||||
this.isMuted = newVal.attributes.is_volume_muted;
|
||||
this.supportsPause = (newVal.attributes.supported_media_commands & 1) !== 0;
|
||||
this.supportsVolumeSet = (newVal.attributes.supported_media_commands & 4) !== 0;
|
||||
this.supportsVolumeMute = (newVal.attributes.supported_media_commands & 8) !== 0;
|
||||
this.supportsPreviousTrack = (newVal.attributes.supported_media_commands & 16) !== 0;
|
||||
this.supportsNextTrack = (newVal.attributes.supported_media_commands & 32) !== 0;
|
||||
this.supportsTurnOn = (newVal.attributes.supported_media_commands & 128) !== 0;
|
||||
this.supportsTurnOff = (newVal.attributes.supported_media_commands & 256) !== 0;
|
||||
}
|
||||
|
||||
this.debounce('more-info-volume-animation-finish', function() {
|
||||
@ -101,35 +151,37 @@
|
||||
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
|
||||
},
|
||||
|
||||
computeMediaState: function(stateObj) {
|
||||
return stateObj.state == 'idle' ? 'idle' : stateObj.attributes.media_state;
|
||||
},
|
||||
|
||||
computeIsIdle: function(stateObj) {
|
||||
return stateObj.state == 'idle';
|
||||
},
|
||||
|
||||
computePowerButtonCaption: function(isIdle) {
|
||||
return isIdle ? 'Turn on' : 'Turn off';
|
||||
computeIsOff: function(stateObj) {
|
||||
return stateObj.state == 'off';
|
||||
},
|
||||
|
||||
computeMuteVolumeIcon: function(isMuted) {
|
||||
return isMuted ? 'av:volume-off' : 'av:volume-up';
|
||||
},
|
||||
|
||||
computePlayPauseIcon: function(stateObj) {
|
||||
return stateObj.attributes.media_state == 'playing' ? 'av:pause' : 'av:play-arrow';
|
||||
computePlaybackControlIcon: function(stateObj) {
|
||||
if (this.isPlaying) {
|
||||
return this.supportsPause ? 'av:pause' : 'av:stop';
|
||||
}
|
||||
return 'av:play-arrow';
|
||||
},
|
||||
|
||||
computeHidePowerButton: function(isOff, supportsTurnOn, supportsTurnOff) {
|
||||
return isOff && !supportsTurnOn || !isOff && supportsTurnOff;
|
||||
},
|
||||
|
||||
handleTogglePower: function() {
|
||||
this.callService(this.isIdle ? 'turn_on' : 'turn_off');
|
||||
this.callService(this.isOff ? 'turn_on' : 'turn_off');
|
||||
},
|
||||
|
||||
handlePrevious: function() {
|
||||
this.callService('media_prev_track');
|
||||
this.callService('media_previous_track');
|
||||
},
|
||||
|
||||
handlePlayPause: function() {
|
||||
handlePlaybackControl: function() {
|
||||
if (this.isPlaying && !this.supportsPause) {
|
||||
alert('This case is not supported yet');
|
||||
}
|
||||
this.callService('media_play_pause');
|
||||
},
|
||||
|
||||
@ -138,14 +190,16 @@
|
||||
},
|
||||
|
||||
handleVolumeTap: function() {
|
||||
this.callService('volume_mute', { mute: !this.isMuted });
|
||||
if (!this.supportsVolumeMute) {
|
||||
return;
|
||||
}
|
||||
this.callService('volume_mute', { is_volume_muted: !this.isMuted });
|
||||
},
|
||||
|
||||
volumeSliderChanged: function(ev) {
|
||||
var volPercentage = parseFloat(ev.target.value);
|
||||
var vol = volPercentage > 0 ? volPercentage / 100 : 0;
|
||||
|
||||
this.callService('volume_set', { volume: vol });
|
||||
this.callService('volume_set', { volume_level: vol });
|
||||
},
|
||||
|
||||
callService: function(service, data) {
|
||||
|
@ -48,7 +48,7 @@ window.hass.uiUtil.domainIcon = function(domain, state) {
|
||||
case "media_player":
|
||||
var icon = "hardware:cast";
|
||||
|
||||
if (state && state !== "idle") {
|
||||
if (state && state !== "off" && state !== 'idle') {
|
||||
icon += "-connected";
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
SERVICE_MEDIA_PLAY_PAUSE)
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ def media_next_track(hass):
|
||||
|
||||
def media_prev_track(hass):
|
||||
""" Press the keyboard button for prev track. """
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK)
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
@ -79,7 +79,7 @@ def setup(hass, config):
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_next_track_key))
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_MEDIA_PREV_TRACK,
|
||||
hass.services.register(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
lambda service:
|
||||
keyboard.tap_key(keyboard.media_prev_track_key))
|
||||
|
||||
|
@ -10,11 +10,12 @@ from homeassistant.components import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING,
|
||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
|
||||
SERVICE_VOLUME_MUTE,
|
||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK)
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
|
||||
|
||||
DOMAIN = 'media_player'
|
||||
DEPENDENCIES = []
|
||||
@ -28,28 +29,70 @@ DISCOVERY_PLATFORMS = {
|
||||
|
||||
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
|
||||
|
||||
STATE_NO_APP = 'idle'
|
||||
|
||||
ATTR_STATE = 'state'
|
||||
ATTR_OPTIONS = 'options'
|
||||
ATTR_MEDIA_STATE = 'media_state'
|
||||
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
|
||||
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
|
||||
ATTR_MEDIA_SEEK_POSITION = 'seek_position'
|
||||
ATTR_MEDIA_CONTENT_ID = 'media_content_id'
|
||||
ATTR_MEDIA_CONTENT_TYPE = 'media_content_type'
|
||||
ATTR_MEDIA_DURATION = 'media_duration'
|
||||
ATTR_MEDIA_TITLE = 'media_title'
|
||||
ATTR_MEDIA_ARTIST = 'media_artist'
|
||||
ATTR_MEDIA_ALBUM = 'media_album'
|
||||
ATTR_MEDIA_IMAGE_URL = 'media_image_url'
|
||||
ATTR_MEDIA_VOLUME = 'media_volume'
|
||||
ATTR_MEDIA_IS_VOLUME_MUTED = 'media_is_volume_muted'
|
||||
ATTR_MEDIA_DURATION = 'media_duration'
|
||||
ATTR_MEDIA_DATE = 'media_date'
|
||||
ATTR_MEDIA_ALBUM_NAME = 'media_album_name'
|
||||
ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist'
|
||||
ATTR_MEDIA_TRACK = 'media_track'
|
||||
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
|
||||
ATTR_MEDIA_SEASON = 'media_season'
|
||||
ATTR_MEDIA_EPISODE = 'media_episode'
|
||||
ATTR_APP_ID = 'app_id'
|
||||
ATTR_APP_NAME = 'app_name'
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
|
||||
|
||||
MEDIA_STATE_UNKNOWN = 'unknown'
|
||||
MEDIA_STATE_PLAYING = 'playing'
|
||||
MEDIA_STATE_PAUSED = 'paused'
|
||||
MEDIA_STATE_STOPPED = 'stopped'
|
||||
MEDIA_TYPE_MUSIC = 'music'
|
||||
MEDIA_TYPE_TVSHOW = 'tvshow'
|
||||
MEDIA_TYPE_VIDEO = 'movie'
|
||||
|
||||
SUPPORT_PAUSE = 1
|
||||
SUPPORT_SEEK = 2
|
||||
SUPPORT_VOLUME_SET = 4
|
||||
SUPPORT_VOLUME_MUTE = 8
|
||||
SUPPORT_PREVIOUS_TRACK = 16
|
||||
SUPPORT_NEXT_TRACK = 32
|
||||
SUPPORT_YOUTUBE = 64
|
||||
SUPPORT_TURN_ON = 128
|
||||
SUPPORT_TURN_OFF = 256
|
||||
|
||||
YOUTUBE_COVER_URL_FORMAT = 'http://img.youtube.com/vi/{}/1.jpg'
|
||||
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
|
||||
|
||||
SERVICE_TO_METHOD = {
|
||||
SERVICE_TURN_ON: 'turn_on',
|
||||
SERVICE_TURN_OFF: 'turn_off',
|
||||
SERVICE_VOLUME_UP: 'volume_up',
|
||||
SERVICE_VOLUME_DOWN: 'volume_down',
|
||||
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
|
||||
SERVICE_MEDIA_PLAY: 'media_play',
|
||||
SERVICE_MEDIA_PAUSE: 'media_pause',
|
||||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
||||
}
|
||||
|
||||
ATTR_TO_PROPERTY = [
|
||||
ATTR_MEDIA_VOLUME_LEVEL,
|
||||
ATTR_MEDIA_VOLUME_MUTED,
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_TITLE,
|
||||
ATTR_MEDIA_ARTIST,
|
||||
ATTR_MEDIA_ALBUM_NAME,
|
||||
ATTR_MEDIA_ALBUM_ARTIST,
|
||||
ATTR_MEDIA_TRACK,
|
||||
ATTR_MEDIA_SERIES_TITLE,
|
||||
ATTR_MEDIA_SEASON,
|
||||
ATTR_MEDIA_EPISODE,
|
||||
ATTR_APP_ID,
|
||||
ATTR_APP_NAME,
|
||||
ATTR_SUPPORTED_MEDIA_COMMANDS,
|
||||
]
|
||||
|
||||
|
||||
def is_on(hass, entity_id=None):
|
||||
@ -58,7 +101,7 @@ def is_on(hass, entity_id=None):
|
||||
|
||||
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
|
||||
|
||||
return any(not hass.states.is_state(entity_id, STATE_NO_APP)
|
||||
return any(not hass.states.is_state(entity_id, STATE_OFF)
|
||||
for entity_id in entity_ids)
|
||||
|
||||
|
||||
@ -90,21 +133,22 @@ def volume_down(hass, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
|
||||
|
||||
|
||||
def volume_mute(hass, entity_id=None):
|
||||
""" Send the media player the command to toggle its mute state. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
def mute_volume(hass, mute, entity_id=None):
|
||||
""" Send the media player the command for volume down. """
|
||||
data = {ATTR_MEDIA_VOLUME_MUTED: mute}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data)
|
||||
|
||||
|
||||
def volume_set(hass, entity_id=None, volume=None):
|
||||
""" Set volume on media player. """
|
||||
data = {
|
||||
key: value for key, value in [
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_MEDIA_VOLUME, volume),
|
||||
] if value is not None
|
||||
}
|
||||
def set_volume_level(hass, volume, entity_id=None):
|
||||
""" Send the media player the command for volume down. """
|
||||
data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data)
|
||||
|
||||
@ -137,24 +181,11 @@ def media_next_track(hass, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
|
||||
|
||||
|
||||
def media_prev_track(hass, entity_id=None):
|
||||
def media_previous_track(hass, entity_id=None):
|
||||
""" Send the media player the command for prev track. """
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data)
|
||||
|
||||
|
||||
SERVICE_TO_METHOD = {
|
||||
SERVICE_TURN_ON: 'turn_on',
|
||||
SERVICE_TURN_OFF: 'turn_off',
|
||||
SERVICE_VOLUME_UP: 'volume_up',
|
||||
SERVICE_VOLUME_DOWN: 'volume_down',
|
||||
SERVICE_MEDIA_PLAY_PAUSE: 'media_play_pause',
|
||||
SERVICE_MEDIA_PLAY: 'media_play',
|
||||
SERVICE_MEDIA_PAUSE: 'media_pause',
|
||||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||
SERVICE_MEDIA_PREV_TRACK: 'media_prev_track',
|
||||
}
|
||||
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
@ -180,35 +211,56 @@ def setup(hass, config):
|
||||
for service in SERVICE_TO_METHOD:
|
||||
hass.services.register(DOMAIN, service, media_player_service_handler)
|
||||
|
||||
def volume_set_service(service, volume):
|
||||
def volume_set_service(service):
|
||||
""" Set specified volume on the media player. """
|
||||
target_players = component.extract_from_service(service)
|
||||
|
||||
if ATTR_MEDIA_VOLUME_LEVEL not in service.data:
|
||||
return
|
||||
|
||||
volume = service.data[ATTR_MEDIA_VOLUME_LEVEL]
|
||||
|
||||
for player in target_players:
|
||||
player.volume_set(volume)
|
||||
player.set_volume_level(volume)
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_VOLUME_SET,
|
||||
lambda service:
|
||||
volume_set_service(
|
||||
service, service.data.get('volume')))
|
||||
hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service)
|
||||
|
||||
def volume_mute_service(service, mute):
|
||||
def volume_mute_service(service):
|
||||
""" Mute (true) or unmute (false) the media player. """
|
||||
target_players = component.extract_from_service(service)
|
||||
|
||||
if ATTR_MEDIA_VOLUME_MUTED not in service.data:
|
||||
return
|
||||
|
||||
mute = service.data[ATTR_MEDIA_VOLUME_MUTED]
|
||||
|
||||
for player in target_players:
|
||||
player.volume_mute(mute)
|
||||
player.mute_volume(mute)
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE,
|
||||
lambda service:
|
||||
volume_mute_service(
|
||||
service, service.data.get('mute')))
|
||||
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service)
|
||||
|
||||
def media_seek_service(service):
|
||||
""" Seek to a position. """
|
||||
target_players = component.extract_from_service(service)
|
||||
|
||||
if ATTR_MEDIA_SEEK_POSITION not in service.data:
|
||||
return
|
||||
|
||||
position = service.data[ATTR_MEDIA_SEEK_POSITION]
|
||||
|
||||
for player in target_players:
|
||||
player.seek(position)
|
||||
|
||||
if player.should_poll:
|
||||
player.update_ha_state(True)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service)
|
||||
|
||||
def play_youtube_video_service(service, media_id):
|
||||
""" Plays specified media_id on the media player. """
|
||||
@ -239,51 +291,215 @@ def setup(hass, config):
|
||||
|
||||
class MediaPlayerDevice(Entity):
|
||||
""" ABC for media player devices. """
|
||||
# pylint: disable=too-many-public-methods,no-self-use
|
||||
|
||||
# Implement these for your media player
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" State of the player. """
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def volume_level(self):
|
||||
""" Volume level of the media player (0..1). """
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
""" Boolean if volume is currently muted. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_artist(self):
|
||||
""" Artist of current playing media. (Music track only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_album_name(self):
|
||||
""" Album name of current playing media. (Music track only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_album_artist(self):
|
||||
""" Album arist of current playing media. (Music track only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_track(self):
|
||||
""" Track number of current playing media. (Music track only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_series_title(self):
|
||||
""" Series title of current playing media. (TV Show only)"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_season(self):
|
||||
""" Season of current playing media. (TV Show only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def media_episode(self):
|
||||
""" Episode of current playing media. (TV Show only) """
|
||||
return None
|
||||
|
||||
@property
|
||||
def app_id(self):
|
||||
""" ID of the current running app. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def app_name(self):
|
||||
""" Name of the current running app. """
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return 0
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
""" Extra attributes a device wants to expose. """
|
||||
return None
|
||||
|
||||
def turn_on(self):
|
||||
""" turn media player on. """
|
||||
pass
|
||||
""" turn the media player on. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def turn_off(self):
|
||||
""" turn media player off. """
|
||||
pass
|
||||
""" turn the media player off. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def volume_up(self):
|
||||
""" volume_up media player. """
|
||||
pass
|
||||
def mute_volume(self, mute):
|
||||
""" mute the volume. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def volume_down(self):
|
||||
""" volume_down media player. """
|
||||
pass
|
||||
|
||||
def volume_mute(self, mute):
|
||||
""" mute (true) or unmute (false) media player. """
|
||||
pass
|
||||
|
||||
def volume_set(self, volume):
|
||||
""" set volume level of media player. """
|
||||
pass
|
||||
|
||||
def media_play_pause(self):
|
||||
""" media_play_pause media player. """
|
||||
pass
|
||||
def set_volume_level(self, volume):
|
||||
""" set volume level, range 0..1. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
pass
|
||||
""" Send play commmand. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_pause(self):
|
||||
""" media_pause media player. """
|
||||
pass
|
||||
""" Send pause command. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_prev_track(self):
|
||||
""" media_prev_track media player. """
|
||||
pass
|
||||
def media_previous_track(self):
|
||||
""" Send previous track command. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_next_track(self):
|
||||
""" media_next_track media player. """
|
||||
pass
|
||||
""" Send next track command. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def media_seek(self, position):
|
||||
""" Send seek command. """
|
||||
raise NotImplementedError()
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
pass
|
||||
raise NotImplementedError()
|
||||
|
||||
# No need to overwrite these.
|
||||
@property
|
||||
def support_pause(self):
|
||||
""" Boolean if pause is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_PAUSE)
|
||||
|
||||
@property
|
||||
def support_seek(self):
|
||||
""" Boolean if seek is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_SEEK)
|
||||
|
||||
@property
|
||||
def support_volume_set(self):
|
||||
""" Boolean if setting volume is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_VOLUME_SET)
|
||||
|
||||
@property
|
||||
def support_volume_mute(self):
|
||||
""" Boolean if muting volume is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_VOLUME_MUTE)
|
||||
|
||||
@property
|
||||
def support_previous_track(self):
|
||||
""" Boolean if previous track command supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_PREVIOUS_TRACK)
|
||||
|
||||
@property
|
||||
def support_next_track(self):
|
||||
""" Boolean if next track command supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK)
|
||||
|
||||
@property
|
||||
def support_youtube(self):
|
||||
""" Boolean if YouTube is supported. """
|
||||
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
|
||||
|
||||
def volume_up(self):
|
||||
""" volume_up media player. """
|
||||
if self.volume_level < 1:
|
||||
self.set_volume_level(min(1, self.volume_level + .1))
|
||||
|
||||
def volume_down(self):
|
||||
""" volume_down media player. """
|
||||
if self.volume_level > 0:
|
||||
self.set_volume_level(max(0, self.volume_level - .1))
|
||||
|
||||
def media_play_pause(self):
|
||||
""" media_play_pause media player. """
|
||||
if self.state == STATE_PLAYING:
|
||||
self.media_pause()
|
||||
else:
|
||||
self.media_play()
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Return the state attributes. """
|
||||
if self.state == STATE_OFF:
|
||||
state_attr = {}
|
||||
else:
|
||||
state_attr = {
|
||||
attr: getattr(self, attr) for attr
|
||||
in ATTR_TO_PROPERTY if getattr(self, attr)
|
||||
}
|
||||
|
||||
if self.media_image_url:
|
||||
state_attr[ATTR_ENTITY_PICTURE] = self.media_image_url
|
||||
|
||||
device_attr = self.device_state_attributes
|
||||
|
||||
if device_attr:
|
||||
state_attr.update(device_attr)
|
||||
|
||||
return state_attr
|
||||
|
@ -14,18 +14,21 @@ try:
|
||||
except ImportError:
|
||||
pychromecast = None
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||
from homeassistant.const import (
|
||||
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
# ATTR_MEDIA_ALBUM, ATTR_MEDIA_IMAGE_URL,
|
||||
# ATTR_MEDIA_ARTIST,
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, ATTR_MEDIA_TITLE,
|
||||
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_VOLUME, ATTR_MEDIA_IS_VOLUME_MUTED,
|
||||
MEDIA_STATE_PLAYING, MEDIA_STATE_PAUSED, MEDIA_STATE_STOPPED,
|
||||
MEDIA_STATE_UNKNOWN)
|
||||
MediaPlayerDevice,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||
|
||||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
|
||||
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@ -61,6 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class CastDevice(MediaPlayerDevice):
|
||||
""" Represents a Cast device on the network. """
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
def __init__(self, host):
|
||||
self.cast = pychromecast.Chromecast(host)
|
||||
self.youtube = youtube.YouTubeController()
|
||||
@ -73,6 +78,8 @@ class CastDevice(MediaPlayerDevice):
|
||||
self.cast_status = self.cast.status
|
||||
self.media_status = self.cast.media_controller.status
|
||||
|
||||
# Entity properties and methods
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
return False
|
||||
@ -82,57 +89,121 @@ class CastDevice(MediaPlayerDevice):
|
||||
""" Returns the name of the device. """
|
||||
return self.cast.device.friendly_name
|
||||
|
||||
# MediaPlayerDevice properties and methods
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
if self.cast.is_idle:
|
||||
return STATE_NO_APP
|
||||
""" State of the player. """
|
||||
if self.media_status is None:
|
||||
return STATE_UNKNOWN
|
||||
elif self.media_status.player_is_playing:
|
||||
return STATE_PLAYING
|
||||
elif self.media_status.player_is_paused:
|
||||
return STATE_PAUSED
|
||||
elif self.media_status.player_is_idle:
|
||||
return STATE_IDLE
|
||||
elif self.cast.is_idle:
|
||||
return STATE_OFF
|
||||
else:
|
||||
return self.cast.app_display_name
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@property
|
||||
def media_state(self):
|
||||
""" Returns the media state. """
|
||||
media_controller = self.cast.media_controller
|
||||
|
||||
if media_controller.is_playing:
|
||||
return MEDIA_STATE_PLAYING
|
||||
elif media_controller.is_paused:
|
||||
return MEDIA_STATE_PAUSED
|
||||
elif media_controller.is_idle:
|
||||
return MEDIA_STATE_STOPPED
|
||||
else:
|
||||
return MEDIA_STATE_UNKNOWN
|
||||
def volume_level(self):
|
||||
""" Volume level of the media player (0..1). """
|
||||
return self.cast_status.volume_level if self.cast_status else None
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
cast_status = self.cast_status
|
||||
media_status = self.media_status
|
||||
media_controller = self.cast.media_controller
|
||||
def is_volume_muted(self):
|
||||
""" Boolean if volume is currently muted. """
|
||||
return self.cast_status.volume_muted if self.cast_status else None
|
||||
|
||||
state_attr = {
|
||||
ATTR_MEDIA_STATE: self.media_state,
|
||||
'application_id': self.cast.app_id,
|
||||
}
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return self.media_status.content_id if self.media_status else None
|
||||
|
||||
if cast_status:
|
||||
state_attr[ATTR_MEDIA_VOLUME] = cast_status.volume_level
|
||||
state_attr[ATTR_MEDIA_IS_VOLUME_MUTED] = cast_status.volume_muted
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
if self.media_status is None:
|
||||
return None
|
||||
elif self.media_status.media_is_tvshow:
|
||||
return MEDIA_TYPE_TVSHOW
|
||||
elif self.media_status.media_is_movie:
|
||||
return MEDIA_TYPE_VIDEO
|
||||
elif self.media_status.media_is_musictrack:
|
||||
return MEDIA_TYPE_MUSIC
|
||||
return None
|
||||
|
||||
if media_status.content_id:
|
||||
state_attr[ATTR_MEDIA_CONTENT_ID] = media_status.content_id
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
return self.media_status.duration if self.media_status else None
|
||||
|
||||
if media_status.duration:
|
||||
state_attr[ATTR_MEDIA_DURATION] = media_status.duration
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
if self.media_status is None:
|
||||
return None
|
||||
|
||||
if media_controller.title:
|
||||
state_attr[ATTR_MEDIA_TITLE] = media_controller.title
|
||||
images = self.media_status.images
|
||||
|
||||
if media_controller.thumbnail:
|
||||
state_attr[ATTR_ENTITY_PICTURE] = media_controller.thumbnail
|
||||
return images[0].url if images else None
|
||||
|
||||
return state_attr
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return self.media_status.title if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_artist(self):
|
||||
""" Artist of current playing media. (Music track only) """
|
||||
return self.media_status.artist if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_album(self):
|
||||
""" Album of current playing media. (Music track only) """
|
||||
return self.media_status.album_name if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_album_artist(self):
|
||||
""" Album arist of current playing media. (Music track only) """
|
||||
return self.media_status.album_artist if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_track(self):
|
||||
""" Track number of current playing media. (Music track only) """
|
||||
return self.media_status.track if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_series_title(self):
|
||||
""" Series title of current playing media. (TV Show only)"""
|
||||
return self.media_status.series_title if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_season(self):
|
||||
""" Season of current playing media. (TV Show only) """
|
||||
return self.media_status.season if self.media_status else None
|
||||
|
||||
@property
|
||||
def media_episode(self):
|
||||
""" Episode of current playing media. (TV Show only) """
|
||||
return self.media_status.episode if self.media_status else None
|
||||
|
||||
@property
|
||||
def app_id(self):
|
||||
""" ID of the current running app. """
|
||||
return self.cast.app_id
|
||||
|
||||
@property
|
||||
def app_name(self):
|
||||
""" Name of the current running app. """
|
||||
return self.cast.app_display_name
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_CAST
|
||||
|
||||
def turn_on(self):
|
||||
""" Turns on the ChromeCast. """
|
||||
@ -145,57 +216,42 @@ class CastDevice(MediaPlayerDevice):
|
||||
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
|
||||
|
||||
def turn_off(self):
|
||||
""" Service to exit any running app on the specimedia player ChromeCast and
|
||||
shows idle screen. Will quit all ChromeCasts if nothing specified.
|
||||
"""
|
||||
""" Turns Chromecast off. """
|
||||
self.cast.quit_app()
|
||||
|
||||
def volume_up(self):
|
||||
""" Service to send the chromecast the command for volume up. """
|
||||
self.cast.volume_up()
|
||||
|
||||
def volume_down(self):
|
||||
""" Service to send the chromecast the command for volume down. """
|
||||
self.cast.volume_down()
|
||||
|
||||
def volume_mute(self, mute):
|
||||
""" Set media player to mute volume. """
|
||||
def mute_volume(self, mute):
|
||||
""" mute the volume. """
|
||||
self.cast.set_volume_muted(mute)
|
||||
|
||||
def volume_set(self, volume):
|
||||
""" Set media player volume, range of volume 0..1 """
|
||||
def set_volume_level(self, volume):
|
||||
""" set volume level, range 0..1. """
|
||||
self.cast.set_volume(volume)
|
||||
|
||||
def media_play_pause(self):
|
||||
""" Service to send the chromecast the command for play/pause. """
|
||||
media_state = self.media_state
|
||||
|
||||
if media_state in (MEDIA_STATE_STOPPED, MEDIA_STATE_PAUSED):
|
||||
self.cast.media_controller.play()
|
||||
elif media_state == MEDIA_STATE_PLAYING:
|
||||
self.cast.media_controller.pause()
|
||||
|
||||
def media_play(self):
|
||||
""" Service to send the chromecast the command for play/pause. """
|
||||
if self.media_state in (MEDIA_STATE_STOPPED, MEDIA_STATE_PAUSED):
|
||||
self.cast.media_controller.play()
|
||||
""" Send play commmand. """
|
||||
self.cast.media_controller.play()
|
||||
|
||||
def media_pause(self):
|
||||
""" Service to send the chromecast the command for play/pause. """
|
||||
if self.media_state == MEDIA_STATE_PLAYING:
|
||||
self.cast.media_controller.pause()
|
||||
""" Send pause command. """
|
||||
self.cast.media_controller.pause()
|
||||
|
||||
def media_prev_track(self):
|
||||
""" media_prev_track media player. """
|
||||
def media_previous_track(self):
|
||||
""" Send previous track command. """
|
||||
self.cast.media_controller.rewind()
|
||||
|
||||
def media_next_track(self):
|
||||
""" media_next_track media player. """
|
||||
""" Send next track command. """
|
||||
self.cast.media_controller.skip()
|
||||
|
||||
def play_youtube_video(self, video_id):
|
||||
""" Plays specified video_id on the Chromecast's YouTube channel. """
|
||||
self.youtube.play_video(video_id)
|
||||
def media_seek(self, position):
|
||||
""" Seek the media to a specific location. """
|
||||
self.cast.media_controller.seek(position)
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
self.youtube.play_video(media_id)
|
||||
|
||||
# implementation of chromecast status_listener methods
|
||||
|
||||
def new_cast_status(self, status):
|
||||
""" Called when a new cast status is received. """
|
||||
|
@ -5,121 +5,334 @@ homeassistant.components.media_player.demo
|
||||
Demo implementation of the media player.
|
||||
|
||||
"""
|
||||
from homeassistant.const import (
|
||||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
|
||||
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED,
|
||||
YOUTUBE_COVER_URL_FORMAT, ATTR_MEDIA_IS_VOLUME_MUTED)
|
||||
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT,
|
||||
MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
|
||||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_NEXT_TRACK)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the cast platform. """
|
||||
add_devices([
|
||||
DemoMediaPlayer(
|
||||
DemoYoutubePlayer(
|
||||
'Living Room', 'eyU3bRy2x44',
|
||||
'♥♥ The Best Fireplace Video (3 hours)'),
|
||||
DemoMediaPlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours')
|
||||
DemoYoutubePlayer('Bedroom', 'kxopViU98Xo', 'Epic sax guy 10 hours'),
|
||||
DemoMusicPlayer(), DemoTVShowPlayer(),
|
||||
])
|
||||
|
||||
|
||||
class DemoMediaPlayer(MediaPlayerDevice):
|
||||
""" A Demo media player that only supports YouTube. """
|
||||
YOUTUBE_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
def __init__(self, name, youtube_id=None, media_title=None):
|
||||
MUSIC_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
NETFLIX_PLAYER_SUPPORT = \
|
||||
SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
|
||||
|
||||
|
||||
class AbstractDemoPlayer(MediaPlayerDevice):
|
||||
""" Base class for demo media players. """
|
||||
# We only implement the methods that we support
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self.is_playing = youtube_id is not None
|
||||
self.youtube_id = youtube_id
|
||||
self.media_title = media_title
|
||||
self.volume = 1.0
|
||||
self.is_volume_muted = False
|
||||
self._player_state = STATE_PLAYING
|
||||
self._volume_level = 1.0
|
||||
self._volume_muted = False
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" No polling needed for a demo componentn. """
|
||||
""" We will push an update after each command. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
""" Name of the media player. """
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return STATE_NO_APP if self.youtube_id is None else "YouTube"
|
||||
""" State of the player. """
|
||||
return self._player_state
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
if self.youtube_id is None:
|
||||
return
|
||||
def volume_level(self):
|
||||
""" Volume level of the media player (0..1). """
|
||||
return self._volume_level
|
||||
|
||||
state_attr = {
|
||||
ATTR_MEDIA_CONTENT_ID: self.youtube_id,
|
||||
ATTR_MEDIA_TITLE: self.media_title,
|
||||
ATTR_MEDIA_DURATION: 100,
|
||||
ATTR_MEDIA_VOLUME: self.volume,
|
||||
ATTR_MEDIA_IS_VOLUME_MUTED: self.is_volume_muted,
|
||||
ATTR_ENTITY_PICTURE:
|
||||
YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id)
|
||||
}
|
||||
|
||||
if self.is_playing:
|
||||
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_PLAYING
|
||||
else:
|
||||
state_attr[ATTR_MEDIA_STATE] = MEDIA_STATE_STOPPED
|
||||
|
||||
return state_attr
|
||||
@property
|
||||
def is_volume_muted(self):
|
||||
""" Boolean if volume is currently muted. """
|
||||
return self._volume_muted
|
||||
|
||||
def turn_on(self):
|
||||
""" turn_off media player. """
|
||||
self.youtube_id = "eyU3bRy2x44"
|
||||
self.is_playing = False
|
||||
""" turn the media player on. """
|
||||
self._player_state = STATE_PLAYING
|
||||
self.update_ha_state()
|
||||
|
||||
def turn_off(self):
|
||||
""" turn_off media player. """
|
||||
self.youtube_id = None
|
||||
self.is_playing = False
|
||||
""" turn the media player off. """
|
||||
self._player_state = STATE_OFF
|
||||
self.update_ha_state()
|
||||
|
||||
def volume_up(self):
|
||||
""" volume_up media player. """
|
||||
if self.volume < 1:
|
||||
self.volume += 0.1
|
||||
self.update_ha_state()
|
||||
|
||||
def volume_down(self):
|
||||
""" volume_down media player. """
|
||||
if self.volume > 0:
|
||||
self.volume -= 0.1
|
||||
self.update_ha_state()
|
||||
|
||||
def volume_mute(self, mute):
|
||||
""" mute (true) or unmute (false) media player. """
|
||||
self.is_volume_muted = mute
|
||||
def mute_volume(self, mute):
|
||||
""" mute the volume. """
|
||||
self._volume_muted = mute
|
||||
self.update_ha_state()
|
||||
|
||||
def media_play_pause(self):
|
||||
""" media_play_pause media player. """
|
||||
self.is_playing = not self.is_playing
|
||||
def set_volume_level(self, volume):
|
||||
""" set volume level, range 0..1. """
|
||||
self._volume_level = volume
|
||||
self.update_ha_state()
|
||||
|
||||
def media_play(self):
|
||||
""" media_play media player. """
|
||||
self.is_playing = True
|
||||
""" Send play commmand. """
|
||||
self._player_state = STATE_PLAYING
|
||||
self.update_ha_state()
|
||||
|
||||
def media_pause(self):
|
||||
""" media_pause media player. """
|
||||
self.is_playing = False
|
||||
""" Send pause command. """
|
||||
self._player_state = STATE_PAUSED
|
||||
self.update_ha_state()
|
||||
|
||||
|
||||
class DemoYoutubePlayer(AbstractDemoPlayer):
|
||||
""" A Demo media player that only supports YouTube. """
|
||||
# We only implement the methods that we support
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, name, youtube_id=None, media_title=None):
|
||||
super().__init__(name)
|
||||
self.youtube_id = youtube_id
|
||||
self._media_title = media_title
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return self.youtube_id
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return MEDIA_TYPE_VIDEO
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
return 360
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
return YOUTUBE_COVER_URL_FORMAT.format(self.youtube_id)
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return self._media_title
|
||||
|
||||
@property
|
||||
def app_name(self):
|
||||
""" Current running app. """
|
||||
return "YouTube"
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return YOUTUBE_PLAYER_SUPPORT
|
||||
|
||||
def play_youtube(self, media_id):
|
||||
""" Plays a YouTube media. """
|
||||
self.youtube_id = media_id
|
||||
self.media_title = 'Demo media title'
|
||||
self.is_playing = True
|
||||
self._media_title = 'some YouTube video'
|
||||
self.update_ha_state()
|
||||
|
||||
|
||||
class DemoMusicPlayer(AbstractDemoPlayer):
|
||||
""" A Demo media player that only supports YouTube. """
|
||||
# We only implement the methods that we support
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
tracks = [
|
||||
('Technohead', 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)'),
|
||||
('Paul Elstak', 'Luv U More'),
|
||||
('Dune', 'Hardcore Vibes'),
|
||||
('Nakatomi', 'Children Of The Night'),
|
||||
('Party Animals',
|
||||
'Have You Ever Been Mellow? (Flamman & Abraxas Radio Mix)'),
|
||||
('Rob G.*', 'Ecstasy, You Got What I Need'),
|
||||
('Lipstick', "I'm A Raver"),
|
||||
('4 Tune Fairytales', 'My Little Fantasy (Radio Edit)'),
|
||||
('Prophet', "The Big Boys Don't Cry"),
|
||||
('Lovechild', 'All Out Of Love (DJ Weirdo & Sim Remix)'),
|
||||
('Stingray & Sonic Driver', 'Cold As Ice (El Bruto Remix)'),
|
||||
('Highlander', 'Hold Me Now (Bass-D & King Matthew Remix)'),
|
||||
('Juggernaut', 'Ruffneck Rules Da Artcore Scene (12" Edit)'),
|
||||
('Diss Reaction', 'Jiiieehaaaa '),
|
||||
('Flamman And Abraxas', 'Good To Go (Radio Mix)'),
|
||||
('Critical Mass', 'Dancing Together'),
|
||||
('Charly Lownoise & Mental Theo',
|
||||
'Ultimate Sex Track (Bass-D & King Matthew Remix)'),
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('Walkman')
|
||||
self._cur_track = 0
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return 'bounzz-1'
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return MEDIA_TYPE_MUSIC
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
return 213
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
return 'https://graph.facebook.com/107771475912710/picture'
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return self.tracks[self._cur_track][1]
|
||||
|
||||
@property
|
||||
def media_artist(self):
|
||||
""" Artist of current playing media. (Music track only) """
|
||||
return self.tracks[self._cur_track][0]
|
||||
|
||||
@property
|
||||
def media_album_name(self):
|
||||
""" Album of current playing media. (Music track only) """
|
||||
# pylint: disable=no-self-use
|
||||
return "Bounzz"
|
||||
|
||||
@property
|
||||
def media_track(self):
|
||||
""" Track number of current playing media. (Music track only) """
|
||||
return self._cur_track + 1
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
support = MUSIC_PLAYER_SUPPORT
|
||||
|
||||
if self._cur_track > 1:
|
||||
support |= SUPPORT_PREVIOUS_TRACK
|
||||
|
||||
if self._cur_track < len(self.tracks)-1:
|
||||
support |= SUPPORT_NEXT_TRACK
|
||||
|
||||
return support
|
||||
|
||||
def media_previous_track(self):
|
||||
""" Send previous track command. """
|
||||
if self._cur_track > 0:
|
||||
self._cur_track -= 1
|
||||
self.update_ha_state()
|
||||
|
||||
def media_next_track(self):
|
||||
""" Send next track command. """
|
||||
if self._cur_track < len(self.tracks)-1:
|
||||
self._cur_track += 1
|
||||
self.update_ha_state()
|
||||
|
||||
|
||||
class DemoTVShowPlayer(AbstractDemoPlayer):
|
||||
""" A Demo media player that only supports YouTube. """
|
||||
# We only implement the methods that we support
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('Lounge room')
|
||||
self._cur_episode = 1
|
||||
self._episode_count = 13
|
||||
|
||||
@property
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return 'house-of-cards-1'
|
||||
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return MEDIA_TYPE_TVSHOW
|
||||
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
return 3600
|
||||
|
||||
@property
|
||||
def media_image_url(self):
|
||||
""" Image url of current playing media. """
|
||||
return 'https://graph.facebook.com/HouseofCards/picture'
|
||||
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return 'Chapter {}'.format(self._cur_episode)
|
||||
|
||||
@property
|
||||
def media_series_title(self):
|
||||
""" Series title of current playing media. (TV Show only)"""
|
||||
return 'House of Cards'
|
||||
|
||||
@property
|
||||
def media_season(self):
|
||||
""" Season of current playing media. (TV Show only) """
|
||||
return 1
|
||||
|
||||
@property
|
||||
def media_episode(self):
|
||||
""" Episode of current playing media. (TV Show only) """
|
||||
return self._cur_episode
|
||||
|
||||
@property
|
||||
def app_name(self):
|
||||
""" Current running app. """
|
||||
return "Netflix"
|
||||
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
support = NETFLIX_PLAYER_SUPPORT
|
||||
|
||||
if self._cur_episode > 1:
|
||||
support |= SUPPORT_PREVIOUS_TRACK
|
||||
|
||||
if self._cur_episode < self._episode_count:
|
||||
support |= SUPPORT_NEXT_TRACK
|
||||
|
||||
return support
|
||||
|
||||
def media_previous_track(self):
|
||||
""" Send previous track command. """
|
||||
if self._cur_episode > 1:
|
||||
self._cur_episode -= 1
|
||||
self.update_ha_state()
|
||||
|
||||
def media_next_track(self):
|
||||
""" Send next track command. """
|
||||
if self._cur_episode < self._episode_count:
|
||||
self._cur_episode += 1
|
||||
self.update_ha_state()
|
||||
|
@ -32,16 +32,28 @@ Location of your Music Player Daemon.
|
||||
import logging
|
||||
import socket
|
||||
|
||||
try:
|
||||
import mpd
|
||||
except ImportError:
|
||||
mpd = None
|
||||
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE,
|
||||
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST,
|
||||
ATTR_MEDIA_ALBUM, ATTR_MEDIA_DATE, ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_VOLUME, MEDIA_STATE_PAUSED, MEDIA_STATE_PLAYING,
|
||||
MEDIA_STATE_STOPPED, MEDIA_STATE_UNKNOWN)
|
||||
MediaPlayerDevice,
|
||||
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||
MEDIA_TYPE_MUSIC)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the MPD platform. """
|
||||
@ -50,10 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
port = config.get('port', 6600)
|
||||
location = config.get('location', 'MPD')
|
||||
|
||||
try:
|
||||
from mpd import MPDClient
|
||||
|
||||
except ImportError:
|
||||
if mpd is None:
|
||||
_LOGGER.exception(
|
||||
"Unable to import mpd2. "
|
||||
"Did you maybe not install the 'python-mpd2' package?")
|
||||
@ -62,7 +71,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
mpd_client = MPDClient()
|
||||
mpd_client = mpd.MPDClient()
|
||||
mpd_client.connect(daemon, port)
|
||||
mpd_client.close()
|
||||
mpd_client.disconnect()
|
||||
@ -73,110 +82,112 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
return False
|
||||
|
||||
mpd = []
|
||||
mpd.append(MpdDevice(daemon, port, location))
|
||||
add_devices(mpd)
|
||||
add_devices([MpdDevice(daemon, port, location)])
|
||||
|
||||
|
||||
class MpdDevice(MediaPlayerDevice):
|
||||
""" Represents a MPD server. """
|
||||
|
||||
def __init__(self, server, port, location):
|
||||
from mpd import MPDClient
|
||||
# MPD confuses pylint
|
||||
# pylint: disable=no-member, abstract-method
|
||||
|
||||
def __init__(self, server, port, location):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self._name = location
|
||||
self.state_attr = {ATTR_MEDIA_STATE: MEDIA_STATE_STOPPED}
|
||||
self.status = None
|
||||
self.currentsong = None
|
||||
|
||||
self.client = MPDClient()
|
||||
self.client = mpd.MPDClient()
|
||||
self.client.timeout = 10
|
||||
self.client.idletimeout = None
|
||||
self.client.connect(self.server, self.port)
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
self.status = self.client.status()
|
||||
self.currentsong = self.client.currentsong()
|
||||
except mpd.ConnectionError:
|
||||
self.client.connect(self.server, self.port)
|
||||
self.status = self.client.status()
|
||||
self.currentsong = self.client.currentsong()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self._name
|
||||
|
||||
# pylint: disable=no-member
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
status = self.client.status()
|
||||
|
||||
if status is None:
|
||||
return STATE_NO_APP
|
||||
else:
|
||||
return self.client.currentsong()['artist']
|
||||
|
||||
@property
|
||||
def media_state(self):
|
||||
""" Returns the media state. """
|
||||
media_controller = self.client.status()
|
||||
|
||||
if media_controller['state'] == 'play':
|
||||
return MEDIA_STATE_PLAYING
|
||||
elif media_controller['state'] == 'pause':
|
||||
return MEDIA_STATE_PAUSED
|
||||
elif media_controller['state'] == 'stop':
|
||||
return MEDIA_STATE_STOPPED
|
||||
if self.status['state'] == 'play':
|
||||
return STATE_PLAYING
|
||||
elif self.status['state'] == 'pause':
|
||||
return STATE_PAUSED
|
||||
else:
|
||||
return MEDIA_STATE_UNKNOWN
|
||||
return STATE_OFF
|
||||
|
||||
# pylint: disable=no-member
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
status = self.client.status()
|
||||
current_song = self.client.currentsong()
|
||||
def media_content_id(self):
|
||||
""" Content ID of current playing media. """
|
||||
return self.currentsong['id']
|
||||
|
||||
if not status and not current_song:
|
||||
state_attr = {}
|
||||
@property
|
||||
def media_content_type(self):
|
||||
""" Content type of current playing media. """
|
||||
return MEDIA_TYPE_MUSIC
|
||||
|
||||
if current_song['id']:
|
||||
state_attr[ATTR_MEDIA_CONTENT_ID] = current_song['id']
|
||||
@property
|
||||
def media_duration(self):
|
||||
""" Duration of current playing media in seconds. """
|
||||
# Time does not exist for streams
|
||||
return self.currentsong.get('time')
|
||||
|
||||
if current_song['date']:
|
||||
state_attr[ATTR_MEDIA_DATE] = current_song['date']
|
||||
@property
|
||||
def media_title(self):
|
||||
""" Title of current playing media. """
|
||||
return self.currentsong['title']
|
||||
|
||||
if current_song['title']:
|
||||
state_attr[ATTR_MEDIA_TITLE] = current_song['title']
|
||||
@property
|
||||
def media_artist(self):
|
||||
""" Artist of current playing media. (Music track only) """
|
||||
return self.currentsong.get('artist')
|
||||
|
||||
if current_song['time']:
|
||||
state_attr[ATTR_MEDIA_DURATION] = current_song['time']
|
||||
@property
|
||||
def media_album_name(self):
|
||||
""" Album of current playing media. (Music track only) """
|
||||
return self.currentsong.get('album')
|
||||
|
||||
if current_song['artist']:
|
||||
state_attr[ATTR_MEDIA_ARTIST] = current_song['artist']
|
||||
@property
|
||||
def volume_level(self):
|
||||
return int(self.status['volume'])/100
|
||||
|
||||
if current_song['album']:
|
||||
state_attr[ATTR_MEDIA_ALBUM] = current_song['album']
|
||||
|
||||
state_attr[ATTR_MEDIA_VOLUME] = status['volume']
|
||||
|
||||
return state_attr
|
||||
@property
|
||||
def supported_media_commands(self):
|
||||
""" Flags of media commands that are supported. """
|
||||
return SUPPORT_MPD
|
||||
|
||||
def turn_off(self):
|
||||
""" Service to exit the running MPD. """
|
||||
self.client.stop()
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
""" Sets volume """
|
||||
self.client.setvol(int(volume * 100))
|
||||
|
||||
def volume_up(self):
|
||||
""" Service to send the MPD the command for volume up. """
|
||||
current_volume = self.client.status()['volume']
|
||||
current_volume = int(self.status['volume'])
|
||||
|
||||
if int(current_volume) <= 100:
|
||||
self.client.setvol(int(current_volume) + 5)
|
||||
if current_volume <= 100:
|
||||
self.client.setvol(current_volume + 5)
|
||||
|
||||
def volume_down(self):
|
||||
""" Service to send the MPD the command for volume down. """
|
||||
current_volume = self.client.status()['volume']
|
||||
current_volume = int(self.status['volume'])
|
||||
|
||||
if int(current_volume) >= 0:
|
||||
self.client.setvol(int(current_volume) - 5)
|
||||
|
||||
def media_play_pause(self):
|
||||
""" Service to send the MPD the command for play/pause. """
|
||||
self.client.pause()
|
||||
if current_volume >= 0:
|
||||
self.client.setvol(current_volume - 5)
|
||||
|
||||
def media_play(self):
|
||||
""" Service to send the MPD the command for play/pause. """
|
||||
@ -190,6 +201,6 @@ class MpdDevice(MediaPlayerDevice):
|
||||
""" Service to send the MPD the command for next track. """
|
||||
self.client.next()
|
||||
|
||||
def media_prev_track(self):
|
||||
def media_previous_track(self):
|
||||
""" Service to send the MPD the command for previous track. """
|
||||
self.client.previous()
|
||||
|
@ -40,6 +40,9 @@ STATE_NOT_HOME = 'not_home'
|
||||
STATE_UNKNOWN = "unknown"
|
||||
STATE_OPEN = 'open'
|
||||
STATE_CLOSED = 'closed'
|
||||
STATE_PLAYING = 'playing'
|
||||
STATE_PAUSED = 'paused'
|
||||
STATE_IDLE = 'idle'
|
||||
|
||||
# #### STATE AND EVENT ATTRIBUTES ####
|
||||
# Contains current time for a TIME_CHANGED event
|
||||
@ -104,7 +107,8 @@ SERVICE_MEDIA_PLAY_PAUSE = "media_play_pause"
|
||||
SERVICE_MEDIA_PLAY = "media_play"
|
||||
SERVICE_MEDIA_PAUSE = "media_pause"
|
||||
SERVICE_MEDIA_NEXT_TRACK = "media_next_track"
|
||||
SERVICE_MEDIA_PREV_TRACK = "media_prev_track"
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK = "media_previous_track"
|
||||
SERVICE_MEDIA_SEEK = "media_seek"
|
||||
|
||||
# #### API / REMOTE ####
|
||||
SERVER_PORT = 8123
|
||||
|
@ -18,7 +18,7 @@ phue>=0.8
|
||||
ledcontroller>=1.0.7
|
||||
|
||||
# Chromecast bindings (media_player.cast)
|
||||
pychromecast>=0.6.4
|
||||
pychromecast>=0.6.5
|
||||
|
||||
# Keyboard (keyboard)
|
||||
pyuserinput>=0.1.9
|
||||
|
@ -10,9 +10,10 @@ import unittest
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.const import (
|
||||
STATE_OFF,
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
|
||||
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREV_TRACK, ATTR_ENTITY_ID)
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, ATTR_ENTITY_ID)
|
||||
import homeassistant.components.media_player as media_player
|
||||
from helpers import mock_service
|
||||
|
||||
@ -29,7 +30,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
self.hass = ha.HomeAssistant()
|
||||
|
||||
self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room')
|
||||
self.hass.states.set(self.test_entity, media_player.STATE_NO_APP)
|
||||
self.hass.states.set(self.test_entity, STATE_OFF)
|
||||
|
||||
self.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom')
|
||||
self.hass.states.set(self.test_entity2, "YouTube")
|
||||
@ -56,7 +57,7 @@ class TestMediaPlayer(unittest.TestCase):
|
||||
SERVICE_MEDIA_PLAY: media_player.media_play,
|
||||
SERVICE_MEDIA_PAUSE: media_player.media_pause,
|
||||
SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track,
|
||||
SERVICE_MEDIA_PREV_TRACK: media_player.media_prev_track
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK: media_player.media_previous_track
|
||||
}
|
||||
|
||||
for service_name, service_method in services.items():
|
||||
|
Loading…
x
Reference in New Issue
Block a user