feat: add basic interface and cleanup
This commit is contained in:
parent
573429acd2
commit
c8abb8943e
4 changed files with 211 additions and 53 deletions
141
music_player.py
141
music_player.py
|
@ -3,20 +3,26 @@ import time
|
|||
from enum import Enum
|
||||
|
||||
import pygame
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PlayerState(Enum):
|
||||
Playing = "Playing"
|
||||
Paused = "Paused"
|
||||
Stopped = "Stopped"
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Track(BaseModel):
|
||||
artist: str
|
||||
title: str
|
||||
duration: int
|
||||
filepath: str = Field(hidden=True) # don't show it in API responses
|
||||
filepath: str
|
||||
|
||||
|
||||
class PlaybackState(Enum):
|
||||
Playing = "Playing"
|
||||
Paused = "Paused"
|
||||
Stopped = "Stopped"
|
||||
|
||||
|
||||
class PlayerState(BaseModel):
|
||||
playback_state: PlaybackState = PlaybackState.Stopped
|
||||
track: Track | None = None
|
||||
volume: float
|
||||
|
||||
|
||||
class Queue:
|
||||
|
@ -40,94 +46,141 @@ class Queue:
|
|||
|
||||
class MusicPlayer:
|
||||
def __init__(self):
|
||||
# Player State
|
||||
self._state = PlayerState(volume=1)
|
||||
self._state_changed: bool = False
|
||||
|
||||
# Sound initialization
|
||||
pygame.init()
|
||||
pygame.mixer.init()
|
||||
pygame.mixer.music.set_endevent(pygame.USEREVENT)
|
||||
pygame.mixer.music.set_volume(self._state.volume)
|
||||
|
||||
# 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._thread = threading.Thread(target=self._loop, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
# Queue
|
||||
self._queue: Queue = Queue()
|
||||
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._handle_track_finished()
|
||||
self._play_next_track()
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def _set_playback_state(self, value: PlaybackState):
|
||||
self._state.playback_state = value
|
||||
self._state_changed = True
|
||||
|
||||
def _set_track(self, track: Track | None):
|
||||
self._state.track = track
|
||||
self._state_changed = True
|
||||
|
||||
def _handle_track_finished(self) -> None:
|
||||
print(f"Finished playing {self._state.track}")
|
||||
self._set_track(None)
|
||||
self._set_playback_state(PlaybackState.Stopped)
|
||||
|
||||
def _load_track(self, track: Track):
|
||||
self.current_track = track
|
||||
self._set_track(track)
|
||||
pygame.mixer.music.unload()
|
||||
pygame.mixer.music.load(self.current_track.filepath)
|
||||
pygame.mixer.music.load(track.filepath)
|
||||
|
||||
def _queue_add(self, track):
|
||||
self._queue.add(track)
|
||||
self._queue_changed = True
|
||||
|
||||
def _queue_next(self) -> Track | None:
|
||||
next_track = self._queue.next()
|
||||
self._queue_changed = True
|
||||
return next_track
|
||||
|
||||
def _start_playback(self):
|
||||
pygame.mixer.music.play()
|
||||
self._set_playback_state(PlaybackState.Playing)
|
||||
|
||||
def _pause_playback(self):
|
||||
pygame.mixer.music.pause()
|
||||
self._set_playback_state(PlaybackState.Paused)
|
||||
|
||||
def _resume_playback(self):
|
||||
pygame.mixer.music.unpause()
|
||||
self._set_playback_state(PlaybackState.Playing)
|
||||
|
||||
def _stop_playback(self):
|
||||
pygame.mixer.music.stop()
|
||||
self._set_playback_state(PlaybackState.Stopped)
|
||||
self._set_track(None)
|
||||
|
||||
def _play_next_track(self):
|
||||
next_track = self.queue.next()
|
||||
next_track = self._queue_next()
|
||||
if next_track:
|
||||
self._load_track(next_track)
|
||||
pygame.mixer.music.play()
|
||||
self._state = PlayerState.Playing
|
||||
self._start_playback()
|
||||
|
||||
def add_to_queue(self, track: Track):
|
||||
with self.lock:
|
||||
que_len = self.queue.len()
|
||||
self.queue.add(track)
|
||||
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:
|
||||
if que_len == 0 and not self._state.track:
|
||||
self._play_next_track()
|
||||
|
||||
def play(self):
|
||||
with self.lock:
|
||||
if self.current_track:
|
||||
pygame.mixer.music.play()
|
||||
self._state = PlayerState.Playing
|
||||
if self._state.playback_state == PlaybackState.Playing:
|
||||
return
|
||||
if self._state.track:
|
||||
self._start_playback()
|
||||
else:
|
||||
self._play_next_track()
|
||||
|
||||
def pause(self):
|
||||
with self.lock:
|
||||
pygame.mixer.music.pause()
|
||||
self._state = PlayerState.Paused
|
||||
self._pause_playback()
|
||||
|
||||
def resume(self):
|
||||
with self.lock:
|
||||
pygame.mixer.music.unpause()
|
||||
self._state = PlayerState.Playing
|
||||
self._resume_playback()
|
||||
|
||||
def stop(self):
|
||||
with self.lock:
|
||||
pygame.mixer.music.stop()
|
||||
self._state = PlayerState.Stopped
|
||||
self.current_track = None
|
||||
self._stop_playback()
|
||||
|
||||
def next(self):
|
||||
with self.lock:
|
||||
self._play_next_track()
|
||||
|
||||
def shutdown(self):
|
||||
with self.lock:
|
||||
self._running = False
|
||||
self.thread.join()
|
||||
self._thread.join()
|
||||
pygame.mixer.quit()
|
||||
|
||||
def get_queue(self) -> Queue:
|
||||
return self.queue._queue
|
||||
def get_queue(self) -> list[Track]:
|
||||
return self._queue._queue
|
||||
|
||||
def set_volume(self, volume: float):
|
||||
def _set_volume(self, volume: float):
|
||||
self._state.volume = volume
|
||||
pygame.mixer.music.set_volume(volume)
|
||||
self._state_changed = True
|
||||
|
||||
def set_volume(self, volume: float) -> None:
|
||||
with self.lock:
|
||||
pygame.mixer.music.set_volume(volume)
|
||||
self._set_volume(volume)
|
||||
|
||||
def get_volume(self):
|
||||
return pygame.mixer.music.get_volume()
|
||||
def get_volume(self) -> float:
|
||||
return self._state.volume
|
||||
|
||||
def get_state(self) -> PlayerState:
|
||||
return self._state
|
||||
|
||||
def get_current_track(self) -> Track | None:
|
||||
return self.current_track
|
||||
return self._state.track
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue