Self-hosted RTMP ingest, streaming dashboard, and recording management
  • JavaScript 79%
  • CSS 11.6%
  • PowerShell 3.7%
  • XSLT 2.9%
  • HTML 2.5%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-27 08:19:10 +02:00
frontend Initial RTMP site import 2026-09-27 08:19:10 +02:00
nginx Initial RTMP site import 2026-09-27 08:19:10 +02:00
.gitattributes Initial RTMP site import 2026-09-27 08:19:10 +02:00
.gitignore Initial RTMP site import 2026-09-27 08:19:10 +02:00
README.md Initial RTMP site import 2026-09-27 08:19:10 +02:00
start-prod.bat Initial RTMP site import 2026-09-27 08:19:10 +02:00
start-prod.ps1 Initial RTMP site import 2026-09-27 08:19:10 +02:00
start.bat Initial RTMP site import 2026-09-27 08:19:10 +02:00
start.ps1 Initial RTMP site import 2026-09-27 08:19:10 +02:00
stop-prod.bat Initial RTMP site import 2026-09-27 08:19:10 +02:00
stop-prod.ps1 Initial RTMP site import 2026-09-27 08:19:10 +02:00
stop.bat Initial RTMP site import 2026-09-27 08:19:10 +02:00
stop.ps1 Initial RTMP site import 2026-09-27 08:19:10 +02:00

VRCDJ Live logo

VRCDJ Live

A self-hosted broadcast control room for live VR DJ events.

Publish from OBS over RTMP, manage operators from a React dashboard, and deliver low-latency HLS streams to VR worlds and browsers.

About the project

VRCDJ Live is a Windows-first streaming platform built to make running virtual DJ events less fragmented. It brings account management, per-operator stream keys, live status, browser previews, VR-ready playback links, set archives, and platform administration into one interface.

The project began as a PHP-based RTMP utility and has grown into a React and Node.js application backed by nginx-rtmp and FFmpeg. It is both a working self-hosted service and an exploration of the systems behind a small live-streaming platform: ingest authentication, media transcoding, session state, access control, and operational tooling.

Highlights

  • Operator dashboard with OBS connection details, masked stream keys, live health, and in-browser HLS preview
  • RTMP ingest authentication using per-user keys and nginx HTTP callbacks
  • Dual HLS output tuned separately for dashboard preview and VR world playback
  • Automatic set recording with downloadable archives and configurable retention
  • Account lifecycle including registration, invite-only access, email verification, password recovery, and session revocation
  • Admin control room for live-stream monitoring, user moderation, key rotation, invite credits, traffic metrics, and audit history
  • Flexible persistence with MySQL in production and a zero-setup JSON store for local development
  • Operational scripts for starting and stopping the complete development or production stack on Windows

How it works

flowchart LR
    OBS[OBS / RTMP encoder] -->|RTMP :1935| NGINX[nginx-rtmp]
    NGINX -->|publish callbacks| API[Node.js / Express API]
    API -->|authenticate and persist| STORE[(MySQL or local JSON)]
    API -->|start and supervise| FFMPEG[FFmpeg workers]
    FFMPEG -->|HLS manifests and segments| NGINX
    NGINX -->|HLS :8081| WORLD[VR world / media player]
    NGINX -->|HLS preview| UI[React dashboard]
    UI <-->|REST API :8787| API

When an operator starts broadcasting, nginx sends the stream key to the API. The API authenticates the publisher, records the live session, and launches FFmpeg workers that pull the local RTMP feed. FFmpeg creates short HLS playlists for the dashboard and VR playback; nginx serves those files while the React interface reports live state and stream health.

Technology

Layer Technology
Interface React 18, Vite, hls.js
API Node.js, Express
Authentication Token sessions, bcrypt password hashing
Persistence MySQL or local JSON
RTMP ingest nginx with the nginx-rtmp module
Media pipeline FFmpeg, H.264/AAC, HLS
Email Nodemailer with an SMTP provider
Operations PowerShell and Windows batch scripts

Project scope

This repository covers the full path from an authenticated RTMP publisher to an HLS consumer. It includes the operator-facing application, API, ingest callbacks, media-worker orchestration, local and database storage adapters, administrative workflows, and Windows service scripts.

It is designed for a single self-hosted installation rather than a multi-region streaming network. The current process-managed FFmpeg workers are practical for a small deployment, but larger installations should move media jobs into a dedicated supervisor or queue. Playback URLs are tokenized but are not yet protected by production-grade signed manifests. See Current limitations for the remaining hardening work.

Repository layout

.
├── frontend/
│   ├── public/              # Brand assets
│   ├── src/                 # React application and UI components
│   ├── server/              # Express API, storage, email, and media pipeline
│   └── scripts/             # Development runtime helpers
├── nginx/
│   ├── conf/                # RTMP ingest, callbacks, stats, and HLS serving
│   └── html/                # nginx status-page assets
├── ffmpeg/                  # Local FFmpeg installation (ignored by Git)
├── recordings/              # Generated set archives (ignored by Git)
├── start.ps1 / start.bat    # Development launcher
└── start-prod.ps1 / .bat    # Production launcher

Getting started

Prerequisites

The supplied launch scripts target Windows 10/11 or Windows Server. You will need:

  • Node.js 20 or newer with npm
  • FFmpeg for Windows, placed at ffmpeg/ffmpeg.exe
  • A Windows nginx build that includes the nginx-rtmp module, placed at nginx/nginx.exe
  • OBS Studio or another RTMP encoder for an end-to-end broadcast test
  • MySQL 8+ for production; local development can use the built-in JSON store
  • An NVIDIA GPU for the default NVENC configuration, or the software-encoding override described below

