Skip to content

Commit f57e5cd

Browse files
Add basic vol change functionality to spotify metadata reader
1 parent 2c7b7a5 commit f57e5cd

File tree

1 file changed

+86
-67
lines changed

1 file changed

+86
-67
lines changed

streams/spot_connect_meta.py

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import requests
1313
from websockets.exceptions import ConnectionClosed, InvalidHandshake
1414
from 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

230249
if __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

Comments
 (0)