fix(mpv, electron): improve process startup & error reporting

Refine MPV/VLC launch error handling in the renderer to extract and show
error.message when available, preventing unhelpful "[object Object]"
messages in the UI.

In the Electron backend, ensure failed MPV instances fall through to
creation by commenting intent, and wrap spawn in a promise to reliably
detect startup failures. On spawn error, clear stored references and
reject with a descriptive message that includes the mpv path. Add an
exit handler, conditional storage of the process for reuse, and proc.unref
for detached runs. Use a short timeout to resolve the promise when spawn
succeeds and log startup state. Convert a silent log to console.error and
rethrow the error to surface failures to callers.

These changes make MPV startup more robust and produce clearer error
feedback for debugging and UI notifications.
This commit is contained in:
4gray committed 2025-11-08 23:02:15 +01:00
1 parent 588664c3aa
commit e06c154e0f
2 files changed
+117 -79

No files matched your search

@@ -96,6 +96,7 @@ ipcMain.handle(
// If it fails, clear the reference and create a new one
mpvProcess = null;
mpvSocketPath = null;
// Fall through to create new instance
}
}
@@ -134,43 +135,57 @@ ipcMain.handle(
// Add URL last
args.push(url);
const proc = spawn(mpvPath, args, {
shell: false,
detached: !reuseInstance,
stdio: 'ignore',
});
// Wrap spawn in a promise to catch startup errors
await new Promise<void>((resolve, reject) => {
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, {
message: `Error: Failed to start MPV player. Make sure that mpv player is installed on your system and the path is correct.`,
}); */
});
proc.on('error', (err) => {
console.error('Failed to start MPV player:', err);
mpvProcess = null;
mpvSocketPath = null;
reject(
new Error(
`Failed to start MPV player: ${err.message}. Make sure MPV is installed and the path '${mpvPath}' is correct.`
)
);
});
proc.on('exit', (code) => {
console.log(`MPV exited with code ${code}`);
mpvProcess = null;
mpvSocketPath = null;
});
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();
}
// 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();
}
// Resolve immediately if spawn succeeds (error event would fire if it fails)
// Give a small window to catch immediate spawn errors
setTimeout(() => {
if (!proc.killed) {
resolve();
}
}, 100);
});
} catch (error) {
console.log(error);
console.error('Error opening MPV player:', error);
mpvProcess = null;
mpvSocketPath = null;
throw error;
}
}
);
@@ -195,7 +210,7 @@ ipcMain.handle('SET_MPV_REUSE_INSTANCE', (_event, reuseInstance: boolean) => {
ipcMain.handle(
'OPEN_VLC_PLAYER',
(
async (
event,
url: string,
title: string,
@@ -203,52 +218,73 @@ ipcMain.handle(
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);
try {
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 args: string[] = [];
const args: string[] = [];
// Add user agent if provided (VLC uses :http-user-agent= format)
if (userAgent) {
args.push(`:http-user-agent=${userAgent}`);
// Add user agent if provided (VLC uses :http-user-agent= format)
if (userAgent) {
args.push(`:http-user-agent=${userAgent}`);
}
// Add referer if provided (VLC uses :http-referrer= format)
if (referer) {
args.push(`:http-referrer=${referer}`);
}
// 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);
// Wrap spawn in a promise to catch startup errors
await new Promise<void>((resolve, reject) => {
const proc = spawn(vlcPath, args, {
shell: false,
detached: true,
stdio: 'ignore',
});
proc.on('error', (err) => {
console.error('Failed to start VLC player:', err);
reject(
new Error(
`Failed to start VLC player: ${err.message}. Make sure VLC is installed and the path '${vlcPath}' is correct.`
)
);
});
proc.on('exit', (code) => {
console.log(`VLC exited with code ${code}`);
});
// Detach the process so it can continue running independently
proc.unref();
// Resolve immediately if spawn succeeds (error event would fire if it fails)
// Give a small window to catch immediate spawn errors
setTimeout(() => {
if (!proc.killed) {
resolve();
}
}, 100);
});
} catch (error) {
console.error('Error opening VLC player:', error);
throw error;
}
// Add referer if provided (VLC uses :http-referrer= format)
if (referer) {
args.push(`:http-referrer=${referer}`);
}
// 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();
}
);
@@ -68,8 +68,9 @@ export class ElectronService extends DataService {
data.origin ?? undefined
);
/* thumbnail: data.thumbnail ?? '', */
} catch (error) {
this.snackBar.open(`Error launching MPV: ${error}`, 'Close', {
} catch (error: any) {
const errorMessage = error?.message || String(error);
this.snackBar.open(`Error launching MPV: ${errorMessage}`, 'Close', {
duration: 5000,
});
console.error('MPV launch error:', error);
@@ -85,8 +86,9 @@ export class ElectronService extends DataService {
data.referer ?? undefined,
data.origin ?? undefined
);
} catch (error) {
this.snackBar.open(`Error launching VLC: ${error}`, 'Close', {
} catch (error: any) {
const errorMessage = error?.message || String(error);
this.snackBar.open(`Error launching VLC: ${errorMessage}`, 'Close', {
duration: 5000,
});
console.error('VLC launch error:', error);