mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Use DataUpdateCoordinator for Spotify devices (#66314)
This commit is contained in:
parent
57624347e6
commit
acf2033734
@ -1,9 +1,12 @@
|
|||||||
"""The spotify integration."""
|
"""The spotify integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import requests
|
||||||
from spotipy import Spotify, SpotifyException
|
from spotipy import Spotify, SpotifyException
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -22,10 +25,11 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
|||||||
async_get_config_entry_implementation,
|
async_get_config_entry_implementation,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from . import config_flow
|
from . import config_flow
|
||||||
from .browse_media import async_browse_media
|
from .browse_media import async_browse_media
|
||||||
from .const import DOMAIN, SPOTIFY_SCOPES
|
from .const import DOMAIN, LOGGER, SPOTIFY_SCOPES
|
||||||
from .util import is_spotify_media_type, resolve_spotify_media_type
|
from .util import is_spotify_media_type, resolve_spotify_media_type
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
@ -57,6 +61,7 @@ class HomeAssistantSpotifyData:
|
|||||||
|
|
||||||
client: Spotify
|
client: Spotify
|
||||||
current_user: dict[str, Any]
|
current_user: dict[str, Any]
|
||||||
|
devices: DataUpdateCoordinator
|
||||||
session: OAuth2Session
|
session: OAuth2Session
|
||||||
|
|
||||||
|
|
||||||
@ -101,10 +106,35 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
if not current_user:
|
if not current_user:
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
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.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = HomeAssistantSpotifyData(
|
hass.data[DOMAIN][entry.entry_id] = HomeAssistantSpotifyData(
|
||||||
client=spotify,
|
client=spotify,
|
||||||
current_user=current_user,
|
current_user=current_user,
|
||||||
|
devices=device_coordinator,
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
"""Define constants for the Spotify integration."""
|
"""Define constants for the Spotify integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_TYPE_ALBUM,
|
MEDIA_TYPE_ALBUM,
|
||||||
MEDIA_TYPE_ARTIST,
|
MEDIA_TYPE_ARTIST,
|
||||||
@ -9,6 +12,8 @@ from homeassistant.components.media_player.const import (
|
|||||||
|
|
||||||
DOMAIN = "spotify"
|
DOMAIN = "spotify"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
SPOTIFY_SCOPES = [
|
SPOTIFY_SCOPES = [
|
||||||
# Needed to be able to control playback
|
# Needed to be able to control playback
|
||||||
"user-modify-playback-state",
|
"user-modify-playback-state",
|
||||||
|
@ -33,7 +33,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
|
from homeassistant.const import CONF_ID, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
@ -147,7 +147,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
SPOTIFY_SCOPES
|
SPOTIFY_SCOPES
|
||||||
)
|
)
|
||||||
self._currently_playing: dict | None = {}
|
self._currently_playing: dict | None = {}
|
||||||
self._devices: list[dict] | None = []
|
|
||||||
self._playlist: dict | None = None
|
self._playlist: dict | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -258,9 +257,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
@property
|
@property
|
||||||
def source_list(self) -> list[str] | None:
|
def source_list(self) -> list[str] | None:
|
||||||
"""Return a list of source devices."""
|
"""Return a list of source devices."""
|
||||||
if not self._devices:
|
return [device["name"] for device in self.data.devices.data]
|
||||||
return None
|
|
||||||
return [device["name"] for device in self._devices]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shuffle(self) -> bool | None:
|
def shuffle(self) -> bool | None:
|
||||||
@ -332,19 +329,16 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
if (
|
if (
|
||||||
self._currently_playing
|
self._currently_playing
|
||||||
and not self._currently_playing.get("device")
|
and not self._currently_playing.get("device")
|
||||||
and self._devices
|
and self.data.devices.data
|
||||||
):
|
):
|
||||||
kwargs["device_id"] = self._devices[0].get("id")
|
kwargs["device_id"] = self.data.devices.data[0].get("id")
|
||||||
|
|
||||||
self.data.client.start_playback(**kwargs)
|
self.data.client.start_playback(**kwargs)
|
||||||
|
|
||||||
@spotify_exception_handler
|
@spotify_exception_handler
|
||||||
def select_source(self, source: str) -> None:
|
def select_source(self, source: str) -> None:
|
||||||
"""Select playback device."""
|
"""Select playback device."""
|
||||||
if not self._devices:
|
for device in self.data.devices.data:
|
||||||
return
|
|
||||||
|
|
||||||
for device in self._devices:
|
|
||||||
if device["name"] == source:
|
if device["name"] == source:
|
||||||
self.data.client.transfer_playback(
|
self.data.client.transfer_playback(
|
||||||
device["id"], self.state == STATE_PLAYING
|
device["id"], self.state == STATE_PLAYING
|
||||||
@ -386,9 +380,6 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
if context["type"] == MEDIA_TYPE_PLAYLIST:
|
if context["type"] == MEDIA_TYPE_PLAYLIST:
|
||||||
self._playlist = self.data.client.playlist(current["context"]["uri"])
|
self._playlist = self.data.client.playlist(current["context"]["uri"])
|
||||||
|
|
||||||
devices = self.data.client.devices() or {}
|
|
||||||
self._devices = devices.get("devices", [])
|
|
||||||
|
|
||||||
async def async_browse_media(
|
async def async_browse_media(
|
||||||
self, media_content_type: str | None = None, media_content_id: str | None = None
|
self, media_content_type: str | None = None, media_content_id: str | None = None
|
||||||
) -> BrowseMedia:
|
) -> BrowseMedia:
|
||||||
@ -408,3 +399,17 @@ class SpotifyMediaPlayer(MediaPlayerEntity):
|
|||||||
media_content_type,
|
media_content_type,
|
||||||
media_content_id,
|
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.data.devices.async_add_listener(self._handle_devices_update)
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user