Proxy requests to the media player's media image (#2693)
This is needed when the media server and UI client are not on the same network.
This commit is contained in:
parent
e36c6b24ee
commit
bb22ad3064
2 changed files with 92 additions and 7 deletions
|
@ -6,6 +6,7 @@ https://home-assistant.io/components/media_player/
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ from homeassistant.config import load_yaml_config_file
|
||||||
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.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||||
|
from homeassistant.components.http import HomeAssistantView
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE,
|
STATE_OFF, STATE_UNKNOWN, STATE_PLAYING, STATE_IDLE,
|
||||||
|
@ -25,10 +27,13 @@ from homeassistant.const import (
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = 'media_player'
|
DOMAIN = 'media_player'
|
||||||
|
DEPENDENCIES = ['http']
|
||||||
SCAN_INTERVAL = 10
|
SCAN_INTERVAL = 10
|
||||||
|
|
||||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||||
|
|
||||||
|
ENTITY_IMAGE_URL = '/api/media_player_proxy/{0}?token={1}'
|
||||||
|
|
||||||
SERVICE_PLAY_MEDIA = 'play_media'
|
SERVICE_PLAY_MEDIA = 'play_media'
|
||||||
SERVICE_SELECT_SOURCE = 'select_source'
|
SERVICE_SELECT_SOURCE = 'select_source'
|
||||||
SERVICE_CLEAR_PLAYLIST = 'clear_playlist'
|
SERVICE_CLEAR_PLAYLIST = 'clear_playlist'
|
||||||
|
@ -286,6 +291,8 @@ def setup(hass, config):
|
||||||
component = EntityComponent(
|
component = EntityComponent(
|
||||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
||||||
|
|
||||||
|
hass.wsgi.register_view(MediaPlayerImageView(hass, component.entities))
|
||||||
|
|
||||||
component.setup(config)
|
component.setup(config)
|
||||||
|
|
||||||
descriptions = load_yaml_config_file(
|
descriptions = load_yaml_config_file(
|
||||||
|
@ -398,6 +405,11 @@ class MediaPlayerDevice(Entity):
|
||||||
"""State of the player."""
|
"""State of the player."""
|
||||||
return STATE_UNKNOWN
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def access_token(self):
|
||||||
|
"""Access token for this media player."""
|
||||||
|
return str(id(self))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume_level(self):
|
def volume_level(self):
|
||||||
"""Volume level of the media player (0..1)."""
|
"""Volume level of the media player (0..1)."""
|
||||||
|
@ -633,7 +645,8 @@ class MediaPlayerDevice(Entity):
|
||||||
@property
|
@property
|
||||||
def entity_picture(self):
|
def entity_picture(self):
|
||||||
"""Return image of the media playing."""
|
"""Return image of the media playing."""
|
||||||
return None if self.state == STATE_OFF else self.media_image_url
|
return None if self.state == STATE_OFF else \
|
||||||
|
ENTITY_IMAGE_URL.format(self.entity_id, self.access_token)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
|
@ -649,3 +662,35 @@ class MediaPlayerDevice(Entity):
|
||||||
}
|
}
|
||||||
|
|
||||||
return state_attr
|
return state_attr
|
||||||
|
|
||||||
|
|
||||||
|
class MediaPlayerImageView(HomeAssistantView):
|
||||||
|
"""Media player view to serve an image."""
|
||||||
|
|
||||||
|
url = "/api/media_player_proxy/<entity(domain=media_player):entity_id>"
|
||||||
|
name = "api:media_player:image"
|
||||||
|
|
||||||
|
def __init__(self, hass, entities):
|
||||||
|
"""Initialize a media player view."""
|
||||||
|
super().__init__(hass)
|
||||||
|
self.entities = entities
|
||||||
|
|
||||||
|
def get(self, request, entity_id):
|
||||||
|
"""Start a get request."""
|
||||||
|
player = self.entities.get(entity_id)
|
||||||
|
|
||||||
|
if player is None:
|
||||||
|
return self.Response(status=404)
|
||||||
|
|
||||||
|
authenticated = (request.authenticated or
|
||||||
|
request.args.get('token') == player.access_token)
|
||||||
|
|
||||||
|
if not authenticated:
|
||||||
|
return self.Response(status=401)
|
||||||
|
|
||||||
|
response = requests.get(player.media_image_url)
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
return self.Response(status=500)
|
||||||
|
|
||||||
|
return self.Response(response)
|
||||||
|
|
|
@ -1,23 +1,50 @@
|
||||||
"""The tests for the Demo Media player platform."""
|
"""The tests for the Demo Media player platform."""
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from homeassistant import bootstrap
|
||||||
import homeassistant.components.media_player as mp
|
import homeassistant.components.media_player as mp
|
||||||
|
import homeassistant.components.http as http
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant
|
import requests
|
||||||
|
import requests_mock
|
||||||
|
import time
|
||||||
|
|
||||||
|
from tests.common import get_test_home_assistant, get_test_instance_port
|
||||||
|
|
||||||
|
SERVER_PORT = get_test_instance_port()
|
||||||
|
HTTP_BASE_URL = 'http://127.0.0.1:{}'.format(SERVER_PORT)
|
||||||
|
|
||||||
|
hass = None
|
||||||
|
|
||||||
entity_id = 'media_player.walkman'
|
entity_id = 'media_player.walkman'
|
||||||
|
|
||||||
|
|
||||||
|
def setUpModule(): # pylint: disable=invalid-name
|
||||||
|
"""Initalize a Home Assistant server."""
|
||||||
|
global hass
|
||||||
|
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
bootstrap.setup_component(hass, http.DOMAIN, {
|
||||||
|
http.DOMAIN: {
|
||||||
|
http.CONF_SERVER_PORT: SERVER_PORT
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
hass.start()
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
"""Stop the Home Assistant server."""
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
|
||||||
class TestDemoMediaPlayer(unittest.TestCase):
|
class TestDemoMediaPlayer(unittest.TestCase):
|
||||||
"""Test the media_player module."""
|
"""Test the media_player module."""
|
||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = hass
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
|
||||||
"""Stop everything that was started."""
|
|
||||||
self.hass.stop()
|
|
||||||
|
|
||||||
def test_source_select(self):
|
def test_source_select(self):
|
||||||
"""Test the input source service."""
|
"""Test the input source service."""
|
||||||
|
@ -175,6 +202,19 @@ class TestDemoMediaPlayer(unittest.TestCase):
|
||||||
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
|
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
|
||||||
state.attributes.get('supported_media_commands'))
|
state.attributes.get('supported_media_commands'))
|
||||||
|
|
||||||
|
@requests_mock.Mocker(real_http=True)
|
||||||
|
def test_media_image_proxy(self, m):
|
||||||
|
"""Test the media server image proxy server ."""
|
||||||
|
fake_picture_data = 'test.test'
|
||||||
|
m.get('https://graph.facebook.com/v2.5/107771475912710/'
|
||||||
|
'picture?type=large', text=fake_picture_data)
|
||||||
|
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
|
||||||
|
assert self.hass.states.is_state(entity_id, 'playing')
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
req = requests.get(HTTP_BASE_URL +
|
||||||
|
state.attributes.get('entity_picture'))
|
||||||
|
assert req.text == fake_picture_data
|
||||||
|
|
||||||
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.'
|
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.'
|
||||||
'media_seek')
|
'media_seek')
|
||||||
def test_play_media(self, mock_seek):
|
def test_play_media(self, mock_seek):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue