spotifywebapiPython 1.0.293__py3-none-any.whl → 1.0.295__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.
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +15 -3
- spotifywebapipython/docs/index.html +2 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
- spotifywebapipython/spotifyclient.py +1 -1
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +253 -173
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +1 -1
- {spotifywebapipython-1.0.293.dist-info → spotifywebapipython-1.0.295.dist-info}/METADATA +1 -1
- {spotifywebapipython-1.0.293.dist-info → spotifywebapipython-1.0.295.dist-info}/RECORD +25 -25
- {spotifywebapipython-1.0.293.dist-info → spotifywebapipython-1.0.295.dist-info}/LICENSE +0 -0
- {spotifywebapipython-1.0.293.dist-info → spotifywebapipython-1.0.295.dist-info}/WHEEL +0 -0
- {spotifywebapipython-1.0.293.dist-info → spotifywebapipython-1.0.295.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# external package imports.
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
import copy
|
|
4
|
+
from collections import Counter
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
import hashlib
|
|
6
7
|
from pychromecast import APP_MEDIA_RECEIVER, CastBrowser, CastInfo, Chromecast, get_chromecast_from_cast_info, get_chromecast_from_host
|
|
@@ -512,7 +513,6 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
512
513
|
def _VerifyMultizoneGroupLeader(
|
|
513
514
|
self,
|
|
514
515
|
scDevice: SpotifyConnectDevice,
|
|
515
|
-
discoveryResult: ZeroconfDiscoveryResult,
|
|
516
516
|
) -> None:
|
|
517
517
|
"""
|
|
518
518
|
Verifies the current leader for a cast group is connected and available.
|
|
@@ -520,94 +520,169 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
520
520
|
Args:
|
|
521
521
|
scDevice (SpotifyConnectDevice):
|
|
522
522
|
SpotifyConnectDevice object that contains discovery result details.
|
|
523
|
-
|
|
524
|
-
|
|
523
|
+
|
|
524
|
+
This method should be called from a method that syncronizes access via lock
|
|
525
|
+
(using `self._SpotifyConnectDevices_RLock`), as it could possibly modify the
|
|
526
|
+
SpotifyConnectDevices collection.
|
|
525
527
|
"""
|
|
528
|
+
|
|
529
|
+
member_summary:list[str] = []
|
|
530
|
+
member_hosts:list[str] = []
|
|
531
|
+
timeout:float = 2.0
|
|
532
|
+
group_uuid:UUID = UUID(scDevice.DiscoveryResult.Key)
|
|
533
|
+
|
|
526
534
|
try:
|
|
527
535
|
|
|
528
|
-
|
|
536
|
+
# get a list of device IP addresses that are members of the group, and also log
|
|
537
|
+
# a user-friendly trace line for debugging.
|
|
529
538
|
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
# device reference from it, and retrieve the host IP address.
|
|
539
|
+
# trace.
|
|
540
|
+
_logsi.LogObject(SILevel.Verbose, "Verifying Chromecast Multizone group leader for Cast Group: %s [UUID=%s]" % (scDevice.Title, group_uuid), scDevice.DiscoveryResult, excludeNonPublic=True, colorValue=SIColors.Coral)
|
|
533
541
|
|
|
534
|
-
#
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
# determine which of the two (if any!) is the active group leader.
|
|
542
|
+
# get multizone controller reference for the group.
|
|
543
|
+
castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(group_uuid), None)
|
|
544
|
+
if (castMultizoneController is not None):
|
|
538
545
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
546
|
+
# process all members, building a list of their host addresses.
|
|
547
|
+
for member in castMultizoneController.members:
|
|
548
|
+
scMemberDevice:SpotifyConnectDevice = self._SpotifyConnectDevices.GetDeviceByDiscoveryKey(member)
|
|
549
|
+
if (scMemberDevice is None):
|
|
550
|
+
member_summary.append(member)
|
|
551
|
+
else:
|
|
552
|
+
member_summary.append("%s [ip=%s]" % (scMemberDevice.Title, scMemberDevice.DiscoveryResult.HostIpTitle))
|
|
553
|
+
member_hosts.append(scMemberDevice.DiscoveryResult.HostIpAddress)
|
|
542
554
|
|
|
543
|
-
#
|
|
544
|
-
|
|
545
|
-
if (castDevice):
|
|
555
|
+
# trace.
|
|
556
|
+
_logsi.LogArray(SILevel.Verbose, "Chromecast Multizone group member summary: \"%s\" (count=%d)" % (scDevice.Title, len(member_summary)), member_summary, colorValue=SIColors.Coral)
|
|
546
557
|
|
|
547
|
-
|
|
548
|
-
leaderIP = castDevice.socket_client.host
|
|
558
|
+
# process each member in the group, retrieving it's multi-zone status.
|
|
549
559
|
|
|
550
|
-
|
|
551
|
-
|
|
560
|
+
votes: Counter[str] = Counter()
|
|
561
|
+
port_for: dict[str, int] = {}
|
|
552
562
|
|
|
553
|
-
|
|
554
|
-
# reference and the mDNS service update?
|
|
555
|
-
if (leaderIP != discoveryResult.HostIpAddress):
|
|
563
|
+
for host in member_hosts:
|
|
556
564
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
# responds, and use it as the group leader.
|
|
565
|
+
# trace.
|
|
566
|
+
_logsi.LogVerbose("Retrieving Chromecast Multizone group status for member host: %s" % (host), colorValue=SIColors.Coral)
|
|
560
567
|
|
|
561
|
-
|
|
562
|
-
|
|
568
|
+
# get multizone status for the specified group member.
|
|
569
|
+
# note that `get_multizone_status` can return both IPV4 (e.g. "192.168.1.97") and IPV6 (e.g. "::ffff:c0a8:161") addresses!
|
|
570
|
+
status = get_multizone_status(host, timeout=timeout)
|
|
563
571
|
|
|
564
|
-
|
|
572
|
+
# trace.
|
|
573
|
+
_logsi.LogDictionary(SILevel.Verbose, "Chromecast Multizone group status for member host: %s" % (host), status, prettyPrint=True, colorValue=SIColors.Coral)
|
|
574
|
+
|
|
575
|
+
# if we could not retrieve status, then move on to the next group member.
|
|
576
|
+
if status is None:
|
|
577
|
+
continue
|
|
578
|
+
|
|
579
|
+
# add to the vote count for this host address.
|
|
580
|
+
# a member that is the LEADER answers with its own host and the group's cast_port;
|
|
581
|
+
# a member that is a FOLLOWER answers with the leader's host and no port (pychromecast
|
|
582
|
+
# strips the ':10001' multizone port); right after a re-form, a single member can still
|
|
583
|
+
# say 'self' although it no longer leads, so take the majority.
|
|
584
|
+
for g in status.groups:
|
|
585
|
+
if g.uuid == group_uuid and g.host:
|
|
586
|
+
votes[g.host] += 1
|
|
587
|
+
if g.port:
|
|
588
|
+
port_for[g.host] = g.port
|
|
589
|
+
|
|
590
|
+
# add to the vote count for this host address for dynamic groups as well.
|
|
591
|
+
for g in status.dynamic_groups:
|
|
592
|
+
if g.uuid == group_uuid and g.host:
|
|
593
|
+
votes[g.host] += 1
|
|
594
|
+
if g.port:
|
|
595
|
+
port_for[g.host] = g.port
|
|
565
596
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
castDevice = get_chromecast_from_host(
|
|
569
|
-
host=(discoveryResult.HostIpAddress, discoveryResult.HostIpPort, discoveryResult.Key, scDevice.DeviceInfo.ModelDisplayName, scDevice.DeviceInfo.RemoteName),
|
|
570
|
-
tries=1,
|
|
571
|
-
retry_wait=0.5,
|
|
572
|
-
timeout=2)
|
|
597
|
+
# trace.
|
|
598
|
+
_logsi.LogCollection(SILevel.Verbose, "Chromecast Multizone group leader vote summary for Cast Group: %s [UUID=%s]" % (scDevice.Title, group_uuid), votes.most_common(), colorValue=SIColors.Coral)
|
|
573
599
|
|
|
574
|
-
|
|
575
|
-
|
|
600
|
+
# we will now verify connectivity to all devices that were resolved
|
|
601
|
+
# as group leaders from the various group members. in most cases, this
|
|
602
|
+
# should be just one device, if they all reported the same leader.
|
|
603
|
+
# if multiple leaders were reported (this can happen while the group is
|
|
604
|
+
# in the midst of reforming), then we will use the host address that was
|
|
605
|
+
# reported the most, using the host address that was reported the least
|
|
606
|
+
# as a fallback in case we could not connect to the most-used address.
|
|
576
607
|
|
|
577
|
-
|
|
608
|
+
# process all reported leaders, using most reported host address first.
|
|
609
|
+
for host, _ in votes.most_common():
|
|
578
610
|
|
|
579
|
-
|
|
611
|
+
# trace.
|
|
612
|
+
_logsi.LogVerbose("Verifying connectivity to Chromecast Multizone group leader: %s" % (host), colorValue=SIColors.Coral)
|
|
580
613
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
host=(leaderIP, scDevice.DiscoveryResult.HostIpPort, scDevice.DiscoveryResult.Key, scDevice.DeviceInfo.ModelDisplayName, scDevice.DeviceInfo.RemoteName),
|
|
585
|
-
tries=1,
|
|
586
|
-
retry_wait=0.5,
|
|
587
|
-
timeout=2)
|
|
614
|
+
# ensure we resolved a port for this host.
|
|
615
|
+
port = port_for.get(host)
|
|
616
|
+
if port is None:
|
|
588
617
|
|
|
589
|
-
|
|
590
|
-
|
|
618
|
+
# if a port was not resolved for this host, then it points at a host we have not
|
|
619
|
+
# retrieved multizone status for yet - ask it for its port.
|
|
591
620
|
|
|
592
|
-
|
|
621
|
+
# trace.
|
|
622
|
+
_logsi.LogVerbose("Retrieving Chromecast Multizone group status for member host: %s" % (host), colorValue=SIColors.Coral)
|
|
593
623
|
|
|
594
|
-
|
|
624
|
+
# get multizone status for the specified group member.
|
|
625
|
+
# note that `get_multizone_status` can return both IPV4 (e.g. "192.168.1.97") and IPV6 (e.g. "::ffff:c0a8:161") addresses!
|
|
626
|
+
status = get_multizone_status(host, timeout=timeout)
|
|
627
|
+
|
|
628
|
+
# trace.
|
|
629
|
+
_logsi.LogDictionary(SILevel.Verbose, "Chromecast Multizone group status for member host: %s" % (host), status, prettyPrint=True, colorValue=SIColors.Coral)
|
|
630
|
+
|
|
631
|
+
if status:
|
|
632
|
+
for g in status.groups:
|
|
633
|
+
if g.uuid == group_uuid and g.host == host and g.port:
|
|
634
|
+
port = g.port
|
|
635
|
+
for g in status.dynamic_groups:
|
|
636
|
+
if g.uuid == group_uuid and g.host == host and g.port:
|
|
637
|
+
port = g.port
|
|
638
|
+
|
|
639
|
+
# verify connectivity if we have a port for this host.
|
|
640
|
+
if port:
|
|
641
|
+
|
|
642
|
+
try:
|
|
595
643
|
|
|
596
|
-
#
|
|
597
|
-
|
|
644
|
+
# trace.
|
|
645
|
+
_logsi.LogVerbose("Verifying connection to Chromecast Multizone group leader host candidate: ip=%s, port=%s" % (host, port), colorValue=SIColors.Coral)
|
|
646
|
+
|
|
647
|
+
# connect to the host on the designated port. this should return almost
|
|
648
|
+
# immediately if the device is found on the local network.
|
|
649
|
+
with socket.create_connection((host, port), timeout=timeout):
|
|
650
|
+
|
|
651
|
+
# if it's a good socket connection, then we will treat this host as the
|
|
652
|
+
# group leader; update the devices collection if the addresses are different.
|
|
653
|
+
if (host != scDevice.DiscoveryResult.HostIpAddress):
|
|
654
|
+
|
|
655
|
+
# note that we only care about IPV4 addresses, as `get_multizone_status`
|
|
656
|
+
# can return both IPV4 and IPV6 addresses.
|
|
657
|
+
if (host.count(":")) > 1: # IPV6 address?
|
|
658
|
+
_logsi.LogVerbose("Ignoring Chromecast Multizone group leader candidate address: %s (IPV6 address)" % (host), colorValue=SIColors.Coral)
|
|
659
|
+
else:
|
|
660
|
+
_logsi.LogObject(SILevel.Verbose, "Cast Group leader IP was changed from [%s] to [%s] for Cast Group \"%s\"" % (scDevice.DiscoveryResult.HostIpAddress, host, scDevice.Name), scDevice.DiscoveryResult, excludeNonPublic=True, colorValue=SIColors.ForestGreen)
|
|
661
|
+
scDevice.DiscoveryResult.HostIpAddress = host
|
|
662
|
+
scDevice.DiscoveryResult.Id = "\"%s\" (%s:%s)" % (scDevice.DiscoveryResult.DeviceName, scDevice.DiscoveryResult.HostIpAddress, scDevice.DiscoveryResult.HostIpPort)
|
|
663
|
+
|
|
664
|
+
# connection was good; we are done here.
|
|
665
|
+
_logsi.LogVerbose("Chromecast Multizone group leader host was verified for Cast Group: \"%s\" [%s:%s]" % (scDevice.Name, host, port), colorValue=SIColors.Coral)
|
|
666
|
+
return
|
|
598
667
|
|
|
599
|
-
|
|
600
|
-
if (leaderIP != scDevice.DiscoveryResult.HostIpAddress):
|
|
668
|
+
except Exception as ex:
|
|
601
669
|
|
|
602
670
|
# trace.
|
|
603
|
-
_logsi.
|
|
604
|
-
|
|
605
|
-
|
|
671
|
+
_logsi.LogException("Chromecast Group member [ip=%s, port=%s] could not be reached; moving on to next host" % (host, port, str(ex)), ex, logToSystemLogger=False)
|
|
672
|
+
# ignore exception and check the next available host
|
|
673
|
+
|
|
674
|
+
else:
|
|
675
|
+
_logsi.LogVerbose("Ignoring Chromecast Multizone group leader address: %s (port not found)" % (host), colorValue=SIColors.Coral)
|
|
676
|
+
|
|
677
|
+
# if we make it here, then the cast group leader could not be verified!
|
|
678
|
+
|
|
679
|
+
# trace.
|
|
680
|
+
_logsi.LogVerbose("Could not verify Chromecast Multizone group leader for Cast Group: %s [UUID=%s] - existing host [ip=%s] will be used" % (scDevice.Title, group_uuid, scDevice.DiscoveryResult.HostIpTitle), colorValue=SIColors.Coral)
|
|
606
681
|
|
|
607
682
|
except Exception as ex:
|
|
608
683
|
|
|
609
684
|
# trace.
|
|
610
|
-
_logsi.LogException("Chromecast Multizone group leader
|
|
685
|
+
_logsi.LogException("Chromecast Multizone group leader verification exception: %s" % (str(ex)), ex, logToSystemLogger=False)
|
|
611
686
|
# ignore exception, as nothing can be done about it.
|
|
612
687
|
|
|
613
688
|
|
|
@@ -617,6 +692,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
617
692
|
transferPlayback:bool=False,
|
|
618
693
|
timeoutActivation:float=15.0,
|
|
619
694
|
timeoutTransfer:float=10.0,
|
|
695
|
+
activateAppMediaReceiverOnGroupMembers:bool=False,
|
|
620
696
|
) -> str:
|
|
621
697
|
"""
|
|
622
698
|
Activates the Spotify Cast Application on the specified Chromecast device.
|
|
@@ -637,18 +713,22 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
637
713
|
|
|
638
714
|
Args:
|
|
639
715
|
deviceName (str):
|
|
640
|
-
Chromecast device friendly name (or device id) to activate.
|
|
716
|
+
Chromecast device friendly name (or device id) to activate.
|
|
641
717
|
transferPlayback (bool):
|
|
642
718
|
True to transfer playback to the device; otherwise, False to just activate
|
|
643
|
-
the Spotify Cast App on the device.
|
|
719
|
+
the Spotify Cast App on the device.
|
|
644
720
|
timeoutActivation (float):
|
|
645
721
|
Amount of time to wait (in seconds) for the Spotify Cast App to be fully activated,
|
|
646
722
|
the user logged in, and ready for transfer of playback.
|
|
647
|
-
Default is 15 seconds.
|
|
723
|
+
Default is 15 seconds.
|
|
648
724
|
timeoutTransfer (float):
|
|
649
725
|
If `transferPlayback` argument is True, the amount of time to wait (in seconds)
|
|
650
726
|
for the transfer to complete; otherwise, argument is ignored.
|
|
651
|
-
Default is 10 seconds.
|
|
727
|
+
Default is 10 seconds.
|
|
728
|
+
activateAppMediaReceiverOnGroupMembers (bool):
|
|
729
|
+
True to force start the APP_MEDIA_RECEIVER on each group member cast device prior
|
|
730
|
+
to activating the Spotify Cast App; otherwise, False (default) to not start the
|
|
731
|
+
APP_MEDIA_RECEIVER on any group devices.
|
|
652
732
|
|
|
653
733
|
Returns:
|
|
654
734
|
The deviceId that was activated.
|
|
@@ -664,6 +744,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
664
744
|
apiMethodParms.AppendKeyValue("transferPlayback", transferPlayback)
|
|
665
745
|
apiMethodParms.AppendKeyValue("timeoutActivation", timeoutActivation)
|
|
666
746
|
apiMethodParms.AppendKeyValue("timeoutTransfer", timeoutTransfer)
|
|
747
|
+
apiMethodParms.AppendKeyValue("activateAppMediaReceiverOnGroupMembers", activateAppMediaReceiverOnGroupMembers)
|
|
667
748
|
_logsi.LogMethodParmList(SILevel.Verbose, "Activating Spotify App on Chromecast device: \"%s\"" % (deviceName), apiMethodParms)
|
|
668
749
|
|
|
669
750
|
# validations.
|
|
@@ -693,7 +774,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
693
774
|
|
|
694
775
|
# verify the current leader of the group.
|
|
695
776
|
# the device collection entry will be updated if a change was detected.
|
|
696
|
-
self._VerifyMultizoneGroupLeader(scDevice
|
|
777
|
+
self._VerifyMultizoneGroupLeader(scDevice)
|
|
697
778
|
|
|
698
779
|
# is this a chromecast device?
|
|
699
780
|
if (not scDevice.IsChromeCast):
|
|
@@ -754,13 +835,13 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
754
835
|
|
|
755
836
|
# for groups, the SpotifyAppTask must be activated on the group leader (e.g. coordinator).
|
|
756
837
|
# the cast zeroconf info may not contain the group leader host info, as it is constantly
|
|
757
|
-
# being updated as devices are added / removed from the group.
|
|
758
|
-
#
|
|
759
|
-
#
|
|
760
|
-
#
|
|
761
|
-
#
|
|
762
|
-
#
|
|
763
|
-
#
|
|
838
|
+
# being updated as devices are added / removed from the group. the resolution of the group
|
|
839
|
+
# leader should have been handled by the `_VerifyMultizoneGroupLeader` call above.
|
|
840
|
+
#
|
|
841
|
+
# if this the the second attempt at activating the Spotify Cast App, then we will try
|
|
842
|
+
# starting the APP_MEDIA_RECEIVER on each group member device, as well as calling the
|
|
843
|
+
# `get_multizone_status` function again to verify the status of the zone before trying to
|
|
844
|
+
# start the Spotify Cast App.
|
|
764
845
|
|
|
765
846
|
# trace.
|
|
766
847
|
if (_logsi.IsOn(SILevel.Verbose)):
|
|
@@ -770,110 +851,109 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
770
851
|
groupHost:str = scDevice.DiscoveryResult.HostIpAddress
|
|
771
852
|
groupPort:int = scDevice.DiscoveryResult.HostIpPort
|
|
772
853
|
|
|
773
|
-
|
|
854
|
+
# are we starting the APP_MEDIA_RECEIVER on each group member?
|
|
855
|
+
if (activateAppMediaReceiverOnGroupMembers != True):
|
|
774
856
|
|
|
775
|
-
|
|
776
|
-
# note that this will just send a request to the cast group device for its
|
|
777
|
-
# status, which will then update the members list via event listener callbacks.
|
|
778
|
-
castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castInfo.uuid), None)
|
|
779
|
-
if (castMultizoneController) and (castMultizoneController._socket_client):
|
|
780
|
-
castMultizoneController.update_members()
|
|
781
|
-
time.sleep(2.0) # give controller some time to update member list.
|
|
782
|
-
|
|
783
|
-
# activate media receiver app for each member in the group.
|
|
784
|
-
# process all device members in the group / zone.
|
|
785
|
-
for zone_member_uuid in castMultizoneController.members:
|
|
786
|
-
|
|
787
|
-
# get castInfo instance so we can connect to the device.
|
|
788
|
-
zone_member_castInfo = self._CastBrowser.devices.get(UUID(zone_member_uuid))
|
|
789
|
-
if zone_member_castInfo is not None:
|
|
790
|
-
|
|
791
|
-
# get cast device instance.
|
|
792
|
-
zone_member_castDevice = get_chromecast_from_cast_info(
|
|
793
|
-
cast_info=zone_member_castInfo,
|
|
794
|
-
zconf=self._ZeroconfInstance,
|
|
795
|
-
tries=2,
|
|
796
|
-
retry_wait=0.5,
|
|
797
|
-
timeout=5)
|
|
798
|
-
|
|
799
|
-
# wait for the cast device to provide an initial status.
|
|
800
|
-
zone_member_castDevice.wait(10)
|
|
801
|
-
|
|
802
|
-
# formulate title for trace messages.
|
|
803
|
-
trc_zone_member_title:str = "\"%s\" [ip=%s:%s]" % (zone_member_castDevice.cast_info.friendly_name, zone_member_castDevice.socket_client.host, zone_member_castDevice.socket_client.port)
|
|
804
|
-
|
|
805
|
-
# trace.
|
|
806
|
-
_logsi.LogObject(SILevel.Verbose, "%s - Cast group \"%s\" member Chromecast device status: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), zone_member_castDevice.status, colorValue=SIColors.Coral)
|
|
807
|
-
|
|
808
|
-
# if current app is APP_SPOTIFY, then stop the app before starting the media receiver.
|
|
809
|
-
if (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY):
|
|
810
|
-
_logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
811
|
-
zone_member_castDevice.quit_app(timeout=5.0)
|
|
812
|
-
elif (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY_CONNECT):
|
|
813
|
-
_logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY_CONNECT) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
814
|
-
zone_member_castDevice.quit_app(timeout=5.0)
|
|
815
|
-
|
|
816
|
-
# start the media receiver app.
|
|
817
|
-
# this will stabilize routing for the group using the Default Media Receiver.
|
|
818
|
-
_logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
819
|
-
zone_member_castDevice.start_app(APP_MEDIA_RECEIVER, force_launch=True, timeout=10.0)
|
|
820
|
-
|
|
821
|
-
# start the connection worker thread, if needed.
|
|
822
|
-
# failure to do this will result in the following exception being thrown when
|
|
823
|
-
# the `launch_app()` method is called:
|
|
824
|
-
# - `pychromecast.error.NotConnected: Chromecast unknown:8009 is connecting...`
|
|
825
|
-
if (zone_member_castDevice.is_idle):
|
|
826
|
-
if (zone_member_castDevice.socket_client is not None) and (zone_member_castDevice.socket_client.first_connection):
|
|
827
|
-
_logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER connection worker thread for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
828
|
-
zone_member_castDevice.start()
|
|
829
|
-
zone_member_castDevice.wait(timeout=5.0)
|
|
830
|
-
|
|
831
|
-
# media receiver app needs time to start.
|
|
832
|
-
time.sleep(2.0)
|
|
857
|
+
_logsi.LogVerbose("Bypassing start of APP_MEDIA_RECEIVER for group: %s" % (scDevice.Title), colorValue=SIColors.Coral)
|
|
833
858
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
# trace.
|
|
837
|
-
_logsi.LogException("Chromecast Multizone group update_members exception: %s" % (str(ex)), ex, logToSystemLogger=False)
|
|
838
|
-
# ignore exception, as nothing can be done about it.
|
|
839
|
-
|
|
840
|
-
# get current multizone status for all groups.
|
|
841
|
-
castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
|
|
842
|
-
_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)
|
|
843
|
-
|
|
844
|
-
# ensure group device is still on the network;
|
|
845
|
-
# if group device dropped off the network, then we can't query it!
|
|
846
|
-
if (castMultiZoneStatus is not None):
|
|
847
|
-
|
|
848
|
-
# find the group coordinator for the selected device.
|
|
849
|
-
zoneInfo:MultizoneInfo = None
|
|
850
|
-
for zoneInfo in castMultiZoneStatus.groups:
|
|
851
|
-
|
|
852
|
-
# is this our group?
|
|
853
|
-
if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
|
|
854
|
-
|
|
855
|
-
# yes - get host info to use for the device.
|
|
856
|
-
# we will use the device discovery result HostIpAddress instead of the zoneInfo.host
|
|
857
|
-
# value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
|
|
858
|
-
# issues if the device was originally activated using an IPV4 address!
|
|
859
|
-
groupHost = scDevice.DiscoveryResult.HostIpAddress
|
|
860
|
-
groupPort = scDevice.DiscoveryResult.HostIpPort
|
|
861
|
-
break
|
|
859
|
+
else:
|
|
862
860
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
861
|
+
try:
|
|
862
|
+
|
|
863
|
+
# request a multizone group member update.
|
|
864
|
+
# note that this will just send a request to the cast group device for its
|
|
865
|
+
# status, which will then update the members list via event listener callbacks.
|
|
866
|
+
castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castInfo.uuid), None)
|
|
867
|
+
if (castMultizoneController) and (castMultizoneController._socket_client):
|
|
868
|
+
castMultizoneController.update_members()
|
|
869
|
+
time.sleep(2.0) # give controller some time to update member list.
|
|
870
|
+
|
|
871
|
+
# activate media receiver app for each member in the group.
|
|
872
|
+
# process all device members in the group / zone.
|
|
873
|
+
for zone_member_uuid in castMultizoneController.members:
|
|
874
|
+
|
|
875
|
+
# get castInfo instance so we can connect to the device.
|
|
876
|
+
zone_member_castInfo = self._CastBrowser.devices.get(UUID(zone_member_uuid))
|
|
877
|
+
if zone_member_castInfo is not None:
|
|
878
|
+
|
|
879
|
+
# get cast device instance.
|
|
880
|
+
zone_member_castDevice = get_chromecast_from_cast_info(
|
|
881
|
+
cast_info=zone_member_castInfo,
|
|
882
|
+
zconf=self._ZeroconfInstance,
|
|
883
|
+
tries=2,
|
|
884
|
+
retry_wait=0.5,
|
|
885
|
+
timeout=5)
|
|
886
|
+
|
|
887
|
+
# wait for the cast device to provide an initial status.
|
|
888
|
+
zone_member_castDevice.wait(10)
|
|
889
|
+
|
|
890
|
+
# formulate title for trace messages.
|
|
891
|
+
trc_zone_member_title:str = "\"%s\" [ip=%s:%s]" % (zone_member_castDevice.cast_info.friendly_name, zone_member_castDevice.socket_client.host, zone_member_castDevice.socket_client.port)
|
|
892
|
+
|
|
893
|
+
# trace.
|
|
894
|
+
_logsi.LogObject(SILevel.Verbose, "%s - Cast group \"%s\" member Chromecast device status: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), zone_member_castDevice.status, colorValue=SIColors.Coral)
|
|
895
|
+
|
|
896
|
+
# if current app is APP_SPOTIFY, then stop the app before starting the media receiver.
|
|
897
|
+
if (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY):
|
|
898
|
+
_logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
899
|
+
zone_member_castDevice.quit_app(timeout=5.0)
|
|
900
|
+
elif (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY_CONNECT):
|
|
901
|
+
_logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY_CONNECT) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
902
|
+
zone_member_castDevice.quit_app(timeout=5.0)
|
|
903
|
+
|
|
904
|
+
# start the media receiver app.
|
|
905
|
+
# this will stabilize routing for the group using the Default Media Receiver.
|
|
906
|
+
_logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
907
|
+
zone_member_castDevice.start_app(APP_MEDIA_RECEIVER, force_launch=True, timeout=10.0)
|
|
908
|
+
|
|
909
|
+
# start the connection worker thread, if needed.
|
|
910
|
+
# failure to do this will result in the following exception being thrown when
|
|
911
|
+
# the `launch_app()` method is called:
|
|
912
|
+
# - `pychromecast.error.NotConnected: Chromecast unknown:8009 is connecting...`
|
|
913
|
+
if (zone_member_castDevice.is_idle):
|
|
914
|
+
if (zone_member_castDevice.socket_client is not None) and (zone_member_castDevice.socket_client.first_connection):
|
|
915
|
+
_logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER connection worker thread for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
|
|
916
|
+
zone_member_castDevice.start()
|
|
917
|
+
zone_member_castDevice.wait(timeout=5.0)
|
|
918
|
+
|
|
919
|
+
# media receiver app needs time to start.
|
|
920
|
+
time.sleep(2.0)
|
|
921
|
+
|
|
922
|
+
except Exception as ex:
|
|
923
|
+
|
|
924
|
+
# trace.
|
|
925
|
+
_logsi.LogException("Chromecast Multizone group update_members exception: %s" % (str(ex)), ex, logToSystemLogger=False)
|
|
926
|
+
# ignore exception, as nothing can be done about it.
|
|
927
|
+
|
|
928
|
+
# get current multizone status for all groups.
|
|
929
|
+
castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
|
|
930
|
+
_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)
|
|
931
|
+
|
|
932
|
+
# ensure group device is still on the network;
|
|
933
|
+
# if group device dropped off the network, then we can't query it!
|
|
934
|
+
if (castMultiZoneStatus is not None):
|
|
935
|
+
|
|
936
|
+
# find the group coordinator for the selected device.
|
|
937
|
+
zoneInfo:MultizoneInfo = None
|
|
938
|
+
for zoneInfo in castMultiZoneStatus.groups:
|
|
939
|
+
|
|
940
|
+
# is this our group?
|
|
941
|
+
if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
|
|
942
|
+
|
|
943
|
+
# yes - get host info to use for the device.
|
|
944
|
+
# we will use the device discovery result HostIpAddress instead of the zoneInfo.host
|
|
945
|
+
# value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
|
|
946
|
+
# issues if the device was originally activated using an IPV4 address!
|
|
947
|
+
groupHost = scDevice.DiscoveryResult.HostIpAddress
|
|
948
|
+
groupPort = scDevice.DiscoveryResult.HostIpPort
|
|
949
|
+
break
|
|
869
950
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
# deviceWaitTimeoutSecs = (groupCount * 5.0)
|
|
951
|
+
# connect to the device and build a Chromecast instance from group coordinator host info.
|
|
952
|
+
castDevice = get_chromecast_from_host(
|
|
953
|
+
host=(groupHost, groupPort, castInfo.uuid, castInfo.model_name, castInfo.friendly_name),
|
|
954
|
+
tries=2,
|
|
955
|
+
retry_wait=0.5,
|
|
956
|
+
timeout=10)
|
|
877
957
|
|
|
878
958
|
# trace.
|
|
879
959
|
_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)
|
|
@@ -2117,7 +2197,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2117
2197
|
|
|
2118
2198
|
# verify the current leader of the group.
|
|
2119
2199
|
# the device collection entry will be updated if a change was detected.
|
|
2120
|
-
self._VerifyMultizoneGroupLeader(scDevice
|
|
2200
|
+
self._VerifyMultizoneGroupLeader(scDevice)
|
|
2121
2201
|
|
|
2122
2202
|
# is this a Google Cast Group "update_cast" event?
|
|
2123
2203
|
# if so, AND the device name did not change, then ignore the update since
|
|
@@ -149,7 +149,7 @@ class SpotifyConnectZeroconfCastListener(AbstractCastListener):
|
|
|
149
149
|
|
|
150
150
|
# ensure IPV4 connection - ignore if IPV6.
|
|
151
151
|
# Windows OS does not support dual-stack socket handling IPv4 (224.0.0.251) and IPv6 (ff02::fb).
|
|
152
|
-
if (castInfoHost.count(":")) > 1:
|
|
152
|
+
if (castInfoHost.count(":")) > 1: #IPV6?
|
|
153
153
|
_logsi.LogDebug("Chromecast IPV6 device addresses are not supported; device could cause problems: \"%s\" (%s)" % (castInfo.friendly_name, serviceName), colorValue=SIColors.Red)
|
|
154
154
|
|
|
155
155
|
# will still allow this for now, but we might want to ignore IPV6 devices
|