From d39f5f848a51165abe157aa5bd5ee56c04a1861b Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 11 Jul 2026 00:56:26 +0200 Subject: [PATCH] feat(embedded-mpv): Settings toggle for the frame-copy engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New 'Embedded MPV: frame-copy engine' checkbox in Settings > Playback, shown only when the machine can run it (macOS arm64 with the helper binary present — support now reports frameCopyAvailable). The choice persists to the main-process config store because the engine relaxes the window sandbox for the preload frame pump, which is fixed at window creation: main.ts reads the store before creating the window and sets the engine env var; an explicitly set env var (including '0') always wins, and the UI shows a restart hint while the saved choice differs from the active engine. Localized in all 18 locales. Co-Authored-By: Claude Fable 5 --- .../src/app/events/settings.events.ts | 6 ++++ .../services/embedded-mpv-native.service.ts | 12 +++++++ .../src/app/services/store.service.ts | 8 +++++ apps/electron-backend/src/main.ts | 18 ++++++++++ .../src/app/settings/settings-form.utils.ts | 2 ++ .../settings-playback-section.component.html | 36 +++++++++++++++++++ .../settings-playback-section.component.ts | 4 +++ .../src/app/settings/settings.component.html | 2 ++ .../app/settings/settings.component.spec.ts | 1 + .../src/app/settings/settings.component.ts | 6 ++++ apps/web/src/assets/i18n/ar.json | 5 ++- apps/web/src/assets/i18n/ary.json | 5 ++- apps/web/src/assets/i18n/by.json | 5 ++- apps/web/src/assets/i18n/de.json | 5 ++- apps/web/src/assets/i18n/el.json | 5 ++- apps/web/src/assets/i18n/en.json | 5 ++- apps/web/src/assets/i18n/es.json | 5 ++- apps/web/src/assets/i18n/fr.json | 5 ++- apps/web/src/assets/i18n/it.json | 5 ++- apps/web/src/assets/i18n/ja.json | 5 ++- apps/web/src/assets/i18n/ko.json | 5 ++- apps/web/src/assets/i18n/nl.json | 5 ++- apps/web/src/assets/i18n/pl.json | 5 ++- apps/web/src/assets/i18n/pt.json | 5 ++- apps/web/src/assets/i18n/ru.json | 5 ++- apps/web/src/assets/i18n/tr.json | 5 ++- apps/web/src/assets/i18n/zh.json | 5 ++- apps/web/src/assets/i18n/zhtw.json | 5 ++- .../src/lib/settings-store.service.ts | 3 ++ .../src/lib/embedded-mpv-session.interface.ts | 6 ++++ .../interfaces/src/lib/settings.interface.ts | 6 ++++ 31 files changed, 182 insertions(+), 18 deletions(-) diff --git a/apps/electron-backend/src/app/events/settings.events.ts b/apps/electron-backend/src/app/events/settings.events.ts index 5d3797880..5eb290f2c 100644 --- a/apps/electron-backend/src/app/events/settings.events.ts +++ b/apps/electron-backend/src/app/events/settings.events.ts @@ -1,6 +1,7 @@ import { ipcMain } from 'electron'; import { normalizeExternalPlayerArguments } from '@iptvnator/shared/interfaces'; import { + EMBEDDED_MPV_FRAME_COPY, MPV_PLAYER_ARGUMENTS, MPV_REUSE_INSTANCE, store, @@ -37,6 +38,11 @@ ipcMain.handle('SETTINGS_UPDATE', (_event, arg) => { store.set(MPV_REUSE_INSTANCE, arg.mpvReuseInstance); } + // Applied on the next app start (window sandbox is fixed at creation). + if (arg.embeddedMpvFrameCopy !== undefined) { + store.set(EMBEDDED_MPV_FRAME_COPY, !!arg.embeddedMpvFrameCopy); + } + if (arg.vlcReuseInstance !== undefined) { store.set(VLC_REUSE_INSTANCE, arg.vlcReuseInstance); } diff --git a/apps/electron-backend/src/app/services/embedded-mpv-native.service.ts b/apps/electron-backend/src/app/services/embedded-mpv-native.service.ts index 78a483f90..22e85be22 100644 --- a/apps/electron-backend/src/app/services/embedded-mpv-native.service.ts +++ b/apps/electron-backend/src/app/services/embedded-mpv-native.service.ts @@ -131,6 +131,14 @@ export class EmbeddedMpvNativeService { return this.isFrameCopyEngineActive() ? 'frame-copy' : 'native'; } + isFrameCopyAvailable(): boolean { + return ( + process.platform === 'darwin' && + process.arch === 'arm64' && + this.resolveFrameCopyHelperPath() !== null + ); + } + getFrameSource(sessionId: string): EmbeddedMpvFrameSource | null { return this.frameCopyAdapter?.getFrameSource(sessionId) ?? null; } @@ -250,6 +258,7 @@ export class EmbeddedMpvNativeService { supported: true, platform: process.platform, engine: 'frame-copy', + frameCopyAvailable: true, capabilities: { subtitles: true, playbackSpeed: true, @@ -274,6 +283,7 @@ export class EmbeddedMpvNativeService { supported: true, platform: process.platform, engine: this.getActiveEngine(), + frameCopyAvailable: this.isFrameCopyAvailable(), capabilities: this.detectCapabilities(), }; } catch (error) { @@ -339,6 +349,7 @@ export class EmbeddedMpvNativeService { supported: true, platform: process.platform, engine: this.getActiveEngine(), + frameCopyAvailable: this.isFrameCopyAvailable(), capabilities: this.detectCapabilities(), }; } catch (error) { @@ -370,6 +381,7 @@ export class EmbeddedMpvNativeService { supported: true, platform: process.platform, engine: this.getActiveEngine(), + frameCopyAvailable: this.isFrameCopyAvailable(), capabilities: this.detectCapabilities(), }; } catch (error) { diff --git a/apps/electron-backend/src/app/services/store.service.ts b/apps/electron-backend/src/app/services/store.service.ts index a472708aa..0a73bcaa4 100644 --- a/apps/electron-backend/src/app/services/store.service.ts +++ b/apps/electron-backend/src/app/services/store.service.ts @@ -8,6 +8,13 @@ export const VLC_PLAYER_PATH = 'VLC_PLAYER_PATH'; export const VLC_PLAYER_ARGUMENTS = 'VLC_PLAYER_ARGUMENTS'; export const MPV_REUSE_INSTANCE = 'MPV_REUSE_INSTANCE'; export const VLC_REUSE_INSTANCE = 'VLC_REUSE_INSTANCE'; +/** + * Embedded MPV frame-copy engine opt-in (macOS arm64). Lives in the main + * process config file because it must be readable synchronously before the + * BrowserWindow is created — the engine relaxes the window sandbox for its + * preload frame pump, which cannot change after window creation. + */ +export const EMBEDDED_MPV_FRAME_COPY = 'EMBEDDED_MPV_FRAME_COPY'; export type StoreType = { [WINDOW_BOUNDS]: Electron.Rectangle; @@ -17,6 +24,7 @@ export type StoreType = { [VLC_PLAYER_ARGUMENTS]: string; [MPV_REUSE_INSTANCE]: boolean; [VLC_REUSE_INSTANCE]: boolean; + [EMBEDDED_MPV_FRAME_COPY]: boolean; }; // Export singleton store instance diff --git a/apps/electron-backend/src/main.ts b/apps/electron-backend/src/main.ts index 84f772e1c..1d1787805 100644 --- a/apps/electron-backend/src/main.ts +++ b/apps/electron-backend/src/main.ts @@ -31,6 +31,10 @@ import { databaseWorkerClient } from './app/services/database-worker-client'; import WindowEvents from './app/events/window.events'; import XtreamEvents from './app/events/xtream.events'; import { environment } from './environments/environment'; +import { + EMBEDDED_MPV_FRAME_COPY, + store, +} from './app/services/store.service'; app.setName('iptvnator'); @@ -55,6 +59,20 @@ if (electronUserDataPath) { app.setPath('userData', electronUserDataPath); } +// The frame-copy embedded MPV engine must be decided before the main window +// exists: it relaxes the window sandbox for the preload frame pump, and +// webPreferences are fixed at window creation. The Settings toggle persists +// to the main-process config store; an explicitly set env var (including +// '0') always wins so dev/CI behavior stays scriptable. +if ( + process.platform === 'darwin' && + process.arch === 'arm64' && + process.env.IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY === undefined && + store.get(EMBEDDED_MPV_FRAME_COPY, false) +) { + process.env.IPTVNATOR_ENABLE_EMBEDDED_MPV_FRAME_COPY = '1'; +} + let fixPathScheduled = false; /** diff --git a/apps/web/src/app/settings/settings-form.utils.ts b/apps/web/src/app/settings/settings-form.utils.ts index 19a52df6c..da7d20cef 100644 --- a/apps/web/src/app/settings/settings-form.utils.ts +++ b/apps/web/src/app/settings/settings-form.utils.ts @@ -72,6 +72,7 @@ export function createSettingsForm( ], ], recordingFolder: '', + embeddedMpvFrameCopy: false, coverSize: 'medium' as CoverSize, ...(supportsEpg ? { @@ -135,6 +136,7 @@ export function createSettingsFromFormValue( remoteControl: value.remoteControl ?? false, remoteControlPort: Number(value.remoteControlPort ?? 8765), recordingFolder: value.recordingFolder ?? '', + embeddedMpvFrameCopy: value.embeddedMpvFrameCopy ?? false, coverSize: value.coverSize ?? 'medium', epgUrl, preferUploadedEpgOverXtream: diff --git a/apps/web/src/app/settings/settings-playback-section.component.html b/apps/web/src/app/settings/settings-playback-section.component.html index 8b3e15080..c368b3c7d 100644 --- a/apps/web/src/app/settings/settings-playback-section.component.html +++ b/apps/web/src/app/settings/settings-playback-section.component.html @@ -114,6 +114,42 @@ + @if (frameCopyAvailable()) { +
+
+

+ {{ 'SETTINGS.EMBEDDED_MPV_FRAME_COPY' | translate }} +

+

+ {{ + 'SETTINGS.EMBEDDED_MPV_FRAME_COPY_DESCRIPTION' + | translate + }} + @if ( + form().value.embeddedMpvFrameCopy !== + frameCopyActive() + ) { + + {{ + 'SETTINGS.EMBEDDED_MPV_FRAME_COPY_RESTART' + | translate + }} + + } +

+
+
+ +
+
+ } +

{{ 'SETTINGS.RECORDING_FOLDER_LABEL' | translate }}

diff --git a/apps/web/src/app/settings/settings-playback-section.component.ts b/apps/web/src/app/settings/settings-playback-section.component.ts index a0d6a6531..5e8f645b7 100644 --- a/apps/web/src/app/settings/settings-playback-section.component.ts +++ b/apps/web/src/app/settings/settings-playback-section.component.ts @@ -45,6 +45,10 @@ export class SettingsPlaybackSectionComponent { readonly players = input.required(); readonly streamFormatEnum = input.required(); readonly isDesktop = input(false); + /** Frame-copy embedded MPV engine is possible on this machine */ + readonly frameCopyAvailable = input(false); + /** Frame-copy engine is what the current app run actually uses */ + readonly frameCopyActive = input(false); readonly supportsManagedExternalPlayers = input(false); readonly supportsExternalPlayerPathSettings = input(false); readonly selectRecordingFolder = output(); diff --git a/apps/web/src/app/settings/settings.component.html b/apps/web/src/app/settings/settings.component.html index 68aa05b07..c1bbd7758 100644 --- a/apps/web/src/app/settings/settings.component.html +++ b/apps/web/src/app/settings/settings.component.html @@ -50,6 +50,8 @@ [isDesktop]="isDesktop" [supportsManagedExternalPlayers]="supportsManagedExternalPlayers" [supportsExternalPlayerPathSettings]="supportsExternalPlayerPathSettings" + [frameCopyAvailable]="frameCopyAvailable()" + [frameCopyActive]="frameCopyActive()" (selectRecordingFolder)="selectRecordingFolder()" /> diff --git a/apps/web/src/app/settings/settings.component.spec.ts b/apps/web/src/app/settings/settings.component.spec.ts index 245985f79..95ffca533 100644 --- a/apps/web/src/app/settings/settings.component.spec.ts +++ b/apps/web/src/app/settings/settings.component.spec.ts @@ -102,6 +102,7 @@ const DEFAULT_SETTINGS = { remoteControlPort: 8765, epgUrl: [], recordingFolder: '', + embeddedMpvFrameCopy: false, coverSize: 'medium', dashboardRails: DEFAULT_DASHBOARD_RAILS, preferUploadedEpgOverXtream: false, diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index 66736cdfc..2b67bf278 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -165,6 +165,12 @@ export class SettingsComponent implements OnInit, OnDestroy { readonly supportsEmbeddedMpv = computed( () => this.isDesktop && !!this.embeddedMpvSupport()?.supported ); + readonly frameCopyAvailable = computed( + () => this.isDesktop && !!this.embeddedMpvSupport()?.frameCopyAvailable + ); + readonly frameCopyActive = computed( + () => this.embeddedMpvSupport()?.engine === 'frame-copy' + ); readonly isPwa = this.runtime.isPwa; diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 4293e33aa..7e63ec8f6 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "MPV المدمج: محرك نسخ الإطارات (تجريبي)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "يُرسم الفيديو على لوحة التطبيق بحيث تظهر القوائم ومربعات الحوار فوقه. متاح فقط على macOS بمعالج Apple Silicon.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "يسري المفعول بعد إعادة تشغيل التطبيق." }, "THEMES": { "DARK_THEME": "داكن", diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index 4aad181c8..057077bdb 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "MPV المدمج: محرك نسخ الإطارات (تجريبي)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "الفيديو كيترسم على لوحة التطبيق باش القوائم والحوارات يبانو فوقو. غير على macOS ب Apple Silicon.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "غادي يخدم من بعد ما تعاود تشغل التطبيق." }, "THEMES": { "DARK_THEME": "داكن", diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 1e92b244f..20c5a7dc5 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: рухавік frame-copy (эксперыментальна)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Відэа малюецца на канвасе праграмы, таму меню і дыялогі паказваюцца па-над ім. Толькі macOS на Apple Silicon.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Пачне дзейнічаць пасля перазапуску праграмы." }, "THEMES": { "DARK_THEME": "Цёмная", diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index 4bb53182e..b5ba7e1e0 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Wird nach einem Neustart der App wirksam." }, "THEMES": { "DARK_THEME": "Dunkel", diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 70b2795e3..b43040df2 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: μηχανή frame-copy (πειραματικό)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Το βίντεο σχεδιάζεται στον καμβά της εφαρμογής, ώστε τα μενού και τα παράθυρα διαλόγου να εμφανίζονται από πάνω του. Μόνο macOS με Apple Silicon.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Ισχύει μετά την επανεκκίνηση της εφαρμογής." }, "THEMES": { "DARK_THEME": "Σκούρο", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index b77a49418..574359965 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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 only.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Takes effect after restarting the app." }, "THEMES": { "DARK_THEME": "Dark", diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index 0c4087ff0..dc0f79392 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Se aplica tras reiniciar la aplicación." }, "THEMES": { "DARK_THEME": "Oscuro", diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index 30be779d4..528294c42 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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 uniquement.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Prend effet après le redémarrage de l'application." }, "THEMES": { "DARK_THEME": "Sombre", diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index e5f8559a4..e4aeaa1ea 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Ha effetto dopo il riavvio dell'app." }, "THEMES": { "DARK_THEME": "Scuro", diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index 9be5c7845..ef693252c 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "埋め込みMPV:フレームコピーエンジン(実験的)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "映像をアプリのキャンバスに描画するため、メニューやダイアログをその上に表示できます。Apple Silicon搭載macOSのみ。", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "アプリの再起動後に有効になります。" }, "THEMES": { "DARK_THEME": "ダーク", diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index 5b35c4905..082517e84 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "내장 MPV: 프레임 복사 엔진(실험적)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "비디오를 앱 캔버스에 렌더링하여 메뉴와 대화 상자가 그 위에 표시됩니다. Apple Silicon macOS 전용.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "앱을 다시 시작한 후 적용됩니다." }, "THEMES": { "DARK_THEME": "어둡게", diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index 70e472cce..d92459595 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Wordt van kracht na het herstarten van de app." }, "THEMES": { "DARK_THEME": "Donker", diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index 38fdaea82..74642284f 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Zacznie działać po ponownym uruchomieniu aplikacji." }, "THEMES": { "DARK_THEME": "Ciemny", diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index a1b75be66..047f5af0b 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Entra em vigor após reiniciar a aplicação." }, "THEMES": { "DARK_THEME": "Escuro", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index f043a6b13..f697e0a06 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Обновить", "APP_UPDATE_INSTALL": "Перезагрузить приложение", "APP_UPDATE_OPEN_RELEASE": "Открыть GitHub release", - "APP_UPDATE_RELEASE_NOTES": "Что нового" + "APP_UPDATE_RELEASE_NOTES": "Что нового", + "EMBEDDED_MPV_FRAME_COPY": "Embedded MPV: движок frame-copy (экспериментально)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "Видео рисуется на канвасе приложения, поэтому меню и диалоги отображаются поверх него. Только macOS на Apple Silicon.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Вступит в силу после перезапуска приложения." }, "THEMES": { "DARK_THEME": "Тёмная", diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index 577f4ac60..0a6fceecc 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "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.", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "Uygulama yeniden başlatıldıktan sonra geçerli olur." }, "THEMES": { "DARK_THEME": "Koyu", diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index e473b0886..f35f3e5bf 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "嵌入式 MPV:帧拷贝引擎(实验性)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "视频渲染到应用画布上,菜单和对话框可以显示在其上方。仅限 Apple Silicon 的 macOS。", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "重启应用后生效。" }, "THEMES": { "DARK_THEME": "深色", diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index b138661e7..0988233f6 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -428,7 +428,10 @@ "APP_UPDATE_DOWNLOAD": "Update", "APP_UPDATE_INSTALL": "Restart application", "APP_UPDATE_OPEN_RELEASE": "Open GitHub release", - "APP_UPDATE_RELEASE_NOTES": "What's new" + "APP_UPDATE_RELEASE_NOTES": "What's new", + "EMBEDDED_MPV_FRAME_COPY": "內嵌 MPV:幀複製引擎(實驗性)", + "EMBEDDED_MPV_FRAME_COPY_DESCRIPTION": "影片會繪製到應用程式畫布上,選單和對話框可顯示在其上方。僅限 Apple Silicon 的 macOS。", + "EMBEDDED_MPV_FRAME_COPY_RESTART": "重新啟動應用程式後生效。" }, "THEMES": { "DARK_THEME": "深色", diff --git a/libs/services/src/lib/settings-store.service.ts b/libs/services/src/lib/settings-store.service.ts index 883a5e8f8..ced7df83d 100644 --- a/libs/services/src/lib/settings-store.service.ts +++ b/libs/services/src/lib/settings-store.service.ts @@ -45,6 +45,7 @@ const DEFAULT_SETTINGS: Settings = { epgUrl: [], downloadFolder: '', recordingFolder: '', + embeddedMpvFrameCopy: false, coverSize: 'medium', epgViewMode: 'timeline', dashboardRails: DEFAULT_DASHBOARD_RAILS_SETTINGS, @@ -187,6 +188,8 @@ export const SettingsStore = signalStore( recordingFolder: store.recordingFolder?.() ?? DEFAULT_SETTINGS.recordingFolder, + embeddedMpvFrameCopy: + store.embeddedMpvFrameCopy?.() ?? false, coverSize: store.coverSize?.() ?? DEFAULT_SETTINGS.coverSize, epgViewMode: store.epgViewMode?.() ?? DEFAULT_SETTINGS.epgViewMode, diff --git a/libs/shared/interfaces/src/lib/embedded-mpv-session.interface.ts b/libs/shared/interfaces/src/lib/embedded-mpv-session.interface.ts index 4d1b696da..0a02540bd 100644 --- a/libs/shared/interfaces/src/lib/embedded-mpv-session.interface.ts +++ b/libs/shared/interfaces/src/lib/embedded-mpv-session.interface.ts @@ -35,6 +35,12 @@ export interface EmbeddedMpvSupport { * `frame-copy` = helper process + shm ring + renderer canvas. */ engine?: EmbeddedMpvEngine; + /** + * True when this machine could run the frame-copy engine (macOS arm64 + * with the helper binary present), regardless of whether it is active. + * Drives the Settings toggle; switching engines requires an app restart. + */ + frameCopyAvailable?: boolean; } /** diff --git a/libs/shared/interfaces/src/lib/settings.interface.ts b/libs/shared/interfaces/src/lib/settings.interface.ts index 0242ca73a..6c394e19a 100644 --- a/libs/shared/interfaces/src/lib/settings.interface.ts +++ b/libs/shared/interfaces/src/lib/settings.interface.ts @@ -108,6 +108,12 @@ export interface Settings { downloadFolder?: string; /** Custom live recording folder path (uses system Downloads folder if not set) */ recordingFolder?: string; + /** + * Embedded MPV frame-copy engine (experimental, macOS Apple Silicon). + * Applied on the next app start — the engine relaxes the window sandbox + * for its preload frame pump, which is fixed at window creation. + */ + embeddedMpvFrameCopy?: boolean; /** Cover/poster sizing preset applied across grids and rails */ coverSize?: CoverSize; /** Live EPG panel layout: horizontal timeline (default) or vertical list */