{"id":50316243,"url":"https://github.com/msitarzewski/oneye","last_synced_at":"2026-05-29T00:03:00.651Z","repository":{"id":334720187,"uuid":"1142055648","full_name":"msitarzewski/oneye","owner":"msitarzewski","description":"Decentralized P2P live streaming with distributed amplification. Zero CDN. One HTML file.","archived":false,"fork":false,"pushed_at":"2026-03-12T20:04:35.000Z","size":664,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-13T00:44:21.456Z","etag":null,"topics":["broadcasting","decentralized","live-streaming","p2p","sfu","webrtc"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msitarzewski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-25T21:59:45.000Z","updated_at":"2026-03-12T20:04:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/msitarzewski/oneye","commit_stats":null,"previous_names":["msitarzewski/oneye"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/msitarzewski/oneye","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Foneye","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Foneye/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Foneye/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Foneye/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msitarzewski","download_url":"https://codeload.github.com/msitarzewski/oneye/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Foneye/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33631002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["broadcasting","decentralized","live-streaming","p2p","sfu","webrtc"],"created_at":"2026-05-29T00:02:58.021Z","updated_at":"2026-05-29T00:03:00.636Z","avatar_url":"https://github.com/msitarzewski.png","language":"HTML","funding_links":["https://github.com/sponsors/msitarzewski"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# `oneye`\n\n### Uncensorable live streaming. Zero infrastructure. Zero identity. Zero compromise.\n\n[![Node.js](https://img.shields.io/badge/Node.js-18+-339933?style=flat-square\u0026logo=nodedotjs\u0026logoColor=white)](https://nodejs.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)\n[![Single HTML File](https://img.shields.io/badge/Client-Single%20HTML%20File-ff3366?style=flat-square)]()\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-a855f7?style=flat-square)](https://github.com/msitarzewski/oneye/pulls)\n\n**[Live Demo](https://msitarzewski.github.io/oneye/)** | **[GitHub](https://github.com/msitarzewski/oneye)** | **[Sponsor](https://github.com/sponsors/msitarzewski)**\n\n\u003c/div\u003e\n\n---\n\n## The Mission\n\nSomewhere 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.\n\noneye 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.\n\nOne 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.\n\nViewers 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.\n\n\u003cdiv align=\"center\"\u003e\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cimg src=\"docs/screenshots/splash.png\" alt=\"Splash screen\" width=\"400\"\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cimg src=\"docs/screenshots/live-streams.png\" alt=\"Live streams\" width=\"400\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eSplash screen\u003c/em\u003e\u003c/td\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eLive streams\u003c/em\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cimg src=\"docs/screenshots/map.png\" alt=\"Map view\" width=\"400\"\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cimg src=\"docs/screenshots/sidebar.png\" alt=\"Sidebar with categories\" width=\"400\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eMap view\u003c/em\u003e\u003c/td\u003e\n\u003ctd align=\"center\"\u003e\u003cem\u003eSidebar with categories\u003c/em\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\u003c/div\u003e\n\n## Quick Start\n\n### Run a Relay\n\n```bash\nnpm install\nnode server.js\n```\n\nThe relay binds to port 3000 and prints every available network interface:\n\n```\n[oneye] Relay listening on http://0.0.0.0:3000\n[oneye] LAN: http://192.168.1.144:3000\n```\n\n**Configuration** -- copy `config.json.example` to `config.json`:\n\n```json\n{\n  \"port\": 3000,\n  \"host\": \"127.0.0.1\",\n  \"publicUrl\": \"wss://relay.yourdomain.com\",\n  \"allowedOrigins\": null,\n  \"maxConnections\": 500\n}\n```\n\nEnvironment variables override `config.json`: `PORT`, `HOST`, `PUBLIC_URL`, `ALLOWED_ORIGINS`, `MAX_WS_CONNECTIONS`.\n\n**TURN server** (optional, for NAT traversal): `TURN_URL`, `TURN_USERNAME`, `TURN_CREDENTIAL` -- or set `turnUrl`, `turnUsername`, `turnCredential` in `config.json`. See [TURN Server](#turn-server-optional) below.\n\n### Go Live\n\n1. Open the relay URL in your browser (e.g., `http://192.168.1.144:3000`)\n2. Click **Go Live**\n3. Grant camera/microphone access\n4. Share the URL shown at the bottom of your preview\n\n### Watch a Stream\n\n1. Open the shared URL, or navigate to any relay in the network\n2. Click on a stream card to watch\n3. Audio plays automatically; video appears in the overlay\n\n## Architecture\n\n```mermaid\nflowchart TB\n    subgraph decentralized [\"🌐 DECENTRALIZED LAYER\"]\n        direction TB\n        broadcaster[\"📱 Broadcaster\"]\n        relay1[\"🔄 Relay (SFU)\"]\n        relay2[\"🔄 Relay (SFU)\"]\n        viewer[\"👁️ Viewer\"]\n        amplifier[\"📡 Amplifier\"]\n\n        broadcaster --\u003e|WebRTC| relay1\n        relay1 \u003c--\u003e|DHT| relay2\n        relay1 --\u003e|WebRTC| viewer\n        relay1 --\u003e|WebRTC| amplifier\n    end\n\n    subgraph bridge [\"🔌 BRIDGE\"]\n        obs[\"🎬 OBS / Mobile App\"]\n    end\n\n    subgraph centralized [\"📺 CENTRALIZED PLATFORMS\"]\n        youtube[\"YouTube\"]\n        facebook[\"Facebook\"]\n        tiktok[\"TikTok\"]\n    end\n\n    amplifier --\u003e|Screen Capture| obs\n    obs --\u003e|RTMP| youtube\n    obs --\u003e|RTMP| facebook\n    obs --\u003e|RTMP| tiktok\n\n    style decentralized fill:#1a1a2e,stroke:#00d4ff,color:#fff\n    style bridge fill:#2d2d44,stroke:#a855f7,color:#fff\n    style centralized fill:#1a1a2e,stroke:#ff3366,color:#fff\n```\n\nThe system has three layers:\n\n- **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.\n- **Broadcaster** -- Captures camera and mic, pushes to the nearest relay over WebRTC.\n- **Viewer** -- Pulls the stream from any relay in the network. High-bandwidth viewers can forward to peers, reducing relay load.\n- **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.\n\n## Features\n\n### Self-Discovering Network\nDeploy a relay anywhere. It joins the global mesh via DHT automatically. No configuration. No coordination. It just works.\n\n### Cross-Relay Streaming\nStreams announced on Relay A are visible on Relay B. Every relay sees everything. Viewers watch from whichever node they connect to.\n\n### Chat\nReal-time WebSocket chat built into every stream. Messages travel through the relay alongside video. No accounts required -- your keypair is your identity.\n\n### Archives\nServer-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.\n\n### Embeddable Player\nEvery live stream and archived recording gets an iframe embed code. Drop it into any page. The player handles connection, quality adaptation, and playback automatically.\n\n### Block / Mute\nClient-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.\n\n### Mesh Forwarding\nHigh-bandwidth viewers can forward streams to other viewers, reducing relay load. Auto-enabled on WiFi with good battery.\n\n### Bandwidth Adaptation\nBroadcasters encode in multiple quality layers (simulcast). Viewers receive the layer matching their connection. Quality degrades gracefully, never drops entirely.\n\n### Distributed Amplification\nSupporters 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.\n\n### Map View\nSee 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.\n\n### Privacy by Design\n- **Ephemeral by default. Recording is opt-in.** When a stream ends, it's gone -- unless the broadcaster explicitly enabled archiving.\n- **No accounts.** Your identity is a keypair stored in your browser. Close the tab, it's gone.\n- **No tracking.** No analytics, no cookies, no persistent data on relays.\n- **Location control.** Choose exact, neighborhood, city, or region-level precision. Or share nothing.\n\n### Settings and Preferences\nUser preferences persist in localStorage:\n- **Theme**: System, Dark, or Light mode\n- **Auto-play**: Control whether streams play automatically\n- **Location**: Remember location permission and default precision\n- **Notifications**: Browser notifications for new streams\n- **Default View**: Start with Live Streams, Archives, or Map\n\n### Broadcast Setup\nBefore going live, configure your stream:\n- Set a title\n- Choose categories and add custom tags\n- Enable/disable location sharing and precision\n- Enable server-side recording (optional)\n\n### Mobile-First Design\n- Hamburger menu with full navigation on mobile\n- Category grid in a 2-column layout\n- Tap-to-toggle tooltips and responsive controls\n- Sidebar collapses to icons, expands on interaction\n\n## Amplify a Stream\n\nHelp spread the signal by restreaming to YouTube, Facebook, TikTok, or Instagram.\n\n### Desktop (OBS Studio)\n\n1. Watch a stream on oneye\n2. Click **Pop Out** to open a clean capture window\n3. In OBS, add a **Window Capture** source\n4. Select the oneye pop-out window\n5. Add your RTMP destination (YouTube/Twitch/etc)\n6. Click **Start Streaming**\n\n### Mobile (iOS/Android)\n\n**Prism Live Studio** (Free, multi-platform):\n1. Install from App Store / Play Store\n2. Enable screen recording\n3. Open oneye in browser, start watching\n4. In Prism, go live to your platforms\n\n**Streamlabs** (Alternative):\n1. Install Streamlabs mobile app\n2. Set up your streaming accounts\n3. Use screen broadcast feature\n4. Open oneye and watch the stream\n\n### Amplify Mode Features\n\n- **Pop Out** -- Clean, borderless window optimized for OBS capture\n- **Picture-in-Picture** -- Floats video over other windows\n- **Fullscreen** -- Native fullscreen for full display capture\n- **Quality Selector** -- Choose 720p/360p/180p based on your bandwidth\n- **Stream Info** -- Press **I** in amplify mode to toggle stream title overlay\n\n### Tips for Amplifiers\n\n- Use **720p** for best restream quality\n- Close other apps to reduce CPU load\n- Wired connection preferred (ethernet \u003e WiFi \u003e cellular)\n- Multiple amplifiers = wider reach. Coordinate.\n\n### Recommended Apps\n\n**Desktop:**\n- OBS Studio (Free, open source) -- Best for window capture\n- Streamlabs Desktop (Free) -- Simpler UI than OBS\n\n**Mobile:**\n- Prism Live Studio (Free) -- Multi-platform restreaming\n- Streamlabs Mobile (Free) -- Easy setup\n- Restream (Freemium) -- Web-based, multistreams\n\n## Network Discovery\n\nClients find relays through multiple methods, in priority order:\n\n1. **URL hash** -- `https://example.com/#relay=wss://relay.example.com`\n2. **Bootstrap** -- Fetches relay list from GitHub Pages ([`relays.json`](docs/relays.json)). Update this file when relays change.\n3. **Well-known** -- `/.well-known/oneye.json` on the current domain\n4. **DNS TXT** -- `_oneye.example.com` TXT records via DNS-over-HTTPS\n5. **Self** -- The URL you loaded becomes the relay\n\n## Deploy Your Own Relay\n\n### Local / LAN\n\n```bash\ngit clone https://github.com/msitarzewski/oneye.git\ncd oneye\nnpm install\nnode server.js\n# or: PORT=3333 node server.js\n```\n\nShare your LAN IP with others on the same network.\n\n### Public (with ngrok)\n\n```bash\nnode server.js \u0026\nngrok http 3000\n```\n\nShare the ngrok HTTPS URL. WebSocket connections upgrade to WSS automatically.\n\n### Production\n\n```bash\ncp config.json.example config.json\n# Edit config.json with your domain and settings, then:\nnode server.js\n```\n\nPut behind nginx or caddy for TLS termination. Set `publicUrl` in `config.json` so the relay announces its public address to the DHT.\n\n### TURN Server (Optional)\n\nWebRTC 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.\n\nWithout TURN, affected users will see stream thumbnails but video won't play.\n\n**Self-hosted with coturn:**\n\n```bash\n# Install coturn\nsudo apt install coturn   # Debian/Ubuntu\nbrew install coturn        # macOS\n\n# /etc/turnserver.conf\nlistening-port=3478\nmin-port=49152\nmax-port=50175\nrealm=relay.example.com\nuser=oneye:your-secret-here\nlt-cred-mech\nfingerprint\n```\n\nOpen firewall ports: UDP/TCP 3478 + UDP 49152-50175. TURN traffic goes direct to coturn, not through your reverse proxy.\n\n```bash\nTURN_URL=turn:relay.example.com:3478 \\\nTURN_USERNAME=oneye \\\nTURN_CREDENTIAL=your-secret-here \\\nPUBLIC_URL=wss://relay.example.com \\\nnode server.js\n```\n\nThe relay sends ICE config (including TURN) to clients on connect. Each relay in the network can run its own TURN server independently.\n\n**Other TURN providers:** Cloudflare Calls TURN (free), metered.ca (free tier), Twilio (paid). Set the env vars to match your provider's credentials.\n\n## API\n\n### WebSocket Messages\n\n**Client to Relay:**\n- `subscribe` -- Join the network, receive stream list\n- `announce` -- Start broadcasting a stream\n- `unannounce` -- Stop broadcasting\n- `view` -- Request to watch a stream\n- `signal_forward` -- WebRTC SDP offer (broadcaster)\n- `answer` -- WebRTC SDP answer (viewer)\n- `candidate` -- ICE candidate\n- `layer_request` -- Request specific quality layer (`h`/`m`/`l`) for simulcast\n- `bandwidth_report` -- Report estimated bandwidth for adaptive streaming\n- `chat` -- Send a chat message. Fields: `text` (string), `from` (sender pubkey)\n\n**Relay to Client:**\n- `ice_servers` -- ICE configuration (STUN + TURN if configured), sent on connect\n- `welcome` / `subscribed` -- Connection confirmed, includes stream and relay lists\n- `stream_available` -- New stream announced\n- `stream_gone` -- Stream ended\n- `signal` -- WebRTC SDP (offer to viewer, answer to broadcaster)\n- `candidate` -- ICE candidate\n- `chat` -- Relayed chat message with `text`, `from`, and `streamId`\n\n### HTTP Endpoints\n\n- `GET /` -- Serve the app\n- `GET /health` -- JSON status: `{ ok, streams, clients, relays }`\n- `GET /relays` -- List of known relays\n- `GET /.well-known/oneye.json` -- Relay discovery endpoint\n- `GET /archives` -- List available archived recordings\n- `POST /archives/:id/delete` -- Delete an archived recording\n- `GET /embed` -- Embeddable player page for streams and archives\n\n## License\n\nMIT -- See [LICENSE](LICENSE)\n\n### Bundled Libraries\n\nAll dependencies are inlined. No external CDN requests:\n\n- **QRCode.js** -- MIT License -- [github.com/davidshimjs/qrcodejs](https://github.com/davidshimjs/qrcodejs)\n- **werift-webrtc** -- MIT License -- [github.com/shinyoshiaki/werift-webrtc](https://github.com/shinyoshiaki/werift-webrtc)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsitarzewski%2Foneye","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsitarzewski%2Foneye","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsitarzewski%2Foneye/lists"}