8.6 KiB
Announcements
Live streaming platform for librarians and teachers to host morning announcements. Hosts log in via OIDC or local accounts; viewers watch publicly without accounts.
Stack
- Backend: Node.js + Express + TypeScript
- Frontend: React + Vite + TailwindCSS
- Database: PostgreSQL (production) or SQLite (development/simple deployments)
- Session store: Valkey (Redis-compatible)
- Streaming: mediamtx — SRT ingest on port 7000, HLS delivery
- Auth: Multi-provider OIDC (openid-client v5) + optional local username/password
- Container: Docker
Quick Start (Production)
# 1. Copy and fill in environment variables
cp .env.example .env
# Edit .env — at minimum set BASE_URL and SESSION_SECRET
# 2. Build and run (PostgreSQL stack)
docker compose -f compose.prod.yml up --build
# App: http://localhost:3000
# SRT ingest: srt://yourserver:7000?streamid=publish:<stream_key>
Development
docker compose -f compose.dev.yml up --build
# Server API: http://localhost:3000
# Vite dev server: http://localhost:5173
# SRT ingest: srt://localhost:7000?streamid=publish:<stream_key>
Ports
| Port | Protocol | Purpose |
|---|---|---|
| 3000 | TCP/HTTP | Web app + API |
| 7000 | UDP/SRT | SRT ingest (OBS → server) |
In production, put a TLS-terminating reverse proxy (Caddy, nginx) in front of port 3000. Keep port 7000/UDP open directly — SRT does not work through a standard HTTP reverse proxy.
First-Time Setup
When the database is empty, navigating to the app redirects to /setup. Fill in an email, display name, and password to create the initial admin account, then log in with those credentials.
After the first admin is created, all user management (including creating additional local or OIDC-provisioned accounts) happens in the admin UI at /admin/users.
OIDC Setup
- Go to
/admin/providers(admin login required) - Add your identity provider (Entra ID, Google Workspace, or any OIDC-compliant IdP)
- Register the callback URL shown in the UI:
https://yourserver/api/auth/callback/<provider-id> - Users are provisioned automatically on first OIDC login (JIT provisioning) with the
viewerrole by default
Role Mappings
Providers can automatically assign roles based on claims in the OIDC token (e.g. groups, roles, or any custom claim). Configure mappings in the provider's edit form:
| Claim | Value | Role |
|---|---|---|
groups |
Admins |
admin |
groups |
Teachers |
host |
- Claim values can be a string or an array (e.g. Azure AD
groups). - The highest-priority role (
admin>host>viewer) wins when multiple mappings match. - Roles are synced on every login — the IdP is the source of truth when mappings are configured.
- If no mapping matches, JIT-provisioned users default to
viewer.
Local Account Setup
- Go to
/admin/users - Create a user and set a password directly — no OIDC provider needed
- Users log in at
/loginwith email + password
Channels
Channels group streams together (e.g. by school, grade, or subject). Admins manage channels and their memberships at /channels. Hosts can only create streams inside channels they belong to. Viewers browse channels publicly at /channels.
Within a channel page:
- Live Now — streams currently broadcasting
- Starting Soon — scheduled streams whose window opens within the next 10 minutes
- Streams — pre-created streams waiting to be broadcast
- Past Streams — completed sessions that have recordings
The public Live Now page (/) also shows a Starting Soon section for any streams across all channels starting within 10 minutes.
Scheduling
Streams can be restricted to a schedule so the stream key only works during defined windows. Schedules are configured per-stream from the channel admin view.
| Type | Description |
|---|---|
once |
A single date + time window |
daily |
Repeats every day at the same time |
weekly |
Repeats on a chosen weekday |
hourly |
Repeats every hour at a fixed minute |
All schedule times are stored in UTC and displayed to the user in their local timezone.
Early start window: hosts and admins may start publishing up to 10 minutes before the scheduled window opens so they can prepare. The stream appears live to admins and hosts during this window but remains hidden from viewers until the scheduled time begins.
Recording trim: footage recorded before the scheduled window opens is automatically trimmed from the saved recording so viewers only see content from the actual start time.
When a scheduled stream goes offline, it shows the next upcoming start time on the stream card and watch page, and the watch page refreshes automatically when the stream goes live.
OBS / Streaming Software Setup
Announcements uses SRT ingest (not RTMP).
- In the channel settings, create a stream and note its Stream Key
- Open OBS → Settings → Stream → Service: Custom...
- Server:
srt://yourserver:7000 - Stream Key:
publish:<your-stream-key>(OBS sends this as the SRT stream ID) - Click OK, then Start Streaming
The stream's broadcast page (/dashboard/channels/<channel>/streams/<stream>) shows the stream key and full OBS instructions. Stream keys can be regenerated at any time without changing the public viewer URL.
Viewers watch at: https://yourserver/channels/<channel-slug>
Recordings
When a stream ends, mediamtx automatically saves the session as an MP4 file. Each live session on a stream produces a separate recording. Recordings appear on the channel page under Past Streams.
- Watch: available to all visitors (no login required)
- Download MP4: admin only
When a broadcaster stops, viewers play through the remaining buffered segments (~2–6 seconds) before the stream is marked ended, avoiding an abrupt mid-sentence cut.
S3 Storage (optional)
By default, recordings are saved to disk inside the container (app-recordings volume). To store recordings in S3-compatible object storage instead, set these environment variables:
S3_ENDPOINT=https://s3.example.com # omit for AWS S3
S3_REGION=us-east-1
S3_BUCKET=my-recordings-bucket
S3_ACCESS_KEY=your-access-key-id
S3_SECRET_KEY=your-secret-access-key
Works with AWS S3, Cloudflare R2, MinIO, Garage, and other S3-compatible services. Local files are automatically deleted after a successful upload.
Hardware-Accelerated Recording (optional)
By default, mediamtx records streams using its built-in remuxer (very CPU-efficient — no re-encoding). Setting HWACCEL switches to an FFmpeg-based recording pipeline that encodes using a GPU, which is useful for transcoding incoming streams or reducing CPU load on high-bitrate sources.
| Value | Hardware | Requirement |
|---|---|---|
vaapi |
Intel / AMD iGPU or dGPU | /dev/dri device passthrough |
nvenc |
NVIDIA GPU | nvidia-container-toolkit + runtime: nvidia |
qsv |
Intel QuickSync | /dev/dri device passthrough |
Enabling VAAPI (Intel/AMD)
- In
.env, setHWACCEL=vaapi - In
compose.prod.yml, uncomment the/dev/dri:/dev/dridevices block under theappservice - Rebuild:
docker compose -f compose.prod.yml up --build
Enabling NVENC (NVIDIA)
- Install nvidia-container-toolkit on the host
- In
.env, setHWACCEL=nvenc - In
compose.prod.yml, uncomment the NVIDIA devices block and addruntime: nvidiato theappservice - Rebuild
Environment Variables
| Variable | Default | Description |
|---|---|---|
BASE_URL |
— | Public URL of the app (required) |
SESSION_SECRET |
— | Random secret ≥ 16 chars (required) |
DB_DRIVER |
sqlite |
sqlite or postgresql |
DATABASE_PATH |
/app/data/announcements.db |
SQLite file path |
DATABASE_URL |
— | PostgreSQL connection string (required when DB_DRIVER=postgresql) |
REDIS_URL |
redis://localhost:6379 |
Valkey/Redis connection URL |
HLS_OUTPUT_PATH |
— | Directory for HLS segments (required) |
RECORDINGS_PATH |
/app/recordings |
Directory for local recording storage |
HLS_RETENTION_MS |
3600000 |
How long to keep HLS segments after a stream ends (ms) |
HWACCEL |
none |
Hardware-accelerated recording: vaapi, nvenc, or qsv |
S3_ENDPOINT |
— | S3-compatible endpoint URL (omit for AWS) |
S3_REGION |
us-east-1 |
S3 region |
S3_BUCKET |
— | S3 bucket name |
S3_ACCESS_KEY |
— | S3 access key ID |
S3_SECRET_KEY |
— | S3 secret access key |