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.
6.5 KiB
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
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:
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):
./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:
./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.
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:
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 " 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:
./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:
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:
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:
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.
adb shell 'ls /system/etc/security/cacerts/'
Optional tuning
All of these have working defaults — set them only if the app changes.
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:
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
./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.