diff --git a/.envrc b/.envrc index 59ac7aa..3231fc9 100644 --- a/.envrc +++ b/.envrc @@ -10,9 +10,21 @@ layout_uv() { VIRTUAL_ENV="$(pwd)/.venv" fi - export PATH=$PATH":$VIRTUAL_ENV/bin" + PATH_add "$VIRTUAL_ENV/bin" export UV_ACTIVE=1 # or VENV_ACTIVE=1 export VIRTUAL_ENV } layout_uv + + +# Path to your virtual environment directory +VENV_DIR="./venv" + +# Load and activate the virtual environment if it exists +if [[ -d "$VENV_DIR" ]]; then + layout python "$VENV_DIR" +else + echo "Virtual environment not found at $VENV_DIR" +fi + diff --git a/main.py b/main.py index 0c6e927..e54970b 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ from enum import Enum from fastapi import FastAPI -from pygame.mixer import music from download_service import DownloadService from music_player import MusicPlayer @@ -10,15 +9,10 @@ from music_player import MusicPlayer class ChangePlayerState(Enum): play = "play" pause = "pause" - unpause = "unpause" + resume = "resume" stop = "stop" -class Songs(Enum): - era = "era.mp3" - royal_beggars = "royal-beggars.mp3" - - queue: list[str] = [] tags_metadata = [ @@ -32,22 +26,37 @@ dl_service = DownloadService() # Experimental -@app.get("/test/player/queue", tags=["experimental"]) +@app.get("/queue", tags=["queue"]) def play_music(): return player.get_queue() -@app.post("/test/queue", tags=["experimental"]) +@app.post("/queue", tags=["queue"]) def post_to_queue(url: str): track = dl_service.download(url) player.add_to_queue(track) -@app.post("/player/play", tags=["experimental"]) -def play(): +@app.post("/player/play", tags=["player"]) +def player_play(): player.play() +@app.post("/player/pause", tags=["player"]) +def player_pause(): + player.pause() + + +@app.post("/player/resume", tags=["player"]) +def player_resume(): + player.resume() + + +@app.post("/player/stop", tags=["player"]) +def player_stop(): + player.stop() + + # Player @app.put("/player/volume", tags=["player"]) def set_volume(volume: float): @@ -59,24 +68,6 @@ def get_volume(): return {"volume": player.get_volume()} -@app.patch("/player/state", tags=["player"]) -def update_state(status: ChangePlayerState): - match status: - case ChangePlayerState.play: - music.play() - case ChangePlayerState.pause: - music.pause() - case ChangePlayerState.unpause: - music.unpause() - case ChangePlayerState.stop: - music.stop() - - @app.get("/player/state", tags=["player"]) def get_state(): return {"state": player.get_state(), "current_track": player.get_current_track()} - - -@app.post("/queue", tags=["queue"]) -def add_to_queue(filename: str): - queue.append(filename) diff --git a/music_player.py b/music_player.py index 30b1fa9..94aac96 100644 --- a/music_player.py +++ b/music_player.py @@ -3,7 +3,7 @@ import time from enum import Enum import pygame -from pydantic import BaseModel +from pydantic import BaseModel, Field class PlayerState(Enum): @@ -16,23 +16,26 @@ class Track(BaseModel): artist: str title: str duration: int - filepath: str + 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] = [] + self._queue: list[Track] = [] def next(self) -> Track | None: - if len(self.queue) > 0: - return self.queue.pop(0) + if len(self._queue) > 0: + return self._queue.pop(0) else: return None def add(self, track: Track) -> None: - self.queue.append(track) + self._queue.append(track) + + def len(self) -> int: + return len(self._queue) class MusicPlayer: @@ -47,67 +50,74 @@ class MusicPlayer: self.thread.start() # Music Player self.queue: Queue = Queue() - self._current_track: Track | None = None + 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: - if self._current_track: - print(f"Track {self._current_track.title} ended") + 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): - with self.lock: - self.current_track = track - pygame.mixer.music.unload() - pygame.mixer.music.load(self.current_track.filepath) + 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): - self.queue.add(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): - if self._current_track: - with self.lock: + with self.lock: + if self.current_track: pygame.mixer.music.play() - else: - self._play_next_track() - self._state = PlayerState.Playing + self._state = PlayerState.Playing + else: + self._play_next_track() def pause(self): with self.lock: pygame.mixer.music.pause() - self._state = PlayerState.Paused - print("Set player state to Paused") + self._state = PlayerState.Paused def resume(self): with self.lock: pygame.mixer.music.unpause() - self._state = PlayerState.Playing - print("Set player state to Playing") + self._state = PlayerState.Playing def stop(self): with self.lock: pygame.mixer.music.stop() - self._state = PlayerState.Stopped - self._current_track = None + self._state = PlayerState.Stopped + self.current_track = None def shutdown(self): - self._running = False - self.thread.join() - pygame.mixer.quit() + with self.lock: + self._running = False + self.thread.join() + pygame.mixer.quit() def get_queue(self) -> Queue: - return self.queue + return self.queue._queue def set_volume(self, volume: float): with self.lock: @@ -120,4 +130,4 @@ class MusicPlayer: return self._state def get_current_track(self) -> Track | None: - return self._current_track + return self.current_track