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: (