mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Add support for music library folder to Sonos (#139554)
* initial prototype * use constants * make playing work * remove unneeded code * remove unneeded code * fix regressions issues * refactor add_to_queue * refactor add_to_queue * refactor add_to_queue * simplify * add tests * remove bad test * rename constants * comments * comments * comments * use snapshot * refactor to use add_to_queue * refactor to use add_to_queue * add comments, redo snapshots * update comment * merge formatting * code review changes * fix: merge issue * fix: update snapshot to include new can_search field
This commit is contained in:
parent
3ff3cb975b
commit
73811eac0a
@ -31,9 +31,12 @@ SONOS_ALBUM_ARTIST = "album_artists"
|
|||||||
SONOS_TRACKS = "tracks"
|
SONOS_TRACKS = "tracks"
|
||||||
SONOS_COMPOSER = "composers"
|
SONOS_COMPOSER = "composers"
|
||||||
SONOS_RADIO = "radio"
|
SONOS_RADIO = "radio"
|
||||||
|
SONOS_SHARE = "share"
|
||||||
SONOS_OTHER_ITEM = "other items"
|
SONOS_OTHER_ITEM = "other items"
|
||||||
SONOS_AUDIO_BOOK = "audio book"
|
SONOS_AUDIO_BOOK = "audio book"
|
||||||
|
|
||||||
|
MEDIA_TYPE_DIRECTORY = MediaClass.DIRECTORY
|
||||||
|
|
||||||
SONOS_STATE_PLAYING = "PLAYING"
|
SONOS_STATE_PLAYING = "PLAYING"
|
||||||
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
||||||
|
|
||||||
@ -43,12 +46,14 @@ EXPANDABLE_MEDIA_TYPES = [
|
|||||||
MediaType.COMPOSER,
|
MediaType.COMPOSER,
|
||||||
MediaType.GENRE,
|
MediaType.GENRE,
|
||||||
MediaType.PLAYLIST,
|
MediaType.PLAYLIST,
|
||||||
|
MEDIA_TYPE_DIRECTORY,
|
||||||
SONOS_ALBUM,
|
SONOS_ALBUM,
|
||||||
SONOS_ALBUM_ARTIST,
|
SONOS_ALBUM_ARTIST,
|
||||||
SONOS_ARTIST,
|
SONOS_ARTIST,
|
||||||
SONOS_GENRE,
|
SONOS_GENRE,
|
||||||
SONOS_COMPOSER,
|
SONOS_COMPOSER,
|
||||||
SONOS_PLAYLISTS,
|
SONOS_PLAYLISTS,
|
||||||
|
SONOS_SHARE,
|
||||||
]
|
]
|
||||||
|
|
||||||
SONOS_TO_MEDIA_CLASSES = {
|
SONOS_TO_MEDIA_CLASSES = {
|
||||||
@ -59,6 +64,8 @@ SONOS_TO_MEDIA_CLASSES = {
|
|||||||
SONOS_GENRE: MediaClass.GENRE,
|
SONOS_GENRE: MediaClass.GENRE,
|
||||||
SONOS_PLAYLISTS: MediaClass.PLAYLIST,
|
SONOS_PLAYLISTS: MediaClass.PLAYLIST,
|
||||||
SONOS_TRACKS: MediaClass.TRACK,
|
SONOS_TRACKS: MediaClass.TRACK,
|
||||||
|
SONOS_SHARE: MediaClass.DIRECTORY,
|
||||||
|
"object.container": MediaClass.DIRECTORY,
|
||||||
"object.container.album.musicAlbum": MediaClass.ALBUM,
|
"object.container.album.musicAlbum": MediaClass.ALBUM,
|
||||||
"object.container.genre.musicGenre": MediaClass.PLAYLIST,
|
"object.container.genre.musicGenre": MediaClass.PLAYLIST,
|
||||||
"object.container.person.composer": MediaClass.PLAYLIST,
|
"object.container.person.composer": MediaClass.PLAYLIST,
|
||||||
@ -79,6 +86,7 @@ SONOS_TO_MEDIA_TYPES = {
|
|||||||
SONOS_GENRE: MediaType.GENRE,
|
SONOS_GENRE: MediaType.GENRE,
|
||||||
SONOS_PLAYLISTS: MediaType.PLAYLIST,
|
SONOS_PLAYLISTS: MediaType.PLAYLIST,
|
||||||
SONOS_TRACKS: MediaType.TRACK,
|
SONOS_TRACKS: MediaType.TRACK,
|
||||||
|
"object.container": MEDIA_TYPE_DIRECTORY,
|
||||||
"object.container.album.musicAlbum": MediaType.ALBUM,
|
"object.container.album.musicAlbum": MediaType.ALBUM,
|
||||||
"object.container.genre.musicGenre": MediaType.PLAYLIST,
|
"object.container.genre.musicGenre": MediaType.PLAYLIST,
|
||||||
"object.container.person.composer": MediaType.PLAYLIST,
|
"object.container.person.composer": MediaType.PLAYLIST,
|
||||||
@ -97,6 +105,7 @@ MEDIA_TYPES_TO_SONOS: dict[MediaType | str, str] = {
|
|||||||
MediaType.GENRE: SONOS_GENRE,
|
MediaType.GENRE: SONOS_GENRE,
|
||||||
MediaType.PLAYLIST: SONOS_PLAYLISTS,
|
MediaType.PLAYLIST: SONOS_PLAYLISTS,
|
||||||
MediaType.TRACK: SONOS_TRACKS,
|
MediaType.TRACK: SONOS_TRACKS,
|
||||||
|
MEDIA_TYPE_DIRECTORY: SONOS_SHARE,
|
||||||
}
|
}
|
||||||
|
|
||||||
SONOS_TYPES_MAPPING = {
|
SONOS_TYPES_MAPPING = {
|
||||||
@ -127,6 +136,7 @@ LIBRARY_TITLES_MAPPING = {
|
|||||||
"A:GENRE": "Genres",
|
"A:GENRE": "Genres",
|
||||||
"A:PLAYLISTS": "Playlists",
|
"A:PLAYLISTS": "Playlists",
|
||||||
"A:TRACKS": "Tracks",
|
"A:TRACKS": "Tracks",
|
||||||
|
"S:": "Folders",
|
||||||
}
|
}
|
||||||
|
|
||||||
PLAYABLE_MEDIA_TYPES = [
|
PLAYABLE_MEDIA_TYPES = [
|
||||||
|
@ -9,7 +9,7 @@ import logging
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from soco.data_structures import DidlObject
|
from soco.data_structures import DidlContainer, DidlObject
|
||||||
from soco.ms_data_structures import MusicServiceItem
|
from soco.ms_data_structures import MusicServiceItem
|
||||||
from soco.music_library import MusicLibrary
|
from soco.music_library import MusicLibrary
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ from .const import (
|
|||||||
SONOS_ALBUM,
|
SONOS_ALBUM,
|
||||||
SONOS_ALBUM_ARTIST,
|
SONOS_ALBUM_ARTIST,
|
||||||
SONOS_GENRE,
|
SONOS_GENRE,
|
||||||
|
SONOS_SHARE,
|
||||||
SONOS_TO_MEDIA_CLASSES,
|
SONOS_TO_MEDIA_CLASSES,
|
||||||
SONOS_TO_MEDIA_TYPES,
|
SONOS_TO_MEDIA_TYPES,
|
||||||
SONOS_TRACKS,
|
SONOS_TRACKS,
|
||||||
@ -105,6 +106,24 @@ def media_source_filter(item: BrowseMedia) -> bool:
|
|||||||
return item.media_content_type.startswith("audio/")
|
return item.media_content_type.startswith("audio/")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_title(id_string: str) -> str:
|
||||||
|
"""Extract a suitable title from the content id string."""
|
||||||
|
if id_string.startswith("S:"):
|
||||||
|
# Format is S://server/share/folder
|
||||||
|
# If just S: this will be in the mappings; otherwise use the last folder in path.
|
||||||
|
title = LIBRARY_TITLES_MAPPING.get(
|
||||||
|
id_string, urllib.parse.unquote(id_string.split("/")[-1])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
parts = id_string.split("/")
|
||||||
|
title = (
|
||||||
|
urllib.parse.unquote(parts[1])
|
||||||
|
if len(parts) > 1
|
||||||
|
else LIBRARY_TITLES_MAPPING.get(id_string, id_string)
|
||||||
|
)
|
||||||
|
return title
|
||||||
|
|
||||||
|
|
||||||
async def async_browse_media(
|
async def async_browse_media(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
speaker: SonosSpeaker,
|
speaker: SonosSpeaker,
|
||||||
@ -240,10 +259,7 @@ def build_item_response(
|
|||||||
thumbnail = get_thumbnail_url(search_type, payload["idstring"])
|
thumbnail = get_thumbnail_url(search_type, payload["idstring"])
|
||||||
|
|
||||||
if not title:
|
if not title:
|
||||||
try:
|
title = _get_title(id_string=payload["idstring"])
|
||||||
title = urllib.parse.unquote(payload["idstring"].split("/")[1])
|
|
||||||
except IndexError:
|
|
||||||
title = LIBRARY_TITLES_MAPPING[payload["idstring"]]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
media_class = SONOS_TO_MEDIA_CLASSES[
|
media_class = SONOS_TO_MEDIA_CLASSES[
|
||||||
@ -288,12 +304,12 @@ def item_payload(item: DidlObject, get_thumbnail_url=None) -> BrowseMedia:
|
|||||||
thumbnail = get_thumbnail_url(media_class, content_id, item=item)
|
thumbnail = get_thumbnail_url(media_class, content_id, item=item)
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=item.title,
|
title=_get_title(item.item_id) if item.title is None else item.title,
|
||||||
thumbnail=thumbnail,
|
thumbnail=thumbnail,
|
||||||
media_class=media_class,
|
media_class=media_class,
|
||||||
media_content_id=content_id,
|
media_content_id=content_id,
|
||||||
media_content_type=SONOS_TO_MEDIA_TYPES[media_type],
|
media_content_type=SONOS_TO_MEDIA_TYPES[media_type],
|
||||||
can_play=can_play(item.item_class),
|
can_play=can_play(item.item_class, item_id=content_id),
|
||||||
can_expand=can_expand(item),
|
can_expand=can_expand(item),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -396,6 +412,10 @@ def library_payload(media_library: MusicLibrary, get_thumbnail_url=None) -> Brow
|
|||||||
with suppress(UnknownMediaType):
|
with suppress(UnknownMediaType):
|
||||||
children.append(item_payload(item, get_thumbnail_url))
|
children.append(item_payload(item, get_thumbnail_url))
|
||||||
|
|
||||||
|
# Add entry for Folders at the top level of the music library.
|
||||||
|
didl_item = DidlContainer(title="Folders", parent_id="", item_id="S:")
|
||||||
|
children.append(item_payload(didl_item, get_thumbnail_url))
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title="Music Library",
|
title="Music Library",
|
||||||
media_class=MediaClass.DIRECTORY,
|
media_class=MediaClass.DIRECTORY,
|
||||||
@ -508,12 +528,16 @@ def get_media_type(item: DidlObject) -> str:
|
|||||||
return SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0], item.item_class)
|
return SONOS_TYPES_MAPPING.get(item.item_id.split("/")[0], item.item_class)
|
||||||
|
|
||||||
|
|
||||||
def can_play(item: DidlObject) -> bool:
|
def can_play(item_class: str, item_id: str | None = None) -> bool:
|
||||||
"""Test if playable.
|
"""Test if playable.
|
||||||
|
|
||||||
Used by async_browse_media.
|
Used by async_browse_media.
|
||||||
"""
|
"""
|
||||||
return SONOS_TO_MEDIA_TYPES.get(item) in PLAYABLE_MEDIA_TYPES
|
# Folders are playable once we reach the folder level.
|
||||||
|
# Format is S://server_address/share/folder
|
||||||
|
if item_id and item_id.startswith("S:") and item_class == "object.container":
|
||||||
|
return item_id.count("/") >= 4
|
||||||
|
return SONOS_TO_MEDIA_TYPES.get(item_class) in PLAYABLE_MEDIA_TYPES
|
||||||
|
|
||||||
|
|
||||||
def can_expand(item: DidlObject) -> bool:
|
def can_expand(item: DidlObject) -> bool:
|
||||||
@ -565,6 +589,19 @@ def get_media(
|
|||||||
matches = media_library.get_music_library_information(
|
matches = media_library.get_music_library_information(
|
||||||
search_type, search_term=search_term, full_album_art_uri=True
|
search_type, search_term=search_term, full_album_art_uri=True
|
||||||
)
|
)
|
||||||
|
elif search_type == SONOS_SHARE:
|
||||||
|
# In order to get the MusicServiceItem, we browse the parent folder
|
||||||
|
# and find one that matches on item_id.
|
||||||
|
parts = item_id.rstrip("/").split("/")
|
||||||
|
parent_folder = "/".join(parts[:-1])
|
||||||
|
matches = media_library.browse_by_idstring(
|
||||||
|
search_type, parent_folder, full_album_art_uri=True
|
||||||
|
)
|
||||||
|
result = next(
|
||||||
|
(item for item in matches if (item_id == item.item_id)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
matches = [result]
|
||||||
else:
|
else:
|
||||||
# When requesting media by album_artist, composer, genre use the browse interface
|
# When requesting media by album_artist, composer, genre use the browse interface
|
||||||
# to navigate the hierarchy. This occurs when invoked from media browser or service
|
# to navigate the hierarchy. This occurs when invoked from media browser or service
|
||||||
|
@ -53,6 +53,7 @@ from . import UnjoinData, media_browser
|
|||||||
from .const import (
|
from .const import (
|
||||||
DATA_SONOS,
|
DATA_SONOS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MEDIA_TYPE_DIRECTORY,
|
||||||
MEDIA_TYPES_TO_SONOS,
|
MEDIA_TYPES_TO_SONOS,
|
||||||
MODELS_LINEIN_AND_TV,
|
MODELS_LINEIN_AND_TV,
|
||||||
MODELS_LINEIN_ONLY,
|
MODELS_LINEIN_ONLY,
|
||||||
@ -656,6 +657,10 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
media_id, timeout=LONG_SERVICE_TIMEOUT
|
media_id, timeout=LONG_SERVICE_TIMEOUT
|
||||||
)
|
)
|
||||||
soco.play_from_queue(0)
|
soco.play_from_queue(0)
|
||||||
|
elif media_type == MEDIA_TYPE_DIRECTORY:
|
||||||
|
self._play_media_directory(
|
||||||
|
soco=soco, media_type=media_type, media_id=media_id, enqueue=enqueue
|
||||||
|
)
|
||||||
elif media_type in {MediaType.MUSIC, MediaType.TRACK}:
|
elif media_type in {MediaType.MUSIC, MediaType.TRACK}:
|
||||||
# If media ID is a relative URL, we serve it from HA.
|
# If media ID is a relative URL, we serve it from HA.
|
||||||
media_id = async_process_play_media_url(self.hass, media_id)
|
media_id = async_process_play_media_url(self.hass, media_id)
|
||||||
@ -738,6 +743,25 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
if enqueue == MediaPlayerEnqueue.PLAY:
|
if enqueue == MediaPlayerEnqueue.PLAY:
|
||||||
soco.play_from_queue(new_pos - 1)
|
soco.play_from_queue(new_pos - 1)
|
||||||
|
|
||||||
|
def _play_media_directory(
|
||||||
|
self,
|
||||||
|
soco: SoCo,
|
||||||
|
media_type: MediaType | str,
|
||||||
|
media_id: str,
|
||||||
|
enqueue: MediaPlayerEnqueue,
|
||||||
|
):
|
||||||
|
"""Play a directory from a music library share."""
|
||||||
|
item = media_browser.get_media(self.media.library, media_id, media_type)
|
||||||
|
if not item:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="invalid_media",
|
||||||
|
translation_placeholders={
|
||||||
|
"media_id": media_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._play_media_queue(soco, item, enqueue)
|
||||||
|
|
||||||
@soco_error()
|
@soco_error()
|
||||||
def set_sleep_timer(self, sleep_time: int) -> None:
|
def set_sleep_timer(self, sleep_time: int) -> None:
|
||||||
"""Set the timer on the player."""
|
"""Set the timer on the player."""
|
||||||
|
@ -21,6 +21,7 @@ from soco.events_base import Event as SonosEvent
|
|||||||
from homeassistant.components import ssdp
|
from homeassistant.components import ssdp
|
||||||
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||||
from homeassistant.components.sonos import DOMAIN
|
from homeassistant.components.sonos import DOMAIN
|
||||||
|
from homeassistant.components.sonos.const import SONOS_SHARE
|
||||||
from homeassistant.const import CONF_HOSTS
|
from homeassistant.const import CONF_HOSTS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_UDN, SsdpServiceInfo
|
from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_UDN, SsdpServiceInfo
|
||||||
@ -501,6 +502,45 @@ def mock_browse_by_idstring(
|
|||||||
return list_from_json_fixture("music_library_tracks.json")
|
return list_from_json_fixture("music_library_tracks.json")
|
||||||
if search_type == "albums" and idstring == "A:ALBUM":
|
if search_type == "albums" and idstring == "A:ALBUM":
|
||||||
return list_from_json_fixture("music_library_albums.json")
|
return list_from_json_fixture("music_library_albums.json")
|
||||||
|
if search_type == SONOS_SHARE and idstring == "S:":
|
||||||
|
return [
|
||||||
|
MockMusicServiceItem(
|
||||||
|
None,
|
||||||
|
"S://192.168.1.1/music",
|
||||||
|
"S:",
|
||||||
|
"object.container",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if search_type == SONOS_SHARE and idstring == "S://192.168.1.1/music":
|
||||||
|
return [
|
||||||
|
MockMusicServiceItem(
|
||||||
|
None,
|
||||||
|
"S://192.168.1.1/music/beatles",
|
||||||
|
"S://192.168.1.1/music",
|
||||||
|
"object.container",
|
||||||
|
),
|
||||||
|
MockMusicServiceItem(
|
||||||
|
None,
|
||||||
|
"S://192.168.1.1/music/elton%20john",
|
||||||
|
"S://192.168.1.1/music",
|
||||||
|
"object.container",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
if search_type == SONOS_SHARE and idstring == "S://192.168.1.1/music/elton%20john":
|
||||||
|
return [
|
||||||
|
MockMusicServiceItem(
|
||||||
|
None,
|
||||||
|
"S://192.168.1.1/music/elton%20john/Greatest%20Hits",
|
||||||
|
"S://192.168.1.1/music/elton%20john",
|
||||||
|
"object.container",
|
||||||
|
),
|
||||||
|
MockMusicServiceItem(
|
||||||
|
None,
|
||||||
|
"S://192.168.1.1/music/elton%20john/Good%20Bye%20Yellow%20Brick%20Road",
|
||||||
|
"S://192.168.1.1/music/elton%20john",
|
||||||
|
"object.container",
|
||||||
|
),
|
||||||
|
]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,6 +192,17 @@
|
|||||||
'thumbnail': None,
|
'thumbnail': None,
|
||||||
'title': 'Playlists',
|
'title': 'Playlists',
|
||||||
}),
|
}),
|
||||||
|
dict({
|
||||||
|
'can_expand': True,
|
||||||
|
'can_play': False,
|
||||||
|
'can_search': False,
|
||||||
|
'children_media_class': None,
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S:',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'Folders',
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
# ---
|
# ---
|
||||||
# name: test_browse_media_library_albums
|
# name: test_browse_media_library_albums
|
||||||
@ -242,6 +253,71 @@
|
|||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_browse_media_library_folders[S://192.168.1.1/music]
|
||||||
|
dict({
|
||||||
|
'can_expand': False,
|
||||||
|
'can_play': False,
|
||||||
|
'can_search': False,
|
||||||
|
'children': list([
|
||||||
|
dict({
|
||||||
|
'can_expand': True,
|
||||||
|
'can_play': True,
|
||||||
|
'can_search': False,
|
||||||
|
'children_media_class': None,
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S://192.168.1.1/music/beatles',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'beatles',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'can_expand': True,
|
||||||
|
'can_play': True,
|
||||||
|
'can_search': False,
|
||||||
|
'children_media_class': None,
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S://192.168.1.1/music/elton%20john',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'elton john',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'children_media_class': 'directory',
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S://192.168.1.1/music',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'not_shown': 0,
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'music',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_browse_media_library_folders[S:]
|
||||||
|
dict({
|
||||||
|
'can_expand': False,
|
||||||
|
'can_play': False,
|
||||||
|
'can_search': False,
|
||||||
|
'children': list([
|
||||||
|
dict({
|
||||||
|
'can_expand': True,
|
||||||
|
'can_play': False,
|
||||||
|
'can_search': False,
|
||||||
|
'children_media_class': None,
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S://192.168.1.1/music',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'music',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'children_media_class': 'directory',
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': 'S:',
|
||||||
|
'media_content_type': 'directory',
|
||||||
|
'not_shown': 0,
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'Folders',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_browse_media_root
|
# name: test_browse_media_root
|
||||||
list([
|
list([
|
||||||
dict({
|
dict({
|
||||||
|
@ -5,11 +5,19 @@ from functools import partial
|
|||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
|
from homeassistant.components.media_player import (
|
||||||
|
ATTR_MEDIA_CONTENT_ID,
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE,
|
||||||
|
BrowseMedia,
|
||||||
|
MediaClass,
|
||||||
|
MediaType,
|
||||||
|
)
|
||||||
|
from homeassistant.components.sonos.const import MEDIA_TYPE_DIRECTORY
|
||||||
from homeassistant.components.sonos.media_browser import (
|
from homeassistant.components.sonos.media_browser import (
|
||||||
build_item_response,
|
build_item_response,
|
||||||
get_thumbnail_url_full,
|
get_thumbnail_url_full,
|
||||||
)
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .conftest import SoCoMockFactory
|
from .conftest import SoCoMockFactory
|
||||||
@ -217,3 +225,37 @@ async def test_browse_media_favorites(
|
|||||||
response = await client.receive_json()
|
response = await client.receive_json()
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"] == snapshot
|
assert response["result"] == snapshot
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"media_content_id",
|
||||||
|
[
|
||||||
|
("S:"),
|
||||||
|
("S://192.168.1.1/music"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_browse_media_library_folders(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
soco_factory: SoCoMockFactory,
|
||||||
|
async_autosetup_sonos,
|
||||||
|
media_content_id: str,
|
||||||
|
hass_ws_client: WebSocketGenerator,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test the async_browse_media method."""
|
||||||
|
soco_mock = soco_factory.mock_list.get("192.168.42.2")
|
||||||
|
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "media_player/browse_media",
|
||||||
|
ATTR_ENTITY_ID: "media_player.zone_a",
|
||||||
|
ATTR_MEDIA_CONTENT_ID: media_content_id,
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_DIRECTORY,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == snapshot
|
||||||
|
assert soco_mock.music_library.browse_by_idstring.call_count == 1
|
||||||
|
@ -28,6 +28,7 @@ from homeassistant.components.media_player import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.sonos.const import (
|
from homeassistant.components.sonos.const import (
|
||||||
DOMAIN as SONOS_DOMAIN,
|
DOMAIN as SONOS_DOMAIN,
|
||||||
|
MEDIA_TYPE_DIRECTORY,
|
||||||
SOURCE_LINEIN,
|
SOURCE_LINEIN,
|
||||||
SOURCE_TV,
|
SOURCE_TV,
|
||||||
)
|
)
|
||||||
@ -182,6 +183,19 @@ async def test_entity_basic(
|
|||||||
"play_pos": 0,
|
"play_pos": 0,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
MEDIA_TYPE_DIRECTORY,
|
||||||
|
"S://192.168.1.1/music/elton%20john",
|
||||||
|
MediaPlayerEnqueue.REPLACE,
|
||||||
|
{
|
||||||
|
"title": None,
|
||||||
|
"item_id": "S://192.168.1.1/music/elton%20john",
|
||||||
|
"clear_queue": 1,
|
||||||
|
"position": None,
|
||||||
|
"play": 1,
|
||||||
|
"play_pos": 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_play_media_library(
|
async def test_play_media_library(
|
||||||
@ -247,6 +261,11 @@ async def test_play_media_library(
|
|||||||
"A:ALBUM/UnknowAlbum",
|
"A:ALBUM/UnknowAlbum",
|
||||||
"Sonos does not support media content type: UnknownContent",
|
"Sonos does not support media content type: UnknownContent",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
MEDIA_TYPE_DIRECTORY,
|
||||||
|
"S://192.168.1.1/music/error",
|
||||||
|
"Could not find media in library: S://192.168.1.1/music/error",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_play_media_library_content_error(
|
async def test_play_media_library_content_error(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user