Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 17 additions & 46 deletions pychromecast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,7 @@ def new_cast_status(self, status: CastStatus) -> None:
if status:
self.status_event.set()

def start_app(
self, app_id: str, force_launch: bool = False, timeout: float = REQUEST_TIMEOUT
) -> None:
async def start_app(self, app_id: str, force_launch: bool = False, timeout: float = REQUEST_TIMEOUT) -> None:
"""Start an app on the Chromecast."""
self.logger.info("Starting app %s", app_id)
response_handler = WaitResponse(timeout, f"start app {app_id}")
Expand All @@ -450,29 +448,29 @@ def start_app(
force_launch=force_launch,
callback_function=response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()

def quit_app(self, timeout: float = REQUEST_TIMEOUT) -> None:
async def quit_app(self, timeout: float = REQUEST_TIMEOUT) -> None:
"""Tells the Chromecast to quit current app_id."""
self.logger.info("Quitting current app")

response_handler = WaitResponse(timeout, "quit app")
self.socket_client.receiver_controller.stop_app(
callback_function=response_handler.callback
)
response_handler.wait_response()
await response_handler.wait_response()

def volume_up(self, delta: float = 0.1, timeout: float = REQUEST_TIMEOUT) -> float:
async def volume_up(self, delta: float = 0.1, timeout: float = REQUEST_TIMEOUT) -> float:
"""Increment volume by 0.1 (or delta) unless it is already maxed.
Returns the new volume.
"""
if delta <= 0:
raise ValueError(f"volume delta must be greater than zero, not {delta}")
if not self.status:
raise NotConnected
return self.set_volume(self.status.volume_level + delta, timeout=timeout)
return await self.set_volume(self.status.volume_level + delta, timeout=timeout)

def volume_down(
async def volume_down(
self, delta: float = 0.1, timeout: float = REQUEST_TIMEOUT
) -> float:
"""Decrement the volume by 0.1 (or delta) unless it is already 0.
Expand All @@ -482,9 +480,9 @@ def volume_down(
raise ValueError(f"volume delta must be greater than zero, not {delta}")
if not self.status:
raise NotConnected
return self.set_volume(self.status.volume_level - delta, timeout=timeout)
return await self.set_volume(self.status.volume_level - delta, timeout=timeout)

def wait(self, timeout: float | None = None) -> None:
async def connect(self, timeout: float | None = None) -> None:
"""
Waits until the cast device is ready for communication. The device
is ready as soon a status message has been received.
Expand All @@ -498,47 +496,20 @@ def wait(self, timeout: float | None = None) -> None:
operation in seconds (or fractions thereof). Or None
to block forever.
"""
if not self.socket_client.is_alive():
self.socket_client.start()
ready = self.status_event.wait(timeout=timeout)
if not ready:
raise RequestTimeout("wait", cast(float, timeout))
if timeout:
self.socket_client.timeout = timeout

def disconnect(self, timeout: float | None = None) -> None:
"""
Disconnects the chromecast and waits for it to terminate.
if not self.socket_client.connected:
await self.socket_client.connect()

:param timeout: A floating point number specifying a timeout for the
operation in seconds (or fractions thereof). Or None
to block forever. Set to 0 to not block.
def disconnect(self) -> None:
"""
self.socket_client.disconnect()
self.join(timeout=timeout)

def join(self, timeout: float | None = None) -> None:
Disconnects the chromecast.
"""
Blocks the thread of the caller until the chromecast connection is
stopped.

:param timeout: a floating point number specifying a timeout for the
operation in seconds (or fractions thereof). Or None
to block forever.
"""
self.socket_client.join(timeout=timeout)
if self.socket_client.is_alive():
raise TimeoutError("join", timeout)

def start(self) -> None:
"""
Start the chromecast connection's worker thread.
"""
self.socket_client.start()
self.socket_client.disconnect()

def __del__(self) -> None:
try:
self.socket_client.stop.set()
except AttributeError:
pass
self.disconnect()

def __repr__(self) -> str:
return (
Expand Down
62 changes: 24 additions & 38 deletions pychromecast/controllers/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ def _send_start_play_media( # pylint: disable=too-many-locals

self.send_message(msg, inc_session_id=True, callback_function=callback_function)

def quick_play(self, *, media_id: str, timeout: float, **kwargs: Any) -> None:
async def quick_play(self, *, media_id: str, timeout: float, **kwargs: Any) -> None:
"""Quick Play"""

media_type = kwargs.pop("media_type", "video/mp4")
Expand All @@ -556,7 +556,7 @@ def quick_play(self, *, media_id: str, timeout: float, **kwargs: Any) -> None:
self.play_media(
media_id, media_type, **kwargs, callback_function=response_handler.callback
)
response_handler.wait_response()
await response_handler.wait_response()


class MediaController(BaseMediaPlayer):
Expand Down Expand Up @@ -623,35 +623,35 @@ def _send_command(
command, callback_function=callback_function, inc_session_id=True
)

def play(self, timeout: float = 10.0) -> None:
async def play(self, timeout: float = 10.0) -> None:
"""Send the PLAY command."""
response_handler = WaitResponse(timeout, "play")
self._send_command({MESSAGE_TYPE: TYPE_PLAY}, response_handler.callback)
response_handler.wait_response()
await response_handler.wait_response()

def pause(self, timeout: float = 10.0) -> None:
async def pause(self, timeout: float = 10.0) -> None:
"""Send the PAUSE command."""
response_handler = WaitResponse(timeout, "pause")
self._send_command({MESSAGE_TYPE: TYPE_PAUSE}, response_handler.callback)
response_handler.wait_response()
await response_handler.wait_response()

def stop(self, timeout: float = 10.0) -> None:
async def stop(self, timeout: float = 10.0) -> None:
"""Send the STOP command."""
response_handler = WaitResponse(timeout, "stop")
self._send_command({MESSAGE_TYPE: TYPE_STOP}, response_handler.callback)
response_handler.wait_response()
await response_handler.wait_response()

def rewind(self, timeout: float = 10.0) -> None:
async def rewind(self, timeout: float = 10.0) -> None:
"""Starts playing the media from the beginning."""
self.seek(0, timeout)
await self.seek(0, timeout)

def skip(self, timeout: float = 10.0) -> None:
async def skip(self, timeout: float = 10.0) -> None:
"""Skips rest of the media. Values less then -5 behaved flaky."""
if not self.status.duration or self.status.duration < 5:
return
self.seek(int(self.status.duration) - 5, timeout)
await self.seek(int(self.status.duration) - 5, timeout)

def seek(self, position: float, timeout: float = 10.0) -> None:
async def seek(self, position: float, timeout: float = 10.0) -> None:
"""Seek the media to a specific location."""
response_handler = WaitResponse(timeout, f"seek {position}")
self._send_command(
Expand All @@ -662,9 +662,9 @@ def seek(self, position: float, timeout: float = 10.0) -> None:
},
response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()

def set_playback_rate(self, playback_rate: float, timeout: float = 10.0) -> None:
async def set_playback_rate(self, playback_rate: float, timeout: float = 10.0) -> None:
"""Set the playback rate. 1.0 is regular time, 0.5 is slow motion."""
response_handler = WaitResponse(timeout, "set playback rate")
self._send_command(
Expand All @@ -674,55 +674,41 @@ def set_playback_rate(self, playback_rate: float, timeout: float = 10.0) -> None
},
response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()

def queue_next(self, timeout: float = 10.0) -> None:
async def queue_next(self, timeout: float = 10.0) -> None:
"""Send the QUEUE_NEXT command."""
response_handler = WaitResponse(timeout, "queue next")
self._send_command(
{MESSAGE_TYPE: TYPE_QUEUE_UPDATE, "jump": 1}, response_handler.callback
)
response_handler.wait_response()
await response_handler.wait_response()

def queue_prev(self, timeout: float = 10.0) -> None:
async def queue_prev(self, timeout: float = 10.0) -> None:
"""Send the QUEUE_PREV command."""
response_handler = WaitResponse(timeout, "queue prev")
self._send_command(
{MESSAGE_TYPE: TYPE_QUEUE_UPDATE, "jump": -1}, response_handler.callback
)
response_handler.wait_response()
await response_handler.wait_response()

def enable_subtitle(self, track_id: int, timeout: float = 10.0) -> None:
async def enable_subtitle(self, track_id: int, timeout: float = 10.0) -> None:
"""Enable specific text track."""
response_handler = WaitResponse(timeout, "enable subtitle")
self._send_command(
{MESSAGE_TYPE: TYPE_EDIT_TRACKS_INFO, "activeTrackIds": [track_id]},
response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()

def disable_subtitle(self, timeout: float = 10.0) -> None:
async def disable_subtitle(self, timeout: float = 10.0) -> None:
"""Disable subtitle."""
response_handler = WaitResponse(timeout, "disable subtitle")
self._send_command(
{MESSAGE_TYPE: TYPE_EDIT_TRACKS_INFO, "activeTrackIds": []},
response_handler.callback,
)
response_handler.wait_response()

def block_until_active(self, timeout: float | None = None) -> None:
"""
Blocks thread until the media controller session is active on the
chromecast. The media controller only accepts playback control
commands when a media session is active.

If a session is already active then the method returns immediately.

:param timeout: a floating point number specifying a timeout for the
operation in seconds (or fractions thereof). Or None
to block forever.
"""
self.session_active_event.wait(timeout=timeout)
await response_handler.wait_response()

def _process_media_status(self, data: dict) -> None:
"""Processes a STATUS message."""
Expand Down
8 changes: 4 additions & 4 deletions pychromecast/controllers/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def stop_app(
callback_function=callback_function,
)

def set_volume(self, volume: float, timeout: float = REQUEST_TIMEOUT) -> float:
async def set_volume(self, volume: float, timeout: float = REQUEST_TIMEOUT) -> float:
"""Allows to set volume. Should be value between 0..1.
Returns the new volume.

Expand All @@ -253,17 +253,17 @@ def set_volume(self, volume: float, timeout: float = REQUEST_TIMEOUT) -> float:
{MESSAGE_TYPE: "SET_VOLUME", "volume": {"level": volume}},
callback_function=response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()
return volume

def set_volume_muted(self, muted: bool, timeout: float = REQUEST_TIMEOUT) -> None:
async def set_volume_muted(self, muted: bool, timeout: float = REQUEST_TIMEOUT) -> None:
"""Allows to mute volume."""
response_handler = WaitResponse(timeout, "mute volume")
self.send_message(
{MESSAGE_TYPE: "SET_VOLUME", "volume": {"muted": muted}},
callback_function=response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()

@staticmethod
def _parse_status(data: dict, cast_type: str) -> CastStatus:
Expand Down
4 changes: 2 additions & 2 deletions pychromecast/controllers/supla.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def play_media(
no_add_request_id=True,
)

def quick_play(
async def quick_play(
self, *, media_id: str, timeout: float, is_live: bool = False, **kwargs: Any
) -> None:
"""Quick Play"""
Expand All @@ -66,4 +66,4 @@ def quick_play(
**kwargs,
callback_function=response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()
4 changes: 2 additions & 2 deletions pychromecast/controllers/yleareena.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def play_areena_media( # pylint: disable=too-many-locals

self.send_message(msg, inc_session_id=True, callback_function=callback_function)

def quick_play(
async def quick_play(
self,
*,
media_id: str,
Expand All @@ -74,4 +74,4 @@ def quick_play(
**kwargs,
callback_function=response_handler.callback,
)
response_handler.wait_response()
await response_handler.wait_response()
14 changes: 9 additions & 5 deletions pychromecast/response_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from __future__ import annotations

from collections.abc import Callable
import asyncio
import contextlib
import logging
import threading
from collections.abc import Callable
from typing import Protocol

from .error import RequestFailed, RequestTimeout
Expand Down Expand Up @@ -37,7 +38,7 @@ class WaitResponse:

def __init__(self, timeout: float, request: str) -> None:
"""Initialize."""
self._event = threading.Event()
self._event = asyncio.Event()
self._request = request
self._timeout = timeout

Expand All @@ -47,9 +48,12 @@ def callback(self, msg_sent: bool, response: dict | None) -> None:
self.msg_sent = msg_sent
self._event.set()

def wait_response(self) -> None:
async def wait_response(self) -> None:
"""Wait for the request to finish."""
request_completed = self._event.wait(self._timeout)
with contextlib.suppress(asyncio.TimeoutError):
await asyncio.wait_for(self._event.wait(), self._timeout)

request_completed = self._event.is_set()
if not request_completed:
raise RequestTimeout(self._request, self._timeout)

Expand Down
Loading