From c3c1f4cc5f8a08d39f915bbd77e425ab195322f3 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 6 Jun 2023 10:48:47 -0700 Subject: [PATCH] Refactor mpv initialization/cleanup - Don't re-initialize the player on re-render - Fixes the player potentially crashing on hot reload --- src/main/features/core/player/index.ts | 124 +++++++++++++++--- src/main/main.ts | 32 +++-- src/main/preload/mpv-player.ts | 10 ++ src/renderer/app.tsx | 38 ++++-- .../player/hooks/use-center-controls.ts | 5 - 5 files changed, 157 insertions(+), 52 deletions(-) diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index 3f18a778..5d9018cb 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -1,3 +1,4 @@ +import console from 'console'; import { ipcMain } from 'electron'; import { getMainWindow, getMpvInstance } from '../../../main'; 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 () => { - await getMpvInstance()?.play(); + await getMpvInstance() + ?.play() + .catch((err) => { + console.log('MPV failed to play', err); + }); }); // Starts the player ipcMain.on('player-play', async () => { - await getMpvInstance()?.play(); + await getMpvInstance() + ?.play() + .catch((err) => { + console.log('MPV failed to play', err); + }); }); // Pauses the player ipcMain.on('player-pause', async () => { - await getMpvInstance()?.pause(); + await getMpvInstance() + ?.pause() + .catch((err) => { + console.log('MPV failed to pause', err); + }); }); // Stops the player 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 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 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 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 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 ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) => { if (!data.queue.current && !data.queue.next) { - await getMpvInstance()?.clearPlaylist(); - await getMpvInstance()?.pause(); + await getMpvInstance() + ?.clearPlaylist() + .catch((err) => { + console.log('MPV failed to clear playlist', err); + }); + await getMpvInstance() + ?.pause() + .catch((err) => { + console.log('MPV failed to pause', err); + }); return; } @@ -69,11 +119,19 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) } else { try { 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) { - 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; @@ -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 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) { return; } 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) { - 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 // This allows us to easily set update the next song in the queue without // 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) { - 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) 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 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 => { diff --git a/src/main/main.ts b/src/main/main.ts index 4211ab50..2a153e97 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -439,10 +439,10 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record { - console.log('MPV Event: start error', error); + mpv.start().catch((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); if (status.property === 'playlist-pos') { if (status.value === -1) { - mpvInstance?.stop(); + mpv?.stop(); } if (status.value !== 0) { @@ -470,31 +470,33 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record { + mpv.on('resumed', () => { console.log('MPV Event: resumed'); getMainWindow()?.webContents.send('renderer-player-play'); }); // Automatically updates the play button when the player is stopped - mpvInstance.on('stopped', () => { + mpv.on('stopped', () => { console.log('MPV Event: stopped'); getMainWindow()?.webContents.send('renderer-player-stop'); }); // Automatically updates the play button when the player is paused - mpvInstance.on('paused', () => { + mpv.on('paused', () => { console.log('MPV Event: 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) => { + mpv.on('timeposition', (time: number) => { getMainWindow()?.webContents.send('renderer-player-current-time', time); }); - mpvInstance.on('quit', () => { + mpv.on('quit', () => { console.log('MPV Event: quit'); }); + + return mpv; }; export const getMpvInstance = () => { @@ -517,14 +519,15 @@ ipcMain.on( 'player-restart', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { mpvInstance?.quit(); - createMpv(data); + mpvInstance = createMpv(data); }, ); ipcMain.on( 'player-initialize', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { - createMpv(data); + console.log('Initializing MPV with data: ', data); + mpvInstance = createMpv(data); }, ); @@ -614,6 +617,7 @@ ipcMain.on( app.on('before-quit', () => { getMpvInstance()?.stop(); + getMpvInstance()?.quit(); }); app.on('window-all-closed', () => { diff --git a/src/main/preload/mpv-player.ts b/src/main/preload/mpv-player.ts index 7442be93..1875537a 100644 --- a/src/main/preload/mpv-player.ts +++ b/src/main/preload/mpv-player.ts @@ -9,6 +9,14 @@ const restart = (data: { extraParameters?: string[]; properties?: Record { + return ipcRenderer.invoke('player-is-running'); +}; + +const cleanup = () => { + return ipcRenderer.invoke('player-clean-up'); +}; + const setProperties = (data: Record) => { console.log('Setting property :>>', data); ipcRenderer.send('player-set-properties', data); @@ -160,9 +168,11 @@ const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => { export const mpvPlayer = { autoNext, + cleanup, currentTime, getCurrentTime, initialize, + isRunning, mute, next, pause, diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 2f2e2e7c..cc91f271 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -34,7 +34,7 @@ export const App = () => { const { type: playbackType } = usePlaybackSettings(); const { bindings } = useHotkeySettings(); const handlePlayQueueAdd = useHandlePlayQueueAdd(); - const { restoreQueue } = useQueueControls(); + const { clearQueue, restoreQueue } = useQueueControls(); useEffect(() => { const root = document.documentElement; @@ -43,24 +43,34 @@ export const App = () => { // Start the mpv instance on startup 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) { - const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; - const properties = { - ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), - }; - - mpvPlayer?.initialize({ - extraParameters, - properties, - }); - - mpvPlayer?.volume(properties.volume); + initializeMpv(); } return () => { - mpvPlayer?.quit(); + clearQueue(); + mpvPlayer?.stop(); + mpvPlayer?.cleanup(); }; - }, [playbackType]); + }, [clearQueue, playbackType]); useEffect(() => { if (isElectron()) { diff --git a/src/renderer/features/player/hooks/use-center-controls.ts b/src/renderer/features/player/hooks/use-center-controls.ts index 97386509..80876230 100644 --- a/src/renderer/features/player/hooks/use-center-controls.ts +++ b/src/renderer/features/player/hooks/use-center-controls.ts @@ -613,10 +613,6 @@ export const useCenterControls = (args: { playersRef: any }) => { handleAutoNext(); }); - mpvPlayerListener.rendererQuit(() => { - handleQuit(); - }); - mpvPlayerListener.rendererToggleShuffle(() => { handleToggleShuffle(); }); @@ -639,7 +635,6 @@ export const useCenterControls = (args: { playersRef: any }) => { ipc?.removeAllListeners('renderer-player-stop'); ipc?.removeAllListeners('renderer-player-current-time'); ipc?.removeAllListeners('renderer-player-auto-next'); - ipc?.removeAllListeners('renderer-player-quit'); ipc?.removeAllListeners('renderer-player-toggle-shuffle'); ipc?.removeAllListeners('renderer-player-toggle-repeat'); ipc?.removeAllListeners('renderer-player-error');