Files
ai-hell/server/streaming.py
T

91 lines
2.8 KiB
Python

"""WebSocket broadcast manager for streaming horror to connected clients."""
import json
import random
from fastapi import WebSocket
class StreamManager:
"""Manages WebSocket clients and broadcasts horror events."""
TRANSITIONS = ["crossfade", "dissolve", "glitch_cut", "melt_morph"]
def __init__(self):
self._clients: set[WebSocket] = set()
@property
def client_count(self) -> int:
return len(self._clients)
def add_client(self, ws: WebSocket) -> None:
self._clients.add(ws)
def remove_client(self, ws: WebSocket) -> None:
self._clients.discard(ws)
async def _broadcast(self, message: str) -> None:
"""Send to all clients, remove dead ones."""
dead: list[WebSocket] = []
for ws in self._clients:
try:
await ws.send_text(message)
except Exception:
dead.append(ws)
for ws in dead:
self._clients.discard(ws)
async def broadcast_phase(self, intensity: float, params: dict) -> None:
"""Push a phase update with current intensity and rendering params."""
msg = json.dumps({
"type": "phase",
"intensity": round(intensity, 2),
"params": params,
})
await self._broadcast(msg)
async def broadcast_asset(
self, url: str, severity: float, transition: str | None = None,
) -> None:
"""Push a new image asset reference."""
if transition is None:
transition = random.choice(self.TRANSITIONS)
msg = json.dumps({
"type": "asset",
"url": url,
"severity": round(severity, 2),
"transition": transition,
})
await self._broadcast(msg)
async def broadcast_whisper(
self, url: str, pan: float, volume: float, reverb: float,
) -> None:
"""Push a whisper audio clip reference."""
msg = json.dumps({
"type": "whisper",
"url": url,
"pan": round(pan, 2),
"volume": round(volume, 2),
"reverb": round(reverb, 2),
})
await self._broadcast(msg)
async def broadcast_address(self, audio_b64: str, text: str) -> None:
"""Push a direct address audio clip (base64-encoded WAV)."""
msg = json.dumps({
"type": "address",
"audio": audio_b64,
"text": text,
})
await self._broadcast(msg)
async def broadcast_scare(self, effect: str, duration_ms: int) -> None:
"""Push a scare event (face flash, white-out, inversion, etc.)."""
msg = json.dumps({
"type": "scare",
"effect": effect,
"duration_ms": duration_ms,
})
await self._broadcast(msg)