TeleFuser Stream Server Guide¶
This guide covers TeleFuser's real-time stream server, which delivers continuous video (and optional audio) over WebRTC connections — as opposed to the batch request-response mode of telefuser serve.
Quick Start¶
# 1. Install with WebRTC support
pip install -e ".[webrtc]"
# 2. Start the stream server
telefuser stream-serve examples/stream_server/stream_video_replay.py -p 8088 --skip-validation
# 3. Open the WebRTC client demo
python examples/stream_server/webrtc_client_demo.py --server-url http://localhost:8088
Open http://localhost:8090 in a browser, type a prompt, and click Connect to see real-time video.
Stream Modes¶
TeleFuser stream server supports two interaction modes, both available over WebRTC:
| Mode | Transport | Direction | Use Case |
|---|---|---|---|
| Server Push | WebRTC (RTP) | Server → Client | Real-time preview, text-to-video streaming |
| Bidirectional | WebRTC (RTP + DataChannel) | Client ↔ Server | Interactive generation, keyboard/camera control, speech-to-video |
Server Push (WebRTC)¶
Client Server
│ │
│ POST /v1/stream/webrtc/offer │
│ (SDP offer + prompt) │
│──────────────────────────────►│
│ │ stream_task() → serve()
│ SDP answer │ on background thread
│◄──────────────────────────────│
│ │
│ ◄──── RTP video frames ──── │
│ ◄──── RTP audio frames ──── │ (optional)
│ │
│ DELETE /v1/stream/webrtc/{id}│
│──────────────────────────────►│ cleanup
The pipeline's serve() method yields chunks containing JPEG-encoded frames. The WebRTC layer decodes them into av.VideoFrame objects and streams them to the browser via RTP at the target frame rate.
Bidirectional (WebRTC)¶
Client Server
│ │
│ pc.createDataChannel("telefuser")
│ pc.addTransceiver("video", recvonly)
│ (optionally add camera/mic tracks)
│ │
│ POST /v1/stream/webrtc/offer │
│ (SDP offer + config) │
│──────────────────────────────►│
│ │ create_session(config)
│ │ pull_chunks(session_id)
│ SDP answer │ start ChunkRouter
│◄──────────────────────────────│
│ │
│ ──── DataChannel JSON ─────► │ push_chunk(session_id, data)
│ ◄──── RTP video frames ──── │ ChunkRouter → FrameGeneratorTrack
│ ◄──── RTP audio frames ──── │ ChunkRouter → AudioGeneratorTrack
│ ◄──── DataChannel JSON ──── │ ChunkRouter → metadata
│ │
│ ──── Media tracks (opt) ───► │ IncomingVideoRelay / AudioRelay
│ │ → push_chunk(session_id, frames)
│ │
│ {"type": "stop"} │
│ or DELETE /v1/stream/webrtc/{}│
│──────────────────────────────►│ close_session + cleanup
The client must create a DataChannel named "telefuser" before generating the SDP offer. The server reuses that single channel for receiving control input and sending metadata output. Video and audio are transported over RTP media tracks in both directions.
ChunkRouter is the server-side fan-out adapter that consumes the pipeline's pull_chunks() generator exactly once and dispatches:
frames_b64→ decoded JPEG →av.VideoFrame→ pushed toFrameGeneratorTrack(RTP video)audio_b64→ decoded PCM16 → fed toAudioGeneratorTrack(RTP audio)- Remaining metadata → serialized as
StreamChunkMessageJSON → sent over DataChannel
CLI Usage¶
Options¶
| Flag | Default | Description |
|---|---|---|
--port, -p | 8088 | Server port |
--host | 0.0.0.0 | Bind address |
--security-level | strict | Pipeline validation level (strict, standard, permissive) |
--skip-validation | false | Skip pipeline file security checks |
Examples¶
# Start with default settings
telefuser stream-serve examples/stream_server/stream_video_replay.py
# Custom port and host
telefuser stream-serve my_pipeline.py -p 9000 --host 127.0.0.1
# Skip validation (development only)
telefuser stream-serve my_pipeline.py -p 8088 --skip-validation
Creating a Stream Pipeline¶
A stream pipeline is a Python file that defines a get_service() function returning a service object. The service must implement one of two protocols.
ServerPushService Protocol¶
For pipelines that accept a single request and stream output continuously.
from __future__ import annotations
from collections.abc import AsyncGenerator
class MyService:
def start(self) -> None:
"""Called once at server startup. Load models, open files, etc."""
...
def stop(self) -> None:
"""Called at server shutdown. Release resources."""
...
async def serve(self, request: dict) -> AsyncGenerator[dict, None]:
"""Yield output chunks for a single request.
The request dict contains all fields from the WebRTC offer
(prompt, task, fps, duration_s, etc.).
"""
for i in range(10):
yield {
"type": "chunk",
"index": i,
"frames_b64": [encode_frame(frame)],
"fps": 24,
}
def get_service() -> MyService:
return MyService()
BidirectionalService Protocol¶
For pipelines that accept continuous input and produce continuous output.
from __future__ import annotations
from collections.abc import AsyncGenerator
class MyBidirectionalService:
def start(self) -> None: ...
def stop(self) -> None: ...
def create_session(self, config: dict) -> str:
"""Create a new session. Return session_id."""
...
def push_chunk(self, session_id: str, chunk: dict) -> None:
"""Push an input chunk into the session."""
...
async def pull_chunks(self, session_id: str) -> AsyncGenerator[dict, None]:
"""Yield output chunks for the session."""
...
def close_session(self, session_id: str) -> None:
"""Close the session and free resources."""
...
def get_service() -> MyBidirectionalService:
return MyBidirectionalService()
Chunk Format¶
Server-push chunks should contain the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
type | str | Yes | Always "chunk" |
index | int | Yes | Chunk sequence number |
frames_b64 | list[str] | Yes | Base64-encoded JPEG frames |
fps | int | Yes | Target frame rate |
num_frames | int | No | Number of frames in this chunk |
resolution | str | No | Frame resolution (e.g., "1920x1080") |
prompt | str | No | Echo of input prompt |
timestamp | float | No | Chunk generation timestamp |
audio_b64 | str | No | Base64-encoded PCM16 audio data |
audio_sample_rate | int | No | Audio sample rate (default: 48000) |
audio_channels | int | No | Number of audio channels (default: 1) |
Audio fields are optional. When present, the WebRTC layer creates an audio track alongside the video track.
API Reference¶
Endpoints Overview¶
| Endpoint | Method | Mode | Description |
|---|---|---|---|
/v1/stream/webrtc/offer | POST | Both | WebRTC SDP offer/answer exchange |
/v1/stream/webrtc/{session_id} | DELETE | Both | Close WebRTC session |
/v1/stream/sessions/{session_id} | DELETE | Both | Close session (pipeline + WebRTC) |
/v1/stream/sessions/{session_id}/status | GET | Both | Get session status |
WebRTC: SDP Offer¶
POST /v1/stream/webrtc/offer
This endpoint serves both server-push and bidirectional modes. The server auto-detects the mode based on the pipeline's service type.
Request:
{
"sdp": "<SDP offer string>",
"type": "offer",
"session_id": "optional-uuid",
"task": "t2v",
"prompt": "A sunset over the ocean",
"fps": 24,
"duration_s": 10,
"config": {}
}
| Field | Type | Required | Description |
|---|---|---|---|
sdp | str | Yes | SDP offer from browser |
type | str | Yes | SDP type (usually "offer") |
session_id | str | No | Custom session ID (auto-generated if omitted) |
task | str | Yes | Task type (e.g. t2v, i2v, bidirectional) |
prompt | str | No | Input prompt |
fps | int | No | Target video FPS (default: 24) |
config | dict | No | Extra config passed to BidirectionalService.create_session() |
Additional fields are allowed (
"extra": "allow") and forwarded to the pipeline.
Response (200 OK):
Error responses:
| Status | Condition |
|---|---|
400 | Invalid SDP or negotiation failure |
503 | Stream service not running or max sessions reached |
WebRTC: Close Session¶
DELETE /v1/stream/webrtc/{session_id}
Response (200 OK):
WebRTC: DataChannel Protocol (Bidirectional)¶
In bidirectional mode, the client-created "telefuser" DataChannel carries JSON messages in both directions.
Client → Server messages:
// Control input (e.g., keyboard, prompt)
{"type": "control", "key": "ArrowUp", "action": "press"}
{"type": "control", "prompt": "new prompt text"}
// Stop session
{"type": "stop"}
Server → Client messages:
// Output chunk metadata (media sent separately via RTP)
{
"type": "chunk",
"session_id": "abc123",
"index": 0,
"data": {"type": "chunk", "index": 0, "fps": 24, "timestamp": 1714000000.0},
"timestamp": 1714000000.0
}
// Generation complete
{
"type": "done",
"session_id": "abc123",
"total_chunks": 240,
"timestamp": 1714000010.0
}
The control message format is pipeline-defined. The examples above show the convention used by
ArrowOverlayService. Your pipeline'spush_chunk()receives whatever JSON the client sends.
Stream-Specific Health Fields¶
GET /v1/service/health returns additional fields when stream service is active:
{
"status": "healthy",
"stream_ready": true,
"stream_mode": "server_push",
"webrtc_active_sessions": 1,
"webrtc_server_push_sessions": 1,
"webrtc_bidirectional_sessions": 0,
"webrtc_max_sessions": 10
}
Stream-Specific Metadata Fields¶
GET /v1/service/metadata:
{
"service_type": "stream",
"stream_mode": "server_push",
"pipeline_file": "examples/stream_server/stream_video_replay.py",
"security_level": "STRICT",
"runner": "StreamPipelineService",
"webrtc_active_sessions": 0,
"webrtc_server_push_sessions": 0,
"webrtc_bidirectional_sessions": 0,
"webrtc_max_sessions": 10
}
Client Integration¶
WebRTC: Server Push (JavaScript)¶
Minimal browser client for server-push mode:
// For LAN: no iceServers needed (or use default STUN)
// For public network: configure STUN + TURN servers
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
// Add TURN server for production / NAT traversal:
// { urls: "turn:your-domain.com:3478", username: "user", credential: "pass" },
],
});
pc.addTransceiver("video", { direction: "recvonly" });
pc.addTransceiver("audio", { direction: "recvonly" });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const resp = await fetch("http://localhost:8088/v1/stream/webrtc/offer", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sdp: pc.localDescription.sdp,
type: pc.localDescription.type,
task: "t2v",
prompt: "A cat playing piano",
}),
});
const answer = await resp.json();
await pc.setRemoteDescription(answer);
pc.ontrack = (event) => {
document.getElementById("video").srcObject = event.streams[0];
};
WebRTC: Bidirectional (JavaScript)¶
Full-duplex client with DataChannel for control and optional camera/mic input:
const pc = new RTCPeerConnection();
// 1. Client MUST create DataChannel before generating the offer
const dc = pc.createDataChannel("telefuser");
dc.onopen = () => {
// Send control messages after channel opens
dc.send(JSON.stringify({ type: "control", key: "ArrowUp", action: "press" }));
};
dc.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
if (msg.type === "done") console.log("Generation complete:", msg.total_chunks, "chunks");
};
// 2. Optionally send camera/mic to the server
// const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
// stream.getTracks().forEach(t => pc.addTrack(t, stream));
// 3. Receive server output video/audio
pc.addTransceiver("video", { direction: "recvonly" });
pc.addTransceiver("audio", { direction: "recvonly" });
pc.ontrack = (evt) => {
if (evt.track.kind === "video") {
document.getElementById("video").srcObject = evt.streams[0];
}
};
// 4. SDP exchange (same endpoint as server-push)
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const resp = await fetch("http://localhost:8088/v1/stream/webrtc/offer", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
sdp: pc.localDescription.sdp,
type: pc.localDescription.type,
task: "bidirectional",
prompt: "A dog running",
config: { fps: 24 },
}),
});
const answer = await resp.json();
await pc.setRemoteDescription(new RTCSessionDescription({
sdp: answer.sdp,
type: answer.type,
}));
// 5. Stop session
// dc.send(JSON.stringify({ type: "stop" }));
// await fetch(`http://localhost:8088/v1/stream/webrtc/${answer.session_id}`, { method: "DELETE" });
Gradio UI¶
TeleFuser includes a Gradio-based web UI for WebRTC streaming:
# Start the stream server
telefuser stream-serve examples/stream_server/stream_video_replay.py -p 8088
# Launch the Gradio UI (separate terminal)
python webui/stream_app.py --server-url http://localhost:8088 --port 7860
The Gradio UI provides a prompt input, duration slider, and embedded WebRTC video player.
Examples¶
Video Replay Service¶
examples/stream_server/stream_video_replay.py — A server-push pipeline that loads a local video file and streams it as frame chunks:
- Loads video frames and audio with PyAV at
start()time - Pre-encodes all frames as JPEG (cached, not re-encoded per request)
- Streams chunks at configurable pacing with optional audio
- Supports
duration_s,realtime, andpromptrequest parameters
WebRTC Client Demo¶
examples/stream_server/webrtc_client_demo.py — A standalone HTML page served via Python's HTTP server:
Opens a browser page with video player, prompt input, and connect/stop/unmute buttons.
Arrow Overlay Service (Bidirectional)¶
examples/stream_server/stream_arrow_overlay.py — A bidirectional pipeline that loads a video and overlays a D-pad HUD based on keyboard input:
- Implements
BidirectionalServiceprotocol with session state management push_chunk()receives{"type": "control", "key": "ArrowUp", "action": "press"}and updatespressed_keyspull_chunks()yields video frames with D-pad overlay drawn based onpressed_keys- Loops through video frames indefinitely until session closes
# Start the server
telefuser stream-serve examples/stream_server/stream_arrow_overlay.py -p 8088 --skip-validation
# Start the client (opens browser at localhost:8092)
python examples/stream_server/webrtc_arrow_overlay_demo.py --server-url http://localhost:8088
Press arrow keys in the browser to see the D-pad overlay respond in real time.
Bidirectional Client Demo¶
examples/stream_server/webrtc_bidirectional_demo.py — A general-purpose bidirectional WebRTC client with:
- DataChannel for sending prompts and control messages
- Optional camera and microphone input via media tracks
- Server output video player and DataChannel message log
LingBot-World-Fast Streaming¶
examples/stream_server/stream_lingbot_world_fast.py provides a bidirectional streaming service for LingBot-World-Fast. The service generates video over WebRTC RTP and receives prompts and direction control messages over DataChannel. The current demo page does not capture the browser camera or microphone; LingBot currently outputs video only, no audio.
Model Files¶
LingBot-World-Fast requires two sets of weights:
| Environment Variable | Example | Description |
|---|---|---|
LINGBOT_WORLD_CHECKPOINT_DIR | /storage/model_zoo/Wan2.2-Distill-Models | Base weight directory containing VAE, T5 text encoder, and tokenizer |
LINGBOT_WORLD_FAST_CHECKPOINT_SUBDIR | /storage/model_zoo/lingbot-world-fast | LingBot-World-Fast DiT weight directory; can be an absolute path |
Start the Server¶
Recommended launch with two GPUs: DiT on GPU 0, text encoder and VAE on GPU 1. If GPU 1 VRAM is tight, move VAE back to CPU.
TELEFUSER_TURN_SERVER='turn:127.0.0.1:3478' \
TELEFUSER_TURN_USERNAME=telefuser \
TELEFUSER_TURN_CREDENTIAL=your-turn-password \
LINGBOT_WORLD_CHECKPOINT_DIR=/storage/model_zoo/Wan2.2-Distill-Models \
LINGBOT_WORLD_FAST_CHECKPOINT_SUBDIR=/storage/model_zoo/lingbot-world-fast \
LINGBOT_WORLD_CONTROL_TYPE=cam \
LINGBOT_WORLD_MAX_AREA=99840 \
telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py \
-p 8088 --host 0.0.0.0 --skip-validation
Wait for the following log line before connecting the browser demo:
Verify the service is ready:
Start the Browser Demo¶
When using VS Code Remote SSH to access the server from a laptop browser, browser JavaScript runs locally. In this case, use TURN and forward the remote 3478 port to local 3478.
python examples/stream_server/webrtc_bidirectional_demo.py \
--server-url http://localhost:8088 \
--port 8091 \
--image-path /tmp/lingbot_test_input.png \
--frame-num 81 \
--chunk-size 3 \
--fps 16 \
--turn-url 'turn:localhost:3478?transport=tcp' \
--turn-username telefuser \
--turn-credential your-turn-password \
--force-turn-relay \
--ice-gather-timeout-ms 30000 \
--control-lateral-step 0.25 \
--control-yaw-step-degrees 12 \
--no-open
Open in browser:
--image-path is a server-side file path, not the laptop local path. The demo enables proxying by default; the browser only needs to access the demo port. Requests to /v1/stream/webrtc/* are forwarded by the demo process to --server-url.
Without TURN: View on Server Browser¶
If the browser actually runs on the server side (e.g., Chrome in remote desktop, VNC, or noVNC), you can skip TURN. Do not set TELEFUSER_TURN_* and do not pass --turn-url to the demo.
Server:
env -u TELEFUSER_TURN_SERVER \
-u TELEFUSER_TURN_USERNAME \
-u TELEFUSER_TURN_CREDENTIAL \
LINGBOT_WORLD_CHECKPOINT_DIR=/storage/model_zoo/Wan2.2-Distill-Models \
LINGBOT_WORLD_FAST_CHECKPOINT_SUBDIR=/storage/model_zoo/lingbot-world-fast \
LINGBOT_WORLD_CONTROL_TYPE=cam \
LINGBOT_WORLD_MAX_AREA=99840 \
telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py \
-p 8088 --host 0.0.0.0 --skip-validation
Demo:
env -u TELEFUSER_TURN_SERVER \
-u TELEFUSER_TURN_USERNAME \
-u TELEFUSER_TURN_CREDENTIAL \
python examples/stream_server/webrtc_bidirectional_demo.py \
--server-url http://127.0.0.1:8088 \
--port 8091 \
--image-path /tmp/lingbot_test_input.png \
--frame-num 81 \
--chunk-size 3 \
--fps 16 \
--control-lateral-step 0.25 \
--control-yaw-step-degrees 12 \
--no-open
Open on server browser:
Direction Control¶
The demo supports keyboard arrow keys and a page D-pad:
| Input | cam mode meaning |
|---|---|
↑ | Camera forward |
↓ | Camera backward |
← | Turn left and strafe left |
→ | Turn right and strafe right |
Controls only affect chunks that have not yet started generating; chunks already denoising or decoding are not immediately changed. Therefore, it is recommended to hold a direction key early after connecting, rather than clicking near the end of the video.
The following states in the DataChannel log indicate that direction control has been consumed by the server and applied to a generation chunk:
The demo enables Control HUD by default. The HUD overlay appears in the top-left corner of output chunks that received direction control, confirming the control pipeline is active. Once confirmed, you can uncheck the HUD on the page to observe pure model output.
Common control strength parameters:
| Parameter | Default | Description |
|---|---|---|
--control-move-step | 0.18 | Forward/backward displacement step size |
--control-yaw-step-degrees | 10.0 | Yaw angle per latent frame |
--control-lateral-step | 0.12 | Left/right lateral step size |
--show-control-hud / --no-show-control-hud | true | Whether to overlay direction HUD on controlled chunks |
If left/right effects are subtle, increase --control-lateral-step, e.g., 0.25 or 0.3.
cam vs act Control Modes¶
| Mode | Input | Description |
|---|---|---|
cam | poses + intrinsics | Camera trajectory control. The server converts arrow keys into camera poses, then builds a 6-channel camera control tensor. |
act | poses + intrinsics + action | Action control. Requires 7-channel action-control weights. |
The current demo defaults to cam. If the model weights are camera-control weights, keep:
Only switch to act when using action-control weights:
VRAM and Resolution¶
LingBot's KV cache grows with frame_num and output resolution. 832×480 at 81 frames approaches the 80 GB H100 VRAM limit. Start with a lower resolution to verify the pipeline:
Common tuning parameters:
| Parameter | Effect |
|---|---|
LINGBOT_WORLD_MAX_AREA | Reduce output area, significantly lower VRAM usage |
--frame-num | Reduce total generated frames and latent chunk count |
--chunk-size | Affect the latent chunk size per generation step |
The service currently allows only one LingBot active session. Before reconnecting, click Stop on the demo, or call:
If still experiencing OOM, check whether other processes are using the GPU:
Configuration¶
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
TELEFUSER_WEBRTC_MAX_SESSIONS | 10 | Maximum concurrent WebRTC sessions (1-100) |
TELEFUSER_STUN_SERVERS | ["stun:stun.l.google.com:19302"] | STUN server URLs (JSON array) |
TELEFUSER_TURN_SERVER | None | TURN server URL (e.g. turn:your-domain.com:3478) |
TELEFUSER_TURN_USERNAME | None | TURN server username |
TELEFUSER_TURN_CREDENTIAL | None | TURN server credential |
CORS¶
CORS middleware is automatically added when stream service is initialized (required for browser-based WebRTC clients). All origins are allowed by default.
Security Levels¶
Pipeline files are validated before loading. Use --skip-validation only for development:
| Level | Description |
|---|---|
strict | Full validation — no dangerous imports, no file system access |
standard | Moderate — allows common libraries |
permissive | Minimal checks |
Public Network Deployment¶
When deploying the stream server on a public network (server and client on different networks), WebRTC requires additional configuration for NAT traversal and browser security.
1. HTTPS (Required)¶
Browsers block WebRTC on non-localhost HTTP pages. You must serve over HTTPS:
# Option A: Let's Encrypt (recommended for production)
certbot certonly --standalone -d your-domain.com
# Option B: Self-signed certificate (development/testing)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
# Start with TLS
uvicorn telefuser.service.api.app:app \
--host 0.0.0.0 --port 443 \
--ssl-keyfile key.pem --ssl-certfile cert.pem
2. STUN/TURN Servers¶
STUN discovers the client's public IP (lightweight, free). TURN relays media when direct connections fail (requires bandwidth, self-host recommended).
Minimal /etc/turnserver.conf:
listening-port=3478
tls-listening-port=5349
realm=your-domain.com
server-name=your-domain.com
fingerprint
lt-cred-mech
user=telefuser:your-secret-password
external-ip=203.0.113.10
min-port=49152
max-port=65535
Configure TeleFuser to use the TURN server:
export TELEFUSER_TURN_SERVER="turn:your-domain.com:3478"
export TELEFUSER_TURN_USERNAME="telefuser"
export TELEFUSER_TURN_CREDENTIAL="your-secret-password"
telefuser stream-serve pipeline.py -p 8000
3. Firewall Ports¶
| Port | Protocol | Purpose |
|---|---|---|
| 443 | TCP | HTTPS (API + SDP signaling) |
| 3478 | TCP+UDP | STUN/TURN |
| 5349 | TCP | TURN over TLS |
| 49152-65535 | UDP | TURN relay media ports |
Deployment Architecture¶
┌─────────┐ HTTPS/443 ┌──────────────┐
│ Client │◄──────────────────►│ Nginx/CDN │
│ (Browser)│ │ (TLS term.) │
│ │ UDP ├──────────────┤
│ │◄──────────────────►│ TeleFuser │
│ │ (WebRTC media) │ :8000 │
│ │ ├──────────────┤
│ │◄──────────────────►│ coturn │
│ │ UDP 3478 + │ (TURN relay)│
│ │ 49152-65535 │ │
└─────────┘ └──────────────┘
LAN deployment requires no STUN/TURN configuration. The default settings work out of the box when server and client are on the same network.
Troubleshooting¶
WebRTC Connection Fails (ICE Error)¶
For LAN: ensure the server binds to 0.0.0.0 (not 127.0.0.1) and no firewall blocks UDP.
For public network:
- Verify STUN/TURN servers are configured (see Public Network Deployment)
- Ensure TURN server ports (3478, 49152-65535) are open
- Test TURN connectivity:
turnutils_uclient -u user -w pass your-domain.com - Check browser DevTools →
chrome://webrtc-internalsfor ICE candidate details
No Audio in Browser¶
Click the Unmute button. Browsers require a user gesture before playing audio. The video element starts muted by default.
Port Already in Use¶
"Stream service is not running" (503)¶
The pipeline's start() method failed. Check server logs for errors (e.g., missing video file, import errors).
High Memory Usage¶
Each WebRTC session holds video frames in a queue. Limit webrtc_max_sessions and ensure pipelines don't buffer excessive frames. The server automatically cleans up sessions when clients disconnect.