feat: terminal spotify client (ncmpcpp-style)
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import threading
|
||||
import time
|
||||
from .client import SpotifyClient
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, client: SpotifyClient):
|
||||
self.client = client
|
||||
self._poll_thread = None
|
||||
self._running = False
|
||||
self._callbacks = []
|
||||
self.current_playback = None
|
||||
self.current_track = None
|
||||
self.devices = []
|
||||
|
||||
def register_callback(self, cb):
|
||||
self._callbacks.append(cb)
|
||||
|
||||
def _notify(self):
|
||||
for cb in self._callbacks:
|
||||
try:
|
||||
cb(self.current_playback, self.current_track)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def start_polling(self, interval=2):
|
||||
self._running = True
|
||||
self._poll_thread = threading.Thread(target=self._poll_loop, args=(interval,), daemon=True)
|
||||
self._poll_thread.start()
|
||||
|
||||
def stop_polling(self):
|
||||
self._running = False
|
||||
if self._poll_thread:
|
||||
self._poll_thread.join(timeout=3)
|
||||
|
||||
def _poll_loop(self, interval):
|
||||
while self._running:
|
||||
try:
|
||||
self.refresh()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(interval)
|
||||
|
||||
def refresh(self):
|
||||
try:
|
||||
self.current_playback = self.client.get_playback()
|
||||
if self.current_playback:
|
||||
self.devices = self.current_playback.get("devices", [])
|
||||
except Exception:
|
||||
self.current_playback = None
|
||||
|
||||
try:
|
||||
self.current_track = self.client.get_current_track()
|
||||
except Exception:
|
||||
self.current_track = None
|
||||
|
||||
self._notify()
|
||||
|
||||
def toggle_play(self):
|
||||
if not self.current_playback:
|
||||
return
|
||||
if self.current_playback.get("is_playing"):
|
||||
self.client.pause()
|
||||
else:
|
||||
self.client.play()
|
||||
time.sleep(0.3)
|
||||
self.refresh()
|
||||
|
||||
def next(self):
|
||||
self.client.next_track()
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def previous(self):
|
||||
self.client.previous_track()
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def seek(self, position_ms):
|
||||
self.client.seek(position_ms)
|
||||
time.sleep(0.3)
|
||||
self.refresh()
|
||||
|
||||
def set_volume(self, volume):
|
||||
volume = max(0, min(100, volume))
|
||||
self.client.set_volume(volume)
|
||||
self.refresh()
|
||||
|
||||
def volume_up(self, step=5):
|
||||
vol = self.get_volume()
|
||||
self.set_volume(vol + step)
|
||||
|
||||
def volume_down(self, step=5):
|
||||
vol = self.get_volume()
|
||||
self.set_volume(vol - step)
|
||||
|
||||
def get_volume(self):
|
||||
if self.current_playback and self.current_playback.get("device"):
|
||||
return self.current_playback["device"].get("volume_percent", 50)
|
||||
return 50
|
||||
|
||||
def toggle_repeat(self):
|
||||
if not self.current_playback:
|
||||
return
|
||||
state = self.current_playback.get("repeat_state", "off")
|
||||
new_state = "track" if state == "off" else "off"
|
||||
self.client.set_repeat(new_state)
|
||||
self.refresh()
|
||||
|
||||
def toggle_shuffle(self):
|
||||
if not self.current_playback:
|
||||
return
|
||||
state = not self.current_playback.get("shuffle_state", False)
|
||||
self.client.set_shuffle(state)
|
||||
self.refresh()
|
||||
|
||||
def play_context(self, context_uri, offset=None):
|
||||
self.client.play(context_uri=context_uri, offset=offset)
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def play_tracks(self, uris, offset=0):
|
||||
self.client.play(uris=uris, offset=offset)
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def play_track(self, uri):
|
||||
self.client.play(uris=[uri])
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def transfer_to(self, device_id):
|
||||
self.client.transfer_playback(device_id)
|
||||
time.sleep(0.5)
|
||||
self.refresh()
|
||||
|
||||
def get_track_info(self):
|
||||
if not self.current_track or not self.current_track.get("item"):
|
||||
return None
|
||||
track = self.current_track["item"]
|
||||
artists = ", ".join(a["name"] for a in track.get("artists", []))
|
||||
album = track.get("album", {}).get("name", "")
|
||||
progress = self.current_track.get("progress_ms", 0)
|
||||
duration = track.get("duration_ms", 0)
|
||||
return {
|
||||
"name": track["name"],
|
||||
"artists": artists,
|
||||
"album": album,
|
||||
"duration_ms": duration,
|
||||
"progress_ms": progress,
|
||||
"uri": track["uri"],
|
||||
"is_playing": self.current_track.get("is_playing", False),
|
||||
"image": track.get("album", {}).get("images", [{}])[0].get("url") if track.get("album", {}).get("images") else None,
|
||||
}
|
||||
|
||||
def get_playback_state(self):
|
||||
if not self.current_playback:
|
||||
return None
|
||||
return {
|
||||
"is_playing": self.current_playback.get("is_playing", False),
|
||||
"repeat_state": self.current_playback.get("repeat_state", "off"),
|
||||
"shuffle_state": self.current_playback.get("shuffle_state", False),
|
||||
"volume": self.current_playback.get("device", {}).get("volume_percent", 50),
|
||||
"device": self.current_playback.get("device", {}).get("name", "Unknown"),
|
||||
"device_type": self.current_playback.get("device", {}).get("type", ""),
|
||||
}
|
||||
Reference in New Issue
Block a user