Hello community,

here is the log from the commit of package python-PyChromecast for 
openSUSE:Leap:15.2 checked in at 2020-05-06 20:41:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-PyChromecast (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python-PyChromecast.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-PyChromecast"

Wed May  6 20:41:51 2020 rev:7 rq:800726 version:5.0.0

Changes:
--------
--- 
/work/SRC/openSUSE:Leap:15.2/python-PyChromecast/python-PyChromecast.changes    
    2020-03-29 14:56:26.179192667 +0200
+++ 
/work/SRC/openSUSE:Leap:15.2/.python-PyChromecast.new.2738/python-PyChromecast.changes
      2020-05-06 20:41:52.180828967 +0200
@@ -1,0 +2,19 @@
+Tue May  5 06:46:04 UTC 2020 - Johannes Grassler <[email protected]>
+
+- Update to 5.0.0
+  * remove .travis.yml
+  * extract test reqs
+  * Update test.yml
+  * Create test.yml
+  * Bump linters, run flake and black on examples (#355)
+  * Speed up SocketClient shutdown (#352)
+  * Improve debug messages (#353)
+  * Fix bugs in Chromecast.is_idle and ReceiverController.launch_app (#350)
+  * Improve examples and docstrings (#351)
+  * Remove blocking option from Chromecast (#349)
+  * Add helper function get_listed_chromecasts (#348)
+  * Correct mistake in PR#345 (#346)
+  * Lookup manufacturer, remove multizone helper. (#345)
+- Update python-zeroconf Require
+ 
+-------------------------------------------------------------------

Old:
----
  PyChromecast-4.2.0.tar.gz

New:
----
  PyChromecast-5.0.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-PyChromecast.spec ++++++
--- /var/tmp/diff_new_pack.NwWcBW/_old  2020-05-06 20:41:52.584829803 +0200
+++ /var/tmp/diff_new_pack.NwWcBW/_new  2020-05-06 20:41:52.584829803 +0200
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-PyChromecast
-Version:        4.2.0
+Version:        5.0.0
 Release:        0
 Summary:        Python module to talk to Google Chromecast
 License:        MIT
@@ -32,7 +32,7 @@
 Requires:       python-casttube >= 0.2.0
 Requires:       python-protobuf >= 3.0.0
 Requires:       python-requests >= 2.0
-Requires:       python-zeroconf >= 0.24.4
+Requires:       python-zeroconf >= 0.25.1
 BuildArch:      noarch
 %python_subpackages
 

++++++ PyChromecast-4.2.0.tar.gz -> PyChromecast-5.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/PKG-INFO 
new/PyChromecast-5.0.0/PKG-INFO
--- old/PyChromecast-4.2.0/PKG-INFO     2020-03-17 17:56:27.838616000 +0100
+++ new/PyChromecast-5.0.0/PKG-INFO     2020-04-20 17:28:40.250786500 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: PyChromecast
-Version: 4.2.0
+Version: 5.0.0
 Summary: Python module to talk to Google Chromecast.
 Home-page: https://github.com/balloob/pychromecast
 Author: Paulus Schoutsen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/PyChromecast.egg-info/PKG-INFO 
new/PyChromecast-5.0.0/PyChromecast.egg-info/PKG-INFO
--- old/PyChromecast-4.2.0/PyChromecast.egg-info/PKG-INFO       2020-03-17 
17:56:27.000000000 +0100
+++ new/PyChromecast-5.0.0/PyChromecast.egg-info/PKG-INFO       2020-04-20 
17:28:40.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: PyChromecast
-Version: 4.2.0
+Version: 5.0.0
 Summary: Python module to talk to Google Chromecast.
 Home-page: https://github.com/balloob/pychromecast
 Author: Paulus Schoutsen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/PyChromecast.egg-info/SOURCES.txt 
new/PyChromecast-5.0.0/PyChromecast.egg-info/SOURCES.txt
--- old/PyChromecast-4.2.0/PyChromecast.egg-info/SOURCES.txt    2020-03-17 
17:56:27.000000000 +0100
+++ new/PyChromecast-5.0.0/PyChromecast.egg-info/SOURCES.txt    2020-04-20 
17:28:40.000000000 +0200
@@ -14,6 +14,7 @@
 pychromecast/authority_keys_pb2.py
 pychromecast/cast_channel_pb2.py
 pychromecast/config.py
+pychromecast/const.py
 pychromecast/dial.py
 pychromecast/discovery.py
 pychromecast/error.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/PyChromecast-4.2.0/PyChromecast.egg-info/requires.txt 
new/PyChromecast-5.0.0/PyChromecast.egg-info/requires.txt
--- old/PyChromecast-4.2.0/PyChromecast.egg-info/requires.txt   2020-03-17 
17:56:27.000000000 +0100
+++ new/PyChromecast-5.0.0/PyChromecast.egg-info/requires.txt   2020-04-20 
17:28:40.000000000 +0200
@@ -1,4 +1,4 @@
 requests>=2.0
 protobuf>=3.0.0
-zeroconf>=0.24.4
+zeroconf>=0.25.1
 casttube>=0.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/__init__.py 
new/PyChromecast-5.0.0/pychromecast/__init__.py
--- old/PyChromecast-4.2.0/pychromecast/__init__.py     2020-03-17 
17:56:15.000000000 +0100
+++ new/PyChromecast-5.0.0/pychromecast/__init__.py     2020-04-20 
17:28:34.000000000 +0200
@@ -3,20 +3,21 @@
 """
 import logging
 import fnmatch
+from threading import Event
 
 # pylint: disable=wildcard-import
 import threading
 from .config import *  # noqa
 from .error import *  # noqa
 from . import socket_client
-from .discovery import discover_chromecasts, start_discovery, stop_discovery
-from .dial import (
-    get_device_status,
-    reboot,
-    DeviceStatus,
-    CAST_TYPES,
-    CAST_TYPE_CHROMECAST,
+from .discovery import (
+    DISCOVER_TIMEOUT,
+    discover_chromecasts,
+    start_discovery,
+    stop_discovery,
 )
+from .dial import get_device_status, reboot, DeviceStatus
+from .const import CAST_MANUFACTURERS, CAST_TYPES, CAST_TYPE_CHROMECAST
 from .controllers.media import STREAM_TYPE_BUFFERED  # noqa
 
 __all__ = ("__version__", "__version_info__", "get_chromecasts", "Chromecast")
@@ -29,9 +30,7 @@
 _LOGGER = logging.getLogger(__name__)
 
 
-def _get_chromecast_from_host(
-    host, tries=None, retry_wait=None, timeout=None, blocking=True
-):
+def get_chromecast_from_host(host, tries=None, retry_wait=None, timeout=None):
     """Creates a Chromecast object from a zeroconf host."""
     # Build device status from the mDNS info, this information is
     # the primary source and the remaining will be fetched
@@ -39,10 +38,11 @@
     ip_address, port, uuid, model_name, friendly_name = host
     _LOGGER.debug("_get_chromecast_from_host %s", host)
     cast_type = CAST_TYPES.get(model_name.lower(), CAST_TYPE_CHROMECAST)
+    manufacturer = CAST_MANUFACTURERS.get(model_name.lower(), "Google Inc.")
     device = DeviceStatus(
         friendly_name=friendly_name,
         model_name=model_name,
-        manufacturer=None,
+        manufacturer=manufacturer,
         uuid=uuid,
         cast_type=cast_type,
     )
@@ -53,13 +53,14 @@
         tries=tries,
         timeout=timeout,
         retry_wait=retry_wait,
-        blocking=blocking,
     )
 
 
-def _get_chromecast_from_service(
-    services, tries=None, retry_wait=None, timeout=None, blocking=True
-):
+# Alias for backwards compatibility
+_get_chromecast_from_host = get_chromecast_from_host  # pylint: 
disable=invalid-name
+
+
+def get_chromecast_from_service(services, tries=None, retry_wait=None, 
timeout=None):
     """Creates a Chromecast object from a zeroconf service."""
     # Build device status from the mDNS service name info, this
     # information is the primary source and the remaining will be
@@ -67,10 +68,11 @@
     services, zconf, uuid, model_name, friendly_name = services
     _LOGGER.debug("_get_chromecast_from_service %s", services)
     cast_type = CAST_TYPES.get(model_name.lower(), CAST_TYPE_CHROMECAST)
+    manufacturer = CAST_MANUFACTURERS.get(model_name.lower(), "Google Inc.")
     device = DeviceStatus(
         friendly_name=friendly_name,
         model_name=model_name,
-        manufacturer=None,
+        manufacturer=manufacturer,
         uuid=uuid,
         cast_type=cast_type,
     )
@@ -80,32 +82,93 @@
         tries=tries,
         timeout=timeout,
         retry_wait=retry_wait,
-        blocking=blocking,
         services=services,
         zconf=zconf,
     )
 
 
+# Alias for backwards compatibility
+_get_chromecast_from_service = (  # pylint: disable=invalid-name
+    get_chromecast_from_service
+)
+
+
+def get_listed_chromecasts(
+    friendly_names=None,
+    uuids=None,
+    tries=None,
+    retry_wait=None,
+    timeout=None,
+    discovery_timeout=DISCOVER_TIMEOUT,
+):
+    """
+    Searches the network for chromecast devices matching a list of friendly
+    names or a list of UUIDs.
+
+    Returns a list of discovered chromecast devices matching the criteria,
+    or an empty list if no matching chromecasts were found.
+
+    :param friendly_names: A list of wanted friendly names
+    :param uuids: A list of wanted uuids
+    :param tries: passed to get_chromecasts
+    :param retry_wait: passed to get_chromecasts
+    :param timeout: passed to get_chromecasts
+    :param discovery_timeout: A floating point number specifying the time to 
wait
+                               devices matching the criteria have been found.
+    """
+
+    cc_list = set()
+
+    def callback(chromecast):
+        _LOGGER.debug("Found chromecast %s", chromecast)
+        if uuids and chromecast.uuid in uuids:
+            cc_list.add(chromecast)
+            uuids.remove(chromecast.uuid)
+        elif friendly_names and chromecast.name in friendly_names:
+            cc_list.add(chromecast)
+            friendly_names.remove(chromecast.name)
+        if not friendly_names and not uuids:
+            discover_complete.set()
+
+    discover_complete = Event()
+    internal_stop = get_chromecasts(
+        tries=tries,
+        retry_wait=retry_wait,
+        timeout=timeout,
+        callback=callback,
+        blocking=False,
+    )
+    # Wait for the timeout or found all wanted devices
+    discover_complete.wait(discovery_timeout)
+    internal_stop()
+    return list(cc_list)
+
+
 # pylint: disable=too-many-locals
 def get_chromecasts(
     tries=None, retry_wait=None, timeout=None, blocking=True, callback=None
 ):
     """
-    Searches the network for chromecast devices.
-
-    If blocking = True, returns a list of discovered chromecast devices.
-    If blocking = False, triggers a callback for each discovered chromecast,
-                         and returns a function which can be executed to stop
-                         discovery.
+    Searches the network for chromecast devices and creates a Chromecast 
instance
+    for each discovered device.
 
     May return an empty list if no chromecasts were found.
 
-    Tries is specified if you want to limit the number of times the
-    underlying socket associated with your Chromecast objects will
-    retry connecting if connection is lost or it fails to connect
-    in the first place. The number of seconds spent between each retry
-    can be defined by passing the retry_wait parameter, the default is
-    to wait 5 seconds.
+    Parameters tries, timeout, retry_wait and blocking_app_launch controls the
+    behavior of the created Chromecast instances.
+
+    :param tries: Number of retries to perform if the connection fails.
+                  None for inifinite retries.
+    :param timeout: A floating point number specifying the socket timeout in
+                    seconds. None means to use the default which is 30 seconds.
+    :param retry_wait: A floating point number specifying how many seconds to
+                       wait between each retry. None means to use the default
+                       which is 5 seconds.
+    :param blocking: If True, returns a list of discovered chromecast devices.
+                     If False, triggers a callback for each discovered 
chromecast,
+                     and returns a function which can be executed to stop 
discovery.
+    :param callback: Callback which is triggerd for each discovered chromecast 
when
+                     blocking = False.
     """
     if blocking:
         # Thread blocking chromecast discovery
@@ -114,12 +177,8 @@
         for host in hosts:
             try:
                 cc_list.append(
-                    _get_chromecast_from_host(
-                        host,
-                        tries=tries,
-                        retry_wait=retry_wait,
-                        timeout=timeout,
-                        blocking=blocking,
+                    get_chromecast_from_host(
+                        host, tries=tries, retry_wait=retry_wait, 
timeout=timeout
                     )
                 )
             except ChromecastConnectionError:  # noqa
@@ -134,12 +193,11 @@
         """Called when zeroconf has discovered a new chromecast."""
         try:
             callback(
-                _get_chromecast_from_host(
+                get_chromecast_from_host(
                     listener.services[name],
                     tries=tries,
                     retry_wait=retry_wait,
                     timeout=timeout,
-                    blocking=blocking,
                 )
             )
         except ChromecastConnectionError:  # noqa
@@ -158,6 +216,7 @@
     """
     Class to interface with a ChromeCast.
 
+    :param host: The host to connect to.
     :param port: The port to use when connecting to the device, set to None to
                  use the default of 8009. Special devices such as Cast Groups
                  may return a different port number so we need to use that.
@@ -170,13 +229,21 @@
     :param retry_wait: A floating point number specifying how many seconds to
                        wait between each retry. None means to use the default
                        which is 5 seconds.
+    :param services: A list of mDNS services to try to connect to. If present,
+                     parameters host and port are ignored and host and port are
+                     instead resolved through mDNS. The list of services may be
+                     modified, for example if speaker group leadership is 
handed
+                     over. SocketClient will catch modifications to the list 
when
+                     attempting reconnect.
+    :param zconf: A zeroconf instance, needed if a list of services is passed.
+                  The zeroconf instance may be obtained from the browser 
returned by
+                  pychromecast.start_discovery().
     """
 
     def __init__(self, host, port=None, device=None, **kwargs):
         tries = kwargs.pop("tries", None)
         timeout = kwargs.pop("timeout", None)
         retry_wait = kwargs.pop("retry_wait", None)
-        blocking = kwargs.pop("blocking", True)
         services = kwargs.pop("services", None)
         zconf = kwargs.pop("zconf", True)
 
@@ -223,7 +290,6 @@
             tries=tries,
             timeout=timeout,
             retry_wait=retry_wait,
-            blocking=blocking,
             services=services,
             zconf=zconf,
         )
@@ -260,7 +326,11 @@
         return (
             self.status is None
             or self.app_id in (None, IDLE_APP_ID)
-            or (not self.status.is_active_input and not self.ignore_cec)
+            or (
+                self.cast_type == CAST_TYPE_CHROMECAST
+                and not self.status.is_active_input
+                and not self.ignore_cec
+            )
         )
 
     @property
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/const.py 
new/PyChromecast-5.0.0/pychromecast/const.py
--- old/PyChromecast-4.2.0/pychromecast/const.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/PyChromecast-5.0.0/pychromecast/const.py        2020-04-20 
17:28:34.000000000 +0200
@@ -0,0 +1,23 @@
+"""
+Chromecast constants
+"""
+# Regular chromecast, supports video/audio
+CAST_TYPE_CHROMECAST = "cast"
+# Cast Audio device, supports only audio
+CAST_TYPE_AUDIO = "audio"
+# Cast Audio group device, supports only audio
+CAST_TYPE_GROUP = "group"
+
+MF_GOOGLE = "Google Inc."
+
+CAST_TYPES = {
+    "chromecast": CAST_TYPE_CHROMECAST,
+    "eureka dongle": CAST_TYPE_CHROMECAST,
+    "chromecast audio": CAST_TYPE_AUDIO,
+    "google home": CAST_TYPE_AUDIO,
+    "google home mini": CAST_TYPE_AUDIO,
+    "google cast group": CAST_TYPE_GROUP,
+}
+
+# Known models not manufactured by Google
+CAST_MANUFACTURERS = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/controllers/plex.py 
new/PyChromecast-5.0.0/pychromecast/controllers/plex.py
--- old/PyChromecast-4.2.0/pychromecast/controllers/plex.py     2020-03-17 
17:56:15.000000000 +0100
+++ new/PyChromecast-5.0.0/pychromecast/controllers/plex.py     2020-04-20 
17:28:34.000000000 +0200
@@ -322,10 +322,8 @@
         Args:
             status (None, optional): override for on/off
         """
-        if status is not None:
-            status = status
-        else:
-            status = not status.volume_muted
+        if status is None:
+            status = not self.status.volume_muted
 
         self._socket_client.receiver_controller.set_volume_muted(status)
 
@@ -517,9 +515,11 @@
 
     def disable_subtitle(self):
         """Disable a subtitle."""
-        _, __, part = (
-            self._get_current_media()
-        )  # noqa: 501 pylint disable=unused-variable
+        (
+            _,
+            __,
+            part,
+        ) = self._get_current_media()  # noqa: 501 pylint 
disable=unused-variable
         part.resetDefaultSubtitleStream()
         self._reset_playback()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/dial.py 
new/PyChromecast-5.0.0/pychromecast/dial.py
--- old/PyChromecast-4.2.0/pychromecast/dial.py 2020-03-17 17:56:15.000000000 
+0100
+++ new/PyChromecast-5.0.0/pychromecast/dial.py 2020-04-20 17:28:34.000000000 
+0200
@@ -7,28 +7,13 @@
 import logging
 import requests
 
+from .const import CAST_TYPE_CHROMECAST
 from .discovery import get_info_from_service, get_host_from_service_info
 
 XML_NS_UPNP_DEVICE = "{urn:schemas-upnp-org:device-1-0}"
 
 FORMAT_BASE_URL = "http://{}:8008";
 
-# Regular chromecast, supports video/audio
-CAST_TYPE_CHROMECAST = "cast"
-# Cast Audio device, supports only audio
-CAST_TYPE_AUDIO = "audio"
-# Cast Audio group device, supports only audio
-CAST_TYPE_GROUP = "group"
-
-CAST_TYPES = {
-    "chromecast": CAST_TYPE_CHROMECAST,
-    "eureka dongle": CAST_TYPE_CHROMECAST,
-    "chromecast audio": CAST_TYPE_AUDIO,
-    "google home": CAST_TYPE_AUDIO,
-    "google home mini": CAST_TYPE_AUDIO,
-    "google cast group": CAST_TYPE_GROUP,
-}
-
 _LOGGER = logging.getLogger(__name__)
 
 
@@ -90,15 +75,14 @@
         status = _get_status(host, services, zconf, 
"/setup/eureka_info?options=detail")
 
         friendly_name = status.get("name", "Unknown Chromecast")
+        # model_name and manufacturer is no longer included in the response,
+        # mark as unknown
         model_name = "Unknown model name"
         manufacturer = "Unknown manufacturer"
-        if "detail" in status:
-            model_name = status["detail"].get("model_name", model_name)
-            manufacturer = status["detail"].get("manufacturer", manufacturer)
 
         udn = status.get("ssdp_udn", None)
 
-        cast_type = CAST_TYPES.get(model_name.lower(), CAST_TYPE_CHROMECAST)
+        cast_type = CAST_TYPE_CHROMECAST
 
         uuid = None
         if udn:
@@ -110,49 +94,6 @@
         return None
 
 
-def get_multizone_status(host, services=None, zconf=None):
-    """
-    :param host: Hostname or ip to fetch status from
-    :type host: str
-    :return: The multizone status as a named tuple.
-    :rtype: pychromecast.dial.MultizoneStatus or None
-    """
-
-    try:
-        status = status = _get_status(
-            host, services, zconf, "/setup/eureka_info?params=multizone"
-        )
-
-        dynamic_groups = []
-        if "multizone" in status and "dynamic_groups" in status["multizone"]:
-            for group in status["multizone"]["dynamic_groups"]:
-                name = group.get("name", "Unknown group name")
-                udn = group.get("uuid", None)
-                uuid = None
-                if udn:
-                    uuid = UUID(udn.replace("-", ""))
-                dynamic_groups.append(MultizoneInfo(name, uuid))
-
-        groups = []
-        if "multizone" in status and "groups" in status["multizone"]:
-            for group in status["multizone"]["groups"]:
-                name = group.get("name", "Unknown group name")
-                udn = group.get("uuid", None)
-                uuid = None
-                if udn:
-                    uuid = UUID(udn.replace("-", ""))
-                groups.append(MultizoneInfo(name, uuid))
-
-        return MultizoneStatus(dynamic_groups, groups)
-
-    except (requests.exceptions.RequestException, OSError, ValueError):
-        return None
-
-
 DeviceStatus = namedtuple(
     "DeviceStatus", ["friendly_name", "model_name", "manufacturer", "uuid", 
"cast_type"]
 )
-
-MultizoneInfo = namedtuple("MultizoneInfo", ["friendly_name", "uuid"])
-
-MultizoneStatus = namedtuple("MultizoneStatus", ["dynamic_groups", "groups"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/discovery.py 
new/PyChromecast-5.0.0/pychromecast/discovery.py
--- old/PyChromecast-4.2.0/pychromecast/discovery.py    2020-03-17 
17:56:15.000000000 +0100
+++ new/PyChromecast-5.0.0/pychromecast/discovery.py    2020-04-20 
17:28:34.000000000 +0200
@@ -1,6 +1,7 @@
 """Discovers Chromecasts on the network using mDNS/zeroconf."""
 import logging
 import socket
+from threading import Event
 from uuid import UUID
 
 import zeroconf
@@ -122,8 +123,6 @@
 
 def discover_chromecasts(max_devices=None, timeout=DISCOVER_TIMEOUT):
     """ Discover chromecasts on the network. """
-    from threading import Event
-
     browser = False
     try:
         # pylint: disable=unused-argument
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/pychromecast/socket_client.py 
new/PyChromecast-5.0.0/pychromecast/socket_client.py
--- old/PyChromecast-4.2.0/pychromecast/socket_client.py        2020-03-17 
17:56:15.000000000 +0100
+++ new/PyChromecast-5.0.0/pychromecast/socket_client.py        2020-04-20 
17:28:34.000000000 +0200
@@ -23,14 +23,13 @@
 from . import cast_channel_pb2
 from .controllers import BaseController
 from .controllers.media import MediaController
-from .dial import CAST_TYPE_CHROMECAST, CAST_TYPE_AUDIO, CAST_TYPE_GROUP
+from .const import CAST_TYPE_AUDIO, CAST_TYPE_CHROMECAST, CAST_TYPE_GROUP
 from .discovery import get_info_from_service, get_host_from_service_info
 from .error import (
     ChromecastConnectionError,
     UnsupportedNamespace,
     NotConnected,
     PyChromecastStopped,
-    LaunchError,
 )
 
 NS_CONNECTION = "urn:x-cast:com.google.cast.tp.connection"
@@ -156,6 +155,7 @@
     """
     Class to interact with a Chromecast through a socket.
 
+    :param host: The host to connect to.
     :param port: The port to use when connecting to the device, set to None to
                  use the default of 8009. Special devices such as Cast Groups
                  may return a different port number so we need to use that.
@@ -163,24 +163,29 @@
                       dial.CAST_TYPE_* for types.
     :param tries: Number of retries to perform if the connection fails.
                   None for inifinite retries.
+    :param timeout: A floating point number specifying the socket timeout in
+                    seconds. None means to use the default which is 30 seconds.
     :param retry_wait: A floating point number specifying how many seconds to
                        wait between each retry. None means to use the default
                        which is 5 seconds.
+    :param services: A list of mDNS services to try to connect to. If present,
+                     parameters host and port are ignored and host and port are
+                     instead resolved through mDNS. The list of services may be
+                     modified, for example if speaker group leadership is 
handed
+                     over. SocketClient will catch modifications to the list 
when
+                     attempting reconnect.
+    :param zconf: A zeroconf instance, needed if a list of services is passed.
+                  The zeroconf instance may be obtained from the browser 
returned by
+                  pychromecast.start_discovery().
     """
 
     def __init__(self, host, port=None, cast_type=CAST_TYPE_CHROMECAST, 
**kwargs):
         tries = kwargs.pop("tries", None)
         timeout = kwargs.pop("timeout", None)
         retry_wait = kwargs.pop("retry_wait", None)
-        self.blocking = kwargs.pop("blocking", True)
         services = kwargs.pop("services", None)
         zconf = kwargs.pop("zconf", None)
 
-        if self.blocking:
-            self.polltime = POLL_TIME_BLOCKING
-        else:
-            self.polltime = POLL_TIME_NON_BLOCKING
-
         super(SocketClient, self).__init__()
 
         self.daemon = True
@@ -201,6 +206,8 @@
 
         self.source_id = "sender-0"
         self.stop = threading.Event()
+        # socketpair used to interrupt the worker thread
+        self.socketpair = socket.socketpair()
 
         self.app_namespaces = []
         self.destination_id = None
@@ -211,13 +218,14 @@
         self._open_channels = []
 
         self.connecting = True
+        self.first_connection = True
         self.socket = None
 
         # dict mapping namespace on Controller objects
         self._handlers = {}
         self._connection_listeners = []
 
-        self.receiver_controller = ReceiverController(cast_type, self.blocking)
+        self.receiver_controller = ReceiverController(cast_type)
         self.media_controller = MediaController()
         self.heartbeat_controller = HeartbeatController()
 
@@ -229,7 +237,7 @@
         self.receiver_controller.register_status_listener(self)
 
     def initialize_connection(
-        self
+        self,
     ):  # noqa: E501 pylint:disable=too-many-statements, too-many-branches
         """Initialize a socket to a Chromecast, retrying as necessary."""
         tries = self.tries
@@ -302,8 +310,9 @@
                             except (AttributeError, KeyError, UnicodeError):
                                 pass
                             self.logger.debug(
-                                "[%s:%s] Resolved service %s to %s:%s",
-                                self.fn or self.host,
+                                "[%s(%s):%s] Resolved service %s to %s:%s",
+                                self.fn or "",
+                                self.host,
                                 self.port,
                                 service,
                                 host,
@@ -313,8 +322,9 @@
                             self.port = port
                         else:
                             self.logger.debug(
-                                "[%s:%s] Failed to resolve service %s",
-                                self.fn or self.host,
+                                "[%s(%s):%s] Failed to resolve service %s",
+                                self.fn or "",
+                                self.host,
                                 self.port,
                                 service,
                             )
@@ -330,8 +340,9 @@
                             continue
 
                     self.logger.debug(
-                        "[%s:%s] Connecting to %s:%s",
-                        self.fn or self.host,
+                        "[%s(%s):%s] Connecting to %s:%s",
+                        self.fn or "",
+                        self.host,
                         self.port,
                         self.host,
                         self.port,
@@ -350,17 +361,29 @@
                     self.heartbeat_controller.ping()
                     self.heartbeat_controller.reset()
 
-                    self.logger.debug(
-                        "[%s:%s] Connected!", self.fn or self.host, self.port
-                    )
+                    if self.first_connection:
+                        self.first_connection = False
+                        self.logger.debug(
+                            "[%s(%s):%s] Connected!",
+                            self.fn or "",
+                            self.host,
+                            self.port,
+                        )
+                    else:
+                        self.logger.warning(
+                            "[%s(%s):%s] Connection reestablished!",
+                            self.fn or "",
+                            self.host,
+                            self.port,
+                        )
                     return
                 except OSError as err:
                     self.connecting = True
                     if self.stop.is_set():
                         self.logger.error(
-                            "[%s:%s] Failed to connect: %s. "
-                            "aborting due to stop signal.",
-                            self.fn or self.host,
+                            "[%s(%s):%s] Failed to connect: %s. aborting due 
to stop signal.",
+                            self.fn or "",
+                            self.host,
                             self.port,
                             err,
                         )
@@ -374,9 +397,9 @@
                     )
                     if service is not None:
                         retry_log_fun(
-                            "[%s:%s] Failed to connect to service %s"
-                            ", retrying in %.1fs",
-                            self.fn or self.host,
+                            "[%s(%s):%s] Failed to connect to service %s, 
retrying in %.1fs",
+                            self.fn or "",
+                            self.host,
                             self.port,
                             service,
                             retry["delay"],
@@ -384,8 +407,9 @@
                         mdns_backoff(service, retry)
                     else:
                         retry_log_fun(
-                            "[%s:%s] Failed to connect, retrying in %.1fs",
-                            self.fn or self.host,
+                            "[%s(%s):%s] Failed to connect, retrying in %.1fs",
+                            self.fn or "",
+                            self.host,
                             self.port,
                             self.retry_wait,
                         )
@@ -394,8 +418,9 @@
             # Only sleep if we have another retry remaining
             if tries is None or tries > 1:
                 self.logger.debug(
-                    "[%s:%s] Not connected, sleeping for %.1fs. Services: %s",
-                    self.fn or self.host,
+                    "[%s(%s):%s] Not connected, sleeping for %.1fs. Services: 
%s",
+                    self.fn or "",
+                    self.host,
                     self.port,
                     self.retry_wait,
                     self.services,
@@ -407,7 +432,10 @@
 
         self.stop.set()
         self.logger.error(
-            "[%s:%s] Failed to connect. No retries.", self.fn or self.host, 
self.port
+            "[%s(%s):%s] Failed to connect. No retries.",
+            self.fn or "",
+            self.host,
+            self.port,
         )
         raise ChromecastConnectionError("Failed to connect")
 
@@ -429,6 +457,12 @@
     def disconnect(self):
         """ Disconnect socket connection to Chromecast device """
         self.stop.set()
+        try:
+            # Write to the socket to interrupt the worker thread
+            self.socketpair[1].send(b"x")
+        except socket.error:
+            # The socketpair may already be closed during shutdown, ignore it
+            pass
 
     def register_handler(self, handler):
         """ Register a new namespace handler. """
@@ -494,13 +528,13 @@
         logging.debug("Thread started...")
         while not self.stop.is_set():
 
-            if self.run_once() == 1:
+            if self.run_once(timeout=POLL_TIME_BLOCKING) == 1:
                 break
 
         # Clean up
         self._cleanup()
 
-    def run_once(self):
+    def run_once(self, timeout=POLL_TIME_NON_BLOCKING):
         """
         Use run_once() in your own main loop after you
         receive something on the socket (get_socket()).
@@ -513,8 +547,9 @@
         except ChromecastConnectionError:
             return 1
 
-        # poll the socket
-        can_read, _, _ = select.select([self.socket], [], [], self.polltime)
+        # poll the socket, as well as the socketpair to allow us to be 
interrupted
+        rlist = [self.socket, self.socketpair[0]]
+        can_read, _, _ = select.select(rlist, [], [], timeout)
 
         # read messages from chromecast
         message = data = None
@@ -524,14 +559,16 @@
             except InterruptLoop as exc:
                 if self.stop.is_set():
                     self.logger.info(
-                        "[%s:%s] Stopped while reading message, " 
"disconnecting.",
-                        self.fn or self.host,
+                        "[%s(%s):%s] Stopped while reading message, 
disconnecting.",
+                        self.fn or "",
+                        self.host,
                         self.port,
                     )
                 else:
                     self.logger.error(
-                        "[%s:%s] Interruption caught without being stopped: " 
"%s",
-                        self.fn or self.host,
+                        "[%s(%s):%s] Interruption caught without being 
stopped: %s",
+                        self.fn or "",
+                        self.host,
                         self.port,
                         exc,
                     )
@@ -544,20 +581,26 @@
             except socket.error:
                 self._force_recon = True
                 self.logger.error(
-                    "[%s:%s] Error reading from socket.",
-                    self.fn or self.host,
+                    "[%s(%s):%s] Error reading from socket.",
+                    self.fn or "",
+                    self.host,
                     self.port,
                 )
             else:
                 data = _json_from_message(message)
-        if not message:
-            return 0
+
+        if self.socketpair[0] in can_read:
+            # Clear the socket's buffer
+            self.socketpair[0].recv(128)
 
         # If we are stopped after receiving a message we skip the message
         # and tear down the connection
         if self.stop.is_set():
             return 1
 
+        if not message:
+            return 0
+
         # See if any handlers will accept this message
         self._route_message(message, data)
 
@@ -591,16 +634,18 @@
         reset = False
         if self._force_recon:
             self.logger.warning(
-                "[%s:%s] Error communicating with socket, resetting " 
"connection",
-                self.fn or self.host,
+                "[%s(%s):%s] Error communicating with socket, resetting 
connection",
+                self.fn or "",
+                self.host,
                 self.port,
             )
             reset = True
 
         elif self.heartbeat_controller.is_expired():
             self.logger.warning(
-                "[%s:%s] Heartbeat timeout, resetting connection",
-                self.fn or self.host,
+                "[%s(%s):%s] Heartbeat timeout, resetting connection",
+                self.fn or "",
+                self.host,
                 self.port,
             )
             reset = True
@@ -628,8 +673,9 @@
             # debug messages
             if message.namespace != NS_HEARTBEAT:
                 self.logger.debug(
-                    "[%s:%s] Received: %s",
-                    self.fn or self.host,
+                    "[%s(%s):%s] Received: %s",
+                    self.fn or "",
+                    self.host,
                     self.port,
                     _message_to_string(message, data),
                 )
@@ -643,18 +689,20 @@
                 if not handled:
                     if data.get(REQUEST_ID) not in self._request_callbacks:
                         self.logger.debug(
-                            "[%s:%s] Message unhandled: %s",
-                            self.fn or self.host,
+                            "[%s(%s):%s] Message unhandled: %s",
+                            self.fn or "",
+                            self.host,
                             self.port,
                             _message_to_string(message, data),
                         )
             except Exception:  # pylint: disable=broad-except
                 self.logger.exception(
                     (
-                        "[%s:%s] Exception caught while sending message to "
+                        "[%s(%s):%s] Exception caught while sending message to 
"
                         "controller %s: %s"
                     ),
-                    self.fn or self.host,
+                    self.fn or "",
+                    self.host,
                     self.port,
                     type(self._handlers[message.namespace]).__name__,
                     _message_to_string(message, data),
@@ -662,8 +710,9 @@
 
         else:
             self.logger.debug(
-                "[%s:%s] Received unknown namespace: %s",
-                self.fn or self.host,
+                "[%s(%s):%s] Received unknown namespace: %s",
+                self.fn or "",
+                self.host,
                 self.port,
                 _message_to_string(message, data),
             )
@@ -685,12 +734,18 @@
         try:
             self.socket.close()
         except Exception:  # pylint: disable=broad-except
-            self.logger.exception("[%s:%s] _cleanup", self.fn or self.host, 
self.port)
+            self.logger.exception(
+                "[%s(%s):%s] _cleanup", self.fn or "", self.host, self.port
+            )
         self._report_connection_status(
             ConnectionStatus(
                 CONNECTION_STATUS_DISCONNECTED, NetworkAddress(self.host, 
self.port)
             )
         )
+
+        self.socketpair[0].close()
+        self.socketpair[1].close()
+
         self.connecting = True
 
     def _report_connection_status(self, status):
@@ -698,17 +753,20 @@
         for listener in self._connection_listeners:
             try:
                 self.logger.debug(
-                    "[%s:%s] connection listener: %x (%s)",
-                    self.fn or self.host,
+                    "[%s(%s):%s] connection listener: %x (%s) %s",
+                    self.fn or "",
+                    self.host,
                     self.port,
                     id(listener),
                     type(listener).__name__,
+                    status,
                 )
                 listener.new_connection_status(status)
             except Exception:  # pylint: disable=broad-except
                 self.logger.exception(
-                    "[%s:%s] Exception thrown when calling connection " 
"listener",
-                    self.fn or self.host,
+                    "[%s(%s):%s] Exception thrown when calling connection 
listener",
+                    self.fn or "",
+                    self.host,
                     self.port,
                 )
 
@@ -794,8 +852,9 @@
         # Log all messages except heartbeat
         if msg.namespace != NS_HEARTBEAT:
             self.logger.debug(
-                "[%s:%s] Sending: %s",
-                self.fn or self.host,
+                "[%s(%s):%s] Sending: %s",
+                self.fn or "",
+                self.host,
                 self.port,
                 _message_to_string(msg, data),
             )
@@ -815,7 +874,10 @@
                 self._request_callbacks.pop(request_id, None)
                 self._force_recon = True
                 self.logger.info(
-                    "[%s:%s] Error writing to socket.", self.fn or self.host, 
self.port
+                    "[%s(%s):%s] Error writing to socket.",
+                    self.fn or "",
+                    self.host,
+                    self.port,
                 )
         else:
             raise NotConnected(
@@ -898,7 +960,7 @@
                 pass
             except Exception:  # pylint: disable=broad-except
                 self.logger.exception(
-                    "[%s:%s] Exception", self.fn or self.host, self.port
+                    "[%s(%s):%s] Exception", self.fn or "", self.host, 
self.port
                 )
 
             self._open_channels.remove(destination_id)
@@ -1003,14 +1065,13 @@
     :param cast_type: Type of Chromecast device.
     """
 
-    def __init__(self, cast_type=CAST_TYPE_CHROMECAST, blocking=True):
+    def __init__(self, cast_type=CAST_TYPE_CHROMECAST):
         super(ReceiverController, self).__init__(NS_RECEIVER, 
target_platform=True)
 
         self.status = None
         self.launch_failure = None
         self.app_to_launch = None
         self.cast_type = cast_type
-        self.blocking = blocking
         self.app_launch_event = threading.Event()
         self.app_launch_event_function = None
 
@@ -1061,7 +1122,7 @@
             Will only launch if it is not currently running unless
             force_launch=True. """
 
-        if not force_launch and self.app_id is None:
+        if not force_launch and self.status is None:
             self.update_status(
                 lambda response: self._send_launch_message(
                     app_id, force_launch, callback_function
@@ -1079,25 +1140,12 @@
             self.app_launch_event_function = callback_function
             self.launch_failure = None
 
-            self.send_message(
-                {MESSAGE_TYPE: TYPE_LAUNCH, APP_ID: app_id},
-                callback_function=lambda response: 
self._block_till_launched(app_id),
-            )
+            self.send_message({MESSAGE_TYPE: TYPE_LAUNCH, APP_ID: app_id})
         else:
             self.logger.info("Not launching app %s - already running", app_id)
             if callback_function:
                 callback_function()
 
-    def _block_till_launched(self, app_id):
-        if self.blocking:
-            self.app_launch_event.wait()
-            if self.launch_failure:
-                raise LaunchError(
-                    "Failed to launch app: {}, Reason: {}".format(
-                        app_id, self.launch_failure.reason
-                    )
-                )
-
     def stop_app(self, callback_function_param=False):
         """ Stops the current running app on the Chromecast. """
         self.logger.info("Receiver:Stopping current app '%s'", self.app_id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/requirements.txt 
new/PyChromecast-5.0.0/requirements.txt
--- old/PyChromecast-4.2.0/requirements.txt     2020-03-17 17:56:15.000000000 
+0100
+++ new/PyChromecast-5.0.0/requirements.txt     2020-04-20 17:28:34.000000000 
+0200
@@ -1,4 +1,4 @@
 requests>=2.0
 protobuf>=3.0.0
-zeroconf>=0.24.4
+zeroconf>=0.25.1
 casttube>=0.2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/PyChromecast-4.2.0/setup.py 
new/PyChromecast-5.0.0/setup.py
--- old/PyChromecast-4.2.0/setup.py     2020-03-17 17:56:15.000000000 +0100
+++ new/PyChromecast-5.0.0/setup.py     2020-04-20 17:28:34.000000000 +0200
@@ -5,7 +5,7 @@
 
 setup(
     name="PyChromecast",
-    version="4.2.0",
+    version="5.0.0",
     license="MIT",
     url="https://github.com/balloob/pychromecast";,
     author="Paulus Schoutsen",


Reply via email to