From f82da2e76b5550c8a4abe6a434fd3652c60dbe7c Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Sun, 11 Feb 2024 13:56:29 -0800 Subject: [PATCH 01/12] [enhancement]: Support disabling MPV entirely Supports running Feishin solely using web audio (useful for clients with problems with MPV). Also moves save/restore queue to utils, as MPV object is now optional --- src/i18n/locales/en.json | 2 + src/main/main.ts | 4 +- src/main/preload.ts | 6 ++- src/main/preload/mpv-player.ts | 24 +--------- src/main/preload/utils.ts | 22 +++++++++ src/renderer/app.tsx | 47 ++++++++++--------- .../components/mpv-required.tsx | 35 ++++++++++---- .../routes/action-required-route.tsx | 22 ++++----- .../components/playback/audio-settings.tsx | 31 +++++++++++- src/renderer/router/app-outlet.tsx | 8 +++- 10 files changed, 127 insertions(+), 74 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 41a4528d..b4721db4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -428,6 +428,8 @@ "customFontPath_description": "sets the path to the custom font to use for the application", "disableAutomaticUpdates": "disable automatic updates", "disableLibraryUpdateOnStartup": "disable checking for new versions on startup", + "disableMpv": "Disable MPV", + "disableMpv_description": "If checked, prevent MPV from starting and bypass MPV requirement.", "discordApplicationId": "{{discord}} application id", "discordApplicationId_description": "the application id for {{discord}} rich presence (defaults to {{defaultId}})", "discordIdleStatus": "show rich presence idle status", diff --git a/src/main/main.ts b/src/main/main.ts index a3722c34..235cf345 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -316,7 +316,7 @@ const createWindow = async () => { } const queue = JSON.parse(data.toString()); - getMainWindow()?.webContents.send('renderer-player-restore-queue', queue); + getMainWindow()?.webContents.send('renderer-restore-queue', queue); }); }); }); @@ -362,7 +362,7 @@ const createWindow = async () => { event.preventDefault(); saved = true; - getMainWindow()?.webContents.send('renderer-player-save-queue'); + getMainWindow()?.webContents.send('renderer-save-queue'); ipcMain.once('player-save-queue', async (_event, data: Record) => { const queueLocation = join(app.getPath('userData'), 'queue'); diff --git a/src/main/preload.ts b/src/main/preload.ts index d2183785..963a7927 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -9,6 +9,8 @@ import { mpvPlayer, mpvPlayerListener } from './preload/mpv-player'; import { remote } from './preload/remote'; import { utils } from './preload/utils'; +const disableMpv = localSettings.get('disable_mpv'); + contextBridge.exposeInMainWorld('electron', { browser, discordRpc, @@ -16,8 +18,8 @@ contextBridge.exposeInMainWorld('electron', { localSettings, lyrics, mpris, - mpvPlayer, - mpvPlayerListener, + mpvPlayer: disableMpv ? undefined : mpvPlayer, + mpvPlayerListener: disableMpv ? undefined : mpvPlayerListener, remote, utils, }); diff --git a/src/main/preload/mpv-player.ts b/src/main/preload/mpv-player.ts index a53d52d1..79d68561 100644 --- a/src/main/preload/mpv-player.ts +++ b/src/main/preload/mpv-player.ts @@ -1,5 +1,5 @@ import { ipcRenderer, IpcRendererEvent } from 'electron'; -import { PlayerData, PlayerState } from '/@/renderer/store'; +import { PlayerData } from '/@/renderer/store'; const initialize = (data: { extraParameters?: string[]; properties?: Record }) => { return ipcRenderer.invoke('player-initialize', data); @@ -50,14 +50,6 @@ const previous = () => { ipcRenderer.send('player-previous'); }; -const restoreQueue = () => { - ipcRenderer.send('player-restore-queue'); -}; - -const saveQueue = (data: Record) => { - ipcRenderer.send('player-save-queue', data); -}; - const seek = (seconds: number) => { ipcRenderer.send('player-seek', seconds); }; @@ -154,16 +146,6 @@ const rendererQuit = (cb: (event: IpcRendererEvent) => void) => { ipcRenderer.on('renderer-player-quit', cb); }; -const rendererSaveQueue = (cb: (event: IpcRendererEvent) => void) => { - ipcRenderer.on('renderer-player-save-queue', cb); -}; - -const rendererRestoreQueue = ( - cb: (event: IpcRendererEvent, data: Partial) => void, -) => { - ipcRenderer.on('renderer-player-restore-queue', cb); -}; - const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => { ipcRenderer.on('renderer-player-error', cb); }; @@ -182,8 +164,6 @@ export const mpvPlayer = { previous, quit, restart, - restoreQueue, - saveQueue, seek, seekTo, setProperties, @@ -203,8 +183,6 @@ export const mpvPlayerListener = { rendererPlayPause, rendererPrevious, rendererQuit, - rendererRestoreQueue, - rendererSaveQueue, rendererSkipBackward, rendererSkipForward, rendererStop, diff --git a/src/main/preload/utils.ts b/src/main/preload/utils.ts index 77bc4dad..83b56653 100644 --- a/src/main/preload/utils.ts +++ b/src/main/preload/utils.ts @@ -1,9 +1,31 @@ +import { IpcRendererEvent, ipcRenderer } from 'electron'; import { isMacOS, isWindows, isLinux } from '../utils'; +import { PlayerState } from '/@/renderer/store'; + +const saveQueue = (data: Record) => { + ipcRenderer.send('player-save-queue', data); +}; + +const restoreQueue = () => { + ipcRenderer.send('player-restore-queue'); +}; + +const onSaveQueue = (cb: (event: IpcRendererEvent) => void) => { + ipcRenderer.on('renderer-save-queue', cb); +}; + +const onRestoreQueue = (cb: (event: IpcRendererEvent, data: Partial) => void) => { + ipcRenderer.on('renderer-restore-queue', cb); +}; export const utils = { isLinux, isMacOS, isWindows, + onRestoreQueue, + onSaveQueue, + restoreQueue, + saveQueue, }; export type Utils = typeof utils; diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index d64b2149..6e2882e7 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -33,9 +33,9 @@ ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule initSimpleImg({ threshold: 0.05 }, true); const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null; -const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : null; const ipc = isElectron() ? window.electron.ipc : null; const remote = isElectron() ? window.electron.remote : null; +const utils = isElectron() ? window.electron.utils : null; export const App = () => { const theme = useTheme(); @@ -97,28 +97,31 @@ export const App = () => { // Start the mpv instance on startup useEffect(() => { const initializeMpv = async () => { - const isRunning: boolean | undefined = await mpvPlayer?.isRunning(); + if (playbackType === PlaybackType.LOCAL) { + const isRunning: boolean | undefined = await mpvPlayer?.isRunning(); - mpvPlayer?.stop(); + mpvPlayer?.stop(); - if (!isRunning) { - const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; - const properties: Record = { - speed: usePlayerStore.getState().current.speed, - ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), - }; + if (!isRunning) { + const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; + const properties: Record = { + speed: usePlayerStore.getState().current.speed, + ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), + }; - await mpvPlayer?.initialize({ - extraParameters, - properties, - }); + await mpvPlayer?.initialize({ + extraParameters, + properties, + }); - mpvPlayer?.volume(properties.volume); + mpvPlayer?.volume(properties.volume); + } } - mpvPlayer?.restoreQueue(); + + utils?.restoreQueue(); }; - if (isElectron() && playbackType === PlaybackType.LOCAL) { + if (isElectron()) { initializeMpv(); } @@ -136,8 +139,8 @@ export const App = () => { }, [bindings]); useEffect(() => { - if (isElectron()) { - mpvPlayerListener!.rendererSaveQueue(() => { + if (utils) { + utils.onSaveQueue(() => { const { current, queue } = usePlayerStore.getState(); const stateToSave: Partial> = { current: { @@ -146,10 +149,10 @@ export const App = () => { }, queue, }; - mpvPlayer!.saveQueue(stateToSave); + utils.saveQueue(stateToSave); }); - mpvPlayerListener!.rendererRestoreQueue((_event: any, data) => { + utils.onRestoreQueue((_event: any, data) => { const playerData = restoreQueue(data); if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueue(playerData, true); @@ -158,8 +161,8 @@ export const App = () => { } return () => { - ipc?.removeAllListeners('renderer-player-restore-queue'); - ipc?.removeAllListeners('renderer-player-save-queue'); + ipc?.removeAllListeners('renderer-restore-queue'); + ipc?.removeAllListeners('renderer-save-queue'); }; }, [playbackType, restoreQueue]); diff --git a/src/renderer/features/action-required/components/mpv-required.tsx b/src/renderer/features/action-required/components/mpv-required.tsx index 275d5d44..aff386a9 100644 --- a/src/renderer/features/action-required/components/mpv-required.tsx +++ b/src/renderer/features/action-required/components/mpv-required.tsx @@ -1,23 +1,36 @@ import { useEffect, useState } from 'react'; import isElectron from 'is-electron'; -import { FileInput, Text, Button } from '/@/renderer/components'; +import { FileInput, Text, Button, Checkbox } from '/@/renderer/components'; +import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store'; +import { PlaybackType } from '/@/renderer/types'; +import { useTranslation } from 'react-i18next'; const localSettings = isElectron() ? window.electron.localSettings : null; export const MpvRequired = () => { const [mpvPath, setMpvPath] = useState(''); + const settings = usePlaybackSettings(); + const { setSettings } = useSettingsStoreActions(); + const [disabled, setDisabled] = useState(false); + const { t } = useTranslation(); + const handleSetMpvPath = (e: File) => { localSettings?.set('mpv_path', e.path); }; - useEffect(() => { - const getMpvPath = async () => { - if (!localSettings) return setMpvPath(''); - const mpvPath = localSettings.get('mpv_path') as string; - return setMpvPath(mpvPath); - }; + const handleSetDisableMpv = (disabled: boolean) => { + setDisabled(disabled); + localSettings?.set('disable_mpv', disabled); - getMpvPath(); + setSettings({ + playback: { ...settings, type: disabled ? PlaybackType.WEB : PlaybackType.LOCAL }, + }); + }; + + useEffect(() => { + if (!localSettings) return setMpvPath(''); + const mpvPath = localSettings.get('mpv_path') as string; + return setMpvPath(mpvPath); }, []); return ( @@ -34,9 +47,15 @@ export const MpvRequired = () => { + {t('setting.disable_mpv', { context: 'description' })} + handleSetDisableMpv(e.currentTarget.checked)} + /> ); diff --git a/src/renderer/features/action-required/routes/action-required-route.tsx b/src/renderer/features/action-required/routes/action-required-route.tsx index b5f79e5e..16b64714 100644 --- a/src/renderer/features/action-required/routes/action-required-route.tsx +++ b/src/renderer/features/action-required/routes/action-required-route.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useMemo } from 'react'; import { Center, Group, Stack } from '@mantine/core'; import isElectron from 'is-electron'; import { useTranslation } from 'react-i18next'; @@ -18,23 +18,19 @@ const localSettings = isElectron() ? window.electron.localSettings : null; const ActionRequiredRoute = () => { const { t } = useTranslation(); const currentServer = useCurrentServer(); - const [isMpvRequired, setIsMpvRequired] = useState(false); const isServerRequired = !currentServer; const isCredentialRequired = false; - useEffect(() => { - const getMpvPath = async () => { - if (!localSettings) return setIsMpvRequired(false); - const mpvPath = await localSettings.get('mpv_path'); + const isMpvRequired = useMemo(() => { + if (!localSettings) return false; - if (mpvPath) { - return setIsMpvRequired(false); - } + const mpvPath = localSettings.get('mpv_path'); + if (mpvPath) { + return false; + } - return setIsMpvRequired(true); - }; - - getMpvPath(); + const mpvDisabled = localSettings.get('disable_mpv'); + return !mpvDisabled; }, []); const checks = [ diff --git a/src/renderer/features/settings/components/playback/audio-settings.tsx b/src/renderer/features/settings/components/playback/audio-settings.tsx index 28575279..032fb385 100644 --- a/src/renderer/features/settings/components/playback/audio-settings.tsx +++ b/src/renderer/features/settings/components/playback/audio-settings.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { SelectItem } from '@mantine/core'; import isElectron from 'is-electron'; -import { Select, Slider, toast } from '/@/renderer/components'; +import { Checkbox, Select, Slider, toast } from '/@/renderer/components'; import { SettingsSection, SettingOption, @@ -12,12 +12,15 @@ import { PlaybackType, PlayerStatus, PlaybackStyle, CrossfadeStyle } from '/@/re import { useTranslation } from 'react-i18next'; const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null; +const localSettings = isElectron() ? window.electron.localSettings : null; const getAudioDevice = async () => { const devices = await navigator.mediaDevices.enumerateDevices(); return (devices || []).filter((dev: MediaDeviceInfo) => dev.kind === 'audiooutput'); }; +const initialDisable = (localSettings?.get('disable_mpv') as boolean | undefined) ?? false; + export const AudioSettings = () => { const { t } = useTranslation(); const settings = usePlaybackSettings(); @@ -25,6 +28,18 @@ export const AudioSettings = () => { const status = useCurrentStatus(); const [audioDevices, setAudioDevices] = useState([]); + const [disableMpv, setDisableMpv] = useState(initialDisable); + + const handleSetDisableMpv = (disabled: boolean) => { + setDisableMpv(disabled); + localSettings?.set('disable_mpv', disabled); + + if (disabled) { + setSettings({ + playback: { ...settings, type: disabled ? PlaybackType.WEB : PlaybackType.LOCAL }, + }); + } + }; useEffect(() => { const getAudioDevices = () => { @@ -45,6 +60,18 @@ export const AudioSettings = () => { }, [settings.type, t]); const audioOptions: SettingOption[] = [ + { + control: ( + handleSetDisableMpv(e.currentTarget.checked)} + /> + ), + description: t('setting.disableMpv', { context: 'description' }), + isHidden: !isElectron(), + note: t('common.restartRequired', { postProcess: 'sentenceCase' }), + title: t('setting.disableMpv'), + }, { control: ( { context: 'description', postProcess: 'sentenceCase', }), - isHidden: !isElectron() || initialDisable || disableMpv, + isHidden: !isElectron(), note: status === PlayerStatus.PLAYING ? t('common.playerMustBePaused', { postProcess: 'sentenceCase' }) diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx index c62fb913..10131c53 100644 --- a/src/renderer/router/app-outlet.tsx +++ b/src/renderer/router/app-outlet.tsx @@ -5,7 +5,6 @@ import { AppRoute } from '/@/renderer/router/routes'; import { useCurrentServer } from '/@/renderer/store'; import { toast } from '/@/renderer/components'; -const localSettings = isElectron() ? window.electron.localSettings : null; const ipc = isElectron() ? window.electron.ipc : null; const utils = isElectron() ? window.electron.utils : null; @@ -13,20 +12,9 @@ export const AppOutlet = () => { const currentServer = useCurrentServer(); const isActionsRequired = useMemo(() => { - const isMpvRequired = () => { - if (!localSettings) return false; - const mpvPath = localSettings.get('mpv_path'); - if (mpvPath) { - return false; - } - - const mpvDisabled = localSettings.get('disable_mpv'); - return !mpvDisabled; - }; - const isServerRequired = !currentServer; - const actions = [isServerRequired, isMpvRequired()]; + const actions = [isServerRequired]; const isActionRequired = actions.some((c) => c); return isActionRequired; From 60105103f383df12da8559b6d79da06081cf30ef Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 12 Feb 2024 20:11:55 -0800 Subject: [PATCH 07/12] Improve mpv error logging --- src/main/features/core/player/index.ts | 274 +++++++++++++------------ src/main/main.ts | 3 +- src/main/preload/mpv-player.ts | 1 - src/main/utils.ts | 31 ++- 4 files changed, 178 insertions(+), 131 deletions(-) diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index b1843acc..1e218fdf 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -4,7 +4,7 @@ import uniq from 'lodash/uniq'; import MpvAPI from 'node-mpv'; import { getMainWindow, sendToastToRenderer } from '../../../main'; import { PlayerData } from '/@/renderer/store'; -import { isWindows } from '../../../utils'; +import { createLog, isWindows } from '../../../utils'; import { store } from '../settings'; declare module 'node-mpv'; @@ -19,6 +19,40 @@ declare module 'node-mpv'; let mpvInstance: MpvAPI | null = null; +const NodeMpvErrorCode = { + 0: 'Unable to load file or stream', + 1: 'Invalid argument', + 2: 'Binary not found', + 3: 'IPC command invalid', + 4: 'Unable to bind IPC socket', + 5: 'Connection timeout', + 6: 'MPV is already running', + 7: 'Could not send IPC message', + 8: 'MPV is not running', + 9: 'Unsupported protocol', +}; + +type NodeMpvError = { + errcode: number; + method: string; + stackTrace: string; + verbose: string; +}; + +const mpvLog = (action: string, err?: NodeMpvError) => { + if (err) { + const message = `[AUDIO PLAYER] ${action} - mpv errorcode ${err.errcode} - ${ + NodeMpvErrorCode[err.errcode as keyof typeof NodeMpvErrorCode] + }`; + + sendToastToRenderer({ message, type: 'error' }); + createLog({ message, type: 'error' }); + } + + const message = `[AUDIO PLAYER] ${action}`; + createLog({ message, type: 'error' }); +}; + const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined; const isDevelopment = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; @@ -45,7 +79,6 @@ const createMpv = async (data: { const { extraParameters, properties } = data; const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]); - console.log('Setting mpv params: ', params); const extra = isDevelopment ? '-dev' : ''; @@ -62,15 +95,13 @@ const createMpv = async (data: { try { await mpv.start(); - } catch (error: { message: string; stack: any } | any) { - console.log('MPV failed to start', error); + } catch (error: any) { + console.log('mpv failed to start', error); } finally { - console.log('Setting MPV properties: ', properties); await mpv.setMultipleProperties(properties || {}); } - mpv.on('status', (status, ...rest) => { - console.log('MPV Event: status', status.property, status.value, rest); + mpv.on('status', (status) => { if (status.property === 'playlist-pos') { if (status.value === -1) { mpv?.stop(); @@ -84,19 +115,16 @@ const createMpv = async (data: { // Automatically updates the play button when the player is playing mpv.on('resumed', () => { - console.log('MPV Event: resumed'); getMainWindow()?.webContents.send('renderer-player-play'); }); // Automatically updates the play button when the player is 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 mpv.on('paused', () => { - console.log('MPV Event: paused'); getMainWindow()?.webContents.send('renderer-player-pause'); }); @@ -105,10 +133,6 @@ const createMpv = async (data: { getMainWindow()?.webContents.send('renderer-player-current-time', time); }); - mpv.on('quit', () => { - console.log('MPV Event: quit'); - }); - return mpv; }; @@ -117,27 +141,31 @@ export const getMpvInstance = () => { }; ipcMain.on('player-set-properties', async (_event, data: Record) => { + mpvLog(`Setting properties: ${JSON.stringify(data)}`); if (data.length === 0) { return; } - if (data.length === 1) { - getMpvInstance()?.setProperty(Object.keys(data)[0], Object.values(data)[0]); - } else { - getMpvInstance()?.setMultipleProperties(data); + try { + if (data.length === 1) { + getMpvInstance()?.setProperty(Object.keys(data)[0], Object.values(data)[0]); + } else { + getMpvInstance()?.setMultipleProperties(data); + } + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to set properties: ${JSON.stringify(data)}`, err); } }); ipcMain.on( 'player-restart', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { - console.log('Initializing MPV with data: ', data); mpvInstance?.quit(); try { + mpvLog(`Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`); mpvInstance = await createMpv(data); - } catch (err) { - console.log('init error', err); - sendToastToRenderer({ message: 'Initialization error', type: 'error' }); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to initialize mpv', err); } }, ); @@ -145,20 +173,23 @@ ipcMain.on( ipcMain.handle( 'player-initialize', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { - console.log('Initializing MPV with data: ', data); try { + mpvLog(`Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`); mpvInstance = await createMpv(data); - } catch (err) { - console.log('init error', err); - sendToastToRenderer({ message: 'Initialization error', type: 'error' }); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to initialize mpv', err); } }, ); ipcMain.on('player-quit', async () => { - mpvInstance?.stop(); - mpvInstance?.quit(); - mpvInstance = null; + try { + mpvInstance?.stop(); + mpvInstance?.quit(); + mpvInstance = null; + } catch (err: NodeMpvError | any) { + mpvLog('Failed to quit mpv', err); + } }); ipcMain.handle('player-is-running', async () => { @@ -171,99 +202,93 @@ ipcMain.handle('player-clean-up', async () => { }); ipcMain.on('player-start', async () => { - await getMpvInstance() - ?.play() - .catch((err) => { - console.log('MPV failed to play', err); - }); + try { + await getMpvInstance()?.play(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to start mpv playback', err); + } }); // Starts the player ipcMain.on('player-play', async () => { - await getMpvInstance() - ?.play() - .catch((err) => { - console.log('MPV failed to play', err); - }); + try { + await getMpvInstance()?.play(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to start mpv playback', err); + } }); // Pauses the player ipcMain.on('player-pause', async () => { - await getMpvInstance() - ?.pause() - .catch((err) => { - console.log('MPV failed to pause', err); - }); + try { + await getMpvInstance()?.pause(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to pause mpv playback', err); + } }); // Stops the player ipcMain.on('player-stop', async () => { - await getMpvInstance() - ?.stop() - .catch((err) => { - console.log('MPV failed to stop', err); - }); + try { + await getMpvInstance()?.stop(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to stop mpv playback', err); + } }); // Goes to the next track in the playlist ipcMain.on('player-next', async () => { - await getMpvInstance() - ?.next() - .catch((err) => { - console.log('MPV failed to go to next', err); - }); + try { + await getMpvInstance()?.next(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to go to next track', err); + } }); // Goes to the previous track in the playlist ipcMain.on('player-previous', async () => { - await getMpvInstance() - ?.prev() - .catch((err) => { - console.log('MPV failed to go to previous', err); - }); + try { + await getMpvInstance()?.prev(); + } catch (err: NodeMpvError | any) { + mpvLog('Failed to go to previous track', err); + } }); // Seeks forward or backward by the given amount of seconds ipcMain.on('player-seek', async (_event, time: number) => { - await getMpvInstance() - ?.seek(time) - .catch((err) => { - console.log('MPV failed to seek', err); - }); + try { + await getMpvInstance()?.seek(time); + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to seek by ${time} seconds`, err); + } }); // Seeks to the given time in seconds ipcMain.on('player-seek-to', async (_event, time: number) => { - await getMpvInstance() - ?.goToPosition(time) - .catch((err) => { - console.log(`MPV failed to seek to ${time}`, err); - }); + try { + await getMpvInstance()?.goToPosition(time); + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to seek to ${time} seconds`, 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() - .catch((err) => { - console.log('MPV failed to clear playlist', err); - }); - - await getMpvInstance() - ?.pause() - .catch((err) => { - console.log('MPV failed to pause', err); - }); - return; + try { + await getMpvInstance()?.clearPlaylist(); + await getMpvInstance()?.pause(); + return; + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to clear play queue`, err); + } } try { if (data.queue.current) { await getMpvInstance() ?.load(data.queue.current.streamUrl, 'replace') - .catch((err) => { - console.log('MPV failed to load song', err); + .catch(() => { getMpvInstance()?.play(); }); @@ -271,8 +296,8 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) await getMpvInstance()?.load(data.queue.next.streamUrl, 'append'); } } - } catch (err) { - console.error(err); + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to set play queue`, err); } if (pause) { @@ -282,30 +307,22 @@ 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() - .catch((err) => { - console.log('MPV failed to get playlist size', err); - }); + try { + const size = await getMpvInstance()?.getPlaylistSize(); - if (!size) { - return; - } + if (!size) { + return; + } - if (size > 1) { - await getMpvInstance() - ?.playlistRemove(1) - .catch((err) => { - console.log('MPV failed to remove song from playlist', err); - }); - } + if (size > 1) { + await getMpvInstance()?.playlistRemove(1); + } - if (data.queue.next) { - await getMpvInstance() - ?.load(data.queue.next.streamUrl, 'append') - .catch((err) => { - console.log('MPV failed to load next song', err); - }); + if (data.queue.next) { + await getMpvInstance()?.load(data.queue.next.streamUrl, 'append'); + } + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to set play queue`, err); } }); @@ -314,38 +331,41 @@ 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) - .catch((err) => { - console.log('MPV failed to remove song from playlist', err); - getMpvInstance()?.pause(); - }); - - if (data.queue.next) { + try { await getMpvInstance() - ?.load(data.queue.next.streamUrl, 'append') - .catch((err) => { - console.log('MPV failed to load next song', err); + ?.playlistRemove(0) + .catch(() => { + getMpvInstance()?.pause(); }); + + if (data.queue.next) { + await getMpvInstance()?.load(data.queue.next.streamUrl, 'append'); + } + } catch (err: NodeMpvError | any) { + mpvLog(`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) - .catch((err) => { - console.log('MPV failed to set volume', err); - }); + try { + if (!value || value < 0 || value > 100) { + return; + } + + await getMpvInstance()?.volume(value); + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to set volume to ${value}`, err); + } }); // Toggles the mute status ipcMain.on('player-mute', async (_event, mute: boolean) => { - await getMpvInstance() - ?.mute(mute) - .catch((err) => { - console.log('MPV failed to toggle mute', err); - }); + try { + await getMpvInstance()?.mute(mute); + } catch (err: NodeMpvError | any) { + mpvLog(`Failed to set mute status`, err); + } }); ipcMain.handle('player-get-time', async (): Promise => { diff --git a/src/main/main.ts b/src/main/main.ts index e02e430c..848fc3a8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -38,6 +38,7 @@ import { isWindows, resolveHtmlPath, createLog, + autoUpdaterLogInterface, } from './utils'; import './features'; import type { TitleTheme } from '/@/renderer/types'; @@ -47,7 +48,7 @@ declare module 'node-mpv'; export default class AppUpdater { constructor() { log.transports.file.level = 'info'; - autoUpdater.logger = log; + autoUpdater.logger = autoUpdaterLogInterface; autoUpdater.checkForUpdatesAndNotify(); } } diff --git a/src/main/preload/mpv-player.ts b/src/main/preload/mpv-player.ts index 79d68561..d7d84f8d 100644 --- a/src/main/preload/mpv-player.ts +++ b/src/main/preload/mpv-player.ts @@ -18,7 +18,6 @@ const cleanup = () => { }; const setProperties = (data: Record) => { - console.log('Setting property :>>', data); ipcRenderer.send('player-set-properties', data); }; diff --git a/src/main/utils.ts b/src/main/utils.ts index ab49f2a1..8a29f6ef 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -52,7 +52,7 @@ export const hotkeyToElectronAccelerator = (hotkey: string) => { return accelerator; }; -const logInstance = { +const logMethod = { debug: log.debug, error: log.error, info: log.info, @@ -61,9 +61,36 @@ const logInstance = { warning: log.warn, }; +const logColor = { + debug: 'blue', + error: 'red', + info: 'blue', + success: 'green', + verbose: 'blue', + warning: 'yellow', +}; + export const createLog = (data: { message: string; type: 'debug' | 'verbose' | 'success' | 'error' | 'warning' | 'info'; }) => { - logInstance[data.type](data.message); + logMethod[data.type](`%c${data.message}`, `color: ${logColor[data.type]}`); +}; + +export const autoUpdaterLogInterface = { + debug: (message: string) => { + createLog({ message: `[SYSTEM] ${message}`, type: 'debug' }); + }, + + error: (message: string) => { + createLog({ message: `[SYSTEM] ${message}`, type: 'error' }); + }, + + info: (message: string) => { + createLog({ message: `[SYSTEM] ${message}`, type: 'info' }); + }, + + warn: (message: string) => { + createLog({ message: `[SYSTEM] ${message}`, type: 'warning' }); + }, }; From dcd130fb6c1d05f6b9ba2ea4ebb743df877920ac Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 12 Feb 2024 20:50:09 -0800 Subject: [PATCH 08/12] Refactor mpv log to allow for custom toast --- src/main/features/core/player/index.ts | 59 ++++++++++++++++---------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index 1e218fdf..22457b81 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -39,7 +39,12 @@ type NodeMpvError = { verbose: string; }; -const mpvLog = (action: string, err?: NodeMpvError) => { +const mpvLog = ( + data: { action: string; toast?: 'info' | 'success' | 'warning' }, + err?: NodeMpvError, +) => { + const { action, toast } = data; + if (err) { const message = `[AUDIO PLAYER] ${action} - mpv errorcode ${err.errcode} - ${ NodeMpvErrorCode[err.errcode as keyof typeof NodeMpvErrorCode] @@ -51,6 +56,9 @@ const mpvLog = (action: string, err?: NodeMpvError) => { const message = `[AUDIO PLAYER] ${action}`; createLog({ message, type: 'error' }); + if (toast) { + sendToastToRenderer({ message, type: toast }); + } }; const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined; @@ -141,7 +149,7 @@ export const getMpvInstance = () => { }; ipcMain.on('player-set-properties', async (_event, data: Record) => { - mpvLog(`Setting properties: ${JSON.stringify(data)}`); + mpvLog({ action: `Setting properties: ${JSON.stringify(data)}` }); if (data.length === 0) { return; } @@ -153,19 +161,22 @@ ipcMain.on('player-set-properties', async (_event, data: Record) => getMpvInstance()?.setMultipleProperties(data); } } catch (err: NodeMpvError | any) { - mpvLog(`Failed to set properties: ${JSON.stringify(data)}`, err); + mpvLog({ action: `Failed to set properties: ${JSON.stringify(data)}` }, err); } }); -ipcMain.on( +ipcMain.handle( 'player-restart', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { mpvInstance?.quit(); try { - mpvLog(`Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`); + mpvLog({ + action: `Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`, + }); mpvInstance = await createMpv(data); + mpvLog({ action: 'Restarted mpv', toast: 'success' }); } catch (err: NodeMpvError | any) { - mpvLog('Failed to initialize mpv', err); + mpvLog({ action: 'Failed to initialize mpv' }, err); } }, ); @@ -174,10 +185,12 @@ ipcMain.handle( 'player-initialize', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { try { - mpvLog(`Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`); + mpvLog({ + action: `Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`, + }); mpvInstance = await createMpv(data); } catch (err: NodeMpvError | any) { - mpvLog('Failed to initialize mpv', err); + mpvLog({ action: 'Failed to initialize mpv' }, err); } }, ); @@ -188,7 +201,7 @@ ipcMain.on('player-quit', async () => { mpvInstance?.quit(); mpvInstance = null; } catch (err: NodeMpvError | any) { - mpvLog('Failed to quit mpv', err); + mpvLog({ action: 'Failed to quit mpv' }, err); } }); @@ -205,7 +218,7 @@ ipcMain.on('player-start', async () => { try { await getMpvInstance()?.play(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to start mpv playback', err); + mpvLog({ action: 'Failed to start mpv playback' }, err); } }); @@ -214,7 +227,7 @@ ipcMain.on('player-play', async () => { try { await getMpvInstance()?.play(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to start mpv playback', err); + mpvLog({ action: 'Failed to start mpv playback' }, err); } }); @@ -223,7 +236,7 @@ ipcMain.on('player-pause', async () => { try { await getMpvInstance()?.pause(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to pause mpv playback', err); + mpvLog({ action: 'Failed to pause mpv playback' }, err); } }); @@ -232,7 +245,7 @@ ipcMain.on('player-stop', async () => { try { await getMpvInstance()?.stop(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to stop mpv playback', err); + mpvLog({ action: 'Failed to stop mpv playback' }, err); } }); @@ -241,7 +254,7 @@ ipcMain.on('player-next', async () => { try { await getMpvInstance()?.next(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to go to next track', err); + mpvLog({ action: 'Failed to go to next track' }, err); } }); @@ -250,7 +263,7 @@ ipcMain.on('player-previous', async () => { try { await getMpvInstance()?.prev(); } catch (err: NodeMpvError | any) { - mpvLog('Failed to go to previous track', err); + mpvLog({ action: 'Failed to go to previous track' }, err); } }); @@ -259,7 +272,7 @@ ipcMain.on('player-seek', async (_event, time: number) => { try { await getMpvInstance()?.seek(time); } catch (err: NodeMpvError | any) { - mpvLog(`Failed to seek by ${time} seconds`, err); + mpvLog({ action: `Failed to seek by ${time} seconds` }, err); } }); @@ -268,7 +281,7 @@ ipcMain.on('player-seek-to', async (_event, time: number) => { try { await getMpvInstance()?.goToPosition(time); } catch (err: NodeMpvError | any) { - mpvLog(`Failed to seek to ${time} seconds`, err); + mpvLog({ action: `Failed to seek to ${time} seconds` }, err); } }); @@ -280,7 +293,7 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) await getMpvInstance()?.pause(); return; } catch (err: NodeMpvError | any) { - mpvLog(`Failed to clear play queue`, err); + mpvLog({ action: `Failed to clear play queue` }, err); } } @@ -297,7 +310,7 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) } } } catch (err: NodeMpvError | any) { - mpvLog(`Failed to set play queue`, err); + mpvLog({ action: `Failed to set play queue` }, err); } if (pause) { @@ -322,7 +335,7 @@ ipcMain.on('player-set-queue-next', async (_event, data: PlayerData) => { await getMpvInstance()?.load(data.queue.next.streamUrl, 'append'); } } catch (err: NodeMpvError | any) { - mpvLog(`Failed to set play queue`, err); + mpvLog({ action: `Failed to set play queue` }, err); } }); @@ -342,7 +355,7 @@ ipcMain.on('player-auto-next', async (_event, data: PlayerData) => { await getMpvInstance()?.load(data.queue.next.streamUrl, 'append'); } } catch (err: NodeMpvError | any) { - mpvLog(`Failed to load next song`, err); + mpvLog({ action: `Failed to load next song` }, err); } }); @@ -355,7 +368,7 @@ ipcMain.on('player-volume', async (_event, value: number) => { await getMpvInstance()?.volume(value); } catch (err: NodeMpvError | any) { - mpvLog(`Failed to set volume to ${value}`, err); + mpvLog({ action: `Failed to set volume to ${value}` }, err); } }); @@ -364,7 +377,7 @@ ipcMain.on('player-mute', async (_event, mute: boolean) => { try { await getMpvInstance()?.mute(mute); } catch (err: NodeMpvError | any) { - mpvLog(`Failed to set mute status`, err); + mpvLog({ action: `Failed to set mute status` }, err); } }); From 8f4ff9286a72d488c75393d52b35188c39794885 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 12 Feb 2024 20:50:50 -0800 Subject: [PATCH 09/12] Allow deletion on local settings keys --- src/main/preload/local-settings.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/preload/local-settings.ts b/src/main/preload/local-settings.ts index 7b1b3732..b8aafba0 100644 --- a/src/main/preload/local-settings.ts +++ b/src/main/preload/local-settings.ts @@ -4,7 +4,15 @@ import type { TitleTheme } from '/@/renderer/types'; const store = new Store(); -const set = (property: string, value: string | Record | boolean | string[]) => { +const set = ( + property: string, + value: string | Record | boolean | string[] | undefined, +) => { + if (value === undefined) { + store.delete(property); + return; + } + store.set(`${property}`, value); }; From fb08502e510e7ba380ac99ef695125a8cee5e0e2 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 12 Feb 2024 21:21:17 -0800 Subject: [PATCH 10/12] Add mpv path reload and clear functionality --- src/i18n/locales/en.json | 5 +- src/main/features/core/player/index.ts | 25 ++++-- src/main/preload/mpv-player.ts | 6 +- .../components/playback/mpv-settings.tsx | 76 +++++++++++++++++-- 4 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 41a4528d..0b9e23e3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -84,6 +84,7 @@ "random": "random", "rating": "rating", "refresh": "refresh", + "reload": "reload", "reset": "reset", "resetToDefault": "reset to default", "restartRequired": "restart required", @@ -506,9 +507,9 @@ "minimumScrobbleSeconds": "minimum scrobble (seconds)", "minimumScrobbleSeconds_description": "the minimum duration in seconds of the song that must be played before it is scrobbled", "mpvExecutablePath": "mpv executable path", - "mpvExecutablePath_description": "sets the path to the mpv executable", - "mpvExecutablePath_help": "one per line", + "mpvExecutablePath_description": "sets the path to the mpv executable. if left empty, the default path will be used", "mpvExtraParameters": "mpv parameters", + "mpvExtraParameters_help": "one per line", "passwordStore": "passwords/secret store", "passwordStore_description": "what password/secret store to use. change this if you are having issues storing passwords.", "playbackStyle": "playback style", diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index 22457b81..f096829f 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -81,10 +81,11 @@ const DEFAULT_MPV_PARAMETERS = (extraParameters?: string[]) => { }; const createMpv = async (data: { + binaryPath?: string; extraParameters?: string[]; properties?: Record; }): Promise => { - const { extraParameters, properties } = data; + const { extraParameters, properties, binaryPath } = data; const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]); @@ -94,7 +95,7 @@ const createMpv = async (data: { { audio_only: true, auto_restart: false, - binary: MPV_BINARY_PATH || undefined, + binary: binaryPath || MPV_BINARY_PATH || undefined, socket: isWindows() ? `\\\\.\\pipe\\mpvserver${extra}` : `/tmp/node-mpv${extra}.sock`, time_update: 1, }, @@ -168,15 +169,20 @@ ipcMain.on('player-set-properties', async (_event, data: Record) => ipcMain.handle( 'player-restart', async (_event, data: { extraParameters?: string[]; properties?: Record }) => { - mpvInstance?.quit(); try { mpvLog({ action: `Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`, }); + + // Clean up previous mpv instance + getMpvInstance()?.stop(); + getMpvInstance()?.quit(); + mpvInstance = null; + mpvInstance = await createMpv(data); mpvLog({ action: 'Restarted mpv', toast: 'success' }); } catch (err: NodeMpvError | any) { - mpvLog({ action: 'Failed to initialize mpv' }, err); + mpvLog({ action: 'Failed to restart mpv' }, err); } }, ); @@ -197,8 +203,8 @@ ipcMain.handle( ipcMain.on('player-quit', async () => { try { - mpvInstance?.stop(); - mpvInstance?.quit(); + getMpvInstance()?.stop(); + getMpvInstance()?.quit(); mpvInstance = null; } catch (err: NodeMpvError | any) { mpvLog({ action: 'Failed to quit mpv' }, err); @@ -382,7 +388,12 @@ ipcMain.on('player-mute', async (_event, mute: boolean) => { }); ipcMain.handle('player-get-time', async (): Promise => { - return getMpvInstance()?.getTimePosition(); + try { + return getMpvInstance()?.getTimePosition(); + } catch (err: NodeMpvError | any) { + mpvLog({ action: `Failed to get current time` }, err); + return 0; + } }); app.on('before-quit', () => { diff --git a/src/main/preload/mpv-player.ts b/src/main/preload/mpv-player.ts index d7d84f8d..e93bacde 100644 --- a/src/main/preload/mpv-player.ts +++ b/src/main/preload/mpv-player.ts @@ -5,7 +5,11 @@ const initialize = (data: { extraParameters?: string[]; properties?: Record }) => { +const restart = (data: { + binaryPath?: string; + extraParameters?: string[]; + properties?: Record; +}) => { return ipcRenderer.invoke('player-restart', data); }; diff --git a/src/renderer/features/settings/components/playback/mpv-settings.tsx b/src/renderer/features/settings/components/playback/mpv-settings.tsx index 15443673..2128ba44 100644 --- a/src/renderer/features/settings/components/playback/mpv-settings.tsx +++ b/src/renderer/features/settings/components/playback/mpv-settings.tsx @@ -1,7 +1,15 @@ import { useEffect, useState } from 'react'; -import { Divider, Stack } from '@mantine/core'; +import { Divider, Group, Stack } from '@mantine/core'; import isElectron from 'is-electron'; -import { FileInput, Textarea, Text, Select, NumberInput, Switch } from '/@/renderer/components'; +import { + FileInput, + Textarea, + Text, + Select, + NumberInput, + Switch, + Button, +} from '/@/renderer/components'; import { SettingsSection, SettingOption, @@ -9,10 +17,13 @@ import { import { SettingsState, usePlaybackSettings, + useSettingsStore, useSettingsStoreActions, } from '/@/renderer/store/settings.store'; import { PlaybackType } from '/@/renderer/types'; import { useTranslation } from 'react-i18next'; +import { RiCloseLine, RiRestartLine } from 'react-icons/ri'; +import { usePlayerControls, usePlayerStore, useQueueControls } from '/@/renderer/store'; const localSettings = isElectron() ? window.electron.localSettings : null; const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null; @@ -64,11 +75,20 @@ export const MpvSettings = () => { const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); + const { pause } = usePlayerControls(); + const { clearQueue } = useQueueControls(); const [mpvPath, setMpvPath] = useState(''); - const handleSetMpvPath = (e: File) => { + const handleSetMpvPath = (e: File | null) => { + if (e === null) { + localSettings?.set('mpv_path', undefined); + setMpvPath(''); + return; + } + localSettings?.set('mpv_path', e.path); + setMpvPath(e.path); }; useEffect(() => { @@ -100,6 +120,22 @@ export const MpvSettings = () => { mpvPlayer?.setProperties(mpvSetting); }; + const handleReloadMpv = () => { + pause(); + clearQueue(); + + const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; + const properties: Record = { + speed: usePlayerStore.getState().current.speed, + ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), + }; + mpvPlayer?.restart({ + binaryPath: mpvPath || undefined, + extraParameters, + properties, + }); + }; + const handleSetExtraParameters = (data: string[]) => { setSettings({ playback: { @@ -112,11 +148,35 @@ export const MpvSettings = () => { const options: SettingOption[] = [ { control: ( - + + + + {mpvPath && ( + + )} + ), description: t('setting.mpvExecutablePath', { context: 'description', From 9b0c9ba3acdb08e6d1bc54fc0f39e05e67161e5a Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 13 Feb 2024 02:05:59 -0800 Subject: [PATCH 11/12] Fallback to web player if mpv fails to run --- src/main/features/core/player/index.ts | 12 +++++-- src/main/preload/mpv-player.ts | 5 +++ .../context-menu/context-menu-provider.tsx | 16 ++++----- .../features/lyrics/synchronized-lyrics.tsx | 10 +++--- .../components/play-queue-list-controls.tsx | 14 ++++---- .../now-playing/components/play-queue.tsx | 8 ++--- .../player/components/center-controls.tsx | 8 ++--- .../features/player/components/playerbar.tsx | 5 +-- .../player/hooks/use-center-controls.ts | 33 +++++++++---------- .../player/hooks/use-handle-playqueue-add.ts | 8 ++--- .../components/playback/mpv-settings.tsx | 27 ++++++++------- src/renderer/router/app-outlet.tsx | 11 +++++-- src/renderer/store/player.store.ts | 16 ++++++++- src/renderer/store/settings.store.ts | 12 ++++++- 14 files changed, 115 insertions(+), 70 deletions(-) diff --git a/src/main/features/core/player/index.ts b/src/main/features/core/player/index.ts index f096829f..26307093 100644 --- a/src/main/features/core/player/index.ts +++ b/src/main/features/core/player/index.ts @@ -149,6 +149,10 @@ export const getMpvInstance = () => { return mpvInstance; }; +const setAudioPlayerFallback = (isError: boolean) => { + getMainWindow()?.webContents.send('renderer-player-fallback', isError); +}; + ipcMain.on('player-set-properties', async (_event, data: Record) => { mpvLog({ action: `Setting properties: ${JSON.stringify(data)}` }); if (data.length === 0) { @@ -181,8 +185,10 @@ ipcMain.handle( mpvInstance = await createMpv(data); mpvLog({ action: 'Restarted mpv', toast: 'success' }); + setAudioPlayerFallback(false); } catch (err: NodeMpvError | any) { - mpvLog({ action: 'Failed to restart mpv' }, err); + mpvLog({ action: 'Failed to restart mpv, falling back to web player' }, err); + setAudioPlayerFallback(true); } }, ); @@ -195,8 +201,10 @@ ipcMain.handle( action: `Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`, }); mpvInstance = await createMpv(data); + setAudioPlayerFallback(false); } catch (err: NodeMpvError | any) { - mpvLog({ action: 'Failed to initialize mpv' }, err); + mpvLog({ action: 'Failed to initialize mpv, falling back to web player' }, err); + setAudioPlayerFallback(true); } }, ); diff --git a/src/main/preload/mpv-player.ts b/src/main/preload/mpv-player.ts index e93bacde..78108e29 100644 --- a/src/main/preload/mpv-player.ts +++ b/src/main/preload/mpv-player.ts @@ -153,6 +153,10 @@ const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => { ipcRenderer.on('renderer-player-error', cb); }; +const rendererPlayerFallback = (cb: (event: IpcRendererEvent, data: boolean) => void) => { + ipcRenderer.on('renderer-player-fallback', cb); +}; + export const mpvPlayer = { autoNext, cleanup, @@ -184,6 +188,7 @@ export const mpvPlayerListener = { rendererPause, rendererPlay, rendererPlayPause, + rendererPlayerFallback, rendererPrevious, rendererQuit, rendererSkipBackward, diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index 4ebe749a..83cce029 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -51,7 +51,7 @@ import { usePlayerStore, useQueueControls, } from '/@/renderer/store'; -import { usePlayerType } from '/@/renderer/store/settings.store'; +import { usePlaybackType } from '/@/renderer/store/settings.store'; import { Play, PlaybackType } from '/@/renderer/types'; type ContextMenuContextProps = { @@ -575,7 +575,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { [ctx.data, ctx.dataNodes, updateRatingMutation], ); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const { moveToBottomOfQueue, moveToTopOfQueue, removeFromQueue } = useQueueControls(); const handleMoveToBottom = useCallback(() => { @@ -584,10 +584,10 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const playerData = moveToBottomOfQueue(uniqueIds); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } - }, [ctx.dataNodes, moveToBottomOfQueue, playerType]); + }, [ctx.dataNodes, moveToBottomOfQueue, playbackType]); const handleMoveToTop = useCallback(() => { const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); @@ -595,10 +595,10 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const playerData = moveToTopOfQueue(uniqueIds); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } - }, [ctx.dataNodes, moveToTopOfQueue, playerType]); + }, [ctx.dataNodes, moveToTopOfQueue, playbackType]); const handleRemoveSelected = useCallback(() => { const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); @@ -608,7 +608,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { const playerData = removeFromQueue(uniqueIds); const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { if (isCurrentSongRemoved) { mpvPlayer!.setQueue(playerData); } else { @@ -621,7 +621,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { if (isCurrentSongRemoved) { remote?.updateSong({ song: playerData.current.song }); } - }, [ctx.dataNodes, ctx.tableApi, playerType, removeFromQueue]); + }, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]); const handleDeselectAll = useCallback(() => { ctx.tableApi?.deselectAll(); diff --git a/src/renderer/features/lyrics/synchronized-lyrics.tsx b/src/renderer/features/lyrics/synchronized-lyrics.tsx index 1207798c..e0e0c17e 100644 --- a/src/renderer/features/lyrics/synchronized-lyrics.tsx +++ b/src/renderer/features/lyrics/synchronized-lyrics.tsx @@ -3,7 +3,7 @@ import { useCurrentStatus, useCurrentTime, useLyricsSettings, - usePlayerType, + usePlaybackType, useSeeked, } from '/@/renderer/store'; import { PlaybackType, PlayerStatus } from '/@/renderer/types'; @@ -59,7 +59,7 @@ export const SynchronizedLyrics = ({ }: SynchronizedLyricsProps) => { const playersRef = PlayersRef; const status = useCurrentStatus(); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const now = useCurrentTime(); const settings = useLyricsSettings(); @@ -96,7 +96,7 @@ export const SynchronizedLyrics = ({ }; const getCurrentTime = useCallback(async () => { - if (isElectron() && playerType !== PlaybackType.WEB) { + if (isElectron() && playbackType !== PlaybackType.WEB) { if (mpvPlayer) { return mpvPlayer.getCurrentTime(); } @@ -116,7 +116,7 @@ export const SynchronizedLyrics = ({ if (!player) return 0; return player.currentTime; - }, [playerType, playersRef]); + }, [playbackType, playersRef]); const setCurrentLyric = useCallback( (timeInMs: number, epoch?: number, targetIndex?: number) => { @@ -222,7 +222,7 @@ export const SynchronizedLyrics = ({ } return () => {}; - }, [getCurrentTime, lyrics, playerType, setCurrentLyric, status]); + }, [getCurrentTime, lyrics, playbackType, setCurrentLyric, status]); useEffect(() => { // This handler is used to deal with changes to the current delay. If the offset diff --git a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx index 72879812..ea13d0b5 100644 --- a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx +++ b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx @@ -15,7 +15,7 @@ import { import { Song } from '/@/renderer/api/types'; import { usePlayerControls, useQueueControls } from '/@/renderer/store'; import { PlaybackType, TableType } from '/@/renderer/types'; -import { usePlayerType } from '/@/renderer/store/settings.store'; +import { usePlaybackType } from '/@/renderer/store/settings.store'; import { usePlayerStore, useSetCurrentTime } from '../../../store/player.store'; import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; @@ -34,7 +34,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const { pause } = usePlayerControls(); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const setCurrentTime = useSetCurrentTime(); const handleMoveToBottom = () => { @@ -44,7 +44,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const playerData = moveToBottomOfQueue(uniqueIds); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } }; @@ -56,7 +56,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const playerData = moveToTopOfQueue(uniqueIds); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } }; @@ -70,7 +70,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const playerData = removeFromQueue(uniqueIds); const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { if (isCurrentSongRemoved) { mpvPlayer!.setQueue(playerData); } else { @@ -86,7 +86,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const handleClearQueue = () => { const playerData = clearQueue(); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueue(playerData); mpvPlayer!.pause(); } @@ -100,7 +100,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr const handleShuffleQueue = () => { const playerData = shuffleQueue(); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } }; diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 0b8bc581..1c98b6c3 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -19,7 +19,7 @@ import { useVolume, } from '/@/renderer/store'; import { - usePlayerType, + usePlaybackType, useSettingsStore, useSettingsStoreActions, useTableSettings, @@ -56,7 +56,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { const { setAppStore } = useAppStoreActions(); const tableConfig = useTableSettings(type); const [gridApi, setGridApi] = useState(); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const { play } = usePlayerControls(); const volume = useVolume(); const isFocused = useAppFocus(); @@ -87,7 +87,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { status: PlayerStatus.PLAYING, }); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.volume(volume); mpvPlayer!.setQueue(playerData); mpvPlayer!.play(); @@ -111,7 +111,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref) => { const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.setQueueNext(playerData); } diff --git a/src/renderer/features/player/components/center-controls.tsx b/src/renderer/features/player/components/center-controls.tsx index cb27df61..c2d72733 100644 --- a/src/renderer/features/player/components/center-controls.tsx +++ b/src/renderer/features/player/components/center-controls.tsx @@ -32,7 +32,7 @@ import { } from '/@/renderer/store'; import { useHotkeySettings, - usePlayerType, + usePlaybackType, useSettingsStore, } from '/@/renderer/store/settings.store'; import { PlayerStatus, PlaybackType, PlayerShuffle, PlayerRepeat } from '/@/renderer/types'; @@ -99,7 +99,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { const currentSong = useCurrentSong(); const skip = useSettingsStore((state) => state.general.skipButtons); const buttonSize = useSettingsStore((state) => state.general.buttonSize); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const player1 = playersRef?.current?.player1; const player2 = playersRef?.current?.player2; const status = useCurrentStatus(); @@ -134,7 +134,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { let interval: any; if (status === PlayerStatus.PLAYING && !isSeeking) { - if (!isElectron() || playerType === PlaybackType.WEB) { + if (!isElectron() || playbackType === PlaybackType.WEB) { interval = setInterval(() => { setCurrentTime(currentPlayerRef.getCurrentTime()); }, 1000); @@ -144,7 +144,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { } return () => clearInterval(interval); - }, [currentPlayerRef, isSeeking, setCurrentTime, playerType, status]); + }, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]); const [seekValue, setSeekValue] = useState(0); diff --git a/src/renderer/features/player/components/playerbar.tsx b/src/renderer/features/player/components/playerbar.tsx index b251ad0d..03555297 100644 --- a/src/renderer/features/player/components/playerbar.tsx +++ b/src/renderer/features/player/components/playerbar.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import isElectron from 'is-electron'; import styled from 'styled-components'; -import { useSettingsStore } from '/@/renderer/store/settings.store'; +import { usePlaybackType, useSettingsStore } from '/@/renderer/store/settings.store'; import { PlaybackType } from '/@/renderer/types'; import { AudioPlayer } from '/@/renderer/components'; import { @@ -64,6 +64,7 @@ const remote = isElectron() ? window.electron.remote : null; export const Playerbar = () => { const playersRef = PlayersRef; const settings = useSettingsStore((state) => state.playback); + const playbackType = usePlaybackType(); const volume = useVolume(); const player1 = usePlayer1Data(); const player2 = usePlayer2Data(); @@ -96,7 +97,7 @@ export const Playerbar = () => { - {settings.type === PlaybackType.WEB && ( + {playbackType === PlaybackType.WEB && ( { const { t } = useTranslation(); const { playersRef } = args; - const settings = useSettingsStore((state) => state.playback); const currentPlayer = useCurrentPlayer(); const { setShuffle, setRepeat, play, pause, previous, next, setCurrentIndex, autoNext } = usePlayerControls(); @@ -41,7 +38,7 @@ export const useCenterControls = (args: { playersRef: any }) => { const playerStatus = useCurrentStatus(); const repeatStatus = useRepeatStatus(); const shuffleStatus = useShuffleStatus(); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const player1Ref = playersRef?.current?.player1; const player2Ref = playersRef?.current?.player2; const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref; @@ -77,7 +74,7 @@ export const useCenterControls = (args: { playersRef: any }) => { resetPlayers(); }, [player1Ref, player2Ref, resetPlayers]); - const isMpvPlayer = isElectron() && settings.type === PlaybackType.LOCAL; + const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL; const mprisUpdateSong = (args?: { currentTime?: number; @@ -282,13 +279,13 @@ export const useCenterControls = (args: { playersRef: any }) => { switch (repeatStatus) { case PlayerRepeat.NONE: - handleRepeatNone[playerType](); + handleRepeatNone[playbackType](); break; case PlayerRepeat.ALL: - handleRepeatAll[playerType](); + handleRepeatAll[playbackType](); break; case PlayerRepeat.ONE: - handleRepeatOne[playerType](); + handleRepeatOne[playbackType](); break; default: @@ -299,7 +296,7 @@ export const useCenterControls = (args: { playersRef: any }) => { checkIsLastTrack, pause, play, - playerType, + playbackType, repeatStatus, resetPlayers, setCurrentIndex, @@ -380,13 +377,13 @@ export const useCenterControls = (args: { playersRef: any }) => { switch (repeatStatus) { case PlayerRepeat.NONE: - handleRepeatNone[playerType](); + handleRepeatNone[playbackType](); break; case PlayerRepeat.ALL: - handleRepeatAll[playerType](); + handleRepeatAll[playbackType](); break; case PlayerRepeat.ONE: - handleRepeatOne[playerType](); + handleRepeatOne[playbackType](); break; default: @@ -398,7 +395,7 @@ export const useCenterControls = (args: { playersRef: any }) => { checkIsLastTrack, next, pause, - playerType, + playbackType, repeatStatus, resetPlayers, setCurrentIndex, @@ -511,13 +508,13 @@ export const useCenterControls = (args: { playersRef: any }) => { switch (repeatStatus) { case PlayerRepeat.NONE: - handleRepeatNone[playerType](); + handleRepeatNone[playbackType](); break; case PlayerRepeat.ALL: - handleRepeatAll[playerType](); + handleRepeatAll[playbackType](); break; case PlayerRepeat.ONE: - handleRepeatOne[playerType](); + handleRepeatOne[playbackType](); break; default: @@ -531,7 +528,7 @@ export const useCenterControls = (args: { playersRef: any }) => { handleScrobbleFromSongRestart, isMpvPlayer, pause, - playerType, + playbackType, previous, queue.length, repeatStatus, diff --git a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts index 1950b678..099bb4c8 100644 --- a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts +++ b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts @@ -1,7 +1,7 @@ import { useCallback, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store'; -import { usePlayerType } from '/@/renderer/store/settings.store'; +import { usePlaybackType } from '/@/renderer/store/settings.store'; import { PlayQueueAddOptions, Play, @@ -65,7 +65,7 @@ const addToQueue = usePlayerStore.getState().actions.addToQueue; export const useHandlePlayQueueAdd = () => { const { t } = useTranslation(); const queryClient = useQueryClient(); - const playerType = usePlayerType(); + const playbackType = usePlaybackType(); const server = useCurrentServer(); const { play } = usePlayerControls(); const timeoutIds = useRef> | null>({}); @@ -170,7 +170,7 @@ export const useHandlePlayQueueAdd = () => { const hadSong = usePlayerStore.getState().queue.default.length > 0; const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs }); - if (playerType === PlaybackType.LOCAL) { + if (playbackType === PlaybackType.LOCAL) { mpvPlayer!.volume(usePlayerStore.getState().volume); if (playType === Play.NEXT || playType === Play.LAST) { @@ -200,7 +200,7 @@ export const useHandlePlayQueueAdd = () => { return null; }, - [play, playerType, queryClient, server, t], + [play, playbackType, queryClient, server, t], ); return handlePlayQueueAdd; diff --git a/src/renderer/features/settings/components/playback/mpv-settings.tsx b/src/renderer/features/settings/components/playback/mpv-settings.tsx index 2128ba44..9004a465 100644 --- a/src/renderer/features/settings/components/playback/mpv-settings.tsx +++ b/src/renderer/features/settings/components/playback/mpv-settings.tsx @@ -161,21 +161,24 @@ export const MpvSettings = () => { handleSetMpvPath(null)} + > + + + ) + } width={200} onChange={handleSetMpvPath} /> - {mpvPath && ( - - )} ), description: t('setting.mpvExecutablePath', { diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx index 10131c53..d77a8981 100644 --- a/src/renderer/router/app-outlet.tsx +++ b/src/renderer/router/app-outlet.tsx @@ -2,14 +2,16 @@ import { useMemo, useEffect } from 'react'; import isElectron from 'is-electron'; import { Navigate, Outlet } from 'react-router-dom'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer } from '/@/renderer/store'; +import { useCurrentServer, useSetPlayerFallback } from '/@/renderer/store'; import { toast } from '/@/renderer/components'; const ipc = isElectron() ? window.electron.ipc : null; const utils = isElectron() ? window.electron.utils : null; +const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : null; export const AppOutlet = () => { const currentServer = useCurrentServer(); + const setFallback = useSetPlayerFallback(); const isActionsRequired = useMemo(() => { const isServerRequired = !currentServer; @@ -25,10 +27,15 @@ export const AppOutlet = () => { toast.show(data); }); + mpvPlayerListener?.rendererPlayerFallback((_event, data) => { + setFallback(data); + }); + return () => { ipc?.removeAllListeners('toast-from-main'); + ipc?.removeAllListeners('renderer-player-fallback'); }; - }, []); + }, [setFallback]); if (isActionsRequired) { return ( diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 7e67523b..737122b3 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -22,6 +22,7 @@ export interface PlayerState { status: PlayerStatus; time: number; }; + fallback: boolean | null; muted: boolean; queue: { default: QueueSong[]; @@ -85,6 +86,7 @@ export interface PlayerSlice extends PlayerState { setCurrentSpeed: (speed: number) => void; setCurrentTime: (time: number, seek?: boolean) => void; setCurrentTrack: (uniqueId: string) => PlayerData; + setFallback: (fallback: boolean | null) => boolean; setFavorite: (ids: string[], favorite: boolean) => string[]; setMuted: (muted: boolean) => void; setRating: (ids: string[], rating: number | null) => string[]; @@ -806,6 +808,13 @@ export const usePlayerStore = create()( return get().actions.getPlayerData(); }, + setFallback: (fallback) => { + set((state) => { + state.fallback = fallback; + }); + + return fallback || false; + }, setFavorite: (ids, favorite) => { const { default: queue } = get().queue; const foundUniqueIds = []; @@ -953,6 +962,7 @@ export const usePlayerStore = create()( status: PlayerStatus.PAUSED, time: 0, }, + fallback: null, muted: false, queue: { default: [], @@ -973,7 +983,7 @@ export const usePlayerStore = create()( }, name: 'store_player', partialize: (state) => { - const notPersisted = ['queue', 'current', 'entry']; + const notPersisted = ['queue', 'current', 'entry', 'fallback']; return Object.fromEntries( Object.entries(state).filter(([key]) => !notPersisted.includes(key)), ); @@ -1066,6 +1076,10 @@ export const useMuted = () => usePlayerStore((state) => state.muted); export const useSpeed = () => usePlayerStore((state) => state.current.speed); +export const usePlayerFallback = () => usePlayerStore((state) => state.fallback); + +export const useSetPlayerFallback = () => usePlayerStore((state) => state.actions.setFallback); + export const useSetCurrentSpeed = () => usePlayerStore((state) => state.actions.setCurrentSpeed); export const useSetQueueFavorite = () => usePlayerStore((state) => state.actions.setFavorite); diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 0f0b5f92..b58a8b75 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -23,6 +23,7 @@ import { } from '/@/renderer/types'; import { randomString } from '/@/renderer/utils'; import i18n from '/@/i18n/i18n'; +import { usePlayerStore } from '/@/renderer/store/player.store'; const utils = isElectron() ? window.electron.utils : null; @@ -616,7 +617,16 @@ export const useTableSettings = (type: TableType) => export const useGeneralSettings = () => useSettingsStore((state) => state.general, shallow); -export const usePlayerType = () => useSettingsStore((state) => state.playback.type, shallow); +export const usePlaybackType = () => + useSettingsStore((state) => { + const isFallback = usePlayerStore.getState().fallback; + + if (isFallback) { + return PlaybackType.WEB; + } + + return state.playback.type; + }); export const usePlayButtonBehavior = () => useSettingsStore((state) => state.general.playButtonBehavior, shallow); From b4092c394a47c6e6bf4415666d597bdabab82dae Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 13 Feb 2024 05:50:38 -0800 Subject: [PATCH 12/12] Set playback type to WEB by default --- src/renderer/store/settings.store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index b58a8b75..c698ee50 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -382,7 +382,7 @@ const initialState: SettingsState = { scrobbleAtPercentage: 75, }, style: PlaybackStyle.GAPLESS, - type: PlaybackType.LOCAL, + type: PlaybackType.WEB, }, remote: { enabled: false,