2019-02-13 20:21:14 +00:00
|
|
|
"""Support to interface with Sonos players."""
|
2021-04-04 21:28:29 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-01-21 21:49:06 +00:00
|
|
|
from asyncio import run_coroutine_threadsafe
|
2015-09-11 22:32:47 +00:00
|
|
|
import datetime
|
2022-01-26 05:10:11 +00:00
|
|
|
import json
|
2016-02-19 05:27:50 +00:00
|
|
|
import logging
|
2021-05-11 17:36:40 +00:00
|
|
|
from typing import Any
|
2022-01-21 21:49:06 +00:00
|
|
|
from urllib.parse import quote
|
2017-04-06 06:24:30 +00:00
|
|
|
|
2021-07-22 22:40:30 +00:00
|
|
|
from soco import alarms
|
|
|
|
from soco.core import (
|
2021-01-30 14:38:43 +00:00
|
|
|
MUSIC_SRC_LINE_IN,
|
|
|
|
MUSIC_SRC_RADIO,
|
2020-11-23 09:20:06 +00:00
|
|
|
PLAY_MODE_BY_MEANING,
|
|
|
|
PLAY_MODES,
|
|
|
|
)
|
2022-01-14 17:28:25 +00:00
|
|
|
from soco.data_structures import DidlFavorite
|
2020-01-20 01:55:18 +00:00
|
|
|
import voluptuous as vol
|
2019-04-27 17:05:50 +00:00
|
|
|
|
2022-01-25 20:43:43 +00:00
|
|
|
from homeassistant.components import media_source, spotify
|
2022-01-21 21:49:06 +00:00
|
|
|
from homeassistant.components.http.auth import async_sign_path
|
2021-02-24 19:51:12 +00:00
|
|
|
from homeassistant.components.media_player import MediaPlayerEntity
|
2019-02-08 22:18:18 +00:00
|
|
|
from homeassistant.components.media_player.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_MEDIA_ENQUEUE,
|
2020-09-02 08:57:12 +00:00
|
|
|
MEDIA_TYPE_ALBUM,
|
|
|
|
MEDIA_TYPE_ARTIST,
|
2019-07-31 19:25:30 +00:00
|
|
|
MEDIA_TYPE_MUSIC,
|
2019-08-20 17:53:45 +00:00
|
|
|
MEDIA_TYPE_PLAYLIST,
|
2020-09-02 08:57:12 +00:00
|
|
|
MEDIA_TYPE_TRACK,
|
2020-10-12 22:30:38 +00:00
|
|
|
REPEAT_MODE_ALL,
|
|
|
|
REPEAT_MODE_OFF,
|
|
|
|
REPEAT_MODE_ONE,
|
2020-09-02 08:57:12 +00:00
|
|
|
SUPPORT_BROWSE_MEDIA,
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_CLEAR_PLAYLIST,
|
2022-01-05 15:22:36 +00:00
|
|
|
SUPPORT_GROUPING,
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_NEXT_TRACK,
|
|
|
|
SUPPORT_PAUSE,
|
|
|
|
SUPPORT_PLAY,
|
|
|
|
SUPPORT_PLAY_MEDIA,
|
|
|
|
SUPPORT_PREVIOUS_TRACK,
|
2020-10-12 22:30:38 +00:00
|
|
|
SUPPORT_REPEAT_SET,
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_SEEK,
|
|
|
|
SUPPORT_SELECT_SOURCE,
|
|
|
|
SUPPORT_SHUFFLE_SET,
|
|
|
|
SUPPORT_STOP,
|
|
|
|
SUPPORT_VOLUME_MUTE,
|
|
|
|
SUPPORT_VOLUME_SET,
|
|
|
|
)
|
2021-01-13 14:24:44 +00:00
|
|
|
from homeassistant.components.plex.const import PLEX_URI_SCHEME
|
2022-01-26 05:10:11 +00:00
|
|
|
from homeassistant.components.plex.services import lookup_plex_media
|
2021-04-04 21:28:29 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
2021-04-25 17:20:21 +00:00
|
|
|
from homeassistant.const import ATTR_TIME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
|
2021-04-04 21:28:29 +00:00
|
|
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
2022-01-05 15:22:36 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2020-01-20 01:55:18 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, entity_platform, service
|
2021-05-11 17:36:40 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
2021-04-29 10:28:14 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
2022-01-21 21:49:06 +00:00
|
|
|
from homeassistant.helpers.network import get_url
|
2015-09-13 04:09:51 +00:00
|
|
|
|
2022-01-14 17:28:25 +00:00
|
|
|
from . import media_browser
|
2020-09-02 08:57:12 +00:00
|
|
|
from .const import (
|
2021-06-16 15:30:05 +00:00
|
|
|
DATA_SONOS,
|
2019-10-19 21:52:42 +00:00
|
|
|
DOMAIN as SONOS_DOMAIN,
|
2021-02-24 19:51:12 +00:00
|
|
|
MEDIA_TYPES_TO_SONOS,
|
|
|
|
PLAYABLE_MEDIA_TYPES,
|
2021-04-27 14:52:05 +00:00
|
|
|
SONOS_CREATE_MEDIA_PLAYER,
|
2021-05-11 21:06:51 +00:00
|
|
|
SONOS_STATE_PLAYING,
|
|
|
|
SONOS_STATE_TRANSITIONING,
|
2021-05-11 17:36:40 +00:00
|
|
|
SOURCE_LINEIN,
|
|
|
|
SOURCE_TV,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2021-04-25 17:20:21 +00:00
|
|
|
from .entity import SonosEntity
|
2021-05-11 17:36:40 +00:00
|
|
|
from .helpers import soco_error
|
|
|
|
from .speaker import SonosMedia, SonosSpeaker
|
2016-11-01 17:42:38 +00:00
|
|
|
|
2015-09-11 22:32:47 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SUPPORT_SONOS = (
|
2020-09-02 08:57:12 +00:00
|
|
|
SUPPORT_BROWSE_MEDIA
|
|
|
|
| SUPPORT_CLEAR_PLAYLIST
|
2022-01-05 15:22:36 +00:00
|
|
|
| SUPPORT_GROUPING
|
2020-09-02 08:57:12 +00:00
|
|
|
| SUPPORT_NEXT_TRACK
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_PAUSE
|
2020-09-02 08:57:12 +00:00
|
|
|
| SUPPORT_PLAY
|
|
|
|
| SUPPORT_PLAY_MEDIA
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_PREVIOUS_TRACK
|
2020-10-12 22:30:38 +00:00
|
|
|
| SUPPORT_REPEAT_SET
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_SEEK
|
2020-09-02 08:57:12 +00:00
|
|
|
| SUPPORT_SELECT_SOURCE
|
2019-07-31 19:25:30 +00:00
|
|
|
| SUPPORT_SHUFFLE_SET
|
2020-09-02 08:57:12 +00:00
|
|
|
| SUPPORT_STOP
|
|
|
|
| SUPPORT_VOLUME_MUTE
|
|
|
|
| SUPPORT_VOLUME_SET
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
VOLUME_INCREMENT = 2
|
2016-07-15 16:00:41 +00:00
|
|
|
|
2020-10-17 07:40:43 +00:00
|
|
|
REPEAT_TO_SONOS = {
|
|
|
|
REPEAT_MODE_OFF: False,
|
|
|
|
REPEAT_MODE_ALL: True,
|
|
|
|
REPEAT_MODE_ONE: "ONE",
|
|
|
|
}
|
|
|
|
|
|
|
|
SONOS_TO_REPEAT = {meaning: mode for mode, meaning in REPEAT_TO_SONOS.items()}
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_SONOS_GROUP = "sonos_group"
|
2017-01-25 21:03:36 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
UPNP_ERRORS_TO_IGNORE = ["701", "711", "712"]
|
2017-06-02 07:03:10 +00:00
|
|
|
|
2020-01-20 01:55:18 +00:00
|
|
|
SERVICE_JOIN = "join"
|
|
|
|
SERVICE_UNJOIN = "unjoin"
|
|
|
|
SERVICE_SNAPSHOT = "snapshot"
|
|
|
|
SERVICE_RESTORE = "restore"
|
|
|
|
SERVICE_SET_TIMER = "set_sleep_timer"
|
|
|
|
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
|
|
|
|
SERVICE_UPDATE_ALARM = "update_alarm"
|
|
|
|
SERVICE_PLAY_QUEUE = "play_queue"
|
2020-06-08 14:37:35 +00:00
|
|
|
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
2020-01-20 01:55:18 +00:00
|
|
|
|
|
|
|
ATTR_SLEEP_TIME = "sleep_time"
|
|
|
|
ATTR_ALARM_ID = "alarm_id"
|
|
|
|
ATTR_VOLUME = "volume"
|
|
|
|
ATTR_ENABLED = "enabled"
|
|
|
|
ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
|
|
|
|
ATTR_MASTER = "master"
|
|
|
|
ATTR_WITH_GROUP = "with_group"
|
|
|
|
ATTR_QUEUE_POSITION = "queue_position"
|
|
|
|
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-04-25 17:20:21 +00:00
|
|
|
async def async_setup_entry(
|
2021-04-29 10:28:14 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
config_entry: ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
2021-04-04 21:28:29 +00:00
|
|
|
) -> None:
|
2018-06-14 19:17:54 +00:00
|
|
|
"""Set up Sonos from a config entry."""
|
2021-05-03 16:34:28 +00:00
|
|
|
platform = entity_platform.async_get_current_platform()
|
2020-01-20 01:55:18 +00:00
|
|
|
|
2021-04-25 17:20:21 +00:00
|
|
|
@callback
|
|
|
|
def async_create_entities(speaker: SonosSpeaker) -> None:
|
|
|
|
"""Handle device discovery and create entities."""
|
2021-12-22 04:36:12 +00:00
|
|
|
_LOGGER.debug("Creating media_player on %s", speaker.zone_name)
|
2021-04-30 05:01:09 +00:00
|
|
|
async_add_entities([SonosMediaPlayerEntity(speaker)])
|
2021-04-25 17:20:21 +00:00
|
|
|
|
2020-02-05 23:50:20 +00:00
|
|
|
@service.verify_domain_control(hass, SONOS_DOMAIN)
|
2021-04-04 21:28:29 +00:00
|
|
|
async def async_service_handle(service_call: ServiceCall) -> None:
|
2019-05-05 19:25:57 +00:00
|
|
|
"""Handle dispatched services."""
|
2021-04-04 21:28:29 +00:00
|
|
|
assert platform is not None
|
2020-01-20 01:55:18 +00:00
|
|
|
entities = await platform.async_extract_from_service(service_call)
|
|
|
|
|
|
|
|
if not entities:
|
|
|
|
return
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
speakers = []
|
2021-04-04 21:28:29 +00:00
|
|
|
for entity in entities:
|
2021-04-25 17:20:21 +00:00
|
|
|
assert isinstance(entity, SonosMediaPlayerEntity)
|
2021-05-11 17:36:40 +00:00
|
|
|
speakers.append(entity.speaker)
|
2021-04-04 21:28:29 +00:00
|
|
|
|
2020-01-20 01:55:18 +00:00
|
|
|
if service_call.service == SERVICE_JOIN:
|
|
|
|
master = platform.entities.get(service_call.data[ATTR_MASTER])
|
2019-03-13 09:17:09 +00:00
|
|
|
if master:
|
2021-05-11 17:36:40 +00:00
|
|
|
await SonosSpeaker.join_multi(hass, master.speaker, speakers) # type: ignore[arg-type]
|
2020-01-20 01:55:18 +00:00
|
|
|
else:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Invalid master specified for join service: %s",
|
|
|
|
service_call.data[ATTR_MASTER],
|
|
|
|
)
|
|
|
|
elif service_call.service == SERVICE_UNJOIN:
|
2021-05-11 17:36:40 +00:00
|
|
|
await SonosSpeaker.unjoin_multi(hass, speakers) # type: ignore[arg-type]
|
2020-01-20 01:55:18 +00:00
|
|
|
elif service_call.service == SERVICE_SNAPSHOT:
|
2021-05-11 17:36:40 +00:00
|
|
|
await SonosSpeaker.snapshot_multi(
|
|
|
|
hass, speakers, service_call.data[ATTR_WITH_GROUP] # type: ignore[arg-type]
|
2020-01-20 01:55:18 +00:00
|
|
|
)
|
|
|
|
elif service_call.service == SERVICE_RESTORE:
|
2021-05-11 17:36:40 +00:00
|
|
|
await SonosSpeaker.restore_multi(
|
|
|
|
hass, speakers, service_call.data[ATTR_WITH_GROUP] # type: ignore[arg-type]
|
2020-01-20 01:55:18 +00:00
|
|
|
)
|
|
|
|
|
2021-04-27 14:52:05 +00:00
|
|
|
config_entry.async_on_unload(
|
|
|
|
async_dispatcher_connect(hass, SONOS_CREATE_MEDIA_PLAYER, async_create_entities)
|
|
|
|
)
|
2021-04-25 17:20:21 +00:00
|
|
|
|
2020-02-05 23:50:20 +00:00
|
|
|
hass.services.async_register(
|
2020-01-20 01:55:18 +00:00
|
|
|
SONOS_DOMAIN,
|
|
|
|
SERVICE_JOIN,
|
|
|
|
async_service_handle,
|
|
|
|
cv.make_entity_service_schema({vol.Required(ATTR_MASTER): cv.entity_id}),
|
|
|
|
)
|
|
|
|
|
2020-02-05 23:50:20 +00:00
|
|
|
hass.services.async_register(
|
2020-01-20 01:55:18 +00:00
|
|
|
SONOS_DOMAIN,
|
|
|
|
SERVICE_UNJOIN,
|
|
|
|
async_service_handle,
|
|
|
|
cv.make_entity_service_schema({}),
|
|
|
|
)
|
|
|
|
|
|
|
|
join_unjoin_schema = cv.make_entity_service_schema(
|
|
|
|
{vol.Optional(ATTR_WITH_GROUP, default=True): cv.boolean}
|
|
|
|
)
|
|
|
|
|
2020-02-05 23:50:20 +00:00
|
|
|
hass.services.async_register(
|
|
|
|
SONOS_DOMAIN, SERVICE_SNAPSHOT, async_service_handle, join_unjoin_schema
|
2020-01-20 01:55:18 +00:00
|
|
|
)
|
|
|
|
|
2020-02-05 23:50:20 +00:00
|
|
|
hass.services.async_register(
|
|
|
|
SONOS_DOMAIN, SERVICE_RESTORE, async_service_handle, join_unjoin_schema
|
2020-01-20 01:55:18 +00:00
|
|
|
)
|
2016-02-26 19:05:34 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
platform.async_register_entity_service( # type: ignore
|
2020-01-20 01:55:18 +00:00
|
|
|
SERVICE_SET_TIMER,
|
|
|
|
{
|
|
|
|
vol.Required(ATTR_SLEEP_TIME): vol.All(
|
|
|
|
vol.Coerce(int), vol.Range(min=0, max=86399)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
"set_sleep_timer",
|
|
|
|
)
|
2017-05-15 07:42:45 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
platform.async_register_entity_service(SERVICE_CLEAR_TIMER, {}, "clear_sleep_timer") # type: ignore
|
2020-01-20 01:55:18 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
platform.async_register_entity_service( # type: ignore
|
2020-01-20 01:55:18 +00:00
|
|
|
SERVICE_UPDATE_ALARM,
|
|
|
|
{
|
|
|
|
vol.Required(ATTR_ALARM_ID): cv.positive_int,
|
|
|
|
vol.Optional(ATTR_TIME): cv.time,
|
2021-08-27 16:25:40 +00:00
|
|
|
vol.Optional(ATTR_VOLUME): cv.small_float,
|
2020-01-20 01:55:18 +00:00
|
|
|
vol.Optional(ATTR_ENABLED): cv.boolean,
|
|
|
|
vol.Optional(ATTR_INCLUDE_LINKED_ZONES): cv.boolean,
|
|
|
|
},
|
|
|
|
"set_alarm",
|
|
|
|
)
|
2019-05-10 20:37:03 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
platform.async_register_entity_service( # type: ignore
|
2020-01-20 01:55:18 +00:00
|
|
|
SERVICE_PLAY_QUEUE,
|
|
|
|
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
|
|
|
|
"play_queue",
|
|
|
|
)
|
2017-12-17 12:08:35 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
platform.async_register_entity_service( # type: ignore
|
2020-06-08 14:37:35 +00:00
|
|
|
SERVICE_REMOVE_FROM_QUEUE,
|
|
|
|
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
|
|
|
|
"remove_from_queue",
|
|
|
|
)
|
|
|
|
|
2016-02-26 19:05:34 +00:00
|
|
|
|
2021-04-25 17:20:21 +00:00
|
|
|
class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
2019-02-24 17:45:08 +00:00
|
|
|
"""Representation of a Sonos entity."""
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-12-03 18:57:19 +00:00
|
|
|
_attr_supported_features = SUPPORT_SONOS
|
|
|
|
_attr_media_content_type = MEDIA_TYPE_MUSIC
|
|
|
|
|
|
|
|
def __init__(self, speaker: SonosSpeaker) -> None:
|
|
|
|
"""Initialize the media player entity."""
|
|
|
|
super().__init__(speaker)
|
|
|
|
self._attr_unique_id = self.soco.uid
|
|
|
|
self._attr_name = self.speaker.zone_name
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
@property
|
|
|
|
def coordinator(self) -> SonosSpeaker:
|
|
|
|
"""Return the current coordinator SonosSpeaker."""
|
|
|
|
return self.speaker.coordinator or self.speaker
|
2019-09-03 12:14:33 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
def __hash__(self) -> int:
|
2019-02-25 21:03:15 +00:00
|
|
|
"""Return a hash of self."""
|
|
|
|
return hash(self.unique_id)
|
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def state(self) -> str:
|
2019-02-24 17:45:08 +00:00
|
|
|
"""Return the state of the entity."""
|
2021-05-11 17:36:40 +00:00
|
|
|
if self.media.playback_status in (
|
2020-08-27 11:56:20 +00:00
|
|
|
"PAUSED_PLAYBACK",
|
|
|
|
"STOPPED",
|
|
|
|
):
|
2020-03-12 21:47:57 +00:00
|
|
|
# Sonos can consider itself "paused" but without having media loaded
|
|
|
|
# (happens if playing Spotify and via Spotify app you pick another device to play on)
|
2021-05-11 17:36:40 +00:00
|
|
|
if self.media.title is None:
|
2020-03-12 21:47:57 +00:00
|
|
|
return STATE_IDLE
|
2015-09-11 22:32:47 +00:00
|
|
|
return STATE_PAUSED
|
2021-05-11 21:06:51 +00:00
|
|
|
if self.media.playback_status in (
|
|
|
|
SONOS_STATE_PLAYING,
|
|
|
|
SONOS_STATE_TRANSITIONING,
|
|
|
|
):
|
2015-09-11 22:32:47 +00:00
|
|
|
return STATE_PLAYING
|
2016-11-01 17:42:38 +00:00
|
|
|
return STATE_IDLE
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-10-23 21:11:27 +00:00
|
|
|
async def _async_poll(self) -> None:
|
2021-06-03 04:10:27 +00:00
|
|
|
"""Retrieve latest state by polling."""
|
2021-06-16 15:30:05 +00:00
|
|
|
await self.hass.data[DATA_SONOS].favorites[
|
|
|
|
self.speaker.household_id
|
|
|
|
].async_poll()
|
2021-05-28 10:07:58 +00:00
|
|
|
await self.hass.async_add_executor_job(self._update)
|
2019-06-19 08:09:50 +00:00
|
|
|
|
2021-05-28 10:07:58 +00:00
|
|
|
def _update(self) -> None:
|
2021-06-03 04:10:27 +00:00
|
|
|
"""Retrieve latest state by polling."""
|
|
|
|
self.speaker.update_groups()
|
|
|
|
self.speaker.update_volume()
|
|
|
|
if self.speaker.is_coordinator:
|
|
|
|
self.speaker.update_media()
|
2018-03-21 02:27:07 +00:00
|
|
|
|
2015-09-11 22:32:47 +00:00
|
|
|
@property
|
2021-04-05 03:26:55 +00:00
|
|
|
def volume_level(self) -> float | None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Volume level of the media player (0..1)."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.speaker.volume and self.speaker.volume / 100
|
2015-09-11 22:32:47 +00:00
|
|
|
|
|
|
|
@property
|
2021-04-04 21:28:29 +00:00
|
|
|
def is_volume_muted(self) -> bool | None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return true if volume is muted."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.speaker.muted
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def shuffle(self) -> str | None:
|
2017-12-07 19:44:06 +00:00
|
|
|
"""Shuffling state."""
|
2021-05-11 17:36:40 +00:00
|
|
|
shuffle: str = PLAY_MODES[self.media.play_mode][0]
|
2021-04-04 21:28:29 +00:00
|
|
|
return shuffle
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def repeat(self) -> str | None:
|
2020-10-12 22:30:38 +00:00
|
|
|
"""Return current repeat mode."""
|
2021-05-11 17:36:40 +00:00
|
|
|
sonos_repeat = PLAY_MODES[self.media.play_mode][1]
|
2020-10-17 07:40:43 +00:00
|
|
|
return SONOS_TO_REPEAT[sonos_repeat]
|
2020-10-12 22:30:38 +00:00
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
@property
|
|
|
|
def media(self) -> SonosMedia:
|
|
|
|
"""Return the SonosMedia object from the coordinator speaker."""
|
|
|
|
return self.coordinator.media
|
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_content_id(self) -> str | None:
|
2020-03-10 07:17:07 +00:00
|
|
|
"""Content id of current playing media."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.uri
|
2020-03-10 07:17:07 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_duration(self) -> float | None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Duration of current playing media in seconds."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.duration
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_position(self) -> float | None:
|
2016-11-28 01:45:49 +00:00
|
|
|
"""Position of current playing media in seconds."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.position
|
2016-11-28 01:45:49 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_position_updated_at(self) -> datetime.datetime | None:
|
2018-02-18 19:05:20 +00:00
|
|
|
"""When was the position of the current playing media valid."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.position_updated_at
|
2016-11-28 01:45:49 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_image_url(self) -> str | None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Image url of current playing media."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.image_url or None
|
2016-10-25 22:37:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_channel(self) -> str | None:
|
2020-09-03 21:47:32 +00:00
|
|
|
"""Channel currently playing."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.channel or None
|
2020-09-03 21:47:32 +00:00
|
|
|
|
2021-06-13 22:24:46 +00:00
|
|
|
@property
|
|
|
|
def media_playlist(self) -> str | None:
|
|
|
|
"""Title of playlist currently playing."""
|
|
|
|
return self.media.playlist_name
|
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_artist(self) -> str | None:
|
2016-10-25 22:37:47 +00:00
|
|
|
"""Artist of current playing media, music track only."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.artist or None
|
2016-10-25 22:37:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_album_name(self) -> str | None:
|
2016-10-25 22:37:47 +00:00
|
|
|
"""Album name of current playing media, music track only."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.album_name or None
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def media_title(self) -> str | None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Title of current playing media."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.title or None
|
2020-06-08 14:37:35 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def source(self) -> str | None:
|
2018-02-18 19:05:20 +00:00
|
|
|
"""Name of the current input source."""
|
2021-05-11 17:36:40 +00:00
|
|
|
return self.media.source_name or None
|
2017-12-17 12:08:35 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def volume_up(self) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Volume up media player."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.soco.volume += VOLUME_INCREMENT
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def volume_down(self) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Volume down media player."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.soco.volume -= VOLUME_INCREMENT
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def set_volume_level(self, volume: str) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Set volume level, range 0..1."""
|
2018-02-18 19:05:20 +00:00
|
|
|
self.soco.volume = str(int(volume * 100))
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-10-01 15:58:04 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def set_shuffle(self, shuffle: str) -> None:
|
2017-12-07 19:44:06 +00:00
|
|
|
"""Enable/Disable shuffle mode."""
|
2020-10-17 07:40:43 +00:00
|
|
|
sonos_shuffle = shuffle
|
2021-05-11 17:36:40 +00:00
|
|
|
sonos_repeat = PLAY_MODES[self.media.play_mode][1]
|
|
|
|
self.coordinator.soco.play_mode = PLAY_MODE_BY_MEANING[
|
|
|
|
(sonos_shuffle, sonos_repeat)
|
|
|
|
]
|
2017-12-07 19:44:06 +00:00
|
|
|
|
2020-10-12 22:30:38 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def set_repeat(self, repeat: str) -> None:
|
2020-10-12 22:30:38 +00:00
|
|
|
"""Set repeat mode."""
|
2021-05-11 17:36:40 +00:00
|
|
|
sonos_shuffle = PLAY_MODES[self.media.play_mode][0]
|
2020-10-17 07:40:43 +00:00
|
|
|
sonos_repeat = REPEAT_TO_SONOS[repeat]
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.play_mode = PLAY_MODE_BY_MEANING[
|
|
|
|
(sonos_shuffle, sonos_repeat)
|
|
|
|
]
|
2020-10-12 22:30:38 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def mute_volume(self, mute: bool) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Mute (true) or unmute (false) media player."""
|
2018-02-18 19:05:20 +00:00
|
|
|
self.soco.mute = mute
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def select_source(self, source: str) -> None:
|
2016-07-15 16:00:41 +00:00
|
|
|
"""Select input source."""
|
2021-05-11 17:36:40 +00:00
|
|
|
soco = self.coordinator.soco
|
2018-02-18 19:05:20 +00:00
|
|
|
if source == SOURCE_LINEIN:
|
2021-05-11 17:36:40 +00:00
|
|
|
soco.switch_to_line_in()
|
2022-01-14 17:28:25 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if source == SOURCE_TV:
|
2021-05-11 17:36:40 +00:00
|
|
|
soco.switch_to_tv()
|
2022-01-14 17:28:25 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self._play_favorite_by_name(source)
|
|
|
|
|
|
|
|
def _play_favorite_by_name(self, name: str) -> None:
|
|
|
|
"""Play a favorite by name."""
|
|
|
|
fav = [fav for fav in self.speaker.favorites if fav.title == name]
|
|
|
|
|
|
|
|
if len(fav) != 1:
|
|
|
|
return
|
|
|
|
|
|
|
|
src = fav.pop()
|
|
|
|
self._play_favorite(src)
|
|
|
|
|
|
|
|
def _play_favorite(self, favorite: DidlFavorite) -> None:
|
|
|
|
"""Play a favorite."""
|
|
|
|
uri = favorite.reference.get_uri()
|
|
|
|
soco = self.coordinator.soco
|
|
|
|
if soco.music_source_from_uri(uri) in [
|
|
|
|
MUSIC_SRC_RADIO,
|
|
|
|
MUSIC_SRC_LINE_IN,
|
|
|
|
]:
|
|
|
|
soco.play_uri(uri, title=favorite.title)
|
2016-12-14 18:05:03 +00:00
|
|
|
else:
|
2022-01-14 17:28:25 +00:00
|
|
|
soco.clear_queue()
|
|
|
|
soco.add_to_queue(favorite.reference)
|
|
|
|
soco.play_from_queue(0)
|
2016-07-15 16:00:41 +00:00
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
@property # type: ignore[misc]
|
|
|
|
def source_list(self) -> list[str]:
|
2016-07-15 16:00:41 +00:00
|
|
|
"""List of available input sources."""
|
2021-05-16 09:11:35 +00:00
|
|
|
sources = [fav.title for fav in self.speaker.favorites]
|
2017-02-07 08:27:55 +00:00
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
model = self.coordinator.model_name.upper()
|
2019-07-31 19:25:30 +00:00
|
|
|
if "PLAY:5" in model or "CONNECT" in model:
|
2018-02-18 19:05:20 +00:00
|
|
|
sources += [SOURCE_LINEIN]
|
2019-07-31 19:25:30 +00:00
|
|
|
elif "PLAYBAR" in model:
|
2018-02-18 19:05:20 +00:00
|
|
|
sources += [SOURCE_LINEIN, SOURCE_TV]
|
2020-06-08 14:37:35 +00:00
|
|
|
elif "BEAM" in model or "PLAYBASE" in model:
|
2018-09-01 14:02:38 +00:00
|
|
|
sources += [SOURCE_TV]
|
2016-07-15 16:00:41 +00:00
|
|
|
|
2016-12-14 18:05:03 +00:00
|
|
|
return sources
|
2016-07-15 16:00:41 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_play(self) -> None:
|
2016-03-26 05:57:28 +00:00
|
|
|
"""Send play command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.play()
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_stop(self) -> None:
|
2016-12-08 08:36:37 +00:00
|
|
|
"""Send stop command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.stop()
|
2016-12-08 08:36:37 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_pause(self) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send pause command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.pause()
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-03-05 02:30:15 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_next_track(self) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send next track command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.next()
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-03-05 02:30:15 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_previous_track(self) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send next track command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.previous()
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-03-05 02:30:15 +00:00
|
|
|
@soco_error(UPNP_ERRORS_TO_IGNORE)
|
2021-04-04 21:28:29 +00:00
|
|
|
def media_seek(self, position: str) -> None:
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send seek command."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.seek(str(datetime.timedelta(seconds=int(position))))
|
2015-09-11 22:32:47 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def clear_playlist(self) -> None:
|
2016-07-15 16:00:41 +00:00
|
|
|
"""Clear players playlist."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.clear_queue()
|
2016-03-26 05:57:14 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def play_media(self, media_type: str, media_id: str, **kwargs: Any) -> None:
|
2016-05-20 06:30:19 +00:00
|
|
|
"""
|
|
|
|
Send the play_media command to the media player.
|
|
|
|
|
2021-01-13 14:24:44 +00:00
|
|
|
If media_id is a Plex payload, attempt Plex->Sonos playback.
|
|
|
|
|
2022-01-24 13:54:27 +00:00
|
|
|
If media_id is an Apple Music, Deezer, Sonos, or Tidal share link,
|
|
|
|
attempt playback using the respective service.
|
2021-06-17 09:09:57 +00:00
|
|
|
|
2019-08-20 17:53:45 +00:00
|
|
|
If media_type is "playlist", media_id should be a Sonos
|
|
|
|
Playlist name. Otherwise, media_id should be a URI.
|
|
|
|
|
2016-05-20 06:30:19 +00:00
|
|
|
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
|
|
|
|
"""
|
2022-01-25 20:43:43 +00:00
|
|
|
if spotify.is_spotify_media_type(media_type):
|
|
|
|
media_type = spotify.resolve_spotify_media_type(media_type)
|
|
|
|
|
2022-01-21 21:49:06 +00:00
|
|
|
if media_source.is_media_source_id(media_id):
|
|
|
|
media_type = MEDIA_TYPE_MUSIC
|
|
|
|
media_id = (
|
|
|
|
run_coroutine_threadsafe(
|
|
|
|
media_source.async_resolve_media(self.hass, media_id),
|
|
|
|
self.hass.loop,
|
|
|
|
)
|
|
|
|
.result()
|
|
|
|
.url
|
|
|
|
)
|
|
|
|
|
2022-01-14 17:28:25 +00:00
|
|
|
if media_type == "favorite_item_id":
|
|
|
|
favorite = self.speaker.favorites.lookup_by_item_id(media_id)
|
|
|
|
if favorite is None:
|
|
|
|
raise ValueError(f"Missing favorite for media_id: {media_id}")
|
|
|
|
self._play_favorite(favorite)
|
|
|
|
return
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
soco = self.coordinator.soco
|
2021-01-13 14:24:44 +00:00
|
|
|
if media_id and media_id.startswith(PLEX_URI_SCHEME):
|
2022-01-26 05:10:11 +00:00
|
|
|
plex_plugin = self.speaker.plex_plugin
|
2021-01-13 14:24:44 +00:00
|
|
|
media_id = media_id[len(PLEX_URI_SCHEME) :]
|
2022-01-26 05:10:11 +00:00
|
|
|
payload = json.loads(media_id)
|
|
|
|
shuffle = payload.pop("shuffle", None)
|
|
|
|
media = lookup_plex_media(self.hass, media_type, json.dumps(payload))
|
|
|
|
if not kwargs.get(ATTR_MEDIA_ENQUEUE):
|
|
|
|
soco.clear_queue()
|
|
|
|
if shuffle:
|
|
|
|
self.set_shuffle(True)
|
|
|
|
plex_plugin.play_now(media)
|
2021-06-17 09:09:57 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
share_link = self.speaker.share_link
|
|
|
|
if share_link.is_share_link(media_id):
|
|
|
|
if kwargs.get(ATTR_MEDIA_ENQUEUE):
|
|
|
|
share_link.add_share_link_to_queue(media_id)
|
|
|
|
else:
|
|
|
|
soco.clear_queue()
|
|
|
|
share_link.add_share_link_to_queue(media_id)
|
|
|
|
soco.play_from_queue(0)
|
2021-01-13 14:24:44 +00:00
|
|
|
elif media_type in (MEDIA_TYPE_MUSIC, MEDIA_TYPE_TRACK):
|
2022-01-21 21:49:06 +00:00
|
|
|
# If media ID is a relative URL, we serve it from HA.
|
|
|
|
# Create a signed path.
|
|
|
|
if media_id[0] == "/":
|
|
|
|
media_id = async_sign_path(
|
|
|
|
self.hass,
|
|
|
|
quote(media_id),
|
2022-01-26 05:10:11 +00:00
|
|
|
datetime.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
|
2022-01-21 21:49:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# prepend external URL
|
|
|
|
hass_url = get_url(self.hass, prefer_external=True)
|
|
|
|
media_id = f"{hass_url}{media_id}"
|
|
|
|
|
2019-08-20 17:53:45 +00:00
|
|
|
if kwargs.get(ATTR_MEDIA_ENQUEUE):
|
2021-06-17 09:09:57 +00:00
|
|
|
soco.add_uri_to_queue(media_id)
|
2019-08-20 17:53:45 +00:00
|
|
|
else:
|
2021-06-17 09:09:57 +00:00
|
|
|
soco.play_uri(media_id)
|
2019-08-20 17:53:45 +00:00
|
|
|
elif media_type == MEDIA_TYPE_PLAYLIST:
|
2020-09-02 08:57:12 +00:00
|
|
|
if media_id.startswith("S:"):
|
2022-01-14 17:28:25 +00:00
|
|
|
item = media_browser.get_media(self.media.library, media_id, media_type) # type: ignore[no-untyped-call]
|
2021-05-11 17:36:40 +00:00
|
|
|
soco.play_uri(item.get_uri())
|
2020-09-02 08:57:12 +00:00
|
|
|
return
|
2017-04-06 06:24:30 +00:00
|
|
|
try:
|
2021-05-11 17:36:40 +00:00
|
|
|
playlists = soco.get_sonos_playlists()
|
2019-08-20 17:53:45 +00:00
|
|
|
playlist = next(p for p in playlists if p.title == media_id)
|
2021-06-17 09:09:57 +00:00
|
|
|
except StopIteration:
|
|
|
|
_LOGGER.error('Could not find a Sonos playlist named "%s"', media_id)
|
|
|
|
else:
|
2021-05-11 17:36:40 +00:00
|
|
|
soco.clear_queue()
|
|
|
|
soco.add_to_queue(playlist)
|
|
|
|
soco.play_from_queue(0)
|
2020-09-02 08:57:12 +00:00
|
|
|
elif media_type in PLAYABLE_MEDIA_TYPES:
|
2022-01-14 17:28:25 +00:00
|
|
|
item = media_browser.get_media(self.media.library, media_id, media_type) # type: ignore[no-untyped-call]
|
2020-09-02 08:57:12 +00:00
|
|
|
|
|
|
|
if not item:
|
|
|
|
_LOGGER.error('Could not find "%s" in the library', media_id)
|
|
|
|
return
|
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
soco.play_uri(item.get_uri())
|
2016-05-20 06:30:19 +00:00
|
|
|
else:
|
2019-08-20 17:53:45 +00:00
|
|
|
_LOGGER.error('Sonos does not support a media type of "%s"', media_type)
|
2016-04-17 20:17:13 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def set_sleep_timer(self, sleep_time: int) -> None:
|
2016-10-26 06:22:17 +00:00
|
|
|
"""Set the timer on the player."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.set_sleep_timer(sleep_time)
|
2016-10-26 06:22:17 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def clear_sleep_timer(self) -> None:
|
2016-10-26 06:22:17 +00:00
|
|
|
"""Clear the timer on the player."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.set_sleep_timer(None)
|
2017-01-25 21:03:36 +00:00
|
|
|
|
2018-01-13 08:59:50 +00:00
|
|
|
@soco_error()
|
2020-01-20 01:55:18 +00:00
|
|
|
def set_alarm(
|
2021-04-04 21:28:29 +00:00
|
|
|
self,
|
|
|
|
alarm_id: int,
|
|
|
|
time: datetime.datetime | None = None,
|
|
|
|
volume: float | None = None,
|
|
|
|
enabled: bool | None = None,
|
|
|
|
include_linked_zones: bool | None = None,
|
|
|
|
) -> None:
|
2017-05-15 07:42:45 +00:00
|
|
|
"""Set the alarm clock on the player."""
|
2018-02-18 19:05:20 +00:00
|
|
|
alarm = None
|
2021-05-11 17:36:40 +00:00
|
|
|
for one_alarm in alarms.get_alarms(self.coordinator.soco):
|
2021-05-25 16:32:25 +00:00
|
|
|
if one_alarm.alarm_id == str(alarm_id):
|
2018-02-18 19:05:20 +00:00
|
|
|
alarm = one_alarm
|
|
|
|
if alarm is None:
|
2021-03-19 14:26:36 +00:00
|
|
|
_LOGGER.warning("Did not find alarm with id %s", alarm_id)
|
2017-05-15 07:42:45 +00:00
|
|
|
return
|
2020-01-20 01:55:18 +00:00
|
|
|
if time is not None:
|
|
|
|
alarm.start_time = time
|
|
|
|
if volume is not None:
|
|
|
|
alarm.volume = int(volume * 100)
|
|
|
|
if enabled is not None:
|
|
|
|
alarm.enabled = enabled
|
|
|
|
if include_linked_zones is not None:
|
|
|
|
alarm.include_linked_zones = include_linked_zones
|
2018-02-18 19:05:20 +00:00
|
|
|
alarm.save()
|
2017-05-15 07:42:45 +00:00
|
|
|
|
2019-07-06 15:19:03 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def play_queue(self, queue_position: int = 0) -> None:
|
2019-07-06 15:19:03 +00:00
|
|
|
"""Start playing the queue."""
|
2020-01-20 01:55:18 +00:00
|
|
|
self.soco.play_from_queue(queue_position)
|
2019-07-06 15:19:03 +00:00
|
|
|
|
2020-06-08 14:37:35 +00:00
|
|
|
@soco_error()
|
2021-04-04 21:28:29 +00:00
|
|
|
def remove_from_queue(self, queue_position: int = 0) -> None:
|
2020-06-08 14:37:35 +00:00
|
|
|
"""Remove item from the queue."""
|
2021-05-11 17:36:40 +00:00
|
|
|
self.coordinator.soco.remove_from_queue(queue_position)
|
2020-06-08 14:37:35 +00:00
|
|
|
|
2017-01-25 21:03:36 +00:00
|
|
|
@property
|
2021-04-04 21:28:29 +00:00
|
|
|
def extra_state_attributes(self) -> dict[str, Any]:
|
2019-02-24 17:45:08 +00:00
|
|
|
"""Return entity specific state attributes."""
|
2021-04-04 21:28:29 +00:00
|
|
|
attributes: dict[str, Any] = {
|
2021-05-11 17:36:40 +00:00
|
|
|
ATTR_SONOS_GROUP: self.speaker.sonos_group_entities
|
2021-04-04 21:28:29 +00:00
|
|
|
}
|
2017-12-17 12:08:35 +00:00
|
|
|
|
2021-05-11 17:36:40 +00:00
|
|
|
if self.media.queue_position is not None:
|
|
|
|
attributes[ATTR_QUEUE_POSITION] = self.media.queue_position
|
2020-06-08 14:37:35 +00:00
|
|
|
|
2017-12-17 12:08:35 +00:00
|
|
|
return attributes
|
2020-09-02 08:57:12 +00:00
|
|
|
|
2021-02-24 19:51:12 +00:00
|
|
|
async def async_get_browse_image(
|
2021-04-04 21:28:29 +00:00
|
|
|
self,
|
2022-01-13 09:43:52 +00:00
|
|
|
media_content_type: str,
|
|
|
|
media_content_id: str,
|
2021-04-04 21:28:29 +00:00
|
|
|
media_image_id: str | None = None,
|
2022-01-13 09:43:52 +00:00
|
|
|
) -> tuple[bytes | None, str | None]:
|
2021-02-24 19:51:12 +00:00
|
|
|
"""Fetch media browser image to serve via proxy."""
|
|
|
|
if (
|
|
|
|
media_content_type in [MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST]
|
|
|
|
and media_content_id
|
|
|
|
):
|
|
|
|
item = await self.hass.async_add_executor_job(
|
2022-01-14 17:28:25 +00:00
|
|
|
media_browser.get_media,
|
2021-05-11 17:36:40 +00:00
|
|
|
self.media.library,
|
2021-02-24 19:51:12 +00:00
|
|
|
media_content_id,
|
|
|
|
MEDIA_TYPES_TO_SONOS[media_content_type],
|
|
|
|
)
|
2021-10-31 18:01:16 +00:00
|
|
|
if image_url := getattr(item, "album_art_uri", None):
|
2021-04-04 21:28:29 +00:00
|
|
|
result = await self._async_fetch_image(image_url) # type: ignore[no-untyped-call]
|
|
|
|
return result # type: ignore
|
2021-02-24 19:51:12 +00:00
|
|
|
|
|
|
|
return (None, None)
|
|
|
|
|
2021-04-04 21:28:29 +00:00
|
|
|
async def async_browse_media(
|
|
|
|
self, media_content_type: str | None = None, media_content_id: str | None = None
|
|
|
|
) -> Any:
|
2020-09-02 08:57:12 +00:00
|
|
|
"""Implement the websocket media browsing helper."""
|
2022-01-21 21:49:06 +00:00
|
|
|
return await media_browser.async_browse_media(
|
|
|
|
self.hass,
|
|
|
|
self.speaker,
|
|
|
|
self.media,
|
|
|
|
self.get_browse_image_url,
|
|
|
|
media_content_id,
|
|
|
|
media_content_type,
|
2020-09-02 08:57:12 +00:00
|
|
|
)
|
2022-01-05 15:22:36 +00:00
|
|
|
|
|
|
|
def join_players(self, group_members):
|
|
|
|
"""Join `group_members` as a player group with the current player."""
|
|
|
|
speakers = []
|
|
|
|
for entity_id in group_members:
|
|
|
|
if speaker := self.hass.data[DATA_SONOS].entity_id_mappings.get(entity_id):
|
|
|
|
speakers.append(speaker)
|
|
|
|
else:
|
|
|
|
raise HomeAssistantError(f"Not a known Sonos entity_id: {entity_id}")
|
|
|
|
|
|
|
|
self.speaker.join(speakers)
|
|
|
|
|
|
|
|
def unjoin_player(self):
|
|
|
|
"""Remove this player from any group."""
|
|
|
|
self.speaker.unjoin()
|