mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Support announce and enqueue in forked-daapd (#77744)
This commit is contained in:
parent
e079968ef4
commit
420285f7ef
@ -12,7 +12,10 @@ from pylibrespot_java import LibrespotJavaAPI
|
||||
|
||||
from homeassistant.components import media_source
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_ANNOUNCE,
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
BrowseMedia,
|
||||
MediaPlayerEnqueue,
|
||||
MediaPlayerEntity,
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
@ -349,11 +352,8 @@ class ForkedDaapdMaster(MediaPlayerEntity):
|
||||
@callback
|
||||
def _update_queue(self, queue, event):
|
||||
self._queue = queue
|
||||
if (
|
||||
self._tts_requested
|
||||
and self._queue["count"] == 1
|
||||
and self._queue["items"][0]["uri"].find("tts_proxy") != -1
|
||||
):
|
||||
if self._tts_requested:
|
||||
# Assume the change was due to the request
|
||||
self._tts_requested = False
|
||||
self._tts_queued = True
|
||||
|
||||
@ -669,10 +669,48 @@ class ForkedDaapdMaster(MediaPlayerEntity):
|
||||
|
||||
if media_type == MediaType.MUSIC:
|
||||
media_id = async_process_play_media_url(self.hass, media_id)
|
||||
elif media_type not in CAN_PLAY_TYPE:
|
||||
_LOGGER.warning("Media type '%s' not supported", media_type)
|
||||
return
|
||||
|
||||
await self._async_announce(media_id)
|
||||
else:
|
||||
_LOGGER.debug("Media type '%s' not supported", media_type)
|
||||
if kwargs.get(ATTR_MEDIA_ANNOUNCE):
|
||||
return await self._async_announce(media_id)
|
||||
|
||||
# if kwargs[ATTR_MEDIA_ENQUEUE] is None, we assume MediaPlayerEnqueue.REPLACE
|
||||
# if kwargs[ATTR_MEDIA_ENQUEUE] is True, we assume MediaPlayerEnqueue.ADD
|
||||
# kwargs[ATTR_MEDIA_ENQUEUE] is assumed to never be False
|
||||
# See https://github.com/home-assistant/architecture/issues/765
|
||||
enqueue: bool | MediaPlayerEnqueue = kwargs.get(
|
||||
ATTR_MEDIA_ENQUEUE, MediaPlayerEnqueue.REPLACE
|
||||
)
|
||||
if enqueue in {True, MediaPlayerEnqueue.ADD, MediaPlayerEnqueue.REPLACE}:
|
||||
return await self._api.add_to_queue(
|
||||
uris=media_id,
|
||||
playback="start",
|
||||
clear=enqueue == MediaPlayerEnqueue.REPLACE,
|
||||
)
|
||||
|
||||
current_position = next(
|
||||
(
|
||||
item["position"]
|
||||
for item in self._queue["items"]
|
||||
if item["id"] == self._player["item_id"]
|
||||
),
|
||||
0,
|
||||
)
|
||||
if enqueue == MediaPlayerEnqueue.NEXT:
|
||||
return await self._api.add_to_queue(
|
||||
uris=media_id,
|
||||
playback="start",
|
||||
position=current_position + 1,
|
||||
)
|
||||
# enqueue == MediaPlayerEnqueue.PLAY
|
||||
return await self._api.add_to_queue(
|
||||
uris=media_id,
|
||||
playback="start",
|
||||
position=current_position,
|
||||
playback_from_position=current_position,
|
||||
)
|
||||
|
||||
async def _async_announce(self, media_id: str) -> None:
|
||||
"""Play a URI."""
|
||||
|
@ -22,10 +22,12 @@ from homeassistant.components.media_player import (
|
||||
ATTR_INPUT_SOURCE,
|
||||
ATTR_MEDIA_ALBUM_ARTIST,
|
||||
ATTR_MEDIA_ALBUM_NAME,
|
||||
ATTR_MEDIA_ANNOUNCE,
|
||||
ATTR_MEDIA_ARTIST,
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
ATTR_MEDIA_DURATION,
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
ATTR_MEDIA_POSITION,
|
||||
ATTR_MEDIA_SEEK_POSITION,
|
||||
ATTR_MEDIA_SHUFFLE,
|
||||
@ -49,6 +51,7 @@ from homeassistant.components.media_player import (
|
||||
SERVICE_TURN_ON,
|
||||
SERVICE_VOLUME_MUTE,
|
||||
SERVICE_VOLUME_SET,
|
||||
MediaPlayerEnqueue,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
@ -112,6 +115,28 @@ SAMPLE_PLAYER_STOPPED = {
|
||||
"item_progress_ms": 5,
|
||||
}
|
||||
|
||||
SAMPLE_QUEUE = {
|
||||
"version": 833,
|
||||
"count": 1,
|
||||
"items": [
|
||||
{
|
||||
"id": 12322,
|
||||
"position": 0,
|
||||
"track_id": 1234,
|
||||
"title": "Some song",
|
||||
"artist": "Some artist",
|
||||
"album": "No album",
|
||||
"album_artist": "The xx",
|
||||
"artwork_url": "http://art",
|
||||
"length_ms": 0,
|
||||
"track_number": 1,
|
||||
"media_kind": "music",
|
||||
"data_kind": "url",
|
||||
"uri": "library:track:5",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
SAMPLE_QUEUE_TTS = {
|
||||
"version": 833,
|
||||
"count": 1,
|
||||
@ -291,7 +316,7 @@ async def get_request_return_values_fixture():
|
||||
"config": SAMPLE_CONFIG,
|
||||
"outputs": SAMPLE_OUTPUTS_ON,
|
||||
"player": SAMPLE_PLAYER_PAUSED,
|
||||
"queue": SAMPLE_QUEUE_TTS,
|
||||
"queue": SAMPLE_QUEUE,
|
||||
}
|
||||
|
||||
|
||||
@ -323,13 +348,12 @@ async def mock_api_object_fixture(hass, config_entry, get_request_return_values)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def add_to_queue_side_effect(
|
||||
uris, playback=None, playback_from_position=None, clear=None
|
||||
uris, playback=None, position=None, playback_from_position=None, clear=None
|
||||
):
|
||||
await updater_update(["queue", "player"])
|
||||
|
||||
mock_api.return_value.add_to_queue.side_effect = (
|
||||
add_to_queue_side_effect # for play_media testing
|
||||
)
|
||||
# for play_media testing
|
||||
mock_api.return_value.add_to_queue.side_effect = add_to_queue_side_effect
|
||||
|
||||
async def pause_side_effect():
|
||||
await updater_update(["player"])
|
||||
@ -361,8 +385,8 @@ def test_master_state(hass, mock_api_object):
|
||||
assert state.attributes[ATTR_MEDIA_DURATION] == 0.05
|
||||
assert state.attributes[ATTR_MEDIA_POSITION] == 0.005
|
||||
assert state.attributes[ATTR_MEDIA_TITLE] == "No album" # reversed for url
|
||||
assert state.attributes[ATTR_MEDIA_ARTIST] == "Google"
|
||||
assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "Short TTS file" # reversed
|
||||
assert state.attributes[ATTR_MEDIA_ARTIST] == "Some artist"
|
||||
assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "Some song" # reversed
|
||||
assert state.attributes[ATTR_MEDIA_ALBUM_ARTIST] == "The xx"
|
||||
assert state.attributes[ATTR_MEDIA_TRACK] == 1
|
||||
assert not state.attributes[ATTR_MEDIA_SHUFFLE]
|
||||
@ -562,16 +586,18 @@ async def test_async_play_media_from_paused(hass, mock_api_object):
|
||||
assert state.last_updated > initial_state.last_updated
|
||||
|
||||
|
||||
async def test_async_play_media_from_stopped(
|
||||
async def test_async_play_media_announcement_from_stopped(
|
||||
hass, get_request_return_values, mock_api_object
|
||||
):
|
||||
"""Test async play media from stopped."""
|
||||
"""Test async play media announcement (from stopped)."""
|
||||
updater_update = mock_api_object.start_websocket_handler.call_args[0][2]
|
||||
|
||||
get_request_return_values["player"] = SAMPLE_PLAYER_STOPPED
|
||||
await updater_update(["player"])
|
||||
await hass.async_block_till_done()
|
||||
initial_state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
|
||||
get_request_return_values["queue"] = SAMPLE_QUEUE_TTS
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
@ -579,6 +605,7 @@ async def test_async_play_media_from_stopped(
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/somefile.mp3",
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
},
|
||||
)
|
||||
state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
@ -602,8 +629,8 @@ async def test_async_play_media_unsupported(hass, mock_api_object):
|
||||
assert state.last_updated == initial_state.last_updated
|
||||
|
||||
|
||||
async def test_async_play_media_tts_timeout(hass, mock_api_object):
|
||||
"""Test async play media with TTS timeout."""
|
||||
async def test_async_play_media_announcement_tts_timeout(hass, mock_api_object):
|
||||
"""Test async play media announcement with TTS timeout."""
|
||||
mock_api_object.add_to_queue.side_effect = None
|
||||
with patch("homeassistant.components.forked_daapd.media_player.TTS_TIMEOUT", 0):
|
||||
initial_state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
@ -614,6 +641,7 @@ async def test_async_play_media_tts_timeout(hass, mock_api_object):
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/somefile.mp3",
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
},
|
||||
)
|
||||
state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
@ -713,8 +741,8 @@ async def test_librespot_java_stuff(
|
||||
assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "some album"
|
||||
|
||||
|
||||
async def test_librespot_java_play_media(hass, pipe_control_api_object):
|
||||
"""Test play media with librespot-java pipe."""
|
||||
async def test_librespot_java_play_announcement(hass, pipe_control_api_object):
|
||||
"""Test play announcement with librespot-java pipe."""
|
||||
initial_state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
await _service_call(
|
||||
hass,
|
||||
@ -723,6 +751,7 @@ async def test_librespot_java_play_media(hass, pipe_control_api_object):
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/somefile.mp3",
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
},
|
||||
)
|
||||
state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
@ -783,3 +812,79 @@ async def test_websocket_disconnect(hass, mock_api_object):
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(TEST_MASTER_ENTITY_NAME).state == STATE_UNAVAILABLE
|
||||
assert hass.states.get(TEST_ZONE_ENTITY_NAMES[0]).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_async_play_media_enqueue(hass, mock_api_object):
|
||||
"""Test async play media with different enqueue options."""
|
||||
initial_state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/play.mp3",
|
||||
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.PLAY,
|
||||
},
|
||||
)
|
||||
state = hass.states.get(TEST_MASTER_ENTITY_NAME)
|
||||
assert state.state == initial_state.state
|
||||
assert state.last_updated > initial_state.last_updated
|
||||
mock_api_object.add_to_queue.assert_called_with(
|
||||
uris="http://example.com/play.mp3",
|
||||
playback="start",
|
||||
position=0,
|
||||
playback_from_position=0,
|
||||
)
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/replace.mp3",
|
||||
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.REPLACE,
|
||||
},
|
||||
)
|
||||
mock_api_object.add_to_queue.assert_called_with(
|
||||
uris="http://example.com/replace.mp3", playback="start", clear=True
|
||||
)
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/add.mp3",
|
||||
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.ADD,
|
||||
},
|
||||
)
|
||||
mock_api_object.add_to_queue.assert_called_with(
|
||||
uris="http://example.com/add.mp3", playback="start", clear=False
|
||||
)
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/add.mp3",
|
||||
ATTR_MEDIA_ENQUEUE: True,
|
||||
},
|
||||
)
|
||||
mock_api_object.add_to_queue.assert_called_with(
|
||||
uris="http://example.com/add.mp3", playback="start", clear=False
|
||||
)
|
||||
await _service_call(
|
||||
hass,
|
||||
TEST_MASTER_ENTITY_NAME,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: "http://example.com/next.mp3",
|
||||
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.NEXT,
|
||||
},
|
||||
)
|
||||
mock_api_object.add_to_queue.assert_called_with(
|
||||
uris="http://example.com/next.mp3", playback="start", position=1
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user