feat: split frontend into separate folder

This commit is contained in:
Radu C. Martin 2025-04-15 18:36:26 +02:00
parent 4bd6ad85e3
commit d265b92f1d
3 changed files with 79 additions and 80 deletions

33
main.py
View file

@ -1,7 +1,7 @@
import asyncio import asyncio
from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles
from models import MusicPlayer, PlayerState, WSConnectionType from models import MusicPlayer, PlayerState, WSConnectionType
from services import ConnectionManager, DownloadService from services import ConnectionManager, DownloadService
@ -12,17 +12,16 @@ tags_metadata = [
{"name": "experimental"}, {"name": "experimental"},
{"name": "queue"}, {"name": "queue"},
] ]
app = FastAPI(openapi_tags=tags_metadata) api_app = FastAPI(openapi_tags=tags_metadata, title="Dashdio API")
player = MusicPlayer() player = MusicPlayer()
dl_service = DownloadService() dl_service = DownloadService()
ws_manager = ConnectionManager() ws_manager = ConnectionManager()
app = FastAPI(title="Dashdio App")
# Interface # Interface
@app.get("/", response_class=HTMLResponse) app.mount("/api", api_app)
async def root(): app.mount("/", StaticFiles(directory="ui", html=True), name="ui")
with open("index.html") as f:
return f.read()
@app.on_event("startup") @app.on_event("startup")
@ -46,7 +45,7 @@ async def queue_broadcast_loop():
# Status updates # Status updates
@app.websocket("/player") @api_app.websocket("/player")
async def websocket_player(websocket: WebSocket): async def websocket_player(websocket: WebSocket):
await ws_manager.connect(websocket, WSConnectionType.state) await ws_manager.connect(websocket, WSConnectionType.state)
try: try:
@ -58,7 +57,7 @@ async def websocket_player(websocket: WebSocket):
# Queue updates # Queue updates
@app.websocket("/queue") @api_app.websocket("/queue")
async def websocket_queue(websocket: WebSocket): async def websocket_queue(websocket: WebSocket):
await ws_manager.connect(websocket, WSConnectionType.queue) await ws_manager.connect(websocket, WSConnectionType.queue)
try: try:
@ -69,48 +68,48 @@ async def websocket_queue(websocket: WebSocket):
ws_manager.disconnect(websocket) ws_manager.disconnect(websocket)
@app.get("/queue", tags=["queue"]) @api_app.get("/queue", tags=["queue"])
def get_queue(): def get_queue():
return player.get_queue() return player.get_queue()
@app.post("/queue", tags=["queue"]) @api_app.post("/queue", tags=["queue"])
async def post_to_queue(url: str): async def post_to_queue(url: str):
track = await dl_service.download(url) track = await dl_service.download(url)
await player.add_to_queue(track) await player.add_to_queue(track)
@app.delete("/queue/{track_id}", tags=["queue"]) @api_app.delete("/queue/{track_id}", tags=["queue"])
def delete_from_queue(track_id: str): def delete_from_queue(track_id: str):
player.remove_from_queue_by_id(track_id) player.remove_from_queue_by_id(track_id)
@app.post("/player/play", tags=["player"]) @api_app.post("/player/play", tags=["player"])
async def player_play(): async def player_play():
await player.play() await player.play()
@app.post("/player/stop", tags=["player"]) @api_app.post("/player/stop", tags=["player"])
async def player_stop(): async def player_stop():
await player.stop() await player.stop()
@app.post("/player/skip", tags=["player"]) @api_app.post("/player/skip", tags=["player"])
async def player_skip(): async def player_skip():
await player.next() await player.next()
# Player # Player
@app.put("/player/volume", tags=["player"]) @api_app.put("/player/volume", tags=["player"])
async def set_volume(volume: float): async def set_volume(volume: float):
await player.set_volume(volume) await player.set_volume(volume)
@app.get("/player/volume", tags=["player"]) @api_app.get("/player/volume", tags=["player"])
def get_volume(): def get_volume():
return {"volume": player.get_volume()} return {"volume": player.get_volume()}
@app.get("/player", tags=["player"]) @api_app.get("/player", tags=["player"])
def get_player_state() -> PlayerState: def get_player_state() -> PlayerState:
return player.get_state() return player.get_state()

56
ui/index.html Normal file
View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Music Player</title>
<style>
body { font-family: sans-serif; padding: 1rem; }
button { margin: 0.5rem; }
#queueList { margin-top: 1rem; }
</style>
</head>
<body>
<div style="font-family: sans-serif; max-width: 600px; margin: auto;">
<h2>🎵 Dashdio</h2>
<div id="nowPlaying">
<strong>Artist:</strong> <span id="trackArtist">-</span><br>
<strong>Title:</strong> <span id="trackTitle">-</span><br>
<strong>Status:</strong> <span id="playbackState">-</span><br>
<div style="margin-top: 10px;">
<progress id="progressBar" value="0" max="100" style="width: 100%; height: 20px;"></progress>
<div style="text-align: right;">
<span id="elapsedTime">0:00</span> / <span id="totalTime">0:00</span>
</div>
</div>
</div>
<div style="margin-top: 20px;">
<label for="volumeSlider"><strong>🔊 Volume:</strong> <span id="volumeValue">1</span></label><br>
<input type="range" min="0" max="1" step="0.01" id="volumeSlider" oninput="setVolume(this.value)">
<button onclick="play()">Play/Pause</button>
<button onclick="stop()">Stop</button>
<button onclick="skip()">Skip</button>
</div>
<h3 style="margin-top: 30px;">📃 Queue</h3>
<input type="text" id="trackUrl" placeholder="Track URL">
<button onclick="addToQueue()">Add to Queue</button>
<table id="queueTable" style="width: 100%; border-collapse: collapse; font-size: 0.95em;">
<thead>
<tr style="background-color: #f0f0f0;">
<th style="text-align: left; padding: 8px;">#</th>
<th style="text-align: left; padding: 8px;">Artist</th>
<th style="text-align: left; padding: 8px;">Title</th>
<th style="text-align: right; padding: 8px;">Duration</th>
<th style="text-align: center; padding: 8px;">Actions</th>
</tr>
</thead>
<tbody id="queueBody">
<!-- Filled by JS -->
</tbody>
</table>
</div>
<script src="script.js"></script>
</body>
</html>

