Skip to content

Commit

Permalink
feat: bump to 2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
noahhusby committed Sep 9, 2024
1 parent 8a38f38 commit 0b7f5bf
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 52 deletions.
2 changes: 2 additions & 0 deletions aiostreammagic/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
UPDATE = "/system/update"
POSITION = "/zone/play_state/position"
NOW_PLAYING = "/zone/now_playing"
PLAY_CONTROL = "/zone/play_control"
POWER = "/system/power"
24 changes: 15 additions & 9 deletions aiostreammagic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ class State(DataClassORJSONMixin):
mute: bool = field(metadata=field_options(alias="mute"), default=False)


@dataclass
class PlayState(DataClassORJSONMixin):
"""Data class representing StreamMagic play state."""

state: str = field(metadata=field_options(alias="state"), default="not_ready")
metadata: PlayStateMetadata = field(metadata=field_options(alias="metadata"), default=None)
presettable: bool = field(metadata=field_options(alias="presettable"), default=False)
position: int = field(metadata=field_options(alias="position"), default=None)
mode_repeat: str = field(metadata=field_options(alias="mode_repeat"), default="off")
mode_shuffle: str = field(metadata=field_options(alias="mode_shuffle"), default="off")


@dataclass
class PlayStateMetadata(DataClassORJSONMixin):
"""Data class representing StreamMagic play state metadata."""
Expand All @@ -72,12 +84,6 @@ class PlayStateMetadata(DataClassORJSONMixin):


@dataclass
class PlayState(DataClassORJSONMixin):
"""Data class representing StreamMagic play state."""

state: str = field(metadata=field_options(alias="state"), default="not_ready")
metadata: PlayStateMetadata = field(metadata=field_options(alias="metadata"), default=PlayStateMetadata())
presettable: bool = field(metadata=field_options(alias="presettable"), default=False)
position: int = field(metadata=field_options(alias="position"), default=None)
mode_repeat: str = field(metadata=field_options(alias="mode_repeat"), default="off")
mode_shuffle: str = field(metadata=field_options(alias="mode_shuffle"), default="off")
class NowPlaying(DataClassORJSONMixin):
"""Data class representing NowPlaying state."""
controls: list[str] = field(metadata=field_options(alias="controls"), default=None)
104 changes: 62 additions & 42 deletions aiostreammagic/stream_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from yarl import URL

from aiostreammagic.exceptions import StreamMagicError, StreamMagicConnectionError
from aiostreammagic.models import Info, Source, State, PlayState
from aiostreammagic.models import Info, Source, State, PlayState, NowPlaying

from websockets.client import connect as ws_connect

Expand All @@ -36,15 +36,18 @@ def __init__(self, host):
self.connect_result: Future | None = None
self.connect_task: Task | None = None
self.state_update_callbacks: list[Any] = []
self._allow_state_update = False
self.info: Info | None = None
self.sources: list[Source] | None = None
self.state: State | None = None
self.play_state: PlayState | None = None
self.now_playing: NowPlaying | None = None

async def register_state_update_callbacks(self, callback: Any):
"""Register state update callback."""
self.state_update_callbacks.append(callback)
await callback(self)
if self._allow_state_update:
await callback(self)

def unregister_state_update_callbacks(self, callback: Any):
"""Unregister state update callback."""
Expand All @@ -57,6 +60,8 @@ def clear_state_update_callbacks(self):

async def do_state_update_callbacks(self):
"""Call state update callbacks."""
if not self.state_update_callbacks:
return
callbacks = set()
for callback in self.state_update_callbacks:
callbacks.add(callback(self))
Expand Down Expand Up @@ -87,24 +92,27 @@ async def _ws_connect(self, uri):
async def connect_handler(self, res):
"""Handle connection for StreamMagic."""
self.futures = {}
self._allow_state_update = False
uri = f"ws://{self.host}/smoip"
ws = await self._ws_connect(uri)
self.connection = ws
x = asyncio.create_task(
self.consumer_handler(ws, self._subscriptions, self.futures)
)
self.info, self.sources = await asyncio.gather(self.get_info(), self.get_sources())
self.info, self.sources, self.state, self.play_state, self.now_playing = await asyncio.gather(self.get_info(), self.get_sources(), self.get_state(), self.get_play_state(), self.get_now_playing())
subscribe_state_updates = {
self.subscribe(self._async_handle_info, ep.INFO),
self.subscribe(self._async_handle_sources, ep.SOURCES),
self.subscribe(self._async_handle_zone_state, ep.ZONE_STATE),
self.subscribe(self._async_handle_play_state, ep.PLAY_STATE),
self.subscribe(self._async_handle_position, ep.POSITION),
self.subscribe(self._async_handle_now_playing, ep.NOW_PLAYING)
}
subscribe_tasks = set()
for state_update in subscribe_state_updates:
subscribe_tasks.add(asyncio.create_task(state_update))
await asyncio.wait(subscribe_tasks)
self._allow_state_update = True

