spotifywebapiPython 1.0.243__py3-none-any.whl → 1.0.251__py3-none-any.whl

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 (33) hide show
  1. spotifywebapipython/const.py +1 -1
  2. spotifywebapipython/docs/__init__.html +56 -2
  3. spotifywebapipython/docs/index.html +5 -2
  4. spotifywebapipython/docs/search.js +1 -1
  5. spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
  6. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  7. spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
  8. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  9. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  11. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +235 -4
  12. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  14. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
  16. spotifywebapipython/models/__init__.py +2 -0
  17. spotifywebapipython/models/episodesimplified.py +41 -0
  18. spotifywebapipython/models/imagepalettecolors.py +111 -0
  19. spotifywebapipython/models/imagevibrantcolors.py +2 -0
  20. spotifywebapipython/models/pageobject.py +1 -0
  21. spotifywebapipython/spotifyclient.py +219 -6
  22. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +227 -76
  23. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +2 -2
  24. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonecontrollerlistener.py +247 -0
  25. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonemanagerlistener.py +384 -0
  26. spotifywebapipython/vibrant/__init__.py +2 -0
  27. spotifywebapipython/vibrant/colorthieffast.py +626 -0
  28. {spotifywebapipython-1.0.243.dist-info → spotifywebapipython-1.0.251.dist-info}/METADATA +3 -2
  29. {spotifywebapipython-1.0.243.dist-info → spotifywebapipython-1.0.251.dist-info}/RECORD +32 -29
  30. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonelistener.py +0 -212
  31. {spotifywebapipython-1.0.243.dist-info → spotifywebapipython-1.0.251.dist-info}/LICENSE +0 -0
  32. {spotifywebapipython-1.0.243.dist-info → spotifywebapipython-1.0.251.dist-info}/WHEEL +0 -0
  33. {spotifywebapipython-1.0.243.dist-info → spotifywebapipython-1.0.251.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,11 @@
1
1
  # external package imports.
2
2
  from abc import abstractmethod
3
- from collections.abc import Mapping
4
3
  import copy
5
4
  from datetime import datetime
6
5
  import hashlib
7
6
  from pychromecast import CastBrowser, CastInfo, Chromecast, get_chromecast_from_cast_info, get_chromecast_from_host
8
7
  from pychromecast.dial import MultizoneInfo, MultizoneStatus, get_multizone_status
9
- from pychromecast.controllers.multizone import MultizoneController
8
+ from pychromecast.controllers.multizone import MultiZoneManagerListener, MultizoneController, MultizoneManager
10
9
  from pychromecast.const import CAST_TYPE_GROUP
11
10
  import socket
12
11
  from soco import SoCo
@@ -19,7 +18,8 @@ from zeroconf import Zeroconf, ServiceBrowser
19
18
  from .spotifyconnectzeroconfexceptions import SpotifyConnectDeviceNotFound
20
19
  from .spotifyconnectzeroconfcastapptask import SpotifyConnectZeroconfCastAppTask
21
20
  from .spotifyconnectzeroconfcastlistener import SpotifyConnectZeroconfCastListener
22
- from .spotifyconnectzeroconfcastmultizonelistener import SpotifyConnectZeroconfCastMultiZoneListener
21
+ from .spotifyconnectzeroconfcastmultizonecontrollerlistener import SpotifyConnectZeroconfCastMultiZoneControllerListener
22
+ from .spotifyconnectzeroconfcastmultizonemanagerlistener import SpotifyConnectZeroconfCastMultiZoneManagerListener
23
23
  from .spotifyconnectdeviceeventargs import SpotifyConnectDeviceEventArgs
24
24
  from .spotifyconnectzeroconflistener import ZEROCONF_SERVICETYPE_SPOTIFYCONNECT, SpotifyConnectZeroconfListener
25
25
  from .spotifyconnectzeroconfcastcontroller import (
@@ -108,6 +108,8 @@ class SpotifyConnectDirectoryTask(threading.Thread):
108
108
  self._CastAppTasks:dict[str, SpotifyConnectZeroconfCastAppTask] = {}
109
109
  self._CastBrowser:CastBrowser = None
110
110
  self._CastMultiZoneControllers:dict[str, MultizoneController] = {}
111
+ self._CastMultiZoneManager:MultizoneManager = None
112
+ self._CastMultiZoneManagerListeners:dict[str, MultiZoneManagerListener] = {}
111
113
  self._InitialDiscoveryTimeout = initialDiscoveryTimeout
112
114
  self._IsStopRequested:bool = False
113
115
  self._SonosPlayers:dict = {}
@@ -269,6 +271,9 @@ class SpotifyConnectDirectoryTask(threading.Thread):
269
271
  handlers=[handler.OnServiceStateChange]
270
272
  )
271
273
 
274
+ # create new chromecast multizone manager that will inform us of multizone events.
275
+ self._CastMultiZoneManager = MultizoneManager()
276
+
272
277
  # create new chromecast zeroconf discovery browser instance.
273
278
  # this will discover Chromecasts on the local network.
274
279
  # when a Chromecast is found, cast_listener.add_cast is called.
@@ -401,6 +406,107 @@ class SpotifyConnectDirectoryTask(threading.Thread):
401
406
  # ignore exceptions, since we are shutting down.
402
407
 
403
408
 
409
+ def _TraceMultizoneGroupMembers(
410
+ self,
411
+ group_uuid:str,
412
+ friendly_name:str,
413
+ ) -> None:
414
+ """
415
+ Adds user-friendly multizone group member list to the trace log.
416
+
417
+ Args:
418
+ uuid (str):
419
+ CastInfo uuid of the multizone group.
420
+ friendly_name (str):
421
+ Friendly name of the multizone group.
422
+ """
423
+ try:
424
+
425
+ # get multizone controller reference for the group.
426
+ castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(group_uuid, None)
427
+ if (castMultizoneController is not None):
428
+
429
+ # trace.
430
+ if (_logsi.IsOn(SILevel.Verbose)):
431
+ membersSummary:list[str] = []
432
+ for member in castMultizoneController.members:
433
+ scMemberDevice:SpotifyConnectDevice = self._SpotifyConnectDevices.GetDeviceByDiscoveryKey(member)
434
+ if (scMemberDevice is None):
435
+ membersSummary.append(member)
436
+ else:
437
+ membersSummary.append("%s [ip=%s]" % (scMemberDevice.Title, scMemberDevice.DiscoveryResult.HostIpTitle))
438
+ _logsi.LogArray(SILevel.Verbose, "%s - Chromecast multizone group members for group: \"%s\" (count=%d)" % (self.name, friendly_name, len(membersSummary)), membersSummary, colorValue=SIColors.Coral)
439
+
440
+ except Exception as ex:
441
+
442
+ # trace.
443
+ _logsi.LogException("Chromecast Multizone group members trace exception: %s" % (str(ex)), ex, logToSystemLogger=False)
444
+ # ignore exception, as nothing can be done about it.
445
+
446
+
447
+ def _VerifyCastGroupMultiZoneController(
448
+ self,
449
+ castDevice:Chromecast,
450
+ scDevice:SpotifyConnectDevice,
451
+ ) -> None:
452
+ """
453
+ Verifies that a multizone controller is registered for the the cast group device,
454
+ and that the controller is connected.
455
+
456
+ A new controller will be registered (or re-registered) if necessary.
457
+
458
+ Args:
459
+ castDevice (Chromecast):
460
+ Cast group device to register the controller for.
461
+ scDevice (SpotifyConnectDevice):
462
+ SpotifyConnectDevice instance that contains extended device information.
463
+
464
+ A controller can go inactive if the device drops off the network and is re-attached.
465
+ This will be detected if it happens, and a new controller instantiated.
466
+ """
467
+ try:
468
+
469
+ # multizone controllers only need to be registered to group devices.
470
+ if (castDevice.cast_type == CAST_TYPE_GROUP):
471
+
472
+ # have we registered a multizone controller for this group yet?
473
+ castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castDevice.uuid), None)
474
+
475
+ # is multizone controller still registered, and connected to the network?
476
+ # the controller can go inactive if the device drops off the network and is re-attached.
477
+ if (castMultizoneController) and (castMultizoneController._socket_client is None):
478
+
479
+ # trace
480
+ _logsi.LogVerbose("Chromecast Multizone Controller is no longer active; removing the controller instance so it can be re-created for the device", colorValue=SIColors.Red)
481
+
482
+ # remove inactive multizone controller listener.
483
+ castMultizoneController.tear_down()
484
+ self._CastMultiZoneControllers.pop(str(castDevice.uuid), None)
485
+ castMultizoneController = None
486
+
487
+ # is multizone controller still registered?
488
+ if (castMultizoneController is None):
489
+
490
+ # trace.
491
+ _logsi.LogVerbose("Chromecast Multizone Controller Listener is being added for Cast device: %s [%s]" % (scDevice.Title, scDevice.DiscoveryResult.HostIpTitle), colorValue=SIColors.Lavender)
492
+
493
+ # add cast device to multizone controller instance.
494
+ # this will add a listener for multizone status events to be processed.
495
+ castMultizoneController = MultizoneController(castDevice.uuid)
496
+ castMultizoneController.register_listener(SpotifyConnectZeroconfCastMultiZoneControllerListener(self, self._ZeroconfInstance, self._Zeroconf_RLock, castMultizoneController, castDevice))
497
+ castDevice.register_handler(castMultizoneController)
498
+ self._CastMultiZoneControllers[str(castDevice.uuid)] = castMultizoneController
499
+
500
+ # request an initial status update.
501
+ castMultizoneController.update_members()
502
+
503
+ except Exception as ex:
504
+
505
+ # trace.
506
+ _logsi.LogException("Chromecast Multizone verify controller exception: %s" % (str(ex)), ex, logToSystemLogger=False)
507
+ # ignore exception, as nothing can be done about it.
508
+
509
+
404
510
  def ActivateCastAppSpotify(
405
511
  self,
406
512
  deviceName:str,
@@ -530,6 +636,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
530
636
  scDevice.ZeroconfResponseInfo = ZeroconfResponse()
531
637
 
532
638
  castDevice:Chromecast = None
639
+ deviceWaitTimeoutSecs:float = 20.0
533
640
 
534
641
  # is this a cast group?
535
642
  if (castInfo.cast_type == CAST_TYPE_GROUP):
@@ -544,46 +651,66 @@ class SpotifyConnectDirectoryTask(threading.Thread):
544
651
  # current group leader host info for each group. we will then create a cast device to
545
652
  # the group leader host address, and start the SpotifyCastApp on that specific host.
546
653
 
547
- # get current multizone status for all groups.
548
- castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
549
- _logsi.LogObject(SILevel.Verbose, "%s - Chromecast group multizone status for device: %s [ip=%s:%s]" % (self.name, scDevice.Title, castInfo.host, castInfo.port), castMultiZoneStatus, colorValue=SIColors.Coral)
654
+ # trace.
655
+ if (_logsi.IsOn(SILevel.Verbose)):
656
+ self._TraceMultizoneGroupMembers(str(castInfo.uuid), castInfo.friendly_name)
550
657
 
551
658
  # default host ip and port in case we don't find the group coordinator.
552
659
  groupHost:str = scDevice.DiscoveryResult.HostIpAddress
553
660
  groupPort:int = scDevice.DiscoveryResult.HostIpPort
554
661
 
555
- # find the group coordinator for the selected device.
556
- zoneInfo:MultizoneInfo = None
557
- for zoneInfo in castMultiZoneStatus.groups:
662
+ try:
558
663
 
559
- # is this our group?
560
- if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
664
+ # request a multizone group member update.
665
+ # note that this will just send a request to the cast group device for its
666
+ # status, which will then update the members list via event listener callbacks.
667
+ castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castInfo.uuid), None)
668
+ if (castMultizoneController) and (castMultizoneController._socket_client):
669
+ castMultizoneController.update_members()
561
670
 
