A module is a folder. Drop it under apps/modules/<name>/ and
capture / agent load it. You can automate almost the whole phone path:
force-stop, launch, wait for UI, tap, swipe, run device shell, wait for playback,
then let the MITM grab license + manifest. Branchy UI? Add Starlark. Need CDM math? Use the Python helper.
Layout
apps/modules/myapp/
module.yaml # required — identity, channels, steps
hooks/open_live.star # optional — custom UI logic
helpers/parse.py # optional — your own scripts (call via shell / host)
Example module.yaml
name: myapp
package: com.example.tvapp
proxy_bin: bin/proxy-android-arm64
ca_hash: "6c3578b4"
license_url: "https://example.com/widevine/license" # optional default
capture:
profile: myapp
require: [license_url, mpd, pssh] # what “done” means
channels:
main:
label: Main live
vars:
live_card_index: 0
launch:
- force_stop: {}
- sleep: 400ms
- monkey_launch: {}
- sleep: 4s
- dismiss_shade: {}
- wait_ui:
any_desc: ["live", "home"]
timeout: 30s
autoplay:
# Pure YAML path — or set hook: hooks/open_live.star
- tap_ui:
desc_re: ["(?i)Live"]
timeout: 20s
- sleep: 2s
- tap_indexed_cards:
index: "{{ vars.live_card_index }}"
timeout: 25s
- wait_playback:
timeout: 60s
soft_fail: true
Step actions (YAML)
force_stop, monkey_launch, sleep, dismiss_shade,
wait_ui, tap_ui, tap_xy, swipe,
shell, flow, tap_chip_then_play,
tap_indexed_cards, wait_playback, starlark
Device shell (ADB)
Run any on-device command mid-flow — clear caches, am start extras, dumpsys, even a tiny on-phone script:
- shell:
args: ["am", "start", "-n", "com.example/.LiveActivity"]
- shell:
args: ["sh", "-c", "rm -f /data/local/tmp/debug.log"]
Starlark when YAML is not enough
# hooks/open_live.star
def open_live():
dismiss_shade()
nodes = dump_ui()
for n in nodes:
if "live" in n["desc"].lower():
tap(n["cx"], n["cy"])
break
sleep(2)
tap_indexed_card(index=var("live_card_index"), timeout=25)
if not playback_active():
fail("playback did not start")
open_live()
Builtins: log, fail, sleep, tap, swipe,
dump_ui, dismiss_shade, playback_active,
tap_indexed_card, call_flow, var, plus
package / channel / label.
Python on the host (CDM)
Phone automation stays in YAML/Starlark. License crypto stays in a thin Python helper:
# apps/wvkey/wvkey.py — called by Go after MITM capture
python apps/wvkey/wvkey.py \
--mode raw \
--wvd data/device.wvd \
--pssh <base64> \
--license-url https://… \
--quiet
# → prints KID:KEY lines
Modes: modulardrm (JSON license bodies) and raw (octet-stream / Brightcove).
Keep custom Python next to your module if you want — wrap it from your own host scripts, or call tools after capture finishes.
How much can modules do?
Enough to cover a new app without touching Go: identity, channels, full launch/autoplay,
MITM capture hints, Starlark for messy UI, device shell for edge cases, and host Python for CDM.
Point streamd at app=myapp; any agent with that APK installed can refresh it in a swarm.