docs(embedded-mpv): document Linux frame-copy packaging

This commit is contained in:
4gray committed 2026-07-17 22:42:36 +02:00
1 parent f9207a1884
commit 9bc8d74f35
5 files changed
+396 -164

No files matched your search

+26
View File
@@ -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`
+25 -1
View File
@@ -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=<x11-window>` 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 `<canvas data-embedded-mpv-frame>`, 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
`<canvas data-embedded-mpv-frame>`. 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
+197 -58
View File
@@ -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/<platform>-<arch>/`. 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/<platform>-<arch>/` 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/<platform>-<arch>/` 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
+130 -99
View File
@@ -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/<declared closure>
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/<platform>-<arch>/` 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.
+18 -6
View File
@@ -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`.