Somewhere right now, someone is pointing a phone at something the world needs to see. A protest. A disaster. An act of power that was never meant to be witnessed. The stream goes up, the platform takes it down. Account suspended. Evidence gone.
oneye exists because live video should not require permission. No sign-up forms. No terms of service. No content moderation team in a corporate office deciding what counts as news. You open a browser, you go live. Your identity is a cryptographic keypair that lives and dies in your browser tab. The relay you connect to is one node in a self-organizing mesh -- kill one, the others keep running.
One HTML file. That's the entire client. No build step, no dependency tree, no app store approval. Serve it from a USB stick, a Raspberry Pi, a borrowed VPS, or open it directly from GitHub Pages. The relay is a single Node.js process. Clone. Install. Run. You're broadcasting.
Viewers don't just watch -- they amplify. Any viewer can restream to YouTube, Facebook, TikTok through OBS or a mobile app. The broadcaster stays anonymous on the decentralized network while amplifiers push the signal into every centralized platform simultaneously. One stream becomes ten. The message gets out.
npm install
node server.jsThe relay binds to port 3000 and prints every available network interface:
[oneye] Relay listening on http://0.0.0.0:3000
[oneye] LAN: http://192.168.1.144:3000
Configuration -- copy config.json.example to config.json:
{
"port": 3000,
"host": "127.0.0.1",
"publicUrl": "wss://relay.yourdomain.com",
"allowedOrigins": null,
"maxConnections": 500
}Environment variables override config.json: PORT, HOST, PUBLIC_URL, ALLOWED_ORIGINS, MAX_WS_CONNECTIONS.
TURN server (optional, for NAT traversal): TURN_URL, TURN_USERNAME, TURN_CREDENTIAL -- or set turnUrl, turnUsername, turnCredential in config.json. See TURN Server below.
- Open the relay URL in your browser (e.g.,
http://192.168.1.144:3000) - Click Go Live
- Grant camera/microphone access
- Share the URL shown at the bottom of your preview
- Open the shared URL, or navigate to any relay in the network
- Click on a stream card to watch
- Audio plays automatically; video appears in the overlay
flowchart TB
subgraph decentralized ["🌐 DECENTRALIZED LAYER"]
direction TB
broadcaster["📱 Broadcaster"]
relay1["🔄 Relay (SFU)"]
relay2["🔄 Relay (SFU)"]
viewer["👁️ Viewer"]
amplifier["📡 Amplifier"]
broadcaster -->|WebRTC| relay1
relay1 <-->|DHT| relay2
relay1 -->|WebRTC| viewer
relay1 -->|WebRTC| amplifier
end
subgraph bridge ["🔌 BRIDGE"]
obs["🎬 OBS / Mobile App"]
end
subgraph centralized ["📺 CENTRALIZED PLATFORMS"]
youtube["YouTube"]
facebook["Facebook"]
tiktok["TikTok"]
end
amplifier -->|Screen Capture| obs
obs -->|RTMP| youtube
obs -->|RTMP| facebook
obs -->|RTMP| tiktok
style decentralized fill:#1a1a2e,stroke:#00d4ff,color:#fff
style bridge fill:#2d2d44,stroke:#a855f7,color:#fff
style centralized fill:#1a1a2e,stroke:#ff3366,color:#fff
The system has three layers:
- Relay (SFU) -- Receives streams from broadcasters and forwards them to viewers. Relays discover each other automatically via DHT (Hyperswarm). No registry. No coordinator. They find each other.
- Broadcaster -- Captures camera and mic, pushes to the nearest relay over WebRTC.
- Viewer -- Pulls the stream from any relay in the network. High-bandwidth viewers can forward to peers, reducing relay load.
- Amplifier -- A viewer who restreams to centralized platforms through OBS or a mobile app. The broadcaster stays decentralized and anonymous. Amplifiers bridge to YouTube, Facebook, and TikTok independently.
Deploy a relay anywhere. It joins the global mesh via DHT automatically. No configuration. No coordination. It just works.
Streams announced on Relay A are visible on Relay B. Every relay sees everything. Viewers watch from whichever node they connect to.
Real-time WebSocket chat built into every stream. Messages travel through the relay alongside video. No accounts required -- your keypair is your identity.
Server-side recording, opt-in per stream. Broadcasts are ephemeral by default. When recording is enabled, archives are browseable in the built-in archive library and playable on demand.
Every live stream and archived recording gets an iframe embed code. Drop it into any page. The player handles connection, quality adaptation, and playback automatically.
Client-side user blocking by pubkey. Block a user and their streams, chat messages, and presence disappear from your view. Runs entirely in the browser -- no server involvement, no moderation authority.
High-bandwidth viewers can forward streams to other viewers, reducing relay load. Auto-enabled on WiFi with good battery.
Broadcasters encode in multiple quality layers (simulcast). Viewers receive the layer matching their connection. Quality degrades gracefully, never drops entirely.
Supporters independently restream to YouTube, Facebook, TikTok, and Instagram. The broadcaster stays on oneye -- decentralized, ephemeral -- while amplifiers push the signal through centralized platforms. Multiple amplifiers multiply reach.
See active streams on an interactive map. Broadcasters can share location with configurable precision: exact, neighborhood, city, or region. Theme-aware tiles adapt to light and dark mode.
- Ephemeral by default. Recording is opt-in. When a stream ends, it's gone -- unless the broadcaster explicitly enabled archiving.
- No accounts. Your identity is a keypair stored in your browser. Close the tab, it's gone.
- No tracking. No analytics, no cookies, no persistent data on relays.
- Location control. Choose exact, neighborhood, city, or region-level precision. Or share nothing.
User preferences persist in localStorage:
- Theme: System, Dark, or Light mode
- Auto-play: Control whether streams play automatically
- Location: Remember location permission and default precision
- Notifications: Browser notifications for new streams
- Default View: Start with Live Streams, Archives, or Map
Before going live, configure your stream:
- Set a title
- Choose categories and add custom tags
- Enable/disable location sharing and precision
- Enable server-side recording (optional)
- Hamburger menu with full navigation on mobile
- Category grid in a 2-column layout
- Tap-to-toggle tooltips and responsive controls
- Sidebar collapses to icons, expands on interaction
Help spread the signal by restreaming to YouTube, Facebook, TikTok, or Instagram.
- Watch a stream on oneye
- Click Pop Out to open a clean capture window
- In OBS, add a Window Capture source
- Select the oneye pop-out window
- Add your RTMP destination (YouTube/Twitch/etc)
- Click Start Streaming
Prism Live Studio (Free, multi-platform):
- Install from App Store / Play Store
- Enable screen recording
- Open oneye in browser, start watching
- In Prism, go live to your platforms
Streamlabs (Alternative):
- Install Streamlabs mobile app
- Set up your streaming accounts
- Use screen broadcast feature
- Open oneye and watch the stream
- Pop Out -- Clean, borderless window optimized for OBS capture
- Picture-in-Picture -- Floats video over other windows
- Fullscreen -- Native fullscreen for full display capture
- Quality Selector -- Choose 720p/360p/180p based on your bandwidth
- Stream Info -- Press I in amplify mode to toggle stream title overlay
- Use 720p for best restream quality
- Close other apps to reduce CPU load
- Wired connection preferred (ethernet > WiFi > cellular)
- Multiple amplifiers = wider reach. Coordinate.
Desktop:
- OBS Studio (Free, open source) -- Best for window capture
- Streamlabs Desktop (Free) -- Simpler UI than OBS
Mobile:
- Prism Live Studio (Free) -- Multi-platform restreaming
- Streamlabs Mobile (Free) -- Easy setup
- Restream (Freemium) -- Web-based, multistreams
Clients find relays through multiple methods, in priority order:
- URL hash --
https://example.com/#relay=wss://relay.example.com - Bootstrap -- Fetches relay list from GitHub Pages (
relays.json). Update this file when relays change. - Well-known --
/.well-known/oneye.jsonon the current domain - DNS TXT --
_oneye.example.comTXT records via DNS-over-HTTPS - Self -- The URL you loaded becomes the relay
git clone https://github.com/msitarzewski/oneye.git
cd oneye
npm install
node server.js
# or: PORT=3333 node server.jsShare your LAN IP with others on the same network.
node server.js &
ngrok http 3000Share the ngrok HTTPS URL. WebSocket connections upgrade to WSS automatically.
cp config.json.example config.json
# Edit config.json with your domain and settings, then:
node server.jsPut behind nginx or caddy for TLS termination. Set publicUrl in config.json so the relay announces its public address to the DHT.
WebRTC requires direct UDP connections between peers. Roughly 15-20% of users sit behind symmetric NAT or restrictive firewalls where STUN alone fails. A TURN server provides a relay fallback for these users.
Without TURN, affected users will see stream thumbnails but video won't play.
Self-hosted with coturn:
# Install coturn
sudo apt install coturn # Debian/Ubuntu
brew install coturn # macOS
# /etc/turnserver.conf
listening-port=3478
min-port=49152
max-port=50175
realm=relay.example.com
user=oneye:your-secret-here
lt-cred-mech
fingerprintOpen firewall ports: UDP/TCP 3478 + UDP 49152-50175. TURN traffic goes direct to coturn, not through your reverse proxy.
TURN_URL=turn:relay.example.com:3478 \
TURN_USERNAME=oneye \
TURN_CREDENTIAL=your-secret-here \
PUBLIC_URL=wss://relay.example.com \
node server.jsThe relay sends ICE config (including TURN) to clients on connect. Each relay in the network can run its own TURN server independently.
Other TURN providers: Cloudflare Calls TURN (free), metered.ca (free tier), Twilio (paid). Set the env vars to match your provider's credentials.
Client to Relay:
subscribe-- Join the network, receive stream listannounce-- Start broadcasting a streamunannounce-- Stop broadcastingview-- Request to watch a streamsignal_forward-- WebRTC SDP offer (broadcaster)answer-- WebRTC SDP answer (viewer)candidate-- ICE candidatelayer_request-- Request specific quality layer (h/m/l) for simulcastbandwidth_report-- Report estimated bandwidth for adaptive streamingchat-- Send a chat message. Fields:text(string),from(sender pubkey)
Relay to Client:
ice_servers-- ICE configuration (STUN + TURN if configured), sent on connectwelcome/subscribed-- Connection confirmed, includes stream and relay listsstream_available-- New stream announcedstream_gone-- Stream endedsignal-- WebRTC SDP (offer to viewer, answer to broadcaster)candidate-- ICE candidatechat-- Relayed chat message withtext,from, andstreamId
GET /-- Serve the appGET /health-- JSON status:{ ok, streams, clients, relays }GET /relays-- List of known relaysGET /.well-known/oneye.json-- Relay discovery endpointGET /archives-- List available archived recordingsPOST /archives/:id/delete-- Delete an archived recordingGET /embed-- Embeddable player page for streams and archives
MIT -- See LICENSE
All dependencies are inlined. No external CDN requests:
- QRCode.js -- MIT License -- github.com/davidshimjs/qrcodejs
- werift-webrtc -- MIT License -- github.com/shinyoshiaki/werift-webrtc



