PyChromecast 14.0.3__tar.gz → 14.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PKG-INFO +1 -1
  2. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/PKG-INFO +1 -1
  3. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/SOURCES.txt +1 -0
  4. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/config.py +1 -0
  5. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/const.py +2 -0
  6. PyChromecast-14.0.5/pychromecast/controllers/heartbeat.py +90 -0
  7. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/socket_client.py +31 -83
  8. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pyproject.toml +1 -1
  9. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/LICENSE +0 -0
  10. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/MANIFEST.in +0 -0
  11. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/dependency_links.txt +0 -0
  12. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/not-zip-safe +0 -0
  13. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/requires.txt +0 -0
  14. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/PyChromecast.egg-info/top_level.txt +0 -0
  15. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/README.rst +0 -0
  16. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/__init__.py +0 -0
  17. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/__init__.py +0 -0
  18. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/bbciplayer.py +0 -0
  19. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/bbcsounds.py +0 -0
  20. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/bubbleupnp.py +0 -0
  21. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/dashcast.py +0 -0
  22. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/homeassistant.py +0 -0
  23. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/homeassistant_media.py +0 -0
  24. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/media.py +0 -0
  25. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/multizone.py +0 -0
  26. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/nrkradio.py +0 -0
  27. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/nrktv.py +0 -0
  28. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/plex.py +0 -0
  29. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/receiver.py +0 -0
  30. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/shaka.py +0 -0
  31. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/supla.py +0 -0
  32. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/yleareena.py +0 -0
  33. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/controllers/youtube.py +0 -0
  34. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/dial.py +0 -0
  35. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/discovery.py +0 -0
  36. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/error.py +0 -0
  37. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/authority_keys_pb2.py +0 -0
  38. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/authority_keys_pb2.pyi +0 -0
  39. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/cast_channel_pb2.py +0 -0
  40. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/cast_channel_pb2.pyi +0 -0
  41. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/logging_pb2.py +0 -0
  42. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/logging_pb2.pyi +0 -0
  43. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/generated/readme.txt +0 -0
  44. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/models.py +0 -0
  45. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/quick_play.py +0 -0
  46. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/pychromecast/response_handler.py +0 -0
  47. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/requirements.txt +0 -0
  48. {PyChromecast-14.0.3 → PyChromecast-14.0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyChromecast
3
- Version: 14.0.3
3
+ Version: 14.0.5
4
4
  Summary: Python module to talk to Google Chromecast.
5
5
  Author-email: Paulus Schoutsen <paulus@paulusschoutsen.nl>
6
6
  Project-URL: Homepage, https://github.com/home-assistant-libs/pychromecast
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyChromecast
3
- Version: 14.0.3
3
+ Version: 14.0.5
4
4
  Summary: Python module to talk to Google Chromecast.
5
5
  Author-email: Paulus Schoutsen <paulus@paulusschoutsen.nl>
6
6
  Project-URL: Homepage, https://github.com/home-assistant-libs/pychromecast
@@ -25,6 +25,7 @@ pychromecast/controllers/bbciplayer.py
25
25
  pychromecast/controllers/bbcsounds.py
26
26
  pychromecast/controllers/bubbleupnp.py
27
27
  pychromecast/controllers/dashcast.py
28
+ pychromecast/controllers/heartbeat.py
28
29
  pychromecast/controllers/homeassistant.py
29
30
  pychromecast/controllers/homeassistant_media.py
30
31
  pychromecast/controllers/media.py
@@ -22,6 +22,7 @@ APP_BBCIPLAYER = "5E81F6DB"
22
22
  APP_SHAKA = "07AEE832"
23
23
  APP_NRKTV = "3AEDF8D1"
24
24
  APP_NRKRADIO = "A49874B1"
25
+ APP_AUDIBLE = "25456794"
25
26
 
26
27
 
27
28
  def get_possible_app_ids() -> list[str]:
@@ -70,3 +70,5 @@ CAST_TYPES = {
70
70
  MESSAGE_TYPE = "type"
71
71
  REQUEST_ID = "requestId"
72
72
  SESSION_ID = "sessionId"
73
+
74
+ PLATFORM_DESTINATION_ID = "receiver-0"
@@ -0,0 +1,90 @@
1
+ """Controller to send and respond to heartbeat messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+
8
+ from . import BaseController
9
+
10
+ from ..const import MESSAGE_TYPE, PLATFORM_DESTINATION_ID
11
+ from ..error import ControllerNotRegistered, NotConnected, PyChromecastStopped
12
+
13
+ # pylint: disable-next=no-name-in-module
14
+ from ..generated.cast_channel_pb2 import CastMessage
15
+
16
+ NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat"
17
+
18
+ TYPE_PING = "PING"
19
+ TYPE_PONG = "PONG"
20
+
21
+ HB_PING_TIME = 10
22
+ HB_PONG_TIME = 10
23
+
24
+
25
+ class HeartbeatController(BaseController):
26
+ """Controller to send and respond to heartbeat messages."""
27
+
28
+ def __init__(self) -> None:
29
+ super().__init__(NS_HEARTBEAT, target_platform=True)
30
+ self.last_ping = 0.0
31
+ self.last_pong = time.time()
32
+ self.logger = logging.getLogger(__name__)
33
+
34
+ def receive_message(self, _message: CastMessage, data: dict) -> bool:
35
+ """
36
+ Called when a heartbeat message is received.
37
+
38
+ data is message.payload_utf8 interpreted as a JSON dict.
39
+ """
40
+ if self._socket_client is None:
41
+ raise ControllerNotRegistered
42
+
43
+ if self._socket_client.is_stopped:
44
+ return True
45
+
46
+ if data[MESSAGE_TYPE] == TYPE_PING:
47
+ try:
48
+ self._socket_client.send_message(
49
+ PLATFORM_DESTINATION_ID,
50
+ self.namespace,
51
+ {MESSAGE_TYPE: TYPE_PONG},
52
+ no_add_request_id=True,
53
+ )
54
+ except PyChromecastStopped:
55
+ self._socket_client.logger.debug(
56
+ "Heartbeat error when sending response, "
57
+ "Chromecast connection has stopped"
58
+ )
59
+
60
+ return True
61
+
62
+ if data[MESSAGE_TYPE] == TYPE_PONG:
63
+ self.reset()
64
+ return True
65
+
66
+ return False
67
+
68
+ def ping(self) -> None:
69
+ """Send a ping message."""
70
+ if self._socket_client is None:
71
+ raise ControllerNotRegistered
72
+
73
+ self.last_ping = time.time()
74
+ try:
75
+ self.send_message({MESSAGE_TYPE: TYPE_PING})
76
+ except NotConnected:
77
+ self._socket_client.logger.error(
78
+ "Chromecast is disconnected. Cannot ping until reconnected."
79
+ )
80
+
81
+ def reset(self) -> None:
82
+ """Reset expired counter."""
83
+ self.last_pong = time.time()
84
+
85
+ def is_expired(self) -> bool:
86
+ """Indicates if connection has expired."""
87
+ if time.time() - self.last_ping > HB_PING_TIME:
88
+ self.ping()
89
+
90
+ return (time.time() - self.last_pong) > HB_PING_TIME + HB_PONG_TIME
@@ -24,8 +24,10 @@ from struct import pack, unpack
24
24
 
25
25
  import zeroconf
26
26
 
27
- from .const import MESSAGE_TYPE, REQUEST_ID, SESSION_ID
27
+ from .config import APP_AUDIBLE
28
+ from .const import MESSAGE_TYPE, PLATFORM_DESTINATION_ID, REQUEST_ID, SESSION_ID
28
29
  from .controllers import BaseController, CallbackType
30
+ from .controllers.heartbeat import NS_HEARTBEAT, HeartbeatController
29
31
  from .controllers.media import MediaController
30
32
  from .controllers.receiver import CastStatus, CastStatusListener, ReceiverController
31
33
  from .dial import get_host_from_service
@@ -42,12 +44,7 @@ from .generated.cast_channel_pb2 import CastMessage
42
44
  from .models import HostServiceInfo, MDNSServiceInfo
43
45
 
44
46
  NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection"
45
- NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat"
46
47
 
47
- PLATFORM_DESTINATION_ID = "receiver-0"
48
-
49
- TYPE_PING = "PING"
50
- TYPE_PONG = "PONG"
51
48
  TYPE_CONNECT = "CONNECT"
52
49
  TYPE_CLOSE = "CLOSE"
53
50
  TYPE_LOAD = "LOAD"
@@ -65,10 +62,7 @@ CONNECTION_STATUS_FAILED_RESOLVE = "FAILED_RESOLVE"
65
62
  # The socket connection was lost and needs to be retried
66
63
  CONNECTION_STATUS_LOST = "LOST"
67
64
 
68
- HB_PING_TIME = 10
69
- HB_PONG_TIME = 10
70
- POLL_TIME_BLOCKING = 5.0
71
- POLL_TIME_NON_BLOCKING = 0.01
65
+ SELECT_TIMEOUT = 5.0
72
66
  TIMEOUT_TIME = 30.0
73
67
  RETRY_TIME = 5.0
74
68
 
@@ -492,6 +486,17 @@ class SocketClient(threading.Thread, CastStatusListener):
492
486
  self.session_id = status.session_id
493
487
 
494
488
  if new_channel and self.destination_id is not None:
489
+ # App quirk: If the Audible app is running, wait 1s after receiving cast status
490
+ # before connecting to the media player channel. This is a workaround for
491
+ # https://github.com/home-assistant-libs/pychromecast/issues/738
492
+ if (
493
+ any(namespace in self._handlers for namespace in self.app_namespaces)
494
+ and self.destination_id not in self._open_channels
495
+ and status.app_id == APP_AUDIBLE
496
+ ):
497
+ self.logger.debug("Detected Audible connection. Sleeping for 1s")
498
+ time.sleep(1)
499
+
495
500
  # If any of the namespaces of the new app are supported
496
501
  # we will automatically connect to it to receive updates
497
502
  for namespace in self.app_namespaces:
@@ -540,7 +545,7 @@ class SocketClient(threading.Thread, CastStatusListener):
540
545
  self.logger.debug("Thread started...")
541
546
  while not self.stop.is_set():
542
547
  try:
543
- if self._run_once(timeout=POLL_TIME_BLOCKING) == 1:
548
+ if self._run_once() == 1:
544
549
  break
545
550
  except Exception: # pylint: disable=broad-except
546
551
  self._force_recon = True
@@ -555,7 +560,7 @@ class SocketClient(threading.Thread, CastStatusListener):
555
560
  # Clean up
556
561
  self._cleanup()
557
562
 
558
- def _run_once(self, timeout: float = POLL_TIME_NON_BLOCKING) -> int:
563
+ def _run_once(self) -> int:
559
564
  """Receive from the socket and handle data."""
560
565
  # pylint: disable=too-many-branches, too-many-statements, too-many-return-statements
561
566
 
@@ -568,9 +573,13 @@ class SocketClient(threading.Thread, CastStatusListener):
568
573
  # A connection has been established at this point by self._check_connection
569
574
  assert self.socket is not None
570
575
 
571
- # poll the socket, as well as the socketpair to allow us to be interrupted
576
+ # Poll the socket and the socketpair, with a timeout of SELECT_TIMEOUT
577
+ # The timeout ensures we call _check_connection often enough to avoid
578
+ # the HeartbeatController from detecting a timeout
579
+ # The socketpair allow us to be interrupted on shutdown without waiting
580
+ # for the SELECT_TIMEOUT timout to expire
572
581
  try:
573
- ready = self.selector.select(timeout)
582
+ ready = self.selector.select(SELECT_TIMEOUT)
574
583
  except (ValueError, OSError) as exc:
575
584
  self.logger.error(
576
585
  "[%s(%s):%s] Error in select call: %s",
@@ -689,7 +698,10 @@ class SocketClient(threading.Thread, CastStatusListener):
689
698
  # route message to handlers
690
699
  if message.namespace in self._handlers:
691
700
  # debug messages
692
- if message.namespace != NS_HEARTBEAT:
701
+ if (
702
+ message.namespace != NS_HEARTBEAT
703
+ or self.heartbeat_controller.logger.isEnabledFor(logging.DEBUG)
704
+ ):
693
705
  self.logger.debug(
694
706
  "[%s(%s):%s] Received: %s",
695
707
  self.fn or "",
@@ -873,7 +885,10 @@ class SocketClient(threading.Thread, CastStatusListener):
873
885
  be_size = pack(">I", msg.ByteSize())
874
886
 
875
887
  # Log all messages except heartbeat
876
- if msg.namespace != NS_HEARTBEAT:
888
+ if (
889
+ msg.namespace != NS_HEARTBEAT
890
+ or self.heartbeat_controller.logger.isEnabledFor(logging.DEBUG)
891
+ ):
877
892
  self.logger.debug(
878
893
  "[%s(%s):%s] Sending: %s",
879
894
  self.fn or "",
@@ -1062,73 +1077,6 @@ class ConnectionController(BaseController):
1062
1077
  return False
1063
1078
 
1064
1079
 
1065
- class HeartbeatController(BaseController):
1066
- """Controller to respond to heartbeat messages."""
1067
-
1068
- def __init__(self) -> None:
1069
- super().__init__(NS_HEARTBEAT, target_platform=True)
1070
- self.last_ping = 0.0
1071
- self.last_pong = time.time()
1072
-
1073
- def receive_message(self, _message: CastMessage, data: dict) -> bool:
1074
- """
1075
- Called when a heartbeat message is received.
1076
-
1077
- data is message.payload_utf8 interpreted as a JSON dict.
1078
- """
1079
- if self._socket_client is None:
1080
- raise ControllerNotRegistered
1081
-
1082
- if self._socket_client.is_stopped:
1083
- return True
1084
-
1085
- if data[MESSAGE_TYPE] == TYPE_PING:
1086
- try:
1087
- self._socket_client.send_message(
1088
- PLATFORM_DESTINATION_ID,
1089
- self.namespace,
1090
- {MESSAGE_TYPE: TYPE_PONG},
1091
- no_add_request_id=True,
1092
- )
1093
- except PyChromecastStopped:
1094
- self._socket_client.logger.debug(
1095
- "Heartbeat error when sending response, "
1096
- "Chromecast connection has stopped"
1097
- )
1098
-
1099
- return True
1100
-
1101
- if data[MESSAGE_TYPE] == TYPE_PONG:
1102
- self.reset()
1103
- return True
1104
-
1105
- return False
1106
-
1107
- def ping(self) -> None:
1108
- """Send a ping message."""
1109
- if self._socket_client is None:
1110
- raise ControllerNotRegistered
1111
-
1112
- self.last_ping = time.time()
1113
- try:
1114
- self.send_message({MESSAGE_TYPE: TYPE_PING})
1115
- except NotConnected:
1116
- self._socket_client.logger.error(
1117
- "Chromecast is disconnected. Cannot ping until reconnected."
1118
- )
1119
-
1120
- def reset(self) -> None:
1121
- """Reset expired counter."""
1122
- self.last_pong = time.time()
1123
-
1124
- def is_expired(self) -> bool:
1125
- """Indicates if connection has expired."""
1126
- if time.time() - self.last_ping > HB_PING_TIME:
1127
- self.ping()
1128
-
1129
- return (time.time() - self.last_pong) > HB_PING_TIME + HB_PONG_TIME
1130
-
1131
-
1132
1080
  def new_socket() -> socket.socket:
1133
1081
  """
1134
1082
  Create a new socket with OS-specific parameters
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PyChromecast"
7
- version = "14.0.3"
7
+ version = "14.0.5"
8
8
  description = "Python module to talk to Google Chromecast."
9
9
  readme = "README.rst"
10
10
  authors = [
File without changes
File without changes
File without changes
File without changes