Files
Foxel/domain/video_room/api.py

98 lines
3.7 KiB
Python

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