562
- # yes - get host info to use for the device.
563
- # we will use the device discovery result HostIpAddress instead of the zoneInfo.host
564
- # value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
565
- # issues if the device was originally activated using an IPV4 address!
566
- groupHost = scDevice.DiscoveryResult.HostIpAddress
567
- groupPort = scDevice.DiscoveryResult.HostIpPort
568
- break
671
+ except Exception as ex:
672
+
673
+ # trace.
674
+ _logsi.LogException("Chromecast Multizone group update_members exception: %s" % (str(ex)), ex, logToSystemLogger=False)
675
+ # ignore exception, as nothing can be done about it.
569
676
 
570
- # connect to the device and build a Chromecast instance from group coordinator host info.
571
- castDevice = get_chromecast_from_host(
572
- host=(groupHost, groupPort, castInfo.uuid, castInfo.model_name, castInfo.friendly_name),
573
- tries=2,
574
- retry_wait=0.5,
575
- timeout=5)
677
+ # get current multizone status for all groups.
678
+ castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
679
+ _logsi.LogObject(SILevel.Verbose, "%s - Chromecast group multizone status for device: %s [ip=%s:%s]" % (self.name, scDevice.Title, castInfo.host, castInfo.port), castMultiZoneStatus, colorValue=SIColors.Coral)
680
+
681
+ # if group device dropped off the network, then we can't query it!
682
+ if (castMultiZoneStatus is not None):
576
683
 
