Support Sonos announcements using websockets (#91145)

This commit is contained in:
jjlawren
2023-04-14 01:08:53 -05:00
committed by GitHub
parent a061f56833
commit 27f3b53872
5 changed files with 51 additions and 18 deletions

View File

@ -8,7 +8,7 @@
"documentation": "https://www.home-assistant.io/integrations/sonos",
"iot_class": "local_push",
"loggers": ["soco"],
"requirements": ["soco==0.29.1"],
"requirements": ["soco==0.29.1", "sonos-websocket==0.0.5"],
"ssdp": [
{
"st": "urn:schemas-upnp-org:device:ZonePlayer:1"

View File

@ -1,8 +1,8 @@
"""Support to interface with Sonos players."""
from __future__ import annotations
from asyncio import run_coroutine_threadsafe
import datetime
from functools import partial
import logging
from typing import Any
@ -14,11 +14,13 @@ from soco.core import (
PLAY_MODES,
)
from soco.data_structures import DidlFavorite
from sonos_websocket.exception import SonosWebsocketError
import voluptuous as vol
from homeassistant.components import media_source, spotify
from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
ATTR_MEDIA_ANNOUNCE,
ATTR_MEDIA_ENQUEUE,
BrowseMedia,
MediaPlayerDeviceClass,
@ -491,8 +493,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
"""Clear players playlist."""
self.coordinator.soco.clear_queue()
@soco_error()
def play_media( # noqa: C901
async def async_play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None:
"""Send the play_media command to the media player.
@ -505,8 +506,21 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
If media_type is "playlist", media_id should be a Sonos
Playlist name. Otherwise, media_id should be a URI.
"""
# Use 'replace' as the default enqueue option
enqueue = kwargs.get(ATTR_MEDIA_ENQUEUE, MediaPlayerEnqueue.REPLACE)
if kwargs.get(ATTR_MEDIA_ANNOUNCE):
volume = kwargs.get("extra", {}).get("volume")
_LOGGER.debug("Playing %s using websocket audioclip", media_id)
try:
assert self.speaker.websocket
response, _ = await self.speaker.websocket.play_clip(
media_id,
volume=volume,
)
except SonosWebsocketError as exc:
raise HomeAssistantError(
f"Error when calling Sonos websocket: {exc}"
) from exc
if response["success"]:
return
if spotify.is_spotify_media_type(media_type):
media_type = spotify.resolve_spotify_media_type(media_type)
@ -517,16 +531,21 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
if media_source.is_media_source_id(media_id):
is_radio = media_id.startswith("media-source://radio_browser/")
media_type = MediaType.MUSIC
media_id = (
run_coroutine_threadsafe(
media_source.async_resolve_media(
self.hass, media_id, self.entity_id
),
self.hass.loop,
)
.result()
.url
media = await media_source.async_resolve_media(
self.hass, media_id, self.entity_id
)
media_id = media.url
await self.hass.async_add_executor_job(
partial(self._play_media, media_type, media_id, is_radio, **kwargs)
)
@soco_error()
def _play_media(
self, media_type: MediaType | str, media_id: str, is_radio: bool, **kwargs: Any
) -> None:
"""Wrap sync calls to async_play_media."""
enqueue = kwargs.get(ATTR_MEDIA_ENQUEUE, MediaPlayerEnqueue.REPLACE)
if media_type == "favorite_item_id":
favorite = self.speaker.favorites.lookup_by_item_id(media_id)

View File

@ -18,12 +18,14 @@ from soco.exceptions import SoCoException, SoCoUPnPException
from soco.plugins.plex import PlexPlugin
from soco.plugins.sharelink import ShareLinkPlugin
from soco.snapshot import Snapshot
from sonos_websocket import SonosWebsocket
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
@ -97,6 +99,7 @@ class SonosSpeaker:
"""Initialize a SonosSpeaker."""
self.hass = hass
self.soco = soco
self.websocket: SonosWebsocket | None = None
self.household_id: str = soco.household_id
self.media = SonosMedia(hass, soco)
self._plex_plugin: PlexPlugin | None = None
@ -170,8 +173,13 @@ class SonosSpeaker:
self.snapshot_group: list[SonosSpeaker] = []
self._group_members_missing: set[str] = set()
async def async_setup_dispatchers(self, entry: ConfigEntry) -> None:
"""Connect dispatchers in async context during setup."""
async def async_setup(self, entry: ConfigEntry) -> None:
"""Complete setup in async context."""
self.websocket = SonosWebsocket(
self.soco.ip_address,
player_id=self.soco.uid,
session=async_get_clientsession(self.hass),
)
dispatch_pairs: tuple[tuple[str, Callable[..., Any]], ...] = (
(SONOS_CHECK_ACTIVITY, self.async_check_activity),
(SONOS_SPEAKER_ADDED, self.update_group_for_uid),
@ -198,7 +206,7 @@ class SonosSpeaker:
self.media.poll_media()
future = asyncio.run_coroutine_threadsafe(
self.async_setup_dispatchers(entry), self.hass.loop
self.async_setup(entry), self.hass.loop
)
future.result(timeout=10)

View File

@ -2378,6 +2378,9 @@ solax==0.3.0
# homeassistant.components.somfy_mylink
somfy-mylink-synergy==1.0.6
# homeassistant.components.sonos
sonos-websocket==0.0.5
# homeassistant.components.marytts
speak2mary==1.4.0

View File

@ -1702,6 +1702,9 @@ solax==0.3.0
# homeassistant.components.somfy_mylink
somfy-mylink-synergy==1.0.6
# homeassistant.components.sonos
sonos-websocket==0.0.5
# homeassistant.components.marytts
speak2mary==1.4.0