feat(player): add embedded-mpv player for macOS as experimental feature

- Introduced tooling for building and staging the macOS `libmpv` runtime for IPTVnator's embedded MPV player.
- Added `build-macos-runtime.mjs` for building an LGPL-compatible runtime from source.
- Created `stage-macos-runtime.mjs` for staging the built runtime artifacts.
- Implemented validation for the packaged embedded MPV runtime in `electron-after-pack.cjs` and `embedded-mpv-macos.cjs`.
- Updated packaging scripts to ensure the embedded MPV runtime is correctly integrated and validated during the build process.
- Added README files to document the expected layout and usage for the embedded MPV runtime artifacts.

Entire-Checkpoint: c6e522b4276c
This commit is contained in:
4gray committed 2026-04-27 00:32:14 +02:00
1 parent 10139955e7
commit 9f873e6bed
53 files changed
+6099 -49

No files matched your search

@@ -36,6 +36,45 @@ const DEFAULT_SETTINGS: Settings = {
downloadFolder: '',
};
let embeddedMpvPrepareScheduled = false;
function scheduleEmbeddedMpvPrepare(): void {
if (
embeddedMpvPrepareScheduled ||
typeof window === 'undefined' ||
!window.electron?.prepareEmbeddedMpv
) {
return;
}
embeddedMpvPrepareScheduled = true;
const prepare = () => {
void window.electron
.prepareEmbeddedMpv?.()
.then((support) => {
if (!support?.supported) {
embeddedMpvPrepareScheduled = false;
}
})
.catch((error) => {
embeddedMpvPrepareScheduled = false;
console.warn('Failed to prepare embedded MPV.', error);
});
};
const idleWindow = window as typeof window & {
requestIdleCallback?: (
callback: IdleRequestCallback,
options?: IdleRequestOptions
) => number;
};
if (idleWindow.requestIdleCallback) {
idleWindow.requestIdleCallback(prepare, { timeout: 5000 });
} else {
window.setTimeout(prepare, 2000);
}
}
export const SettingsStore = signalStore(
{ providedIn: 'root' },
withState<Settings>(DEFAULT_SETTINGS),
@@ -50,6 +89,12 @@ export const SettingsStore = signalStore(
...DEFAULT_SETTINGS,
...(stored as Settings),
});
void this.sanitizeEmbeddedMpvSelection().catch((error) => {
console.warn(
'Failed to verify embedded MPV support while loading settings.',
error
);
});
}
} catch (error) {
console.error('Failed to load settings:', error);
@@ -65,6 +110,9 @@ export const SettingsStore = signalStore(
await firstValueFrom(
storage.set(STORE_KEY.Settings, completeSettings)
);
if (completeSettings.player === VideoPlayer.EmbeddedMpv) {
scheduleEmbeddedMpvPrepare();
}
} catch (error) {
console.error('Failed to save settings:', error);
throw error;
@@ -102,9 +150,47 @@ export const SettingsStore = signalStore(
isEmbeddedPlayer() {
return (
store.player() === VideoPlayer.VideoJs ||
store.player() === VideoPlayer.Html5Player
store.player() === VideoPlayer.Html5Player ||
store.player() === VideoPlayer.ArtPlayer ||
store.player() === VideoPlayer.EmbeddedMpv
);
},
async sanitizeEmbeddedMpvSelection() {
if (store.player() !== VideoPlayer.EmbeddedMpv) {
return;
}
if (
typeof window === 'undefined' ||
!window.electron?.getEmbeddedMpvSupport
) {
await this.updateSettings({
player: DEFAULT_SETTINGS.player,
});
return;
}
try {
const support = await window.electron.getEmbeddedMpvSupport();
if (!support.supported) {
await this.updateSettings({
player: DEFAULT_SETTINGS.player,
});
return;
}
scheduleEmbeddedMpvPrepare();
} catch (error) {
console.warn(
'Failed to verify embedded MPV support; reverting to the default inline player.',
error
);
await this.updateSettings({
player: DEFAULT_SETTINGS.player,
});
}
},
})),
withHooks({
onInit(store) {