Browse media class (#39698)

pull/39716/head
Paulus Schoutsen 2020-09-06 15:52:59 +02:00 committed by GitHub
parent 13a6aaa6ff
commit df8daf561e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 342 additions and 235 deletions

View File

@ -5,7 +5,7 @@ from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceC
from arcam.fmj.state import State
from homeassistant import config_entries
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC,
SUPPORT_BROWSE_MEDIA,
@ -253,22 +253,24 @@ class ArcamFmj(MediaPlayerEntity):
presets = self._state.get_preset_details()
radio = [
{
"title": preset.name,
"media_content_id": f"preset:{preset.index}",
"media_content_type": MEDIA_TYPE_MUSIC,
"can_play": True,
}
BrowseMedia(
title=preset.name,
media_content_id=f"preset:{preset.index}",
media_content_type=MEDIA_TYPE_MUSIC,
can_play=True,
can_expand=False,
)
for preset in presets.values()
]
root = {
"title": "Root",
"media_content_id": "root",
"media_content_type": "library",
"can_play": False,
"children": radio,
}
root = BrowseMedia(
title="Root",
media_content_id="root",
media_content_type="library",
can_play=False,
can_expand=True,
children=radio,
)
return root

View File

@ -7,7 +7,7 @@ import functools as ft
import hashlib
import logging
from random import SystemRandom
from typing import Optional
from typing import List, Optional
from urllib.parse import urlparse
from aiohttp import web
@ -811,7 +811,11 @@ class MediaPlayerEntity(Entity):
return state_attr
async def async_browse_media(self, media_content_type=None, media_content_id=None):
async def async_browse_media(
self,
media_content_type: Optional[str] = None,
media_content_id: Optional[str] = None,
) -> "BrowseMedia":
"""
Return a payload for the "media_player/browse_media" websocket command.
@ -976,7 +980,7 @@ async def websocket_browse_media(hass, connection, msg):
To use, media_player integrations can implement MediaPlayerEntity.async_browse_media()
"""
component = hass.data[DOMAIN]
player = component.get_entity(msg["entity_id"])
player: Optional[MediaPlayerDevice] = component.get_entity(msg["entity_id"])
if player is None:
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
@ -1015,6 +1019,12 @@ async def websocket_browse_media(hass, connection, msg):
)
return
# For backwards compat
if isinstance(payload, BrowseMedia):
payload = payload.as_dict()
else:
_LOGGER.warning("Browse Media should use new BrowseMedia class")
connection.send_result(msg["id"], payload)
@ -1028,3 +1038,50 @@ class MediaPlayerDevice(MediaPlayerEntity):
"MediaPlayerDevice is deprecated, modify %s to extend MediaPlayerEntity",
cls.__name__,
)
class BrowseMedia:
"""Represent a browsable media file."""
def __init__(
self,
*,
media_content_id: str,
media_content_type: str,
title: str,
can_play: bool,
can_expand: bool,
children: Optional[List["BrowseMedia"]] = None,
thumbnail: Optional[str] = None,
):
"""Initialize browse media item."""
self.media_content_id = media_content_id
self.media_content_type = media_content_type
self.title = title
self.can_play = can_play
self.can_expand = can_expand
self.children = children
self.thumbnail = thumbnail
def as_dict(self, *, parent: bool = True) -> dict:
"""Convert Media class to browse media dictionary."""
response = {
"title": self.title,
"media_content_type": self.media_content_type,
"media_content_id": self.media_content_id,
"can_play": self.can_play,
"can_expand": self.can_expand,
"thumbnail": self.thumbnail,
}
if not parent:
return response
if self.children:
response["children"] = [
child.as_dict(parent=False) for child in self.children
]
else:
response["children"] = []
return response

View File

