mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
Port the embedded mpv frame-copy pipeline to Windows with WGL rendering and named shared memory. Includes packaging validation, platform gates, tests, and architecture documentation.
13 KiB
13 KiB
Frame-copy engine — Windows/Linux porting handoff
Historical handoff and maintenance notes for the completed Linux and Windows ports. The canonical runtime contract now lives in
docs/architecture/embedded-mpv-native.md.
State as of 2026-07-15
- The macOS base shipped through PR #1169 and is now merged into
master. - The engine works end-to-end on macOS Apple Silicon: Settings toggle → restart → helper process renders mpv offscreen → shm ring → preload pump → WebGL canvas. Verified live with real IPTV + Stalker VOD.
- Scope decision: macOS = arm64 only (Intel Macs keep the native engine).
- The Linux port below is DONE in PR #1171 (rebased directly onto the
merged #1169 result) — headless
EGL helper, portable clock, reader on
__linux__, TS gates, i18n, measurements in RESULTS.md. Verified end-to-end in-app on Ubuntu 25.04 (Wayland session) with the xtream mock portal. Dev-build-only on Linux: the helper links system libmpv andelectron-after-pack.cjsstrips it from packages until milestone 4 (Linux bundled-libmpv runtime) — remove that strip when milestone 4 lands. - The Windows port below landed through PR #1175 — WGL GlContext twin,
QPC clock +
Local\named-file-mapping shm twins (the protocol keeps POSIX-style/impv-*names; the native sides derive the mapping name), reader compiled as C++ on_WIN32(MSVC has no C11<stdatomic.h>), helper.exebinding.gyp target linking the vendored import lib + opengl32 (mpv DLL resolved from the exe's own directory), TS gates +.exehelper discovery, i18n, packaging/CI guards (win32 packages must ship helper + reader; MSVC intermediates and import libs excluded from dist). The open iGPU perf gate is CLOSED: RESULTS.md rows on the same i7-1165G7/Iris Xe laptop as the Linux section (dual boot) — 1080p60 sustained (clean 60 s run), the viewport-price claim reproduces, d3d11va hwdec active (5.5× CPU drop), torn=0 everywhere. Machine gotchas for future sessions: Windows 11 Smart App Control must be OFF to run locally-built unsigned helpers, and a fresh Windows install can leave the iGPU on the Basic Display Adapter — bind the real Intel driver (pnputil /remove-device+/scan-devicesonce the driver is in the store) or WGL has no 3.2 core context and no d3d11va. - PR #1169 credits larsemig's idea (#1154 comment 4932807350). Shared-player controls remain a separate integration after this platform stack; do not conflate that UI layer with the frame-copy transport ports.
What "porting" means
Only the helper (and a small reader-addon branch) is platform-specific. The stdio protocol, shm layout, TS adapter, main-process service, preload pump, and Angular UI are shared and already shipped.
apps/electron-backend/native/helper/ # state after all three ports:
├── mpv_frame_helper.cpp # portable: protocol, mpv session, snapshots
├── frame_helper_io.h # portable: TSV-in/JSON-out, percent-encoding
├── frame_shm.h # portable layout + shared clock (POSIX
│ # CLOCK_MONOTONIC / Windows QPC) + the Windows
│ # Local\ mapping-name derivation
├── frame_helper_render.h # portable render + ShmRing (POSIX shm_open /
│ # Windows CreateFileMapping twins)
└── frame_helper_gl.h # PLATFORM SEAM: GlContext — CGL (macOS),
# EGL (Linux) and WGL (Windows)
apps/electron-backend/native/src/embedded_mpv_frame_reader.c
# real impl on __APPLE__ + __linux__ + _WIN32
# (compiled as C++ there), stub elsewhere
All three ports have landed; new platforms follow the same seams: a
GlContext twin in frame_helper_gl.h, shm create/open + frame_shm_now_ns
twins, the TS gate in embedded-mpv-frame-copy-platform.util.ts, packaging.
Branching & merge strategy
- The platform stack landed in order #1169 → #1171 → #1175.
- Each child was rewritten onto the latest squash-merged
masterusing only its platform-specific commit range; the old parent history was not replayed. - New frame-copy work branches directly from
master. Keep future stacks at most one unmerged level deep and land platform measurements with their port.
Per-platform task lists
Linux (DONE 2026-07-11 — see the update in "State" above)
- Render backend: headless EGL (
EGL_PLATFORM_SURFACELESS_MESA/eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA)with fallback to default-display and GBM candidates) + the same FBO/PBO/readback code. Each candidate is validated through context bind andGL_RENDERER; a hardware renderer wins over an earlier software tier. mpv resolves linked core GL symbols throughdlsym(RTLD_DEFAULT)and falls back toeglGetProcAddressfor extensions. - shm: POSIX
shm_openworks as-is. The ONLY blocker in shared code:clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)is macOS-only — replace with a portablenow_ns()(clock_gettime(CLOCK_MONOTONIC, ...)) inframe_helper_render.h,frame_shmusers, and the reader addon. Keep producer/consumer on the SAME clock. - Reader addon: change
#ifdef __APPLE__to also cover__linux__(code is already POSIX apart from the clock call). - binding.gyp: helper target gets a linux branch — link system libmpv
for dev first (
-lmpv); bundled-libmpv runtime is a later packaging task. NOTE: the LINUX ADDON must still not link libmpv (that rule is for in-process only — the helper is a separate process, linking is the whole point and is legal there). - TS gates:
isFrameCopyEngineActive/isFrameCopyAvailableinembedded-mpv-native.service.ts(drop the darwin/arm64-only condition for linux),EmbeddedMpvFrameCopyAdapter.isSupported, Settings copy + i18n descriptions currently say "macOS Apple Silicon only". - Big prize: no window embedding → native Wayland just works; later
bundle libmpv → no system-mpv-on-PATH requirement → Flatpak/Snap. Update
the Linux support matrix in
docs/architecture/embedded-mpv-native.mdwhen this lands.
Windows (DONE 2026-07-12 — see the update in "State" above)
One deviation from the checklist below: the WGL context uses a hidden regular window, not a message-only one — SetPixelFormat needs a display-capable DC.
- Render backend: WGL headless — create a hidden message-only window +
dummy pixel format,
wglCreateContextAttribsARB3.2 core, then the same FBO/PBO path.MPV_RENDER_API_TYPE_SWis the fallback bring-up if WGL fights (CPU render, still proves the pipeline). - shm:
CreateFileMapping(INVALID_HANDLE_VALUE, ...)+MapViewOfFilebehind the sameFrameShmHeaderlayout. Name mapping:Local\\impv-...(session-local namespace). Reader addon gets the#ifdef _WIN32twin. Atomics:std::atomic<uint64_t>fine with MSVC; the C reader can useInterlockedCompareExchange-free plain_Atomic-equivalent via C11<stdatomic.h>(clang-cl) or volatile+MemoryBarrier— simplest is compiling the reader as C++ on Windows. - Process control:
child.kill('SIGTERM')on Windows is TerminateProcess (no graceful signal) — the quit command + stdin-EOF paths (already implemented) are the graceful route; keep the kill as the hard fallback. stdio pipes work unchanged. - binding.gyp: helper
.exetarget underOS=="win"linking the existing import lib (LIBMPV_IMPORT_LIBenv — see build-embedded-mpv.js Windows path). DLL resolution: the helper exe sits next tolib/with the mpv DLL — either copy the DLL beside the exe at build time or callSetDllDirectory/AddDllDirectoryat startup. Watch the documented import-library-vs-DLL-basename gotcha (embedded-mpv-native.md). - TS gates + audio: same switches as Linux. WASAPI audio comes from mpv directly — nothing to do.
- This is the open PERFORMANCE gate: mid-range iGPU laptop numbers decide go/no-go (RESULTS.md has the methodology + reference M1 numbers: 4K60 sustained, ~10 ms produce→upload, zero torn frames).
Both platforms — shared chores
validatePackagedEmbeddedMpvintools/packaging/embedded-mpv-packaging.cjscurrently requires frame-copy artifacts on darwin only — extend per platform when artifacts ship. Keep tests host-agnostic (CI runs them on a Linux runner; asserting an empty error list for a darwin dir fails there with "link validation must run on a macOS host" — already fixed once, don't regress).getMainWindowScaleFactor(Electronscreen) is cross-platform — no work needed; the helper receives device pixels.- Sandbox story: the flag relaxes the BrowserWindow sandbox for the preload reader require. Same trade-off applies on Win/Linux. Revisit-before- default-on candidates are in the architecture doc.
Hard-won gotchas (do not rediscover these)
- Preload + tslib: repo tsconfig has
target: es2015; ANY construct that emits TS helpers in preload code (async/await, object spread in downlevel positions) withimportHelpers: truemakes webpack externalizetslib→ the sandboxed preload dies withmodule not found: tslib→window.electrondisappears app-wide.apps/electron-backend/tsconfig.app.jsonnow setsimportHelpers: false— NEVER revert it. Symptom to recognize: "Unable to load preload script" in renderer console. - V8 memory cage:
napi_create_external_arraybufferover shm aborts in Electron. The reader MUST memcpy into a V8 buffer. Budgeted (~1.2 ms at 4K). - Frame orientation: helper renders with
MPV_RENDER_PARAM_FLIP_Y=1andglReadPixelsreads rows bottom-up → the shm buffer is already in texture order. The pump shader samples with UN-flipped uv. Adding a second flip shows upside-down video (bug already made and fixed once). - BGRA fast path: readback as
GL_BGRA/GL_UNSIGNED_INT_8_8_8_8_REV, upload as RGBA, swizzle.bgrin the fragment shader. On Windows check whether BGRA readback stays the fast path per driver; measure, don't assume. - Aspect: mpv reports unset
video-aspect-overrideas"-1.000000"→ normalize to"no". The helper aspect-fits the FBO todwidth/dheightinside the viewport (no baked letterbox bars) and bumps a shm generation (<base>-g<N>) on every size change; the pump re-attaches via the FRAME_SOURCE_CHANGED event. - Stale attach race: attach/detach bump a shared epoch in the pump; every await re-checks it. Keep that invariant if touching the pump.
- Lifecycle: dispose escalation is quit-command → stdin.end() (helper
exits on EOF) → SIGTERM(500 ms) → SIGKILL(2 s). The SERVICE also reaps
all sessions on
render-process-gone/did-navigate(renderer crash or hard reload never runs Angular teardown — without this, helpers leak). Watchps | grep iptvnator_mpv_helperduring any manual test session. - Stale opt-in:
isFrameCopyEngineActive()requires the helper binary on disk; missing helper = silent fallback to native, and the Settings checkbox stays visible while the saved value is true so it can always be cleared. - node-gyp naming: module targets emit
<target_name>.node(noproduct_nameneeded); the helper uses the"type": "none"default + per-OS"type": "executable"override trick in binding.gyp. - snapshot protocol: helper's
snapshotJSON mirrorsNativeEmbeddedMpvSessionSnapshotverbatim (volume 0..1,nullable duration/track ids,videoWidth/videoHeightwhen known). Status semantics are ported fromembedded_mpv.mm— END_FILE reason mapping,eof-reached⇒ended(keep-open), pause gated on loadedPath, only fatal/load errors flip status. Don't invent new mappings.
Testing recipes
- Helper standalone (no Electron):
(printf 'load\turl=av://lavfi:testsrc2=size=640x360:rate=30\n'; sleep 5; printf 'quit\n') | ./iptvnator_mpv_helper --shm-base /impv-t --width 1280 --height 720→ expectshmgenerations,snapshotevents at 4 Hz, aspect-fit generation after video loads. - Reader probe (any Node ≥18):
node -e "const r=require('.../embedded_mpv_frame_reader.node'); const i=r.open('/impv-t-g2'); ..."→latestSeq()advancing + pixel min/max spread. - In-app:
IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY=1 pnpm run serve:backend:embedded-mpvor the Settings toggle (+restart). Second parallel instance for CDP testing: build, then runelectron dist/apps/electron-backend/main.js --remote-debugging-port=9223 --user-data-dir=/tmp/xwithELECTRON_IS_DEV=0for the file:// renderer (dist package.json has nomainfield — point at main.js explicitly; a separate user-data-dir avoids the Chromium profile singleton). - Perf gate: follow
RESULTS.mdmethodology (STATS/LONGRUN lines, present-interval sd/p99/late counters). Reference: M1 Pro tables therein. The spike harness in this directory is macOS-only; for Windows/Linux measure through the real app + helper stderr or port collect-results.sh.
Suggested milestone order
- Completed in #1171: Linux helper bring-up (EGL + portable clock) → lavfi smoke → in-app behind flag → measure.
- Windows helper bring-up (WGL, named shm, reader twin) → same ladder → iGPU laptop numbers = the decisive open gate.
- Packaging: per-platform artifact validation + runtime staging.
- Only then: revisit Linux bundled-libmpv + Flatpak/Snap story.