View file

@ -1,56 +1,4 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Music Player</title>
<style>
body { font-family: sans-serif; padding: 1rem; }
button { margin: 0.5rem; }
#queueList { margin-top: 1rem; }
</style>
</head>
<body>
<div style="font-family: sans-serif; max-width: 600px; margin: auto;">
<h2>🎵 Dashdio</h2>
<div id="nowPlaying">
<strong>Artist:</strong> <span id="trackArtist">-</span><br>
<strong>Title:</strong> <span id="trackTitle">-</span><br>
<strong>Status:</strong> <span id="playbackState">-</span><br>
<div style="margin-top: 10px;">
<progress id="progressBar" value="0" max="100" style="width: 100%; height: 20px;"></progress>
<div style="text-align: right;">
<span id="elapsedTime">0:00</span> / <span id="totalTime">0:00</span>
</div>
</div>
</div>
<div style="margin-top: 20px;">
<label for="volumeSlider"><strong>🔊 Volume:</strong> <span id="volumeValue">1</span></label><br>
<input type="range" min="0" max="1" step="0.01" id="volumeSlider" oninput="setVolume(this.value)">
<button onclick="play()">Play/Pause</button>
<button onclick="stop()">Stop</button>
<button onclick="skip()">Skip</button>
</div>
<h3 style="margin-top: 30px;">📃 Queue</h3>
<input type="text" id="trackUrl" placeholder="Track URL">
<button onclick="addToQueue()">Add to Queue</button>
<table id="queueTable" style="width: 100%; border-collapse: collapse; font-size: 0.95em;">
<thead>
<tr style="background-color: #f0f0f0;">
<th style="text-align: left; padding: 8px;">#</th>
<th style="text-align: left; padding: 8px;">Artist</th>
<th style="text-align: left; padding: 8px;">Title</th>
<th style="text-align: right; padding: 8px;">Duration</th>
<th style="text-align: center; padding: 8px;">Actions</th>
</tr>
</thead>
<tbody id="queueBody">
<!-- Filled by JS -->
</tbody>
</table>
</div>
<script>
function formatTime(seconds) { function formatTime(seconds) {
const m = Math.floor(seconds / 60); const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60).toString().padStart(2, '0'); const s = Math.floor(seconds % 60).toString().padStart(2, '0');
@ -58,7 +6,7 @@ return `${m}:${s}`;
} }
const removeFromQueue = async (trackId) => { const removeFromQueue = async (trackId) => {
await api(`/queue/${trackId}`, { method: "DELETE" }); await api(`/api/queue/${trackId}`, { method: "DELETE" });
updateQueue(); // Refresh queue after deletion updateQueue(); // Refresh queue after deletion
}; };
@ -79,26 +27,26 @@ const api = (endpoint, options = {}) =>
fetch(endpoint, options).then(res => res.json()).catch(console.error); fetch(endpoint, options).then(res => res.json()).catch(console.error);
const setVolume = async (val) => { const setVolume = async (val) => {
await api(`/player/volume?volume=${val}`, { method: "PUT" }); await api(`/api/player/volume?volume=${val}`, { method: "PUT" });
document.getElementById("volumeValue").textContent = val; document.getElementById("volumeValue").textContent = val;
}; };
const addToQueue = async () => { const addToQueue = async () => {
const url = document.getElementById("trackUrl").value; const url = document.getElementById("trackUrl").value;
await api(`/queue?url=${encodeURIComponent(url)}`, { method: "POST" }); await api(`/api/queue?url=${encodeURIComponent(url)}`, { method: "POST" });
updateQueue(); updateQueue();
}; };
const play = async () => { await api("/player/play", { method: "POST" });}; const play = async () => { await api("/api/player/play", { method: "POST" });};
const stop = async () => { await api("/player/stop", { method: "POST" });}; const stop = async () => { await api("/api/player/stop", { method: "POST" });};
const skip = async () => { await api("/player/skip", { method: "POST" });}; const skip = async () => { await api("/api/player/skip", { method: "POST" });};
// WebSocket connections // WebSocket connections
let playerSocket, queueSocket; let playerSocket, queueSocket;
function connectWebSockets() { function connectWebSockets() {
const proto = location.protocol === "https:" ? "wss" : "ws"; const proto = location.protocol === "https:" ? "wss" : "ws";
const base = `${proto}://${location.host}`; const base = `${proto}://${location.host}/api`;
playerSocket = new WebSocket(`${base}/player`); playerSocket = new WebSocket(`${base}/player`);
queueSocket = new WebSocket(`${base}/queue`); queueSocket = new WebSocket(`${base}/queue`);
@ -152,7 +100,3 @@ queueSocket.onclose = () => setTimeout(connectWebSockets, 1000);
connectWebSockets(); connectWebSockets();
</script>
</body>
</html>