mirror of
https://github.com/radio95-rnt/RadioPlayer.git
synced 2026-07-30 15:59:16 +02:00
fm95 connection and also sync time
This commit is contained in:
+34
-4
@@ -3,7 +3,7 @@ from queue import Empty
|
|||||||
import json
|
import json
|
||||||
import threading, uuid, time
|
import threading, uuid, time
|
||||||
import asyncio
|
import asyncio
|
||||||
import websockets
|
import websockets, base64
|
||||||
from websockets import ServerConnection, Request, Response, Headers
|
from websockets import ServerConnection, Request, Response, Headers
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import shutil
|
import shutil
|
||||||
@@ -19,8 +19,7 @@ from . import Track, PlayerModule, Path, BaseIMCModule
|
|||||||
|
|
||||||
MAIN_PATH_DIR = Path("/home/user/mixes")
|
MAIN_PATH_DIR = Path("/home/user/mixes")
|
||||||
|
|
||||||
async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue):
|
async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue, writer_q: asyncio.Queue):
|
||||||
await websocket.send(json.dumps({"event": "time", "data": time.monotonic()}))
|
|
||||||
try:
|
try:
|
||||||
initial = {
|
initial = {
|
||||||
"track": json.loads(shared_data.get("track", "{}")),
|
"track": json.loads(shared_data.get("track", "{}")),
|
||||||
@@ -123,6 +122,8 @@ async def ws_handler(websocket: ServerConnection, shared_data: dict, imc_q: mult
|
|||||||
}
|
}
|
||||||
await websocket.send(json.dumps({"event": "fsdb_list", "data": payload, "time": time.monotonic()}))
|
await websocket.send(json.dumps({"event": "fsdb_list", "data": payload, "time": time.monotonic()}))
|
||||||
except Exception as e: await websocket.send(json.dumps({"event": "fsdb_list", "data": {}, "error": str(e), "time": time.monotonic()}))
|
except Exception as e: await websocket.send(json.dumps({"event": "fsdb_list", "data": {}, "error": str(e), "time": time.monotonic()}))
|
||||||
|
elif action == "fm95": await writer_q.put((base64.b64decode(msg.get("data", "")), websocket))
|
||||||
|
elif action == "get_time": await websocket.send(json.dumps({"event": "time", "data": time.monotonic(), "client_time": msg.get("client_time", 0)}))
|
||||||
else: await websocket.send(json.dumps({"event": "error", "error": "unknown action", "time": time.monotonic()}))
|
else: await websocket.send(json.dumps({"event": "error", "error": "unknown action", "time": time.monotonic()}))
|
||||||
|
|
||||||
async def broadcast_worker(ws_q: multiprocessing.Queue, clients: set):
|
async def broadcast_worker(ws_q: multiprocessing.Queue, clients: set):
|
||||||
@@ -145,14 +146,36 @@ async def _safe_send(ws, payload: str, clients: set, ws_q: multiprocessing.Queue
|
|||||||
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"event": "users", "data": len(clients)})
|
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"event": "users", "data": len(clients)})
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
async def socket_handler(socket: asyncio.StreamReader, writer: asyncio.StreamWriter, writer_q: asyncio.Queue):
|
||||||
|
while True:
|
||||||
|
qdata, ws = await writer_q.get()
|
||||||
|
if not qdata or not ws: break
|
||||||
|
|
||||||
|
try:
|
||||||
|
writer.write(qdata)
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
data = await socket.read(256)
|
||||||
|
if not data:
|
||||||
|
await ws.send(json.dumps({"event": "error", "error": "fm95 socket closed", "time": time.monotonic()}))
|
||||||
|
break
|
||||||
|
await ws.send(json.dumps({"event": "fm95", "data": base64.b64encode(data).decode(), "time": time.monotonic()}))
|
||||||
|
except Exception as e:
|
||||||
|
await ws.send(json.dumps({"event": "error", "error": str(e), "time": time.monotonic()}))
|
||||||
|
break
|
||||||
|
|
||||||
def websocket_server_process(shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue):
|
def websocket_server_process(shared_data: dict, imc_q: multiprocessing.Queue, ws_q: multiprocessing.Queue):
|
||||||
async def runner():
|
async def runner():
|
||||||
clients = set()
|
clients = set()
|
||||||
|
reader, writer = await asyncio.open_unix_connection("/etc/fm95/ctl.socket") # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
reader: asyncio.StreamReader
|
||||||
|
writer: asyncio.StreamWriter
|
||||||
|
writer_q: asyncio.Queue[tuple[bytes | None, ServerConnection | None]] = asyncio.Queue()
|
||||||
|
|
||||||
async def handler_wrapper(websocket: ServerConnection):
|
async def handler_wrapper(websocket: ServerConnection):
|
||||||
clients.add(websocket)
|
clients.add(websocket)
|
||||||
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"event": "users", "data": len(clients)})
|
await asyncio.get_event_loop().run_in_executor(None, ws_q.put, {"event": "users", "data": len(clients)})
|
||||||
try: await ws_handler(websocket, shared_data, imc_q, ws_q)
|
try: await ws_handler(websocket, shared_data, imc_q, ws_q, writer_q)
|
||||||
finally:
|
finally:
|
||||||
await websocket.close(1001, "")
|
await websocket.close(1001, "")
|
||||||
clients.discard(websocket)
|
clients.discard(websocket)
|
||||||
@@ -195,7 +218,14 @@ def websocket_server_process(shared_data: dict, imc_q: multiprocessing.Queue, ws
|
|||||||
|
|
||||||
server = await websockets.serve(handler_wrapper, "0.0.0.0", 3001, server_header="RadioPlayer ws plugin", process_request=process_request)
|
server = await websockets.serve(handler_wrapper, "0.0.0.0", 3001, server_header="RadioPlayer ws plugin", process_request=process_request)
|
||||||
broadcaster = asyncio.create_task(broadcast_worker(ws_q, clients))
|
broadcaster = asyncio.create_task(broadcast_worker(ws_q, clients))
|
||||||
|
sockethand = asyncio.create_task(socket_handler(reader, writer, writer_q))
|
||||||
await broadcaster
|
await broadcaster
|
||||||
|
|
||||||
|
await writer_q.put((None, None))
|
||||||
|
await sockethand
|
||||||
|
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
await server.wait_closed()
|
await server.wait_closed()
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
|
|||||||
+14
-1
@@ -85,6 +85,13 @@ function connectWs() {
|
|||||||
ws.addEventListener("message", evt => {
|
ws.addEventListener("message", evt => {
|
||||||
handleMessage(JSON.parse(evt.data));
|
handleMessage(JSON.parse(evt.data));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function syncTime() {
|
||||||
|
wsSend({ action: "get_time", client_time: performance.now() / 1000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(syncTime, 10000);
|
||||||
|
syncTime(); // initial sync
|
||||||
}
|
}
|
||||||
|
|
||||||
function wsSend(obj) {
|
function wsSend(obj) {
|
||||||
@@ -100,7 +107,13 @@ function handleMessage(msg) {
|
|||||||
switch (msg.event) {
|
switch (msg.event) {
|
||||||
case "time": {
|
case "time": {
|
||||||
const localNow = performance.now() / 1000;
|
const localNow = performance.now() / 1000;
|
||||||
timeOffset = localNow - msg.data;
|
const rtt = localNow - msg.client_time; // round-trip time
|
||||||
|
const serverNow = msg.data + rtt / 2; // estimate server time at this moment
|
||||||
|
const newOffset = localNow - serverNow;
|
||||||
|
|
||||||
|
if (timeOffset === 0) timeOffset = newOffset;
|
||||||
|
else timeOffset = timeOffset * 0.9 + newOffset * 0.1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case "state": {
|
case "state": {
|
||||||
const d = msg.data || {};
|
const d = msg.data || {};
|
||||||
|
|||||||
Reference in New Issue
Block a user