diff --git a/apps/electron-backend/src/app/events/player.events.spec.ts b/apps/electron-backend/src/app/events/player.events.spec.ts index a13cb57e1..d8d2e71b4 100644 --- a/apps/electron-backend/src/app/events/player.events.spec.ts +++ b/apps/electron-backend/src/app/events/player.events.spec.ts @@ -145,6 +145,60 @@ describe('player.events Flatpak launch helpers', () => { }); }); + it('resolves custom macOS MPV app bundles to their executable', () => { + const launchContext = resolveExternalPlayerLaunchContext( + 'mpv', + '/Applications/mpv copy.app', + { + platform: 'darwin', + isFlatpak: false, + } + ); + + expect(launchContext).toEqual({ + mode: 'direct', + playerPath: '/Applications/mpv copy.app/Contents/MacOS/mpv', + command: '/Applications/mpv copy.app/Contents/MacOS/mpv', + argsPrefix: [], + }); + }); + + it('resolves custom macOS VLC app bundles to their executable', () => { + const launchContext = resolveExternalPlayerLaunchContext( + 'vlc', + '/Applications/VLC.app/', + { + platform: 'darwin', + isFlatpak: false, + } + ); + + expect(launchContext).toEqual({ + mode: 'direct', + playerPath: '/Applications/VLC.app/Contents/MacOS/VLC', + command: '/Applications/VLC.app/Contents/MacOS/VLC', + argsPrefix: [], + }); + }); + + it('keeps custom macOS executable player paths unchanged', () => { + const launchContext = resolveExternalPlayerLaunchContext( + 'mpv', + '/Applications/mpv.app/Contents/MacOS/mpv', + { + platform: 'darwin', + isFlatpak: false, + } + ); + + expect(launchContext).toEqual({ + mode: 'direct', + playerPath: '/Applications/mpv.app/Contents/MacOS/mpv', + command: '/Applications/mpv.app/Contents/MacOS/mpv', + argsPrefix: [], + }); + }); + it('disables MPV reuse and socket bridging only in Flatpak', () => { expect(shouldReuseMpvInstance(true, true)).toBe(false); expect(shouldUseMpvSocketBridge(true)).toBe(false); diff --git a/apps/electron-backend/src/app/events/player.events.ts b/apps/electron-backend/src/app/events/player.events.ts index 689e8b03f..93c7522d6 100644 --- a/apps/electron-backend/src/app/events/player.events.ts +++ b/apps/electron-backend/src/app/events/player.events.ts @@ -93,6 +93,42 @@ function normalizePlayerPathForStore(value: string | null | undefined): string { return normalizeCustomPlayerPath(value) ?? ''; } +const macOSAppBundleExecutableNames: Record = { + mpv: 'mpv', + vlc: 'VLC', +}; + +function getMacOSAppBundleExecutableName(player: ExternalPlayerName): string { + return macOSAppBundleExecutableNames[player]; +} + +function removeTrailingPathSeparators(value: string): string { + return value.replace(/[\\/]+$/, '') || value; +} + +function resolveMacOSAppBundlePlayerPath( + player: ExternalPlayerName, + playerPath: string, + platform: NodeJS.Platform +): string { + if (platform !== 'darwin') { + return playerPath; + } + + const appBundlePath = removeTrailingPathSeparators(playerPath); + + if (!/\.app$/i.test(appBundlePath)) { + return playerPath; + } + + return path.join( + appBundlePath, + 'Contents', + 'MacOS', + getMacOSAppBundleExecutableName(player) + ); +} + function getDefaultPlayerPath( player: ExternalPlayerName, options: PlayerPathOptions = {} @@ -131,20 +167,25 @@ export function resolveExternalPlayerLaunchContext( isFlatpak, pathExists, }); + const resolvedPlayerPath = resolveMacOSAppBundlePlayerPath( + player, + playerPath, + platform + ); if (platform === 'linux' && isFlatpak) { return { mode: 'flatpak-host', - playerPath, + playerPath: resolvedPlayerPath, command: 'flatpak-spawn', - argsPrefix: ['--host', '--watch-bus', playerPath], + argsPrefix: ['--host', '--watch-bus', resolvedPlayerPath], }; } return { mode: 'direct', - playerPath, - command: playerPath, + playerPath: resolvedPlayerPath, + command: resolvedPlayerPath, argsPrefix: [], }; }