# 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: license_url: "" proxy_bin: bin/proxy-android-arm64 ca_hash: "6c3578b4" score: hosts: - - deny: - 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:///live//vc11/vc11.isml/" vc12: "https:///live//vc12/vc12.isml/" kids: vc11: "" vc12: "" ``` ## 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 " 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 `.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:///live//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.