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.
This commit is contained in:
4gray committed 2025-11-08 10:22:27 +01:00
1 parent e079d268f0
commit 3eed15d806
2 files changed
+164 -88

No files matched your search

@@ -53,87 +53,125 @@ function sendMpvCommand(command: string, args: string[]): Promise<void> {
});
}
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);
@@ -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