feat(embedded-mpv): frame-copy engine wiring in main process and preload

EmbeddedMpvFrameCopyAdapter implements the NativeEmbeddedMpvAddon surface
over a per-session helper process (spawn, stdio protocol, snapshot cache,
graceful quit->SIGTERM->SIGKILL teardown), so EmbeddedMpvNativeService
reuses its polling/diff/power-blocker/recording logic unchanged. The
IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY flag (darwin/arm64 only) routes
getAddon() to the adapter and reports engine: 'frame-copy' in support.

The preload frame pump loads the shm reader addon, copies the newest frame
once per rAF into a reused buffer, and uploads it to WebGL2 on the
renderer's canvas — no frame data crosses the contextBridge; the bridge
only gains attachEmbeddedMpvFrameView/detachEmbeddedMpvFrameView. The
experiment flag relaxes the window sandbox for that native require;
contextIsolation and nodeIntegration:false stay on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-15 18:20:45 +02:00
1 parent aabce726e3
commit 26ea7dc050
9 files changed
+826 -4

No files matched your search

@@ -909,6 +909,14 @@ export interface ElectronBridgeApi {
disposeEmbeddedMpvSession: (
sessionId: string
) => Promise<EmbeddedMpvSession | null>;
/**
* Frame-copy engine only: start/stop the preload frame pump that
* uploads helper frames onto the renderer's
* `<canvas data-embedded-mpv-frame>` element. Optional because older
* preload builds do not ship the pump.
*/
attachEmbeddedMpvFrameView?: (sessionId: string) => Promise<boolean>;
detachEmbeddedMpvFrameView?: () => void;
downloadsStart: (
data: ElectronBridgeDownloadStartPayload
) => Promise<ElectronBridgeDownloadStartResult>;
@@ -22,11 +22,32 @@ export interface EmbeddedMpvCapabilities {
recording: boolean;
}
export type EmbeddedMpvEngine = 'native' | 'frame-copy';
export interface EmbeddedMpvSupport {
supported: boolean;
platform: string;
reason?: string;
capabilities?: EmbeddedMpvCapabilities;
/**
* Rendering engine the main process will use for new sessions.
* `native` = platform video surface (NSOpenGLView/HWND/X11 wid),
* `frame-copy` = helper process + shm ring + renderer canvas.
*/
engine?: EmbeddedMpvEngine;
}
/**
* Where the renderer's frame pump finds the current shm frame ring of a
* frame-copy session. A new generation is announced after every viewport
* resize; the pump re-attaches to the new segment.
*/
export interface EmbeddedMpvFrameSource {
shmName: string;
width: number;
height: number;
generation: number;
readerPath: string;
}
export interface EmbeddedMpvAudioTrack {
@@ -66,6 +66,9 @@ export const EMBEDDED_MPV_SELECT_RECORDING_FOLDER =
export const EMBEDDED_MPV_DISPOSE_SESSION = 'EMBEDDED_MPV_DISPOSE_SESSION';
export const EMBEDDED_MPV_SESSION_UPDATE = 'EMBEDDED_MPV_SESSION_UPDATE';
export const EMBEDDED_MPV_PREPARE = 'EMBEDDED_MPV_PREPARE';
export const EMBEDDED_MPV_GET_FRAME_SOURCE = 'EMBEDDED_MPV_GET_FRAME_SOURCE';
export const EMBEDDED_MPV_FRAME_SOURCE_CHANGED =
'EMBEDDED_MPV_FRAME_SOURCE_CHANGED';
// Xtream
export const XTREAM_REQUEST = 'XTREAM_REQUEST';