@ -29,27 +29,27 @@ ATTR_SOUND_MODE_LIST = "sound_mode_list"
DOMAIN = "media_player"
MEDIA_TYPE_MUSIC = "music"
MEDIA_TYPE_TVSHOW = "tvshow"
MEDIA_TYPE_MOVIE = "movie"
MEDIA_TYPE_VIDEO = "video"
MEDIA_TYPE_EPISODE = "episode"
MEDIA_TYPE_CHANNEL = "channel"
MEDIA_TYPE_CHANNELS = "channels"
MEDIA_TYPE_PLAYLIST = "playlist"
MEDIA_TYPE_IMAGE = "image"
MEDIA_TYPE_URL = "url"
MEDIA_TYPE_GAME = "game"
MEDIA_TYPE_ALBUM = "album"
MEDIA_TYPE_APP = "app"
MEDIA_TYPE_APPS = "apps"
MEDIA_TYPE_ALBUM = "album"
MEDIA_TYPE_TRACK = "track"
MEDIA_TYPE_ARTIST = "artist"
MEDIA_TYPE_CHANNEL = "channel"
MEDIA_TYPE_CHANNELS = "channels"
MEDIA_TYPE_COMPOSER = "composer"
MEDIA_TYPE_CONTRIBUTING_ARTIST = "contributing_artist"
MEDIA_TYPE_EPISODE = "episode"
MEDIA_TYPE_GAME = "game"
MEDIA_TYPE_GENRE = "genre"
MEDIA_TYPE_IMAGE = "image"
MEDIA_TYPE_MOVIE = "movie"
MEDIA_TYPE_MUSIC = "music"
MEDIA_TYPE_PLAYLIST = "playlist"
MEDIA_TYPE_PODCAST = "podcast"
MEDIA_TYPE_SEASON = "season"
MEDIA_TYPE_GENRE = "genre"
MEDIA_TYPE_COMPOSER = "composer"
MEDIA_TYPE_TRACK = "track"
MEDIA_TYPE_TVSHOW = "tvshow"
MEDIA_TYPE_URL = "url"
MEDIA_TYPE_VIDEO = "video"
SERVICE_CLEAR_PLAYLIST = "clear_playlist"
SERVICE_PLAY_MEDIA = "play_media"

View File

@ -68,7 +68,7 @@ def _get_media_item(
@bind_hass
async def async_browse_media(
hass: HomeAssistant, media_content_id: str
) -> models.BrowseMedia:
) -> models.BrowseMediaSource:
"""Return media player browse media results."""
return await _get_media_item(hass, media_content_id).async_browse()
@ -94,7 +94,7 @@ async def websocket_browse_media(hass, connection, msg):
media = await async_browse_media(hass, msg.get("media_content_id"))
connection.send_result(
msg["id"],
media.to_media_player_item(),
media.as_dict(),
)
except BrowseError as err:
connection.send_error(msg["id"], "browse_media_failed", str(err))

View File

