1212import requests
1313from websockets .exceptions import ConnectionClosed , InvalidHandshake
1414from websockets .sync .client import connect
15+ # from ..amplipi import tasks
1516
1617
1718@dataclass
@@ -139,7 +140,7 @@ class Event:
139140 data : Union [None , Track , PlayChange , VolumeChange , SeekChange , ValueChange ] = None
140141
141142 @staticmethod
142- def from_json (json_data : dict ) -> "Event" :
143+ def from_json (parent : "SpotifyMetadataReader" , json_data : dict ) -> "Event" :
143144 """Create an Event object from a JSON payload"""
144145 e = Event (event_type = json_data ["type" ])
145146 if e .event_type in ["active" , "inactive" ]:
@@ -155,76 +156,94 @@ def from_json(json_data: dict) -> "Event":
155156 e .data = SeekChange (** json_data ["data" ])
156157 elif e .event_type == "volume" :
157158 e .data = VolumeChange (** json_data ["data" ])
159+ volume = e .data .value
160+ if volume != parent .last_volume :
161+ url = f"http://localhost:{ parent ._api_port } " # TODO: Implement spotify-side volume bar to reflect the source volume bar's position
162+ # tasks.post.delay(url + 'volume', data={'volume': int(volume * 100)})
163+ delta = float ((volume - parent .last_volume ) / 100 )
164+ vol_change = requests .patch ("http://localhost/api/zones" , json = {"zones" : [0 , 1 , 2 , 3 , 4 , 5 ], "update" : {"vol_delta_f" : delta }}, timeout = 5 ) # TODO: set zone list correctly
165+ if vol_change .ok :
166+ parent .last_volume = volume # update last_volume for future syncs
167+
158168 elif e .event_type in ["shuffle_context" , "repeat_context" , "repeat_track" ]:
159169 e .data = ValueChange (** json_data ["data" ])
160170 return e
161171
162172
163- def read_metadata (url ) -> Optional [Track ]:
164- """
165- Reads metadata from the given URL and writes it to the specified metadata file.
166- If the metadata file already exists, it will be overwritten.
167- """
168- endpoint = url + "/status"
169-
170- # Send a GET request to the URL to retrieve the metadata
171- response = requests .get (endpoint , timeout = 2 )
172-
173- # Check if the request was successful
174- if response .status_code == 200 :
175- # Parse the metadata from the response
176- return Status .from_dict (response .json ())
177- elif response .status_code == 204 :
178- # the metadata isn't populated yet
179- return Status (stopped = True )
180- else :
181- # If the request failed, print an error message
182- print (f"Failed to retrieve metadata from { endpoint } . Status code: { response .status_code } " )
183- return Status ()
184-
185-
186- def watch_metadata (url , metadata_file , debug = False ) -> None :
187- """
188- Watches the api at `url` for metadata updates and writes the current state to `metadata_file`.
189- If the metadata file already exists, it will be overwritten.
190- """
191- # Get the websocket-based event updates
192- ws_events = url .replace ("http://" , "ws://" ) + "/events"
193- try :
194- # read the initial state
195- metadata = read_metadata (url )
196- with open (metadata_file , 'w' , encoding = 'utf8' ) as mf :
197- mf .write (json .dumps (asdict (metadata )))
198- if debug :
199- print (f"Initial metadata: { metadata } " )
200- # Connect to the websocket and listen for state changes
201- with connect (ws_events , open_timeout = 5 ) as websocket :
202- while True :
203- try :
204- msg = websocket .recv ()
205- if debug :
206- print (f"Received: { msg } " )
207- event = Event .from_json (json .loads (msg ))
208- if event .event_type == "metadata" :
209- metadata .track = event .data
210- elif event .event_type == "playing" :
211- metadata .stopped = False
212- metadata .paused = False
213- elif event .event_type == "paused" :
214- metadata .paused = True
215- elif event .event_type == "stopped" :
216- metadata .stopped = True
217- metadata .track = Track ()
218- else :
219- continue
220- with open (args .metadata_file , 'w' , encoding = 'utf8' ) as mf :
221- mf .write (json .dumps (asdict (metadata )))
222- except (KeyError , ConnectionClosed , json .JSONDecodeError ) as e :
223- print (f"Error: { e } " )
224- break
225- except (OSError , InvalidHandshake , TimeoutError ) as e :
226- print (f"Error: { e } " )
227- return
173+ class SpotifyMetadataReader :
174+
175+ def __init__ (self , url , metadata_file , debug = False ):
176+ self .url : str = url
177+ self .metadata_file : str = metadata_file
178+ self .debug : bool = debug
179+
180+ self .last_volume : float = 0
181+ self ._api_port : int = 3678 + int ([char for char in metadata_file if char .isdigit ()][0 ])
182+
183+ def read_metadata (self ) -> Optional [Track ]:
184+ """
185+ Reads metadata from the given URL and writes it to the specified metadata file.
186+ If the metadata file already exists, it will be overwritten.
187+ """
188+ endpoint = self .url + "/status"
189+
190+ # Send a GET request to the URL to retrieve the metadata
191+ response = requests .get (endpoint , timeout = 2 )
192+
193+ # Check if the request was successful
194+ if response .status_code == 200 :
195+ # Parse the metadata from the response
196+ return Status .from_dict (response .json ())
197+ elif response .status_code == 204 :
198+ # the metadata isn't populated yet
199+ return Status (stopped = True )
200+ else :
201+ # If the request failed, print an error message
202+ print (f"Failed to retrieve metadata from { endpoint } . Status code: { response .status_code } " )
203+ return Status ()
204+
205+ def watch_metadata (self ) -> None :
206+ """
207+ Watches the api at `url` for metadata updates and writes the current state to `metadata_file`.
208+ If the metadata file already exists, it will be overwritten.
209+ """
210+ # Get the websocket-based event updates
211+ ws_events = self .url .replace ("http://" , "ws://" ) + "/events"
212+ try :
213+ # read the initial state
214+ metadata = self .read_metadata ()
215+ with open (self .metadata_file , 'w' , encoding = 'utf8' ) as mf :
216+ mf .write (json .dumps (asdict (metadata )))
217+ if self .debug :
218+ print (f"Initial metadata: { metadata } " )
219+ # Connect to the websocket and listen for state changes
220+ with connect (ws_events , open_timeout = 5 ) as websocket :
221+ while True :
222+ try :
223+ msg = websocket .recv ()
224+ if self .debug :
225+ print (f"Received: { msg } " )
226+ event = Event .from_json (self , json .loads (msg ))
227+ if event .event_type == "metadata" :
228+ metadata .track = event .data
229+ elif event .event_type == "playing" :
230+ metadata .stopped = False
231+ metadata .paused = False
232+ elif event .event_type == "paused" :
233+ metadata .paused = True
234+ elif event .event_type == "stopped" :
235+ metadata .stopped = True
236+ metadata .track = Track ()
237+ else :
238+ continue
239+ with open (args .metadata_file , 'w' , encoding = 'utf8' ) as mf :
240+ mf .write (json .dumps (asdict (metadata )))
241+ except (KeyError , ConnectionClosed , json .JSONDecodeError ) as e :
242+ print (f"Error: { e } " )
243+ break
244+ except (OSError , InvalidHandshake , TimeoutError ) as e :
245+ print (f"Error: { e } " )
246+ return
228247
229248
230249if __name__ == "__main__" :
@@ -239,7 +258,7 @@ def watch_metadata(url, metadata_file, debug=False) -> None:
239258
240259 while (True ):
241260 try :
242- watch_metadata (args .url , args .metadata_file , args .debug )
261+ SpotifyMetadataReader (args .url , args .metadata_file , args .debug ). watch_metadata ( )
243262 except (KeyboardInterrupt , SystemExit ):
244263 print ("Exiting..." )
245264 break
0 commit comments