Refactor mpv initialization/cleanup
- Don't re-initialize the player on re-render - Fixes the player potentially crashing on hot reload
This commit is contained in:
parent
d97fe4c621
commit
c3c1f4cc5f
5 changed files with 157 additions and 52 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import console from 'console';
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import { getMainWindow, getMpvInstance } from '../../../main';
|
import { getMainWindow, getMpvInstance } from '../../../main';
|
||||||
import { PlayerData } from '/@/renderer/store';
|
import { PlayerData } from '/@/renderer/store';
|
||||||
|
@ -12,50 +13,99 @@ function wait(timeout: number) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('player-is-running', async () => {
|
||||||
|
return getMpvInstance()?.isRunning();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('player-clean-up', async () => {
|
||||||
|
getMpvInstance()?.stop();
|
||||||
|
getMpvInstance()?.clearPlaylist();
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on('player-start', async () => {
|
ipcMain.on('player-start', async () => {
|
||||||
await getMpvInstance()?.play();
|
await getMpvInstance()
|
||||||
|
?.play()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to play', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Starts the player
|
// Starts the player
|
||||||
ipcMain.on('player-play', async () => {
|
ipcMain.on('player-play', async () => {
|
||||||
await getMpvInstance()?.play();
|
await getMpvInstance()
|
||||||
|
?.play()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to play', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pauses the player
|
// Pauses the player
|
||||||
ipcMain.on('player-pause', async () => {
|
ipcMain.on('player-pause', async () => {
|
||||||
await getMpvInstance()?.pause();
|
await getMpvInstance()
|
||||||
|
?.pause()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to pause', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stops the player
|
// Stops the player
|
||||||
ipcMain.on('player-stop', async () => {
|
ipcMain.on('player-stop', async () => {
|
||||||
await getMpvInstance()?.stop();
|
await getMpvInstance()
|
||||||
|
?.stop()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to stop', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Goes to the next track in the playlist
|
// Goes to the next track in the playlist
|
||||||
ipcMain.on('player-next', async () => {
|
ipcMain.on('player-next', async () => {
|
||||||
await getMpvInstance()?.next();
|
await getMpvInstance()
|
||||||
|
?.next()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to go to next', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Goes to the previous track in the playlist
|
// Goes to the previous track in the playlist
|
||||||
ipcMain.on('player-previous', async () => {
|
ipcMain.on('player-previous', async () => {
|
||||||
await getMpvInstance()?.prev();
|
await getMpvInstance()
|
||||||
|
?.prev()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to go to previous', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seeks forward or backward by the given amount of seconds
|
// Seeks forward or backward by the given amount of seconds
|
||||||
ipcMain.on('player-seek', async (_event, time: number) => {
|
ipcMain.on('player-seek', async (_event, time: number) => {
|
||||||
await getMpvInstance()?.seek(time);
|
await getMpvInstance()
|
||||||
|
?.seek(time)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to seek', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seeks to the given time in seconds
|
// Seeks to the given time in seconds
|
||||||
ipcMain.on('player-seek-to', async (_event, time: number) => {
|
ipcMain.on('player-seek-to', async (_event, time: number) => {
|
||||||
await getMpvInstance()?.goToPosition(time);
|
await getMpvInstance()
|
||||||
|
?.goToPosition(time)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(`MPV failed to seek to ${time}`, err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sets the queue in position 0 and 1 to the given data. Used when manually starting a song or using the next/prev buttons
|
// 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, pause?: boolean) => {
|
ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) => {
|
||||||
if (!data.queue.current && !data.queue.next) {
|
if (!data.queue.current && !data.queue.next) {
|
||||||
await getMpvInstance()?.clearPlaylist();
|
await getMpvInstance()
|
||||||
await getMpvInstance()?.pause();
|
?.clearPlaylist()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to clear playlist', err);
|
||||||
|
});
|
||||||
|
await getMpvInstance()
|
||||||
|
?.pause()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to pause', err);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,11 +119,19 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (data.queue.current) {
|
if (data.queue.current) {
|
||||||
await getMpvInstance()?.load(data.queue.current.streamUrl, 'replace');
|
await getMpvInstance()
|
||||||
|
?.load(data.queue.current.streamUrl, 'replace')
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to load song', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.queue.next) {
|
if (data.queue.next) {
|
||||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
await getMpvInstance()
|
||||||
|
?.load(data.queue.next.streamUrl, 'append')
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to load next song', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
complete = true;
|
complete = true;
|
||||||
|
@ -92,18 +150,30 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
||||||
|
|
||||||
// Replaces the queue in position 1 to the given data
|
// Replaces the queue in position 1 to the given data
|
||||||
ipcMain.on('player-set-queue-next', async (_event, data: PlayerData) => {
|
ipcMain.on('player-set-queue-next', async (_event, data: PlayerData) => {
|
||||||
const size = await getMpvInstance()?.getPlaylistSize();
|
const size = await getMpvInstance()
|
||||||
|
?.getPlaylistSize()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to get playlist size', err);
|
||||||
|
});
|
||||||
|
|
||||||
if (!size) {
|
if (!size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size > 1) {
|
if (size > 1) {
|
||||||
await getMpvInstance()?.playlistRemove(1);
|
await getMpvInstance()
|
||||||
|
?.playlistRemove(1)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to remove song from playlist', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.queue.next) {
|
if (data.queue.next) {
|
||||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
await getMpvInstance()
|
||||||
|
?.load(data.queue.next.streamUrl, 'append')
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to load next song', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,21 +182,37 @@ ipcMain.on('player-auto-next', async (_event, data: PlayerData) => {
|
||||||
// Always keep the current song as position 0 in the mpv queue
|
// 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
|
// This allows us to easily set update the next song in the queue without
|
||||||
// disturbing the currently playing song
|
// disturbing the currently playing song
|
||||||
await getMpvInstance()?.playlistRemove(0);
|
await getMpvInstance()
|
||||||
|
?.playlistRemove(0)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to remove song from playlist', err);
|
||||||
|
});
|
||||||
|
|
||||||
if (data.queue.next) {
|
if (data.queue.next) {
|
||||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
await getMpvInstance()
|
||||||
|
?.load(data.queue.next.streamUrl, 'append')
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to load next song', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sets the volume to the given value (0-100)
|
// Sets the volume to the given value (0-100)
|
||||||
ipcMain.on('player-volume', async (_event, value: number) => {
|
ipcMain.on('player-volume', async (_event, value: number) => {
|
||||||
await getMpvInstance()?.volume(value);
|
await getMpvInstance()
|
||||||
|
?.volume(value)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to set volume', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggles the mute status
|
// Toggles the mute status
|
||||||
ipcMain.on('player-mute', async () => {
|
ipcMain.on('player-mute', async () => {
|
||||||
await getMpvInstance()?.mute();
|
await getMpvInstance()
|
||||||
|
?.mute()
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('MPV failed to toggle mute', err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('player-get-time', async (): Promise<number | undefined> => {
|
ipcMain.handle('player-get-time', async (): Promise<number | undefined> => {
|
||||||
|
|
|
@ -439,10 +439,10 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record<strin
|
||||||
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
|
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
|
||||||
console.log('Setting mpv params: ', params);
|
console.log('Setting mpv params: ', params);
|
||||||
|
|
||||||
mpvInstance = new MpvAPI(
|
const mpv = new MpvAPI(
|
||||||
{
|
{
|
||||||
audio_only: true,
|
audio_only: true,
|
||||||
auto_restart: true,
|
auto_restart: false,
|
||||||
binary: MPV_BINARY_PATH || '',
|
binary: MPV_BINARY_PATH || '',
|
||||||
time_update: 1,
|
time_update: 1,
|
||||||
},
|
},
|
||||||
|
@ -450,17 +450,17 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record<strin
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Setting MPV properties: ', properties);
|
console.log('Setting MPV properties: ', properties);
|
||||||
mpvInstance.setMultipleProperties(properties || {});
|
mpv.setMultipleProperties(properties || {});
|
||||||
|
|
||||||
mpvInstance.start().catch((error) => {
|
mpv.start().catch((error) => {
|
||||||
console.log('MPV Event: start error', error);
|
console.log('MPV failed to start', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
mpvInstance.on('status', (status, ...rest) => {
|
mpv.on('status', (status, ...rest) => {
|
||||||
console.log('MPV Event: status', status.property, status.value, rest);
|
console.log('MPV Event: status', status.property, status.value, rest);
|
||||||
if (status.property === 'playlist-pos') {
|
if (status.property === 'playlist-pos') {
|
||||||
if (status.value === -1) {
|
if (status.value === -1) {
|
||||||
mpvInstance?.stop();
|
mpv?.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.value !== 0) {
|
if (status.value !== 0) {
|
||||||
|
@ -470,31 +470,33 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record<strin
|
||||||
});
|
});
|
||||||
|
|
||||||
// Automatically updates the play button when the player is playing
|
// Automatically updates the play button when the player is playing
|
||||||
mpvInstance.on('resumed', () => {
|
mpv.on('resumed', () => {
|
||||||
console.log('MPV Event: resumed');
|
console.log('MPV Event: resumed');
|
||||||
getMainWindow()?.webContents.send('renderer-player-play');
|
getMainWindow()?.webContents.send('renderer-player-play');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Automatically updates the play button when the player is stopped
|
// Automatically updates the play button when the player is stopped
|
||||||
mpvInstance.on('stopped', () => {
|
mpv.on('stopped', () => {
|
||||||
console.log('MPV Event: stopped');
|
console.log('MPV Event: stopped');
|
||||||
getMainWindow()?.webContents.send('renderer-player-stop');
|
getMainWindow()?.webContents.send('renderer-player-stop');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Automatically updates the play button when the player is paused
|
// Automatically updates the play button when the player is paused
|
||||||
mpvInstance.on('paused', () => {
|
mpv.on('paused', () => {
|
||||||
console.log('MPV Event: paused');
|
console.log('MPV Event: paused');
|
||||||
getMainWindow()?.webContents.send('renderer-player-pause');
|
getMainWindow()?.webContents.send('renderer-player-pause');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event output every interval set by time_update, used to update the current time
|
// Event output every interval set by time_update, used to update the current time
|
||||||
mpvInstance.on('timeposition', (time: number) => {
|
mpv.on('timeposition', (time: number) => {
|
||||||
getMainWindow()?.webContents.send('renderer-player-current-time', time);
|
getMainWindow()?.webContents.send('renderer-player-current-time', time);
|
||||||
});
|
});
|
||||||
|
|
||||||
mpvInstance.on('quit', () => {
|
mpv.on('quit', () => {
|
||||||
console.log('MPV Event: quit');
|
console.log('MPV Event: quit');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return mpv;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMpvInstance = () => {
|
export const getMpvInstance = () => {
|
||||||
|
@ -517,14 +519,15 @@ ipcMain.on(
|
||||||
'player-restart',
|
'player-restart',
|
||||||
async (_event, data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
|
async (_event, data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
|
||||||
mpvInstance?.quit();
|
mpvInstance?.quit();
|
||||||
createMpv(data);
|
mpvInstance = createMpv(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.on(
|
ipcMain.on(
|
||||||
'player-initialize',
|
'player-initialize',
|
||||||
async (_event, data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
|
async (_event, data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
|
||||||
createMpv(data);
|
console.log('Initializing MPV with data: ', data);
|
||||||
|
mpvInstance = createMpv(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -614,6 +617,7 @@ ipcMain.on(
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
getMpvInstance()?.stop();
|
getMpvInstance()?.stop();
|
||||||
|
getMpvInstance()?.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
|
|
|
@ -9,6 +9,14 @@ const restart = (data: { extraParameters?: string[]; properties?: Record<string,
|
||||||
ipcRenderer.send('player-restart', data);
|
ipcRenderer.send('player-restart', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isRunning = () => {
|
||||||
|
return ipcRenderer.invoke('player-is-running');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
return ipcRenderer.invoke('player-clean-up');
|
||||||
|
};
|
||||||
|
|
||||||
const setProperties = (data: Record<string, any>) => {
|
const setProperties = (data: Record<string, any>) => {
|
||||||
console.log('Setting property :>>', data);
|
console.log('Setting property :>>', data);
|
||||||
ipcRenderer.send('player-set-properties', data);
|
ipcRenderer.send('player-set-properties', data);
|
||||||
|
@ -160,9 +168,11 @@ const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => {
|
||||||
|
|
||||||
export const mpvPlayer = {
|
export const mpvPlayer = {
|
||||||
autoNext,
|
autoNext,
|
||||||
|
cleanup,
|
||||||
currentTime,
|
currentTime,
|
||||||
getCurrentTime,
|
getCurrentTime,
|
||||||
initialize,
|
initialize,
|
||||||
|
isRunning,
|
||||||
mute,
|
mute,
|
||||||
next,
|
next,
|
||||||
pause,
|
pause,
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const App = () => {
|
||||||
const { type: playbackType } = usePlaybackSettings();
|
const { type: playbackType } = usePlaybackSettings();
|
||||||
const { bindings } = useHotkeySettings();
|
const { bindings } = useHotkeySettings();
|
||||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||||
const { restoreQueue } = useQueueControls();
|
const { clearQueue, restoreQueue } = useQueueControls();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
|
@ -43,24 +43,34 @@ export const App = () => {
|
||||||
|
|
||||||
// Start the mpv instance on startup
|
// Start the mpv instance on startup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const initializeMpv = async () => {
|
||||||
|
const isRunning: boolean | undefined = await mpvPlayer?.isRunning();
|
||||||
|
|
||||||
|
if (!isRunning) {
|
||||||
|
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
|
||||||
|
const properties = {
|
||||||
|
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
|
||||||
|
};
|
||||||
|
|
||||||
|
mpvPlayer?.initialize({
|
||||||
|
extraParameters,
|
||||||
|
properties,
|
||||||
|
});
|
||||||
|
|
||||||
|
mpvPlayer?.volume(properties.volume);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isElectron() && playbackType === PlaybackType.LOCAL) {
|
if (isElectron() && playbackType === PlaybackType.LOCAL) {
|
||||||
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
|
initializeMpv();
|
||||||
const properties = {
|
|
||||||
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
|
|
||||||
};
|
|
||||||
|
|
||||||
mpvPlayer?.initialize({
|
|
||||||
extraParameters,
|
|
||||||
properties,
|
|
||||||
});
|
|
||||||
|
|
||||||
mpvPlayer?.volume(properties.volume);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mpvPlayer?.quit();
|
clearQueue();
|
||||||
|
mpvPlayer?.stop();
|
||||||
|
mpvPlayer?.cleanup();
|
||||||
};
|
};
|
||||||
}, [playbackType]);
|
}, [clearQueue, playbackType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
|
|
|
@ -613,10 +613,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||||
handleAutoNext();
|
handleAutoNext();
|
||||||
});
|
});
|
||||||
|
|
||||||
mpvPlayerListener.rendererQuit(() => {
|
|
||||||
handleQuit();
|
|
||||||
});
|
|
||||||
|
|
||||||
mpvPlayerListener.rendererToggleShuffle(() => {
|
mpvPlayerListener.rendererToggleShuffle(() => {
|
||||||
handleToggleShuffle();
|
handleToggleShuffle();
|
||||||
});
|
});
|
||||||
|
@ -639,7 +635,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||||
ipc?.removeAllListeners('renderer-player-stop');
|
ipc?.removeAllListeners('renderer-player-stop');
|
||||||
ipc?.removeAllListeners('renderer-player-current-time');
|
ipc?.removeAllListeners('renderer-player-current-time');
|
||||||
ipc?.removeAllListeners('renderer-player-auto-next');
|
ipc?.removeAllListeners('renderer-player-auto-next');
|
||||||
ipc?.removeAllListeners('renderer-player-quit');
|
|
||||||
ipc?.removeAllListeners('renderer-player-toggle-shuffle');
|
ipc?.removeAllListeners('renderer-player-toggle-shuffle');
|
||||||
ipc?.removeAllListeners('renderer-player-toggle-repeat');
|
ipc?.removeAllListeners('renderer-player-toggle-repeat');
|
||||||
ipc?.removeAllListeners('renderer-player-error');
|
ipc?.removeAllListeners('renderer-player-error');
|
||||||
|
|
Reference in a new issue