spotifywebapiPython 1.0.150__py3-none-any.whl → 1.0.151__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.
Potentially problematic release.
This version of spotifywebapiPython might be problematic. Click here for more details.
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.151.dist-info}/METADATA +2 -1
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.151.dist-info}/RECORD +24 -24
- spotifywebapipython/const.py +6 -1
- spotifywebapipython/docs/__init__.html +9 -2
- spotifywebapipython/docs/index.html +2 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapipython/const.html +20 -3
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/sautils.html +65 -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 +42 -2
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.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/models/spotifyconnectdevice.py +12 -0
- spotifywebapipython/sautils.py +50 -0
- spotifywebapipython/spotifyclient.py +491 -163
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +61 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.151.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.151.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.151.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,16 @@ import json
|
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from oauthlib.oauth2 import BackendApplicationClient, WebApplicationClient
|
|
7
7
|
import socket
|
|
8
|
+
from soco import SoCo
|
|
9
|
+
from soco.core import (
|
|
10
|
+
PLAY_MODE_BY_MEANING as SONOS_PLAY_MODE_BY_MEANING,
|
|
11
|
+
PLAY_MODES as SONOS_PLAY_MODES,
|
|
12
|
+
)
|
|
8
13
|
import time
|
|
9
14
|
from typing import Tuple, Callable, Union
|
|
10
15
|
from urllib3 import PoolManager, Timeout, HTTPResponse
|
|
11
16
|
from urllib.parse import urlencode
|
|
17
|
+
import urllib.parse
|
|
12
18
|
from lxml.etree import fromstring, Element, tostring
|
|
13
19
|
from lxml import etree
|
|
14
20
|
from zeroconf import Zeroconf
|
|
@@ -18,7 +24,6 @@ from .oauthcli import AuthClient
|
|
|
18
24
|
from .models import *
|
|
19
25
|
from .models import UserProfile as UserProfileCurrentUser
|
|
20
26
|
from .spotifyconnect import SpotifyConnectDirectoryTask
|
|
21
|
-
from .sautils import export, GetUnixTimestampMSFromUtcNow, _xmlGetInnerText, validateDelay
|
|
22
27
|
from .saappmessages import SAAppMessages
|
|
23
28
|
from .spotifyapierror import SpotifyApiError
|
|
24
29
|
from .spotifyapimessage import SpotifyApiMessage
|
|
@@ -28,6 +33,14 @@ from .spotifywebapiauthenticationerror import SpotifyWebApiAuthenticationError
|
|
|
28
33
|
from .spotifywebapierror import SpotifyWebApiError
|
|
29
34
|
from .zeroconfapi.zeroconfconnect import ZeroconfConnect, ZeroconfGetInfo, ZeroconfResponse, SpotifyZeroconfApiError
|
|
30
35
|
from .zeroconfapi.zeroconfgetinfoalias import ZeroconfGetInfoAlias
|
|
36
|
+
from .sautils import (
|
|
37
|
+
_xmlGetInnerText,
|
|
38
|
+
export,
|
|
39
|
+
GetUnixTimestampMSFromUtcNow,
|
|
40
|
+
mediaPositionHMS_fromSeconds,
|
|
41
|
+
mediaPositionHMS_toSeconds,
|
|
42
|
+
validateDelay
|
|
43
|
+
)
|
|
31
44
|
from .const import (
|
|
32
45
|
SPOTIFY_API_AUTHORIZE_URL,
|
|
33
46
|
SPOTIFY_API_TOKEN_URL,
|
|
@@ -42,6 +55,7 @@ from .const import (
|
|
|
42
55
|
TRACE_MSG_AUTHTOKEN_CREATE,
|
|
43
56
|
TRACE_MSG_AUTOPAGING_NEXT,
|
|
44
57
|
TRACE_MSG_DELAY_DEVICE,
|
|
58
|
+
TRACE_MSG_DELAY_DEVICE_SONOS,
|
|
45
59
|
TRACE_MSG_USERPROFILE,
|
|
46
60
|
TRACE_WARN_SPOTIFY_SEARCH_NO_MARKET,
|
|
47
61
|
VERSION
|
|
@@ -7573,6 +7587,11 @@ class SpotifyClient:
|
|
|
7573
7587
|
if scDevice is not None:
|
|
7574
7588
|
device.Id = scDevice.DeviceInfo.DeviceId
|
|
7575
7589
|
|
|
7590
|
+
# update Spotify Connect Directory with active device details.
|
|
7591
|
+
# IMPORTANT - make sure result contains an object, otherwise it's an endless loop!
|
|
7592
|
+
if result is not None:
|
|
7593
|
+
self.SpotifyConnectDirectory.UpdateActiveDevice(result)
|
|
7594
|
+
|
|
7576
7595
|
# trace.
|
|
7577
7596
|
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
7578
7597
|
return result
|
|
@@ -9158,6 +9177,219 @@ class SpotifyClient:
|
|
|
9158
9177
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
9159
9178
|
|
|
9160
9179
|
|
|
9180
|
+
def GetSonosPlayerPlaybackState(
|
|
9181
|
+
self,
|
|
9182
|
+
scDevice:SpotifyConnectDevice,
|
|
9183
|
+
) -> PlayerPlayState:
|
|
9184
|
+
"""
|
|
9185
|
+
Get information about the Sonos user's current playback state, including track or episode, progress,
|
|
9186
|
+
and active device.
|
|
9187
|
+
|
|
9188
|
+
This is not part of the Spotify Web API specification; it is for Sonos devices only!
|
|
9189
|
+
|
|
9190
|
+
Args:
|
|
9191
|
+
scDevice (SpotifyConnectDevice):
|
|
9192
|
+
Spotify Connect device instance used to identify the Sonos player.
|
|
9193
|
+
|
|
9194
|
+
Returns:
|
|
9195
|
+
A `PlayerPlayState` object that contains Sonos player state details as well as
|
|
9196
|
+
currently playing content.
|
|
9197
|
+
|
|
9198
|
+
Raises:
|
|
9199
|
+
SpotifyApiError:
|
|
9200
|
+
If the method fails for any other reason.
|
|
9201
|
+
"""
|
|
9202
|
+
apiMethodName:str = 'GetSonosPlayerPlaybackState'
|
|
9203
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
9204
|
+
|
|
9205
|
+
try:
|
|
9206
|
+
|
|
9207
|
+
# trace.
|
|
9208
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
9209
|
+
apiMethodParms.AppendKeyValue("scDevice", scDevice)
|
|
9210
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get Sonos user's current playback state", apiMethodParms)
|
|
9211
|
+
|
|
9212
|
+
# create playstate object.
|
|
9213
|
+
playerState = PlayerPlayState()
|
|
9214
|
+
|
|
9215
|
+
# get Sonos Controller instance for the device.
|
|
9216
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
9217
|
+
|
|
9218
|
+
# use the SoCo API to get Sonos playback status.
|
|
9219
|
+
# build a spotify playback status instance with equivalent Sonos state values.
|
|
9220
|
+
_logsi.LogVerbose("Getting Sonos device status via Sonos Control API for device: %s" % (scDevice.Title))
|
|
9221
|
+
sonosTrackInfo:dict = sonosPlayer.get_current_track_info()
|
|
9222
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos device current_track_info results for device: %s" % (scDevice.Title), sonosTrackInfo, prettyPrint=True)
|
|
9223
|
+
sonosTransportInfo:dict = sonosPlayer.get_current_transport_info()
|
|
9224
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos device get_current_transport_info results for device: %s" % (scDevice.Title), sonosTransportInfo, prettyPrint=True)
|
|
9225
|
+
|
|
9226
|
+
# update Spotify Web API player state with Sonos player state.
|
|
9227
|
+
playerState._Device._Id = scDevice.Id
|
|
9228
|
+
playerState._Device._Name = sonosPlayer.player_name
|
|
9229
|
+
playerState._Device._VolumePercent = sonosPlayer.volume
|
|
9230
|
+
playerState._Device._IsActive = True
|
|
9231
|
+
playerState._Device._IsRestricted = True
|
|
9232
|
+
playerState._Device._SupportsVolume = True
|
|
9233
|
+
playerState._Device._Type = 'SPEAKER'
|
|
9234
|
+
|
|
9235
|
+
# try to get the spotify uri from the "metadata" music library DIDL value, as
|
|
9236
|
+
# the "uri" value (e.g. "x-sonos-vli:RINCON_38420B909DC801400:2,spotify:e934941535d7b182815bf688490ce8a8")
|
|
9237
|
+
# is not a true spotify uri value (e.g. "spotify:track:6kYyS9g4WJeRzTYqsmcMmM")
|
|
9238
|
+
spotifyUri:str = sonosTrackInfo.get('uri','')
|
|
9239
|
+
didl:str = sonosTrackInfo.get('metadata',None)
|
|
9240
|
+
if (didl):
|
|
9241
|
+
METADATA_ID:str = 'x-sonos-spotify:'
|
|
9242
|
+
idx:int = didl.find(METADATA_ID)
|
|
9243
|
+
if (idx > -1):
|
|
9244
|
+
spotifyUri:str = didl[idx + len(METADATA_ID):]
|
|
9245
|
+
spotifyUri = urllib.parse.unquote(spotifyUri)
|
|
9246
|
+
idx = spotifyUri.find('?')
|
|
9247
|
+
if (idx > -1):
|
|
9248
|
+
spotifyUri = spotifyUri[:idx]
|
|
9249
|
+
spotifyType = self.GetTypeFromUri(spotifyUri) or ''
|
|
9250
|
+
spotifyId = self.GetIdFromUri(spotifyUri) or ''
|
|
9251
|
+
|
|
9252
|
+
# set base item properties.
|
|
9253
|
+
playerState.IsEmpty = False
|
|
9254
|
+
playerState.ItemType = spotifyType
|
|
9255
|
+
sTimeValue:str = sonosTrackInfo.get('position',None)
|
|
9256
|
+
playerState._ProgressMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
9257
|
+
|
|
9258
|
+
# set some item properties based on the playing type (episode or non-episode).
|
|
9259
|
+
if (spotifyType == 'episode'):
|
|
9260
|
+
|
|
9261
|
+
# get the episode data from spotify, as Sonos Soco data is incomplete.
|
|
9262
|
+
playerState._CurrentlyPlayingType = 'episode'
|
|
9263
|
+
episode:Episode = self.GetEpisode(spotifyId)
|
|
9264
|
+
if (episode.Id is not None):
|
|
9265
|
+
playerState._Item = episode
|
|
9266
|
+
if (playerState._Context is None):
|
|
9267
|
+
playerState._Context = Context()
|
|
9268
|
+
playerState._Context._Type = episode.Show.Type
|
|
9269
|
+
playerState._Context._Uri = episode.Show.Uri
|
|
9270
|
+
playerState._Context._Href = episode.Show.Href
|
|
9271
|
+
playerState._Context._ExternalUrls = episode.Show.ExternalUrls
|
|
9272
|
+
else:
|
|
9273
|
+
# if data could not be obtained from spotify, then use what's
|
|
9274
|
+
# available from Sonos Soco api metadata.
|
|
9275
|
+
playerState._Item = Episode()
|
|
9276
|
+
playerState._Item._Name = sonosTrackInfo.get('title','')
|
|
9277
|
+
playerState._Item._Uri = spotifyUri
|
|
9278
|
+
playerState._Item._Description = 'Sonos device does not provide a description'
|
|
9279
|
+
sTimeValue:str = sonosTrackInfo.get('duration',None)
|
|
9280
|
+
playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
9281
|
+
playerState._Item._Explicit = False
|
|
9282
|
+
playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
|
|
9283
|
+
playerState._Item._HtmlDescription = playerState._Item._Description
|
|
9284
|
+
playerState._Item._Id = spotifyId
|
|
9285
|
+
playerState._Item.Images.append(ImageObject())
|
|
9286
|
+
playerState._Item.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
9287
|
+
playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
|
|
9288
|
+
playerState._Item._ReleaseDate = '0000'
|
|
9289
|
+
playerState._Item._ReleaseDatePrecision = 'year'
|
|
9290
|
+
playerState._Item._Type = 'episode'
|
|
9291
|
+
playerState._Item._Show = Show()
|
|
9292
|
+
playerState._Item._Show._Name = sonosTrackInfo.get('album','') # TODO get this from metadata (<r:podcast>The Elfstones of Shannara</r:podcast>)
|
|
9293
|
+
playerState._Item._Show.Images.append(ImageObject())
|
|
9294
|
+
playerState._Item._Show.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
9295
|
+
playerState._Item._Show._Publisher = sonosTrackInfo.get('artist','')
|
|
9296
|
+
|
|
9297
|
+
# if show name not set then get it from metadata.
|
|
9298
|
+
if (playerState._Item._Show._Name == ''):
|
|
9299
|
+
if (didl):
|
|
9300
|
+
METADATA_ID:str = '<r:podcast>'
|
|
9301
|
+
METADATA_END:str = '</r:podcast>'
|
|
9302
|
+
idx:int = didl.find(METADATA_ID)
|
|
9303
|
+
if (idx > -1):
|
|
9304
|
+
metaValue:str = didl[idx + len(METADATA_ID):]
|
|
9305
|
+
idx = metaValue.find(METADATA_END)
|
|
9306
|
+
if (idx > -1):
|
|
9307
|
+
metaValue = metaValue[:idx]
|
|
9308
|
+
playerState._Item._Show._Name = metaValue
|
|
9309
|
+
|
|
9310
|
+
# if episode is playing then resolve the underlying type (audiobook / podcast show).
|
|
9311
|
+
if (self.IsChapterEpisode(spotifyId)):
|
|
9312
|
+
playerState.ItemType = 'audiobook'
|
|
9313
|
+
else:
|
|
9314
|
+
playerState.ItemType = 'podcast'
|
|
9315
|
+
|
|
9316
|
+
elif (spotifyType == 'track'):
|
|
9317
|
+
|
|
9318
|
+
# get the track data from spotify, as Sonos Soco data is incomplete.
|
|
9319
|
+
track:Track = self.GetTrack(spotifyId)
|
|
9320
|
+
if (track.Id is not None):
|
|
9321
|
+
playerState._Item = track
|
|
9322
|
+
if (playerState._Context is None):
|
|
9323
|
+
playerState._Context = Context()
|
|
9324
|
+
playerState._Context._Type = track.Album.Type
|
|
9325
|
+
playerState._Context._Uri = track.Album.Uri
|
|
9326
|
+
playerState._Context._Href = track.Album.Href
|
|
9327
|
+
playerState._Context._ExternalUrls = track.Album.ExternalUrls
|
|
9328
|
+
else:
|
|
9329
|
+
# if data could not be obtained from spotify, then use what's
|
|
9330
|
+
# available from Sonos Soco api metadata.
|
|
9331
|
+
playerState._CurrentlyPlayingType = 'track'
|
|
9332
|
+
playerState.ItemType = playerState._CurrentlyPlayingType
|
|
9333
|
+
playerState._Item = Track()
|
|
9334
|
+
playerState._Item._Name = sonosTrackInfo.get('title','')
|
|
9335
|
+
playerState._Item._Uri = spotifyUri
|
|
9336
|
+
playerState._Item._Description = 'Sonos device does not provide a description'
|
|
9337
|
+
sTimeValue:str = sonosTrackInfo.get('duration',None)
|
|
9338
|
+
playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
9339
|
+
playerState._Item._Explicit = False
|
|
9340
|
+
playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
|
|
9341
|
+
playerState._Item._HtmlDescription = playerState._Item._Description
|
|
9342
|
+
playerState._Item._Id = spotifyId
|
|
9343
|
+
playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
|
|
9344
|
+
playerState._Item._ReleaseDate = '0000'
|
|
9345
|
+
playerState._Item._ReleaseDatePrecision = 'year'
|
|
9346
|
+
playerState._Item._Type = 'track'
|
|
9347
|
+
playerState._Item._Album = Album()
|
|
9348
|
+
playerState._Item._Album._Name = sonosTrackInfo.get('album','')
|
|
9349
|
+
playerState._Item._Album.Images.append(ImageObject())
|
|
9350
|
+
playerState._Item._Album.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
9351
|
+
playerState._Item.Artists.append(Artist())
|
|
9352
|
+
playerState._Item.Artists[0]._Name = sonosTrackInfo.get('artist','')
|
|
9353
|
+
|
|
9354
|
+
# set transport actions.
|
|
9355
|
+
currentTransportState:str = sonosTransportInfo.get('current_transport_state','')
|
|
9356
|
+
if currentTransportState == 'PLAYING':
|
|
9357
|
+
playerState._IsPlaying = True
|
|
9358
|
+
elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED']:
|
|
9359
|
+
playerState.Actions._Pausing = True
|
|
9360
|
+
|
|
9361
|
+
# only update the following PlayState attributes from SoCo if they are NOT set.
|
|
9362
|
+
# these values are used to set the Sonos PlayMode value, which is the actual value that determines
|
|
9363
|
+
# what the Repeat and Shuffle settings are (ya, it's weird!). What is odd is that the Spotify
|
|
9364
|
+
# Web API PlayerState values for Shuffle and Repeat can be different than what the Sonos Controller
|
|
9365
|
+
# API (SoCo) report for the device! This is why we use the Spotify Web API PlayerState values.
|
|
9366
|
+
if playerState._ShuffleState is None:
|
|
9367
|
+
playerState._ShuffleState = sonosPlayer.shuffle
|
|
9368
|
+
if playerState._RepeatState is None:
|
|
9369
|
+
sonosRepeat = sonosPlayer.repeat
|
|
9370
|
+
if sonosRepeat == 'ONE':
|
|
9371
|
+
playerState._RepeatState = 'track'
|
|
9372
|
+
elif sonosRepeat == False:
|
|
9373
|
+
playerState._RepeatState = 'off'
|
|
9374
|
+
elif sonosRepeat == True:
|
|
9375
|
+
playerState._RepeatState = 'context'
|
|
9376
|
+
|
|
9377
|
+
# trace.
|
|
9378
|
+
_logsi.LogObject(SILevel.Verbose, "Current Spotify player PlayState (built from Sonos device current state) for device: %s" % (scDevice.Title), playerState, excludeNonPublic=True)
|
|
9379
|
+
return playerState
|
|
9380
|
+
|
|
9381
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
9382
|
+
except Exception as ex:
|
|
9383
|
+
|
|
9384
|
+
# format unhandled exception.
|
|
9385
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
9386
|
+
|
|
9387
|
+
finally:
|
|
9388
|
+
|
|
9389
|
+
# trace.
|
|
9390
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
9391
|
+
|
|
9392
|
+
|
|
9161
9393
|
def GetSpotifyConnectDevice(
|
|
9162
9394
|
self,
|
|
9163
9395
|
deviceValue:str,
|
|
@@ -9244,6 +9476,7 @@ class SpotifyClient:
|
|
|
9244
9476
|
apiMethodName:str = 'GetSpotifyConnectDevice'
|
|
9245
9477
|
apiMethodParms:SIMethodParmListContext = None
|
|
9246
9478
|
scDevice:SpotifyConnectDevice = None
|
|
9479
|
+
sonosPlayer:Soco = None
|
|
9247
9480
|
|
|
9248
9481
|
try:
|
|
9249
9482
|
|
|
@@ -9302,7 +9535,7 @@ class SpotifyClient:
|
|
|
9302
9535
|
# if this is a Sonos device and there is no Spotify Client Application token then don't bother.
|
|
9303
9536
|
# in this case, we will exit and let the calling method switch to the Sonos device and play
|
|
9304
9537
|
# using the Sonos local queue.
|
|
9305
|
-
if (
|
|
9538
|
+
if (scDevice.IsSonos):
|
|
9306
9539
|
|
|
9307
9540
|
# check if the Spotify Desktop Application Client oauth2 token is defined.
|
|
9308
9541
|
hasToken:bool = AuthClient.HasTokenForKey(
|
|
@@ -9318,6 +9551,10 @@ class SpotifyClient:
|
|
|
9318
9551
|
_logsi.LogVerbose("Spotify Desktop Application Client oauth2 token was not found for Spotify LoginId: '%s'" % (self._SpotifyConnectLoginId))
|
|
9319
9552
|
return scDevice
|
|
9320
9553
|
|
|
9554
|
+
# get Sonos Controller instance for the device.
|
|
9555
|
+
_logsi.LogVerbose("Target device is Sonos; getting Sonos Controller instance for device %s" % (scDevice.Title))
|
|
9556
|
+
sonosPlayer = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
9557
|
+
|
|
9321
9558
|
# at this point, we will try to activate the device.
|
|
9322
9559
|
|
|
9323
9560
|
# was device activation requested? if not, then we are done.
|
|
@@ -9342,87 +9579,20 @@ class SpotifyClient:
|
|
|
9342
9579
|
tokenStorageDir=self.TokenStorageDir,
|
|
9343
9580
|
tokenStorageFile=self.TokenStorageFile)
|
|
9344
9581
|
|
|
9345
|
-
#
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
# # some devices only support a single connection on the spotify connect
|
|
9359
|
-
# # zeroconf webserver, so we have to give it a little time to process the
|
|
9360
|
-
# # disconnect command before we try to call the Connect method.
|
|
9361
|
-
# _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % DELAY_DISCONNECT)
|
|
9362
|
-
# time.sleep(DELAY_DISCONNECT)
|
|
9363
|
-
|
|
9364
|
-
# # some manufacturers will reset the device host ip port after a disconnect (e.g. "Denon", etc).
|
|
9365
|
-
# # due to this, the device will de-register itself via zeroconf and then re-register itself.
|
|
9366
|
-
# # we will now wait for the device to be re-discovered by zeroconf.
|
|
9367
|
-
# loopTotalDelay:float = 0
|
|
9368
|
-
# LOOP_DELAY:float = 0.25
|
|
9369
|
-
# while True:
|
|
9370
|
-
|
|
9371
|
-
# # wait just a bit between available device list queries.
|
|
9372
|
-
# _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % LOOP_DELAY)
|
|
9373
|
-
# time.sleep(LOOP_DELAY)
|
|
9374
|
-
# loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
9375
|
-
|
|
9376
|
-
# with self._SpotifyConnectDirectory._Zeroconf_Lock:
|
|
9377
|
-
|
|
9378
|
-
# # check for the device in the available device list.
|
|
9379
|
-
# # if we find it, then we are done.
|
|
9380
|
-
|
|
9381
|
-
# device:Device = self.GetPlayerDevice(deviceResultId, True)
|
|
9382
|
-
# if device is not None:
|
|
9383
|
-
# _logsi.LogVerbose("Spotify Connect device \"%s\" is now in the available device list; device found within %f seconds of Connect" % (scDevice.Title, loopTotalDelay))
|
|
9384
|
-
# break
|
|
9385
|
-
|
|
9582
|
+
# is this a Sonos device?
|
|
9583
|
+
if (scDevice.IsSonos):
|
|
9584
|
+
|
|
9585
|
+
# trace.
|
|
9586
|
+
sonosMusicSource:str = sonosPlayer.music_source
|
|
9587
|
+
_logsi.LogVerbose("Sonos device %s music source before Connect: \"%s\"" % (scDevice.Title, sonosMusicSource))
|
|
9588
|
+
|
|
9589
|
+
# was the Sonos device music source set to SPOTIFY_CONNECT?
|
|
9590
|
+
# if not, then issue a Disconnect to (hopefully) reset the music source. this should allow
|
|
9591
|
+
# the subsequent Connect to re-establish a SPOTIFY_CONNECT music source on the Sonos device.
|
|
9592
|
+
if sonosMusicSource != "SPOTIFY_CONNECT":
|
|
9593
|
+
_logsi.LogVerbose("Issuing Disconnect to Sonos Spotify Connect device %s" % (scDevice.Title))
|
|
9594
|
+
zcfResult = zconn.Disconnect(ignoreStatusResult=True)
|
|
9386
9595
|
|
|
9387
|
-
# # re-discover Spotify Connect devices on the network
|
|
9388
|
-
# _logsi.LogVerbose("Rediscovering Spotify Connect devices on the local network (due to previous disconnect)")
|
|
9389
|
-
# rediscovery:SpotifyDiscovery = SpotifyDiscovery(self._ZeroconfClient, printToConsole=False)
|
|
9390
|
-
# rediscovery.DiscoverDevices(timeout=self._SpotifyConnectDiscoveryTimeout)
|
|
9391
|
-
|
|
9392
|
-
# # trace.
|
|
9393
|
-
# _logsi.LogDictionary(SILevel.Verbose, "Rediscovered Spotify Connect devices on the local network (due to previous disconnect)", rediscovery.DiscoveryResults, prettyPrint=True)
|
|
9394
|
-
|
|
9395
|
-
# # process all rediscovered devices, and update selected device.
|
|
9396
|
-
# rediscovery:ZeroconfDiscoveryResult
|
|
9397
|
-
# for rediscovery in rediscovery.DiscoveryResults:
|
|
9398
|
-
|
|
9399
|
-
# # add / update selected device discovery results by serviceinfo key value.
|
|
9400
|
-
# if (rediscovery.Key == scDevice.DiscoveryResult.Key):
|
|
9401
|
-
|
|
9402
|
-
# # save old discovery result id so we can log it later.
|
|
9403
|
-
# oldDiscoveryResultId:str = discovery.Id
|
|
9404
|
-
|
|
9405
|
-
# # update selected device discovery result.
|
|
9406
|
-
# _logsi.LogObject(SILevel.Verbose, "Spotify Connect device \"%s\" DiscoveryResult instance rediscovered properties" % (rediscovery.Id), rediscovery, excludeNonPublic=True)
|
|
9407
|
-
# _logsi.LogVerbose("Updating Spotify Connect device \"%s\" DiscoveryResult instance with rediscovered properties" % (scDevice.Title))
|
|
9408
|
-
# scDevice.DiscoveryResult = rediscovery
|
|
9409
|
-
# discovery = rediscovery
|
|
9410
|
-
|
|
9411
|
-
# # did the host ip address or port change?
|
|
9412
|
-
# # if so, then we need to recreate the ZeroconfConnect instance
|
|
9413
|
-
# # in order to access the updated ip address and port.
|
|
9414
|
-
# if (discovery.HostIpAddress != zconn.HostIpAddress) \
|
|
9415
|
-
# or (discovery.HostIpPort != zconn.HostIpPort):
|
|
9416
|
-
|
|
9417
|
-
# _logsi.LogVerbose("Spotify Connect device info changed to %s from %s after disconnect; recreating ZeroconfConnect instance" % (discovery.Id, oldDiscoveryResultId))
|
|
9418
|
-
# zconn = ZeroconfConnect(discovery.HostIpAddress,
|
|
9419
|
-
# discovery.HostIpPort,
|
|
9420
|
-
# discovery.SpotifyConnectCPath,
|
|
9421
|
-
# useSSL=False,
|
|
9422
|
-
# tokenStorageDir=self.TokenStorageDir,
|
|
9423
|
-
# tokenStorageFile=self.TokenStorageFile)
|
|
9424
|
-
# break
|
|
9425
|
-
|
|
9426
9596
|
# connect the device to OUR Spotify Connect user context.
|
|
9427
9597
|
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
9428
9598
|
_logsi.LogVerbose("Issuing Connect to Spotify Connect device %s for user context \"%s\" (ip=%s:%s)" % (scDevice.Title, self._SpotifyConnectLoginId, zconn.HostIpAddress, zconn.HostIpPort))
|
|
@@ -9434,6 +9604,26 @@ class SpotifyClient:
|
|
|
9434
9604
|
# trace.
|
|
9435
9605
|
_logsi.LogVerbose("User context switched from \"%s\" to \"%s\" for Spotify Connect device %s" % (oldActiveUser, self._SpotifyConnectLoginId, scDevice.Title))
|
|
9436
9606
|
|
|
9607
|
+
# is this a Sonos device?
|
|
9608
|
+
if (scDevice.IsSonos):
|
|
9609
|
+
|
|
9610
|
+
# trace.
|
|
9611
|
+
sonosMusicSource:str = sonosPlayer.music_source
|
|
9612
|
+
_logsi.LogVerbose("Sonos device %s music source after Connect: \"%s\"" % (scDevice.Title, sonosMusicSource))
|
|
9613
|
+
|
|
9614
|
+
# was the Sonos device music source set to Spotify Connect?
|
|
9615
|
+
# if not, then it's a lost cause at this point since it's probably in an UNKNOWN state.
|
|
9616
|
+
if sonosMusicSource != "SPOTIFY_CONNECT":
|
|
9617
|
+
_logsi.LogVerbose("Sonos device %s music source after Connect is not SPOTIFY_CONNECT; it will probably fail to play" % (scDevice.Title))
|
|
9618
|
+
|
|
9619
|
+
# at this point the Sonos music source should be set to SPOTIFY_CONNECT, and control
|
|
9620
|
+
# transferred to the device. note that the Sonos device will still NOT appear in the
|
|
9621
|
+
# player device list, but SHOULD be the active (restricted) device in player state.
|
|
9622
|
+
|
|
9623
|
+
# wait for the device to enter the Spotify Web API player device list, or
|
|
9624
|
+
# to become the active device.
|
|
9625
|
+
# Sonos devices are famous for not showing up in the device list, but DO
|
|
9626
|
+
# appear as the active (restricted) device!
|
|
9437
9627
|
loopTotalDelay:float = 0
|
|
9438
9628
|
LOOP_DELAY:float = 0.350
|
|
9439
9629
|
while True:
|
|
@@ -9466,7 +9656,7 @@ class SpotifyClient:
|
|
|
9466
9656
|
# only check so many times before we give up;
|
|
9467
9657
|
# we will keep checking until we find the device, or we exceed the verifyTimeout value.
|
|
9468
9658
|
if (loopTotalDelay > verifyTimeout):
|
|
9469
|
-
_logsi.LogWarning("Verification timeout waiting for Spotify Connect device %s to be added to the player device list; device not found within %f seconds of Connect" % (scDevice.Title, verifyTimeout))
|
|
9659
|
+
_logsi.LogWarning("Verification timeout waiting for Spotify Connect device %s to be added to the player device list or become the active device; device not found within %f seconds of Connect" % (scDevice.Title, verifyTimeout))
|
|
9470
9660
|
break
|
|
9471
9661
|
|
|
9472
9662
|
# return the device instance.
|
|
@@ -11217,15 +11407,24 @@ class SpotifyClient:
|
|
|
11217
11407
|
# resolve / activate the device object if needed.
|
|
11218
11408
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
11219
11409
|
|
|
11220
|
-
#
|
|
11221
|
-
|
|
11222
|
-
urlParms['device_id'] = scDevice.Id
|
|
11410
|
+
# is this an active Sonos device?
|
|
11411
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
11223
11412
|
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11413
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
|
|
11414
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
11415
|
+
sonosPlayer.pause()
|
|
11416
|
+
|
|
11417
|
+
else:
|
|
11418
|
+
|
|
11419
|
+
# build spotify web api request parameters.
|
|
11420
|
+
urlParms:dict = {}
|
|
11421
|
+
urlParms['device_id'] = scDevice.Id
|
|
11422
|
+
|
|
11423
|
+
# execute spotify web api request.
|
|
11424
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/pause')
|
|
11425
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
11426
|
+
msg.UrlParameters = urlParms
|
|
11427
|
+
self.MakeRequest('PUT', msg)
|
|
11229
11428
|
|
|
11230
11429
|
# give spotify web api time to process the change.
|
|
11231
11430
|
if delay > 0:
|
|
@@ -11732,15 +11931,24 @@ class SpotifyClient:
|
|
|
11732
11931
|
# resolve / activate the device object if needed.
|
|
11733
11932
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
11734
11933
|
|
|
11735
|
-
#
|
|
11736
|
-
|
|
11737
|
-
urlParms['device_id'] = scDevice.Id
|
|
11934
|
+
# is this an active Sonos device?
|
|
11935
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
11738
11936
|
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11937
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PLAY (RESUME)" % (scDevice.Title))
|
|
11938
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
11939
|
+
sonosPlayer.play()
|
|
11940
|
+
|
|
11941
|
+
else:
|
|
11942
|
+
|
|
11943
|
+
# build spotify web api request parameters.
|
|
11944
|
+
urlParms:dict = {}
|
|
11945
|
+
urlParms['device_id'] = scDevice.Id
|
|
11946
|
+
|
|
11947
|
+
# execute spotify web api request.
|
|
11948
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play')
|
|
11949
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
11950
|
+
msg.UrlParameters = urlParms
|
|
11951
|
+
self.MakeRequest('PUT', msg)
|
|
11744
11952
|
|
|
11745
11953
|
# give spotify web api time to process the change.
|
|
11746
11954
|
if delay > 0:
|
|
@@ -11871,18 +12079,28 @@ class SpotifyClient:
|
|
|
11871
12079
|
newPositionMS = 0
|
|
11872
12080
|
positionMS = newPositionMS
|
|
11873
12081
|
|
|
11874
|
-
#
|
|
11875
|
-
|
|
11876
|
-
{
|
|
11877
|
-
'position_ms': positionMS
|
|
11878
|
-
}
|
|
11879
|
-
urlParms['device_id'] = scDevice.Id
|
|
12082
|
+
# is this an active Sonos device?
|
|
12083
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
11880
12084
|
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
12085
|
+
sonosPosition:str = mediaPositionHMS_fromSeconds(positionMS / 1000) # convert from milliseconds to Sonos H:MM:SS format
|
|
12086
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: SEEK (sonosPosition=%s)" % (scDevice.Title, sonosPosition))
|
|
12087
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12088
|
+
sonosPlayer.seek(str(sonosPosition))
|
|
12089
|
+
|
|
12090
|
+
else:
|
|
12091
|
+
|
|
12092
|
+
# build spotify web api request parameters.
|
|
12093
|
+
urlParms:dict = \
|
|
12094
|
+
{
|
|
12095
|
+
'position_ms': positionMS
|
|
12096
|
+
}
|
|
12097
|
+
urlParms['device_id'] = scDevice.Id
|
|
12098
|
+
|
|
12099
|
+
# execute spotify web api request.
|
|
12100
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/seek')
|
|
12101
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12102
|
+
msg.UrlParameters = urlParms
|
|
12103
|
+
self.MakeRequest('PUT', msg)
|
|
11886
12104
|
|
|
11887
12105
|
# give spotify web api time to process the change.
|
|
11888
12106
|
if delay > 0:
|
|
@@ -11964,15 +12182,24 @@ class SpotifyClient:
|
|
|
11964
12182
|
# resolve / activate the device object if needed.
|
|
11965
12183
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
11966
12184
|
|
|
11967
|
-
#
|
|
11968
|
-
|
|
11969
|
-
urlParms['device_id'] = scDevice.Id
|
|
12185
|
+
# is this an active Sonos device?
|
|
12186
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
11970
12187
|
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
12188
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: NEXT" % (scDevice.Title))
|
|
12189
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12190
|
+
sonosPlayer.next()
|
|
12191
|
+
|
|
12192
|
+
else:
|
|
12193
|
+
|
|
12194
|
+
# build spotify web api request parameters.
|
|
12195
|
+
urlParms:dict = {}
|
|
12196
|
+
urlParms['device_id'] = scDevice.Id
|
|
12197
|
+
|
|
12198
|
+
# execute spotify web api request.
|
|
12199
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/next')
|
|
12200
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12201
|
+
msg.UrlParameters = urlParms
|
|
12202
|
+
self.MakeRequest('POST', msg)
|
|
11976
12203
|
|
|
11977
12204
|
# give spotify web api time to process the change.
|
|
11978
12205
|
if delay > 0:
|
|
@@ -12054,15 +12281,24 @@ class SpotifyClient:
|
|
|
12054
12281
|
# resolve / activate the device object if needed.
|
|
12055
12282
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12056
12283
|
|
|
12057
|
-
#
|
|
12058
|
-
|
|
12059
|
-
urlParms['device_id'] = scDevice.Id
|
|
12284
|
+
# is this an active Sonos device?
|
|
12285
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
12060
12286
|
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12287
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PREVIOUS" % (scDevice.Title))
|
|
12288
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12289
|
+
sonosPlayer.previous()
|
|
12290
|
+
|
|
12291
|
+
else:
|
|
12292
|
+
|
|
12293
|
+
# build spotify web api request parameters.
|
|
12294
|
+
urlParms:dict = {}
|
|
12295
|
+
urlParms['device_id'] = scDevice.Id
|
|
12296
|
+
|
|
12297
|
+
# execute spotify web api request.
|
|
12298
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/previous')
|
|
12299
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12300
|
+
msg.UrlParameters = urlParms
|
|
12301
|
+
self.MakeRequest('POST', msg)
|
|
12066
12302
|
|
|
12067
12303
|
# give spotify web api time to process the change.
|
|
12068
12304
|
if delay > 0:
|
|
@@ -12152,18 +12388,44 @@ class SpotifyClient:
|
|
|
12152
12388
|
# resolve / activate the device object if needed.
|
|
12153
12389
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12154
12390
|
|
|
12155
|
-
#
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12391
|
+
# is this an active Sonos device?
|
|
12392
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
12393
|
+
|
|
12394
|
+
# get current Spotify Connect player state.
|
|
12395
|
+
playerState = self.GetSonosPlayerPlaybackState(scDevice)
|
|
12396
|
+
|
|
12397
|
+
# set the Sonos PlayMode directly (instead of setting the Sonos repeat property)!
|
|
12398
|
+
# The Spotify Web API PlayerState `ShuffleState` value can sometimes be different than what the
|
|
12399
|
+
# Sonos device reports for its `shuffle` property value!
|
|
12400
|
+
if state == 'track':
|
|
12401
|
+
sonos_repeat = 'ONE'
|
|
12402
|
+
elif state == 'off':
|
|
12403
|
+
sonos_repeat = False
|
|
12404
|
+
elif state == 'context':
|
|
12405
|
+
sonos_repeat = True
|
|
12406
|
+
else:
|
|
12407
|
+
sonos_repeat = False
|
|
12408
|
+
playMode:str = SONOS_PLAY_MODE_BY_MEANING[(playerState.ShuffleState, sonos_repeat)]
|
|
12409
|
+
|
|
12410
|
+
# execute SoCo api request.
|
|
12411
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: REPEAT (playmode=%s)" % (scDevice.Title, playMode))
|
|
12412
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12413
|
+
sonosPlayer.play_mode = playMode
|
|
12161
12414
|
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12415
|
+
else:
|
|
12416
|
+
|
|
12417
|
+
# build spotify web api request parameters.
|
|
12418
|
+
urlParms:dict = \
|
|
12419
|
+
{
|
|
12420
|
+
'state': state
|
|
12421
|
+
}
|
|
12422
|
+
urlParms['device_id'] = scDevice.Id
|
|
12423
|
+
|
|
12424
|
+
# execute spotify web api request.
|
|
12425
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/repeat')
|
|
12426
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12427
|
+
msg.UrlParameters = urlParms
|
|
12428
|
+
self.MakeRequest('PUT', msg)
|
|
12167
12429
|
|
|
12168
12430
|
# give spotify web api time to process the change.
|
|
12169
12431
|
if delay > 0:
|
|
@@ -12252,18 +12514,45 @@ class SpotifyClient:
|
|
|
12252
12514
|
# resolve / activate the device object if needed.
|
|
12253
12515
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12254
12516
|
|
|
12255
|
-
#
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12517
|
+
# is this an active Sonos device?
|
|
12518
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
12519
|
+
|
|
12520
|
+
# get current Spotify Connect player state.
|
|
12521
|
+
playerState = self.GetSonosPlayerPlaybackState(scDevice)
|
|
12522
|
+
|
|
12523
|
+
# set the Sonos PlayMode directly (instead of setting the Sonos `shuffle` property)!
|
|
12524
|
+
# the Spotify Web API PlayerState `RepeatState` value can sometimes be different than what the
|
|
12525
|
+
# Sonos device reports for its `repeat` property value!
|
|
12526
|
+
sonos_repeat:str
|
|
12527
|
+
if playerState.RepeatState == 'track':
|
|
12528
|
+
sonos_repeat = 'ONE'
|
|
12529
|
+
elif playerState.RepeatState == 'off':
|
|
12530
|
+
sonos_repeat = False
|
|
12531
|
+
elif playerState.RepeatState == 'context':
|
|
12532
|
+
sonos_repeat = True
|
|
12533
|
+
else: # assume off if nothing else.
|
|
12534
|
+
sonos_repeat = False
|
|
12535
|
+
playMode:str = SONOS_PLAY_MODE_BY_MEANING[(state, sonos_repeat)]
|
|
12536
|
+
|
|
12537
|
+
# execute SoCo api request.
|
|
12538
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: SHUFFLE (playmode=%s)" % (scDevice.Title, playMode))
|
|
12539
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12540
|
+
sonosPlayer.play_mode = playMode
|
|
12261
12541
|
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12542
|
+
else:
|
|
12543
|
+
|
|
12544
|
+
# build spotify web api request parameters.
|
|
12545
|
+
urlParms:dict = \
|
|
12546
|
+
{
|
|
12547
|
+
'state': state
|
|
12548
|
+
}
|
|
12549
|
+
urlParms['device_id'] = scDevice.Id
|
|
12550
|
+
|
|
12551
|
+
# execute spotify web api request.
|
|
12552
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/shuffle')
|
|
12553
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12554
|
+
msg.UrlParameters = urlParms
|
|
12555
|
+
self.MakeRequest('PUT', msg)
|
|
12267
12556
|
|
|
12268
12557
|
# give spotify web api time to process the change.
|
|
12269
12558
|
if delay > 0:
|
|
@@ -12351,18 +12640,27 @@ class SpotifyClient:
|
|
|
12351
12640
|
# resolve / activate the device object if needed.
|
|
12352
12641
|
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12353
12642
|
|
|
12354
|
-
#
|
|
12355
|
-
|
|
12356
|
-
{
|
|
12357
|
-
'volume_percent': volumePercent
|
|
12358
|
-
}
|
|
12359
|
-
urlParms['device_id'] = scDevice.Id
|
|
12643
|
+
# is this an active Sonos device?
|
|
12644
|
+
if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
|
|
12360
12645
|
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12646
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: VOLUME = %s" % (scDevice.Title, volumePercent))
|
|
12647
|
+
sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12648
|
+
sonosPlayer.volume = volumePercent
|
|
12649
|
+
|
|
12650
|
+
else:
|
|
12651
|
+
|
|
12652
|
+
# build spotify web api request parameters.
|
|
12653
|
+
urlParms:dict = \
|
|
12654
|
+
{
|
|
12655
|
+
'volume_percent': volumePercent
|
|
12656
|
+
}
|
|
12657
|
+
urlParms['device_id'] = scDevice.Id
|
|
12658
|
+
|
|
12659
|
+
# execute spotify web api request.
|
|
12660
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/volume')
|
|
12661
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12662
|
+
msg.UrlParameters = urlParms
|
|
12663
|
+
self.MakeRequest('PUT', msg)
|
|
12366
12664
|
|
|
12367
12665
|
# give spotify web api time to process the change.
|
|
12368
12666
|
if delay > 0:
|
|
@@ -12472,10 +12770,40 @@ class SpotifyClient:
|
|
|
12472
12770
|
|
|
12473
12771
|
# is this a Sonos device?
|
|
12474
12772
|
# if so, then control was automatically transferred by activating the device.
|
|
12475
|
-
# note that
|
|
12476
|
-
# considered a restricted device and the Spotify Web API cannot control it!
|
|
12477
|
-
|
|
12478
|
-
|
|
12773
|
+
# note that the Spotify Web API cannot stop nor start play on the device either,
|
|
12774
|
+
# as it's considered a restricted device and the Spotify Web API cannot control it!
|
|
12775
|
+
# all requests to control the device will be made via the Sonos Controller API.
|
|
12776
|
+
if (scDevice.IsSonos):
|
|
12777
|
+
|
|
12778
|
+
# get Sonos Controller instance for the device.
|
|
12779
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12780
|
+
|
|
12781
|
+
# get current Sonos transport status.
|
|
12782
|
+
sonosTransportInfo:dict = sonosPlayer.get_current_transport_info()
|
|
12783
|
+
currentTransportState:str = sonosTransportInfo.get('current_transport_state', None)
|
|
12784
|
+
currentTransportStatus:str = sonosTransportInfo.get('current_transport_status', None)
|
|
12785
|
+
_logsi.LogVerbose("Sonos device %s current transport state after activation: \"%s\" (Status=%s)" % (scDevice.Title, currentTransportState, currentTransportStatus))
|
|
12786
|
+
|
|
12787
|
+
# stop / start play as requested.
|
|
12788
|
+
wasCmdIssued:bool = False
|
|
12789
|
+
if currentTransportState == 'PLAYING':
|
|
12790
|
+
if play == False:
|
|
12791
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
|
|
12792
|
+
sonosPlayer.pause()
|
|
12793
|
+
wasCmdIssued = True
|
|
12794
|
+
elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED','TRANSITIONING']:
|
|
12795
|
+
if play == True:
|
|
12796
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PLAY" % (scDevice.Title))
|
|
12797
|
+
sonosPlayer.play()
|
|
12798
|
+
wasCmdIssued = True
|
|
12799
|
+
|
|
12800
|
+
# give SoCo api time to process the change.
|
|
12801
|
+
if (wasCmdIssued) and (delay > 0):
|
|
12802
|
+
_logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE_SONOS % delay)
|
|
12803
|
+
time.sleep(delay)
|
|
12804
|
+
|
|
12805
|
+
# trace.
|
|
12806
|
+
_logsi.LogVerbose("Sonos device detected (IsRestricted); bypassing call to Spotify Web API Transfer Playback endpoint")
|
|
12479
12807
|
return
|
|
12480
12808
|
|
|
12481
12809
|
# before transferring playback to the device, we first need to check to see if anything
|