res.set_result(True)
await asyncio.wait([x], return_when=asyncio.FIRST_COMPLETED)
Expand Down Expand Up @@ -220,6 +228,11 @@ async def get_play_state(self) -> PlayState:
data = await self.request(ep.PLAY_STATE)
return PlayState.from_dict(data)

async def get_now_playing(self) -> NowPlaying:
"""Get now playing information from device."""
data = await self.request(ep.NOW_PLAYING)
return NowPlaying.from_dict(data)

async def _async_handle_info(self, payload) -> None:
"""Handle async info update."""
params = payload["params"]
Expand Down Expand Up @@ -254,15 +267,22 @@ async def _async_handle_position(self, payload) -> None:
if "data" in params and params["data"]["position"] and self.play_state:
self.play_state.position = params["data"]["position"]
await self.do_state_update_callbacks()
#
# async def power_on(self) -> None:
# """Set the power of the device to on."""
# await self._request_device('system/power', query='power=ON')
#
# async def power_off(self) -> None:
# """Set the power of the device to network."""
# await self._request_device('system/power', query='power=NETWORK')
#

async def _async_handle_now_playing(self, payload) -> None:
"""Handle async now playing update."""
params = payload["params"]
if "data" in params:
self.now_playing = NowPlaying.from_dict(params["data"])
await self.do_state_update_callbacks()

async def power_on(self) -> None:
"""Set the power of the device to on."""
await self.request(ep.POWER, params={"power": "ON"})

async def power_off(self) -> None:
"""Set the power of the device to network."""
await self.request(ep.POWER, params={"power": "NETWORK"})

# async def volume_up(self) -> None:
# """Increase the volume of the device by 1."""
# await self._request_device('zone/state', query='zone=ZONE1&volume_step_change=1')
Expand All @@ -284,39 +304,39 @@ async def _async_handle_position(self, payload) -> None:
# async def unmute(self) -> None:
# """Unmute the device."""
# await self._request_device('zone/state', query='zone=ZONE1&mute=false')
#
# async def set_source(self, source: Source) -> None:
# """Set the source of the device."""
# await self.set_source_by_id(source.id)
#
# async def set_source_by_id(self, source_id: str) -> None:
# """Set the source of the device."""
# await self._request_device('zone/state', query=f"zone=ZONE1&source={source_id}")
#

async def set_source(self, source: Source) -> None:
"""Set the source of the device."""
await self.set_source_by_id(source.id)

async def set_source_by_id(self, source_id: str) -> None:
"""Set the source of the device."""
await self.request(ep.ZONE_STATE, params={"zone": "ZONE1", "source": source_id})

# async def media_seek(self, position: int) -> None:
# """Set the media position of the device."""
# await self._request_device('zone/play_control', query=f"position={position}")
#
# async def next_track(self) -> None:
# """Skip the next track."""
# await self._request_device('zone/play_control', query='skip_track=1')
#
# async def previous_track(self) -> None:
# """Skip the next track."""
# await self._request_device('zone/play_control', query='skip_track=-1')
#
# async def play_pause(self) -> None:
# """Toggle play/pause."""
# await self._request_device('zone/play_control', query='action=toggle')
#
# async def pause(self) -> None:
# """Pause the device."""
# await self._request_device('zone/play_control', query='action=pause')
#
# async def stop(self) -> None:
# """Pause the device."""
# await self._request_device('zone/play_control', query='action=stop')
#

async def next_track(self) -> None:
"""Skip the next track."""
await self.request(ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "skip_track": 1})

async def previous_track(self) -> None:
"""Skip the next track."""
await self.request(ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "skip_track": -1})

async def play_pause(self) -> None:
"""Toggle play/pause."""
await self.request(ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "action": "toggle"})

async def pause(self) -> None:
"""Pause the device."""
await self.request(ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "action": "pause"})

async def stop(self) -> None:
"""Pause the device."""
await self.request(ep.PLAY_CONTROL, params={"match": "none", "zone": "ZONE1", "action": "stop"})

# async def set_shuffle(self, shuffle: str):
# """Set the shuffle of the device."""
# await self._request_device('zone/play_control', query=f"mode_shuffle={shuffle}")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aiostreammagic"
version = "1.0.0"
version = "2.0.0"
description = "An async python package for interfacing with Cambridge Audio / Stream Magic compatible streamers."
authors = ["Noah Husby <32528627+noahhusby@users.noreply.github.com>"]
maintainers = ["Noah Husby <32528627+noahhusby@users.noreply.github.com>"]
Expand Down

0 comments on commit 0b7f5bf

Please sign in to comment.