feat(embedded-mpv): enable the frame-copy engine gates on Windows

isFrameCopyPlatformSupported() now includes win32; helper discovery looks
for iptvnator_mpv_helper.exe there (accessSync X_OK degrades to existence
on Windows, which is the right gate — no execute bit). Settings copy in
all 18 locales lists Windows. The adapter spec's reader-path expectation
is built with path.join so it also passes on Windows checkouts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and 4gray committed 2026-07-15 20:44:30 +02:00
1 parent d438773388
commit b04448636c
22 files changed
+54 -34

No files matched your search

@@ -32,7 +32,8 @@ describe('embedded-mpv-frame-copy-platform.util', () => {
['darwin', 'x64', false],
['linux', 'x64', true],
['linux', 'arm64', true],
['win32', 'x64', false],
['win32', 'x64', true],
['freebsd', 'x64', false],
])('%s/%s -> %s', (platform, arch, expected) => {
Object.defineProperty(process, 'platform', { value: platform });
Object.defineProperty(process, 'arch', { value: arch });
@@ -54,8 +55,14 @@ describe('embedded-mpv-frame-copy-platform.util', () => {
'build',
'Release'
);
// The resolver looks for the host platform's binary name, so the
// fixture must follow it for the spec to stay host-agnostic.
const helperFileName = () =>
process.platform === 'win32'
? 'iptvnator_mpv_helper.exe'
: 'iptvnator_mpv_helper';
const helperPath = () =>
path.join(releaseDir(), 'iptvnator_mpv_helper');
path.join(releaseDir(), helperFileName());
const readerPath = () =>
path.join(releaseDir(), 'embedded_mpv_frame_reader.node');
@@ -134,7 +141,7 @@ describe('embedded-mpv-frame-copy-platform.util', () => {
mkdirSync(packagedNativeDir, { recursive: true });
const packagedHelper = path.join(
packagedNativeDir,
'iptvnator_mpv_helper'
helperFileName()
);
writeFileSync(packagedHelper, '#!/bin/sh\n');
chmodSync(packagedHelper, 0o755);
@@ -8,12 +8,17 @@ import path from 'path';
* sandbox and engine decisions cannot drift. These helpers must remain
* callable before app.whenReady().
*
* macOS stays Apple-Silicon-only. Linux supports every architecture because
* its helper renders through headless EGL and links libmpv out of process.
* macOS: Apple Silicon only (owner decision 2026-07-10) — Intel Macs keep
* the docked native engine. Linux: any arch — the helper renders offscreen
* through headless EGL and links libmpv out of process, so neither window
* embedding nor the in-process-libmpv ban constrains it. Windows: any arch
* with a helper binary (WGL offscreen render; in practice x64, the only
* vendored runtime) — the helper-presence check below is the real gate.
*/
export function isFrameCopyPlatformSupported(): boolean {
return (
process.platform === 'linux' ||
process.platform === 'win32' ||
(process.platform === 'darwin' && process.arch === 'arm64')
);
}
@@ -80,7 +85,8 @@ export function getEmbeddedMpvAddonCandidatePaths(): string[] {
/**
* Resolve the first executable frame-copy helper with a readable regular-file
* frame reader beside it. Both artifacts are required before the engine may
* relax the renderer sandbox.
* relax the renderer sandbox. Windows uses the `.exe` helper name; X_OK is an
* existence check there, while POSIX platforms also require the execute bit.
*/
export function resolveFrameCopyHelperPath(): string | null {
// Packaged startup must never fall through to cwd/dist development
@@ -89,12 +95,15 @@ export function resolveFrameCopyHelperPath(): string | null {
const addonCandidates = app.isPackaged
? getPackagedAddonPaths()
: [getLocalBuildAddonPath(), ...getDistAddonPaths()];
const helperFileName =
process.platform === 'win32'
? 'iptvnator_mpv_helper.exe'
: 'iptvnator_mpv_helper';
return (
addonCandidates
.map((candidatePath) => path.dirname(candidatePath))
.map((nativeDir) => ({
helper: path.join(nativeDir, 'iptvnator_mpv_helper'),
helper: path.join(nativeDir, helperFileName),
reader: path.join(
nativeDir,
'embedded_mpv_frame_reader.node'
@@ -1,4 +1,5 @@
import { EventEmitter } from 'events';
import path from 'path';
const spawnMock = jest.fn();
jest.mock('child_process', () => ({
@@ -117,8 +118,10 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
expect(frameSourceChanges).toEqual([
{ sessionId, shmName: `/${sessionId}-g1` },
]);
// path.join output is host-specific; build the expectation the
// same way so the spec passes on Windows checkouts too.
expect(adapter.getFrameSource(sessionId)?.readerPath).toBe(
'/native/embedded_mpv_frame_reader.node'
path.join('/native', 'embedded_mpv_frame_reader.node')
);
});
@@ -190,7 +193,8 @@ describe('EmbeddedMpvFrameCopyAdapter', () => {
['darwin', 'x64', false],
['linux', 'x64', true],
['linux', 'arm64', true],
['win32', 'x64', false],
['win32', 'x64', true],
['freebsd', 'x64', false],
])(
'on %s/%s with a helper binary present -> %s',
(platform, arch, expected) => {
@@ -111,9 +111,9 @@ export class EmbeddedMpvNativeService {
/**
* Frame-copy engine: helper process + shm ring + renderer canvas.
* Experimental, macOS Apple Silicon (owner decision 2026-07-10) and
* Linux, opted into with IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY=1 on
* top of the regular embedded MPV experiment flag.
* Experimental, macOS Apple Silicon (owner decision 2026-07-10), Linux
* and Windows, opted into with IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY=1
* on top of the regular embedded MPV experiment flag.
*/
private isFrameCopyEngineRequested(): boolean {
return ['1', 'true', 'yes', 'on'].includes(
@@ -215,9 +215,9 @@ export class EmbeddedMpvNativeService {
};
}
// The frame-copy engine renders offscreen (headless EGL on Linux)
// into a renderer canvas: the Linux X11/Xwayland and system-mpv
// requirements below only bind the native --wid engine. Both
// The frame-copy engine renders offscreen (headless EGL on Linux,
// WGL on Windows) into a renderer canvas: the Linux X11/Xwayland and
// system-mpv requirements below only bind the native --wid engine. Both
// native-engine failure returns still advertise frameCopyAvailable
// so the Settings toggle stays reachable — otherwise the states the
// frame-copy engine exists to fix would hide the way to enable it.
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "MPV المدمج: محرك نسخ الإطارات (تجريبي)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "يُرسم الفيديو على لوحة التطبيق بحيث تظهر القوائم ومربعات الحوار فوقه. متاح فقط على macOS بمعالج Apple Silicon وعلى Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "يُرسم الفيديو على لوحة التطبيق بحيث تظهر القوائم ومربعات الحوار فوقه. متاح فقط على macOS بمعالج Apple Silicon وعلى Windows وLinux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "يسري المفعول بعد إعادة تشغيل التطبيق."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "MPV المدمج: محرك نسخ الإطارات (تجريبي)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "الفيديو كيترسم على لوحة التطبيق باش القوائم والحوارات يبانو فوقو. غير على macOS ب Apple Silicon و Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "الفيديو كيترسم على لوحة التطبيق باش القوائم والحوارات يبانو فوقو. غير على macOS ب Apple Silicon و Windows و Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "غادي يخدم من بعد ما تعاود تشغل التطبيق."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: рухавік frame-copy (эксперыментальна)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Відэа малюецца на канвасе праграмы, таму меню і дыялогі паказваюцца па-над ім. Толькі macOS на Apple Silicon і Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Відэа малюецца на канвасе праграмы, таму меню і дыялогі паказваюцца па-над ім. Толькі macOS на Apple Silicon, Windows і Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Пачне дзейнічаць пасля перазапуску праграмы."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: Frame-Copy-Engine (experimentell)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Das Video wird auf die App-Leinwand gerendert, sodass Menüs und Dialoge darüber liegen. Nur macOS auf Apple Silicon und Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Das Video wird auf die App-Leinwand gerendert, sodass Menüs und Dialoge darüber liegen. Nur macOS auf Apple Silicon, Windows und Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Wird nach einem Neustart der App wirksam."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: μηχανή frame-copy (πειραματικό)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Το βίντεο σχεδιάζεται στον καμβά της εφαρμογής, ώστε τα μενού και τα παράθυρα διαλόγου να εμφανίζονται από πάνω του. Μόνο macOS με Apple Silicon και Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Το βίντεο σχεδιάζεται στον καμβά της εφαρμογής, ώστε τα μενού και τα παράθυρα διαλόγου να εμφανίζονται από πάνω του. Μόνο macOS με Apple Silicon, Windows και Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Ισχύει μετά την επανεκκίνηση της εφαρμογής."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: frame-copy engine (experimental)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Render video onto the app canvas so menus and dialogs stack above it. macOS (Apple Silicon) and Linux only.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Render video onto the app canvas so menus and dialogs stack above it. macOS (Apple Silicon), Windows, and Linux only.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Takes effect after restarting the app."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: motor frame-copy (experimental)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "El vídeo se dibuja en el lienzo de la aplicación, de modo que los menús y diálogos quedan encima. Solo macOS con Apple Silicon y Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "El vídeo se dibuja en el lienzo de la aplicación, de modo que los menús y diálogos quedan encima. Solo macOS con Apple Silicon, Windows y Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Se aplica tras reiniciar la aplicación."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV : moteur frame-copy (expérimental)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "La vidéo est rendue sur le canevas de l'application, les menus et boîtes de dialogue s'affichent donc au-dessus. macOS Apple Silicon et Linux uniquement.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "La vidéo est rendue sur le canevas de l'application, les menus et boîtes de dialogue s'affichent donc au-dessus. macOS Apple Silicon, Windows et Linux uniquement.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Prend effet après le redémarrage de l'application."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: motore frame-copy (sperimentale)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Il video viene disegnato sul canvas dell'app, così menu e finestre di dialogo appaiono sopra di esso. Solo macOS con Apple Silicon e Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Il video viene disegnato sul canvas dell'app, così menu e finestre di dialogo appaiono sopra di esso. Solo macOS con Apple Silicon, Windows e Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Ha effetto dopo il riavvio dell'app."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "埋め込みMPV:フレームコピーエンジン(実験的)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "映像をアプリのキャンバスに描画するため、メニューやダイアログをその上に表示できます。Apple Silicon搭載macOSおよびLinuxのみ。",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "映像をアプリのキャンバスに描画するため、メニューやダイアログをその上に表示できます。Apple Silicon搭載macOS、Windows、Linuxのみ。",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "アプリの再起動後に有効になります。"
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "내장 MPV: 프레임 복사 엔진(실험적)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "비디오를 앱 캔버스에 렌더링하여 메뉴와 대화 상자가 그 위에 표시됩니다. Apple Silicon macOS 및 Linux 전용.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "비디오를 앱 캔버스에 렌더링하여 메뉴와 대화 상자가 그 위에 표시됩니다. Apple Silicon macOS, Windows 및 Linux 전용.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "앱을 다시 시작한 후 적용됩니다."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: frame-copy-engine (experimenteel)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "De video wordt op het app-canvas getekend zodat menu's en dialoogvensters erboven verschijnen. Alleen macOS op Apple Silicon en Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "De video wordt op het app-canvas getekend zodat menu's en dialoogvensters erboven verschijnen. Alleen macOS op Apple Silicon, Windows en Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Wordt van kracht na het herstarten van de app."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: silnik frame-copy (eksperymentalny)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Wideo jest rysowane na kanwie aplikacji, dzięki czemu menu i okna dialogowe wyświetlają się nad nim. Tylko macOS na Apple Silicon i Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Wideo jest rysowane na kanwie aplikacji, dzięki czemu menu i okna dialogowe wyświetlają się nad nim. Tylko macOS na Apple Silicon, Windows i Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Zacznie działać po ponownym uruchomieniu aplikacji."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: motor frame-copy (experimental)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "O vídeo é desenhado na tela da aplicação, para que menus e diálogos fiquem por cima. Apenas macOS com Apple Silicon e Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "O vídeo é desenhado na tela da aplicação, para que menus e diálogos fiquem por cima. Apenas macOS com Apple Silicon, Windows e Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Entra em vigor após reiniciar a aplicação."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Открыть GitHub release",
"APP_UPDATE_RELEASE_NOTES": "Что нового",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: движок frame-copy (экспериментально)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Видео рисуется на канвасе приложения, поэтому меню и диалоги отображаются поверх него. Только macOS на Apple Silicon и Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Видео рисуется на канвасе приложения, поэтому меню и диалоги отображаются поверх него. Только macOS на Apple Silicon, Windows и Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Вступит в силу после перезапуска приложения."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: frame-copy motoru (deneysel)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Video uygulama tuvaline çizilir, böylece menüler ve iletişim kutuları üzerinde görüntülenir. Yalnızca Apple Silicon macOS ve Linux.",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Video uygulama tuvaline çizilir, böylece menüler ve iletişim kutuları üzerinde görüntülenir. Yalnızca Apple Silicon macOS, Windows ve Linux.",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "Uygulama yeniden başlatıldıktan sonra geçerli olur."
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "嵌入式 MPV:帧拷贝引擎(实验性)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "视频渲染到应用画布上,菜单和对话框可以显示在其上方。仅限 Apple Silicon 的 macOS 和 Linux。",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "视频渲染到应用画布上,菜单和对话框可以显示在其上方。仅限 Apple Silicon 的 macOS、Windows 和 Linux。",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "重启应用后生效。"
},
"THEMES": {
+1 -1
View File
@@ -430,7 +430,7 @@
"APP_UPDATE_OPEN_RELEASE": "Open GitHub release",
"APP_UPDATE_RELEASE_NOTES": "What's new",
"EMBEDDED_MPV_FRAME_COPY": "內嵌 MPV:幀複製引擎(實驗性)",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "影片會繪製到應用程式畫布上,選單和對話框可顯示在其上方。僅限 Apple Silicon 的 macOS 和 Linux。",
"EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "影片會繪製到應用程式畫布上,選單和對話框可顯示在其上方。僅限 Apple Silicon 的 macOS、Windows 和 Linux。",
"EMBEDDED_MPV_FRAME_COPY_RESTART": "重新啟動應用程式後生效。"
},
"THEMES": {