577
- # calculate wait timeout for devices to become active, based on number of devices in group.
578
- groupWaitTimeoutSecs:float = 15.0
579
- if (castMultiZoneStatus.groups is not None):
580
- groupCount:int = len(castMultiZoneStatus.groups)
581
- if (groupCount > 1):
582
- groupWaitTimeoutSecs = (groupCount * 7.5)
684
+ # find the group coordinator for the selected device.
685
+ zoneInfo:MultizoneInfo = None
686
+ for zoneInfo in castMultiZoneStatus.groups:
583
687
 
584
- # wait for the group device to become ready.
585
- _logsi.LogVerbose("%s - Waiting %d seconds max for Chromecast group multizone device to activate: %s [ip=%s:%s]" % (self.name, groupWaitTimeoutSecs, scDevice.Title, castInfo.host, castInfo.port), colorValue=SIColors.Coral)
586
- castDevice.wait(groupWaitTimeoutSecs)
688
+ # is this our group?
689
+ if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
690
+
691
+ # yes - get host info to use for the device.
692
+ # we will use the device discovery result HostIpAddress instead of the zoneInfo.host
693
+ # value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
694
+ # issues if the device was originally activated using an IPV4 address!
695
+ groupHost = scDevice.DiscoveryResult.HostIpAddress
696
+ groupPort = scDevice.DiscoveryResult.HostIpPort
697
+ break
698
+
699
+ # connect to the device and build a Chromecast instance from group coordinator host info.
700
+ castDevice = get_chromecast_from_host(
701
+ host=(groupHost, groupPort, castInfo.uuid, castInfo.model_name, castInfo.friendly_name),
702
+ tries=2,
703
+ retry_wait=0.5,
704
+ timeout=10)
705
+
706
+ # calculate wait timeout for devices to become active, based on number of devices in group.
707
+ if (castMultiZoneStatus.groups is not None):
708
+ groupCount:int = len(castMultiZoneStatus.groups)
709
+ if (groupCount > 1):
710
+ deviceWaitTimeoutSecs = (groupCount * 5.0)
711
+
712
+ # trace.
713
+ _logsi.LogVerbose("%s - Waiting %d seconds max for Chromecast group multizone device to activate: %s [ip=%s:%s]" % (self.name, deviceWaitTimeoutSecs, scDevice.Title, groupHost, groupPort), colorValue=SIColors.Coral)
587
714
 
