FLX is designed around a simple split: the BEAM owns the living system, FFmpeg owns media transformation, SQLite owns canonical MVP metadata/search state, and Rust remains an acceleration option behind explicit contracts.
The architecture should make the common path simple while preserving escape hatches for workloads that later prove to need native acceleration or distributed execution.
Clients communicate with a Phoenix/Bandit server over HTTP and realtime channels as needed. Phoenix owns authentication, library/domain state, client capability negotiation, playback planning, session lifecycle, job orchestration, observability, and diagnostics.
Canonical metadata is stored through Ecto in SQLite. FTS5 is the initial search projection. Oban Lite is the intended durable job engine for scans, metadata work, artwork, reconciliation, and other restart-safe jobs. Live playback and live transcodes are supervised OTP processes, not durable jobs.
FFmpeg and ffprobe are external processes supervised through a Media.OSProcess contract. MuonTrap is the preferred initial backend because process-tree containment and cgroup accounting are directly useful to both correctness and observability.
Responsibilities:
- media roots and discovery
- media identity and file locations
- ffprobe ingestion and raw probe retention
- normalized technical metadata
- reconciliation and watcher hints
- derivative generation requests
Filesystem events are hints, never truth. Reconciliation scans establish truth. Media identity is stable across file renames and moves.
Media.Search is a behaviour boundary. The MVP implementation uses SQLite FTS5. Search indexes are disposable projections and must be deterministically rebuildable from canonical metadata. A later Tantivy implementation may live behind the same contract, preferably as a Rust Port rather than a heavyweight NIF.
Playback planning is pure domain logic. Inputs include SourceCapabilities, ClientCapabilities, HardwareCapabilities, PlaybackPreferences, and ResourceConstraints. Output is an immutable, serializable, versioned PlaybackPlan.
A plan may select direct play, remux, audio-only transcode, full transcode, subtitle conversion/burn-in, tone mapping, or another supported strategy. The planner performs no I/O.
Persist enough plan inputs and version information to explain and replay historical decisions.
Each active playback session is isolated under supervision. A playback process owns its runtime state and any transient children such as a transcode process. Failures in a single session must not poison unrelated sessions, library scans, search, or metadata work.
Media.Hardware.Provider is the contract. Initial providers are QSV, NVIDIA, and Software. Providers expose proven decode, filter, and encode capabilities.
Startup or scheduled certification runs tiny real media fixtures through the pipeline, validates the output with ffprobe, and verifies actual accelerator activity. Merely finding an encoder in ffmpeg -encoders is not sufficient.
Quick Sync support targets modern Linux Intel media-driver/oneVPL/QSV paths. NVIDIA support targets NVDEC/CUDA/NVENC and must preserve device-resident frames when compatible filters permit it. Software fallback remains mandatory.
Media.Transcode.Executor isolates where work runs. The MVP implementation is Local and launches FFmpeg under Media.OSProcess. Future implementations may target remote GPU workers, FLAME, OTP distribution, or another execution model without changing playback planning.
Rust is not required for the MVP application server. Preserve separate contracts for:
- Media.Scanner
- Media.Search
- Media.Fingerprint
- Media.Watcher
Potential Rust building blocks include jwalk, ignore, rayon, BLAKE3, Tantivy, and notify. Heavyweight work should prefer an external Port executable so a native crash cannot take down the BEAM. Rustler remains available for small, bounded hot functions where in-process call overhead matters.
SQLite is canonical for MVP application metadata and user state. Enable WAL where appropriate and design writes with SQLite concurrency characteristics in mind.
Raw ffprobe data should be retained alongside normalized schema fields so the interpretation layer can evolve without rereading all source media.
Precious state includes source media, canonical metadata/overrides, user state, and stable media identity. Rebuildable state includes FTS/Tantivy indexes, embeddings, thumbnails, resized artwork, transcode caches, search projections, and hardware capability caches.
Use one product codebase with narrow platform adapters rather than one identical implementation everywhere.
- Google TV: React Native TV/Expo, native Android playback adapter backed by Media3/ExoPlayer when needed.
- tvOS: React Native TV/Expo, native Apple playback adapter backed by AVPlayer/AVFoundation when needed.
- Web: React/React Native Web where useful, with a dedicated web playback adapter such as Shaka when browser media behavior requires it.
Shared code should include navigation/domain state/API contracts/screens/components where that does not compromise television focus behavior, virtualization, or playback quality. Platform-specific focus/grid/player code is acceptable when it materially improves UX.
Verification is a first-class subsystem. The intended stack includes deterministic tests, property tests, PropCheck StateM models, Constrain-style invariants, AssertBoundary-style dependency rules, runtime leak/process oracles, hardware certification, and deterministic failure injection.
Important state machines include playback lifecycle, FFmpeg lifecycle, hardware fallback, client reconnect/disconnect behavior, and library reconciliation.
Real hardware lanes must exercise Intel Quick Sync and NVIDIA paths. Successful exit status alone is not an oracle; validate output structure and real hardware activity.
Telemetry is the internal semantic event bus. Media.Observability owns stable event names, metric definitions, log metadata keys, trace attributes, and cardinality policy.
Logger is the application logging API. Development logs are optimized for humans. Production logs are structured JSON and emitted to stdout for collection. OpenTelemetry is initially authoritative for traces, while metrics flow from Telemetry.Metrics and logs remain Logger-based until Erlang OTel support justifies further convergence.
Every important operation should be correlatable using stable IDs such as request_id, trace_id, playback_id, playback_plan_id, transcode_id, library_scan_id, media_id, job_id, client_id, and hardware_device_id.
Media.Diagnostics powers mix doctor, health information, LiveDashboard pages, diagnostic bundles, and failure evidence collection. Tidewave is enabled only in development for live Phoenix introspection.
Benchmarks are versioned evidence. Record application commit, Elixir/OTP version, FFmpeg build, hardware/driver identity, fixture identity, and results.
Initial performance targets should focus on search latency, scan throughput, playback startup/seek latency, FFmpeg startup-to-first-segment time, transcode speed, scheduler impact, memory peaks, and GPU utilization. Thresholds should be based on repeatable baselines rather than one-off CI timing.
- Never mutate source media.
- Canonicalize and authorize media paths against configured roots.
- Build FFmpeg argv structurally; never construct shell command strings from media metadata.
- Treat filenames, metadata, subtitles, and external provider responses as untrusted input.
- Keep dev-only tooling such as Tidewave out of production.
- Do not expose high-cardinality or sensitive identifiers as metric labels.
- Resource ownership and cancellation must be explicit so dead sessions cannot leak child processes.