MPV player enhancements

- start the player from the renderer
- dynamically modify settings without restart
This commit is contained in:
jeffvli 2023-04-02 21:41:32 -07:00
parent f35152a169
commit 77bfb916ba
9 changed files with 457 additions and 153 deletions

View file

@ -1,5 +1,5 @@
import { ipcMain } from 'electron';
import { mpv } from '../../../main';
import { getMpvInstance } from '../../../main';
import { PlayerData } from '/@/renderer/store';
declare module 'node-mpv';
@ -13,49 +13,49 @@ function wait(timeout: number) {
}
ipcMain.on('player-start', async () => {
await mpv.play();
await getMpvInstance()?.play();
});
// Starts the player
ipcMain.on('player-play', async () => {
await mpv.play();
await getMpvInstance()?.play();
});
// Pauses the player
ipcMain.on('player-pause', async () => {
await mpv.pause();
await getMpvInstance()?.pause();
});
// Stops the player
ipcMain.on('player-stop', async () => {
await mpv.stop();
await getMpvInstance()?.stop();
});
// Goes to the next track in the playlist
ipcMain.on('player-next', async () => {
await mpv.next();
await getMpvInstance()?.next();
});
// Goes to the previous track in the playlist
ipcMain.on('player-previous', async () => {
await mpv.prev();
await getMpvInstance()?.prev();
});
// Seeks forward or backward by the given amount of seconds
ipcMain.on('player-seek', async (_event, time: number) => {
await mpv.seek(time);
await getMpvInstance()?.seek(time);
});
// Seeks to the given time in seconds
ipcMain.on('player-seek-to', async (_event, time: number) => {
await mpv.goToPosition(time);
await getMpvInstance()?.goToPosition(time);
});
// Sets the queue in position 0 and 1 to the given data. Used when manually starting a song or using the next/prev buttons
ipcMain.on('player-set-queue', async (_event, data: PlayerData) => {
if (!data.queue.current && !data.queue.next) {
await mpv.clearPlaylist();
await mpv.pause();
await getMpvInstance()?.clearPlaylist();
await getMpvInstance()?.pause();
return;
}
@ -64,11 +64,11 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData) => {
while (!complete) {
try {
if (data.queue.current) {
await mpv.load(data.queue.current.streamUrl, 'replace');
await getMpvInstance()?.load(data.queue.current.streamUrl, 'replace');
}
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
}
complete = true;
@ -81,14 +81,18 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData) => {
// Replaces the queue in position 1 to the given data
ipcMain.on('player-set-queue-next', async (_event, data: PlayerData) => {
const size = await mpv.getPlaylistSize();
const size = await getMpvInstance()?.getPlaylistSize();
if (!size) {
return;
}
if (size > 1) {
await mpv.playlistRemove(1);
await getMpvInstance()?.playlistRemove(1);
}
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
}
});
@ -97,23 +101,23 @@ ipcMain.on('player-auto-next', async (_event, data: PlayerData) => {
// Always keep the current song as position 0 in the mpv queue
// This allows us to easily set update the next song in the queue without
// disturbing the currently playing song
await mpv.playlistRemove(0);
await getMpvInstance()?.playlistRemove(0);
if (data.queue.next) {
await mpv.load(data.queue.next.streamUrl, 'append');
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
}
});
// Sets the volume to the given value (0-100)
ipcMain.on('player-volume', async (_event, value: number) => {
await mpv.volume(value);
await getMpvInstance()?.volume(value);
});
// Toggles the mute status
ipcMain.on('player-mute', async () => {
await mpv.mute();
await getMpvInstance()?.mute();
});
ipcMain.on('player-quit', async () => {
await mpv.stop();
await getMpvInstance()?.stop();
});

View file

@ -306,13 +306,6 @@ app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,Media
const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined;
const MPV_PARAMETERS = store.get('mpv_parameters') as Array<string> | undefined;
const gaplessAudioParams = [
'--gapless-audio=weak',
'--gapless-audio=no',
'--gapless-audio=yes',
'--gapless-audio',
];
const prefetchPlaylistParams = [
'--prefetch-playlist=no',
'--prefetch-playlist=yes',
@ -321,9 +314,6 @@ const prefetchPlaylistParams = [
const DEFAULT_MPV_PARAMETERS = () => {
const parameters = [];
if (!MPV_PARAMETERS?.some((param) => gaplessAudioParams.includes(param))) {
parameters.push('--gapless-audio=weak');
}
if (!MPV_PARAMETERS?.some((param) => prefetchPlaylistParams.includes(param))) {
parameters.push('--prefetch-playlist=yes');
@ -332,57 +322,89 @@ const DEFAULT_MPV_PARAMETERS = () => {
return parameters;
};
export const mpv = new MpvAPI(
{
audio_only: true,
auto_restart: true,
binary: MPV_BINARY_PATH || '',
time_update: 1,
},
MPV_PARAMETERS
? uniq([...DEFAULT_MPV_PARAMETERS(), ...MPV_PARAMETERS])
: DEFAULT_MPV_PARAMETERS(),
);
let mpvInstance: MpvAPI | null = null;
mpv.start().catch((error) => {
console.log('error starting mpv', error);
});
const createMpv = (data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
const { extraParameters, properties } = data;
mpv.on('status', (status) => {
if (status.property === 'playlist-pos') {
if (status.value !== 0) {
getMainWindow()?.webContents.send('renderer-player-auto-next');
mpvInstance = new MpvAPI(
{
audio_only: true,
auto_restart: false,
binary: MPV_BINARY_PATH || '',
time_update: 1,
},
MPV_PARAMETERS || extraParameters
? uniq([...DEFAULT_MPV_PARAMETERS(), ...(MPV_PARAMETERS || []), ...(extraParameters || [])])
: DEFAULT_MPV_PARAMETERS(),
);
mpvInstance.setMultipleProperties(properties || {});
mpvInstance.start().catch((error) => {
console.log('error starting mpv', error);
});
mpvInstance.on('status', (status) => {
if (status.property === 'playlist-pos') {
if (status.value !== 0) {
getMainWindow()?.webContents.send('renderer-player-auto-next');
}
}
});
// Automatically updates the play button when the player is playing
mpvInstance.on('resumed', () => {
getMainWindow()?.webContents.send('renderer-player-play');
});
// Automatically updates the play button when the player is stopped
mpvInstance.on('stopped', () => {
getMainWindow()?.webContents.send('renderer-player-stop');
});
// Automatically updates the play button when the player is paused
mpvInstance.on('paused', () => {
getMainWindow()?.webContents.send('renderer-player-pause');
});
// Event output every interval set by time_update, used to update the current time
mpvInstance.on('timeposition', (time: number) => {
getMainWindow()?.webContents.send('renderer-player-current-time', time);
});
};
export const getMpvInstance = () => {
return mpvInstance;
};
ipcMain.on('player-set-properties', async (_event, data: Record<string, any>) => {
if (data.length === 0) {
return;
}
if (data.length === 1) {
getMpvInstance()?.setProperty(Object.keys(data)[0], Object.values(data)[0]);
} else {
getMpvInstance()?.setMultipleProperties(data);
}
});
// Automatically updates the play button when the player is playing
mpv.on('resumed', () => {
getMainWindow()?.webContents.send('renderer-player-play');
});
// Automatically updates the play button when the player is stopped
mpv.on('stopped', () => {
getMainWindow()?.webContents.send('renderer-player-stop');
});
// Automatically updates the play button when the player is paused
mpv.on('paused', () => {
getMainWindow()?.webContents.send('renderer-player-pause');
});
// Event output every interval set by time_update, used to update the current time
mpv.on('timeposition', (time: number) => {
getMainWindow()?.webContents.send('renderer-player-current-time', time);
});
ipcMain.on(
'player-restart',
async (_event, data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
getMpvInstance()?.quit();
createMpv(data);
},
);
app.on('before-quit', () => {
mpv.stop();
getMpvInstance()?.stop();
});
app.on('window-all-closed', () => {
globalShortcut.unregisterAll();
getMpvInstance()?.quit();
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (isMacOS()) {

View file

@ -1,6 +1,15 @@
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { PlayerData } from '/@/renderer/store';
const restart = (data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
ipcRenderer.send('player-restart', data);
};
const setProperties = (data: Record<string, any>) => {
console.log('Setting property :>>', data);
ipcRenderer.send('player-set-properties', data);
};
const autoNext = (data: PlayerData) => {
ipcRenderer.send('player-auto-next', data);
};
@ -8,36 +17,47 @@ const autoNext = (data: PlayerData) => {
const currentTime = () => {
ipcRenderer.send('player-current-time');
};
const mute = () => {
ipcRenderer.send('player-mute');
};
const next = () => {
ipcRenderer.send('player-next');
};
const pause = () => {
ipcRenderer.send('player-pause');
};
const play = () => {
ipcRenderer.send('player-play');
};
const previous = () => {
ipcRenderer.send('player-previous');
};
const seek = (seconds: number) => {
ipcRenderer.send('player-seek', seconds);
};
const seekTo = (seconds: number) => {
ipcRenderer.send('player-seek-to', seconds);
};
const setQueue = (data: PlayerData) => {
ipcRenderer.send('player-set-queue', data);
};
const setQueueNext = (data: PlayerData) => {
ipcRenderer.send('player-set-queue-next', data);
};
const stop = () => {
ipcRenderer.send('player-stop');
};
const volume = (value: number) => {
ipcRenderer.send('player-volume', value);
};
@ -91,8 +111,10 @@ export const mpvPlayer = {
play,
previous,
quit,
restart,
seek,
seekTo,
setProperties,
setQueue,
setQueueNext,
stop,