From 9bc8d74f3528a8b97f2aaa783ea3e24bf4768402 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 17 Jul 2026 20:38:21 +0200 Subject: [PATCH] docs(embedded-mpv): document Linux frame-copy packaging --- AGENTS.md | 26 +++ CLAUDE.md | 26 ++- docs/architecture/embedded-mpv-native.md | 255 +++++++++++++++++------ tools/embedded-mpv/README.md | 229 +++++++++++--------- vendor/embedded-mpv/README.md | 24 ++- 5 files changed, 396 insertions(+), 164 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a8069f79e..2263a3f14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -216,6 +216,32 @@ Key files: - Canonical docs: `docs/architecture/player-controls-contract.md` and `docs/architecture/embedded-mpv-native.md` +## Linux Embedded MPV Packaging + +- Official Linux frame-copy artifacts are x64-only. AppImage, DEB, RPM, + Pacman, Snap, and Flatpak are supported; non-x64 Linux packages must remain + marker-only and must never inherit x64 native artifacts from environment + overrides. +- Packaging runs three isolated profiles: + - `system`: DEB/RPM/Pacman, no private `native/lib`, with package + dependencies `libmpv2`/`mpv-libs`/`mpv` + - `portable`: AppImage/Snap with the pinned LGPL-compatible closure + - `flatpak`: Flatpak with the same pinned closure +- Only `iptvnator_mpv_helper` may link libmpv. The Electron executable, + Electron libraries, `embedded_mpv.node`, and + `embedded_mpv_frame_reader.node` must not load or link it. Preserve this + process-isolation contract in build, package, and smoke checks. +- Linux frame-copy availability is fail-closed. The packaged manifest, + artifact modes, declared bundled hashes/closure, and bounded + `--runtime-probe` must all succeed before frame-copy can relax the renderer + sandbox. Any failure reports a stable reason and falls back to native-view + without crashing; an environment flag never bypasses this gate. +- Bundled Linux releases must publish the exact source archives/git records, + checksums, licenses, flags, patches, build scripts, and the pinned hwdata + `pnp.ids` input. Canonical maintenance docs: + `docs/architecture/embedded-mpv-native.md` and + `tools/embedded-mpv/README.md`. + ## Repo Skills - `iptvnator-ui-design` diff --git a/CLAUDE.md b/CLAUDE.md index dfad56813..630c5b795 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -617,7 +617,31 @@ This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use - Built-in web players: HTML5+hls.js, Video.js, and ArtPlayer - External players: MPV, VLC (via IPC to Electron backend) - Embedded MPV (experimental, macOS/Windows/Linux): renders mpv video inside the Electron window through a native addon. macOS uses the libmpv render API in an `NSOpenGLView`; Windows uses in-process libmpv with `--wid` against an app-owned child `HWND`; Linux spawns an out-of-process `mpv --wid=` controlled over a JSON IPC socket (X11/XWayland only, requires system `mpv` on PATH; subtitles/speed/aspect/recording are not exported there). mpv's own screensaver inhibition does not apply to any of these paths, so `EmbeddedMpvNativeService` holds an Electron `powerSaveBlocker` (`prevent-display-sleep`) whenever any session's status is `playing`, and releases it on pause, dispose, or shutdown. Service: `apps/electron-backend/src/app/services/embedded-mpv-native.service.ts`; full architecture: `docs/architecture/embedded-mpv-native.md`. -- Embedded MPV frame-copy engine (experimental, macOS Apple Silicon + Linux + Windows; enabled via `Settings > Playback > Embedded MPV: frame-copy engine` (restart required) or `IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY=1` on top of the embedded MPV experiment flag): a per-session helper renders mpv offscreen at viewport size (headless CGL on macOS, headless EGL on Linux, WGL against a hidden window on Windows) and publishes BGRA frames into a shm ring (POSIX shm; a `Local\` named file mapping on Windows); the preload frame pump uploads them onto a renderer ``, so controls/dialogs are ordinary DOM above the video. Frame-copy is the first runtime consumer of shared `app-player-controls`: `PlayerControlsComponent` and its surface/shortcut/fullscreen collaborators own the DOM UI interactions, while the component-scoped `EmbeddedMpvControlsAdapter` maps session state and commands and coordinates correlated recording state; native-view retains the legacy fixed dock. Stored and explicit opt-ins relax the sandbox only while the base embedded-MPV feature is enabled and a platform-supported packaged runtime contains both the regular-file helper (`iptvnator_mpv_helper` / `.exe`) and readable regular frame-reader addon; packaged discovery is restricted to packaged resources. A disabled base experiment keeps embedded MPV unavailable with the sandbox intact, while a missing, mode-stripped, or incomplete frame-copy runtime falls back to the native engine without relaxing the sandbox. On Linux the engine is dev-build-only for now: the helper links system libmpv (build deps: `libmpv-dev`, `libegl-dev`, `libgl-dev`, `libopengl-dev`, `libgbm-dev`) and is stripped from packages until bundled-runtime staging lands. On Windows the helper links vendored libmpv and package validation requires the exact MPV DLL named in the helper's PE import table beside the executable. Backend process adapter: `apps/electron-backend/src/app/services/embedded-mpv-frame-copy.adapter.ts`; shared-controls adapter: `libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-controls.adapter.ts`; helper: `apps/electron-backend/native/helper/`; details in `docs/architecture/embedded-mpv-native.md` ("Frame-Copy Engine"). +- Embedded MPV frame-copy engine (experimental, macOS Apple Silicon + Linux + x64 + Windows; enabled via `Settings > Playback > Embedded MPV: frame-copy +engine` (restart required) or + `IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY=1` on top of the embedded MPV + experiment flag): a per-session helper renders mpv offscreen (CGL on macOS, + EGL on Linux, WGL on Windows), publishes BGRA frames into a shm ring, and the + preload frame pump uploads them to + ``. Shared `app-player-controls` owns the DOM + UI; native-view retains the legacy dock. On Linux, only + `iptvnator_mpv_helper` may link libmpv; Electron, its shipped libraries, the + addon, and frame reader must not. Official x64 packages use three separate + profiles: DEB/RPM/Pacman depend on system libmpv, AppImage/Snap bundle the + pinned LGPL closure, and Flatpak bundles the same closure. ARM packages are + marker-only. Stored or explicit opt-ins cannot bypass the fail-closed + packaged manifest/file/hash gate and bounded `--runtime-probe`; any failure + keeps the sandbox enabled, records a stable reason, and falls back to + native-view without crashing. On Windows, package validation requires the + exact MPV DLL named by the helper's PE import table beside the executable. + Backend adapter: + `apps/electron-backend/src/app/services/embedded-mpv-frame-copy.adapter.ts`; + shared-controls adapter: + `libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-controls.adapter.ts`; + helper: `apps/electron-backend/native/helper/`; canonical packaging/runtime + contracts: `docs/architecture/embedded-mpv-native.md` and + `tools/embedded-mpv/README.md`. - Shared player-controls layer: `libs/ui/playback/src/lib/player-controls/` exports the engine-neutral `PlayerController` contract, standalone `app-player-controls`, a generic web-video adapter/helper, and component-scoped `WEB_PLAYER_SHARED_CONTROLS` rollout token. Persisted `Settings.webPlayerSharedControls` is default-off, and its checkbox appears only when HTML5, Video.js, or ArtPlayer is selected. `WebPlayerViewComponent` snapshots the preference into the immutable token for each new player host. The parent `/workspace` route awaits the initial `SettingsStore` load, including cold-start direct links, before this snapshot can occur. Saving applies to the next host without an application restart; an existing session never changes controls mode in place. Embedded MPV ignores the web-player preference: frame-copy always uses shared DOM controls through `EmbeddedMpvControlsAdapter`, native-view retains its compositor-safe legacy dock, and external MPV/VLC retain their own UI. The Embedded MPV host selects exactly one controls UI for its reported engine. `showControls=false` detaches the shared surface, modal overlays gate frame-copy playback shortcuts, fullscreen remains DOM-based with Embedded MPV bounds sync, and a playback/session transition key prevents engine or session handoff from presenting stale recording feedback while timers and pending commands are cancelled. Same-session IPC replies yield to a broadcast snapshot received while the command was pending, so a successful recording acknowledgement cannot be rolled back by a stale reply. The built-in HTML5/hls.js player is the second guarded consumer: `HtmlVideoPlayerComponent` provides a component-scoped `WebVideoControlsAdapter`, while its neutral `web-video-support` bridge is shared with ArtPlayer and owns HLS/native tracks, MPEG-TS VOD duration correction, caption preference, and source cleanup. `HtmlVideoElementSession` owns native video-event lifecycle, persisted volume, start-time/time/ended propagation, and legacy post-play caption suppression. Video.js is the third guarded consumer: `VjsPlayerComponent` provides a component-scoped `WebVideoControlsAdapter`; its bridge rebinds the current Tech video after `playerreset`, exposes source-stable audio/subtitle IDs, preserves caption preference and explicit subtitle-off state, and reads Video.js duration. Reset-driven raw MPEG-TS changes pause first, coalesce to the latest desired source, preserve actual volume across Video.js's reset, and restart when authoritative live/VOD metadata changes. In shared-controls mode, Video.js native controls, click/double-click/hotkey actions, and spatial navigation are disabled. ArtPlayer is the fourth guarded consumer: `ArtPlayerComponent` provides a component-scoped `WebVideoControlsAdapter`; `ArtPlayerSourceSession` owns HLS/MPEG-TS/native sources, the neutral web-video bridge, exact cleanup, and a destroyed-session guard for delayed `customType` callbacks, while `ArtPlayerVideoSession` owns native media/ArtPlayer events. Shared ArtPlayer mode uses authoritative live/VOD metadata, HLS/native tracks and caption preference, MPEG-TS VOD duration correction, and reapplies app volume directly after ArtPlayer restores its own stored volume. Vendor chrome/hotkeys are disabled, and a transparent capture layer gives shared controls exclusive click and double-click ownership. `WebPlayerViewComponent.resolvedIsLive` supplies authoritative metadata; visible playback diagnostics disable shared pointer/keyboard ownership and exit only the active HTML5, Video.js, or ArtPlayer shell's own fullscreen so retry/fallback actions remain visible. On the preference-off path, all three web players retain their existing controls, source behavior, and legacy series navigation. Contract: `docs/architecture/player-controls-contract.md`. - Shared web picture-in-picture stays inside that default-off rollout. `PlayerController` exposes capability `pictureInPicture`, state diff --git a/docs/architecture/embedded-mpv-native.md b/docs/architecture/embedded-mpv-native.md index f664f4945..421a80f4d 100644 --- a/docs/architecture/embedded-mpv-native.md +++ b/docs/architecture/embedded-mpv-native.md @@ -18,7 +18,7 @@ Source files for the embedded MPV integration: - `libs/shared/interfaces/src/lib/embedded-mpv-session.interface.ts` defines the shared session and audio-track contract. - `libs/ui/playback/src/lib/embedded-mpv-player/` owns the Angular UI and controls. -Frame-copy engine sources (experimental, macOS Apple Silicon, Linux and +Frame-copy engine sources (experimental, macOS Apple Silicon, Linux x64, and Windows — see the "Frame-Copy Engine" section below): - `apps/electron-backend/native/helper/` — `iptvnator_mpv_helper` process (`mpv_frame_helper.cpp`, `frame_helper_render.h`, `frame_helper_gl.h`, `frame_helper_io.h`, `frame_shm.h`). @@ -60,20 +60,43 @@ Linux native Wayland embedding is not implemented. When Electron is started on X ## Linux Support Matrix -Embedded MPV on Linux is supported only for x64 desktop builds where Electron runs under X11 or Xwayland and an `mpv` executable is available on `PATH`. Native Wayland embedding is not supported in this implementation. +Official Linux frame-copy packaging is x64-only. The native-view and frame-copy +engines have different runtime requirements: -The experimental frame-copy engine (below) is the exception to both requirements: it renders offscreen through headless EGL into a renderer canvas — no window embedding — and the helper links libmpv itself, so neither the X11/Xwayland constraint nor the system-`mpv`-on-`PATH` probe applies while it is active. It is currently a dev-build-only engine on Linux (the helper links the build host's system `libmpv` and is stripped from packaged apps until the bundled-runtime staging lands). Packaged Linux launchers pass `--ozone-platform=x11` so Wayland desktops use Xwayland when it is available, and `main.ts` appends the same switch on Linux when it is absent so direct binary/AppImage launches from a terminal behave like launcher starts. Explicit user intent is never overridden: both a user-provided `--ozone-platform` switch and the `ELECTRON_OZONE_PLATFORM_HINT` environment variable suppress the fallback. +| Engine | Display path | MPV runtime | +| ----------- | ----------------------------- | -------------------------------------------------------------------------- | +| native-view | X11 or Xwayland | An `mpv` executable on `PATH`; playback is an isolated `mpv --wid` process | +| frame-copy | Headless EGL; no window embed | A separately linked and capability-probed `iptvnator_mpv_helper` | -When the `mpv` executable probe fails inside a Flatpak or Snap sandbox (`FLATPAK_ID`/`SNAP` env present), the support reason explains that sandboxed packages cannot access a system mpv instead of asking the user to install it. +Native Wayland embedding is not implemented for native-view. Frame-copy itself +does not embed a window and can render through EGL on a native Wayland desktop, +although packaged launchers still default Electron to X11/Xwayland unless the +user explicitly supplies an Ozone choice. A user-provided `--ozone-platform` +or `ELECTRON_OZONE_PLATFORM_HINT` is never overridden. -Current release-announcement wording should stay close to this: +Linux packages are built in separate passes because Electron Builder reuses +one unpacked application layout per pass: -- Supported display path: X11 or Xwayland. -- Not supported: native Wayland embedding. -- Validated locally: Ubuntu 24.04 GNOME Wayland session with Electron forced to X11/Xwayland and system `mpv`. -- Validated in CI: Ubuntu 22.04 standard Linux package build and Ubuntu 24.04 Flatpak package build. -- Expected standard packages: `.deb` on Ubuntu/Debian, `pacman` on Arch/Manjaro, `.rpm` on RPM-based distributions, and AppImage on x64 glibc systems, all with system `mpv` installed. -- Sandbox caveat: Flatpak and Snap packages build and continue to support the normal inline/external-player flows, but embedded MPV is not announced as supported there yet because the Linux backend launches `mpv --wid` and those sandboxed formats do not expose the host `mpv` executable to the app by default. +| Profile | Formats | Frame-copy libmpv strategy | +| ---------- | ---------------- | --------------------------------------------------------------------------------------------- | +| `system` | DEB, RPM, Pacman | System `libmpv.so.2`; package dependencies are `libmpv2`, `mpv-libs`, and `mpv`, respectively | +| `portable` | AppImage, Snap | Bundled pinned LGPL-compatible runtime under `native/lib` | +| `flatpak` | Flatpak | The same bundled pinned LGPL-compatible runtime under `native/lib` | + +Every x64 layout contains the addon, frame reader, helper, and a normalized +`embedded-mpv-runtime.json`. The Electron executable, Electron libraries, +`embedded_mpv.node`, and the frame reader must not link libmpv; only the helper +may do so. AppImage, Snap, and Flatpak retain dynamically linked, replaceable +runtime libraries and ship the corresponding source/build metadata. DEB, RPM, +and Pacman intentionally contain no private `native/lib` directory. + +ARM Linux packages remain marker-only. They never borrow x64 native artifacts, +even when build environment variables claim a matching staged architecture. +Consequently frame-copy is not advertised there, and the normal inline/external +players remain available. On x64, any missing or unusable frame-copy dependency +falls back to native-view without crashing; if native-view also lacks X11 or a +system `mpv` executable, Embedded MPV is reported unavailable with a stable +diagnostic reason. The flow is: @@ -117,7 +140,16 @@ The renderer never gets direct native-module access. It can only call the preloa - dispose session - subscribe to session updates -Settings uses the preload support API as an availability and capability check. Unsupported paths return before loading the addon when platform, experiment gating, addon presence, bundled runtime presence, or the Linux `mpv` executable check fails. Supported paths load `embedded_mpv.node` so the renderer can receive capability flags from the actual addon binary. Avoid calling this support API from global workspace startup paths; use an explicit user action or idle preparation path when a renderer surface only needs to reveal optional Embedded MPV UI. +Settings uses the preload support API as an availability and capability check. +Unsupported paths return before loading the addon when platform, experiment +gating, native artifacts, the packaged runtime manifest, the Linux helper +probe, or the native-view `mpv` executable check fails. Support diagnostics +include a stable `frameCopyUnavailableReason`; it is tracing/support data, not +user-facing copy. Supported paths load `embedded_mpv.node` so the renderer can +receive capability flags from the actual addon binary. Avoid calling this +support API from global workspace startup paths; use an explicit user action +or idle preparation path when a renderer surface only needs to reveal optional +Embedded MPV UI. When `embedded-mpv` is the saved player, the settings store schedules an idle `prepareEmbeddedMpv()` call. This intentionally moves the first native addon load away from the click-to-play path. It can still block the Electron main process briefly because Node native addon loading is synchronous, but doing it during idle is less visible than doing it when the user clicks a video. Actual MPV session creation still happens on playback because it needs the current Electron window handle and viewport bounds. @@ -194,19 +226,25 @@ service and the adapter): Enabling it: the `Settings > Playback > Embedded MPV: frame-copy engine` checkbox (shown only when support reports `frameCopyAvailable`) persists to -the main-process config store (`electron-conf`), which `main.ts` reads -before creating the window and translates into the env flag; an explicitly -set env var (including `0`) wins over the stored preference, but cannot bypass -the platform/runtime safety gate. Frame-copy can relax the window sandbox only +the main-process config store (`electron-conf`), which `main.ts` reads before +creating the window and translates into the env flag; an explicitly set env +var (including `0`) wins over the stored preference, but cannot bypass the +platform/runtime safety gate. Frame-copy can relax the window sandbox only when embedded MPV itself is enabled for the current run (packaged app or the -regular development experiment flag) and discovery finds both an executable -(`X_OK`) helper and a readable regular frame-reader addon in the same native -directory. Packaged discovery is limited to packaged resource locations and -never falls through to writable cwd/dist development paths. A disabled base -experiment keeps the renderer sandbox enabled and embedded MPV unavailable. -When the base feature is enabled, a missing, mode-stripped, or incomplete -frame-copy runtime keeps the sandbox enabled and falls back to the native -engine. +regular development experiment flag) and one process-wide capability decision +has succeeded. On Linux x64 that decision validates the profile manifest, +regular-file/access modes, the complete declared bundled closure and hashes, +then runs `iptvnator_mpv_helper --runtime-probe` with a three-second timeout. +The probe loads dependencies through the normal ELF loader, initializes an +idle libmpv client, and creates EGL/OpenGL plus mpv render contexts without +opening media or shared memory. It must emit exactly one protocol-v1 JSON line +and return zero. Bundled profiles prepend only their packaged `native/lib` to +`LD_LIBRARY_PATH`; the system profile never injects a private loader path. +Packaged discovery is limited to packaged resource locations and never falls +through to writable cwd/dist development paths. A disabled base experiment or +any failed capability check keeps the renderer sandbox enabled and falls back +to the native engine. The result is cached by helper/manifest identity for the +process lifetime, so the startup and service gates cannot disagree. Changing the toggle requires an app restart because web preferences are fixed at window creation. @@ -232,14 +270,12 @@ the executable resolves it from its own directory. The after-pack hook restores the POSIX helper's executable mode after the asset copy, and optional/skipped native rebuilds remove stale helper/reader artifacts before reporting frame-copy availability. This cleanup prevents known leftover build -output; it is not a compatibility check for a complete but version-mismatched -runtime pair. Linux packages deliberately do NOT ship the helper yet: it links -the build host's system `libmpv`, which packaged apps cannot assume is -installed, so centralized after-pack preparation strips both possible helper -basenames and package validation rejects either one if it survives. The -support probe therefore reports frame-copy unavailable in Linux packages. -The engine is dev-build-only on Linux until bundled-libmpv runtime staging -lands (PORTING.md milestone 4). +output; the manifest and runtime probe are the compatibility check for a +complete Linux runtime. Linux x64 packages retain the helper and frame reader: +system packages resolve the declared `libmpv.so.2` through their package +manager, while portable and sandboxed profiles resolve the source-built closure +through `$ORIGIN/lib`. Foreign-architecture packages remove all native +artifacts and contain only the unavailable marker. Trade-offs and constraints: @@ -251,12 +287,12 @@ Trade-offs and constraints: MessagePort (costs one extra copy + GC churn since Electron ports clone ArrayBuffers) or a WebCodecs-based path. - Scope: on macOS Apple Silicon only by owner decision (2026-07-10); - Intel Macs keep the native-view engine. Linux (any arch) is ported — + Intel Macs keep the native-view engine. Official Linux frame-copy is x64 — headless EGL, works under native Wayland since nothing embeds into a - window; dev builds need `libmpv-dev`, `libegl-dev`, `libgl-dev`, - `libopengl-dev` and `libgbm-dev` (the helper links system libmpv, which - is legal out-of-process — the in-process libmpv ban still binds the - addon). The helper logs the chosen EGL display tier and the GL renderer + window; local system builds need `libmpv-dev`, `libegl-dev`, `libgl-dev`, + `libopengl-dev` and `libgbm-dev`. The helper links libmpv, which is legal + out-of-process; the in-process-libmpv ban still binds the addon and frame + reader. The helper logs the chosen EGL display tier and the GL renderer string to stderr. If an early tier selects Mesa software rendering (for example, while a proprietary NVIDIA driver is reachable through the default display or GBM), it probes the remaining tiers and uses software only when @@ -492,38 +528,73 @@ Current development behavior: - The addon build supports `darwin`, `win32`, and `linux`; Windows and Linux builds require running on that target OS. - The build script first looks for staged inputs at `vendor/embedded-mpv/-/`. On Linux, local development can fall back to distribution `libmpv-dev` headers and libraries; `LIBMPV_INCLUDE_DIR` and `LINUX_NATIVE_LIBRARY_DIR` override the default system paths. -- When the staged-input path is used, it must contain `include/mpv/client.h` and `runtime-manifest.json`. macOS and Windows staging also contains the platform runtime files that are bundled into the app. +- When the staged-input path is used, it must contain `include/mpv/client.h`, + `runtime-manifest.json`, and the platform runtime/build files. The Linux + source builder also stages the complete declared `.so` closure. - The compiled `.node` addon is copied into `dist/apps/electron-backend/native/embedded_mpv.node`. -- Bundled runtime files are copied into `dist/apps/electron-backend/native/lib/` for macOS and Windows. macOS copies `.dylib` and non-`.dylib` Mach-O dependencies; Windows copies the staged `mpv-2.dll`/`libmpv-2.dll`/`mpv.dll`/`libmpv.dll` runtime name plus import libraries. Linux writes an `external-mpv-process` manifest and intentionally leaves `libmpv.so` out of the package. -- Linux does not bundle or load `libmpv` in the Electron process. The addon can compile against staged or system-development MPV headers. Its native engine still depends on an X11/Xwayland window handle plus an `mpv` executable on `PATH`; the dev-only frame-copy helper is a separate process linked to system `libmpv` and renders through headless EGL, so it bypasses those native-engine prerequisites. +- Bundled runtime files are copied into + `dist/apps/electron-backend/native/lib/`. macOS copies `.dylib` and + non-`.dylib` Mach-O dependencies; Windows copies the staged + `mpv-2.dll`/`libmpv-2.dll`/`mpv.dll`/`libmpv.dll` runtime name plus import + libraries. Linux source-runtime builds copy only the manifest-declared + closure. +- Linux never bundles or loads libmpv in the Electron process. The native-view + addon remains X11/process-only; the inverse rule applies to frame-copy: + `iptvnator_mpv_helper` must link exactly the declared `libmpv.so.2`, while + the addon and frame reader must not. - `afterPack` copies `dist/apps/electron-backend/native/` into `app.asar.unpacked/electron-backend/native/` on macOS, Windows, and Linux so the addon, manifest, and runtime libraries are filesystem-addressable. -Current release caveat: +Linux release profiles: -- Release packaging requires a `vendored-lgpl` runtime manifest on macOS and Windows, and an `external-mpv-process` manifest on Linux. -- The Linux addon is built once per CI host architecture (x64). Linux packages for other architectures (arm64, armv7l) must not ship that foreign addon: `afterPack` replaces the native directory with an `embedded-mpv-unavailable.txt` marker explaining that embedded MPV is not bundled for that architecture, and package-layout verification rejects a foreign-architecture `embedded_mpv.node` while requiring the marker. +- `IPTVNATOR_LINUX_FRAME_COPY_PROFILE=system` builds DEB, RPM, and Pacman. + `afterPack` removes the private `lib` directory, writes a + `system-libmpv-frame-copy` manifest, and package metadata requires + `libmpv2`, `mpv-libs`, or `mpv`. +- `IPTVNATOR_LINUX_FRAME_COPY_PROFILE=portable` builds AppImage and Snap with + the pinned source-built closure and a `bundled-lgpl-frame-copy` manifest. +- `IPTVNATOR_LINUX_FRAME_COPY_PROFILE=flatpak` builds Flatpak with the same + source-built closure and manifest origin. +- The three profiles are separate packaging passes; mixing target sets fails + closed. Linux packages for other architectures (arm64, armv7l) must not ship + x64 native artifacts. `afterPack` replaces the native directory with + `embedded-mpv-unavailable.txt`, and package verification requires that + marker. +- Every packaged manifest names its exact artifacts, profile/targets, libmpv + SONAME, loader closure, byte sizes, SHA-256 hashes, package dependencies, and + native-view fallback. Artifact modes and ELF dependency isolation are + verified after packaging. - macOS release packaging rejects embedded MPV binaries linked to `/opt/homebrew` or `/usr/local`. -- Windows release packaging verifies that the platform runtime file is present when Embedded MPV is required. Linux release packaging verifies that the addon and manifest are present, no bundled `libmpv.so` files slipped into the package, and no development-only frame-copy helper survived `afterPack`. +- Windows release packaging verifies that the platform runtime file is present + when Embedded MPV is required. - Local development can opt into Homebrew `libmpv` only by setting `IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1`; packaged release validation rejects that runtime origin. -Before public release, packaging must: +Release packaging must: -- stage an LGPL-compatible `libmpv` runtime for each macOS/Windows release platform/architecture, and stage Linux MPV headers/build metadata for Linux +- stage an LGPL-compatible libmpv runtime for each bundled release + platform/architecture, including the pinned Linux x64 source runtime - collect indirect macOS dependencies expressed as absolute paths, `@loader_path`, or `@rpath` - rewrite macOS install names and dependency paths to app-relative paths such as `@loader_path` - code-sign and notarize the full macOS dependency set - ensure Windows runtime staging includes both the DLL and the import library used by `node-gyp` -- ensure Linux native builds do not gain a direct `libmpv` dependency; the runtime playback path is `mpv --wid` in a separate process -- publish the corresponding FFmpeg/libmpv source and build metadata for bundled macOS/Windows runtimes; Linux should document the distribution package versions used as build inputs +- ensure Linux Electron/addon/reader binaries do not gain a direct libmpv + dependency and the helper does +- execute the helper capability probe in each intended x64 package environment +- publish corresponding source archives, git/submodule records, checksums, + exact flags, local patches, and build scripts for every bundled runtime -Users on macOS and Windows do not need the MPV GUI application for this architecture. Linux currently requires an `mpv` executable because the supported backend is process-isolated. If the native addon/runtime prerequisites or Linux `mpv` executable are missing, embedded MPV is hidden/unsupported and the existing inline/external players remain available. +Users on macOS and Windows do not need the MPV GUI application for this +architecture. Linux native-view still requires an `mpv` executable. Linux +frame-copy system packages need the declared libmpv package, while AppImage, +Snap, and Flatpak carry their own runtime closure. If frame-copy prerequisites +are missing, x64 falls back to native-view; if all Embedded MPV prerequisites +are unavailable, the existing inline/external players remain available. ## Runtime Staging Runtime staging tooling lives in: -- `/Users/4gray/Code/iptvnator/tools/embedded-mpv/` -- `/Users/4gray/Code/iptvnator/vendor/embedded-mpv/` +- `tools/embedded-mpv/` +- `vendor/embedded-mpv/` Release runtime policy: @@ -547,11 +618,59 @@ pnpm embedded-mpv:build-runtime -- arm64 /tmp/embedded-mpv-prefix pnpm embedded-mpv:stage-runtime -- darwin arm64 /tmp/embedded-mpv-prefix ``` -During temporary PR and `master` artifact testing, CI can restore an exact-keyed GitHub Actions cache for the staged `vendor/embedded-mpv/-/` runtime and skip the expensive source build or archive staging path where one exists. The cache only contains `include/`, `lib/`, and `runtime-manifest.json`; it never contains the compiled `embedded_mpv.node` addon because that target depends on Electron headers, ABI, architecture, and build environment. Runtime cache entries are saved only from trusted repository refs, and tagged public macOS release builds continue to rebuild from pinned sources until a dedicated signed and attested runtime artifact flow exists. Windows CI uses a checksum-pinned `win32-x64` runtime archive configured through `IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL` and `IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256` repository variables or secrets on cache miss. Non-tag artifact builds have a pinned `zhongfly/mpv-winbuild` `mpv-dev-lgpl-x86_64` fallback so PR builds can produce a Windows embedded MPV artifact before repository variables are configured; tagged releases still require explicit repository configuration. The Windows archive helper accepts normal `lib/` + `bin/` prefixes and common `mpv-dev-lgpl` flat archives, including `libmpv-2.dll` names, and preserves the DLL basename expected by the import library; when the archive does not include `runtime-manifest.json`, it generates a minimal manifest from the archive URL/path and checksum. Linux stages Ubuntu package build inputs only; adding pinned source builders for Windows and Linux remains a separate release-hardening task. +Linux x64 builds the release runtime from pinned source inputs and stages it +before compiling the helper: -The CI builder pins FFmpeg `8.1`, mpv `0.41.0`, libplacebo `7.360.1`, libass `0.17.3`, FreeType `2.13.3`, FriBidi `1.0.16`, and HarfBuzz `8.5.0`. FFmpeg disables autodetected external libraries so Homebrew libraries cannot silently enter the runtime. Libplacebo is checked out from git with the submodules required by its Meson build because the generated GitHub archive does not include submodule contents. Even with Vulkan disabled, libplacebo still compiles Vulkan stubs and needs `3rdparty/Vulkan-Headers`. The generated manifest records source URLs, archive SHA-256 values where applicable, libplacebo git commit/submodule metadata, FFmpeg configure flags, and mpv Meson flags. The staging step normalizes macOS/Windows manifests to `origin: vendored-lgpl`, which release package validation requires on those platforms. +```bash +pnpm embedded-mpv:build-runtime:linux -- /tmp/embedded-mpv-linux-prefix +pnpm embedded-mpv:stage-runtime -- linux x64 /tmp/embedded-mpv-linux-prefix +``` -The Electron backend build consumes the staged runtime/build inputs and copies macOS/Windows runtime files into the native build output. Linux consumes staged MPV headers when available or distribution development headers for local builds, writes an `external-mpv-process` manifest, and does not copy `libmpv.so` into the package. macOS additionally rewrites Mach-O paths so `embedded_mpv.node` loads `@loader_path/lib/libmpv.2.dylib` instead of a machine-local Homebrew path. After `install_name_tool` rewrites any addon or runtime binary, the build re-signs that binary with an ad-hoc signature for local development. Release packaging still performs the normal app signing and notarization later. +The Linux builder is intentionally host-restricted to Linux x64. It checks +minimum build-tool versions, uses an owned staging directory plus atomic +publish, rejects host pkg-config/runtime leakage, rewrites every bundled +library to an `$ORIGIN` RUNPATH, and enforces the portable ABI ceilings +`GLIBC_2.35` and `GLIBCXX_3.4.30`. It also verifies the exact libmpv SONAME, +complete dependency closure, and absence of build-prefix paths. + +CI may restore exact-keyed caches for staged +`vendor/embedded-mpv/-/` runtimes. The cache includes only +generated headers, libraries, and manifests; it never contains +`embedded_mpv.node`. Runtime cache entries are saved only from trusted +repository refs. The Linux cache key covers the builder/stager and pinned +source inputs, and its accompanying source-compliance artifact remains a +release requirement, not a cache side effect. + +Windows CI uses a checksum-pinned `win32-x64` runtime archive configured +through `IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL` and +`IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256`. Non-tag artifact builds have +a pinned `zhongfly/mpv-winbuild` `mpv-dev-lgpl-x86_64` fallback; tagged +releases require explicit repository configuration. The archive helper accepts +normal `lib/` + `bin/` prefixes and common flat archives, preserves the DLL +basename encoded by the import library, and generates minimal build metadata +only when the archive lacks it. + +The Linux builder pins FFmpeg `8.1`, mpv `0.41.0`, libplacebo `7.360.1`, +libass `0.17.3`, FreeType `2.13.3`, FriBidi `1.0.16`, HarfBuzz `8.5.0`, +Expat `2.8.2`, Fontconfig `2.16.0`, OpenSSL `3.5.7`, hwdata `0.409`, and +libdisplay-info `0.1.1`. FFmpeg disables autodetected external libraries. +Libplacebo is checked out at an exact git commit with all required submodules. +The hwdata archive and its `pnp.ids` build input are pinned so +libdisplay-info cannot silently consume `/usr/share/hwdata` from the builder. +The generated manifest records source URLs/checksums or git commits, +submodules, licenses, exact flags, build-host/toolchain data, runtime hashes, +and the dynamic closure. FFmpeg/mpv remain LGPL-compatible and dynamically +linked; codecs outside that build configuration are not implied. + +The Electron backend build consumes the staged runtime/build inputs. On Linux +it links the helper against the verified staged `libmpv.so.2`, never against a +generic host `-lmpv`, then verifies the helper's `DT_NEEDED` and +`$ORIGIN/lib` RUNPATH with `readelf`. The addon and frame reader are checked +for the opposite invariant. The profile-aware packaging hook later retains or +removes the private closure. Local Linux builds may still use distribution +headers/libraries, but a required package build must use the staged manifest. +macOS additionally rewrites Mach-O paths and re-signs modified local binaries; +release signing/notarization still happens later. For local development before the vendored runtime exists, Homebrew can be used explicitly: @@ -593,7 +712,16 @@ For tagged macOS builds, CI must: For Windows builds, CI must restore the `win32-x64` staged runtime cache or stage the checksum-pinned runtime archive before `pnpm run build:backend`. The Windows job must set `IPTVNATOR_EMBEDDED_MPV_PLATFORM=win32`, `IPTVNATOR_EMBEDDED_MPV_ARCH=x64`, and `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for backend build, package make, and package-layout verification. CI narrows `electron-builder.json` to x64 Windows targets while only a `win32-x64` runtime is available. The Windows job is pinned to `windows-2022` until the Electron `node-gyp` toolchain can identify Visual Studio 18 from `windows-latest`. -For Linux builds, CI must set `IPTVNATOR_EMBEDDED_MPV_PLATFORM=linux`, `IPTVNATOR_EMBEDDED_MPV_ARCH=x64`, and `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` after staging the Ubuntu package build inputs. Linux package verification checks the `external-mpv-process` manifest and confirms that no bundled `libmpv.so` files are present. +For Linux builds, CI first builds or restores the pinned x64 source runtime and +stages it under `vendor/embedded-mpv/linux-x64`. It then runs three isolated +packaging passes with `IPTVNATOR_EMBEDDED_MPV_PLATFORM=linux`, +`IPTVNATOR_EMBEDDED_MPV_ARCH=x64`, +`IPTVNATOR_REQUIRE_EMBEDDED_MPV=1`, and one exact +`IPTVNATOR_LINUX_FRAME_COPY_PROFILE`. Each produced artifact is extracted and +verified, and the x64 helper probe runs in the intended runtime environment. +The source-compliance bundle is uploaded with the binary artifacts. ARM +artifacts are independently verified as marker-only and never run the x64 +helper. During temporary artifact tests, CI may also set `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for PR and `master` push jobs where a runtime is known to exist. After the artifacts are manually validated, remove temporary conditions so ordinary development builds leave `IPTVNATOR_REQUIRE_EMBEDDED_MPV` unset or `0`. This keeps the native feature in-tree without making every non-release build depend on runtime artifacts. @@ -605,7 +733,8 @@ The feature is still experimental. The largest risks are native-process risks, n - packaging can fail if `libmpv` or one of its platform runtime dependencies is missing, unsigned where signing applies, or linked to the wrong runtime path - macOS graphics behavior can vary across Intel, Apple Silicon, external displays, fullscreen transitions, and hardware decoding paths - Windows `HWND` and Linux X11/Xwayland embedding need packaged-app smoke coverage for focus, resize, and fullscreen behavior -- Linux native Wayland is unsupported until a dedicated Wayland embedding path exists +- Linux native-view remains unsupported on native Wayland; frame-copy has no + window-embedding dependency but still requires a working EGL probe - Homebrew `libmpv` builds can target a newer macOS version than IPTVnator's declared deployment target It is reasonable to ship the code in-tree behind the current experiment flag. It is not yet safe to make it the default player. It can be exposed as desktop experimental if support detection is strict, the UI clearly labels it experimental, and fallback to Video.js or external MPV/VLC stays available. @@ -616,8 +745,18 @@ If an embedded session fails to initialize, the app should keep the user in cont Do not expose embedded MPV broadly until these pass on every supported target: -- macOS/Windows packaged app starts without system `mpv` installed; Linux reports Embedded MPV unsupported with a clear message when system `mpv` is missing -- bundled `libmpv` and dependent runtime files pass macOS/Windows package validation; Linux package validation confirms the external-process manifest and absence of bundled `libmpv.so` +- macOS/Windows packaged apps start without system `mpv`; Linux x64 + frame-copy starts in each declared package profile, and a missing frame-copy + dependency falls back without crashing +- bundled libmpv and dependent runtime files pass package validation; + DEB/RPM/Pacman contain no private closure and declare the exact system + dependency +- Electron, its shipped libraries, `embedded_mpv.node`, and the frame reader + have no direct libmpv `DT_NEEDED`; the helper resolves the exact declared + libmpv runtime +- AppImage, DEB, RPM, Pacman, Snap, and Flatpak payloads pass extraction, + manifest/mode/ELF checks and the applicable helper probe; ARM payloads are + marker-only - macOS bundled `libmpv` and dependent dylibs pass code signing and notarization - VOD resume starts near the saved offset - series EOF emits `ended` and embedded MPV auto-continues only inside the current season diff --git a/tools/embedded-mpv/README.md b/tools/embedded-mpv/README.md index a4807823b..3386f92d8 100644 --- a/tools/embedded-mpv/README.md +++ b/tools/embedded-mpv/README.md @@ -1,21 +1,33 @@ # Embedded MPV Runtime -This folder contains tooling for preparing MPV runtime/build inputs for IPTVnator's experimental embedded MPV player. macOS and Windows bundle `libmpv`; Linux uses staged MPV headers for compilation and launches the system `mpv` executable at runtime. On Linux, `apps/electron-backend/build-embedded-mpv.js` also falls back to system headers when nothing is staged (`libmpv-dev`; override with `LIBMPV_INCLUDE_DIR`/`LINUX_NATIVE_LIBRARY_DIR`), so a plain distro dev setup builds without staging. The frame-copy helper (`iptvnator_mpv_helper`) additionally needs `libegl-dev`, `libgl-dev`, `libopengl-dev` (for the unversioned glvnd `libOpenGL.so` the linker resolves `-lOpenGL` against), and `libgbm-dev`, and links the system `libmpv` — allowed because it is a separate process; the in-process-libmpv ban still binds the addon. On Windows the same binding.gyp run builds `iptvnator_mpv_helper.exe` against the staged vendored runtime (import library + DLL, resolved from the helper's own directory at runtime) plus `opengl32.lib`; no toolchain beyond the MSVC workload and Windows SDK that node-gyp already requires. +This directory owns the source builders, staging, manifests, and archive +helpers for IPTVnator's experimental Embedded MPV runtime. + +The Linux architecture has a strict process boundary: + +- Electron, `embedded_mpv.node`, and `embedded_mpv_frame_reader.node` must not + load or link libmpv. +- Native-view starts a separate system `mpv --wid` process. +- Frame-copy starts `iptvnator_mpv_helper`; only that helper may link libmpv. + +Do not weaken this boundary to simplify packaging. A missing helper/runtime +must make frame-copy unavailable and leave native-view as the safe x64 +fallback. ## Runtime Policy -Release builds must use an LGPL-compatible runtime: +Release builds use an LGPL-compatible, dynamically linked runtime: -- FFmpeg must be built without `--enable-gpl` and without `--enable-nonfree`. -- mpv must be built with `-Dlibmpv=true` and `-Dgpl=false`. -- The runtime must be dynamically linked so users can inspect and replace LGPL libraries. -- The exact source URLs, versions, build flags, local patches, and checksums must be published with the release. +- FFmpeg is built without `--enable-gpl` and `--enable-nonfree`. +- mpv is built with `-Dlibmpv=true` and `-Dgpl=false`. +- Bundled libraries remain individually replaceable under `native/lib`. +- Exact source URLs, versions, checksums or git commits, submodules, licenses, + build flags, local patches, and build scripts are published with the release. -Do not ship the Homebrew `mpv` runtime. It is acceptable only for local development when `IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1` is set, and release packaging rejects it. +Homebrew mpv is local-development-only. It requires +`IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1`, and release validation rejects it. -## Expected Layout - -The native addon build consumes: +## Generated Layout ```text vendor/embedded-mpv/ @@ -29,126 +41,145 @@ vendor/embedded-mpv/ runtime-manifest.json win32-x64/ include/mpv/client.h - lib/libmpv-2.dll # or mpv-2.dll/mpv.dll/libmpv.dll - lib/libmpv.dll.a # or mpv.lib/mpv-2.lib + lib/libmpv-2.dll # accepted basename variants are preserved + lib/libmpv.dll.a # or an MSVC import library runtime-manifest.json linux-x64/ include/mpv/client.h + lib/libmpv.so + lib/libmpv.so.2 + lib/ runtime-manifest.json ``` -The generated `lib/` and `include/` directories are release inputs, not source files. They are ignored by git by default. +These directories are generated release inputs and are ignored by git. +`runtime-manifest.json` is the profile-neutral source/build manifest. Packaging +writes a normalized `embedded-mpv-runtime.json` beside the native artifacts. -## Staging A Built Runtime +## Building And Staging -After building an LGPL-compatible prefix for one platform/architecture, stage it with: +Stage an existing compatible prefix with: ```bash -pnpm embedded-mpv:stage-runtime -- darwin arm64 /path/to/lgpl-prefix -pnpm embedded-mpv:stage-runtime -- darwin x64 /path/to/lgpl-prefix -pnpm embedded-mpv:stage-runtime -- win32 x64 /path/to/lgpl-prefix -pnpm embedded-mpv:stage-runtime -- linux x64 /path/to/lgpl-prefix +pnpm embedded-mpv:stage-runtime -- darwin arm64 /path/to/prefix +pnpm embedded-mpv:stage-runtime -- darwin x64 /path/to/prefix +pnpm embedded-mpv:stage-runtime -- win32 x64 /path/to/prefix +pnpm embedded-mpv:stage-runtime -- linux x64 /path/to/prefix ``` -For compatibility, the legacy macOS-only staging command is still available: +Build the pinned macOS or Linux source runtime first when no prefix exists: ```bash -pnpm embedded-mpv:stage-runtime:macos -- arm64 /path/to/lgpl-prefix -pnpm embedded-mpv:stage-runtime:macos -- x64 /path/to/lgpl-prefix +pnpm embedded-mpv:build-runtime -- arm64 /tmp/macos-prefix +pnpm embedded-mpv:stage-runtime -- darwin arm64 /tmp/macos-prefix + +pnpm embedded-mpv:build-runtime:linux -- /tmp/linux-prefix +pnpm embedded-mpv:stage-runtime -- linux x64 /tmp/linux-prefix ``` -The prefix must contain `include/mpv/client.h` and the platform runtime/build files: +The Linux builder runs only on Linux x64. It requires the tool versions and +system development interfaces declared in `build-linux-runtime.cjs`, including +Meson 1.6 or newer, Ninja, CMake, NASM, pkg-config, patchelf, and `readelf`. +It builds into an owned staging directory and publishes atomically, so it will +not delete or overwrite an arbitrary destination. -- macOS: `lib/libmpv.2.dylib` or `lib/libmpv.dylib` plus all non-system dylib dependencies -- Windows: `lib/mpv.lib`, `lib/mpv-2.lib`, or `libmpv.dll.a`, and `bin/` or `lib/` containing `mpv-2.dll`, `libmpv-2.dll`, `mpv.dll`, or `libmpv.dll` -- Linux: `include/mpv/client.h`; CI also records the `libmpv-dev` and `mpv` package versions used as build inputs. Linux runtime playback uses the system `mpv` executable and does not bundle `libmpv.so`. +The pinned Linux source stack currently includes FFmpeg 8.1, mpv 0.41.0, +libplacebo 7.360.1, libass 0.17.3, FreeType 2.13.3, FriBidi 1.0.16, +HarfBuzz 8.5.0, Expat 2.8.2, Fontconfig 2.16.0, OpenSSL 3.5.7, hwdata +0.409, and libdisplay-info 0.1.1. The builder stages a private pinned +`pnp.ids`/`hwdata.pc`; libdisplay-info is not allowed to consume the build +host's `/usr/share/hwdata`. -If the prefix contains `runtime-manifest.json`, the staging script copies its build metadata into the vendored manifest. At minimum, record: +Before publication, the Linux builder verifies: -- FFmpeg version, source URL, checksum, configure flags, and patches -- mpv version, source URL, checksum, Meson flags, and patches -- source-distribution URL for the corresponding release +- every archive digest and git/submodule commit; +- the exact FFmpeg/mpv flags and LGPL policy; +- an exact `libmpv.so.2` SONAME and complete reachable shared-library closure; +- `$ORIGIN` RUNPATHs with no build-prefix paths or undeclared host fallback; +- the external system-library allowlist; +- `GLIBC_2.35` and `GLIBCXX_3.4.30` ABI ceilings; +- file hashes, byte sizes, build inputs, licenses, and source obligations. -## Building The CI Runtime +## Linux Package Profiles -Tagged macOS release builds build the runtime from pinned source archives before `electron-backend:build`. The workflow can also enable this path temporarily for macOS PR artifact testing: +Set one exact `IPTVNATOR_LINUX_FRAME_COPY_PROFILE` per packaging pass: -```bash -pnpm embedded-mpv:build-runtime -- arm64 /tmp/embedded-mpv-prefix -pnpm embedded-mpv:stage-runtime -- darwin arm64 /tmp/embedded-mpv-prefix -``` +| Profile | Formats | Runtime handling | +| ---------- | ---------------- | ------------------------------------------------------------ | +| `system` | DEB, RPM, Pacman | Remove `native/lib`; require `libmpv2`, `mpv-libs`, or `mpv` | +| `portable` | AppImage, Snap | Retain the pinned LGPL closure under `native/lib` | +| `flatpak` | Flatpak | Retain the same pinned LGPL closure under `native/lib` | -Linux CI does not build libmpv from source. It installs Ubuntu runner packages -(`libmpv-dev` and `mpv`), stages their headers and build metadata under -`vendor/embedded-mpv/linux-x64/`, and requires the native addon/package layout -to be present. Linux playback does not load or bundle `libmpv` in the Electron -process; the addon creates an X11 child window and starts a system `mpv --wid` -process at runtime. +Profiles cannot share one Electron Builder pass because its targets reuse the +same unpacked application directory. A missing or unsupported profile, or a +target from another profile, fails packaging. -Windows CI does not build libmpv from source. It restores an exact-keyed cache -for `vendor/embedded-mpv/win32-x64/`; on cache miss it stages a checksum-pinned -LGPL-compatible archive from repository configuration: - -```text -IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL -IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 -``` - -The values can be repository variables or secrets. Prefer variables when PR -artifact builds from same-repository branches should include Embedded MPV. For -non-tag artifact builds only, the workflow falls back to a checksum-pinned -`zhongfly/mpv-winbuild` `mpv-dev-lgpl-x86_64` archive when those variables are -unset. Tagged release builds must provide the repository configuration -explicitly. - -The Windows job is pinned to `windows-2022` while the current Electron -`node-gyp` toolchain cannot identify Visual Studio 18 from `windows-latest`. - -The archive must contain a Windows x64 prefix with `include/mpv/client.h`, a -libmpv import library, and `mpv-2.dll`/`mpv.dll` or -`libmpv-2.dll`/`libmpv.dll`. The archive can use either the normal prefix layout -(`lib/` and `bin/`) or the common `mpv-dev-lgpl` flat layout with the import -library and DLL in the archive root. The staged runtime preserves the DLL -basename from the archive because Windows import libraries encode the DLL name -that native binaries must load at runtime. Package validation reads the -frame-copy helper's PE imports and requires that exact DLL basename beside -`iptvnator_mpv_helper.exe`; a different accepted MPV DLL name or a copy only -under `native/lib/` is not sufficient. If -`runtime-manifest.json` is missing, CI generates a minimal manifest from the -archive URL/path and checksum; release-ready runtime archives should still -provide full source/build metadata. - -During temporary PR and `master` artifact testing, CI restores an exact-keyed GitHub Actions cache for the staged `vendor/embedded-mpv/-/` runtime before falling back to the macOS source build or Windows runtime archive where available. The cache key includes the target platform, architecture, macOS deployment target, Xcode version when available, a hash of the Windows runtime checksum when applicable, and hashes of the runtime build/staging scripts. Cache entries are saved only from trusted repository refs and are treated strictly as a speed optimization; tagged macOS release builds continue to rebuild from pinned sources unless a future signed and attested runtime artifact flow is introduced. - -The builder currently pins: - -- FFmpeg `8.1`, configured without `--enable-gpl` or `--enable-nonfree`, and with autodetected external libraries disabled -- mpv `0.41.0`, configured with `-Dlibmpv=true -Dgpl=false` -- libplacebo `7.360.1`, checked out from git with the `glad`, Python template, `fast_float`, and `Vulkan-Headers` submodules required by its Meson build -- libass `0.17.3` plus FreeType, FriBidi, and HarfBuzz - -The build manifest records source URLs, downloaded archive SHA-256 values where applicable, libplacebo git commit/submodule metadata, and the exact FFmpeg/mpv flags. The staged macOS/Windows manifest is normalized to `origin: vendored-lgpl`, which is the only embedded MPV runtime origin allowed in required macOS/Windows release packaging. +Linux frame-copy release artifacts are x64-only. Non-x64 packages are always +marker-only even if environment variables point at the x64 staged runtime. ## Build Integration -`apps/electron-backend/build-embedded-mpv.js` builds the native addon against the staged runtime/build inputs, copies macOS/Windows runtime libraries into `apps/electron-backend/native/build/Release/lib/`, rewrites macOS Mach-O paths to `@loader_path`, and writes `embedded-mpv-runtime.json`. Linux builds use the staged (or system) MPV headers and system X11 development libraries, write an `external-mpv-process` manifest, and the addon must not copy or link directly to `libmpv`; CI validates this with package checks and `ldd`. The frame-copy helper executable built by the same run is the inverse: CI verifies it DOES link `libmpv` (separate process). +`apps/electron-backend/build-embedded-mpv.js` builds the addon, frame reader, +and helper against the staged inputs. On Linux it links the helper to the +verified staged libmpv path rather than a generic host `-lmpv`, then checks +with `readelf` that: -For local macOS development with Homebrew `mpv`, use: +- the helper has exactly the declared libmpv `DT_NEEDED`; +- the helper RUNPATH is `$ORIGIN/lib`; +- the addon and frame reader have no libmpv dependency; +- no runtime dependency contains an absolute/build-prefix loader path. + +The package hook copies native artifacts into +`app.asar.unpacked/electron-backend/native/`, selects the system or bundled +layout, restores exact file modes, and writes the packaged manifest. Package +validation also scans the Electron executable and all shipped Electron +libraries for a direct libmpv dependency. + +At startup, Linux x64 frame-copy is advertised only after the main process +validates that manifest/files and successfully executes: + +```bash +iptvnator_mpv_helper --runtime-probe +``` + +The bounded probe initializes idle libmpv plus EGL/OpenGL and mpv render +contexts without media or shared memory. A timeout, loader failure, malformed +protocol, missing file, hash mismatch, or unusable graphics path returns a +stable reason and keeps the BrowserWindow sandbox enabled. + +## CI And Source Distribution + +Linux CI builds or restores the pinned source runtime once, then packages and +verifies `system`, `portable`, and `flatpak` independently. Every artifact is +extracted for manifest, mode, package-metadata, ELF-isolation, and helper-probe +checks. System formats are probed after their declared dependency is installed; +Snap and Flatpak also require a sandboxed probe where the runner supports it. + +The binary release must include a source-compliance bundle containing the exact +downloaded archives (including hwdata), the recorded libplacebo checkout or git +bundle and submodules, builder/stager/manifest code, generated runtime manifest, +and local patches. A cache hit does not remove this obligation. + +Windows CI stages a checksum-pinned x64 LGPL archive. The DLL basename encoded +in its import library is preserved and must be present beside +`iptvnator_mpv_helper.exe`. Tagged releases require explicit repository +configuration; the public fallback is for non-tag artifacts only. + +## Local Development + +Linux can use distribution development packages for an unshipped local build +(`libmpv-dev`, EGL/OpenGL/GBM development files, and X11 headers). Overrides: +`LIBMPV_INCLUDE_DIR` and `LINUX_NATIVE_LIBRARY_DIR`. Required/release package +builds must use the pinned staged runtime and manifest. + +On macOS: ```bash pnpm run serve:backend:embedded-mpv ``` -The script rebuilds the native addon with `IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1` before starting Electron with the experimental player enabled. Use this only for local testing; release packaging rejects the resulting `homebrew-dev` runtime manifest. +This explicitly permits Homebrew for the local native build and enables the +experiment. It is not a release path. -The `afterPack` hook copies `dist/apps/electron-backend/native/` into `app.asar.unpacked/electron-backend/native/` so the addon, runtime manifest, and runtime libraries are available as real files where needed. Linux packages include the addon and manifest, but no bundled `libmpv.so`, and the hook strips `iptvnator_mpv_helper` from Linux packages (it links the build host's system libmpv; the frame-copy engine stays dev-build-only on Linux until bundled-runtime staging lands). - -During release packaging, `tools/packaging/electron-after-pack.cjs` verifies that macOS/Windows packages use a `vendored-lgpl` runtime/build input set. macOS artifacts additionally verify that Mach-O dependencies have no `/opt/homebrew` or `/usr/local` dynamic links for embedded MPV. Linux artifacts verify that the addon and `external-mpv-process` manifest are present, that no bundled `libmpv.so` files are present, and the runtime support check verifies that `mpv` is available on `PATH`. - -Set `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` when packaging a release artifact that must include Embedded MPV. The same variable is temporarily enabled for macOS PR and `master` push artifacts while the bundled runtime is being tested. Linux CI packaging requires Embedded MPV after staging the Ubuntu package build inputs. Windows CI packaging now requires Embedded MPV for x64 artifacts: the job restores the staged runtime cache or stages the checksum-pinned runtime archive, then fails backend build, package make, or package-layout verification if the addon/runtime is missing. - -## Platform Notes - -- macOS keeps the existing libmpv render-context backend because mpv `wid` stays black inside Electron on macOS. -- Windows uses an embedded child `HWND` and passes it to mpv through `wid`. The experimental frame-copy engine instead renders offscreen through WGL into the app canvas (no child window) and shares frames over a session-local named file mapping. -- Linux uses an X11 child window and starts a system `mpv --wid` process for that window. Native Wayland is not supported in v1; run under X11/Xwayland so `DISPLAY` is set and `mpv` can honor the X11 window id. The experimental frame-copy engine has no window embedding at all (offscreen EGL into a renderer canvas) and therefore works under native Wayland — dev builds only for now. +See `docs/architecture/embedded-mpv-native.md` for the runtime capability, +fallback, controls, and packaged-release contracts. diff --git a/vendor/embedded-mpv/README.md b/vendor/embedded-mpv/README.md index 568b2cdb4..1ac449b8b 100644 --- a/vendor/embedded-mpv/README.md +++ b/vendor/embedded-mpv/README.md @@ -11,10 +11,22 @@ Generated architecture folders are expected at: - `vendor/embedded-mpv/linux-x64/` Each generated folder must contain `include/mpv/client.h` and -`runtime-manifest.json`. macOS and Windows folders also contain platform -runtime/build inputs under `lib/` or `bin/`. The binary runtime directories are -ignored by git by default; generate, stage, or restore them in release packaging -jobs before building the Electron backend. +`runtime-manifest.json`. Platform runtime/build inputs live under `lib/` or +`bin/`. In particular, `linux-x64/lib/` contains the pinned, dynamically linked +LGPL-compatible libmpv closure used to link the out-of-process frame-copy +helper. The binary runtime directories are ignored by git; generate, stage, or +restore them before building the Electron backend. -Linux uses this directory for MPV headers and build metadata only. Linux -packages launch the system `mpv` executable and must not bundle `libmpv.so`. +Linux package profiles consume that one staged x64 source runtime differently: + +- DEB/RPM/Pacman remove the private closure and declare the system libmpv + dependency. +- AppImage/Snap/Flatpak retain the manifest-declared closure under + `app.asar.unpacked/electron-backend/native/lib/`. +- Non-x64 Linux packages retain no native artifacts and ship only the + unavailable marker. + +Only `iptvnator_mpv_helper` may link libmpv. Electron, +`embedded_mpv.node`, and `embedded_mpv_frame_reader.node` must remain free of +direct libmpv dependencies. See `tools/embedded-mpv/README.md` and +`docs/architecture/embedded-mpv-native.md`.