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

@@ -1,8 +1,12 @@
import { contextBridge, ipcRenderer } from 'electron';
import type {
EmbeddedMpvBounds,
EmbeddedMpvSession,
EmbeddedMpvSupport,
ExternalPlayerSession,
PlaylistRefreshEvent,
PlaylistRefreshPayload,
ResolvedPortalPlayback,
XtreamCategory,
} from 'shared-interfaces';
import {
@@ -14,6 +18,7 @@ import {
const PORTAL_DEBUG_EVENT = 'PORTAL_DEBUG_EVENT';
const EXTERNAL_PLAYER_SESSION_UPDATE = 'EXTERNAL_PLAYER_SESSION_UPDATE';
const EMBEDDED_MPV_SESSION_UPDATE = 'EMBEDDED_MPV_SESSION_UPDATE';
const DB_OPERATION_EVENT = 'DB_OPERATION_EVENT';
const PLAYLIST_REFRESH_EVENT = 'PLAYLIST:REFRESH_EVENT';
@@ -229,6 +234,16 @@ const electronApi = {
ipcRenderer.on(EXTERNAL_PLAYER_SESSION_UPDATE, handler);
return () => ipcRenderer.off(EXTERNAL_PLAYER_SESSION_UPDATE, handler);
},
onEmbeddedMpvSessionUpdate: (
callback: (data: EmbeddedMpvSession) => void
) => {
const handler = (
_event: Electron.IpcRendererEvent,
data: EmbeddedMpvSession
) => callback(data);
ipcRenderer.on(EMBEDDED_MPV_SESSION_UPDATE, handler);
return () => ipcRenderer.off(EMBEDDED_MPV_SESSION_UPDATE, handler);
},
onDbOperationEvent: (callback: (data: DbOperationEvent) => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: any) =>
callback(data as DbOperationEvent);
@@ -332,6 +347,55 @@ const electronApi = {
),
closeExternalPlayerSession: (sessionId: string) =>
ipcRenderer.invoke('CLOSE_EXTERNAL_PLAYER_SESSION', sessionId),
getEmbeddedMpvSupport: (): Promise<EmbeddedMpvSupport> =>
ipcRenderer.invoke('EMBEDDED_MPV_SUPPORT'),
prepareEmbeddedMpv: (): Promise<EmbeddedMpvSupport> =>
ipcRenderer.invoke('EMBEDDED_MPV_PREPARE'),
createEmbeddedMpvSession: (
bounds: EmbeddedMpvBounds,
title?: string,
initialVolume?: number
): Promise<EmbeddedMpvSession> =>
ipcRenderer.invoke(
'EMBEDDED_MPV_CREATE_SESSION',
bounds,
title,
initialVolume
),
loadEmbeddedMpvPlayback: (
sessionId: string,
playback: ResolvedPortalPlayback
): Promise<void> =>
ipcRenderer.invoke('EMBEDDED_MPV_LOAD_PLAYBACK', sessionId, playback),
setEmbeddedMpvBounds: (
sessionId: string,
bounds: EmbeddedMpvBounds
): Promise<void> =>
ipcRenderer.invoke('EMBEDDED_MPV_SET_BOUNDS', sessionId, bounds),
setEmbeddedMpvPaused: (
sessionId: string,
paused: boolean
): Promise<EmbeddedMpvSession | null> =>
ipcRenderer.invoke('EMBEDDED_MPV_SET_PAUSED', sessionId, paused),
seekEmbeddedMpv: (
sessionId: string,
seconds: number
): Promise<EmbeddedMpvSession | null> =>
ipcRenderer.invoke('EMBEDDED_MPV_SEEK', sessionId, seconds),
setEmbeddedMpvVolume: (
sessionId: string,
volume: number
): Promise<EmbeddedMpvSession | null> =>
ipcRenderer.invoke('EMBEDDED_MPV_SET_VOLUME', sessionId, volume),
setEmbeddedMpvAudioTrack: (
sessionId: string,
trackId: number
): Promise<EmbeddedMpvSession | null> =>
ipcRenderer.invoke('EMBEDDED_MPV_SET_AUDIO_TRACK', sessionId, trackId),
disposeEmbeddedMpvSession: (
sessionId: string
): Promise<EmbeddedMpvSession | null> =>
ipcRenderer.invoke('EMBEDDED_MPV_DISPOSE_SESSION', sessionId),
autoUpdatePlaylists: (playlists) =>
ipcRenderer.invoke('AUTO_UPDATE', playlists),
fetchEpg: (urls: string[]) =>