From 3eed15d806bc42b4c7980b15055aee2284919e01 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 31 Oct 2025 09:43:27 +0000 Subject: [PATCH] feat(player): use user-agent, referrer, origin for vlc and mpv Enhance MPV player IPC handler to accept and forward extra parameters (url, path, title, userAgent, referer, origin). Attempt to reuse an existing MPV instance when reuse is enabled and an IPC socket is available; if reuse fails, fall back to spawning a new process. When creating a new MPV process, generate a unique platform-aware IPC socket path, build arguments conditionally from provided metadata (user-agent, referrer, origin via http-header-fields, force-media-title) and append the URL last. Improve logging for debugging and handle process spawn errors by clearing stale process/socket references. These changes enable smoother playback handoff, preserve headers and title for remote resources, and make MPV invocations more robust. --- .../src/app/events/player.events.ts | 251 ++++++++++++------ apps/web/src/app/services/electron.service.ts | 1 + 2 files changed, 164 insertions(+), 88 deletions(-) diff --git a/apps/electron-backend/src/app/events/player.events.ts b/apps/electron-backend/src/app/events/player.events.ts index 22be20597..7f1f221b0 100644 --- a/apps/electron-backend/src/app/events/player.events.ts +++ b/apps/electron-backend/src/app/events/player.events.ts @@ -53,87 +53,125 @@ function sendMpvCommand(command: string, args: string[]): Promise { }); } -ipcMain.handle('OPEN_MPV_PLAYER', async (event, url) => { - try { - const mpvPath = getMpvPath(); - const reuseInstance = store.get(MPV_REUSE_INSTANCE, false); +ipcMain.handle( + 'OPEN_MPV_PLAYER', + async ( + event, + url: string, + path: string, + title: string, + userAgent?: string, + referer?: string, + origin?: string + ) => { + try { + const mpvPath = getMpvPath(); + const reuseInstance = store.get(MPV_REUSE_INSTANCE, false); - console.log('Opening MPV player with path:', mpvPath); - console.log('Reuse instance:', reuseInstance); - console.log('URL:', url); + console.log('Opening MPV player with path:', mpvPath); + console.log('Reuse instance:', reuseInstance); + console.log('URL:', url); + console.log('User-Agent:', userAgent); + console.log('Referer:', referer); + console.log('Origin:', origin); - // If reuse is enabled and there's an existing process, try to use it - if ( - reuseInstance && - mpvProcess && - !mpvProcess.killed && - mpvSocketPath - ) { - console.log('Reusing existing MPV instance'); - try { - await sendMpvCommand('loadfile', [url, 'replace']); - console.log( - 'Successfully loaded new URL in existing MPV instance' - ); - return; - } catch (err) { - console.error('Failed to send command to existing MPV:', err); - // If it fails, clear the reference and create a new one + // If reuse is enabled and there's an existing process, try to use it + if ( + reuseInstance && + mpvProcess && + !mpvProcess.killed && + mpvSocketPath + ) { + console.log('Reusing existing MPV instance'); + try { + await sendMpvCommand('loadfile', [url, 'replace']); + console.log( + 'Successfully loaded new URL in existing MPV instance' + ); + return; + } catch (err) { + console.error('Failed to send command to existing MPV:', err); + // If it fails, clear the reference and create a new one + mpvProcess = null; + mpvSocketPath = null; + } + } + + // Create new MPV process + console.log('Creating new MPV instance'); + + // Generate unique socket path + const socketPath = + process.platform === 'win32' + ? `\\\\.\\pipe\\mpv-${Date.now()}` + : `/tmp/mpvsocket-${Date.now()}`; + + const args = [`--input-ipc-server=${socketPath}`, '--idle=yes']; + + // Add user agent if provided + if (userAgent) { + args.push(`--user-agent=${userAgent}`); + } + + // Add referer if provided + if (referer) { + args.push(`--referrer=${referer}`); + } + + // Add origin as custom HTTP header if provided + // MPV doesn't have a direct --origin flag, so we use --http-header-fields + if (origin) { + args.push(`--http-header-fields=Origin: ${origin}`); + } + + // Add title if provided + if (title) { + args.push(`--force-media-title=${title}`); + } + + // Add URL last + args.push(url); + + const proc = spawn(mpvPath, args, { + shell: false, + detached: !reuseInstance, + stdio: 'ignore', + }); + + proc.on('error', (err) => { + console.error('Failed to start MPV player:', err); mpvProcess = null; mpvSocketPath = null; - } - } - - // Create new MPV process - console.log('Creating new MPV instance'); - - // Generate unique socket path - const socketPath = - process.platform === 'win32' - ? `\\\\.\\pipe\\mpv-${Date.now()}` - : `/tmp/mpvsocket-${Date.now()}`; - - const args = [`--input-ipc-server=${socketPath}`, '--idle=yes', url]; - - const proc = spawn(mpvPath, args, { - shell: false, - detached: !reuseInstance, - stdio: 'ignore', - }); - - proc.on('error', (err) => { - console.error('Failed to start MPV player:', err); - mpvProcess = null; - mpvSocketPath = null; - /* event.sender.send(ERROR, { + /* event.sender.send(ERROR, { message: `Error: Failed to start MPV player. Make sure that mpv player is installed on your system and the path is correct.`, }); */ - }); + }); - proc.on('exit', (code) => { - console.log(`MPV exited with code ${code}`); + proc.on('exit', (code) => { + console.log(`MPV exited with code ${code}`); + mpvProcess = null; + mpvSocketPath = null; + }); + + // Store the process reference if reuse is enabled + if (reuseInstance) { + mpvProcess = proc; + mpvSocketPath = socketPath; + console.log( + 'Stored MPV process for reuse with socket:', + socketPath + ); + } else { + // Detach the process so it can continue running independently + proc.unref(); + } + } catch (error) { + console.log(error); mpvProcess = null; mpvSocketPath = null; - }); - - // Store the process reference if reuse is enabled - if (reuseInstance) { - mpvProcess = proc; - mpvSocketPath = socketPath; - console.log( - 'Stored MPV process for reuse with socket:', - socketPath - ); - } else { - // Detach the process so it can continue running independently - proc.unref(); } - } catch (error) { - console.log(error); - mpvProcess = null; - mpvSocketPath = null; } -}); +); ipcMain.handle('SET_MPV_PLAYER_PATH', (_event, mpvPlayerPath) => { console.log('... setting mpv player path', mpvPlayerPath); @@ -153,28 +191,65 @@ ipcMain.handle('SET_MPV_REUSE_INSTANCE', (_event, reuseInstance: boolean) => { } }); -ipcMain.handle('OPEN_VLC_PLAYER', (event, url) => { - const vlcPath = getVlcPath(); - console.log('Opening VLC player with path:', vlcPath); - console.log('URL:', url); +ipcMain.handle( + 'OPEN_VLC_PLAYER', + ( + event, + url: string, + path: string, + title: string, + userAgent?: string, + referer?: string, + origin?: string + ) => { + const vlcPath = getVlcPath(); + console.log('Opening VLC player with path:', vlcPath); + console.log('URL:', url); + console.log('User-Agent:', userAgent); + console.log('Referer:', referer); + console.log('Origin:', origin); - const proc = spawn(vlcPath, [url as string], { - shell: false, - detached: true, - stdio: 'ignore', - }); + const args: string[] = []; - proc.on('error', (err) => { - console.error('Failed to start VLC player:', err); - }); + // Add user agent if provided (VLC uses :http-user-agent= format) + if (userAgent) { + args.push(`:http-user-agent=${userAgent}`); + } - proc.on('exit', (code) => { - console.log(`VLC exited with code ${code}`); - }); + // Add referer if provided (VLC uses :http-referrer= format) + if (referer) { + args.push(`:http-referrer=${referer}`); + } - // Detach the process so it can continue running independently - proc.unref(); -}); + // Note: VLC doesn't have a direct origin option, but origin is typically + // included in referer for most IPTV use cases + + // Add title if provided + if (title) { + args.push(`--meta-title=${title}`); + } + + // Add URL last + args.push(url); + + const proc = spawn(vlcPath, args, { + shell: false, + detached: true, + stdio: 'ignore', + }); + + proc.on('error', (err) => { + console.error('Failed to start VLC player:', err); + }); + + proc.on('exit', (code) => { + console.log(`VLC exited with code ${code}`); + }); + + // Detach the process so it can continue running independently + proc.unref(); + } +); ipcMain.handle('SET_VLC_PLAYER_PATH', (_event, vlcPlayerPath) => { console.log('... setting vlc player path', vlcPlayerPath); diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index 64f475e56..8033e6619 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -80,6 +80,7 @@ export class ElectronService extends DataService { return await window.electron.openInVlc( data.url, data.vlcPlayerPath || '', + data.title ?? '', data['user-agent'] ?? undefined, data.referer ?? undefined, data.origin ?? undefined