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.
224 lines
6.5 KiB
Markdown
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.
|