feat: streamlit api

This commit is contained in:
Radu C. Martin 2025-04-11 13:04:34 +02:00
parent bdf44bcd94
commit 573429acd2
3 changed files with 74 additions and 61 deletions

14
.envrc
View file

@ -10,9 +10,21 @@ layout_uv() {
VIRTUAL_ENV="$(pwd)/.venv" VIRTUAL_ENV="$(pwd)/.venv"
fi fi
export PATH=$PATH":$VIRTUAL_ENV/bin" PATH_add "$VIRTUAL_ENV/bin"
export UV_ACTIVE=1 # or VENV_ACTIVE=1 export UV_ACTIVE=1 # or VENV_ACTIVE=1
export VIRTUAL_ENV export VIRTUAL_ENV
} }
layout_uv 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

49
main.py
View file

@ -1,7 +1,6 @@
from enum import Enum from enum import Enum
from fastapi import FastAPI from fastapi import FastAPI
from pygame.mixer import music
from download_service import DownloadService from download_service import DownloadService
from music_player import MusicPlayer from music_player import MusicPlayer
@ -10,15 +9,10 @@ from music_player import MusicPlayer
class ChangePlayerState(Enum): class ChangePlayerState(Enum):
play = "play" play = "play"
pause = "pause" pause = "pause"
unpause = "unpause" resume = "resume"
stop = "stop" stop = "stop"
class Songs(Enum):
era = "era.mp3"
royal_beggars = "royal-beggars.mp3"
queue: list[str] = [] queue: list[str] = []
tags_metadata = [ tags_metadata = [
@ -32,22 +26,37 @@ dl_service = DownloadService()
# Experimental # Experimental
@app.get("/test/player/queue", tags=["experimental"]) @app.get("/queue", tags=["queue"])
def play_music(): def play_music():
return player.get_queue() return player.get_queue()
@app.post("/test/queue", tags=["experimental"]) @app.post("/queue", tags=["queue"])
def post_to_queue(url: str): def post_to_queue(url: str):
track = dl_service.download(url) track = dl_service.download(url)
player.add_to_queue(track) player.add_to_queue(track)
@app.post("/player/play", tags=["experimental"]) @app.post("/player/play", tags=["player"])
def play(): def player_play():
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 # Player
@app.put("/player/volume", tags=["player"]) @app.put("/player/volume", tags=["player"])
def set_volume(volume: float): def set_volume(volume: float):
@ -59,24 +68,6 @@ def get_volume():
return {"volume": player.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"]) @app.get("/player/state", tags=["player"])
def get_state(): def get_state():
return {"state": player.get_state(), "current_track": player.get_current_track()} 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)

View file

@ -3,7 +3,7 @@ import time
from enum import Enum from enum import Enum
import pygame import pygame
from pydantic import BaseModel from pydantic import BaseModel, Field
class PlayerState(Enum): class PlayerState(Enum):
@ -16,23 +16,26 @@ class Track(BaseModel):
artist: str artist: str
title: str title: str
duration: int duration: int
filepath: str filepath: str = Field(hidden=True) # don't show it in API responses
class Queue: class Queue:
queue: list[Track] queue: list[Track]
def __init__(self) -> None: def __init__(self) -> None:
self.queue: list[Track] = [] self._queue: list[Track] = []
def next(self) -> Track | None: def next(self) -> Track | None:
if len(self.queue) > 0: if len(self._queue) > 0:
return self.queue.pop(0) return self._queue.pop(0)
else: else:
return None return None
def add(self, track: Track) -> 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: class MusicPlayer:
@ -47,21 +50,23 @@ class MusicPlayer:
self.thread.start() self.thread.start()
# Music Player # Music Player
self.queue: Queue = Queue() self.queue: Queue = Queue()
self._current_track: Track | None = None self.current_track: Track | None = None
self._state: PlayerState = PlayerState.Stopped self._state: PlayerState = PlayerState.Stopped
# State change flags
self._queue_changed: bool = False
self._track_changed: bool = False
def _loop(self): def _loop(self):
while self._running: while self._running:
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.USEREVENT: if event.type == pygame.USEREVENT: # a song just ended
if self._current_track: if self.current_track:
print(f"Track {self._current_track.title} ended") print(f"Track {self.current_track.title} ended")
self._play_next_track() self._play_next_track()
time.sleep(0.1) time.sleep(0.1)
def _load_track(self, track: Track): def _load_track(self, track: Track):
with self.lock:
self.current_track = track self.current_track = track
pygame.mixer.music.unload() pygame.mixer.music.unload()
pygame.mixer.music.load(self.current_track.filepath) pygame.mixer.music.load(self.current_track.filepath)
@ -71,43 +76,48 @@ class MusicPlayer:
if next_track: if next_track:
self._load_track(next_track) self._load_track(next_track)
pygame.mixer.music.play() pygame.mixer.music.play()
self._state = PlayerState.Playing
def add_to_queue(self, track: Track): def add_to_queue(self, track: Track):
with self.lock:
que_len = self.queue.len()
self.queue.add(track) 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): def play(self):
if self._current_track:
with self.lock: with self.lock:
if self.current_track:
pygame.mixer.music.play() pygame.mixer.music.play()
self._state = PlayerState.Playing
else: else:
self._play_next_track() self._play_next_track()
self._state = PlayerState.Playing
def pause(self): def pause(self):
with self.lock: with self.lock:
pygame.mixer.music.pause() pygame.mixer.music.pause()
self._state = PlayerState.Paused self._state = PlayerState.Paused
print("Set player state to Paused")
def resume(self): def resume(self):
with self.lock: with self.lock:
pygame.mixer.music.unpause() pygame.mixer.music.unpause()
self._state = PlayerState.Playing self._state = PlayerState.Playing
print("Set player state to Playing")
def stop(self): def stop(self):
with self.lock: with self.lock:
pygame.mixer.music.stop() pygame.mixer.music.stop()
self._state = PlayerState.Stopped self._state = PlayerState.Stopped
self._current_track = None self.current_track = None
def shutdown(self): def shutdown(self):
with self.lock:
self._running = False self._running = False
self.thread.join() self.thread.join()
pygame.mixer.quit() pygame.mixer.quit()
def get_queue(self) -> Queue: def get_queue(self) -> Queue:
return self.queue return self.queue._queue
def set_volume(self, volume: float): def set_volume(self, volume: float):
with self.lock: with self.lock:
@ -120,4 +130,4 @@ class MusicPlayer:
return self._state return self._state
def get_current_track(self) -> Track | None: def get_current_track(self) -> Track | None:
return self._current_track return self.current_track