Capture, decrypt, and restream toolkit with compiled-in app modules (RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www. BBC module.yaml is published (clear streams); other module values stay local.
183 lines
5.5 KiB
Markdown
183 lines
5.5 KiB
Markdown
# Control plane and agent
|
|
|
|
Two long-running pieces: `drm serve` holds the state and the dashboard, `drm agent
|
|
run` keeps credentials fresh using real phones.
|
|
|
|
## drm serve
|
|
|
|
```bash
|
|
./bin/drm serve --bind 127.0.0.1:8083 --data .cache/streamd --token SECRET
|
|
```
|
|
|
|
| Flag | Default | Purpose |
|
|
|---|---|---|
|
|
| `--bind` | `0.0.0.0:8083` | listen address |
|
|
| `--data` | `.cache/streamd` | SQLite, work dirs, HLS output, logs |
|
|
| `--token` | `$STREAMD_TOKEN` | bearer token for mutating routes |
|
|
| `--nre` | `$NRE_PATH`, `bin/`, `PATH` | downloader binary |
|
|
| `--ffmpeg` | `$FFMPEG_PATH`, `bin/`, `PATH` | ffmpeg |
|
|
| `--mp4decrypt` | `$MP4DECRYPT_PATH`, `bin/`, `PATH` | decrypter |
|
|
|
|
Open <http://127.0.0.1:8083>. GET routes are unauthenticated; anything mutating
|
|
needs `Authorization: Bearer <token>`.
|
|
|
|
### API
|
|
|
|
| Route | Purpose |
|
|
|---|---|
|
|
| `GET /api/health` | liveness + uptime |
|
|
| `GET /api/apps` | compiled-in modules, their channels, registered rewriters |
|
|
| `GET /api/streams` | all streams with runtime state |
|
|
| `POST /api/streams` | create |
|
|
| `PATCH /api/streams/:id` | update |
|
|
| `DELETE /api/streams/:id` | remove |
|
|
| `POST /api/streams/:id/start\|stop\|restart` | desired state |
|
|
| `POST /api/streams/:id/credentials` | push a fresh manifest + keys |
|
|
| `POST /api/streams/:id/claim` | agent claims the stream |
|
|
|
|
The dashboard's app and channel pickers are filled from `/api/apps`, so they reflect
|
|
whatever modules the binary was built with. Nothing about a provider is hardcoded in
|
|
the UI or the schema.
|
|
|
|
### Creating a stream
|
|
|
|
```bash
|
|
./bin/drm catalog --app tg4 --channel ioi --keys --wvd data/device.wvd --json > s.json
|
|
|
|
curl -X POST http://127.0.0.1:8083/api/streams \
|
|
-H 'Authorization: Bearer SECRET' -H 'Content-Type: application/json' \
|
|
--data @s.json
|
|
```
|
|
|
|
A stream created without `app`, `video_select`, `audio_select` or `rewriter` keeps
|
|
those empty on purpose. They are resolved when the stream starts:
|
|
|
|
```text
|
|
stored row -> the stream's app module (app.StreamDefaults) -> neutral defaults
|
|
```
|
|
|
|
So a module decides its own downloader settings, an operator override in the row
|
|
still wins, and an unknown app gets something harmless rather than another
|
|
provider's preferences.
|
|
|
|
### Refreshing credentials
|
|
|
|
```bash
|
|
curl -X POST http://127.0.0.1:8083/api/streams/1/credentials \
|
|
-H 'Authorization: Bearer SECRET' -H 'Content-Type: application/json' \
|
|
-d '{"mpd":"https://...","key":"KID:KEY\nKID:KEY","headers_json":"{}"}'
|
|
```
|
|
|
|
This is exactly what the agent does after a successful capture.
|
|
|
|
### Manifest rewriting
|
|
|
|
If a stream's `rewriter` names a registered rewriter and the manifest is not HLS,
|
|
the supervisor starts a local rewrite server and points the downloader at it:
|
|
|
|
```text
|
|
stream rewriter="rte" -> mpd.Lookup("rte") -> http://127.0.0.1:PORT/manifest.mpd
|
|
```
|
|
|
|
The log line is `stream X: rewritten MPD http://127.0.0.1:…`. Streamd never imports
|
|
a provider package to do this — the module registered the rewriter under that name
|
|
at init time.
|
|
|
|
## drm agent run
|
|
|
|
```bash
|
|
export STREAMD_TOKEN=SECRET
|
|
./bin/drm agent run --config apps/agent/agent.yaml
|
|
```
|
|
|
|
What it does, in a loop:
|
|
|
|
1. poll streamd for enabled streams that are down or missing credentials
|
|
2. enqueue a job in a durable SQLite queue
|
|
3. claim a free ADB device that already has the app installed
|
|
4. run a phone capture through the stream's app module
|
|
5. `POST /api/streams/:id/credentials`
|
|
6. force-stop the app and release the device
|
|
|
|
It never installs apps and never runs a downloader. A websocket heartbeat keeps its
|
|
claims alive, so claims expire automatically if the agent dies.
|
|
|
|
### Subcommands
|
|
|
|
```bash
|
|
./bin/drm agent devices # phones it can use
|
|
./bin/drm agent status # recent jobs
|
|
./bin/drm agent enqueue --stream tg4-ioi --reason manual
|
|
./bin/drm agent cancel --job 3
|
|
```
|
|
|
|
### Config
|
|
|
|
`apps/agent/agent.yaml`:
|
|
|
|
```yaml
|
|
streamd_url: http://127.0.0.1:8083
|
|
# token: taken from $STREAMD_TOKEN when unset
|
|
poll_interval_sec: 30
|
|
data_dir: .cache/agent
|
|
workers: 1
|
|
max_attempts: 5
|
|
capture_wait_sec: 180
|
|
# devices: [SERIAL1, SERIAL2] # empty = any authorised device
|
|
# python: path to python for wvkey.py
|
|
# wvd: data/device.wvd
|
|
# user_agent: ""
|
|
# agent_id: defaults to hostname
|
|
```
|
|
|
|
There is no modules directory to configure — app modules are compiled into the
|
|
binary.
|
|
|
|
`wvd` must be set (here or on the phone capture) or key fetches fail.
|
|
|
|
## Running both locally
|
|
|
|
```bash
|
|
# terminal 1
|
|
./bin/drm serve --bind 127.0.0.1:8083 --data .cache/streamd --token SECRET
|
|
|
|
# terminal 2
|
|
export STREAMD_TOKEN=SECRET
|
|
./bin/drm agent run --config apps/agent/agent.yaml
|
|
```
|
|
|
|
Then create a stream, enable it, and watch the agent pick it up:
|
|
|
|
```bash
|
|
./bin/drm agent status
|
|
```
|
|
|
|
## External tools
|
|
|
|
The media supervisor shells out to three binaries. Put them on `PATH`, in `bin/`,
|
|
or name them with flags/env:
|
|
|
|
| Tool | Flag | Env |
|
|
|---|---|---|
|
|
| N_m3u8DL-RE | `--nre` | `NRE_PATH`, `N_M3U8DL_RE` |
|
|
| ffmpeg | `--ffmpeg` | `FFMPEG_PATH`, `FFMPEG` |
|
|
| mp4decrypt | `--mp4decrypt` | `MP4DECRYPT_PATH`, `MP4DECRYPT` |
|
|
|
|
Streams can be created, claimed and credentialed without any of them; only actually
|
|
pulling media needs them. The supervisor is the least exercised part of the system —
|
|
treat its health reporting as control-plane bookkeeping.
|
|
|
|
## Data
|
|
|
|
```text
|
|
.cache/streamd/
|
|
streamd.db SQLite: streams, runtime, claims
|
|
work/<name>/ downloader scratch + keys.txt
|
|
www/<name>/ HLS output
|
|
logs/ per-stream downloader + ffmpeg logs
|
|
|
|
.cache/agent/
|
|
queue.db durable job queue
|
|
```
|
|
|
|
Back up `streamd.db` before upgrading — schema defaults change between versions.
|