mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +00:00
Allow browsing the Spotify media player in Sonos (#64921)
This commit is contained in:
parent
f32d9952c8
commit
a371f8f788
@ -5,7 +5,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||||
"requirements": ["soco==0.26.0"],
|
"requirements": ["soco==0.26.0"],
|
||||||
"dependencies": ["ssdp"],
|
"dependencies": ["ssdp"],
|
||||||
"after_dependencies": ["plex", "zeroconf", "media_source"],
|
"after_dependencies": ["plex", "spotify", "zeroconf", "media_source"],
|
||||||
"zeroconf": ["_sonos._tcp.local."],
|
"zeroconf": ["_sonos._tcp.local."],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ from functools import partial
|
|||||||
import logging
|
import logging
|
||||||
from urllib.parse import quote_plus, unquote
|
from urllib.parse import quote_plus, unquote
|
||||||
|
|
||||||
from homeassistant.components import media_source
|
from homeassistant.components import media_source, spotify
|
||||||
from homeassistant.components.media_player import BrowseMedia
|
from homeassistant.components.media_player import BrowseMedia
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_CLASS_DIRECTORY,
|
MEDIA_CLASS_DIRECTORY,
|
||||||
@ -90,6 +90,14 @@ async def async_browse_media(
|
|||||||
hass, media_content_id, content_filter=media_source_filter
|
hass, media_content_id, content_filter=media_source_filter
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if spotify.is_spotify_media_type(media_content_type):
|
||||||
|
return await spotify.async_browse_media(
|
||||||
|
hass, media_content_type, media_content_id, can_play_artist=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if media_content_type == "spotify":
|
||||||
|
return await spotify.async_browse_media(hass, None, None, can_play_artist=False)
|
||||||
|
|
||||||
if media_content_type == "library":
|
if media_content_type == "library":
|
||||||
return await hass.async_add_executor_job(
|
return await hass.async_add_executor_job(
|
||||||
library_payload,
|
library_payload,
|
||||||
@ -248,6 +256,19 @@ async def root_payload(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "spotify" in hass.config.components:
|
||||||
|
children.append(
|
||||||
|
BrowseMedia(
|
||||||
|
title="Spotify",
|
||||||
|
media_class=MEDIA_CLASS_DIRECTORY,
|
||||||
|
media_content_id="",
|
||||||
|
media_content_type="spotify",
|
||||||
|
thumbnail="https://brands.home-assistant.io/spotify/icon.png",
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if await hass.async_add_executor_job(
|
if await hass.async_add_executor_job(
|
||||||
partial(media.library.browse_by_idstring, "tracks", "", max_items=1)
|
partial(media.library.browse_by_idstring, "tracks", "", max_items=1)
|
||||||
):
|
):
|
||||||
|
@ -18,7 +18,7 @@ from soco.core import (
|
|||||||
from soco.data_structures import DidlFavorite
|
from soco.data_structures import DidlFavorite
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import media_source
|
from homeassistant.components import media_source, spotify
|
||||||
from homeassistant.components.http.auth import async_sign_path
|
from homeassistant.components.http.auth import async_sign_path
|
||||||
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 (
|
||||||
@ -520,6 +520,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
|
|
||||||
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
|
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
|
||||||
"""
|
"""
|
||||||
|
if spotify.is_spotify_media_type(media_type):
|
||||||
|
media_type = spotify.resolve_spotify_media_type(media_type)
|
||||||
|
|
||||||
if media_source.is_media_source_id(media_id):
|
if media_source.is_media_source_id(media_id):
|
||||||
media_type = MEDIA_TYPE_MUSIC
|
media_type = MEDIA_TYPE_MUSIC
|
||||||
media_id = (
|
media_id = (
|
||||||
|
@ -26,8 +26,10 @@ from .const import (
|
|||||||
DATA_SPOTIFY_ME,
|
DATA_SPOTIFY_ME,
|
||||||
DATA_SPOTIFY_SESSION,
|
DATA_SPOTIFY_SESSION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MEDIA_PLAYER_PREFIX,
|
||||||
SPOTIFY_SCOPES,
|
SPOTIFY_SCOPES,
|
||||||
)
|
)
|
||||||
|
from .media_player import async_browse_media_internal
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -44,6 +46,31 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||||
|
|
||||||
|
|
||||||
|
def is_spotify_media_type(media_content_type):
|
||||||
|
"""Return whether the media_content_type is a valid Spotify media_id."""
|
||||||
|
return media_content_type.startswith(MEDIA_PLAYER_PREFIX)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_spotify_media_type(media_content_type):
|
||||||
|
"""Return actual spotify media_content_type."""
|
||||||
|
return media_content_type[len(MEDIA_PLAYER_PREFIX) :]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_browse_media(
|
||||||
|
hass, media_content_type, media_content_id, *, can_play_artist=True
|
||||||
|
):
|
||||||
|
"""Browse Spotify media."""
|
||||||
|
info = list(hass.data[DOMAIN].values())[0]
|
||||||
|
return await async_browse_media_internal(
|
||||||
|
hass,
|
||||||
|
info[DATA_SPOTIFY_CLIENT],
|
||||||
|
info[DATA_SPOTIFY_ME],
|
||||||
|
media_content_type,
|
||||||
|
media_content_id,
|
||||||
|
can_play_artist=can_play_artist,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Spotify integration."""
|
"""Set up the Spotify integration."""
|
||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
|
@ -22,3 +22,5 @@ SPOTIFY_SCOPES = [
|
|||||||
"user-read-recently-played",
|
"user-read-recently-played",
|
||||||
"user-follow-read",
|
"user-follow-read",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MEDIA_PLAYER_PREFIX = "spotify://"
|
||||||
|
@ -4,12 +4,14 @@ from __future__ import annotations
|
|||||||
from asyncio import run_coroutine_threadsafe
|
from asyncio import run_coroutine_threadsafe
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from spotipy import Spotify, SpotifyException
|
from spotipy import Spotify, SpotifyException
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
|
from homeassistant.backports.enum import StrEnum
|
||||||
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_CLASS_ALBUM,
|
MEDIA_CLASS_ALBUM,
|
||||||
@ -63,6 +65,7 @@ from .const import (
|
|||||||
DATA_SPOTIFY_ME,
|
DATA_SPOTIFY_ME,
|
||||||
DATA_SPOTIFY_SESSION,
|
DATA_SPOTIFY_SESSION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MEDIA_PLAYER_PREFIX,
|
||||||
SPOTIFY_SCOPES,
|
SPOTIFY_SCOPES,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -107,63 +110,86 @@ PLAYABLE_MEDIA_TYPES = [
|
|||||||
MEDIA_TYPE_TRACK,
|
MEDIA_TYPE_TRACK,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BrowsableMedia(StrEnum):
|
||||||
|
"""Enum of browsable media."""
|
||||||
|
|
||||||
|
CURRENT_USER_PLAYLISTS = "current_user_playlists"
|
||||||
|
CURRENT_USER_FOLLOWED_ARTISTS = "current_user_followed_artists"
|
||||||
|
CURRENT_USER_SAVED_ALBUMS = "current_user_saved_albums"
|
||||||
|
CURRENT_USER_SAVED_TRACKS = "current_user_saved_tracks"
|
||||||
|
CURRENT_USER_SAVED_SHOWS = "current_user_saved_shows"
|
||||||
|
CURRENT_USER_RECENTLY_PLAYED = "current_user_recently_played"
|
||||||
|
CURRENT_USER_TOP_ARTISTS = "current_user_top_artists"
|
||||||
|
CURRENT_USER_TOP_TRACKS = "current_user_top_tracks"
|
||||||
|
CATEGORIES = "categories"
|
||||||
|
FEATURED_PLAYLISTS = "featured_playlists"
|
||||||
|
NEW_RELEASES = "new_releases"
|
||||||
|
|
||||||
|
|
||||||
LIBRARY_MAP = {
|
LIBRARY_MAP = {
|
||||||
"current_user_playlists": "Playlists",
|
BrowsableMedia.CURRENT_USER_PLAYLISTS: "Playlists",
|
||||||
"current_user_followed_artists": "Artists",
|
BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: "Artists",
|
||||||
"current_user_saved_albums": "Albums",
|
BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: "Albums",
|
||||||
"current_user_saved_tracks": "Tracks",
|
BrowsableMedia.CURRENT_USER_SAVED_TRACKS: "Tracks",
|
||||||
"current_user_saved_shows": "Podcasts",
|
BrowsableMedia.CURRENT_USER_SAVED_SHOWS: "Podcasts",
|
||||||
"current_user_recently_played": "Recently played",
|
BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: "Recently played",
|
||||||
"current_user_top_artists": "Top Artists",
|
BrowsableMedia.CURRENT_USER_TOP_ARTISTS: "Top Artists",
|
||||||
"current_user_top_tracks": "Top Tracks",
|
BrowsableMedia.CURRENT_USER_TOP_TRACKS: "Top Tracks",
|
||||||
"categories": "Categories",
|
BrowsableMedia.CATEGORIES: "Categories",
|
||||||
"featured_playlists": "Featured Playlists",
|
BrowsableMedia.FEATURED_PLAYLISTS: "Featured Playlists",
|
||||||
"new_releases": "New Releases",
|
BrowsableMedia.NEW_RELEASES: "New Releases",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONTENT_TYPE_MEDIA_CLASS = {
|
CONTENT_TYPE_MEDIA_CLASS = {
|
||||||
"current_user_playlists": {
|
BrowsableMedia.CURRENT_USER_PLAYLISTS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_PLAYLIST,
|
"children": MEDIA_CLASS_PLAYLIST,
|
||||||
},
|
},
|
||||||
"current_user_followed_artists": {
|
BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_ARTIST,
|
"children": MEDIA_CLASS_ARTIST,
|
||||||
},
|
},
|
||||||
"current_user_saved_albums": {
|
BrowsableMedia.CURRENT_USER_SAVED_ALBUMS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_ALBUM,
|
"children": MEDIA_CLASS_ALBUM,
|
||||||
},
|
},
|
||||||
"current_user_saved_tracks": {
|
BrowsableMedia.CURRENT_USER_SAVED_TRACKS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_TRACK,
|
"children": MEDIA_CLASS_TRACK,
|
||||||
},
|
},
|
||||||
"current_user_saved_shows": {
|
BrowsableMedia.CURRENT_USER_SAVED_SHOWS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_PODCAST,
|
"children": MEDIA_CLASS_PODCAST,
|
||||||
},
|
},
|
||||||
"current_user_recently_played": {
|
BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_TRACK,
|
"children": MEDIA_CLASS_TRACK,
|
||||||
},
|
},
|
||||||
"current_user_top_artists": {
|
BrowsableMedia.CURRENT_USER_TOP_ARTISTS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_ARTIST,
|
"children": MEDIA_CLASS_ARTIST,
|
||||||
},
|
},
|
||||||
"current_user_top_tracks": {
|
BrowsableMedia.CURRENT_USER_TOP_TRACKS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_TRACK,
|
"children": MEDIA_CLASS_TRACK,
|
||||||
},
|
},
|
||||||
"featured_playlists": {
|
BrowsableMedia.FEATURED_PLAYLISTS: {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_PLAYLIST,
|
"children": MEDIA_CLASS_PLAYLIST,
|
||||||
},
|
},
|
||||||
"categories": {"parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_GENRE},
|
BrowsableMedia.CATEGORIES: {
|
||||||
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
|
"children": MEDIA_CLASS_GENRE,
|
||||||
|
},
|
||||||
"category_playlists": {
|
"category_playlists": {
|
||||||
"parent": MEDIA_CLASS_DIRECTORY,
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
"children": MEDIA_CLASS_PLAYLIST,
|
"children": MEDIA_CLASS_PLAYLIST,
|
||||||
},
|
},
|
||||||
"new_releases": {"parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ALBUM},
|
BrowsableMedia.NEW_RELEASES: {
|
||||||
|
"parent": MEDIA_CLASS_DIRECTORY,
|
||||||
|
"children": MEDIA_CLASS_ALBUM,
|
||||||
|
},
|
||||||
MEDIA_TYPE_PLAYLIST: {
|
MEDIA_TYPE_PLAYLIST: {
|
||||||
"parent": MEDIA_CLASS_PLAYLIST,
|
"parent": MEDIA_CLASS_PLAYLIST,
|
||||||
"children": MEDIA_CLASS_TRACK,
|
"children": MEDIA_CLASS_TRACK,
|
||||||
@ -421,6 +447,9 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
@spotify_exception_handler
|
@spotify_exception_handler
|
||||||
def play_media(self, media_type: str, media_id: str, **kwargs) -> None:
|
def play_media(self, media_type: str, media_id: str, **kwargs) -> None:
|
||||||
"""Play media."""
|
"""Play media."""
|
||||||
|
if media_type.startswith(MEDIA_PLAYER_PREFIX):
|
||||||
|
media_type = media_type[len(MEDIA_PLAYER_PREFIX) :]
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
# Spotify can't handle URI's with query strings or anchors
|
# Spotify can't handle URI's with query strings or anchors
|
||||||
@ -494,57 +523,81 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
)
|
)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
if media_content_type in (None, "library"):
|
return await async_browse_media_internal(
|
||||||
return await self.hass.async_add_executor_job(library_payload)
|
self.hass, self._spotify, self._me, media_content_type, media_content_id
|
||||||
|
|
||||||
payload = {
|
|
||||||
"media_content_type": media_content_type,
|
|
||||||
"media_content_id": media_content_id,
|
|
||||||
}
|
|
||||||
response = await self.hass.async_add_executor_job(
|
|
||||||
build_item_response, self._spotify, self._me, payload
|
|
||||||
)
|
)
|
||||||
if response is None:
|
|
||||||
raise BrowseError(
|
|
||||||
f"Media not found: {media_content_type} / {media_content_id}"
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def build_item_response(spotify, user, payload): # noqa: C901
|
async def async_browse_media_internal(
|
||||||
|
hass,
|
||||||
|
spotify,
|
||||||
|
current_user,
|
||||||
|
media_content_type,
|
||||||
|
media_content_id,
|
||||||
|
*,
|
||||||
|
can_play_artist=True,
|
||||||
|
):
|
||||||
|
"""Browse spotify media."""
|
||||||
|
if media_content_type in (None, f"{MEDIA_PLAYER_PREFIX}library"):
|
||||||
|
return await hass.async_add_executor_job(
|
||||||
|
partial(library_payload, can_play_artist=can_play_artist)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Strip prefix
|
||||||
|
media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX) :]
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"media_content_type": media_content_type,
|
||||||
|
"media_content_id": media_content_id,
|
||||||
|
}
|
||||||
|
response = await hass.async_add_executor_job(
|
||||||
|
partial(
|
||||||
|
build_item_response,
|
||||||
|
spotify,
|
||||||
|
current_user,
|
||||||
|
payload,
|
||||||
|
can_play_artist=can_play_artist,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if response is None:
|
||||||
|
raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def build_item_response(spotify, user, payload, *, can_play_artist): # noqa: C901
|
||||||
"""Create response payload for the provided media query."""
|
"""Create response payload for the provided media query."""
|
||||||
media_content_type = payload["media_content_type"]
|
media_content_type = payload["media_content_type"]
|
||||||
media_content_id = payload["media_content_id"]
|
media_content_id = payload["media_content_id"]
|
||||||
title = None
|
title = None
|
||||||
image = None
|
image = None
|
||||||
if media_content_type == "current_user_playlists":
|
if media_content_type == BrowsableMedia.CURRENT_USER_PLAYLISTS:
|
||||||
media = spotify.current_user_playlists(limit=BROWSE_LIMIT)
|
media = spotify.current_user_playlists(limit=BROWSE_LIMIT)
|
||||||
items = media.get("items", [])
|
items = media.get("items", [])
|
||||||
elif media_content_type == "current_user_followed_artists":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_FOLLOWED_ARTISTS:
|
||||||
media = spotify.current_user_followed_artists(limit=BROWSE_LIMIT)
|
media = spotify.current_user_followed_artists(limit=BROWSE_LIMIT)
|
||||||
items = media.get("artists", {}).get("items", [])
|
items = media.get("artists", {}).get("items", [])
|
||||||
elif media_content_type == "current_user_saved_albums":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_ALBUMS:
|
||||||
media = spotify.current_user_saved_albums(limit=BROWSE_LIMIT)
|
media = spotify.current_user_saved_albums(limit=BROWSE_LIMIT)
|
||||||
items = [item["album"] for item in media.get("items", [])]
|
items = [item["album"] for item in media.get("items", [])]
|
||||||
elif media_content_type == "current_user_saved_tracks":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_TRACKS:
|
||||||
media = spotify.current_user_saved_tracks(limit=BROWSE_LIMIT)
|
media = spotify.current_user_saved_tracks(limit=BROWSE_LIMIT)
|
||||||
items = [item["track"] for item in media.get("items", [])]
|
items = [item["track"] for item in media.get("items", [])]
|
||||||
elif media_content_type == "current_user_saved_shows":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_SAVED_SHOWS:
|
||||||
media = spotify.current_user_saved_shows(limit=BROWSE_LIMIT)
|
media = spotify.current_user_saved_shows(limit=BROWSE_LIMIT)
|
||||||
items = [item["show"] for item in media.get("items", [])]
|
items = [item["show"] for item in media.get("items", [])]
|
||||||
elif media_content_type == "current_user_recently_played":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_RECENTLY_PLAYED:
|
||||||
media = spotify.current_user_recently_played(limit=BROWSE_LIMIT)
|
media = spotify.current_user_recently_played(limit=BROWSE_LIMIT)
|
||||||
items = [item["track"] for item in media.get("items", [])]
|
items = [item["track"] for item in media.get("items", [])]
|
||||||
elif media_content_type == "current_user_top_artists":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_ARTISTS:
|
||||||
media = spotify.current_user_top_artists(limit=BROWSE_LIMIT)
|
media = spotify.current_user_top_artists(limit=BROWSE_LIMIT)
|
||||||
items = media.get("items", [])
|
items = media.get("items", [])
|
||||||
elif media_content_type == "current_user_top_tracks":
|
elif media_content_type == BrowsableMedia.CURRENT_USER_TOP_TRACKS:
|
||||||
media = spotify.current_user_top_tracks(limit=BROWSE_LIMIT)
|
media = spotify.current_user_top_tracks(limit=BROWSE_LIMIT)
|
||||||
items = media.get("items", [])
|
items = media.get("items", [])
|
||||||
elif media_content_type == "featured_playlists":
|
elif media_content_type == BrowsableMedia.FEATURED_PLAYLISTS:
|
||||||
media = spotify.featured_playlists(country=user["country"], limit=BROWSE_LIMIT)
|
media = spotify.featured_playlists(country=user["country"], limit=BROWSE_LIMIT)
|
||||||
items = media.get("playlists", {}).get("items", [])
|
items = media.get("playlists", {}).get("items", [])
|
||||||
elif media_content_type == "categories":
|
elif media_content_type == BrowsableMedia.CATEGORIES:
|
||||||
media = spotify.categories(country=user["country"], limit=BROWSE_LIMIT)
|
media = spotify.categories(country=user["country"], limit=BROWSE_LIMIT)
|
||||||
items = media.get("categories", {}).get("items", [])
|
items = media.get("categories", {}).get("items", [])
|
||||||
elif media_content_type == "category_playlists":
|
elif media_content_type == "category_playlists":
|
||||||
@ -557,7 +610,7 @@ def build_item_response(spotify, user, payload): # noqa: C901
|
|||||||
title = category.get("name")
|
title = category.get("name")
|
||||||
image = fetch_image_url(category, key="icons")
|
image = fetch_image_url(category, key="icons")
|
||||||
items = media.get("playlists", {}).get("items", [])
|
items = media.get("playlists", {}).get("items", [])
|
||||||
elif media_content_type == "new_releases":
|
elif media_content_type == BrowsableMedia.NEW_RELEASES:
|
||||||
media = spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT)
|
media = spotify.new_releases(country=user["country"], limit=BROWSE_LIMIT)
|
||||||
items = media.get("albums", {}).get("items", [])
|
items = media.get("albums", {}).get("items", [])
|
||||||
elif media_content_type == MEDIA_TYPE_PLAYLIST:
|
elif media_content_type == MEDIA_TYPE_PLAYLIST:
|
||||||
@ -591,13 +644,13 @@ def build_item_response(spotify, user, payload): # noqa: C901
|
|||||||
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if media_content_type == "categories":
|
if media_content_type == BrowsableMedia.CATEGORIES:
|
||||||
media_item = BrowseMedia(
|
media_item = BrowseMedia(
|
||||||
title=LIBRARY_MAP.get(media_content_id),
|
title=LIBRARY_MAP.get(media_content_id),
|
||||||
media_class=media_class["parent"],
|
media_class=media_class["parent"],
|
||||||
children_media_class=media_class["children"],
|
children_media_class=media_class["children"],
|
||||||
media_content_id=media_content_id,
|
media_content_id=media_content_id,
|
||||||
media_content_type=media_content_type,
|
media_content_type=MEDIA_PLAYER_PREFIX + media_content_type,
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
children=[],
|
children=[],
|
||||||
@ -614,7 +667,7 @@ def build_item_response(spotify, user, payload): # noqa: C901
|
|||||||
media_class=MEDIA_CLASS_PLAYLIST,
|
media_class=MEDIA_CLASS_PLAYLIST,
|
||||||
children_media_class=MEDIA_CLASS_TRACK,
|
children_media_class=MEDIA_CLASS_TRACK,
|
||||||
media_content_id=item_id,
|
media_content_id=item_id,
|
||||||
media_content_type="category_playlists",
|
media_content_type=MEDIA_PLAYER_PREFIX + "category_playlists",
|
||||||
thumbnail=fetch_image_url(item, key="icons"),
|
thumbnail=fetch_image_url(item, key="icons"),
|
||||||
can_play=False,
|
can_play=False,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
@ -633,14 +686,17 @@ def build_item_response(spotify, user, payload): # noqa: C901
|
|||||||
"media_class": media_class["parent"],
|
"media_class": media_class["parent"],
|
||||||
"children_media_class": media_class["children"],
|
"children_media_class": media_class["children"],
|
||||||
"media_content_id": media_content_id,
|
"media_content_id": media_content_id,
|
||||||
"media_content_type": media_content_type,
|
"media_content_type": MEDIA_PLAYER_PREFIX + media_content_type,
|
||||||
"can_play": media_content_type in PLAYABLE_MEDIA_TYPES,
|
"can_play": media_content_type in PLAYABLE_MEDIA_TYPES
|
||||||
|
and (media_content_type != MEDIA_TYPE_ARTIST or can_play_artist),
|
||||||
"children": [],
|
"children": [],
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
}
|
}
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
params["children"].append(item_payload(item))
|
params["children"].append(
|
||||||
|
item_payload(item, can_play_artist=can_play_artist)
|
||||||
|
)
|
||||||
except (MissingMediaInformation, UnknownMediaType):
|
except (MissingMediaInformation, UnknownMediaType):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -652,7 +708,7 @@ def build_item_response(spotify, user, payload): # noqa: C901
|
|||||||
return BrowseMedia(**params)
|
return BrowseMedia(**params)
|
||||||
|
|
||||||
|
|
||||||
def item_payload(item):
|
def item_payload(item, *, can_play_artist):
|
||||||
"""
|
"""
|
||||||
Create response payload for a single media item.
|
Create response payload for a single media item.
|
||||||
|
|
||||||
@ -681,8 +737,9 @@ def item_payload(item):
|
|||||||
"media_class": media_class["parent"],
|
"media_class": media_class["parent"],
|
||||||
"children_media_class": media_class["children"],
|
"children_media_class": media_class["children"],
|
||||||
"media_content_id": media_id,
|
"media_content_id": media_id,
|
||||||
"media_content_type": media_type,
|
"media_content_type": MEDIA_PLAYER_PREFIX + media_type,
|
||||||
"can_play": media_type in PLAYABLE_MEDIA_TYPES,
|
"can_play": media_type in PLAYABLE_MEDIA_TYPES
|
||||||
|
and (media_type != MEDIA_TYPE_ARTIST or can_play_artist),
|
||||||
"can_expand": can_expand,
|
"can_expand": can_expand,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,7 +751,7 @@ def item_payload(item):
|
|||||||
return BrowseMedia(**payload)
|
return BrowseMedia(**payload)
|
||||||
|
|
||||||
|
|
||||||
def library_payload():
|
def library_payload(*, can_play_artist):
|
||||||
"""
|
"""
|
||||||
Create response payload to describe contents of a specific library.
|
Create response payload to describe contents of a specific library.
|
||||||
|
|
||||||
@ -704,7 +761,7 @@ def library_payload():
|
|||||||
"title": "Media Library",
|
"title": "Media Library",
|
||||||
"media_class": MEDIA_CLASS_DIRECTORY,
|
"media_class": MEDIA_CLASS_DIRECTORY,
|
||||||
"media_content_id": "library",
|
"media_content_id": "library",
|
||||||
"media_content_type": "library",
|
"media_content_type": MEDIA_PLAYER_PREFIX + "library",
|
||||||
"can_play": False,
|
"can_play": False,
|
||||||
"can_expand": True,
|
"can_expand": True,
|
||||||
"children": [],
|
"children": [],
|
||||||
@ -713,7 +770,8 @@ def library_payload():
|
|||||||
for item in [{"name": n, "type": t} for t, n in LIBRARY_MAP.items()]:
|
for item in [{"name": n, "type": t} for t, n in LIBRARY_MAP.items()]:
|
||||||
library_info["children"].append(
|
library_info["children"].append(
|
||||||
item_payload(
|
item_payload(
|
||||||
{"name": item["name"], "type": item["type"], "uri": item["type"]}
|
{"name": item["name"], "type": item["type"], "uri": item["type"]},
|
||||||
|
can_play_artist=can_play_artist,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
response = BrowseMedia(**library_info)
|
response = BrowseMedia(**library_info)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user