588
715
  else:
589
716
 
@@ -595,15 +722,29 @@ class SpotifyConnectDirectoryTask(threading.Thread):
595
722
  zconf=self._ZeroconfInstance,
596
723
  tries=2,
597
724
  retry_wait=0.5,
598
- timeout=3)
725
+ timeout=5)
726
+
727
+ # trace.
728
+ _logsi.LogVerbose("%s - Waiting %d seconds max for Chromecast device to activate: %s [ip=%s:%s]" % (self.name, deviceWaitTimeoutSecs, scDevice.Title, castInfo.host, castInfo.port))
599
729
 
600
- # wait for the device to become ready (10 seconds max).
601
- castDevice.wait(10)
730
+ # were we able to obtain the device reference? if not, then it's an error!
731
+ # this can happen if the device falls off the network and tries to be activated
732
+ # again before zeroconf detects that it was removed.
733
+ if (castDevice is None):
734
+ raise SpotifyApiError("Chromecast device %s [ip=%s] reference could not be obtained; check to ensure it is still available on the network" % (scDevice.Title, scDevice.DiscoveryResult.HostIpTitle), logsi=_logsi)
735
+
736
+ # wait for the device to become ready.
737
+ # this will raise an exception if the device is not available.
738
+ castDevice.wait(deviceWaitTimeoutSecs)
602
739
 
