mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-06 08:07:22 +08:00
feat: switch watch sync from polling to websocket
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, WebSocket, WebSocketDisconnect
|
||||
|
||||
from api.response import success
|
||||
from domain.auth import User, get_current_active_user
|
||||
from domain.video_room.service import VideoRoomService
|
||||
from domain.video_room.types import PlaybackEvent, VideoRoomCreate, VideoRoomInfo
|
||||
|
||||
router = APIRouter(prefix="/api/video-rooms", tags=["Video Rooms"])
|
||||
public_router = APIRouter(prefix="/api/watch", tags=["Video Rooms - Public"])
|
||||
|
||||
|
||||
@router.post("", response_model=VideoRoomInfo)
|
||||
async def create_video_room(
|
||||
request: Request,
|
||||
payload: VideoRoomCreate,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
):
|
||||
room = await VideoRoomService.create_room(
|
||||
user_id=current_user.id,
|
||||
path=payload.path,
|
||||
name=payload.name,
|
||||
expires_in_days=payload.expires_in_days,
|
||||
control_mode=payload.control_mode,
|
||||
)
|
||||
return VideoRoomInfo(
|
||||
id=room.id,
|
||||
name=room.name,
|
||||
token=room.token,
|
||||
path=room.path,
|
||||
control_mode=room.control_mode,
|
||||
created_at=room.created_at.isoformat(),
|
||||
expires_at=room.expires_at.isoformat() if room.expires_at else None,
|
||||
)
|
||||
|
||||
|
||||
@public_router.get("/{token}")
|
||||
async def get_watch_room(request: Request, token: str):
|
||||
room = await VideoRoomService.get_room_by_token(token)
|
||||
state = await VideoRoomService.get_state(room.id)
|
||||
return success({"room": VideoRoomInfo(
|
||||
id=room.id,
|
||||
name=room.name,
|
||||
token=room.token,
|
||||
path=room.path,
|
||||
control_mode=room.control_mode,
|
||||
created_at=room.created_at.isoformat(),
|
||||
expires_at=room.expires_at.isoformat() if room.expires_at else None,
|
||||
).model_dump(), "playback": state})
|
||||
|
||||
|
||||
@public_router.websocket("/{token}/ws")
|
||||
async def watch_room_ws(websocket: WebSocket, token: str):
|
||||
room = await VideoRoomService.get_room_by_token(token)
|
||||
actor = websocket.query_params.get("actor") or "guest"
|
||||
await VideoRoomService.ws_connect(room.id, websocket)
|
||||
try:
|
||||
state = await VideoRoomService.get_state(room.id)
|
||||
await websocket.send_json({"type": "snapshot", "playback": state})
|
||||
while True:
|
||||
msg = await websocket.receive_json()
|
||||
if msg.get("type") == "ping":
|
||||
await websocket.send_json({"type": "pong"})
|
||||
continue
|
||||
event_type = msg.get("event")
|
||||
if event_type not in {"play", "pause", "seek", "rate"}:
|
||||
continue
|
||||
position_ms = int(msg.get("position_ms") or 0)
|
||||
playback_rate = float(msg.get("playback_rate") or 1.0)
|
||||
state = await VideoRoomService.apply_event(
|
||||
room=room,
|
||||
actor=actor,
|
||||
event_type=event_type,
|
||||
position_ms=position_ms,
|
||||
playback_rate=playback_rate,
|
||||
)
|
||||
await VideoRoomService.ws_broadcast(room.id, {"type": "playback", "event": event_type, "playback": state, "actor": actor})
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
finally:
|
||||
await VideoRoomService.ws_disconnect(room.id, websocket)
|
||||
|
||||
|
||||
@public_router.post("/{token}/events")
|
||||
async def push_watch_event(request: Request, token: str, payload: PlaybackEvent):
|
||||
room = await VideoRoomService.get_room_by_token(token)
|
||||
actor = request.headers.get("X-Watch-Actor", "guest")
|
||||
state = await VideoRoomService.apply_event(
|
||||
room=room,
|
||||
actor=actor,
|
||||
event_type=payload.type,
|
||||
position_ms=payload.position_ms,
|
||||
playback_rate=payload.playback_rate,
|
||||
)
|
||||
await VideoRoomService.ws_broadcast(room.id, {"type": "playback", "event": payload.type, "playback": state, "actor": actor})
|
||||
return success({"playback": state})
|
||||
Reference in New Issue
Block a user