PyChromecast 14.0.3__tar.gz → 14.0.4__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.
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PKG-INFO +1 -1
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/PKG-INFO +1 -1
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/SOURCES.txt +1 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/const.py +2 -0
- PyChromecast-14.0.4/pychromecast/controllers/heartbeat.py +90 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/socket_client.py +19 -83
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pyproject.toml +1 -1
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/LICENSE +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/MANIFEST.in +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/dependency_links.txt +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/not-zip-safe +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/requires.txt +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/PyChromecast.egg-info/top_level.txt +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/README.rst +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/__init__.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/config.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/__init__.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/bbciplayer.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/bbcsounds.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/bubbleupnp.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/dashcast.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/homeassistant.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/homeassistant_media.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/media.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/multizone.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/nrkradio.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/nrktv.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/plex.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/receiver.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/shaka.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/supla.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/yleareena.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/controllers/youtube.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/dial.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/discovery.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/error.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/authority_keys_pb2.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/authority_keys_pb2.pyi +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/cast_channel_pb2.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/cast_channel_pb2.pyi +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/logging_pb2.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/logging_pb2.pyi +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/generated/readme.txt +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/models.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/quick_play.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/pychromecast/response_handler.py +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/requirements.txt +0 -0
- {PyChromecast-14.0.3 → PyChromecast-14.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyChromecast
|
|
3
|
-
Version: 14.0.
|
|
3
|
+
Version: 14.0.4
|
|
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
|
+
Version: 14.0.4
|
|
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
|
|
@@ -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,9 @@ 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 .const import MESSAGE_TYPE, PLATFORM_DESTINATION_ID, REQUEST_ID, SESSION_ID
|
|
28
28
|
from .controllers import BaseController, CallbackType
|
|
29
|
+
from .controllers.heartbeat import NS_HEARTBEAT, HeartbeatController
|
|
29
30
|
from .controllers.media import MediaController
|
|
30
31
|
from .controllers.receiver import CastStatus, CastStatusListener, ReceiverController
|
|
31
32
|
from .dial import get_host_from_service
|
|
@@ -42,12 +43,7 @@ from .generated.cast_channel_pb2 import CastMessage
|
|
|
42
43
|
from .models import HostServiceInfo, MDNSServiceInfo
|
|
43
44
|
|
|
44
45
|
NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection"
|
|
45
|
-
NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat"
|
|
46
46
|
|
|
47
|
-
PLATFORM_DESTINATION_ID = "receiver-0"
|
|
48
|
-
|
|
49
|
-
TYPE_PING = "PING"
|
|
50
|
-
TYPE_PONG = "PONG"
|
|
51
47
|
TYPE_CONNECT = "CONNECT"
|
|
52
48
|
TYPE_CLOSE = "CLOSE"
|
|
53
49
|
TYPE_LOAD = "LOAD"
|
|
@@ -65,10 +61,7 @@ CONNECTION_STATUS_FAILED_RESOLVE = "FAILED_RESOLVE"
|
|
|
65
61
|
# The socket connection was lost and needs to be retried
|
|
66
62
|
CONNECTION_STATUS_LOST = "LOST"
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
HB_PONG_TIME = 10
|
|
70
|
-
POLL_TIME_BLOCKING = 5.0
|
|
71
|
-
POLL_TIME_NON_BLOCKING = 0.01
|
|
64
|
+
SELECT_TIMEOUT = 5.0
|
|
72
65
|
TIMEOUT_TIME = 30.0
|
|
73
66
|
RETRY_TIME = 5.0
|
|
74
67
|
|
|
@@ -540,7 +533,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
540
533
|
self.logger.debug("Thread started...")
|
|
541
534
|
while not self.stop.is_set():
|
|
542
535
|
try:
|
|
543
|
-
if self._run_once(
|
|
536
|
+
if self._run_once() == 1:
|
|
544
537
|
break
|
|
545
538
|
except Exception: # pylint: disable=broad-except
|
|
546
539
|
self._force_recon = True
|
|
@@ -555,7 +548,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
555
548
|
# Clean up
|
|
556
549
|
self._cleanup()
|
|
557
550
|
|
|
558
|
-
def _run_once(self
|
|
551
|
+
def _run_once(self) -> int:
|
|
559
552
|
"""Receive from the socket and handle data."""
|
|
560
553
|
# pylint: disable=too-many-branches, too-many-statements, too-many-return-statements
|
|
561
554
|
|
|
@@ -568,9 +561,13 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
568
561
|
# A connection has been established at this point by self._check_connection
|
|
569
562
|
assert self.socket is not None
|
|
570
563
|
|
|
571
|
-
#
|
|
564
|
+
# Poll the socket and the socketpair, with a timeout of SELECT_TIMEOUT
|
|
565
|
+
# The timeout ensures we call _check_connection often enough to avoid
|
|
566
|
+
# the HeartbeatController from detecting a timeout
|
|
567
|
+
# The socketpair allow us to be interrupted on shutdown without waiting
|
|
568
|
+
# for the SELECT_TIMEOUT timout to expire
|
|
572
569
|
try:
|
|
573
|
-
ready = self.selector.select(
|
|
570
|
+
ready = self.selector.select(SELECT_TIMEOUT)
|
|
574
571
|
except (ValueError, OSError) as exc:
|
|
575
572
|
self.logger.error(
|
|
576
573
|
"[%s(%s):%s] Error in select call: %s",
|
|
@@ -689,7 +686,10 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
689
686
|
# route message to handlers
|
|
690
687
|
if message.namespace in self._handlers:
|
|
691
688
|
# debug messages
|
|
692
|
-
if
|
|
689
|
+
if (
|
|
690
|
+
message.namespace != NS_HEARTBEAT
|
|
691
|
+
or self.heartbeat_controller.logger.isEnabledFor(logging.DEBUG)
|
|
692
|
+
):
|
|
693
693
|
self.logger.debug(
|
|
694
694
|
"[%s(%s):%s] Received: %s",
|
|
695
695
|
self.fn or "",
|
|
@@ -873,7 +873,10 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
873
873
|
be_size = pack(">I", msg.ByteSize())
|
|
874
874
|
|
|
875
875
|
# Log all messages except heartbeat
|
|
876
|
-
if
|
|
876
|
+
if (
|
|
877
|
+
msg.namespace != NS_HEARTBEAT
|
|
878
|
+
or self.heartbeat_controller.logger.isEnabledFor(logging.DEBUG)
|
|
879
|
+
):
|
|
877
880
|
self.logger.debug(
|
|
878
881
|
"[%s(%s):%s] Sending: %s",
|
|
879
882
|
self.fn or "",
|
|
@@ -1062,73 +1065,6 @@ class ConnectionController(BaseController):
|
|
|
1062
1065
|
return False
|
|
1063
1066
|
|
|
1064
1067
|
|
|
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
1068
|
def new_socket() -> socket.socket:
|
|
1133
1069
|
"""
|
|
1134
1070
|
Create a new socket with OS-specific parameters
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|