603
740
  # trace.
604
741
  if (_logsi.IsOn(SILevel.Verbose)):
605
742
  _logsi.LogObject(SILevel.Verbose, "%s - Chromecast device object: \"%s\" (ip=%s:%s)" % (self.name, castInfo.friendly_name, castDevice.socket_client.host, castDevice.socket_client.port), castDevice)
606
- _logsi.LogObject(SILevel.Verbose, "%s - Chromecast device status: %s [ip=%s:%s]" % (self.name, scDevice.Title, castDevice.socket_client.host, castDevice.socket_client.port), castDevice.status, colorValue=SIColors.Coral)
743
+ _logsi.LogObject(SILevel.Verbose, "%s - Chromecast device status: %s [ip=%s:%s]" % (self.name, scDevice.Title, castDevice.socket_client.host, castDevice.socket_client.port), castDevice.status)
744
+ self._TraceMultizoneGroupMembers(str(castInfo.uuid), castInfo.friendly_name)
745
+
746
+ # verify multizone controller instance is registered for cast group device.
747
+ self._VerifyCastGroupMultiZoneController(castDevice, scDevice)
607
748
 
608
749
  # start the Spotify Cast application on the Chromecast device.
609
750
  # this will start the spotify app on the cast device.
@@ -655,6 +796,10 @@ class SpotifyConnectDirectoryTask(threading.Thread):
655
796
  # at this point we have received an `adduserResponse` from the Chromecast device, indicating
656
797
  # that it has launched the Spotify Cast App and that the user is logged in.
657
798
 
799
+ # trace.
800
+ if (_logsi.IsOn(SILevel.Verbose)):
801
+ self._TraceMultizoneGroupMembers(str(castInfo.uuid), castInfo.friendly_name)
802
+
658
803
  # was transfer playback specified?
659
804
  if (transferPlayback == True):
660
805
 
@@ -1675,6 +1820,19 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1675
1820
  # remove the dynamic device from the collection.
1676
1821
  self.RemoveDevice(spDynamicDevice.Id, dynamicDeviceOnly=True)
1677
1822
 
1823
+ try:
1824
+
1825
+ # register the multizone manager listener with each Chromecast non-group device.
1826
+ if (castInfo.cast_type != CAST_TYPE_GROUP):
1827
+ _logsi.LogVerbose("Chromecast Multizone Manager Listener is being added for non-group device: \"%s\"" % (castInfo.friendly_name))
1828
+ castMultiZoneManagerListener = SpotifyConnectZeroconfCastMultiZoneManagerListener(self, self._ZeroconfInstance, self._Zeroconf_RLock, self._CastMultiZoneManager, castDevice)
1829
+ self._CastMultiZoneManagerListeners[str(castDevice.uuid)] = castMultiZoneManagerListener
1830
+ self._CastMultiZoneManager.register_listener(castDevice.uuid, castMultiZoneManagerListener)
1831
+
1832
+ except Exception as ex:
1833
+
1834
+ _logsi.LogException("%s - MultiZone Manager register_listener Exception: %s" % (self.name, str(ex)), ex, logToSystemLogger=False)
1835
+
1678
1836
  # is this a Google Cast Group?