@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util import sanitize_path
from .const import DOMAIN, MEDIA_MIME_TYPES
from .models import BrowseMedia, MediaSource, MediaSourceItem, PlayMedia
from .models import BrowseMediaSource, MediaSource, MediaSourceItem, PlayMedia
@callback
@ -67,7 +67,7 @@ class LocalSource(MediaSource):
async def async_browse_media(
self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES
) -> BrowseMedia:
) -> BrowseMediaSource:
"""Return media."""
try:
source_dir_id, location = async_parse_identifier(item)
@ -92,37 +92,41 @@ class LocalSource(MediaSource):
def _build_item_response(self, source_dir_id: str, path: Path, is_child=False):
mime_type, _ = mimetypes.guess_type(str(path))
media = BrowseMedia(
DOMAIN,
f"{source_dir_id}/{path.relative_to(self.hass.config.path('media'))}",
path.name,
path.is_file(),
path.is_dir(),
mime_type,
)
is_file = path.is_file()
is_dir = path.is_dir()
# Make sure it's a file or directory
if not media.can_play and not media.can_expand:
if not is_file and not is_dir:
return None
# Check that it's a media file
if media.can_play and (
if is_file and (
not mime_type or mime_type.split("/")[0] not in MEDIA_MIME_TYPES
):
return None
if not media.can_expand:
title = path.name
if is_dir:
title += "/"
media = BrowseMediaSource(
domain=DOMAIN,
identifier=f"{source_dir_id}/{path.relative_to(self.hass.config.path('media'))}",
media_content_type="directory",
title=title,
can_play=is_file,
can_expand=is_dir,
)
if is_file or is_child:
return media
media.name += "/"
# Append first level children
if not is_child:
media.children = []
for child_path in path.iterdir():
child = self._build_item_response(source_dir_id, child_path, True)
if child:
media.children.append(child)
media.children = []
for child_path in path.iterdir():
child = self._build_item_response(source_dir_id, child_path, True)
if child:
media.children.append(child)
return media

View File

@ -3,6 +3,11 @@ from abc import ABC
from dataclasses import dataclass
from typing import List, Optional, Tuple
from homeassistant.components.media_player import BrowseMedia
from homeassistant.components.media_player.const import (
MEDIA_TYPE_CHANNEL,
MEDIA_TYPE_CHANNELS,
)
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN, URI_SCHEME, URI_SCHEME_REGEX
@ -16,49 +21,21 @@ class PlayMedia:
mime_type: str
@dataclass
class BrowseMedia:
class BrowseMediaSource(BrowseMedia):
"""Represent a browsable media file."""
domain: str
identifier: str
children: Optional[List["BrowseMediaSource"]]
name: str
can_play: bool = False
can_expand: bool = False
media_content_type: str = None
children: List = None
thumbnail: str = None
def __init__(self, *, domain: Optional[str], identifier: Optional[str], **kwargs):
"""Initialize media source browse media."""
media_content_id = f"{URI_SCHEME}{domain or ''}"
if identifier:
media_content_id += f"/{identifier}"
def to_uri(self):
"""Return URI of media."""
uri = f"{URI_SCHEME}{self.domain or ''}"
if self.identifier:
uri += f"/{self.identifier}"
return uri
super().__init__(media_content_id=media_content_id, **kwargs)
def to_media_player_item(self):
"""Convert Media class to browse media dictionary."""
content_type = self.media_content_type
if content_type is None:
content_type = "folder" if self.can_expand else "file"
response = {
"title": self.name,
"media_content_type": content_type,
"media_content_id": self.to_uri(),
"can_play": self.can_play,
"can_expand": self.can_expand,
"thumbnail": self.thumbnail,
}
if self.children:
response["children"] = [
child.to_media_player_item() for child in self.children
]
return response
self.domain = domain
self.identifier = identifier
@dataclass
@ -69,12 +46,26 @@ class MediaSourceItem:
domain: Optional[str]
identifier: str
async def async_browse(self) -> BrowseMedia:
async def async_browse(self) -> BrowseMediaSource:
"""Browse this item."""
if self.domain is None:
base = BrowseMedia(None, None, "Media Sources", False, True)
base = BrowseMediaSource(
domain=None,
identifier=None,
media_content_type=MEDIA_TYPE_CHANNELS,
title="Media Sources",
can_play=False,
can_expand=True,
)
base.children = [
BrowseMedia(source.domain, None, source.name, False, True)
BrowseMediaSource(
domain=source.domain,
identifier=None,
media_content_type=MEDIA_TYPE_CHANNEL,
title=source.name,
can_play=False,
can_expand=True,
)
for source in self.hass.data[DOMAIN].values()
]
return base
@ -121,6 +112,6 @@ class MediaSource(ABC):
async def async_browse_media(
self, item: MediaSourceItem, media_types: Tuple[str]
) -> BrowseMedia:
) -> BrowseMediaSource:
"""Browse media."""
raise NotImplementedError

View File

