ryan.mcgeeandClaude Sonnet 4.6 c056b9ed3b
Build and Push Container / build (push) Failing after 13m55s
fix: destructure setSearchParams in Watch.tsx to fix TypeScript build error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:13:07 -05:00
2026-03-31 08:19:37 -05:00
2026-03-31 12:57:57 -05:00
2026-01-08 15:15:03 +00:00

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

  1. Go to /admin/providers (admin login required)
  2. Add your identity provider (Entra ID, Google Workspace, or any OIDC-compliant IdP)
  3. Register the callback URL shown in the UI: https://yourserver/api/auth/callback/<provider-id>
  4. Users are provisioned automatically on first OIDC login (JIT provisioning) with the viewer role 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

  1. Go to /admin/users
  2. Create a user and set a password directly — no OIDC provider needed
  3. Users log in at /login with 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).

  1. In the channel settings, create a stream and note its Stream Key
  2. Open OBS → Settings → Stream → Service: Custom...
  3. Server: srt://yourserver:7000
  4. Stream Key: publish:<your-stream-key> (OBS sends this as the SRT stream ID)
  5. 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 (~26 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)

  1. In .env, set HWACCEL=vaapi
  2. In compose.prod.yml, uncomment the /dev/dri:/dev/dri devices block under the app service
  3. Rebuild: docker compose -f compose.prod.yml up --build

Enabling NVENC (NVIDIA)

  1. Install nvidia-container-toolkit on the host
  2. In .env, set HWACCEL=nvenc
  3. In compose.prod.yml, uncomment the NVIDIA devices block and add runtime: nvidia to the app service
  4. 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
S
Description
No description provided
Readme GPL-3.0
752 KiB
Languages
TypeScript 97.6%
Shell 1.1%
Dockerfile 0.8%
CSS 0.3%