- JavaScript 79%
- CSS 11.6%
- PowerShell 3.7%
- XSLT 2.9%
- HTML 2.5%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| frontend | ||
| nginx | ||
| .gitattributes | ||
| .gitignore | ||
| README.md | ||
| start-prod.bat | ||
| start-prod.ps1 | ||
| start.bat | ||
| start.ps1 | ||
| stop-prod.bat | ||
| stop-prod.ps1 | ||
| stop.bat | ||
| stop.ps1 | ||
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 |
| 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
- Register an operator account in the web application and sign in.
- Copy the OBS server address and stream key shown in the dashboard.
- In OBS, open Settings → Stream, choose Custom, and paste both values.
- Start streaming. The dashboard should move to a live state after the nginx callback is accepted.
- 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.
- Copy
frontend/.env.exampletofrontend/.envand provide production domain, database, SMTP, and ingest-secret values. - Set
APP_ENV=production,DB_ENABLED=true, and configureDB_HOST,DB_NAME,DB_USER, andDB_PASSWORD. - Set the same ingest secret in the nginx callback URLs.
- 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
- Run
.\start-prod.ps1from 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 |
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
.gitignoredoes not remove it from Git history. - Use a long, unique
INGEST_WEBHOOK_SECRETand keep the API bound to loopback behind a trusted reverse proxy. - Treat
frontend/server/data/state.jsonas 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
frontend/INGEST_SETUP.md— callback contract and ingest behaviorfrontend/MIGRATION.md— background on the PHP-to-React migrationnginx/README.md— details of the nginx-rtmp bundle and HLS serving