From 2877ec59e5fea7a2a908d3d7998e8c1eb76889f4 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 17 Jul 2026 17:46:54 +0200 Subject: [PATCH] docs: design Linux frame-copy packaging --- ...mbedded-mpv-frame-copy-packaging-design.md | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-17-linux-embedded-mpv-frame-copy-packaging-design.md diff --git a/docs/superpowers/specs/2026-07-17-linux-embedded-mpv-frame-copy-packaging-design.md b/docs/superpowers/specs/2026-07-17-linux-embedded-mpv-frame-copy-packaging-design.md new file mode 100644 index 000000000..697c2f594 --- /dev/null +++ b/docs/superpowers/specs/2026-07-17-linux-embedded-mpv-frame-copy-packaging-design.md @@ -0,0 +1,206 @@ +# Linux Embedded MPV Frame-Copy Packaging Design + +**Date:** 2026-07-17 + +**Status:** Approved + +## Goal + +Ship a genuinely usable Embedded MPV frame-copy runtime in every official +Linux x64 package format produced by IPTVnator: AppImage, DEB, RPM, Pacman, +Snap, and Flatpak. Keep libmpv outside the Electron process, retain the +existing native-view engine as a safe fallback, and never advertise +frame-copy from artifact presence alone. + +Linux arm64 and armv7l remain out of scope for native Embedded MPV artifacts. +The current CI cross-packages those architectures from an x64 host and cannot +produce or exercise matching native addons. Those packages must continue to +carry an explicit unavailable marker and must not contain x64 native binaries. + +## Evidence From The Existing Implementation + +- `apps/electron-backend/native/binding.gyp` builds three distinct artifacts: + the native-view addon, the N-API shared-memory frame reader, and the + `iptvnator_mpv_helper` process. On Linux only the helper links `-lmpv`; the + addon uses X11/Xext/dlopen and must remain free of libmpv linkage. +- `tools/packaging/embedded-mpv-frame-copy-files.cjs` deliberately deletes the + helper from every Linux package. +- `tools/packaging/embedded-mpv-packaging.cjs` rejects Linux packages that + contain either the helper or libmpv, and accepts only the + `external-mpv-process` manifest origin. +- `resolveFrameCopyHelperPath()` currently treats an executable helper plus a + readable reader addon as a usable runtime. It does not prove that the ELF + loader can resolve libmpv or that libmpv/EGL initialization works. +- `.github/workflows/build-and-make.yaml` builds and verifies the Linux helper + against Ubuntu's system libmpv, then relies on the after-pack hook to remove + it. The same x64 build output is used for foreign-architecture Linux + packages, which receive the unavailable marker. +- Electron Builder creates one unpacked application layout before producing + multiple distributable targets. A system-runtime layout and a bundled + portable-runtime layout therefore cannot safely share one packaging pass. + +## Selected Distribution Strategy + +Linux packaging is split into explicit profiles: + +| Profile | Formats | libmpv strategy | +| --- | --- | --- | +| `system` | DEB, RPM, Pacman | Depend on the distribution package and resolve `libmpv.so.2` from the host | +| `portable` | AppImage, Snap | Bundle the pinned LGPL-compatible runtime closure under `native/lib` | +| `flatpak` | Flatpak | Bundle the same pinned LGPL-compatible runtime closure under `native/lib` | + +The official CI matrix must run these profiles independently. The packaging +hook receives the profile through a required environment value and validates +that the selected target set matches the runtime mode. It must fail closed if +an official x64 package is requested with an absent, incomplete, or ambiguous +profile. + +System package dependencies are: + +- DEB: `libmpv2` +- RPM: `mpv-libs` +- Pacman: `mpv` + +These names match the current Debian, Fedora, and Arch package databases. +System packages do not copy libmpv into IPTVnator. The helper keeps an +`$ORIGIN/lib` RPATH first for a consistent binary, but naturally resolves the +system SONAME when the private directory is absent. + +Portable and sandboxed packages use a source-built runtime rather than copying +the Ubuntu runner's mpv package. The runtime build is checksum/version pinned, +uses FFmpeg without GPL/nonfree switches and mpv with `-Dgpl=false`, records +sources and exact flags in the manifest, and keeps shared libraries replaceable +under the LGPL. The minimal codec baseline is FFmpeg's built-in LGPL decoders, +demuxers, protocols, and software scaling/resampling plus libass text +subtitles. Hardware decoding remains opportunistic through host Mesa/driver +interfaces and must fall back to software decoding. + +## Runtime Layout And Linkage + +The x64 packaged native directory is: + +```text +resources/app.asar.unpacked/electron-backend/native/ + embedded_mpv.node + embedded_mpv_frame_reader.node + iptvnator_mpv_helper + embedded-mpv-runtime.json + lib/ + libmpv.so.2 + libavcodec.so.* + libavformat.so.* + libavutil.so.* + libavfilter.so.* + libswresample.so.* + libswscale.so.* + libass.so.* + ...other non-system runtime dependencies +``` + +For `system`, `lib/` is absent and the manifest declares the required SONAME +and package-family dependency. For `portable` and `flatpak`, `lib/` contains +the complete non-system dependency closure. ELF dependencies inside that +closure and the helper use only SONAMEs plus `$ORIGIN`-relative RPATH/RUNPATH; +they may not retain build-prefix paths. + +`embedded_mpv.node`, the Electron executable (`iptvnator.bin`), and Electron's +shipped libraries must not have a direct `DT_NEEDED` entry for libmpv. +`iptvnator_mpv_helper` must have one. Process isolation is an invariant, not a +profile-specific choice. + +The manifest records: + +- schema version, platform, architecture, profile, and runtime origin; +- required helper/reader names and executable/readable expectations; +- libmpv SONAME and either system package requirements or bundled files; +- source package versions, URLs/checksums, license identifiers, and exact + FFmpeg/mpv build flags for bundled profiles; +- runtime closure and total byte size; +- the native-view backend contract and the fact that only the helper links + libmpv. + +## Honest Capability Detection + +The helper gains a side-effect-free `--runtime-probe` mode. It must: + +1. load through the normal ELF loader and therefore prove that all `DT_NEEDED` + dependencies resolve; +2. create and initialize an idle libmpv handle with `vo=libmpv`; +3. create the platform render pipeline far enough to prove EGL/OpenGL/GBM + availability without opening media or shared memory; +4. emit one versioned JSON result and exit promptly with status zero only on + success. + +The main process invokes this probe synchronously with a bounded timeout before +BrowserWindow creation. Probe success is cached for the process lifetime. +The probe environment prepends the packaged `native/lib` directory only when +the manifest declares a bundled runtime. The system profile does not inject a +private loader path. + +Frame-copy is usable only when all of the following are true: + +- platform and architecture are supported; +- helper and reader are regular files with correct access modes; +- the runtime manifest is present, parses, matches Linux/x64, names the actual + artifacts, and uses an allowed profile/origin; +- every manifest-declared bundled file exists as a readable regular file; +- `--runtime-probe` succeeds and returns the expected protocol version. + +Any failure returns `false`, keeps the renderer sandbox enabled, and makes the +native service choose native-view. The capability result includes a stable +reason code for tracing and diagnostics but does not crash startup. + +If dependencies disappear after startup, helper spawn/early-exit remains a +session error and follows the existing renderer fallback path. The helper is +never loaded into Electron as a library. + +## Packaging And CI Validation + +Unit and packaging tests cover: + +- profile-to-target mapping and rejection of mixed system/bundled passes; +- Linux runtime staging, manifest normalization, closure collection, RPATH, + file modes, and stale-artifact cleanup; +- package validation for system, portable, Flatpak, foreign architecture, and + malformed/incomplete manifests; +- capability probe timeout, nonzero exit, invalid JSON, manifest mismatch, + missing dependency, and successful result caching; +- helper probe protocol and failure behavior; +- package metadata dependencies for DEB/RPM/Pacman. + +Linux CI must: + +1. build or restore the pinned x64 LGPL runtime; +2. build the addon, reader, and helper once against that staged runtime; +3. prove with `readelf`/`ldd` that Electron and `embedded_mpv.node` do not link + libmpv and the helper does; +4. package the three profiles independently; +5. unpack or mount each produced format and validate its real payload, modes, + manifest, RPATH, dependency closure, and profile; +6. install/run a lightweight helper probe inside the actual Snap and Flatpak + sandboxes and from AppImage; +7. install system packages in matching disposable distro containers and run + the helper probe after the declared libmpv dependency is installed; +8. run a packaged Electron smoke test that confirms frame-copy capability, + creates a helper session against a deterministic local media fixture, sees + at least one frame/snapshot, and then repeats with libmpv hidden or removed + to prove non-crashing native-view fallback. + +Checks that require Linux kernel/package tooling or GPU/EGL are CI-only. +macOS development can run all pure Node/Jest tests and static source checks, +but cannot establish Linux ELF, package-manager, sandbox, or rendering +behavior. + +## Documentation And Release Compliance + +Update `docs/architecture/embedded-mpv-native.md`, +`tools/embedded-mpv/README.md`, `vendor/embedded-mpv/README.md`, +`AGENTS.md`, and `CLAUDE.md`. The docs must describe the x64 format matrix, +profile selection, manifest/probe contract, process-isolation invariant, +fallback behavior, source-distribution obligations, codec baseline, and ARM +status. + +Release artifacts must publish the generated runtime manifest and exact source +archives/metadata required by the recorded LGPL source-distribution statement. +No publication, push, pull request, or merge is part of this task. +