65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import threading
|
|
from collections.abc import Callable
|
|
|
|
import websocket
|
|
|
|
|
|
class SyncClient:
|
|
def __init__(
|
|
self,
|
|
on_event: Callable[[str, dict], None],
|
|
on_error: Callable[[str], None],
|
|
) -> None:
|
|
self.on_event = on_event
|
|
self.on_error = on_error
|
|
self.ws: websocket.WebSocketApp | None = None
|
|
self.thread: threading.Thread | None = None
|
|
|
|
def connect(self, server_base_url: str, room_code: str, device_id: str) -> None:
|
|
self.close()
|
|
ws_base = (
|
|
server_base_url.rstrip("/")
|
|
.replace("https://", "wss://")
|
|
.replace("http://", "ws://")
|
|
)
|
|
url = f"{ws_base}/ws?roomCode={room_code}&deviceId={device_id}"
|
|
self.ws = websocket.WebSocketApp(
|
|
url,
|
|
on_message=self._on_message,
|
|
on_error=lambda _ws, err: self.on_error(str(err)),
|
|
on_close=lambda _ws, _code, reason: self.on_error(f"websocket closed: {reason}"),
|
|
)
|
|
self.thread = threading.Thread(target=self.ws.run_forever, daemon=True)
|
|
self.thread.start()
|
|
|
|
def close(self) -> None:
|
|
if self.ws:
|
|
self.ws.close()
|
|
self.ws = None
|
|
|
|
def set_source(self, payload: dict) -> None:
|
|
self._send("setSource", payload)
|
|
|
|
def play(self, position_ms: int) -> None:
|
|
self._send("play", {"positionMs": position_ms})
|
|
|
|
def pause(self, position_ms: int) -> None:
|
|
self._send("pause", {"positionMs": position_ms})
|
|
|
|
def seek(self, position_ms: int) -> None:
|
|
self._send("seek", {"state": "playing", "positionMs": position_ms})
|
|
|
|
def sync_to_live(self) -> None:
|
|
self._send("syncToLive", {"state": "playing", "positionMs": 0, "targetLatencyMs": 3000})
|
|
|
|
def _send(self, event_type: str, payload: dict) -> None:
|
|
if self.ws:
|
|
self.ws.send(json.dumps({"type": event_type, "payload": payload}))
|
|
|
|
def _on_message(self, _ws: websocket.WebSocketApp, message: str) -> None:
|
|
data = json.loads(message)
|
|
self.on_event(data.get("type", ""), data.get("payload") or {})
|