133 lines
3.5 KiB
Python
133 lines
3.5 KiB
Python
import threading
|
|
import time
|
|
from enum import Enum
|
|
|
|
import pygame
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class PlayerState(Enum):
|
|
Playing = "Playing"
|
|
Paused = "Paused"
|
|
Stopped = "Stopped"
|
|
|
|
|
|
class Track(BaseModel):
|
|
artist: str
|
|
title: str
|
|
duration: int
|
|
filepath: str = Field(hidden=True) # don't show it in API responses
|
|
|
|
|
|
class Queue:
|
|
queue: list[Track]
|
|
|
|
def __init__(self) -> None:
|
|
self._queue: list[Track] = []
|
|
|
|
def next(self) -> Track | None:
|
|
if len(self._queue) > 0:
|
|
return self._queue.pop(0)
|
|
else:
|
|
return None
|
|
|
|
def add(self, track: Track) -> None:
|
|
self._queue.append(track)
|
|
|
|
def len(self) -> int:
|
|
return len(self._queue)
|
|
|
|
|
|
class MusicPlayer:
|
|
def __init__(self):
|
|
pygame.init()
|
|
pygame.mixer.init()
|
|
pygame.mixer.music.set_endevent(pygame.USEREVENT)
|
|
# Threading
|
|
self.lock = threading.Lock()
|
|
self._running = True
|
|
self.thread = threading.Thread(target=self._loop, daemon=True)
|
|
self.thread.start()
|
|
# Music Player
|
|
self.queue: Queue = Queue()
|
|
self.current_track: Track | None = None
|
|
self._state: PlayerState = PlayerState.Stopped
|
|
# State change flags
|
|
self._queue_changed: bool = False
|
|
self._track_changed: bool = False
|
|
|
|
def _loop(self):
|
|
while self._running:
|
|
for event in pygame.event.get():
|
|
if event.type == pygame.USEREVENT: # a song just ended
|
|
if self.current_track:
|
|
print(f"Track {self.current_track.title} ended")
|
|
self._play_next_track()
|
|
|
|
time.sleep(0.1)
|
|
|
|
def _load_track(self, track: Track):
|
|
self.current_track = track
|
|
pygame.mixer.music.unload()
|
|
pygame.mixer.music.load(self.current_track.filepath)
|
|
|
|
def _play_next_track(self):
|
|
next_track = self.queue.next()
|
|
if next_track:
|
|
self._load_track(next_track)
|
|
pygame.mixer.music.play()
|
|
self._state = PlayerState.Playing
|
|
|
|
def add_to_queue(self, track: Track):
|
|
with self.lock:
|
|
que_len = self.queue.len()
|
|
self.queue.add(track)
|
|
# If queue is empty and no corrent track, start playing
|
|
if que_len == 0 and not self.current_track:
|
|
self._play_next_track()
|
|
|
|
def play(self):
|
|
with self.lock:
|
|
if self.current_track:
|
|
pygame.mixer.music.play()
|
|
self._state = PlayerState.Playing
|
|
else:
|
|
self._play_next_track()
|
|
|
|
def pause(self):
|
|
with self.lock:
|
|
pygame.mixer.music.pause()
|
|
self._state = PlayerState.Paused
|
|
|
|
def resume(self):
|
|
with self.lock:
|
|
pygame.mixer.music.unpause()
|
|
self._state = PlayerState.Playing
|
|
|
|
def stop(self):
|
|
with self.lock:
|
|
pygame.mixer.music.stop()
|
|
self._state = PlayerState.Stopped
|
|
self.current_track = None
|
|
|
|
def shutdown(self):
|
|
with self.lock:
|
|
self._running = False
|
|
self.thread.join()
|
|
pygame.mixer.quit()
|
|
|
|
def get_queue(self) -> Queue:
|
|
return self.queue._queue
|
|
|
|
def set_volume(self, volume: float):
|
|
with self.lock:
|
|
pygame.mixer.music.set_volume(volume)
|
|
|
|
def get_volume(self):
|
|
return pygame.mixer.music.get_volume()
|
|
|
|
def get_state(self) -> PlayerState:
|
|
return self._state
|
|
|
|
def get_current_track(self) -> Track | None:
|
|
return self.current_track
|