Files
2026-09-27 15:55:38 +08:00

4.8 KiB

NGINX HLS VOD

Docker Compose deployment for local MP4 to HLS VOD packaging using Kaltura nginx-vod-module. The service maps a media tree read-only into the container and preserves its hierarchy in HLS URLs.

Scope

  • ARM64 Linux Docker host
  • Trusted LAN HTTP service on port 8124
  • One H.264 video track and one AAC audio track per MP4
  • One configured browser origin for CORS
  • No authentication, TLS, ABR, external subtitles, or multiple audio/video tracks

An attached-picture video stream such as MP4 cover art is ignored by the media validator.

Configuration

Copy the environment template and replace the example player origin:

cp .env.example .env

Required values:

PLAYER_ORIGIN=http://192.168.1.20:3000
MEDIA_DIR=/srv/media
CATALOG_CACHE_DIR=/srv/nginx-hls/catalog-cache
HLS_PORT=8124

MEDIA_DIR=./data is appropriate only for local development. Production uses an absolute path on the Docker host. The container user must be able to read the mounted files and directories. CATALOG_CACHE_DIR stores catalog snapshots and generated covers. Create it before startup, for example install -d -m 0700 /srv/nginx-hls/catalog-cache. CATALOG_REFRESH_INTERVAL_SECONDS defaults to 21600 (6 hours).

The Dockerfile deliberately fixes NGINX 1.30.5 and Kaltura module commit 26f06877b0f2a2336e59cda93a3de18d7b23a3e2. Change either only as a deliberate upgrade, then rebuild and run the smoke test.

Run

Start the stack after creating the configured cache directory:

set -a
. ./.env
set +a
install -d -m 0700 "$CATALOG_CACHE_DIR"
docker compose --env-file .env up -d --build
docker compose ps

The catalog validates files during its background refresh and omits invalid MP4s from the published snapshot. Use ./scripts/validate-media.sh "$MEDIA_DIR" before publishing only when an update must contain no invalid files at all.

For a media file at:

/srv/media/movies/example.mp4

request this HLS master playlist:

http://<vod-host>:8124/hls/movies/example.mp4/master.m3u8

Path segments must be URL encoded by clients. There is intentionally no directory listing or progressive-download route.

Media Catalog

Clients discover validated MP4 files through the same origin:

GET /api/media

The response is a flat array from the latest completed catalog snapshot:

[
  {
    "path": "movies/example.mp4",
    "name": "example",
    "playlistUrl": "/hls/movies/example.mp4/master.m3u8",
    "coverUrl": "/api/media/covers/<cache-key>.jpg"
  }
]

Catalog requests never scan media or run FFmpeg. A background worker scans the tree at startup and then every six hours, validates one H.264 video track plus one AAC audio track, and pre-generates covers for every valid file. It publishes the completed JSON and JPEGs atomically, so clients continue to read the last successful snapshot while a refresh runs or fails. Without any completed snapshot, media and cover requests return 503.

Use the coverUrl from the media item to request a pre-generated cover:

GET /api/media/covers/<cache-key>.jpg

GET /api/media/status reports the refresh state, latest successful timestamp, valid and skipped file counts, and the latest build error. Trigger an immediate asynchronous rebuild with POST /api/media/rescan; concurrent requests do not start duplicate builds. The service is intended for a trusted LAN, so this endpoint has no additional authentication. Browser caching remains disabled.

curl -sS http://<vod-host>:8124/api/media/status
curl -i -X POST http://<vod-host>:8124/api/media/rescan

Run the smoke test after the container becomes healthy. Its default test media is data/aa/1170193193-1-192.mp4; override it with a relative, URL-encoded TEST_MEDIA_PATH when needed.

export PLAYER_ORIGIN=http://192.168.1.20:3000
./scripts/smoke-test.sh

Publishing Updates

Use a staging directory on the same filesystem as the media library, validate the staged MP4, and atomically rename it into place. Restart the container only after the rename completes:

./scripts/validate-media.sh /srv/media/.staging
mv -T /srv/media/.staging/example.mp4 /srv/media/movies/example.mp4
./scripts/refresh-vod.sh

The service sends Cache-Control: no-store and the restart clears VOD caches. This makes a replacement visible promptly, but intentionally interrupts active playback for a short period. Do not overwrite MP4 files in place.

Operations

  • Health endpoint: GET /healthz
  • Logs: docker compose logs -f vod
  • Stop: docker compose down
  • The host firewall should limit TCP 8124 to the trusted LAN. This service does not provide TLS or authentication.

The first build downloads the fixed source inputs and base image. The ARM64 target host therefore needs access to Docker registries, nginx.org, and GitHub during docker compose build.