{"id":51455881,"url":"https://github.com/hyprlab/viibestream","last_synced_at":"2026-07-06T01:00:16.691Z","repository":{"id":365224653,"uuid":"1271076986","full_name":"hyprlab/viibestream","owner":"hyprlab","description":"A self-hosted live video streaming web app — capture your camera and mic or share a video file in the browser and broadcast straight to your viewers over WebSocket. Built with Claude Code.","archived":false,"fork":false,"pushed_at":"2026-07-04T14:00:20.000Z","size":10801,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T15:27:49.584Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://viibeware.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyprlab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":null,"dco":null,"cla":null}},"created_at":"2026-06-16T10:05:41.000Z","updated_at":"2026-07-04T14:00:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hyprlab/viibestream","commit_stats":null,"previous_names":["viibeware/viibestream","hyprlab/viibestream"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/hyprlab/viibestream","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Fviibestream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Fviibestream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Fviibestream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Fviibestream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyprlab","download_url":"https://codeload.github.com/hyprlab/viibestream/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyprlab%2Fviibestream/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35174071,"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-07-05T02:00:06.290Z","response_time":100,"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":[],"created_at":"2026-07-06T01:00:15.742Z","updated_at":"2026-07-06T01:00:16.664Z","avatar_url":"https://github.com/hyprlab.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Viibestream\n\nA self-hosted, browser-native live video streaming app. Sign in to the\nadmin, point your browser camera/mic at it (or share a video file), and\nanyone who hits the public page sees you live — no plugins, no native\napps, no third-party services in the path. Just browser capture +\nMediaSource playback over Socket.IO.\n\n\u003e **License:** Viibestream is free software released under the\n\u003e [GNU Affero General Public License v3.0](#license) (AGPLv3).\n\n## Contents\n\n- [Features](#features)\n- [How it works](#how-it-works)\n- [Requirements](#requirements)\n- [Install with the pre-built image](#install-with-the-pre-built-image)\n  - [1. Create the Compose file](#1-create-the-compose-file)\n  - [2. Create your `.env`](#2-create-your-env)\n  - [3. Choose an HTTPS strategy](#3-choose-an-https-strategy)\n  - [4. Pull and run](#4-pull-and-run)\n  - [5. First sign-in](#5-first-sign-in)\n- [Build from source instead](#build-from-source-instead)\n- [Configuration reference (`.env`)](#configuration-reference-env)\n- [Production behind a reverse proxy](#production-behind-a-reverse-proxy)\n- [Operating the container](#operating-the-container)\n- [Updating](#updating)\n- [Release notes \u0026 changelog](#release-notes--changelog)\n- [Development (without Docker)](#development-without-docker)\n- [Tech](#tech)\n- [License](#license)\n\n## Features\n\n### Watching\n\n- **Public viewer at `/`** — autoplay (muted by default), mute/unmute,\n  volume, fullscreen, and a live viewer count. No account needed to\n  watch.\n- **\"Now Showing\" panel** — the broadcaster can label the stream with a\n  title, description, IMDB link, and a poster image; viewers open it\n  from the player and it updates live as the host changes it.\n- **Light \u0026 dark theme** — a polished, responsive interface that\n  remembers your theme choice, with no flash on load.\n\n### Live chat\n\n- **Real-time chat panel** alongside the stream — slides in from the\n  edge, with a live participant count and a message history for\n  late-joiners.\n- **Pick a name and emoji avatar** — viewers join with a display name\n  and a fun emoji from a curated palette; profiles are editable on the\n  fly.\n- **Replies, @-mentions, and emoji reactions** — reply to a specific\n  message, mention other participants from an autocomplete menu, and\n  react to messages with emoji. Pin the panel open or mute it as you\n  like.\n- **Join / leave announcements** with a short grace period so quick\n  reconnects don't spam the room.\n\n### Voice talk-back\n\n- **Viewers can talk back** — joined viewers can speak so the whole room\n  hears them. The mic is captured, run through voice-activity detection,\n  downsampled, and streamed over the same Socket.IO connection; every\n  page mixes all speakers through one Web Audio context, so several\n  people can talk at once.\n\n### Broadcasting \u0026 moderation\n\n- **Broadcast from the browser** — capture camera + mic, or share a\n  video file, and go live in one click. No plugins or native apps.\n- **Broadcaster console at `/admin/stream`** — go live, edit the \"Now\n  Showing\" metadata, and watch the chat in real time.\n- **Live participants panel** — a roster of everyone in the chat with\n  per-person mute/unmute, a \"mute all\" control, and a mic indicator\n  that highlights whoever is speaking. Disruptive viewers can be banned\n  by IP.\n\n### Administration \u0026 security\n\n- **Admin dashboard at `/admin`** — login-gated, with a fixed-header /\n  scrollable-middle / fixed-footer sidebar and a settings modal (Profile\n  / Users / Security / About) over a blurred backdrop.\n- **Roles \u0026 permissions** — admin / streamer / viewer with a tight\n  capability map. Admins manage users from Settings → Users; streamers\n  can go live.\n- **Secure by default** — bcrypt passwords, CSRF on all forms, hardened\n  session cookies, per-request CSP nonces, account-lockout on brute\n  force, login rate limiting, and an optional Cloudflare Turnstile\n  captcha. ProxyFix support for HTTPS-terminating reverse proxies.\n- **Docker-first** — runs as a single container; image published on\n  [Docker Hub](https://hub.docker.com/r/hyprlab/viibestream).\n\n## How it works\n\nThe admin's browser captures camera + mic via `getUserMedia`, encodes\nWebM with `MediaRecorder`, and ships ~250 ms chunks over a Socket.IO\nWebSocket to the server. The server caches the first chunk (the WebM\ninit segment) and fans every chunk out to the `viewers` room. Each\nviewer's browser uses MediaSource Extensions to append chunks to a\n`SourceBuffer`, so late-joiners receive the cached init segment first\nand join the stream mid-flight. See [`CLAUDE.md`](CLAUDE.md) for the\nfull architecture map.\n\n## Requirements\n\n- **Docker Engine 20.10+** and the **Docker Compose v2** plugin\n  (`docker compose`, not the legacy `docker-compose`). Check with:\n  ```bash\n  docker --version\n  docker compose version\n  ```\n- A modern browser for broadcasting. Browsers only expose\n  `getUserMedia` (camera/mic) on `http://localhost` or over **HTTPS** —\n  see [step 3](#3-choose-an-https-strategy).\n\n## Install with the pre-built image\n\nThe fastest path: run the published image from\n[`hyprlab/viibestream`](https://hub.docker.com/r/hyprlab/viibestream)\n— no clone, no build. You just need an empty folder for your Compose\nfile and your `.env`.\n\n```bash\nmkdir viibestream \u0026\u0026 cd viibestream\n```\n\n### 1. Create the Compose file\n\nCreate `docker-compose.yml` in that folder, pointing at the published\nimage:\n\n```yaml\n# docker-compose.yml\nservices:\n  viibestream:\n    image: hyprlab/viibestream:latest   # or pin a version, e.g. :0.2.4\n    container_name: viibestream\n    restart: unless-stopped\n    env_file:\n      - .env\n    environment:\n      - FLASK_ENV=production\n    ports:\n      # host PORT → container INTERNAL_PORT (8000 plain / 8443 with TLS)\n      - \"0.0.0.0:${PORT:-8080}:${INTERNAL_PORT:-8000}\"\n    volumes:\n      - viibestream_data:/app/instance   # SQLite DB persists here\n    security_opt:\n      - no-new-privileges:true\n    tmpfs:\n      - /tmp\n    cap_drop:\n      - ALL\n    cap_add:\n      - CHOWN\n      - SETGID\n      - SETUID\n\nvolumes:\n  viibestream_data:\n```\n\n### 2. Create your `.env`\n\nAll configuration lives in a `.env` file next to `docker-compose.yml`.\nGrab the annotated template from the repo and copy it to `.env`:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/hyprlab/viibestream/main/.env.example -o .env\n```\n\n`.env` holds secrets and must **never** be committed to git. Open it in\nyour editor and set the values below.\n\n**Generate a `SECRET_KEY`.** It signs session cookies and CSRF tokens\nand must be a long, random, secret string:\n\n```bash\npython3 -c \"import secrets; print(secrets.token_urlsafe(64))\"\n# no Python on the host? use OpenSSL:\nopenssl rand -base64 64 | tr -d '\\n'; echo\n```\n\nPaste the output into `.env`:\n\n```env\nSECRET_KEY=\u003cpaste the long random string here\u003e\n```\n\n\u003e Treat `SECRET_KEY` like a password. Changing it later invalidates all\n\u003e existing sessions (everyone is logged out) — which is exactly what you\n\u003e want if it ever leaks.\n\n**Set the bootstrap admin password.** On first boot, if no admin exists\nyet, Viibestream seeds one from these values:\n\n```env\nINITIAL_ADMIN_USERNAME=admin\nINITIAL_ADMIN_EMAIL=admin@example.com\nINITIAL_ADMIN_PASSWORD=\u003ca strong one-time password\u003e\n```\n\n`INITIAL_ADMIN_PASSWORD` is used only to create that first account.\nAfter you sign in and change your password (Settings → Profile), blank\nthe line so the bootstrap can't run again:\n\n```env\nINITIAL_ADMIN_PASSWORD=\n```\n\n### 3. Choose an HTTPS strategy\n\nBrowsers refuse to expose camera/microphone capture on any non-localhost\nHTTP origin, so pick **one** strategy in `.env`:\n\n| Strategy | When | `.env` |\n|---|---|---|\n| **A — Reverse proxy** (recommended for real domains) | Caddy / nginx / Traefik / Cloudflare terminates TLS in front of the container | `BEHIND_HTTPS_PROXY=1`, `TLS_ENABLE=0` |\n| **B — In-container self-signed TLS** | LAN / quick demos; the container serves HTTPS on `:8443` and browsers warn once per device | `BEHIND_HTTPS_PROXY=0`, `TLS_ENABLE=1`, `TLS_HOSTS=localhost,127.0.0.1,\u003cyour-LAN-IP\u003e`, `PORT=8443` |\n| **C — HTTP on localhost** | single-machine dev only | `BEHIND_HTTPS_PROXY=0`, `TLS_ENABLE=0` |\n\nAlso set `PUBLIC_ORIGIN` to every origin a browser will load the page\nfrom (comma-separated) — Socket.IO rejects WebSocket handshakes from\norigins not in this list. For a public domain include the `https://`\nform, e.g. `PUBLIC_ORIGIN=https://stream.example.com`.\n\n### 4. Pull and run\n\n```bash\ndocker compose pull\ndocker compose up -d\n```\n\nBy default the service publishes host port **`8080`** (override with\n`PORT` in `.env`). Watch the logs and health:\n\n```bash\ndocker compose logs -f          # follow startup logs (Ctrl-C to stop)\ncurl -fsS http://localhost:8080/healthz   # -\u003e {\"ok\": true, \"version\": \"...\"}\n```\n\n### 5. First sign-in\n\n1. Open **http://localhost:8080** — the public viewer (no stream yet).\n2. Go to **http://localhost:8080/auth/login** and sign in with\n   `INITIAL_ADMIN_USERNAME` / `INITIAL_ADMIN_PASSWORD`.\n3. Change your password under **Settings → Profile**, then blank\n   `INITIAL_ADMIN_PASSWORD` in `.env` (see [step 2](#2-create-your-env)).\n4. Open **http://localhost:8080/admin/stream** and go live.\n\n## Build from source instead\n\nPrefer to build the image yourself? Clone the repo — it ships its own\n`docker-compose.yml` with `build: .`:\n\n```bash\ngit clone https://github.com/hyprlab/viibestream.git\ncd viibestream\ncp .env.example .env            # then edit as in step 2 above\ndocker compose up --build -d\n```\n\nThe `.env` setup is identical to\n[step 2](#2-create-your-env) and [step 3](#3-choose-an-https-strategy)\nabove; the only difference is `docker compose up --build` compiles the\nimage locally instead of pulling it.\n\n## Configuration reference (`.env`)\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `SECRET_KEY` | — (**required**) | Signs session cookies and CSRF tokens. Long random string. |\n| `INITIAL_ADMIN_USERNAME` | `admin` | Username for the bootstrap admin (first boot only). |\n| `INITIAL_ADMIN_EMAIL` | `admin@example.com` | Email for the bootstrap admin. |\n| `INITIAL_ADMIN_PASSWORD` | — | One-time bootstrap password. Blank it after first sign-in. |\n| `BEHIND_HTTPS_PROXY` | `0` | `1` enables HSTS, `Secure` cookies, and trusts one hop of `X-Forwarded-*`. |\n| `TLS_ENABLE` | `0` | `1` makes the container serve self-signed HTTPS on `:8443`. |\n| `TLS_HOSTS` | `localhost,127.0.0.1` | SANs baked into the self-signed cert when `TLS_ENABLE=1`. |\n| `PUBLIC_ORIGIN` | `http://localhost:8080` | Allowed browser origin(s) for the Socket.IO CORS check (comma-separated). |\n| `PORT` | `8080` | Host port published by Docker Compose. |\n| `INTERNAL_PORT` | `8000` | Container listen port (`8000` plain / `8443` with TLS). |\n| `DATABASE_URL` | `sqlite:////app/instance/viibestream.db` | SQLAlchemy database URL (SQLite on the `instance` volume by default). |\n| `TURNSTILE_SITE_KEY` | — | Optional: seeds the Cloudflare Turnstile site key on first boot. |\n| `TURNSTILE_SECRET_KEY` | — | Optional: seeds the Turnstile secret key. Manage from Settings → Security after. |\n\n## Production behind a reverse proxy\n\nFor a real domain, terminate TLS at a reverse proxy and forward plain\nHTTP to the container. In `.env`:\n\n```env\nSECRET_KEY=...long random token...\nPUBLIC_ORIGIN=https://stream.example.com\nBEHIND_HTTPS_PROXY=1\nTLS_ENABLE=0\n```\n\nYour proxy must:\n\n- Send `X-Forwarded-Proto: https` and `X-Forwarded-For` (ProxyFix\n  expects exactly **1** hop).\n- Proxy WebSocket upgrades for Socket.IO (`/socket.io/*`).\n- Forward `Host` so cookie domains match.\n\nExample nginx fragment:\n\n```nginx\nlocation / {\n    proxy_pass         http://viibestream:8000;\n    proxy_http_version 1.1;\n    proxy_set_header   Upgrade $http_upgrade;\n    proxy_set_header   Connection \"upgrade\";\n    proxy_set_header   Host $host;\n    proxy_set_header   X-Real-IP $remote_addr;\n    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header   X-Forwarded-Proto $scheme;\n    proxy_read_timeout 3600s;\n}\n```\n\nCaddy needs almost no config — it terminates TLS and proxies WebSockets\nautomatically:\n\n```caddy\nstream.example.com {\n    reverse_proxy viibestream:8000\n}\n```\n\n## Operating the container\n\n```bash\ndocker compose ps                 # status\ndocker compose logs -f            # follow logs\ndocker compose restart            # restart the service\ndocker compose down               # stop and remove the container\ndocker compose down -v            # ALSO delete the data volume (wipes the DB!)\n```\n\nThe SQLite database lives in the named Docker volume `viibestream_data`\n(mounted at `/app/instance`), so it **survives rebuilds and restarts**.\nOnly `docker compose down -v` removes it.\n\n## Updating\n\n**Pre-built image:**\n\n```bash\ndocker compose pull\ndocker compose up -d\n```\n\n**Built from source:**\n\n```bash\ngit pull\ndocker compose up --build -d\n```\n\nEither way, your `.env` and the `viibestream_data` volume are preserved.\n\n## Release notes \u0026 changelog\n\nBoth live at the repo root and are the **single source of truth**:\n\n- [`RELEASE_NOTES.md`](RELEASE_NOTES.md) — user-friendly summary of each version.\n- [`CHANGELOG.md`](CHANGELOG.md) — the full implementation log.\n\nThese same files render in-app under **Settings → About** (release notes\nexpanded, changelog collapsed). Editing the Markdown is the only step\nneeded to update both the docs and the in-app view — see\n[`app/about_docs.py`](app/about_docs.py).\n\n## Development (without Docker)\n\n```bash\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -r requirements.txt\nexport FLASK_ENV=development SECRET_KEY=dev INITIAL_ADMIN_PASSWORD=devpassword123\npython run.py            # http://localhost:8000\n```\n\n## Tech\n\nFlask 3, Flask-SocketIO (eventlet), Flask-Login, Flask-WTF,\nFlask-Limiter, Flask-Migrate, SQLAlchemy + SQLite, bcrypt, Markdown,\nvanilla JS + MediaRecorder + MediaSource Extensions, Gunicorn, Docker\nCompose.\n\n## License\n\nCopyright (C) 2026 Hyprlab.\n\nViibestream is free software: you can redistribute it and/or modify it\nunder the terms of the **GNU Affero General Public License** as published\nby the Free Software Foundation, either version 3 of the License, or (at\nyour option) any later version.\n\nThis program is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero\nGeneral Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with this program. If not, see \u003chttps://www.gnu.org/licenses/\u003e.\n\nThe full license text is in [`LICENSE`](LICENSE). As an AGPLv3 work, if\nyou run a modified version of Viibestream as a network service, you must\nmake the modified source available to its users.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Fviibestream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyprlab%2Fviibestream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyprlab%2Fviibestream/lists"}