Skip to content

Commit fd156f9

Browse files
Make the volume handler multithreaded and event based on the spotify side, combine both prior volumes into a more authoritative "shared volume" that is checked against as the authority for what is true or what should become true
1 parent ea18bfe commit fd156f9

File tree

1 file changed

+55
-46
lines changed

1 file changed

+55
-46
lines changed

streams/spotify_volume_handler.py

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
import argparse
33
from time import sleep
44
import requests
5+
import websockets
6+
from spot_connect_meta import Event
7+
import json
8+
import asyncio
9+
import threading
510

611

712
class 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

2645
class 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

5168
class 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

Comments
 (0)