Skip to content

Commit

Permalink
Several follow-up fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt committed Aug 25, 2024
1 parent 4216641 commit 46db841
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 17 deletions.
28 changes: 28 additions & 0 deletions aiosonos/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,18 @@ class PlaybackStatus(TypedDict):
previousPositionMillis: int


class MusicService(StrEnum):
"""Enum with (known) possible container service Id's."""

SPOTIFY = "9"
MUSIC_ASSISTANT = "mass"
TUNEIN = "303"
QOBUZ = "31"
YOUTUBE_MUSIC = "284"
LOCAL_LIBRARY = "local-library"
# TODO: complete this list with other known services


class MetadataId(TypedDict):
"""Representation of an ID, used in metadata objects."""

Expand Down Expand Up @@ -456,6 +468,22 @@ class PlaybackSession(TypedDict):
accountId: str


class ContainerType(StrEnum):
"""Enum with possible container types."""

LINEIN = "linein"
STATION = "station"
PLAYLIST = "playlist"
AIRPLAY = "linein.airplay"
PODCAST = "podcast"
BOOK = "book"
ARTIST = "artist"
ALBUM = "album"
ARTIST_LOCAL = "artist.local"
ALBUM_LOCAL = "album.local"
# TODO: complete this list with other known types


class Container(TypedDict):
"""
Representation of a container.
Expand Down
72 changes: 55 additions & 17 deletions aiosonos/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
from __future__ import annotations

import time
from contextlib import suppress
from typing import TYPE_CHECKING

from aiosonos.api.models import PlayBackState
from aiosonos.api.models import ContainerType, MusicService, PlayBackState
from aiosonos.const import EventType, GroupEvent
from aiosonos.exceptions import FailedCommand

Expand Down Expand Up @@ -46,6 +47,12 @@ def __init__(self, client: SonosLocalApiClient, data: GroupData) -> None:
self.active_session_id: str | None = None
self._data = data
self._playback_status_last_updated: float = 0.0
self._unsubscribe_callbacks = []

def __del__(self) -> None:
"""Handle deletion."""
for unsubscribe_callback in self._unsubscribe_callbacks:
unsubscribe_callback()

async def async_init(self) -> None:
"""Handle Async initialization."""
Expand All @@ -65,18 +72,20 @@ async def async_init(self) -> None:
self.id,
)
)
await self.client.api.playback.subscribe(
self.id,
self._handle_playback_status_update,
)
await self.client.api.group_volume.subscribe(
self.id,
self._handle_volume_update,
)
await self.client.api.playback_metadata.subscribe(
self.id,
self._handle_metadata_status_update,
)
self._unsubscribe_callbacks = [
await self.client.api.playback.subscribe(
self.id,
self._handle_playback_status_update,
),
await self.client.api.group_volume.subscribe(
self.id,
self._handle_volume_update,
),
await self.client.api.playback_metadata.subscribe(
self.id,
self._handle_metadata_status_update,
),
]
except FailedCommand as err:
if err.error_code == "groupCoordinatorChanged":
# retrieving group details is not possible for remote groups when
Expand Down Expand Up @@ -152,6 +161,32 @@ def play_modes(self) -> PlayModes:
"""Return the play modes of this group."""
return self._play_modes

@property
def container_type(self) -> ContainerType | None:
"""Return the container_type of the active source of this group (if any)."""
if not (container := self._playback_metadata_data.get("container")):
return None
if container_type := container.get("type"):
if container_type in ContainerType:
return ContainerType(container_type)
# return the raw string value if it's not a known container type
return container_type
return None

@property
def active_service(self) -> MusicService | None:
"""Return the active service of the active source of this group (if any)."""
if not (container := self._playback_metadata_data.get("container")):
return None
if (container_id := container.get("id")) and (
service_id := container_id.get("serviceId")
):
if service_id in MusicService:
return MusicService(service_id)
# return the raw string value if it's not a known container type
return service_id
return None

async def play(self) -> None:
"""Send play command to group."""
await self.client.api.playback.play(self.id)
Expand All @@ -164,8 +199,12 @@ async def stop(self) -> None:
"""Send stop command to group."""
if session_id := self.active_session_id:
self.active_session_id = None
await self.client.api.playback_session.suspend(session_id)
return
with suppress(FailedCommand):
await self.client.api.playback_session.suspend(session_id)
return
# always fall back to pause if no session is active
# TODO: figure out if there is some better way to figure out
# the active session id to suspend it.
await self.client.api.playback.pause(self.id)

async def toggle_play_pause(self) -> None:
Expand Down Expand Up @@ -269,8 +308,7 @@ async def create_playback_session(

async def play_stream_url(self, url: str, metadata: Container) -> None:
"""Create a new playback session and start playing a single (radio) stream URL."""
if not self.active_session_id:
await self.create_playback_session()
await self.create_playback_session()
await self.client.api.playback_session.load_stream_url(
self.active_session_id,
stream_url=url,
Expand Down

0 comments on commit 46db841

Please sign in to comment.