1679
1837
  if (castInfo.cast_type == CAST_TYPE_GROUP):
1680
1838
 
@@ -1698,6 +1856,14 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1698
1856
  # RemoteName = "Kitchen Parlor"
1699
1857
  # DeviceID = 3fb9b12379e4fa1957d065b4c4568939
1700
1858
 
1859
+ # verify multizone controller instance is registered for cast group device.
1860
+ self._VerifyCastGroupMultiZoneController(castDevice, scDevice)
1861
+
1862
+ # add group to multizone manager.
1863
+ # make sure we do this AFTER adding the MultizoneController listener.
1864
+ _logsi.LogVerbose("ChromeCast Multizone Manager is adding multizone group: \"%s\"" % (castDevice.cast_info.friendly_name))
1865
+ self._CastMultiZoneManager.add_multizone(castDevice)
1866
+
1701
1867
  # ignore "update_cast" entries, as we don't want to create duplicates.
1702
1868
  if (serviceType == "update_cast"):
1703
1869
  _logsi.LogVerbose("SpotifyConnectDevice instance will be ignored for Google Cast Group data: \"%s\" (%s)" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name))
@@ -1716,36 +1882,6 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1716
1882
  self._SpotifyConnectDevices.DateLastRefreshed = datetime.utcnow().timestamp()
1717
1883
  return
1718
1884
 
1719
- # register a multizone listener to listen for group change events; these
1720
- # events will (hopefully) help us to determine the leader of a group.
1721
- try:
1722
-
1723
- # is this an add_cast event?
1724
- if (serviceType == "add_cast"):
1725
-
1726
- # do we have a multizone controller registered for this device yet?
1727
- castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(scDevice.DiscoveryResult.Key, None)
1728
- if (castMultizoneController is None):
1729
-
1730
- # trace.
1731
- _logsi.LogVerbose("Chromecast Multizone Controller Listener is being added for Cast device: %s [%s]" % (scDevice.Title, scDevice.DiscoveryResult.HostIpTitle), colorValue=SIColors.Lavender)
1732
-
1733
- # add cast device to multizone controller instance. this will add a listener
1734
- # for multizone status events to be processed, which allows us to keep track
1735
- # of which device is a group leader.
1736
- castMultizoneController = MultizoneController(castDevice.uuid)
1737
- castMultizoneController.register_listener(SpotifyConnectZeroconfCastMultiZoneListener(self, self._ZeroconfInstance, self._Zeroconf_RLock, castMultizoneController, castDevice))
1738
- castDevice.register_handler(castMultizoneController)
1739
- castMultizoneController.update_members() # Request an initial status
1740
-
1741
- # add cast multizone controller to active multizone controllers collection.
1742
- self._CastMultiZoneControllers[str(castDevice.uuid)] = castMultizoneController
1743
-
1744
- except Exception as ex:
1745
-
1746
- # trace.
1747
- _logsi.LogException("%s - MultiZoneController register_listener Exception: %s" % (self.name, str(ex)), ex, logToSystemLogger=False)
1748
-
1749
1885
  # add new Spotify Connect Device to devices collection.
1750
1886
  self._SpotifyConnectDevices.Items.append(scDevice)
1751
1887
  self._SpotifyConnectDevices.DateLastRefreshed = datetime.utcnow().timestamp()
@@ -1779,6 +1915,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1779
1915
 
1780
1916
  # did the device name change? if so, then update name and id properties,
1781
1917
  # as well as the corresponding getInfo properties.
1918
+ # note that the discovery key value does not change!
1782
1919
  deviceNameChanged:bool = False
1783
1920
  if (scDevice.Name != zeroconfDiscoveryResult.DeviceName):
1784
1921
 
@@ -1899,26 +2036,40 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1899
2036
 
1900
2037
  else:
1901
2038
 
1902
- # unregister the multizone listener, if one was registered.
2039
+ # get device information.
2040
+ scDevice:SpotifyConnectDevice = self._SpotifyConnectDevices.Items[idx]
2041
+
1903
2042
  try:
