Merge pull request #158 from balloob/refactor-media-player

Initial refactor media player
This commit is contained in:
Paulus Schoutsen 2015-06-10 23:59:53 -07:00
commit 2ea195a5d2
14 changed files with 1073 additions and 407 deletions

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "1cc966bcef26a859d053bd5c46769a99" VERSION = "d0b5982b1bc41a96b8a4f49aaa92af5d"

View File

@ -16984,7 +16984,7 @@ window.hass.uiUtil.domainIcon = function(domain, state) {
case "media_player": case "media_player":
var icon = "hardware:cast"; var icon = "hardware:cast";
if (state && state !== "idle") { if (state && state !== "off" && state !== 'idle') {
icon += "-connected"; 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"> <div class="horizontal justified layout">
<state-info state-obj="[[stateObj]]"></state-info> <state-info state-obj="[[stateObj]]"></state-info>
<div class="state"> <div class="state">
<div class="main-text">[[computePrimaryText(stateObj)]]</div> <div class="main-text">[[computePrimaryText(stateObj, isPlaying)]]</div>
<div class="secondary-text">[[computeSecondaryText(stateObj)]]</div> <div class="secondary-text">[[computeSecondaryText(stateObj, isPlaying)]]</div>
</div> </div>
</div> </div>
</template> </template>
@ -22321,6 +22321,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<script> <script>
(function() { (function() {
var PLAYING_STATES = ['playing', 'paused'];
Polymer({ Polymer({
is: 'state-card-media_player', is: 'state-card-media_player',
@ -22328,14 +22329,41 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
stateObj: { stateObj: {
type: Object, type: Object,
}, },
isPlaying: {
type: Boolean,
computed: 'computeIsPlaying(stateObj)',
},
}, },
computePrimaryText: function(stateObj) { computeIsPlaying: function(stateObj) {
return stateObj.attributes.media_title || stateObj.stateDisplay; return PLAYING_STATES.indexOf(stateObj.state) !== -1;
}, },
computeSecondaryText: function(stateObj) { computePrimaryText: function(stateObj, isPlaying) {
return stateObj.attributes.media_title ? stateObj.stateDisplay : ''; 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%; width: 100%;
cursor: pointer; cursor: pointer;
overflow: hidden;
} }
</style> </style>
@ -25819,8 +25848,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
text-transform: capitalize; text-transform: capitalize;
} }
/* Accent the power button because the user should use that first */ paper-icon-button[highlight] {
paper-icon-button[focus] {
color: var(--accent-color); 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; transition: max-height .5s ease-in;
} }
.has-media_volume .volume { .has-volume_level .volume {
max-height: 40px; max-height: 40px;
} }
</style> </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$="[[computeClassNames(stateObj)]]">
<div class="layout horizontal"> <div class="layout horizontal">
<div class="flex"> <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>
<div> <div>
<template is="dom-if" if="[[!isIdle]]"> <template is="dom-if" if="[[!isOff]]">
<paper-icon-button icon="av:skip-previous" on-tap="handlePrevious"></paper-icon-button> <paper-icon-button icon="av:skip-previous" on-tap="handlePrevious" hidden$="[[!supportsPreviousTrack]]"></paper-icon-button>
<paper-icon-button icon="[[computePlayPauseIcon(stateObj)]]" focus$="" on-tap="handlePlayPause"></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"></paper-icon-button> <paper-icon-button icon="av:skip-next" on-tap="handleNext" hidden$="[[!supportsNextTrack]]"></paper-icon-button>
</template> </template>
</div> </div>
</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-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> </paper-slider>
</div> </div>
</div> </div>
@ -25863,7 +25891,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
(function() { (function() {
var serviceActions = window.hass.serviceActions; var serviceActions = window.hass.serviceActions;
var uiUtil = window.hass.uiUtil; var uiUtil = window.hass.uiUtil;
var ATTRIBUTE_CLASSES = ['media_volume']; var ATTRIBUTE_CLASSES = ['volume_level'];
Polymer({ Polymer({
is: 'more-info-media_player', 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', observer: 'stateObjChanged',
}, },
isIdle: { isOff: {
type: Boolean, type: Boolean,
computed: 'computeIsIdle(stateObj)', value: false,
},
isPlaying: {
type: Boolean,
value: false,
}, },
isMuted: { isMuted: {
@ -25887,13 +25920,58 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
volumeSliderValue: { volumeSliderValue: {
type: Number, type: Number,
value: 0, 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) { stateObjChanged: function(newVal, oldVal) {
if (newVal) { if (newVal) {
this.volumeSliderValue = newVal.attributes.media_volume * 100; this.isOff = newVal.state == 'off';
this.isMuted = newVal.attributes.media_is_volume_muted; 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() { 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); return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
}, },
computeMediaState: function(stateObj) { computeIsOff: function(stateObj) {
return stateObj.state == 'idle' ? 'idle' : stateObj.attributes.media_state; return stateObj.state == 'off';
},
computeIsIdle: function(stateObj) {
return stateObj.state == 'idle';
},
computePowerButtonCaption: function(isIdle) {
return isIdle ? 'Turn on' : 'Turn off';
}, },
computeMuteVolumeIcon: function(isMuted) { computeMuteVolumeIcon: function(isMuted) {
return isMuted ? 'av:volume-off' : 'av:volume-up'; return isMuted ? 'av:volume-off' : 'av:volume-up';
}, },
computePlayPauseIcon: function(stateObj) { computePlaybackControlIcon: function(stateObj) {
return stateObj.attributes.media_state == 'playing' ? 'av:pause' : 'av:play-arrow'; 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() { handleTogglePower: function() {
this.callService(this.isIdle ? 'turn_on' : 'turn_off'); this.callService(this.isOff ? 'turn_on' : 'turn_off');
}, },
handlePrevious: function() { 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'); 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() { 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) { volumeSliderChanged: function(ev) {
var volPercentage = parseFloat(ev.target.value); var volPercentage = parseFloat(ev.target.value);
var vol = volPercentage > 0 ? volPercentage / 100 : 0; var vol = volPercentage > 0 ? volPercentage / 100 : 0;
this.callService('volume_set', { volume_level: vol });
this.callService('volume_set', { volume: vol });
}, },
callService: function(service, data) { callService: function(service, data) {

View File

@ -33,8 +33,8 @@
<div class='horizontal justified layout'> <div class='horizontal justified layout'>
<state-info state-obj="[[stateObj]]"></state-info> <state-info state-obj="[[stateObj]]"></state-info>
<div class='state'> <div class='state'>
<div class='main-text'>[[computePrimaryText(stateObj)]]</div> <div class='main-text'>[[computePrimaryText(stateObj, isPlaying)]]</div>
<div class='secondary-text'>[[computeSecondaryText(stateObj)]]</div> <div class='secondary-text'>[[computeSecondaryText(stateObj, isPlaying)]]</div>
</div> </div>
</div> </div>
</template> </template>
@ -42,6 +42,7 @@
<script> <script>
(function() { (function() {
var PLAYING_STATES = ['playing', 'paused'];
Polymer({ Polymer({
is: 'state-card-media_player', is: 'state-card-media_player',
@ -49,14 +50,41 @@
stateObj: { stateObj: {
type: Object, type: Object,
}, },
isPlaying: {
type: Boolean,
computed: 'computeIsPlaying(stateObj)',
},
}, },
computePrimaryText: function(stateObj) { computeIsPlaying: function(stateObj) {
return stateObj.attributes.media_title || stateObj.stateDisplay; return PLAYING_STATES.indexOf(stateObj.state) !== -1;
}, },
computeSecondaryText: function(stateObj) { computePrimaryText: function(stateObj, isPlaying) {
return stateObj.attributes.media_title ? stateObj.stateDisplay : ''; 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 '';
}
}, },
}); });
})(); })();

View File

@ -15,6 +15,7 @@
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
overflow: hidden;
} }
</style> </style>

View File

@ -8,8 +8,7 @@
text-transform: capitalize; text-transform: capitalize;
} }
/* Accent the power button because the user should use that first */ paper-icon-button[highlight] {
paper-icon-button[focus] {
color: var(--accent-color); color: var(--accent-color);
} }
@ -21,7 +20,7 @@
transition: max-height .5s ease-in; transition: max-height .5s ease-in;
} }
.has-media_volume .volume { .has-volume_level .volume {
max-height: 40px; max-height: 40px;
} }
</style> </style>
@ -29,25 +28,26 @@
<div class$='[[computeClassNames(stateObj)]]'> <div class$='[[computeClassNames(stateObj)]]'>
<div class='layout horizontal'> <div class='layout horizontal'>
<div class='flex'> <div class='flex'>
<paper-icon-button icon='power-settings-new' focus$='[[isIdle]]' <paper-icon-button icon='power-settings-new' highlight$='[[isOff]]'
on-tap='handleTogglePower'></paper-icon-button> on-tap='handleTogglePower'
hidden$='[[computeHidePowerButton(isOff, supportsTurnOn, supportsTurnOff)]]'></paper-icon-button>
</div> </div>
<div> <div>
<template is='dom-if' if='[[!isIdle]]'> <template is='dom-if' if='[[!isOff]]'>
<paper-icon-button icon='av:skip-previous' <paper-icon-button icon='av:skip-previous' on-tap='handlePrevious'
on-tap='handlePrevious'></paper-icon-button> hidden$='[[!supportsPreviousTrack]]'></paper-icon-button>
<paper-icon-button icon='[[computePlayPauseIcon(stateObj)]]' focus$ <paper-icon-button icon='[[computePlaybackControlIcon(stateObj)]]'
on-tap='handlePlayPause'></paper-icon-button> on-tap='handlePlaybackControl' highlight></paper-icon-button>
<paper-icon-button icon='av:skip-next' <paper-icon-button icon='av:skip-next' on-tap='handleNext'
on-tap='handleNext'></paper-icon-button> hidden$='[[!supportsNextTrack]]'></paper-icon-button>
</template> </template>
</div> </div>
</div> </div>
<div class='volume center horizontal layout'> <div class='volume center horizontal layout' hidden$='[[!supportsVolumeSet]]'>
<paper-icon-button on-tap="handleVolumeTap" <paper-icon-button on-tap="handleVolumeTap"
icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button> icon="[[computeMuteVolumeIcon(isMuted)]]"></paper-icon-button>
<paper-slider hidden='[[isMuted]]' <paper-slider disabled$='[[isMuted]]'
min='0' max='100' value='{{volumeSliderValue}}' min='0' max='100' value='[[volumeSliderValue]]'
on-change='volumeSliderChanged' class='flex'> on-change='volumeSliderChanged' class='flex'>
</paper-slider> </paper-slider>
</div> </div>
@ -59,7 +59,7 @@
(function() { (function() {
var serviceActions = window.hass.serviceActions; var serviceActions = window.hass.serviceActions;
var uiUtil = window.hass.uiUtil; var uiUtil = window.hass.uiUtil;
var ATTRIBUTE_CLASSES = ['media_volume']; var ATTRIBUTE_CLASSES = ['volume_level'];
Polymer({ Polymer({
is: 'more-info-media_player', is: 'more-info-media_player',
@ -70,9 +70,14 @@
observer: 'stateObjChanged', observer: 'stateObjChanged',
}, },
isIdle: { isOff: {
type: Boolean, type: Boolean,
computed: 'computeIsIdle(stateObj)', value: false,
},
isPlaying: {
type: Boolean,
value: false,
}, },
isMuted: { isMuted: {
@ -83,13 +88,58 @@
volumeSliderValue: { volumeSliderValue: {
type: Number, type: Number,
value: 0, 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) { stateObjChanged: function(newVal, oldVal) {
if (newVal) { if (newVal) {
this.volumeSliderValue = newVal.attributes.media_volume * 100; this.isOff = newVal.state == 'off';
this.isMuted = newVal.attributes.media_is_volume_muted; 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() { this.debounce('more-info-volume-animation-finish', function() {
@ -101,35 +151,37 @@
return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES); return uiUtil.attributeClassNames(stateObj, ATTRIBUTE_CLASSES);
}, },
computeMediaState: function(stateObj) { computeIsOff: function(stateObj) {
return stateObj.state == 'idle' ? 'idle' : stateObj.attributes.media_state; return stateObj.state == 'off';
},
computeIsIdle: function(stateObj) {
return stateObj.state == 'idle';
},
computePowerButtonCaption: function(isIdle) {
return isIdle ? 'Turn on' : 'Turn off';
}, },
computeMuteVolumeIcon: function(isMuted) { computeMuteVolumeIcon: function(isMuted) {
return isMuted ? 'av:volume-off' : 'av:volume-up'; return isMuted ? 'av:volume-off' : 'av:volume-up';
}, },
computePlayPauseIcon: function(stateObj) { computePlaybackControlIcon: function(stateObj) {
return stateObj.attributes.media_state == 'playing' ? 'av:pause' : 'av:play-arrow'; 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() { handleTogglePower: function() {
this.callService(this.isIdle ? 'turn_on' : 'turn_off'); this.callService(this.isOff ? 'turn_on' : 'turn_off');
}, },
handlePrevious: function() { 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'); this.callService('media_play_pause');
}, },
@ -138,14 +190,16 @@
}, },
handleVolumeTap: function() { 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) { volumeSliderChanged: function(ev) {
var volPercentage = parseFloat(ev.target.value); var volPercentage = parseFloat(ev.target.value);
var vol = volPercentage > 0 ? volPercentage / 100 : 0; var vol = volPercentage > 0 ? volPercentage / 100 : 0;
this.callService('volume_set', { volume_level: vol });
this.callService('volume_set', { volume: vol });
}, },
callService: function(service, data) { callService: function(service, data) {

View File

@ -48,7 +48,7 @@ window.hass.uiUtil.domainIcon = function(domain, state) {
case "media_player": case "media_player":
var icon = "hardware:cast"; var icon = "hardware:cast";
if (state && state !== "idle") { if (state && state !== "off" && state !== 'idle') {
icon += "-connected"; icon += "-connected";
} }

View File

@ -8,7 +8,7 @@ import logging
from homeassistant.const import ( from homeassistant.const import (
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, 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) SERVICE_MEDIA_PLAY_PAUSE)
@ -43,7 +43,7 @@ def media_next_track(hass):
def media_prev_track(hass): def media_prev_track(hass):
""" Press the keyboard button for prev track. """ """ 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): def setup(hass, config):
@ -79,7 +79,7 @@ def setup(hass, config):
lambda service: lambda service:
keyboard.tap_key(keyboard.media_next_track_key)) 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: lambda service:
keyboard.tap_key(keyboard.media_prev_track_key)) keyboard.tap_key(keyboard.media_prev_track_key))

View File

@ -10,11 +10,12 @@ from homeassistant.components import discovery
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.const import ( 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_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, 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' DOMAIN = 'media_player'
DEPENDENCIES = [] DEPENDENCIES = []
@ -28,28 +29,70 @@ DISCOVERY_PLATFORMS = {
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
STATE_NO_APP = 'idle' ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
ATTR_STATE = 'state' ATTR_MEDIA_SEEK_POSITION = 'seek_position'
ATTR_OPTIONS = 'options'
ATTR_MEDIA_STATE = 'media_state'
ATTR_MEDIA_CONTENT_ID = 'media_content_id' 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_TITLE = 'media_title'
ATTR_MEDIA_ARTIST = 'media_artist' ATTR_MEDIA_ARTIST = 'media_artist'
ATTR_MEDIA_ALBUM = 'media_album' ATTR_MEDIA_ALBUM_NAME = 'media_album_name'
ATTR_MEDIA_IMAGE_URL = 'media_image_url' ATTR_MEDIA_ALBUM_ARTIST = 'media_album_artist'
ATTR_MEDIA_VOLUME = 'media_volume' ATTR_MEDIA_TRACK = 'media_track'
ATTR_MEDIA_IS_VOLUME_MUTED = 'media_is_volume_muted' ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_DURATION = 'media_duration' ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_DATE = 'media_date' 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_TYPE_MUSIC = 'music'
MEDIA_STATE_PLAYING = 'playing' MEDIA_TYPE_TVSHOW = 'tvshow'
MEDIA_STATE_PAUSED = 'paused' MEDIA_TYPE_VIDEO = 'movie'
MEDIA_STATE_STOPPED = 'stopped'
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): 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) 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) 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) hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
def volume_mute(hass, entity_id=None): def mute_volume(hass, mute, entity_id=None):
""" Send the media player the command to toggle its mute state. """ """ Send the media player the command for volume down. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_MEDIA_VOLUME_MUTED: mute}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data) hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data)
def volume_set(hass, entity_id=None, volume=None): def set_volume_level(hass, volume, entity_id=None):
""" Set volume on media player. """ """ Send the media player the command for volume down. """
data = { data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
key: value for key, value in [
(ATTR_ENTITY_ID, entity_id), if entity_id:
(ATTR_MEDIA_VOLUME, volume), data[ATTR_ENTITY_ID] = entity_id
] if value is not None
}
hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data) 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) 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. """ """ Send the media player the command for prev track. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_MEDIA_PREV_TRACK, data) hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_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',
}
def setup(hass, config): def setup(hass, config):
@ -180,35 +211,56 @@ def setup(hass, config):
for service in SERVICE_TO_METHOD: for service in SERVICE_TO_METHOD:
hass.services.register(DOMAIN, service, media_player_service_handler) 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. """ """ Set specified volume on the media player. """
target_players = component.extract_from_service(service) 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: for player in target_players:
player.volume_set(volume) player.set_volume_level(volume)
if player.should_poll: if player.should_poll:
player.update_ha_state(True) player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_VOLUME_SET, hass.services.register(DOMAIN, SERVICE_VOLUME_SET, volume_set_service)
lambda service:
volume_set_service(
service, service.data.get('volume')))
def volume_mute_service(service, mute): def volume_mute_service(service):
""" Mute (true) or unmute (false) the media player. """ """ Mute (true) or unmute (false) the media player. """
target_players = component.extract_from_service(service) 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: for player in target_players:
player.volume_mute(mute) player.mute_volume(mute)
if player.should_poll: if player.should_poll:
player.update_ha_state(True) player.update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, hass.services.register(DOMAIN, SERVICE_VOLUME_MUTE, volume_mute_service)
lambda service:
volume_mute_service( def media_seek_service(service):
service, service.data.get('mute'))) """ 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): def play_youtube_video_service(service, media_id):
""" Plays specified media_id on the media player. """ """ Plays specified media_id on the media player. """
@ -239,51 +291,215 @@ def setup(hass, config):
class MediaPlayerDevice(Entity): class MediaPlayerDevice(Entity):
""" ABC for media player devices. """ """ 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): def turn_on(self):
""" turn media player on. """ """ turn the media player on. """
pass raise NotImplementedError()
def turn_off(self): def turn_off(self):
""" turn media player off. """ """ turn the media player off. """
pass raise NotImplementedError()
def volume_up(self): def mute_volume(self, mute):
""" volume_up media player. """ """ mute the volume. """
pass raise NotImplementedError()
def volume_down(self): def set_volume_level(self, volume):
""" volume_down media player. """ """ set volume level, range 0..1. """
pass raise NotImplementedError()
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 media_play(self): def media_play(self):
""" media_play media player. """ """ Send play commmand. """
pass raise NotImplementedError()
def media_pause(self): def media_pause(self):
""" media_pause media player. """ """ Send pause command. """
pass raise NotImplementedError()
def media_prev_track(self): def media_previous_track(self):
""" media_prev_track media player. """ """ Send previous track command. """
pass raise NotImplementedError()
def media_next_track(self): def media_next_track(self):
""" media_next_track media player. """ """ Send next track command. """
pass raise NotImplementedError()
def media_seek(self, position):
""" Send seek command. """
raise NotImplementedError()
def play_youtube(self, media_id): def play_youtube(self, media_id):
""" Plays a YouTube media. """ """ 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

View File

@ -14,18 +14,21 @@ try:
except ImportError: except ImportError:
pychromecast = None 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 ( from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, ATTR_MEDIA_TITLE, MediaPlayerDevice,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_DURATION, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
ATTR_MEDIA_VOLUME, ATTR_MEDIA_IS_VOLUME_MUTED, SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE,
MEDIA_STATE_PLAYING, MEDIA_STATE_PAUSED, MEDIA_STATE_STOPPED, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_STATE_UNKNOWN) MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png' 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 # pylint: disable=unused-argument
@ -61,6 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CastDevice(MediaPlayerDevice): class CastDevice(MediaPlayerDevice):
""" Represents a Cast device on the network. """ """ Represents a Cast device on the network. """
# pylint: disable=too-many-public-methods
def __init__(self, host): def __init__(self, host):
self.cast = pychromecast.Chromecast(host) self.cast = pychromecast.Chromecast(host)
self.youtube = youtube.YouTubeController() self.youtube = youtube.YouTubeController()
@ -73,6 +78,8 @@ class CastDevice(MediaPlayerDevice):
self.cast_status = self.cast.status self.cast_status = self.cast.status
self.media_status = self.cast.media_controller.status self.media_status = self.cast.media_controller.status
# Entity properties and methods
@property @property
def should_poll(self): def should_poll(self):
return False return False
@ -82,57 +89,121 @@ class CastDevice(MediaPlayerDevice):
""" Returns the name of the device. """ """ Returns the name of the device. """
return self.cast.device.friendly_name return self.cast.device.friendly_name
# MediaPlayerDevice properties and methods
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """ State of the player. """
if self.cast.is_idle: if self.media_status is None:
return STATE_NO_APP 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: else:
return self.cast.app_display_name return STATE_UNKNOWN
@property @property
def media_state(self): def volume_level(self):
""" Returns the media state. """ """ Volume level of the media player (0..1). """
media_controller = self.cast.media_controller return self.cast_status.volume_level if self.cast_status else None
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
@property @property
def state_attributes(self): def is_volume_muted(self):
""" Returns the state attributes. """ """ Boolean if volume is currently muted. """
cast_status = self.cast_status return self.cast_status.volume_muted if self.cast_status else None
media_status = self.media_status
media_controller = self.cast.media_controller
state_attr = { @property
ATTR_MEDIA_STATE: self.media_state, def media_content_id(self):
'application_id': self.cast.app_id, """ Content ID of current playing media. """
} return self.media_status.content_id if self.media_status else None
if cast_status: @property
state_attr[ATTR_MEDIA_VOLUME] = cast_status.volume_level def media_content_type(self):
state_attr[ATTR_MEDIA_IS_VOLUME_MUTED] = cast_status.volume_muted """ 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: @property
state_attr[ATTR_MEDIA_CONTENT_ID] = media_status.content_id 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: @property
state_attr[ATTR_MEDIA_DURATION] = media_status.duration def media_image_url(self):
""" Image url of current playing media. """
if self.media_status is None:
return None
if media_controller.title: images = self.media_status.images
state_attr[ATTR_MEDIA_TITLE] = media_controller.title
if media_controller.thumbnail: return images[0].url if images else None
state_attr[ATTR_ENTITY_PICTURE] = media_controller.thumbnail
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): def turn_on(self):
""" Turns on the ChromeCast. """ """ Turns on the ChromeCast. """
@ -145,57 +216,42 @@ class CastDevice(MediaPlayerDevice):
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED) CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
def turn_off(self): def turn_off(self):
""" Service to exit any running app on the specimedia player ChromeCast and """ Turns Chromecast off. """
shows idle screen. Will quit all ChromeCasts if nothing specified.
"""
self.cast.quit_app() self.cast.quit_app()
def volume_up(self): def mute_volume(self, mute):
""" Service to send the chromecast the command for volume up. """ """ mute the volume. """
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. """
self.cast.set_volume_muted(mute) self.cast.set_volume_muted(mute)
def volume_set(self, volume): def set_volume_level(self, volume):
""" Set media player volume, range of volume 0..1 """ """ set volume level, range 0..1. """
self.cast.set_volume(volume) 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): def media_play(self):
""" Service to send the chromecast the command for play/pause. """ """ Send play commmand. """
if self.media_state in (MEDIA_STATE_STOPPED, MEDIA_STATE_PAUSED): self.cast.media_controller.play()
self.cast.media_controller.play()
def media_pause(self): def media_pause(self):
""" Service to send the chromecast the command for play/pause. """ """ Send pause command. """
if self.media_state == MEDIA_STATE_PLAYING: self.cast.media_controller.pause()
self.cast.media_controller.pause()
def media_prev_track(self): def media_previous_track(self):
""" media_prev_track media player. """ """ Send previous track command. """
self.cast.media_controller.rewind() self.cast.media_controller.rewind()
def media_next_track(self): def media_next_track(self):
""" media_next_track media player. """ """ Send next track command. """
self.cast.media_controller.skip() self.cast.media_controller.skip()
def play_youtube_video(self, video_id): def media_seek(self, position):
""" Plays specified video_id on the Chromecast's YouTube channel. """ """ Seek the media to a specific location. """
self.youtube.play_video(video_id) 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): def new_cast_status(self, status):
""" Called when a new cast status is received. """ """ Called when a new cast status is received. """

View File

@ -5,121 +5,334 @@ homeassistant.components.media_player.demo
Demo implementation of the media player. Demo implementation of the media player.
""" """
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_DURATION, MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
ATTR_MEDIA_VOLUME, MEDIA_STATE_PLAYING, MEDIA_STATE_STOPPED, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
YOUTUBE_COVER_URL_FORMAT, ATTR_MEDIA_IS_VOLUME_MUTED) SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
from homeassistant.const import ATTR_ENTITY_PICTURE SUPPORT_NEXT_TRACK)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """ """ Sets up the cast platform. """
add_devices([ add_devices([
DemoMediaPlayer( DemoYoutubePlayer(
'Living Room', 'eyU3bRy2x44', 'Living Room', 'eyU3bRy2x44',
'♥♥ The Best Fireplace Video (3 hours)'), '♥♥ 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): YOUTUBE_PLAYER_SUPPORT = \
""" A Demo media player that only supports YouTube. """ 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._name = name
self.is_playing = youtube_id is not None self._player_state = STATE_PLAYING
self.youtube_id = youtube_id self._volume_level = 1.0
self.media_title = media_title self._volume_muted = False
self.volume = 1.0
self.is_volume_muted = False
@property @property
def should_poll(self): def should_poll(self):
""" No polling needed for a demo componentn. """ """ We will push an update after each command. """
return False return False
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """ Name of the media player. """
return self._name return self._name
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """ State of the player. """
return STATE_NO_APP if self.youtube_id is None else "YouTube" return self._player_state
@property @property
def state_attributes(self): def volume_level(self):
""" Returns the state attributes. """ """ Volume level of the media player (0..1). """
if self.youtube_id is None: return self._volume_level
return
state_attr = { @property
ATTR_MEDIA_CONTENT_ID: self.youtube_id, def is_volume_muted(self):
ATTR_MEDIA_TITLE: self.media_title, """ Boolean if volume is currently muted. """
ATTR_MEDIA_DURATION: 100, return self._volume_muted
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
def turn_on(self): def turn_on(self):
""" turn_off media player. """ """ turn the media player on. """
self.youtube_id = "eyU3bRy2x44" self._player_state = STATE_PLAYING
self.is_playing = False
self.update_ha_state() self.update_ha_state()
def turn_off(self): def turn_off(self):
""" turn_off media player. """ """ turn the media player off. """
self.youtube_id = None self._player_state = STATE_OFF
self.is_playing = False
self.update_ha_state() self.update_ha_state()
def volume_up(self): def mute_volume(self, mute):
""" volume_up media player. """ """ mute the volume. """
if self.volume < 1: self._volume_muted = mute
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
self.update_ha_state() self.update_ha_state()
def media_play_pause(self): def set_volume_level(self, volume):
""" media_play_pause media player. """ """ set volume level, range 0..1. """
self.is_playing = not self.is_playing self._volume_level = volume
self.update_ha_state() self.update_ha_state()
def media_play(self): def media_play(self):
""" media_play media player. """ """ Send play commmand. """
self.is_playing = True self._player_state = STATE_PLAYING
self.update_ha_state() self.update_ha_state()
def media_pause(self): def media_pause(self):
""" media_pause media player. """ """ Send pause command. """
self.is_playing = False self._player_state = STATE_PAUSED
self.update_ha_state() 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): def play_youtube(self, media_id):
""" Plays a YouTube media. """ """ Plays a YouTube media. """
self.youtube_id = media_id self.youtube_id = media_id
self.media_title = 'Demo media title' self._media_title = 'some YouTube video'
self.is_playing = True
self.update_ha_state() 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()

View File

@ -32,16 +32,28 @@ Location of your Music Player Daemon.
import logging import logging
import socket 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 ( from homeassistant.components.media_player import (
MediaPlayerDevice, STATE_NO_APP, ATTR_MEDIA_STATE, MediaPlayerDevice,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_TURN_OFF,
ATTR_MEDIA_ALBUM, ATTR_MEDIA_DATE, ATTR_MEDIA_DURATION, SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
ATTR_MEDIA_VOLUME, MEDIA_STATE_PAUSED, MEDIA_STATE_PLAYING, MEDIA_TYPE_MUSIC)
MEDIA_STATE_STOPPED, MEDIA_STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
# pylint: disable=unused-argument # pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the MPD platform. """ """ Sets up the MPD platform. """
@ -50,10 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
port = config.get('port', 6600) port = config.get('port', 6600)
location = config.get('location', 'MPD') location = config.get('location', 'MPD')
try: if mpd is None:
from mpd import MPDClient
except ImportError:
_LOGGER.exception( _LOGGER.exception(
"Unable to import mpd2. " "Unable to import mpd2. "
"Did you maybe not install the 'python-mpd2' package?") "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 # pylint: disable=no-member
try: try:
mpd_client = MPDClient() mpd_client = mpd.MPDClient()
mpd_client.connect(daemon, port) mpd_client.connect(daemon, port)
mpd_client.close() mpd_client.close()
mpd_client.disconnect() mpd_client.disconnect()
@ -73,110 +82,112 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False return False
mpd = [] add_devices([MpdDevice(daemon, port, location)])
mpd.append(MpdDevice(daemon, port, location))
add_devices(mpd)
class MpdDevice(MediaPlayerDevice): class MpdDevice(MediaPlayerDevice):
""" Represents a MPD server. """ """ Represents a MPD server. """
def __init__(self, server, port, location): # MPD confuses pylint
from mpd import MPDClient # pylint: disable=no-member, abstract-method
def __init__(self, server, port, location):
self.server = server self.server = server
self.port = port self.port = port
self._name = location 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.timeout = 10
self.client.idletimeout = None 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 @property
def name(self): def name(self):
""" Returns the name of the device. """ """ Returns the name of the device. """
return self._name return self._name
# pylint: disable=no-member
@property @property
def state(self): 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. """ """ Returns the media state. """
media_controller = self.client.status() if self.status['state'] == 'play':
return STATE_PLAYING
if media_controller['state'] == 'play': elif self.status['state'] == 'pause':
return MEDIA_STATE_PLAYING return STATE_PAUSED
elif media_controller['state'] == 'pause':
return MEDIA_STATE_PAUSED
elif media_controller['state'] == 'stop':
return MEDIA_STATE_STOPPED
else: else:
return MEDIA_STATE_UNKNOWN return STATE_OFF
# pylint: disable=no-member
@property @property
def state_attributes(self): def media_content_id(self):
""" Returns the state attributes. """ """ Content ID of current playing media. """
status = self.client.status() return self.currentsong['id']
current_song = self.client.currentsong()
if not status and not current_song: @property
state_attr = {} def media_content_type(self):
""" Content type of current playing media. """
return MEDIA_TYPE_MUSIC
if current_song['id']: @property
state_attr[ATTR_MEDIA_CONTENT_ID] = current_song['id'] 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']: @property
state_attr[ATTR_MEDIA_DATE] = current_song['date'] def media_title(self):
""" Title of current playing media. """
return self.currentsong['title']
if current_song['title']: @property
state_attr[ATTR_MEDIA_TITLE] = current_song['title'] def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self.currentsong.get('artist')
if current_song['time']: @property
state_attr[ATTR_MEDIA_DURATION] = current_song['time'] def media_album_name(self):
""" Album of current playing media. (Music track only) """
return self.currentsong.get('album')
if current_song['artist']: @property
state_attr[ATTR_MEDIA_ARTIST] = current_song['artist'] def volume_level(self):
return int(self.status['volume'])/100
if current_song['album']: @property
state_attr[ATTR_MEDIA_ALBUM] = current_song['album'] def supported_media_commands(self):
""" Flags of media commands that are supported. """
state_attr[ATTR_MEDIA_VOLUME] = status['volume'] return SUPPORT_MPD
return state_attr
def turn_off(self): def turn_off(self):
""" Service to exit the running MPD. """ """ Service to exit the running MPD. """
self.client.stop() self.client.stop()
def set_volume_level(self, volume):
""" Sets volume """
self.client.setvol(int(volume * 100))
def volume_up(self): def volume_up(self):
""" Service to send the MPD the command for volume up. """ """ 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: if current_volume <= 100:
self.client.setvol(int(current_volume) + 5) self.client.setvol(current_volume + 5)
def volume_down(self): def volume_down(self):
""" Service to send the MPD the command for volume down. """ """ 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: if current_volume >= 0:
self.client.setvol(int(current_volume) - 5) self.client.setvol(current_volume - 5)
def media_play_pause(self):
""" Service to send the MPD the command for play/pause. """
self.client.pause()
def media_play(self): def media_play(self):
""" Service to send the MPD the command for play/pause. """ """ 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. """ """ Service to send the MPD the command for next track. """
self.client.next() self.client.next()
def media_prev_track(self): def media_previous_track(self):
""" Service to send the MPD the command for previous track. """ """ Service to send the MPD the command for previous track. """
self.client.previous() self.client.previous()

View File

@ -40,6 +40,9 @@ STATE_NOT_HOME = 'not_home'
STATE_UNKNOWN = "unknown" STATE_UNKNOWN = "unknown"
STATE_OPEN = 'open' STATE_OPEN = 'open'
STATE_CLOSED = 'closed' STATE_CLOSED = 'closed'
STATE_PLAYING = 'playing'
STATE_PAUSED = 'paused'
STATE_IDLE = 'idle'
# #### STATE AND EVENT ATTRIBUTES #### # #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event # 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_PLAY = "media_play"
SERVICE_MEDIA_PAUSE = "media_pause" SERVICE_MEDIA_PAUSE = "media_pause"
SERVICE_MEDIA_NEXT_TRACK = "media_next_track" 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 #### # #### API / REMOTE ####
SERVER_PORT = 8123 SERVER_PORT = 8123

View File

@ -18,7 +18,7 @@ phue>=0.8
ledcontroller>=1.0.7 ledcontroller>=1.0.7
# Chromecast bindings (media_player.cast) # Chromecast bindings (media_player.cast)
pychromecast>=0.6.4 pychromecast>=0.6.5
# Keyboard (keyboard) # Keyboard (keyboard)
pyuserinput>=0.1.9 pyuserinput>=0.1.9

View File

@ -10,9 +10,10 @@ import unittest
import homeassistant as ha import homeassistant as ha
from homeassistant.const import ( from homeassistant.const import (
STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE, 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 import homeassistant.components.media_player as media_player
from helpers import mock_service from helpers import mock_service
@ -29,7 +30,7 @@ class TestMediaPlayer(unittest.TestCase):
self.hass = ha.HomeAssistant() self.hass = ha.HomeAssistant()
self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room') 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.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom')
self.hass.states.set(self.test_entity2, "YouTube") 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_PLAY: media_player.media_play,
SERVICE_MEDIA_PAUSE: media_player.media_pause, SERVICE_MEDIA_PAUSE: media_player.media_pause,
SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track, 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(): for service_name, service_method in services.items():