@ -3,11 +3,12 @@ import datetime as dt
import re
from typing import Optional, Tuple
from homeassistant.components.media_player.const import MEDIA_TYPE_VIDEO
from homeassistant.components.media_player.errors import BrowseError
from homeassistant.components.media_source.const import MEDIA_MIME_TYPES
from homeassistant.components.media_source.error import Unresolvable
from homeassistant.components.media_source.models import (
BrowseMedia,
BrowseMediaSource,
MediaSource,
MediaSourceItem,
PlayMedia,
@ -43,7 +44,7 @@ class NetatmoSource(MediaSource):
async def async_browse_media(
self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES
) -> Optional[BrowseMedia]:
) -> Optional[BrowseMediaSource]:
"""Return media."""
try:
source, camera_id, event_id = async_parse_identifier(item)
@ -54,7 +55,7 @@ class NetatmoSource(MediaSource):
def _browse_media(
self, source: str, camera_id: str, event_id: int
) -> Optional[BrowseMedia]:
) -> Optional[BrowseMediaSource]:
"""Browse media."""
if camera_id and camera_id not in self.events:
raise BrowseError("Camera does not exist.")
@ -66,7 +67,7 @@ class NetatmoSource(MediaSource):
def _build_item_response(
self, source: str, camera_id: str, event_id: int = None
) -> Optional[BrowseMedia]:
) -> Optional[BrowseMediaSource]:
if event_id and event_id in self.events[camera_id]:
created = dt.datetime.fromtimestamp(event_id)
thumbnail = self.events[camera_id][event_id].get("snapshot", {}).get("url")
@ -81,18 +82,18 @@ class NetatmoSource(MediaSource):
else:
path = f"{source}/{camera_id}"
media = BrowseMedia(
DOMAIN,
path,
title,
media = BrowseMediaSource(
domain=DOMAIN,
identifier=path,
media_content_type=MEDIA_TYPE_VIDEO,
title=title,
can_play=bool(
event_id and self.events[camera_id][event_id].get("media_url")
),
can_expand=event_id is None,
thumbnail=thumbnail,
)
media.can_play = bool(
event_id and self.events[camera_id][event_id].get("media_url")
)
media.can_expand = event_id is None
media.thumbnail = thumbnail
if not media.can_play and not media.can_expand:
return None

View File

@ -5,9 +5,14 @@ import logging
from haphilipsjs import PhilipsTV
import voluptuous as vol
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity
from homeassistant.components.media_player import (
PLATFORM_SCHEMA,
BrowseMedia,
MediaPlayerEntity,
)
from homeassistant.components.media_player.const import (
MEDIA_TYPE_CHANNEL,
MEDIA_TYPE_CHANNELS,
SUPPORT_BROWSE_MEDIA,
SUPPORT_NEXT_TRACK,
SUPPORT_PLAY_MEDIA,
@ -281,21 +286,23 @@ class PhilipsTVMediaPlayer(MediaPlayerEntity):
f"Media not found: {media_content_type} / {media_content_id}"
)
return {
"title": "Channels",
"media_content_id": "",
"media_content_type": "library",
"can_play": False,
"children": [
{
"title": channel,
"media_content_id": channel,
"media_content_type": MEDIA_TYPE_CHANNEL,
"can_play": True,
}
return BrowseMedia(
title="Channels",
media_content_id="",
media_content_type=MEDIA_TYPE_CHANNELS,
can_play=False,
can_expand=True,
children=[
BrowseMedia(
title=channel,
media_content_id=channel,
media_content_type=MEDIA_TYPE_CHANNEL,
can_play=True,
can_expand=False,
)
for channel in self._channels.values()
],
}
)
def update(self):
"""Get the latest data and update device state."""

View File

@ -1,6 +1,7 @@
"""Support to interface with the Plex API."""
import logging
from homeassistant.components.media_player import BrowseMedia
from homeassistant.components.media_player.errors import BrowseError
from .const import DOMAIN
@ -34,10 +35,10 @@ def browse_media(
return None
media_info = item_payload(media)
if media_info.get("can_expand"):
media_info["children"] = []
if media_info.can_expand:
media_info.children = []
for item in media:
media_info["children"].append(item_payload(item))
media_info.children.append(item_payload(item))
return media_info
if media_content_id and ":" in media_content_id:
@ -103,12 +104,12 @@ def item_payload(item):
"media_content_id": str(item.ratingKey),
"media_content_type": item.type,
"can_play": True,
"can_expand": item.type in EXPANDABLES,
}
if hasattr(item, "thumbUrl"):
payload["thumbnail"] = item.thumbUrl
if item.type in EXPANDABLES:
payload["can_expand"] = True
return payload
return BrowseMedia(**payload)
def library_section_payload(section):

View File

@ -7,6 +7,7 @@ import voluptuous as vol
from homeassistant.components.media_player import (
DEVICE_CLASS_RECEIVER,
DEVICE_CLASS_TV,
BrowseMedia,
MediaPlayerEntity,
)
from homeassistant.components.media_player.const import (
@ -74,36 +75,36 @@ async def async_setup_entry(hass, entry, async_add_entities):
)
def browse_media_library(channels: bool = False) -> dict:
def browse_media_library(channels: bool = False) -> BrowseMedia:
"""Create response payload to describe contents of a specific library."""
library_info = {
"title": "Media Library",
"media_content_id": "library",
"media_content_type": "library",
"can_play": False,
"can_expand": True,
"children": [],
}
library_info = BrowseMedia(
title="Media Library",
media_content_id="library",
media_content_type="library",
can_play=False,
can_expand=True,
children=[],
)
library_info["children"].append(
{
"title": "Apps",
"media_content_id": "apps",
"media_content_type": MEDIA_TYPE_APPS,
"can_expand": True,
"can_play": False,
}
library_info.children.append(
BrowseMedia(
title="Apps",
media_content_id="apps",
media_content_type=MEDIA_TYPE_APPS,
can_expand=True,
can_play=False,
)
)
if channels:
library_info["children"].append(
{
"title": "Channels",
"media_content_id": "channels",
"media_content_type": MEDIA_TYPE_CHANNELS,
"can_expand": True,
"can_play": False,
}
library_info.children.append(
BrowseMedia(
title="Channels",
media_content_id="channels",
media_content_type=MEDIA_TYPE_CHANNELS,
can_expand=True,
can_play=False,
)
)
return library_info
@ -283,41 +284,43 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
response = None
if media_content_type == MEDIA_TYPE_APPS:
response = {
"title": "Apps",
"media_content_id": "apps",
"media_content_type": MEDIA_TYPE_APPS,
"can_expand": True,
"can_play": False,
"children": [
{
"title": app.name,
"thumbnail": self.coordinator.roku.app_icon_url(app.app_id),
"media_content_id": app.app_id,
"media_content_type": MEDIA_TYPE_APP,
"can_play": True,
}
response = BrowseMedia(
title="Apps",
media_content_id="apps",
media_content_type=MEDIA_TYPE_APPS,
can_expand=True,
can_play=False,
children=[
BrowseMedia(
title=app.name,
thumbnail=self.coordinator.roku.app_icon_url(app.app_id),
media_content_id=app.app_id,
media_content_type=MEDIA_TYPE_APP,
can_play=True,
can_expand=False,
)
for app in self.coordinator.data.apps
],
}
)
if media_content_type == MEDIA_TYPE_CHANNELS:
response = {
"title": "Channels",
"media_content_id": "channels",
"media_content_type": MEDIA_TYPE_CHANNELS,
"can_expand": True,
"can_play": False,
"children": [
{
"title": channel.name,
"media_content_id": channel.number,
"media_content_type": MEDIA_TYPE_CHANNEL,
"can_play": True,
}
response = BrowseMedia(
title="Channels",
media_content_id="channels",
media_content_type=MEDIA_TYPE_CHANNELS,
can_expand=True,
can_play=False,
children=[
BrowseMedia(
title=channel.name,
media_content_id=channel.number,
media_content_type=MEDIA_TYPE_CHANNEL,
can_play=True,
can_expand=False,
)
for channel in self.coordinator.data.channels
],
}
)
if response is None:
raise BrowseError(

View File

@ -14,7 +14,7 @@ import pysonos.music_library
import pysonos.snapshot
import voluptuous as vol
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
from homeassistant.components.media_player.const import (
ATTR_MEDIA_ENQUEUE,
MEDIA_TYPE_ALBUM,
@ -1462,15 +1462,15 @@ def build_item_response(media_library, payload):
except IndexError:
title = LIBRARY_TITLES_MAPPING[payload["idstring"]]
return {
"title": title,
"thumbnail": thumbnail,
"media_content_id": payload["idstring"],
"media_content_type": payload["search_type"],
"children": [item_payload(item) for item in media],
"can_play": can_play(payload["search_type"]),
"can_expand": can_expand(payload["search_type"]),
}
return BrowseMedia(
title=title,
thumbnail=thumbnail,
media_content_id=payload["idstring"],
media_content_type=payload["search_type"],
children=[item_payload(item) for item in media],
can_play=can_play(payload["search_type"]),
can_expand=can_expand(payload["search_type"]),
)
def item_payload(item):

View File

@ -9,7 +9,7 @@ from aiohttp import ClientError
from spotipy import Spotify, SpotifyException
from yarl import URL
from homeassistant.components.media_player import MediaPlayerEntity
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
from homeassistant.components.media_player.const import (
MEDIA_TYPE_ALBUM,
MEDIA_TYPE_ARTIST,
@ -439,28 +439,30 @@ def build_item_response(spotify, payload):
items = media.get("items", [])
else:
media = None
items = []
if media is None:
return None
if title is None:
if "name" in media:
title = media.get("name")
else:
title = LIBRARY_MAP.get(payload["media_content_id"])
response = {
"title": title,
"media_content_id": payload.get("media_content_id"),
"media_content_type": payload.get("media_content_type"),
"can_play": payload.get("media_content_type") in PLAYABLE_MEDIA_TYPES,
"children": [item_payload(item) for item in items],
"can_expand": True,
}
if "name" in media:
response["title"] = media.get("name")
elif title:
response["title"] = title
else:
response["title"] = LIBRARY_MAP.get(payload["media_content_id"])
if "images" in media:
response["thumbnail"] = fetch_image_url(media)
return response
return BrowseMedia(**response)
def item_payload(item):
@ -469,32 +471,31 @@ def item_payload(item):
Used by async_browse_media.
"""
can_expand = item.get("type") not in [None, MEDIA_TYPE_TRACK]
if (
MEDIA_TYPE_TRACK in item
or item.get("type") != MEDIA_TYPE_ALBUM
and "playlists" in item
):
track = item.get(MEDIA_TYPE_TRACK)
payload = {
"title": track.get("name"),
"thumbnail": fetch_image_url(track.get(MEDIA_TYPE_ALBUM, {})),
"media_content_id": track.get("uri"),
"media_content_type": MEDIA_TYPE_TRACK,
"can_play": True,
}
else:
payload = {
"title": item.get("name"),
"thumbnail": fetch_image_url(item),
"media_content_id": item.get("uri"),
"media_content_type": item.get("type"),
"can_play": item.get("type") in PLAYABLE_MEDIA_TYPES,
}
return BrowseMedia(
title=track.get("name"),
thumbnail=fetch_image_url(track.get(MEDIA_TYPE_ALBUM, {})),
media_content_id=track.get("uri"),
media_content_type=MEDIA_TYPE_TRACK,
can_play=True,
can_expand=can_expand,
)
if item.get("type") not in [None, MEDIA_TYPE_TRACK]:
payload["can_expand"] = True
return payload
return BrowseMedia(
title=item.get("name"),
thumbnail=fetch_image_url(item),
media_content_id=item.get("uri"),
media_content_type=item.get("type"),
can_play=item.get("type") in PLAYABLE_MEDIA_TYPES,
can_expand=can_expand,
)
def library_payload():

View File

@ -41,8 +41,8 @@ async def test_async_browse_media(hass):
# Test non-media ignored (/media has test.mp3 and not_media.txt)
media = await media_source.async_browse_media(hass, "")
assert isinstance(media, media_source.models.BrowseMedia)
assert media.name == "media/"
assert isinstance(media, media_source.models.BrowseMediaSource)
assert media.title == "media/"
assert len(media.children) == 1
# Test invalid media content
@ -51,9 +51,9 @@ async def test_async_browse_media(hass):
# Test base URI returns all domains
media = await media_source.async_browse_media(hass, const.URI_SCHEME)
assert isinstance(media, media_source.models.BrowseMedia)
assert isinstance(media, media_source.models.BrowseMediaSource)
assert len(media.children) == 1
assert media.children[0].name == "Local Media"
assert media.children[0].title == "Local Media"
async def test_async_resolve_media(hass):
@ -73,7 +73,14 @@ async def test_websocket_browse_media(hass, hass_ws_client):
client = await hass_ws_client(hass)
media = media_source.models.BrowseMedia(const.DOMAIN, "/media", False, True)
media = media_source.models.BrowseMediaSource(
domain=const.DOMAIN,
identifier="/media",
title="Local Media",
media_content_type="listing",
can_play=False,
can_expand=True,
)
with patch(
"homeassistant.components.media_source.async_browse_media",
@ -90,7 +97,7 @@ async def test_websocket_browse_media(hass, hass_ws_client):
assert msg["success"]
assert msg["id"] == 1
assert media.to_media_player_item() == msg["result"]
assert media.as_dict() == msg["result"]
with patch(
"homeassistant.components.media_source.async_browse_media",

View File

@ -1,17 +1,30 @@
"""Test Media Source model methods."""
from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC
from homeassistant.components.media_source import const, models
async def test_browse_media_to_media_player_item():
"""Test BrowseMedia conversion to media player item dict."""
base = models.BrowseMedia(const.DOMAIN, "media", "media/", False, True)
async def test_browse_media_as_dict():
"""Test BrowseMediaSource conversion to media player item dict."""
base = models.BrowseMediaSource(
domain=const.DOMAIN,
identifier="media",
media_content_type="folder",
title="media/",
can_play=False,
can_expand=True,
)
base.children = [
models.BrowseMedia(
const.DOMAIN, "media/test.mp3", "test.mp3", True, False, "audio/mp3"
models.BrowseMediaSource(
domain=const.DOMAIN,
identifier="media/test.mp3",
media_content_type=MEDIA_TYPE_MUSIC,
title="test.mp3",
can_play=True,
can_expand=False,
)
]
item = base.to_media_player_item()
item = base.as_dict()
assert item["title"] == "media/"
assert item["media_content_type"] == "folder"
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
@ -21,6 +34,26 @@ async def test_browse_media_to_media_player_item():
assert item["children"][0]["title"] == "test.mp3"
async def test_browse_media_parent_no_children():
"""Test BrowseMediaSource conversion to media player item dict."""
base = models.BrowseMediaSource(
domain=const.DOMAIN,
identifier="media",
media_content_type="folder",
title="media/",
can_play=False,
can_expand=True,
)
item = base.as_dict()
assert item["title"] == "media/"
assert item["media_content_type"] == "folder"
assert item["media_content_id"] == f"{const.URI_SCHEME}{const.DOMAIN}/media"
assert not item["can_play"]
assert item["can_expand"]
assert len(item["children"]) == 0
async def test_media_source_default_name():
"""Test MediaSource uses domain as default name."""
source = models.MediaSource(const.DOMAIN)