1904
2043
 
1905
- # do we have a multizone controller registered for this device?
2044
+ # unregister multizone controller listener for the device.
1906
2045
  castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(zeroconfDiscoveryResult.Key, None)
1907
2046
  if (castMultizoneController is not None):
2047
+ _logsi.LogVerbose("Chromecast Multizone Controller Listener is being removed for Cast device: \"%s\" (%s) [%s]" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name, zeroconfDiscoveryResult.Key), colorValue=SIColors.Lavender)
2048
+ castMultizoneController.tear_down()
2049
+ self._CastMultiZoneControllers.pop(zeroconfDiscoveryResult.Key, None)
1908
2050
 
1909
- # trace.
1910
- _logsi.LogVerbose("Chromecast Multizone Controller Listener is being removed: \"%s\" (%s) [%s]" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name, zeroconfDiscoveryResult.Key), colorValue=SIColors.Lavender)
2051
+ except Exception as ex:
1911
2052
 
1912
- # tear down the listener.
1913
- castMultizoneController.tear_down()
2053
+ # trace.
2054
+ _logsi.LogException("Chromecast MultiZoneController tear_down Exception: %s" % (str(ex)), ex, logToSystemLogger=False)
1914
2055
 
1915
- # remove cast multizone controller from active multizone controllers collection.
1916
- self._CastMultiZoneControllers.pop(zeroconfDiscoveryResult.Key, None)
2056
+ try:
2057
+
2058
+ # remove group from multizone manager.
2059
+ # make sure we do this AFTER removing the MultizoneController listener.
2060
+ _logsi.LogVerbose("Chromecast Multizone Manager is removing multizone device: \"%s\"" % (scDevice.Title))
2061
+ self._CastMultiZoneManager.remove_multizone(scDevice.DiscoveryResult.Key)
2062
+
2063
+ # deregister multizone manager listener for the device.
2064
+ _logsi.LogVerbose("Chromecast Multizone Manager Listener is deregistering for device: \"%s\"" % (scDevice.Title))
2065
+ castMultiZoneManagerListener = self._CastMultiZoneManagerListeners.pop(scDevice.DiscoveryResult.Key, None)
2066
+ if castMultiZoneManagerListener:
2067
+ self._CastMultiZoneManager.deregister_listener(scDevice.DiscoveryResult.Key, castMultiZoneManagerListener)
1917
2068
 
1918
2069
  except Exception as ex:
1919
2070
 
1920
2071
  # trace.
1921
- _logsi.LogException("%s - MultiZoneController register_listener Exception: %s" % (self.name, str(ex)), ex, logToSystemLogger=False)
2072
+ _logsi.LogException("Chromecast MultizoneManager deregister_listener Exception: %s" % (str(ex)), ex, logToSystemLogger=False)
1922
2073
 
1923
2074
  # trace.
1924
2075
  _logsi.LogVerbose("Removing existing SpotifyConnectDevice instance from CastInfo data: \"%s\" (%s) [%s]" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name, zeroconfDiscoveryResult.Key))
@@ -151,11 +151,11 @@ class SpotifyConnectZeroconfCastController(BaseController):
151
151
  """
152
152
  Callback function
153
153
  """
154
- # for cast groups, we will wait a couple of seconds for any cast messages to be
154
+ # for cast groups, we will wait a few seconds for any cast messages to be
155
155
  # processed, before we query the device for Spotify Connect getInfo.
156
156
  if (self.castDevice.cast_type == CAST_TYPE_GROUP):
157
157
  delay:float = 3.0
158
- _logsi.LogVerbose("Cast controller is for a Cast Group request; delaying processing for %s seconds to allow cast zeroconf responses to be processed" % (str(delay)), colorValue=SIColors.Red)
158
+ _logsi.LogVerbose("Cast controller is for a Cast Group request; delaying processing for %s seconds to allow cast zeroconf responses to be processed" % (str(delay)), colorValue=SIColors.Coral)
159
159
  time.sleep(delay)
160
160
 
161
161
  # send the initial getInfo request after launching the app.