Null-DRM-Official/apps/pkg/app/app.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

154 lines
4.1 KiB
Go
Raw Permalink 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 app
import (
"flag"
"fmt"
"sort"
"sync"
"drmdecryption/adb"
"drmdecryption/capture"
"drmdecryption/mpd"
"drmdecryption/session"
)
// Channel is a named live target inside an app (e.g. rteone).
type Channel struct {
ID string
Label string
}
// App is a per-streaming-app plugin, compiled in from apps/modules/<name>. The
// capture core stays generic: an app supplies its package name, UI automation,
// capture hints, key mode and optional MPD rewrite.
type App interface {
Name() string
Package() string
LicenseURL() string
Launch(c *adb.Client) error
AutoPlay(c *adb.Client, channel string) error
CaptureHints() capture.Hints
MPDRewriter() mpd.Rewriter
Channels() []Channel
HasChannel(id string) bool
// ProxyBin is the on-device MITM binary to push.
ProxyBin() string
// CAHash is the MITM CA subject hash to reinject.
CAHash() string
// KeyMode selects the wvkey mode: "modulardrm" or "raw".
KeyMode() string
}
// Catalog is implemented by apps whose channels can be resolved without a phone
// (a public playback catalog). Drives the `catalog` subcommand.
type Catalog interface {
// Resolve returns streamd-ready credentials for a channel id.
Resolve(channel string) (session.Stream, error)
// EnrichWithKeys fills PSSH + KID:KEY using the local CDM.
EnrichWithKeys(info *session.Stream, python, wvd string) error
}
// CatalogRow is one channel in a catalog listing.
type CatalogRow struct {
ID string
CatalogID string
Label string
}
// CatalogLister is the optional richer listing a catalog app may provide: each
// channel's resolved catalog id plus whatever feature flags the provider
// publishes alongside it.
type CatalogLister interface {
ListChannels() (rows []CatalogRow, flags map[string]bool, err error)
}
// StreamDefaults is implemented by apps that need particular downloader settings.
// It replaces name-sniffing in the media supervisor.
type StreamDefaults interface {
// VideoSelect / AudioSelect are N_m3u8DL-RE selector expressions.
VideoSelect() string
AudioSelect() string
// RewriterName is the registered mpd rewriter to run, or "none".
RewriterName() string
// LiveWaitSeconds is the downloader's live refresh wait.
LiveWaitSeconds() int
// TSReadyBytes is how much muxed output to buffer before probing.
TSReadyBytes() int64
}
// TransparentMITM is implemented by apps that must use iptables REDIRECT capture
// (no Wi‑Fi http_proxy) so a phone VPN (e.g. NordVPN UK for BBC) can stay on.
type TransparentMITM interface {
UseTransparentMITM() bool
}
var (
mu sync.RWMutex
registry = map[string]func() (App, error){}
)
// Register adds a constructor for --app <name>. Called from each module's init().
func Register(name string, ctor func() (App, error)) {
mu.Lock()
defer mu.Unlock()
registry[name] = ctor
}
// Get constructs a registered app by name.
func Get(name string) (App, error) {
mu.RLock()
ctor, ok := registry[name]
mu.RUnlock()
if !ok {
return nil, fmt.Errorf("unknown app %q — compiled-in apps: %v", name, Names())
}
return ctor()
}
// Names lists registered app ids, sorted.
func Names() []string {
mu.RLock()
defer mu.RUnlock()
out := make([]string, 0, len(registry))
for k := range registry {
out = append(out, k)
}
sort.Strings(out)
return out
}
// All constructs every registered app, skipping those that fail to build.
func All() []App {
out := []App{}
for _, n := range Names() {
a, err := Get(n)
if err != nil {
continue
}
out = append(out, a)
}
return out
}
// flagHooks are registered from each module's init() so overrides can be bound
// to the module's config holder BEFORE any app is constructed.
var flagHooks []func(*flag.FlagSet)
// RegisterFlags records a module's flag binder. Called from init().
func RegisterFlags(bind func(*flag.FlagSet)) {
mu.Lock()
defer mu.Unlock()
flagHooks = append(flagHooks, bind)
}
// BindFlags lets every compiled-in module register its value overrides on fs.
// Call this before fs.Parse, and construct apps only afterwards.
func BindFlags(fs *flag.FlagSet) {
mu.RLock()
hooks := append([]func(*flag.FlagSet){}, flagHooks...)
mu.RUnlock()
for _, h := range hooks {
h(fs)
}
}