diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 5c36a0c71c3..c3e9504c05c 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -1,6 +1,11 @@ """The spotify integration.""" +from __future__ import annotations + +from datetime import timedelta +from typing import Any import aiohttp +import requests from spotipy import Spotify, SpotifyException import voluptuous as vol @@ -20,13 +25,16 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( async_get_config_entry_implementation, ) from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from . import config_flow from .const import ( DATA_SPOTIFY_CLIENT, + DATA_SPOTIFY_DEVICES, DATA_SPOTIFY_ME, DATA_SPOTIFY_SESSION, DOMAIN, + LOGGER, MEDIA_PLAYER_PREFIX, SPOTIFY_SCOPES, ) @@ -112,9 +120,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SpotifyException as err: raise ConfigEntryNotReady from err + async def _update_devices() -> list[dict[str, Any]]: + try: + devices: dict[str, Any] | None = await hass.async_add_executor_job( + spotify.devices + ) + except (requests.RequestException, SpotifyException) as err: + raise UpdateFailed from err + + if devices is None: + return [] + + return devices.get("devices", []) + + device_coordinator: DataUpdateCoordinator[ + list[dict[str, Any]] + ] = DataUpdateCoordinator( + hass, + LOGGER, + name=f"{entry.title} Devices", + update_interval=timedelta(minutes=5), + update_method=_update_devices, + ) + await device_coordinator.async_config_entry_first_refresh() + hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { DATA_SPOTIFY_CLIENT: spotify, + DATA_SPOTIFY_DEVICES: device_coordinator, DATA_SPOTIFY_ME: current_user, DATA_SPOTIFY_SESSION: session, } diff --git a/homeassistant/components/spotify/const.py b/homeassistant/components/spotify/const.py index 7978ac8712f..0ed7cd2412e 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -1,8 +1,13 @@ """Define constants for the Spotify integration.""" +import logging + DOMAIN = "spotify" +LOGGER = logging.getLogger(__package__) + DATA_SPOTIFY_CLIENT = "spotify_client" +DATA_SPOTIFY_DEVICES = "spotify_devices" DATA_SPOTIFY_ME = "spotify_me" DATA_SPOTIFY_SESSION = "spotify_session" diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index e279b150883..b3bb2efd1c0 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -52,7 +52,7 @@ from homeassistant.const import ( STATE_PAUSED, STATE_PLAYING, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session from homeassistant.helpers.device_registry import DeviceEntryType @@ -62,6 +62,7 @@ from homeassistant.util.dt import utc_from_timestamp from .const import ( DATA_SPOTIFY_CLIENT, + DATA_SPOTIFY_DEVICES, DATA_SPOTIFY_ME, DATA_SPOTIFY_SESSION, DOMAIN, @@ -269,7 +270,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity): ) self._currently_playing: dict | None = {} - self._devices: list[dict] | None = [] self._playlist: dict | None = None self._attr_name = self._name @@ -290,6 +290,11 @@ class SpotifyMediaPlayer(MediaPlayerEntity): """Return spotify API.""" return self._spotify_data[DATA_SPOTIFY_CLIENT] + @property + def _devices(self) -> list: + """Return spotify devices.""" + return self._spotify_data[DATA_SPOTIFY_DEVICES].data + @property def device_info(self) -> DeviceInfo: """Return device information about this entity.""" @@ -517,13 +522,13 @@ class SpotifyMediaPlayer(MediaPlayerEntity): current = self._spotify.current_playback() self._currently_playing = current or {} - self._playlist = None context = self._currently_playing.get("context") - if context is not None and context["type"] == MEDIA_TYPE_PLAYLIST: - self._playlist = self._spotify.playlist(current["context"]["uri"]) - - devices = self._spotify.devices() or {} - self._devices = devices.get("devices", []) + if context is not None and ( + self._playlist is None or self._playlist["uri"] != context["uri"] + ): + self._playlist = None + if context["type"] == MEDIA_TYPE_PLAYLIST: + self._playlist = self._spotify.playlist(current["context"]["uri"]) async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" @@ -543,6 +548,22 @@ class SpotifyMediaPlayer(MediaPlayerEntity): media_content_id, ) + @callback + def _handle_devices_update(self) -> None: + """Handle updated data from the coordinator.""" + if not self.enabled: + return + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self.async_on_remove( + self._spotify_data[DATA_SPOTIFY_DEVICES].async_add_listener( + self._handle_devices_update + ) + ) + async def async_browse_media_internal( hass,