From 087af6643463c01e74e66a63350dd38b4c9c5fdc Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 25 Nov 2024 00:22:44 +0000 Subject: [PATCH] refactor: use sse instead of ws for listener count --- requirements-server.txt | 6 ++- server.py | 66 +++++++++++++++++++----------- web/script.js | 89 ++++++++++++++++------------------------- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/requirements-server.txt b/requirements-server.txt index 260e9f6..fa5b71d 100644 --- a/requirements-server.txt +++ b/requirements-server.txt @@ -1,2 +1,4 @@ -fastapi==0.111.1 -websocket_client==1.8.0 +fastapi==0.115.5 +logger==1.4 +Requests==2.32.3 +sse_starlette==2.1.3 diff --git a/server.py b/server.py index c68a35a..1845343 100644 --- a/server.py +++ b/server.py @@ -1,14 +1,22 @@ +import asyncio import threading import os +import json from time import sleep import requests from contextlib import asynccontextmanager -from fastapi import FastAPI, WebSocket, WebSocketDisconnect, status +from fastapi import ( + FastAPI, + Request, + HTTPException, + status, +) from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from logger import log_info, log_warn from websocket_connection_manager import WebSocketConnectionManager +from sse_starlette.sse import EventSourceResponse # the index of the current audio track from 0 to 9 current_index = -1 @@ -130,34 +138,46 @@ def get_current_audio(): return FileResponse(f"{current_index}.mp3") -@app.websocket("/ws") -async def ws_endpoint(ws: WebSocket): - await ws_connection_manager.connect(ws) +@app.get("/status") +def status_stream(request: Request): + async def status_generator(): + last_listener_count = len(active_listeners) + yield json.dumps({"listeners": last_listener_count}) - addr = "" - if ws.client: - addr, _ = ws.client - else: - await ws.close() - ws_connection_manager.disconnect(ws) + while True: + if await request.is_disconnected(): + break - await ws_connection_manager.broadcast(f"{len(active_listeners)}") + listener_count = len(active_listeners) + if listener_count != last_listener_count: + last_listener_count = listener_count + yield json.dumps({"listeners": listener_count}) + + await asyncio.sleep(1) + + return EventSourceResponse(status_generator()) + + +@app.post("/client-status") +async def change_status(request: Request): + body = await request.json() try: - while True: - msg = await ws.receive_text() + is_listening = body["isListening"] - if msg == "playing": - active_listeners.add(addr) - await ws_connection_manager.broadcast(f"{len(active_listeners)}") - elif msg == "paused": - active_listeners.discard(addr) - await ws_connection_manager.broadcast(f"{len(active_listeners)}") + client = request.client + if not client: + raise HTTPException(status_code=400, detail="ip address unavailable.") - except WebSocketDisconnect: - active_listeners.discard(addr) - ws_connection_manager.disconnect(ws) - await ws_connection_manager.broadcast(f"{len(active_listeners)}") + if is_listening: + active_listeners.add(client.host) + else: + active_listeners.discard(client.host) + + return {"isListening": is_listening} + + except KeyError: + raise HTTPException(status_code=400, detail="'isListening' must be a boolean") app.mount("/", StaticFiles(directory="web", html=True), name="web") diff --git a/web/script.js b/web/script.js index 93cd3df..ea61266 100644 --- a/web/script.js +++ b/web/script.js @@ -29,7 +29,6 @@ let maxVolume = 100; let currentVolume = 0; let saveVolumeTimeout = null; let meowCount = 0; -let ws = connectToWebSocket(); function playAudio() { // add a random query parameter at the end to prevent browser caching @@ -37,17 +36,13 @@ function playAudio() { currentAudio.onplay = () => { isPlaying = true; playBtn.innerText = "pause"; - if (ws) { - ws.send("playing"); - } + updateClientStatus({ isListening: true }); }; currentAudio.onpause = () => { isPlaying = false; currentVolume = 0; playBtn.innerText = "play"; - if (ws) { - ws.send("paused"); - } + updateClientStatus({ isListening: false }); }; currentAudio.onended = () => { currentVolume = 0; @@ -107,18 +102,6 @@ function fadeOut() { }, CROSSFADE_INTERVAL_MS); } -function animateCat() { - let current = 0; - setInterval(() => { - if (current === 3) { - current = 0; - } else { - current += 1; - } - catImg.src = `./images/cat-${current}.png`; - }, 500); -} - /** * Allow audio to be played/paused using the space bar */ @@ -143,33 +126,6 @@ function enableSpaceBarControl() { }); } -function connectToWebSocket() { - const ws = new WebSocket( - `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws`, - ); - ws.onmessage = (event) => { - console.log(event.data); - - if (typeof event.data !== "string") { - return; - } - - const listenerCountStr = event.data; - const listenerCount = Number.parseInt(listenerCountStr); - if (Number.isNaN(listenerCount)) { - return; - } - - if (listenerCount <= 1) { - listenerCountLabel.innerText = `${listenerCount} person tuned in`; - } else { - listenerCountLabel.innerText = `${listenerCount} ppl tuned in`; - } - }; - - return ws; -} - function changeVolume(volume) { maxVolume = volume; const v = maxVolume / 100; @@ -229,6 +185,37 @@ function showNotification(title, content, duration) { }, duration); } +function listenToServerStatusEvent() { + const statusEvent = new EventSource("/status"); + statusEvent.addEventListener("message", (event) => { + const data = JSON.parse(event.data); + updateListenerCountLabel(data.listeners); + }); +} + +function updateListenerCountLabel(newCount) { + if (newCount <= 1) { + listenerCountLabel.innerText = `${newCount} person tuned in`; + } else { + listenerCountLabel.innerText = `${newCount} ppl tuned in`; + } +} + +async function updateClientStatus(status) { + await fetch("/client-status", { + method: "POST", + body: JSON.stringify({ isListening: status.isListening }), + headers: { + "Content-Type": "application/json", + }, + keepalive: true, + }); +} + +window.addEventListener("beforeunload", (e) => { + updateClientStatus({ isListening: false }); +}); + playBtn.onmousedown = () => { clickAudio.play(); document.addEventListener( @@ -288,15 +275,7 @@ meowAudio.onplay = () => { // don't wanna jumpscare ppl achievementUnlockedAudio.volume = 0.05; -window.addEventListener("offline", () => { - ws = null; -}); - -window.addEventListener("online", () => { - ws = connectToWebSocket(); -}); - loadMeowCount(); loadInitialVolume(); -animateCat(); enableSpaceBarControl(); +listenToServerStatusEvent();