Null-DRM-Official/docs/providers/rte.md
404errordeveloper 2fa8f2435f Initial commit: Null DRM Official
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.
2026-10-06 00:25:35 +02:00

224 lines
6.5 KiB
Markdown

# RTE module — filling in `module.yaml`
DASH + ModularDrm Widevine, captured from the phone. There is no public catalog, so
every channel needs one phone capture to bootstrap.
Create `apps/modules/rte/module.yaml`. It is gitignored. Nothing below is shipped
in the repo — you obtain it yourself from your own device.
## Skeleton
```yaml
package: <android package id>
license_url: "<Widevine ModularDrm endpoint, incl. its account query string>"
proxy_bin: bin/proxy-android-arm64
ca_hash: "6c3578b4"
score:
hosts:
- <manifest CDN host>
- <ad-stitching host, if the app uses one>
deny:
- <EPG / schedules host fragment>
aliases:
one: chanone
two: chantwo
channels:
chanone:
label: "Channel One"
chip_desc: ["^chanone$"]
play_desc: ["play\\s*chanone"]
chantwo:
label: "Channel Two"
chip_desc: ["^chantwo$"]
play_desc: ["play\\s*chantwo"]
# Origin encoders behind the live channels.
origins:
vc11: "https://<origin host>/live/<path>/vc11/vc11.isml/"
vc12: "https://<origin host>/live/<path>/vc12/vc12.isml/"
kids:
vc11: "<uuid-form KID>"
vc12: "<uuid-form KID>"
```
## Where each value comes from
### `package`
Open the app on the phone, then:
```bash
adb shell dumpsys window | grep mCurrentFocus
```
The part before `/` is the package id.
### `license_url`
Run a discover session and play a channel ([capture.md](capture.md)):
```bash
./bin/drm proxy discover --install-ca --reinject
grep -iE 'ModularDrm|widevine|license' outputs/discover/*/appproxy.log
```
Take the full URL including its query string — the account reference in it is part
of the endpoint. The module also accepts it per run:
```bash
./bin/drm capture --app rte --rte.license-url 'https://.../ModularDrm?...'
```
### `score.hosts` / `score.deny`
`hosts` are the hosts that legitimately serve manifests; `deny` are hosts or URL
fragments that must never be mistaken for one.
```bash
grep -oE '"host":"[^"]+"' outputs/discover/*/appproxy_traffic.jsonl \
| sort | uniq -c | sort -rn | head -20
```
Put the origin and any ad-stitching host in `hosts`. Put EPG/schedule feeds and
analytics in `deny` — this app's schedule feed otherwise scores as a manifest
because its URL looks plausible.
Format-level scoring (`.mpd`, `.m3u8`, `/manifest`, `.isml`) is built in; you only
supply the provider-specific parts.
### `channels.*.chip_desc` and `play_desc`
These are the regexes that locate the channel chip on the Live tab and the large
hero Play button. They match the node **content-description**, case-insensitively.
Navigate to the Live tab by hand and dump the tree:
```bash
adb shell uiautomator dump /sdcard/ui.xml
adb shell cat /sdcard/ui.xml | grep -oE 'content-desc="[^"]*"' | sort -u
```
You are looking for two things:
- a small chip per channel — its description is usually the bare channel name, so
anchor the regex (`^chanone$`) to avoid matching a longer label
- a large "play <channel>" button that appears after the chip is tapped
The module taps the hero Play directly when it is already on screen, otherwise it
taps the chip first and waits for Play to appear. A match is only accepted as the
hero button if its area is at least `hero_min_area` (default 200000), which keeps
small list-item play icons from winning.
### `origins` and `kids` — the important pair
These let the manifest rewriter keep working after the ad-stitched manifest session
expires. Both come from a capture.
**The KID** is in the capture session:
```bash
./bin/drm capture --app rte --channel chanone --auto-play --wvd data/device.wvd
jq -r '.kid' outputs/rte/latest/session.json
```
Convert it to dashed UUID form for the `kids:` map
(`8-4-4-4-12`), e.g. `df1633821dddfdd5bec9822c1ec0f052` becomes
`df163382-1ddd-fdd5-bec9-822c1ec0f052`. Either form is accepted, but UUID form is
easier to read.
**The origin** is the Smooth Streaming base URL. Find it in the discover traffic:
```bash
grep -oE 'https?://[^"]+\.isml[^"]*' outputs/discover/*/appproxy_traffic.jsonl \
| sed -E 's#(\.isml).*#\1/#' | sort -u
```
The last path element before `.isml` is the encoder channel id, which becomes the
map key. So `https://host/live/tc-1/vc11/vc11.isml/` is keyed `vc11`.
**Pair them up** by capturing each channel in turn and noting which KID appeared:
```bash
for ch in chanone chantwo; do
./bin/drm capture --app rte --channel $ch --auto-play --wvd data/device.wvd
echo "$ch -> $(jq -r .kid outputs/rte/latest/session.json)"
done
```
Then confirm the mapping resolves:
```bash
go -C apps/modules test -tags live ./rte/ -v
```
That test fetches each configured origin's manifest, checks the KID resolves back
to its channel, and verifies the synthetic manifest lands on the live encoder's
segment grid. If a KID does not resolve, `kids:` and `origins:` disagree.
### `ca_hash`
The subject hash of the MITM CA, which is what the Magisk mount installs as
`<hash>.0`. `6c3578b4` is the bundled CA; change it only if you generated your own.
```bash
adb shell 'ls /system/etc/security/cacerts/'
```
## Optional tuning
All of these have working defaults — set them only if the app changes.
```yaml
encoder_channel_re: '^(vc|channel)\d+$' # which origin ids use the encoder timeline
origin_marker: .isml # path element identifying an origin BaseURL
hero_min_area: 200000 # smallest node accepted as the hero Play
ui:
launch_desc_contains: ["live tab", "header logo"]
live_tab_desc_re: ["Live tab"]
timeouts:
launch_wait_ui: 25s
live_tab: 30s
chip_wait: 20s
play_wait: 15s
playback: 45s
synthetic:
video_timescale: 600
audio_timescale: 48000
video_name: video
audio_name: audio_128k
video_bandwidth: 6000000
audio_bandwidth: 128000
width: 1920
height: 1080
live_edge_offset_s: 12
window_segments: 6
```
The `synthetic` block describes the origin encoder. Its defaults match a common
Smooth-to-DASH setup; if your origin uses different stream names or bitrates, read
them off the origin manifest:
```bash
curl -s 'https://<origin>/live/<path>/vc11/vc11.isml/Manifest' | head -40
```
`StreamIndex Name="…"` gives `video_name` / `audio_name`, and `Bitrate="…"` gives
the bandwidths. Segment duration and phase are learned at runtime, not configured.
## Verify
```bash
./bin/drm modules # channel count should match your file
./bin/drm capture --app rte --channel chanone --auto-play --wvd data/device.wvd
go -C apps/modules test -tags live ./rte/ -v
```
A good capture yields `auth`, `pid`, `pssh` and `mpd`, and a single `KID:KEY` whose
KID matches the `kids:` entry for that channel's encoder.