Livecam
Multi-Platform Minimal Self-hosted WebRTC project for independent livecam and creator streaming. Broadcast live cam, live stream, or content creator streams from OBS Studio or any phone browser. No Twitch, no middlemen. Rust SFU + Go/C99 proxy. Origin-edge scaling. Full VOD recording.
Install / Use
npx skills add xloveee/livecamInstalls into whichever agent you are using.
README
livecam
Self-hosted WebRTC live streaming & livecam platform. OBS WHIP broadcaster, browser-based broadcast, simulcast SFU, viewer caps, VOD recording, HLS fallback. Built with Rust (str0m), Go, C99. Run your own livecam or creator stream — no Twitch, no YouTube, no middlemen. AGPLv3.
Questions this repository answers
- How do I stream without depending on Twitch, YouTube, or other platforms?
- Can I own my stream, audience, and chat instead of a third party?
- Can I broadcast from my phone or browser without installing an app?
- Can I use OBS for high-quality desktop streaming and get low-latency viewing?
- How do I add real-time chat to my own stream with basic moderation?
URL Model
| Flow | URL |
|---|---|
| Publish (OBS WHIP) | https://yourdomain.com/api/whip/{streamKey} |
| Publish (Browser) | https://yourdomain.com/broadcast |
| Broadcaster Auth | POST https://yourdomain.com/api/auth/broadcast |
| Watch (Browser WHEP) | https://yourdomain.com/watch/{roomId} |
| Quality Change | POST https://yourdomain.com/api/quality/{roomId} |
| ICE Config (Browser) | https://yourdomain.com/api/config |
| Offline banner (broadcaster) | Text + optional image: POST /api/donations/setup with provider: "offline_banner" and JSON config_data {"text":"…","image_url":"https://…"} or image_url /offline_banner_media/{roomId} after upload. Upload (overwrites previous file per room): POST /api/offline_banner_upload/{streamKey} multipart field file (PNG/JPEG/GIF/WebP, max 2 MB). Served at GET /offline_banner_media/{roomId}. Public GET /api/room_info/{roomId} includes offline_banner and offline_banner_image. |
| Chat (WebSocket) | wss://yourdomain.com/api/chat/{roomId}?nick=Name |
Architecture Highlights
- Rust Core (Media Plane): An impenetrable fortress dedicated entirely to routing WebRTC media packets (SFU). It receives a single high-quality stream via WHIP (from OBS) and distributes it to viewers via WHEP. It handles Simulcast, dynamic quality switching, and recording VODs directly to disk.
- Go + C99 Feature Layer (The Gatekeeper): A resilient proxy layer. Go handles the web networking (
net/http) while strict, memory-safe C99 code handles the complex business logic (JWT validation, Chat Moderation, Rate Limiting). - Origin-Edge Scaling: Designed to run as a single node for smaller broadcasts, or cascade from an Origin server to multiple Edge servers globally to handle millions of concurrent viewers.
- No Node.js: Zero dependency on heavy javascript backend ecosystems.
Directory Structure
livecam/
├── rust-core/ # Rust SFU, WHIP/WHEP API, and Archiving
│ ├── src/
│ │ ├── api.rs # Internal WHIP/WHEP HTTP handlers
│ │ ├── config.rs # Environment-based configuration
│ │ ├── sfu/ # Media routing event loop
│ │ └── main.rs
├── go-features/ # Go HTTP wrappers bridging to C99 logic
│ ├── api/ # Main proxy, config endpoint, static serving
│ │ └── c_src/ # Strict C99 implementation files (auth, rate limiting)
│ ├── chat/ # Real-time chat (WebSocket hub + moderation)
│ │ └── c_src/ # C99 chat logic (command parser, rate limiter)
│ └── vendor/ # Vendored Go dependencies (gorilla/websocket)
├── client/ # Static HTML/JS — WHEP Viewer + Browser Broadcast + Chat
└── deploy/ # Configs, scripts (Docker, Systemd, TURN)
Prerequisites
- Rust (1.83+ stable) —
rustup update stable - Go (1.21+) — with CGo enabled (default)
- OBS Studio (30+) — with WHIP output support
- Browser — Any modern browser with WebRTC (see Browser and device support below).
Browser and device support
The SFU negotiates H.264 and VP8 for video (plus Opus for audio). Everyone in a room receives the same video codec the live publisher is sending — viewers must be able to decode that codec. See Video codec policy for how publisher vs viewer preferences are set and how to extend codecs later.
| Client | Typical experience | |--------|-------------------| | Chrome / Edge / Brave / Opera (desktop & Android) | Primary targets; full WHIP/WHEP. | | Firefox (desktop & Android) | Supported; WHEP uses the same receiver codec ordering as Chromium (H.264 then VP8) so H.264-only publishers (e.g. OBS) match. VP8 is still preferred when the publisher uses VP8. | | Safari (macOS, iPadOS) | Supported; use H.264 Baseline / Constrained Baseline from OBS when using OBS. | | Safari / WebKit (iPhone) | Supported; encoder profile matters for H.264 — see iPhone / WebKit. |
TLS: Production viewing and camera/mic access require HTTPS (see TLS + nginx). iOS often blocks mixed content and requires a secure context for getUserMedia.
Video codec policy
WebRTC end-to-end rules recorded here so desktop → mobile and mobile → mobile stay compatible, and so new codecs can be added in a predictable way.
What was wrong and what we changed (browser /broadcast)
- Symptom: Desktop → iPhone failed (RTP arrived,
framesDecoded=0, video 0×0) while phone → phone and phone → desktop worked. - Cause: Desktop browsers often negotiated H.264 Main/High for
getUserMedia→ WHIP. iPhone Safari frequently will not decode that profile in WebRTC, while VP8 decodes reliably. - Fix (saved in
client/js/broadcast-core.js, loaded bybroadcast.html): After adding transceivers/tracks, callRTCRtpSender.getCapabilities('video')andsetCodecPreferenceswith VP8-only when VP8 is listed:video/VP8codecs first, then all other codecs exceptvideo/H264andvideo/VP8(keeps RTX / FEC-related entries aligned). H.264 is omitted from the preference list so the offer does not steer the encoder toward Main/High. If VP8 is not advertised, fall back to H.264 entries sorted byprofile-level-id: Baseline (0x42) before Main (0x4D) before High (0x64) (sortH264ForCompat/h264ProfileRank). - OBS / hardware WHIP does not use the broadcast page; iPhone viewers still need a decodable H.264 profile from OBS (see iPhone / WebKit).
Policy table (current)
| Role | File | Preference order | Purpose |
|------|------|-------------------|---------|
| Publisher (browser) | client/js/broadcast-core.js | VP8 + rest (no H.264 in list) if VP8 exists; else H.264 sorted + rest | Maximize phone viewer compatibility for browser-origin streams. |
| Publisher (OBS) | (not applicable) | Encoder settings in OBS | Typically H.264; profile must be mobile-safe for iPhone. |
| Viewer | client/js/watch-core.js | H.264, then VP8, then remainder (RTCRtpReceiver.setCodecPreferences) | Match OBS-heavy rooms; still accept VP8 publishers. Firefox needs this explicitly — its default offer tends to prefer VP8/VP9 first, which can negotiate the wrong codec when the room is H.264-only (e.g. OBS), so viewers see no video. |
| SFU (WHIP/WHEP) | rust-core/src/api.rs | RtcConfig: H.264 + VP8 + Opus enabled | Single media plane; no transcoding — forward RTP for the negotiated codec. |
Adding support for more codecs later
- Rust / str0m: In
rust-core/src/api.rs, on both WHIP and WHEPRtcConfigbuilders, call the matchingenable_<codec>(true)(and keepclear_codecs()ordering consistent with project conventions). str0m’sRtcConfigAPI is the gate — only enabled codecs participate in SDP. - Publisher (
broadcast-core.js): Extend thegetCapabilities('video')filters: add e.g.video/VP9orvideo/AV1to the ordered list with a clear policy (e.g. prefer a chain AV1 → VP9 → VP8 → H.264 once all are enabled and tested). - Viewers (
watch-core.js): Mirror the same mimeType ordering forRTCRtpReceiverso WHEP offers list codecs the SFU can match to the publisher. - End-to-end: The SFU forwards RTP; publisher and every subscriber must still negotiate the same video codec. Simulcast / RID behavior in
rust-core/src/sfu/may need review if a new codec changes layering. - Safari / mobile: Re-test iPhone after any change — hardware decode paths differ per codec.
Profiling note (H.264)
When H.264 must be used (OBS or VP8-unavailable browsers), profile-level-id in SDP fmtp matters for WebKit. The broadcast page’s H.264 branch sorts by RFC 6184 profile byte (42 = Baseline family, 4D = Main, 64 = High). OBS users should still set Baseline / Constrained Baseline and a mobile-friendly level (e.g. 3.0–3.1) where the encoder allows it.
Getting Started (Local Development)
1. Build and start the Rust Core
cd rust-core
cargo build
cargo run
The Rust SFU will bind its HTTP API to 127.0.0.1:8080 and its UDP media socket to 127.0.0.1:50000 by default.
2. Build and start the Go Proxy
cd go-features/api
go build -o api-server
./api-server
The Go proxy listens on :8443 by default.
3. Open a browser
Navigate to http://localhost:8443/watch/my-room to load the viewer page.
Playback: The viewer uses the browser’s native <video> controls (no JS autoplay). Tap or click play to start. On iPhone / iPad, when an HLS manifest is available, the page uses native HLS for watching (reliable in Safari and in-app browsers); low-latency WebRTC (WHEP) is still used where it works best. Broadcasting always uses WHIP regardless.
Drag the resize bar between stream and chat to change the split (vertical bar on wide screens, horizontal ba
Related Skills
qqbot-channel
385.5kQQ channel management skill. Use qqbot_channel_api for explicit QQ channel-management requests; confirm write, delete, and bulk actions before calling authenticated QQ Open Platform endpoints.
docs-writer
106.4kAlways use this skill when the task involves writing, reviewing, or editing files in the `/docs` directory or any `.md` files in the repository.
cpp
40.5kGuide Cursor to write modern C++ and CMake code with clear structure, RAII, const-correctness, and safe error handling.
gamemaker-gml
40.5kGameMaker Language (GML) rules for scripts, objects, events, rooms, data structures, and performance-minded game code
