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

160 lines
4.8 KiB
Markdown

# 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:
```sh
cp .env.example .env
```
Required values:
```dotenv
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:
```sh
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:
```text
/srv/media/movies/example.mp4
```
request this HLS master playlist:
```text
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:
```text
GET /api/media
```
The response is a flat array from the latest completed catalog snapshot:
```json
[
{
"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:
```text
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.
```sh
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.
```sh
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:
```sh
./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`.