refactor: use sse instead of ws for listener count
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
fastapi==0.111.1
|
fastapi==0.115.5
|
||||||
websocket_client==1.8.0
|
logger==1.4
|
||||||
|
Requests==2.32.3
|
||||||
|
sse_starlette==2.1.3
|
||||||
|
66
server.py
66
server.py
@@ -1,14 +1,22 @@
|
|||||||
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from contextlib import asynccontextmanager
|
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.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from logger import log_info, log_warn
|
from logger import log_info, log_warn
|
||||||
from websocket_connection_manager import WebSocketConnectionManager
|
from websocket_connection_manager import WebSocketConnectionManager
|
||||||
|
from sse_starlette.sse import EventSourceResponse
|
||||||
|
|
||||||
# the index of the current audio track from 0 to 9
|
# the index of the current audio track from 0 to 9
|
||||||
current_index = -1
|
current_index = -1
|
||||||
@@ -130,34 +138,46 @@ def get_current_audio():
|
|||||||
return FileResponse(f"{current_index}.mp3")
|
return FileResponse(f"{current_index}.mp3")
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/ws")
|
@app.get("/status")
|
||||||
async def ws_endpoint(ws: WebSocket):
|
def status_stream(request: Request):
|
||||||
await ws_connection_manager.connect(ws)
|
async def status_generator():
|
||||||
|
last_listener_count = len(active_listeners)
|
||||||
|
yield json.dumps({"listeners": last_listener_count})
|
||||||
|
|
||||||
addr = ""
|
while True:
|
||||||
if ws.client:
|
if await request.is_disconnected():
|
||||||
addr, _ = ws.client
|
break
|
||||||
else:
|
|
||||||
await ws.close()
|
|
||||||
ws_connection_manager.disconnect(ws)
|
|
||||||
|
|
||||||
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:
|
try:
|
||||||
while True:
|
is_listening = body["isListening"]
|
||||||
msg = await ws.receive_text()
|
|
||||||
|
|
||||||
if msg == "playing":
|
client = request.client
|
||||||
active_listeners.add(addr)
|
if not client:
|
||||||
await ws_connection_manager.broadcast(f"{len(active_listeners)}")
|
raise HTTPException(status_code=400, detail="ip address unavailable.")
|
||||||
elif msg == "paused":
|
|
||||||
active_listeners.discard(addr)
|
|
||||||
await ws_connection_manager.broadcast(f"{len(active_listeners)}")
|
|
||||||
|
|
||||||
except WebSocketDisconnect:
|
if is_listening:
|
||||||
active_listeners.discard(addr)
|
active_listeners.add(client.host)
|
||||||
ws_connection_manager.disconnect(ws)
|
else:
|
||||||
await ws_connection_manager.broadcast(f"{len(active_listeners)}")
|
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")
|
app.mount("/", StaticFiles(directory="web", html=True), name="web")
|
||||||
|
@@ -29,7 +29,6 @@ let maxVolume = 100;
|
|||||||
let currentVolume = 0;
|
let currentVolume = 0;
|
||||||
let saveVolumeTimeout = null;
|
let saveVolumeTimeout = null;
|
||||||
let meowCount = 0;
|
let meowCount = 0;
|
||||||
let ws = connectToWebSocket();
|
|
||||||
|
|
||||||
function playAudio() {
|
function playAudio() {
|
||||||
// add a random query parameter at the end to prevent browser caching
|
// add a random query parameter at the end to prevent browser caching
|
||||||
@@ -37,17 +36,13 @@ function playAudio() {
|
|||||||
currentAudio.onplay = () => {
|
currentAudio.onplay = () => {
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
playBtn.innerText = "pause";
|
playBtn.innerText = "pause";
|
||||||
if (ws) {
|
updateClientStatus({ isListening: true });
|
||||||
ws.send("playing");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
currentAudio.onpause = () => {
|
currentAudio.onpause = () => {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
currentVolume = 0;
|
currentVolume = 0;
|
||||||
playBtn.innerText = "play";
|
playBtn.innerText = "play";
|
||||||
if (ws) {
|
updateClientStatus({ isListening: false });
|
||||||
ws.send("paused");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
currentAudio.onended = () => {
|
currentAudio.onended = () => {
|
||||||
currentVolume = 0;
|
currentVolume = 0;
|
||||||
@@ -107,18 +102,6 @@ function fadeOut() {
|
|||||||
}, CROSSFADE_INTERVAL_MS);
|
}, 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
|
* 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) {
|
function changeVolume(volume) {
|
||||||
maxVolume = volume;
|
maxVolume = volume;
|
||||||
const v = maxVolume / 100;
|
const v = maxVolume / 100;
|
||||||
@@ -229,6 +185,37 @@ function showNotification(title, content, duration) {
|
|||||||
}, 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 = () => {
|
playBtn.onmousedown = () => {
|
||||||
clickAudio.play();
|
clickAudio.play();
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
@@ -288,15 +275,7 @@ meowAudio.onplay = () => {
|
|||||||
// don't wanna jumpscare ppl
|
// don't wanna jumpscare ppl
|
||||||
achievementUnlockedAudio.volume = 0.05;
|
achievementUnlockedAudio.volume = 0.05;
|
||||||
|
|
||||||
window.addEventListener("offline", () => {
|
|
||||||
ws = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("online", () => {
|
|
||||||
ws = connectToWebSocket();
|
|
||||||
});
|
|
||||||
|
|
||||||
loadMeowCount();
|
loadMeowCount();
|
||||||
loadInitialVolume();
|
loadInitialVolume();
|
||||||
animateCat();
|
|
||||||
enableSpaceBarControl();
|
enableSpaceBarControl();
|
||||||
|
listenToServerStatusEvent();
|
||||||
|
Reference in New Issue
Block a user