160 lines
4.8 KiB
Markdown
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`.
|