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

129 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package bbc is the BBC iPlayer app module: clear DASH/HLS captured through
// transparent MITM (UK VPN stays on). Only the manifest URL is required — no
// Widevine keys for the mobile-phone-main mediaset streams we capture.
package bbc
import (
"flag"
"fmt"
"os"
"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.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 BBC iPlayer plugin.
type App struct {
*provider.Base
cfg Config
}
// New constructs the plugin 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 none: mobile streams are clear (CDN signed URLs); no wvkey.
func (a *App) KeyMode() string { return "none" }
// CaptureHints: only the MPD/HLS master from mediaselector is required.
func (a *App) CaptureHints() capture.Hints {
return a.Hints("mpd")
}
// UseTransparentMITM: NordVPN UK conflicts with Wi‑Fi http_proxy.
func (a *App) UseTransparentMITM() bool { return true }
// MPDRewriter: passthrough — no rewrite needed for clear BBC DASH.
func (a *App) MPDRewriter() mpd.Rewriter { return mpd.Passthrough{} }
// Channels lists configured live targets.
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 iPlayer.
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()
if err := uiflow.WaitUI(c, a.CacheDir(), uiflow.Match{
DescContains: a.cfg.UI.LaunchDescContains,
ResourceContains: a.cfg.UI.LaunchResourceContains,
}, time.Duration(a.cfg.Timeouts.LaunchWaitUI)); err != nil {
fmt.Fprintf(os.Stderr, "[!] launch UI wait: %v — continue and play manually\n", err)
}
return nil
}
// AutoPlay: best-effort; iPlayer UI varies — operator can play by hand.
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 — open Live and start playback on the phone…\n", a.Label(id))
if ch.VPID != "" {
fmt.Printf(" expected mediaselector vpid ≈ %s\n", ch.VPID)
}
c.DismissShadeIfFocused()
if len(ch.PlayDesc) == 0 && len(ch.ChipDesc) == 0 {
return fmt.Errorf("no UI selectors for %s — play manually on the phone", id)
}
if len(ch.ChipDesc) > 0 {
if err := uiflow.TapUI(c, a.CacheDir(), uiflow.Match{DescRE: ch.ChipDesc}, 20*time.Second); err != nil {
return fmt.Errorf("channel chip: %w", err)
}
time.Sleep(2 * time.Second)
}
if len(ch.PlayDesc) > 0 {
if err := uiflow.TapUI(c, a.CacheDir(), uiflow.Match{DescRE: ch.PlayDesc}, 15*time.Second); err != nil {
return fmt.Errorf("play: %w", err)
}
}
return uiflow.WaitPlayback(c, a.CacheDir(), a.Package(), uiflow.PlaybackOpts{
Timeout: time.Duration(a.cfg.Timeouts.Playback),
})
}
// StreamDefaults for clear live DASH.
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 "none" }
func (a *App) LiveWaitSeconds() int { return 2 }
func (a *App) TSReadyBytes() int64 { return 256 << 10 }