core/homeassistant/components/media_player/itunes.py

468 lines
14 KiB
Python
Raw Normal View History

2015-09-12 03:06:03 +00:00
"""
homeassistant.components.media_player.itunes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to iTunes-API (https://github.com/maddox/itunes-api)
2015-09-14 21:34:57 +00:00
The iTunes media player will allow you to control your iTunes instance. You
can play/pause/next/previous/mute, adjust volume, etc.
In addition to controlling iTunes, your available AirPlay endpoints will be
added as media players as well. You can then individually address them append
turn them on, turn them off, or adjust their volume.
2015-09-12 03:06:03 +00:00
Configuration:
To use iTunes you will need to add something like the following to
your configuration.yaml file.
media_player:
platform: itunes
name: iTunes
host: http://192.168.1.16
port: 8181
Variables:
name
*Optional
The name of the device.
url
*Required
2015-09-12 03:49:43 +00:00
URL of your running version of iTunes-API. Example: http://192.168.1.50:8181
2015-09-12 03:06:03 +00:00
"""
import logging
from homeassistant.components.media_player import (
2015-10-07 03:12:48 +00:00
MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE,
SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
2015-09-14 21:27:00 +00:00
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS)
2015-09-12 03:06:03 +00:00
from homeassistant.const import (
2015-09-14 21:27:00 +00:00
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON)
2015-09-12 03:06:03 +00:00
2015-09-12 04:50:40 +00:00
import requests
2015-09-12 03:06:03 +00:00
_LOGGER = logging.getLogger(__name__)
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
2015-10-07 03:12:48 +00:00
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
SUPPORT_PLAY_MEDIA
2015-09-12 03:06:03 +00:00
2015-09-14 21:27:00 +00:00
SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
DOMAIN = 'itunes'
2015-09-12 03:49:43 +00:00
2015-09-12 03:06:03 +00:00
class Itunes(object):
2015-09-12 04:23:04 +00:00
""" itunes-api client. """
2015-09-12 03:06:03 +00:00
def __init__(self, host, port):
self.host = host
self.port = port
@property
def _base_url(self):
2015-09-12 04:42:11 +00:00
""" Returns the base url for endpoints. """
2015-09-12 03:06:03 +00:00
return self.host + ":" + str(self.port)
def _request(self, method, path, params=None):
2015-09-12 04:42:11 +00:00
""" Makes the actual request and returns the parsed response. """
2015-09-12 03:06:03 +00:00
url = self._base_url + path
try:
if method == 'GET':
2015-09-12 04:42:11 +00:00
response = requests.get(url)
2015-09-12 03:06:03 +00:00
elif method == "POST":
2015-09-12 04:42:11 +00:00
response = requests.put(url, params)
2015-09-12 03:06:03 +00:00
elif method == "PUT":
2015-09-12 04:42:11 +00:00
response = requests.put(url, params)
2015-09-12 03:06:03 +00:00
elif method == "DELETE":
2015-09-12 04:42:11 +00:00
response = requests.delete(url)
2015-09-12 03:06:03 +00:00
2015-09-12 04:42:11 +00:00
return response.json()
2015-09-12 03:06:03 +00:00
except requests.exceptions.HTTPError:
return {'player_state': 'error'}
except requests.exceptions.RequestException:
return {'player_state': 'offline'}
def _command(self, named_command):
2015-09-12 04:42:11 +00:00
""" Makes a request for a controlling command. """
2015-09-12 03:06:03 +00:00
return self._request('PUT', '/' + named_command)
def now_playing(self):
2015-09-12 04:42:11 +00:00
""" Returns the current state. """
2015-09-12 03:06:03 +00:00
return self._request('GET', '/now_playing')
def set_volume(self, level):
2015-09-12 04:42:11 +00:00
""" Sets the volume and returns the current state, level 0-100. """
2015-09-12 03:06:03 +00:00
return self._request('PUT', '/volume', {'level': level})
def set_muted(self, muted):
2015-09-12 04:42:11 +00:00
""" Mutes and returns the current state, muted True or False. """
2015-09-12 03:06:03 +00:00
return self._request('PUT', '/mute', {'muted': muted})
def play(self):
2015-09-12 04:42:11 +00:00
""" Sets playback to play and returns the current state. """
2015-09-12 03:06:03 +00:00
return self._command('play')
def pause(self):
2015-09-12 04:42:11 +00:00
""" Sets playback to paused and returns the current state. """
2015-09-12 03:06:03 +00:00
return self._command('pause')
def next(self):
2015-09-12 04:42:11 +00:00
""" Skips to the next track and returns the current state. """
2015-09-12 03:06:03 +00:00
return self._command('next')
def previous(self):
2015-09-12 04:42:11 +00:00
""" Skips back and returns the current state. """
2015-09-12 03:06:03 +00:00
return self._command('previous')
def play_playlist(self, playlist_id_or_name):
""" Sets a playlist to be current and returns the current state. """
response = self._request('GET', '/playlists')
playlists = response.get('playlists', [])
found_playlists = [playlist for playlist in playlists if playlist["name"] == playlist_id_or_name or playlist["id"] == playlist_id_or_name]
if len(found_playlists) > 0:
playlist = found_playlists[0]
return self._request('PUT', '/playlists/' + playlist['id'] + '/play')
2015-09-12 03:06:03 +00:00
def artwork_url(self):
2015-09-12 04:42:11 +00:00
""" Returns a URL of the current track's album art. """
2015-09-12 03:06:03 +00:00
return self._base_url + '/artwork'
2015-09-14 21:27:00 +00:00
def airplay_devices(self):
""" Returns a list of AirPlay devices. """
return self._request('GET', '/airplay_devices')
2015-09-14 21:39:43 +00:00
def airplay_device(self, device_id):
2015-09-14 21:27:00 +00:00
""" Returns an AirPlay device. """
2015-09-14 21:39:43 +00:00
return self._request('GET', '/airplay_devices/' + device_id)
2015-09-14 21:27:00 +00:00
2015-09-14 21:39:43 +00:00
def toggle_airplay_device(self, device_id, toggle):
2015-09-14 21:27:00 +00:00
""" Toggles airplay device on or off, id, toggle True or False. """
command = 'on' if toggle else 'off'
2015-09-14 21:39:43 +00:00
path = '/airplay_devices/' + device_id + '/' + command
return self._request('PUT', path)
2015-09-14 21:27:00 +00:00
2015-09-14 21:39:43 +00:00
def set_volume_airplay_device(self, device_id, level):
2015-09-14 21:27:00 +00:00
""" Sets volume, returns current state of device, id,level 0-100. """
2015-09-14 21:39:43 +00:00
path = '/airplay_devices/' + device_id + '/volume'
2015-09-14 21:27:00 +00:00
return self._request('PUT', path, {'level': level})
2015-09-12 03:06:03 +00:00
# pylint: disable=unused-argument
2015-09-12 04:42:11 +00:00
# pylint: disable=abstract-method
# pylint: disable=too-many-instance-attributes
2015-09-12 04:16:51 +00:00
2015-09-12 04:49:34 +00:00
2015-09-12 03:06:03 +00:00
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the itunes platform. """
add_devices([
ItunesDevice(
config.get('name', 'iTunes'),
config.get('host'),
2015-09-14 21:27:00 +00:00
config.get('port'),
add_devices
2015-09-12 03:06:03 +00:00
)
])
2015-09-12 03:49:43 +00:00
2015-09-12 03:06:03 +00:00
class ItunesDevice(MediaPlayerDevice):
""" Represents a iTunes-API instance. """
# pylint: disable=too-many-public-methods
2015-09-14 21:27:00 +00:00
def __init__(self, name, host, port, add_devices):
2015-09-12 03:06:03 +00:00
self._name = name
self._host = host
self._port = port
2015-09-14 21:27:00 +00:00
self._add_devices = add_devices
2015-09-12 03:06:03 +00:00
self.client = Itunes(self._host, self._port)
self.current_volume = None
self.muted = None
self.current_title = None
self.current_album = None
self.current_artist = None
self.current_playlist = None
self.content_id = None
self.player_state = None
2015-09-14 21:27:00 +00:00
self.airplay_devices = {}
2015-09-12 03:06:03 +00:00
self.update()
2015-09-12 04:42:11 +00:00
def update_state(self, state_hash):
""" Update all the state properties with the passed in dictionary. """
2015-09-12 03:06:03 +00:00
self.player_state = state_hash.get('player_state', None)
self.current_volume = state_hash.get('volume', 0)
self.muted = state_hash.get('muted', None)
self.current_title = state_hash.get('name', None)
self.current_album = state_hash.get('album', None)
self.current_artist = state_hash.get('artist', None)
self.current_playlist = state_hash.get('playlist', None)
self.content_id = state_hash.get('id', None)
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
2015-09-12 03:49:43 +00:00
if self.player_state == 'offline' or self.player_state is None:
2015-09-12 03:06:03 +00:00
return 'offline'
if self.player_state == 'error':
return 'error'
if self.player_state == 'stopped':
return STATE_IDLE
if self.player_state == 'paused':
return STATE_PAUSED
else:
return STATE_PLAYING
def update(self):
""" Retrieve latest state. """
now_playing = self.client.now_playing()
2015-09-12 04:42:11 +00:00
self.update_state(now_playing)
2015-09-12 03:06:03 +00:00
2015-09-14 21:27:00 +00:00
found_devices = self.client.airplay_devices()
found_devices = found_devices.get('airplay_devices', [])
new_devices = []
for device_data in found_devices:
device_id = device_data.get('id')
if self.airplay_devices.get(device_id):
# update it
airplay_device = self.airplay_devices.get(device_id)
airplay_device.update_state(device_data)
else:
# add it
airplay_device = AirPlayDevice(device_id, self.client)
airplay_device.update_state(device_data)
self.airplay_devices[device_id] = airplay_device
new_devices.append(airplay_device)
if new_devices:
self._add_devices(new_devices)
2015-09-12 03:06:03 +00:00
@property
def is_volume_muted(self):
""" Boolean if volume is currently muted. """
return self.muted
@property
def volume_level(self):
2015-09-12 04:42:11 +00:00
""" Volume level of the media player (0..1). """
2015-09-12 03:06:03 +00:00
return self.current_volume/100.0
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self.content_id
@property
def media_content_type(self):
2015-09-12 04:42:11 +00:00
""" Content type of current playing media. """
2015-09-12 03:06:03 +00:00
return MEDIA_TYPE_MUSIC
@property
def media_image_url(self):
""" Image url of current playing media. """
2015-09-12 04:16:51 +00:00
if self.player_state in (STATE_PLAYING, STATE_IDLE, STATE_PAUSED) and \
self.current_title is not None:
2015-09-12 03:06:03 +00:00
return self.client.artwork_url()
else:
2015-09-12 04:49:34 +00:00
return 'https://cloud.githubusercontent.com/assets/260/9829355' \
'/33fab972-58cf-11e5-8ea2-2ca74bdaae40.png'
2015-09-12 04:16:51 +00:00
2015-09-12 03:06:03 +00:00
@property
def media_title(self):
""" Title of current playing media. """
return self.current_title
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self.current_artist
@property
def media_album_name(self):
""" Album of current playing media. (Music track only) """
return self.current_album
2015-10-07 03:12:30 +00:00
@property
def media_playlist(self):
""" Title of the currently playing playlist. """
return self.current_playlist
2015-09-12 03:06:03 +00:00
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_ITUNES
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
2015-09-12 04:42:11 +00:00
response = self.client.set_volume(int(volume * 100))
self.update_state(response)
2015-09-12 03:06:03 +00:00
def mute_volume(self, mute):
""" mute (true) or unmute (false) media player. """
2015-09-12 04:42:11 +00:00
response = self.client.set_muted(mute)
self.update_state(response)
2015-09-12 03:06:03 +00:00
def media_play(self):
""" media_play media player. """
2015-09-12 04:42:11 +00:00
response = self.client.play()
self.update_state(response)
2015-09-12 03:06:03 +00:00
def media_pause(self):
""" media_pause media player. """
2015-09-12 04:42:11 +00:00
response = self.client.pause()
self.update_state(response)
2015-09-12 03:06:03 +00:00
def media_next_track(self):
""" media_next media player. """
2015-09-12 04:42:11 +00:00
response = self.client.next()
self.update_state(response)
2015-09-12 03:06:03 +00:00
def media_previous_track(self):
""" media_previous media player. """
2015-09-12 04:42:11 +00:00
response = self.client.previous()
self.update_state(response)
2015-09-14 21:27:00 +00:00
2015-10-07 03:12:41 +00:00
def play_media(self, media_type:None, media_id:None):
""" play_media media player. """
response = self.client.play_playlist(media_id)
self.update_state(response)
2015-09-14 21:27:00 +00:00
class AirPlayDevice(MediaPlayerDevice):
""" Represents an AirPlay device via an iTunes-API instance. """
# pylint: disable=too-many-public-methods
2015-09-14 21:39:43 +00:00
def __init__(self, device_id, client):
self._id = device_id
2015-09-14 21:27:00 +00:00
self.client = client
self.device_name = "AirPlay"
self.kind = None
self.active = False
self.selected = False
self.volume = 0
self.supports_audio = False
self.supports_video = False
2015-09-14 21:39:43 +00:00
self.player_state = None
2015-09-14 21:27:00 +00:00
def update_state(self, state_hash):
2015-09-14 21:39:43 +00:00
""" Update all the state properties with the passed in dictionary. """
2015-09-14 21:27:00 +00:00
if 'player_state' in state_hash:
self.player_state = state_hash.get('player_state', None)
if 'name' in state_hash:
name = state_hash.get('name', '')
2015-09-16 01:40:39 +00:00
self.device_name = (name + ' AirTunes Speaker').strip()
2015-09-14 21:27:00 +00:00
if 'kind' in state_hash:
self.kind = state_hash.get('kind', None)
if 'active' in state_hash:
self.active = state_hash.get('active', None)
if 'selected' in state_hash:
self.selected = state_hash.get('selected', None)
if 'sound_volume' in state_hash:
self.volume = state_hash.get('sound_volume', 0)
if 'supports_audio' in state_hash:
self.supports_audio = state_hash.get('supports_audio', None)
if 'supports_video' in state_hash:
self.supports_video = state_hash.get('supports_video', None)
@property
def name(self):
""" Returns the name of the device. """
return self.device_name
@property
def state(self):
""" Returns the state of the device. """
if self.selected is True:
return STATE_ON
else:
return STATE_OFF
def update(self):
""" Retrieve latest state. """
@property
def volume_level(self):
return float(self.volume)/100.0
@property
def media_content_type(self):
return MEDIA_TYPE_MUSIC
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_AIRPLAY
@property
def device_state_attributes(self):
""" Return the state attributes. """
state_attr = {}
state_attr[ATTR_SUPPORTED_MEDIA_COMMANDS] = SUPPORT_AIRPLAY
if self.state == STATE_OFF:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833073'
'/6eb5c906-5958-11e5-9b4a-472cdf36be16.png')
else:
state_attr[ATTR_ENTITY_PICTURE] = \
('https://cloud.githubusercontent.com/assets/260/9833072'
'/6eb13cce-5958-11e5-996f-e2aaefbc9a24.png')
return state_attr
def set_volume_level(self, volume):
""" set volume level, range 0..1. """
volume = int(volume * 100)
response = self.client.set_volume_airplay_device(self._id, volume)
self.update_state(response)
def turn_on(self):
""" Select AirPlay. """
self.update_state({"selected": True})
self.update_ha_state()
response = self.client.toggle_airplay_device(self._id, True)
self.update_state(response)
def turn_off(self):
""" Deselect AirPlay. """
self.update_state({"selected": False})
self.update_ha_state()
response = self.client.toggle_airplay_device(self._id, False)
self.update_state(response)