Null-DRM-Official/apps/modules/rte/rte.go
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

118 lines
3.8 KiB
Go

// Package rte is the RTE Player app module: DASH + ModularDrm Widevine, captured
// through the phone MITM. Launch/auto-play are Go; every value (package name,
// license URL, channel chips, origins, KIDs) comes from the local, untracked
// apps/modules/rte/module.yaml, a flag, or the environment.
package rte
import (
"flag"
"fmt"
"time"
"drmdecryption/adb"
appreg "drmdecryption/app"
"drmdecryption/capture"
"drmdecryption/modules/provider"
"drmdecryption/mpd"
"drmdecryption/uiflow"
)
func init() {
appreg.Register(Name, New)
appreg.RegisterFlags(bindFlags)
}
func bindFlags(fs *flag.FlagSet) {
fs.StringVar(&flags.packageName, Name+".package", "", "override "+Name+" android package id")
fs.StringVar(&flags.licenseURL, Name+".license-url", "", "override "+Name+" Widevine license URL")
fs.StringVar(&flags.caHash, Name+".ca-hash", "", "override "+Name+" MITM CA subject hash")
fs.StringVar(&flags.proxyBin, Name+".proxy-bin", "", "override "+Name+" on-device MITM binary")
}
// App is the RTE app plugin.
type App struct {
*provider.Base
cfg Config
}
// New constructs the plugin, loading values from module.yaml + flags + env.
func New() (appreg.App, error) {
cfg, res, err := loadConfig()
if err != nil {
return nil, err
}
labels := make(map[string]string, len(cfg.Channels))
for id, ch := range cfg.Channels {
labels[id] = ch.Label
}
return &App{Base: provider.New(Name, cfg.Common, labels, res), cfg: cfg}, nil
}
// KeyMode: RTE serves a thePlatform ModularDrm JSON license.
func (a *App) KeyMode() string { return "modulardrm" }
// CaptureHints: the MITM must yield the auth token, program id, PSSH and manifest.
func (a *App) CaptureHints() capture.Hints {
return a.Hints("auth", "pid", "pssh", "mpd")
}
// MPDRewriter publishes the synthetic encoder-timeline manifest.
func (a *App) MPDRewriter() mpd.Rewriter { return NewRewriterWith(a.cfg) }
// Channels lists the configured live channels.
func (a *App) Channels() []appreg.Channel {
out := make([]appreg.Channel, 0, len(a.cfg.Channels))
for _, id := range a.ChannelIDs() {
out = append(out, appreg.Channel{ID: id, Label: a.Label(id)})
}
return out
}
// Launch cold-starts the app and waits for its home UI.
func (a *App) Launch(c *adb.Client) error {
c.EnsureAwake()
fmt.Printf("[*] Launching %s (%s)…\n", Name, a.Package())
c.ForceStop(a.Package())
time.Sleep(400 * time.Millisecond)
if err := uiflow.MonkeyLaunch(c, a.Package()); err != nil {
return err
}
time.Sleep(time.Duration(a.cfg.Timeouts.LaunchSettle))
c.DismissShadeIfFocused()
return uiflow.WaitUI(c, a.CacheDir(), uiflow.Match{
DescContains: a.cfg.UI.LaunchDescContains,
}, time.Duration(a.cfg.Timeouts.LaunchWaitUI))
}
// AutoPlay navigates to a live channel and waits for playback.
func (a *App) AutoPlay(c *adb.Client, channel string) error {
id, err := a.Resolve(channel)
if err != nil {
return err
}
ch := a.cfg.Channels[id]
fmt.Printf("[*] Auto-play %s…\n", a.Label(id))
c.DismissShadeIfFocused()
if err := uiflow.TapUI(c, a.CacheDir(), uiflow.Match{
DescRE: a.cfg.UI.LiveTabDescRE,
}, time.Duration(a.cfg.Timeouts.LiveTab)); err != nil {
return fmt.Errorf("live tab: %w", err)
}
time.Sleep(time.Duration(a.cfg.Timeouts.AfterLiveTab))
if err := a.tapChipThenPlay(c, ch); err != nil {
return err
}
return uiflow.WaitPlayback(c, a.CacheDir(), a.Package(), uiflow.PlaybackOpts{
Timeout: time.Duration(a.cfg.Timeouts.Playback),
})
}
// StreamDefaults — the synthetic MPD carries a single 1080p + AAC pair, so a
// resolution filter is right here, and the rewriter must run.
func (a *App) VideoSelect() string { return "res=1280x720:for=best" }
func (a *App) AudioSelect() string { return "lang=en:for=best" }
func (a *App) RewriterName() string { return Name }
func (a *App) LiveWaitSeconds() int { return 2 }
func (a *App) TSReadyBytes() int64 { return 256 << 10 }