mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Add media browser capability to volumio (#40785)
This commit is contained in:
parent
c5ae801bcb
commit
086378c48f
@ -980,6 +980,7 @@ omit =
|
|||||||
homeassistant/components/vlc_telnet/media_player.py
|
homeassistant/components/vlc_telnet/media_player.py
|
||||||
homeassistant/components/volkszaehler/sensor.py
|
homeassistant/components/volkszaehler/sensor.py
|
||||||
homeassistant/components/volumio/__init__.py
|
homeassistant/components/volumio/__init__.py
|
||||||
|
homeassistant/components/volumio/browse_media.py
|
||||||
homeassistant/components/volumio/media_player.py
|
homeassistant/components/volumio/media_player.py
|
||||||
homeassistant/components/volvooncall/*
|
homeassistant/components/volvooncall/*
|
||||||
homeassistant/components/w800rf32/*
|
homeassistant/components/w800rf32/*
|
||||||
|
167
homeassistant/components/volumio/browse_media.py
Normal file
167
homeassistant/components/volumio/browse_media.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
"""Support for media browsing."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import BrowseError, BrowseMedia
|
||||||
|
from homeassistant.components.media_player.const import (
|
||||||
|
MEDIA_CLASS_ALBUM,
|
||||||
|
MEDIA_CLASS_ARTIST,
|
||||||
|
MEDIA_CLASS_CHANNEL,
|
||||||
|
MEDIA_CLASS_DIRECTORY,
|
||||||
|
MEDIA_CLASS_GENRE,
|
||||||
|
MEDIA_CLASS_PLAYLIST,
|
||||||
|
MEDIA_CLASS_TRACK,
|
||||||
|
MEDIA_TYPE_MUSIC,
|
||||||
|
)
|
||||||
|
|
||||||
|
PLAYABLE_ITEM_TYPES = [
|
||||||
|
"folder",
|
||||||
|
"song",
|
||||||
|
"mywebradio",
|
||||||
|
"webradio",
|
||||||
|
"playlist",
|
||||||
|
"cuesong",
|
||||||
|
"remdisk",
|
||||||
|
"cuefile",
|
||||||
|
"folder-with-favourites",
|
||||||
|
"internal-folder",
|
||||||
|
]
|
||||||
|
|
||||||
|
NON_EXPANDABLE_ITEM_TYPES = [
|
||||||
|
"song",
|
||||||
|
"webradio",
|
||||||
|
"mywebradio",
|
||||||
|
"cuesong",
|
||||||
|
"album",
|
||||||
|
"artist",
|
||||||
|
"cd",
|
||||||
|
"play-playlist",
|
||||||
|
]
|
||||||
|
|
||||||
|
PLAYLISTS_URI_PREFIX = "playlists"
|
||||||
|
ARTISTS_URI_PREFIX = "artists://"
|
||||||
|
ALBUMS_URI_PREFIX = "albums://"
|
||||||
|
GENRES_URI_PREFIX = "genres://"
|
||||||
|
RADIO_URI_PREFIX = "radio"
|
||||||
|
LAST_100_URI_PREFIX = "Last_100"
|
||||||
|
FAVOURITES_URI = "favourites"
|
||||||
|
|
||||||
|
|
||||||
|
def _item_to_children_media_class(item, info=None):
|
||||||
|
if info and "album" in info and "artist" in info:
|
||||||
|
return MEDIA_CLASS_TRACK
|
||||||
|
if item["uri"].startswith(PLAYLISTS_URI_PREFIX):
|
||||||
|
return MEDIA_CLASS_PLAYLIST
|
||||||
|
if item["uri"].startswith(ARTISTS_URI_PREFIX):
|
||||||
|
if len(item["uri"]) > len(ARTISTS_URI_PREFIX):
|
||||||
|
return MEDIA_CLASS_ALBUM
|
||||||
|
return MEDIA_CLASS_ARTIST
|
||||||
|
if item["uri"].startswith(ALBUMS_URI_PREFIX):
|
||||||
|
if len(item["uri"]) > len(ALBUMS_URI_PREFIX):
|
||||||
|
return MEDIA_CLASS_TRACK
|
||||||
|
return MEDIA_CLASS_ALBUM
|
||||||
|
if item["uri"].startswith(GENRES_URI_PREFIX):
|
||||||
|
if len(item["uri"]) > len(GENRES_URI_PREFIX):
|
||||||
|
return MEDIA_CLASS_ALBUM
|
||||||
|
return MEDIA_CLASS_GENRE
|
||||||
|
if item["uri"].startswith(LAST_100_URI_PREFIX) or item["uri"] == FAVOURITES_URI:
|
||||||
|
return MEDIA_CLASS_TRACK
|
||||||
|
if item["uri"].startswith(RADIO_URI_PREFIX):
|
||||||
|
return MEDIA_CLASS_CHANNEL
|
||||||
|
return MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
|
def _item_to_media_class(item, parent_item=None):
|
||||||
|
if "type" not in item:
|
||||||
|
return MEDIA_CLASS_DIRECTORY
|
||||||
|
if item["type"] in ["webradio", "mywebradio"]:
|
||||||
|
return MEDIA_CLASS_CHANNEL
|
||||||
|
if item["type"] in ["song", "cuesong"]:
|
||||||
|
return MEDIA_CLASS_TRACK
|
||||||
|
if item.get("artist"):
|
||||||
|
return MEDIA_CLASS_ALBUM
|
||||||
|
if item["uri"].startswith(ARTISTS_URI_PREFIX) and len(item["uri"]) > len(
|
||||||
|
ARTISTS_URI_PREFIX
|
||||||
|
):
|
||||||
|
return MEDIA_CLASS_ARTIST
|
||||||
|
if parent_item:
|
||||||
|
return _item_to_children_media_class(parent_item)
|
||||||
|
return MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
|
def _list_payload(media_library, item, children=None):
|
||||||
|
return BrowseMedia(
|
||||||
|
title=item["name"],
|
||||||
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
|
children_media_class=_item_to_children_media_class(item),
|
||||||
|
media_content_type=MEDIA_TYPE_MUSIC,
|
||||||
|
media_content_id=json.dumps(item),
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _raw_item_payload(media_library, item, parent_item=None, title=None, info=None):
|
||||||
|
if "type" in item:
|
||||||
|
thumbnail = item.get("albumart")
|
||||||
|
if thumbnail:
|
||||||
|
thumbnail = media_library.canonic_url(thumbnail)
|
||||||
|
else:
|
||||||
|
# don't use the built-in volumio white-on-white icons
|
||||||
|
thumbnail = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"title": title or item.get("title"),
|
||||||
|
"media_class": _item_to_media_class(item, parent_item),
|
||||||
|
"children_media_class": _item_to_children_media_class(item, info),
|
||||||
|
"media_content_type": MEDIA_TYPE_MUSIC,
|
||||||
|
"media_content_id": json.dumps(item),
|
||||||
|
"can_play": item.get("type") in PLAYABLE_ITEM_TYPES,
|
||||||
|
"can_expand": item.get("type") not in NON_EXPANDABLE_ITEM_TYPES,
|
||||||
|
"thumbnail": thumbnail,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _item_payload(media_library, item, parent_item):
|
||||||
|
return BrowseMedia(
|
||||||
|
**_raw_item_payload(media_library, item, parent_item=parent_item)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def browse_top_level(media_library):
|
||||||
|
"""Browse the top-level of a Volumio media hierarchy."""
|
||||||
|
navigation = await media_library.browse()
|
||||||
|
children = [_list_payload(media_library, item) for item in navigation["lists"]]
|
||||||
|
return BrowseMedia(
|
||||||
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
|
media_content_id="library",
|
||||||
|
media_content_type="library",
|
||||||
|
title="Media Library",
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
children=children,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def browse_node(media_library, media_content_type, media_content_id):
|
||||||
|
"""Browse a node of a Volumio media hierarchy."""
|
||||||
|
json_item = json.loads(media_content_id)
|
||||||
|
navigation = await media_library.browse(json_item["uri"])
|
||||||
|
if "lists" not in navigation:
|
||||||
|
raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
|
||||||
|
|
||||||
|
# we only use the first list since the second one could include all tracks
|
||||||
|
first_list = navigation["lists"][0]
|
||||||
|
children = [
|
||||||
|
_item_payload(media_library, item, parent_item=json_item)
|
||||||
|
for item in first_list["items"]
|
||||||
|
]
|
||||||
|
info = navigation.get("info")
|
||||||
|
title = first_list.get("title")
|
||||||
|
if not title:
|
||||||
|
if info:
|
||||||
|
title = f"{info.get('album')} ({info.get('artist')})"
|
||||||
|
else:
|
||||||
|
title = "Media Library"
|
||||||
|
|
||||||
|
payload = _raw_item_payload(media_library, json_item, title=title, info=info)
|
||||||
|
return BrowseMedia(**payload, children=children)
|
@ -5,5 +5,5 @@
|
|||||||
"codeowners": ["@OnFreund"],
|
"codeowners": ["@OnFreund"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"zeroconf": ["_Volumio._tcp.local."],
|
"zeroconf": ["_Volumio._tcp.local."],
|
||||||
"requirements": ["pyvolumio==0.1.2"]
|
"requirements": ["pyvolumio==0.1.3"]
|
||||||
}
|
}
|
@ -4,15 +4,18 @@ Volumio Platform.
|
|||||||
Volumio rest API: https://volumio.github.io/docs/API/REST_API.html
|
Volumio rest API: https://volumio.github.io/docs/API/REST_API.html
|
||||||
"""
|
"""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.media_player import MediaPlayerEntity
|
from homeassistant.components.media_player import MediaPlayerEntity
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_TYPE_MUSIC,
|
MEDIA_TYPE_MUSIC,
|
||||||
|
SUPPORT_BROWSE_MEDIA,
|
||||||
SUPPORT_CLEAR_PLAYLIST,
|
SUPPORT_CLEAR_PLAYLIST,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PREVIOUS_TRACK,
|
||||||
SUPPORT_SEEK,
|
SUPPORT_SEEK,
|
||||||
SUPPORT_SELECT_SOURCE,
|
SUPPORT_SELECT_SOURCE,
|
||||||
@ -31,6 +34,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
|
from .browse_media import browse_node, browse_top_level
|
||||||
from .const import DATA_INFO, DATA_VOLUMIO, DOMAIN
|
from .const import DATA_INFO, DATA_VOLUMIO, DOMAIN
|
||||||
|
|
||||||
_CONFIGURING = {}
|
_CONFIGURING = {}
|
||||||
@ -45,10 +49,12 @@ SUPPORT_VOLUMIO = (
|
|||||||
| SUPPORT_SEEK
|
| SUPPORT_SEEK
|
||||||
| SUPPORT_STOP
|
| SUPPORT_STOP
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
|
| SUPPORT_PLAY_MEDIA
|
||||||
| SUPPORT_VOLUME_STEP
|
| SUPPORT_VOLUME_STEP
|
||||||
| SUPPORT_SELECT_SOURCE
|
| SUPPORT_SELECT_SOURCE
|
||||||
| SUPPORT_SHUFFLE_SET
|
| SUPPORT_SHUFFLE_SET
|
||||||
| SUPPORT_CLEAR_PLAYLIST
|
| SUPPORT_CLEAR_PLAYLIST
|
||||||
|
| SUPPORT_BROWSE_MEDIA
|
||||||
)
|
)
|
||||||
|
|
||||||
PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=15)
|
PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=15)
|
||||||
@ -246,3 +252,14 @@ class Volumio(MediaPlayerEntity):
|
|||||||
async def _async_update_playlists(self, **kwargs):
|
async def _async_update_playlists(self, **kwargs):
|
||||||
"""Update available Volumio playlists."""
|
"""Update available Volumio playlists."""
|
||||||
self._playlists = await self._volumio.get_playlists()
|
self._playlists = await self._volumio.get_playlists()
|
||||||
|
|
||||||
|
async def async_play_media(self, media_type, media_id, **kwargs):
|
||||||
|
"""Send the play_media command to the media player."""
|
||||||
|
await self._volumio.replace_and_play(json.loads(media_id))
|
||||||
|
|
||||||
|
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||||
|
"""Implement the websocket media browsing helper."""
|
||||||
|
if media_content_type in [None, "library"]:
|
||||||
|
return await browse_top_level(self._volumio)
|
||||||
|
|
||||||
|
return await browse_node(self._volumio, media_content_type, media_content_id)
|
||||||
|
@ -1878,7 +1878,7 @@ pyvizio==0.1.56
|
|||||||
pyvlx==0.2.17
|
pyvlx==0.2.17
|
||||||
|
|
||||||
# homeassistant.components.volumio
|
# homeassistant.components.volumio
|
||||||
pyvolumio==0.1.2
|
pyvolumio==0.1.3
|
||||||
|
|
||||||
# homeassistant.components.html5
|
# homeassistant.components.html5
|
||||||
pywebpush==1.9.2
|
pywebpush==1.9.2
|
||||||
|
@ -895,7 +895,7 @@ pyvesync==1.2.0
|
|||||||
pyvizio==0.1.56
|
pyvizio==0.1.56
|
||||||
|
|
||||||
# homeassistant.components.volumio
|
# homeassistant.components.volumio
|
||||||
pyvolumio==0.1.2
|
pyvolumio==0.1.3
|
||||||
|
|
||||||
# homeassistant.components.html5
|
# homeassistant.components.html5
|
||||||
pywebpush==1.9.2
|
pywebpush==1.9.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user