Files
synctv-weihang/windows_py_client/synctv_client/sync_client.py
2026-06-15 22:46:12 +08:00

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 {})