feat(embedded-mpv): Settings toggle for the frame-copy engine

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 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-15 18:20:45 +02:00
1 parent 5a3ea5a3c1
commit d39f5f848a
31 files changed
+182 -18

No files matched your search

@@ -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);
}
@@ -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) {
@@ -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
+18
View File
@@ -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;
/**
@@ -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:
@@ -114,6 +114,42 @@
</div>
</div>
@if (frameCopyAvailable()) {
<div
class="setting-item"
data-test-id="embedded-mpv-frame-copy-setting"
>
<div class="setting-item__meta">
<h4>
{{ 'SETTINGS.EMBEDDED_MPV_FRAME_COPY' | translate }}
</h4>
<p>
{{
'SETTINGS.EMBEDDED_MPV_FRAME_COPY_DESCRIPTION'
| translate
}}
@if (
form().value.embeddedMpvFrameCopy !==
frameCopyActive()
) {
<strong>
{{
'SETTINGS.EMBEDDED_MPV_FRAME_COPY_RESTART'
| translate
}}
</strong>
}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="embeddedMpvFrameCopy"
data-test-id="embedded-mpv-frame-copy-toggle"
></mat-checkbox>
</div>
</div>
}
<div class="setting-item" data-test-id="recording-folder-setting">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.RECORDING_FOLDER_LABEL' | translate }}</h4>
@@ -45,6 +45,10 @@ export class SettingsPlaybackSectionComponent {
readonly players = input.required<SettingsPlayerOption[]>();
readonly streamFormatEnum = input.required<typeof StreamFormat>();
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<void>();
@@ -50,6 +50,8 @@
[isDesktop]="isDesktop"
[supportsManagedExternalPlayers]="supportsManagedExternalPlayers"
[supportsExternalPlayerPathSettings]="supportsExternalPlayerPathSettings"
[frameCopyAvailable]="frameCopyAvailable()"
[frameCopyActive]="frameCopyActive()"
(selectRecordingFolder)="selectRecordingFolder()"
/>
@@ -102,6 +102,7 @@ const DEFAULT_SETTINGS = {
remoteControlPort: 8765,
epgUrl: [],
recordingFolder: '',
embeddedMpvFrameCopy: false,
coverSize: 'medium',
dashboardRails: DEFAULT_DASHBOARD_RAILS,
preferUploadedEpgOverXtream: false,
@@ -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;
+4 -1
View File
@@ -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": "داكن",
+4 -1
View File
@@ -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": "داكن",
+4 -1
View File
@@ -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": "Цёмная",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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": "Σκούρο",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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": "ダーク",
+4 -1
View File
@@ -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": "어둡게",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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": "Тёмная",
+4 -1
View File
@@ -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",
+4 -1
View File
@@ -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": "深色",
+4 -1
View File
@@ -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": "深色",
@@ -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,
@@ -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;
}
/**
@@ -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 */