Third-party executables and generated media are intentionally excluded from Git. A sanitized nginx configuration template, helper scripts, and web assets remain in the repository; the local nginx.exe binary and deployment configuration need to be supplied.

1. Install the application

git clone <your-repository-url>
Set-Location "rtmp-site\frontend"
npm install

2. Create a local environment file

Copy-Item .env.example .env

For a minimal local setup, update these values in frontend/.env:

APP_ENV=development
PUBLIC_DOMAIN=127.0.0.1
RTMP_HOST=127.0.0.1
CALLBACK_BASE_URL=http://127.0.0.1:5173
DB_ENABLED=false
EMAIL_VERIFICATION_REQUIRED=false
INGEST_WEBHOOK_SECRET=replace-with-a-long-random-value

The default media profile uses NVIDIA NVENC. On a machine without a compatible NVIDIA GPU, add:

PREVIEW_VIDEO_ENCODER=libx264
WORLD_VIDEO_ENCODER=libx264
PREVIEW_PRESET=ultrafast
WORLD_PRESET=ultrafast

Never commit frontend/.env; it may contain database, SMTP, and ingest credentials.

3. Configure the ingest callbacks

Create your local nginx configuration from the sanitized template:

Copy-Item ..\nginx\conf\nginx.conf.example ..\nginx\conf\nginx.conf

Open nginx/conf/nginx.conf and make the secret query parameter on all three callback URLs match INGEST_WEBHOOK_SECRET:

  • /api/ingest/publish-start
  • /api/ingest/publish-stop
  • /api/ingest/heartbeat

Use a new secret for every deployment. Do not publish a production secret in the nginx configuration of a public repository.

4. Start the development stack

From the repository root:

.\start.ps1

You can also double-click start.bat. The launcher starts Vite, the Express API, and nginx.

Service Local address
Web application http://127.0.0.1:5173
API health check http://127.0.0.1:8787/api/health
nginx RTMP stats http://127.0.0.1:8081/stat
RTMP ingest rtmp://127.0.0.1:1935/live

Stop the stack with:

.\stop.ps1

5. Test a broadcast with OBS

  1. Register an operator account in the web application and sign in.
  2. Copy the OBS server address and stream key shown in the dashboard.
  3. In OBS, open Settings → Stream, choose Custom, and paste both values.
  4. Start streaming. The dashboard should move to a live state after the nginx callback is accepted.
  5. Verify the HLS preview in the dashboard and the world playback URL shown for the session.

If the API sees the stream but no preview appears, check that FFmpeg can use the configured encoder and that ports 1935 and 8081 are available.

Production deployment

Production mode requires MySQL; the JSON store is intentionally disabled when APP_ENV=production.

  1. Copy frontend/.env.example to frontend/.env and provide production domain, database, SMTP, and ingest-secret values.
  2. Set APP_ENV=production, DB_ENABLED=true, and configure DB_HOST, DB_NAME, DB_USER, and DB_PASSWORD.
  3. Set the same ingest secret in the nginx callback URLs.
  4. Configure a public reverse proxy with TLS:
    • / → http://127.0.0.1:5173
    • /api → http://127.0.0.1:8787/api
    • /hls → http://127.0.0.1:8081/hls
  5. Run .\start-prod.ps1 from the repository root.

The production launcher builds the Vite bundle, starts the API and static frontend server in the background, starts nginx, records PIDs under .runtime, and performs basic health checks. Stop all managed services with .\stop-prod.ps1.

Keep ports 5173, 8787, and 8081 bound to loopback or protected by a firewall. Only the TLS reverse proxy and RTMP ingest port should be exposed intentionally.

Configuration

The complete set of documented variables lives in frontend/.env.example. The most important groups are:

Group Examples Purpose
Application APP_ENV, PUBLIC_DOMAIN, PORT Runtime mode, public host, and API binding
Ingest RTMP_HOST, RTMP_APP, INGEST_WEBHOOK_SECRET OBS endpoint and callback authentication
Media FFMPEG_PATH, HLS_OUTPUT_ROOT, PREVIEW_*, WORLD_* Transcoding, rendition quality, and HLS output
Storage DB_ENABLED, DB_HOST, DB_NAME, DB_USER MySQL connection or local JSON selection
Email SMTP_*, EMAIL_* Verification and password-reset delivery
Security TRUST_PROXY, API_SESSION_TTL_HOURS, RATE_LIMIT_* Proxy handling, session lifetime, and abuse limits

Security notes

  • Rotate any credential that has previously appeared in source control; adding it to .gitignore does not remove it from Git history.
  • Use a long, unique INGEST_WEBHOOK_SECRET and keep the API bound to loopback behind a trusted reverse proxy.
  • Treat frontend/server/data/state.json as sensitive runtime data. It can contain users, hashed credentials, sessions, stream keys, and audit records and is ignored by the root .gitignore.
  • Generated HLS segments and recordings may contain private event footage. They are also ignored and should be protected with suitable filesystem access and retention policies.
  • Terminate public HTTP traffic with TLS and restrict the nginx statistics endpoint in an internet-facing deployment.

Current limitations

  • FFmpeg workers run inside the API process; after a complete API restart, an active publisher needs the next nginx heartbeat to restore its media workers.
  • Playback tokens are application-managed identifiers, not cryptographically signed HLS manifests.
  • The operational scripts and bundled nginx configuration are Windows-specific.
  • The system is designed for a small self-hosted event platform and has not been built as a horizontally scaled CDN.
  • Automated test coverage and external service supervision are still future work.

Further documentation