22import argparse
33from time import sleep
44import requests
5+ import websockets
6+ from spot_connect_meta import Event
7+ import json
8+ import asyncio
9+ import threading
510
611
712class SpotifyData :
@@ -10,27 +15,39 @@ def __init__(self, api_port, debug=False):
1015 self .api_port : int = api_port
1116 self .debug = debug
1217
13- self .status : dict = self .get_status ()
14- self .last_volume : int = self .get_volume ()
18+ self .volume : int = None
1519
16- def get_status (self ):
17- self .status = requests .get (f"http://localhost:{ self .api_port } /status" , timeout = 5 ).json ()
20+ threading .Thread (target = self .run_async_watch , daemon = True ).start ()
21+
22+ def run_async_watch (self ):
23+ asyncio .run (self .watch_vol ())
1824
19- def get_volume (self ): # Not terribly useful, but it's nice to have parity with the AmpliPiData object
20- if self .status :
21- if self .debug :
22- print (f"Got Spotify volume: { self .status ['volume' ]} " )
23- return self .status ["volume" ]
25+ async def watch_vol (self ):
26+ try :
27+ # Connect to the websocket and listen for state changes
28+ async with websockets .connect (f"ws://localhost:{ self .api_port } /events" , open_timeout = 5 ) as websocket :
29+ while True :
30+ print ("Watching volume!" )
31+ try :
32+ msg = await websocket .recv ()
33+ event = Event .from_json (json .loads (msg ))
34+ if event .event_type == "volume" :
35+ self .volume = event .data .value
36+
37+ except Exception as e :
38+ print (f"Error: { e } " )
39+ return
40+ except Exception as e :
41+ print (f"Error: { e } " )
42+ return
2443
2544
2645class AmpliPiData :
2746
2847 def __init__ (self , source_id , debug = False ):
2948 self .source_id = source_id
3049 self .debug = debug
31-
32- self .status : dict = self .get_status ()
33- self .last_volume : float = self .get_volume ()
50+ self .status : dict = None
3451
3552 self .connected_zones : list = []
3653
@@ -40,12 +57,12 @@ def get_status(self):
4057 self .connected_zones = [zone for zone in self .status ["zones" ] if zone ["source_id" ] == self .source_id ]
4158
4259 def get_volume (self ):
60+ if not self .status :
61+ self .get_status ()
62+
4363 if self .connected_zones :
4464 total_vol_f = sum ([zone ["vol_f" ] for zone in self .connected_zones ])
45- avg_vol = total_vol_f / len (self .connected_zones )
46- if self .debug :
47- print (f"Got AmpliPi volume: { avg_vol } " )
48- return avg_vol
65+ return round (total_vol_f / len (self .connected_zones ), 3 ) # Round down to 2 decimals
4966
5067
5168class SpotifyVolumeHandler :
@@ -54,54 +71,45 @@ def __init__(self, port, debug=False):
5471 self .amplipi = AmpliPiData (port - 3679 , debug )
5572 self .spotify = SpotifyData (port , debug )
5673 self .debug : bool = debug
74+ self .shared_volume = self .amplipi .get_volume ()
5775
58- def update_amplipi_volume (self , volume ):
76+ def update_amplipi_volume (self , amplipi_volume ):
5977 """Update AmpliPi's volume via the Spotify client volume slider"""
60- delta = float ((volume - self .spotify .last_volume ) / 100 )
78+ delta = float ((self .spotify .volume / 100 ) - amplipi_volume )
6179 requests .patch ("http://localhost/api/zones" , json = {"zones" : [zone ["id" ] for zone in self .amplipi .connected_zones ], "update" : {"vol_delta_f" : delta , "mute" : False }}, timeout = 5 )
62- self .spotify . last_volume = volume
80+ self .shared_volume = self . spotify . volume / 100
6381
64- def update_spotify_volume (self , volume ):
82+ def update_spotify_volume (self , amplipi_volume ):
6583 """Update the Spotify client's volume slider position based on the averaged volume of all connected zones in AmpliPi"""
6684 if self .debug :
67- print (f"Spotify vol updated: { volume } " )
85+ print (f"Spotify vol updated: { amplipi_volume } " )
6886 url = f"http://localhost:{ self .spotify .api_port } "
69- new_vol = int (volume * 100 )
70- print (f"vol: { volume } , last_vol: { self .amplipi .last_volume } " )
87+ new_vol = int (amplipi_volume * 100 )
7188 requests .post (url + '/player/volume' , json = {"volume" : new_vol }, timeout = 5 )
72- self .amplipi .last_volume = volume
73- self .spotify .last_volume = new_vol
74-
75- def get_statuses (self ):
76- self .amplipi .get_status ()
77- if self .amplipi .last_volume is None :
78- self .amplipi .last_volume = self .amplipi .get_volume ()
79-
80- self .spotify .get_status ()
81- if self .spotify .last_volume is None :
82- self .spotify .last_volume = self .spotify .get_volume ()
89+ self .shared_volume = amplipi_volume
8390
8491 def handle_volumes (self ):
8592 while True :
8693 try :
87- self .get_statuses ()
94+ self .amplipi . get_status ()
8895 amplipi_volume = self .amplipi .get_volume ()
89- spotify_volume = self .spotify .get_volume ()
9096
9197 if self .debug :
92- print (f"amplipi: { amplipi_volume } , last: { self .amplipi .last_volume } " )
93- print (f"spotify: { spotify_volume } , last: { self .spotify .last_volume } " )
98+ print (f"amplipi: { amplipi_volume } , spotify: { self .spotify .volume } , shared: { self .shared_volume } " )
9499 print ("\n \n \n " )
95- if spotify_volume != self .spotify .last_volume :
100+
101+ if self .spotify .volume is None : # Useful for getting spotify's initial state (by forcibly setting it)
102+ self .update_spotify_volume (amplipi_volume )
103+ elif self .spotify .volume != (self .shared_volume * 100 ):
96104 if self .debug :
97- print ("Updating spotify vol..." )
98- self .update_amplipi_volume (spotify_volume )
99- elif amplipi_volume != self .amplipi . last_volume or int (amplipi_volume * 100 ) != spotify_volume :
105+ print ("Updating AmpliPi vol..." )
106+ self .update_amplipi_volume (amplipi_volume )
107+ elif amplipi_volume != self .shared_volume or ( int (amplipi_volume * 100 ) != self . spotify . volume ) :
100108 if self .debug :
101- print ("Updating amplipi vol..." )
109+ print ("Updating Spotify vol..." )
102110 self .update_spotify_volume (amplipi_volume )
103111 except Exception as e :
104- print (f"ERROR : { e } " )
112+ print (f"Error : { e } " )
105113 sleep (2 )
106114
107115
@@ -114,13 +122,14 @@ def handle_volumes(self):
114122
115123 args = parser .parse_args ()
116124
125+ handler = SpotifyVolumeHandler (args .port , args .debug )
117126 while (True ):
118127 try :
119- SpotifyVolumeHandler ( args . port , args . debug ) .handle_volumes ()
128+ handler .handle_volumes ()
120129 except (KeyboardInterrupt , SystemExit ):
121130 print ("Exiting..." )
122131 break
123132 except Exception as e :
124- print (f"Error: { e } " )
133+ print (f"Error 139 : { e } " )
125134 sleep (5 )
126135 continue
0 commit comments