Fallback to web player if mpv fails to run

This commit is contained in:
jeffvli 2024-02-13 02:05:59 -08:00
parent fb08502e51
commit 9b0c9ba3ac
14 changed files with 115 additions and 70 deletions

View file

@ -149,6 +149,10 @@ export const getMpvInstance = () => {
return mpvInstance; return mpvInstance;
}; };
const setAudioPlayerFallback = (isError: boolean) => {
getMainWindow()?.webContents.send('renderer-player-fallback', isError);
};
ipcMain.on('player-set-properties', async (_event, data: Record<string, any>) => { ipcMain.on('player-set-properties', async (_event, data: Record<string, any>) => {
mpvLog({ action: `Setting properties: ${JSON.stringify(data)}` }); mpvLog({ action: `Setting properties: ${JSON.stringify(data)}` });
if (data.length === 0) { if (data.length === 0) {
@ -181,8 +185,10 @@ ipcMain.handle(
mpvInstance = await createMpv(data); mpvInstance = await createMpv(data);
mpvLog({ action: 'Restarted mpv', toast: 'success' }); mpvLog({ action: 'Restarted mpv', toast: 'success' });
setAudioPlayerFallback(false);
} catch (err: NodeMpvError | any) { } 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)}`, action: `Attempting to initialize mpv with parameters: ${JSON.stringify(data)}`,
}); });
mpvInstance = await createMpv(data); mpvInstance = await createMpv(data);
setAudioPlayerFallback(false);
} catch (err: NodeMpvError | any) { } 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);
} }
}, },
); );

View file

@ -153,6 +153,10 @@ const rendererError = (cb: (event: IpcRendererEvent, data: string) => void) => {
ipcRenderer.on('renderer-player-error', cb); ipcRenderer.on('renderer-player-error', cb);
}; };
const rendererPlayerFallback = (cb: (event: IpcRendererEvent, data: boolean) => void) => {
ipcRenderer.on('renderer-player-fallback', cb);
};
export const mpvPlayer = { export const mpvPlayer = {
autoNext, autoNext,
cleanup, cleanup,
@ -184,6 +188,7 @@ export const mpvPlayerListener = {
rendererPause, rendererPause,
rendererPlay, rendererPlay,
rendererPlayPause, rendererPlayPause,
rendererPlayerFallback,
rendererPrevious, rendererPrevious,
rendererQuit, rendererQuit,
rendererSkipBackward, rendererSkipBackward,

View file

@ -51,7 +51,7 @@ import {
usePlayerStore, usePlayerStore,
useQueueControls, useQueueControls,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { usePlayerType } from '/@/renderer/store/settings.store'; import { usePlaybackType } from '/@/renderer/store/settings.store';
import { Play, PlaybackType } from '/@/renderer/types'; import { Play, PlaybackType } from '/@/renderer/types';
type ContextMenuContextProps = { type ContextMenuContextProps = {
@ -575,7 +575,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
[ctx.data, ctx.dataNodes, updateRatingMutation], [ctx.data, ctx.dataNodes, updateRatingMutation],
); );
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const { moveToBottomOfQueue, moveToTopOfQueue, removeFromQueue } = useQueueControls(); const { moveToBottomOfQueue, moveToTopOfQueue, removeFromQueue } = useQueueControls();
const handleMoveToBottom = useCallback(() => { const handleMoveToBottom = useCallback(() => {
@ -584,10 +584,10 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const playerData = moveToBottomOfQueue(uniqueIds); const playerData = moveToBottomOfQueue(uniqueIds);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }
}, [ctx.dataNodes, moveToBottomOfQueue, playerType]); }, [ctx.dataNodes, moveToBottomOfQueue, playbackType]);
const handleMoveToTop = useCallback(() => { const handleMoveToTop = useCallback(() => {
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
@ -595,10 +595,10 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const playerData = moveToTopOfQueue(uniqueIds); const playerData = moveToTopOfQueue(uniqueIds);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }
}, [ctx.dataNodes, moveToTopOfQueue, playerType]); }, [ctx.dataNodes, moveToTopOfQueue, playbackType]);
const handleRemoveSelected = useCallback(() => { const handleRemoveSelected = useCallback(() => {
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
@ -608,7 +608,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const playerData = removeFromQueue(uniqueIds); const playerData = removeFromQueue(uniqueIds);
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId); const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
if (isCurrentSongRemoved) { if (isCurrentSongRemoved) {
mpvPlayer!.setQueue(playerData); mpvPlayer!.setQueue(playerData);
} else { } else {
@ -621,7 +621,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
if (isCurrentSongRemoved) { if (isCurrentSongRemoved) {
remote?.updateSong({ song: playerData.current.song }); remote?.updateSong({ song: playerData.current.song });
} }
}, [ctx.dataNodes, ctx.tableApi, playerType, removeFromQueue]); }, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]);
const handleDeselectAll = useCallback(() => { const handleDeselectAll = useCallback(() => {
ctx.tableApi?.deselectAll(); ctx.tableApi?.deselectAll();

View file

@ -3,7 +3,7 @@ import {
useCurrentStatus, useCurrentStatus,
useCurrentTime, useCurrentTime,
useLyricsSettings, useLyricsSettings,
usePlayerType, usePlaybackType,
useSeeked, useSeeked,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { PlaybackType, PlayerStatus } from '/@/renderer/types'; import { PlaybackType, PlayerStatus } from '/@/renderer/types';
@ -59,7 +59,7 @@ export const SynchronizedLyrics = ({
}: SynchronizedLyricsProps) => { }: SynchronizedLyricsProps) => {
const playersRef = PlayersRef; const playersRef = PlayersRef;
const status = useCurrentStatus(); const status = useCurrentStatus();
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const now = useCurrentTime(); const now = useCurrentTime();
const settings = useLyricsSettings(); const settings = useLyricsSettings();
@ -96,7 +96,7 @@ export const SynchronizedLyrics = ({
}; };
const getCurrentTime = useCallback(async () => { const getCurrentTime = useCallback(async () => {
if (isElectron() && playerType !== PlaybackType.WEB) { if (isElectron() && playbackType !== PlaybackType.WEB) {
if (mpvPlayer) { if (mpvPlayer) {
return mpvPlayer.getCurrentTime(); return mpvPlayer.getCurrentTime();
} }
@ -116,7 +116,7 @@ export const SynchronizedLyrics = ({
if (!player) return 0; if (!player) return 0;
return player.currentTime; return player.currentTime;
}, [playerType, playersRef]); }, [playbackType, playersRef]);
const setCurrentLyric = useCallback( const setCurrentLyric = useCallback(
(timeInMs: number, epoch?: number, targetIndex?: number) => { (timeInMs: number, epoch?: number, targetIndex?: number) => {
@ -222,7 +222,7 @@ export const SynchronizedLyrics = ({
} }
return () => {}; return () => {};
}, [getCurrentTime, lyrics, playerType, setCurrentLyric, status]); }, [getCurrentTime, lyrics, playbackType, setCurrentLyric, status]);
useEffect(() => { useEffect(() => {
// This handler is used to deal with changes to the current delay. If the offset // This handler is used to deal with changes to the current delay. If the offset

View file

@ -15,7 +15,7 @@ import {
import { Song } from '/@/renderer/api/types'; import { Song } from '/@/renderer/api/types';
import { usePlayerControls, useQueueControls } from '/@/renderer/store'; import { usePlayerControls, useQueueControls } from '/@/renderer/store';
import { PlaybackType, TableType } from '/@/renderer/types'; 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 { usePlayerStore, useSetCurrentTime } from '../../../store/player.store';
import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
@ -34,7 +34,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const { pause } = usePlayerControls(); const { pause } = usePlayerControls();
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const setCurrentTime = useSetCurrentTime(); const setCurrentTime = useSetCurrentTime();
const handleMoveToBottom = () => { const handleMoveToBottom = () => {
@ -44,7 +44,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const playerData = moveToBottomOfQueue(uniqueIds); const playerData = moveToBottomOfQueue(uniqueIds);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }
}; };
@ -56,7 +56,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const playerData = moveToTopOfQueue(uniqueIds); const playerData = moveToTopOfQueue(uniqueIds);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }
}; };
@ -70,7 +70,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const playerData = removeFromQueue(uniqueIds); const playerData = removeFromQueue(uniqueIds);
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId); const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
if (isCurrentSongRemoved) { if (isCurrentSongRemoved) {
mpvPlayer!.setQueue(playerData); mpvPlayer!.setQueue(playerData);
} else { } else {
@ -86,7 +86,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const handleClearQueue = () => { const handleClearQueue = () => {
const playerData = clearQueue(); const playerData = clearQueue();
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueue(playerData); mpvPlayer!.setQueue(playerData);
mpvPlayer!.pause(); mpvPlayer!.pause();
} }
@ -100,7 +100,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const handleShuffleQueue = () => { const handleShuffleQueue = () => {
const playerData = shuffleQueue(); const playerData = shuffleQueue();
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }
}; };

View file

@ -19,7 +19,7 @@ import {
useVolume, useVolume,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { import {
usePlayerType, usePlaybackType,
useSettingsStore, useSettingsStore,
useSettingsStoreActions, useSettingsStoreActions,
useTableSettings, useTableSettings,
@ -56,7 +56,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
const { setAppStore } = useAppStoreActions(); const { setAppStore } = useAppStoreActions();
const tableConfig = useTableSettings(type); const tableConfig = useTableSettings(type);
const [gridApi, setGridApi] = useState<AgGridReactType | undefined>(); const [gridApi, setGridApi] = useState<AgGridReactType | undefined>();
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const { play } = usePlayerControls(); const { play } = usePlayerControls();
const volume = useVolume(); const volume = useVolume();
const isFocused = useAppFocus(); const isFocused = useAppFocus();
@ -87,7 +87,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
status: PlayerStatus.PLAYING, status: PlayerStatus.PLAYING,
}); });
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.volume(volume); mpvPlayer!.volume(volume);
mpvPlayer!.setQueue(playerData); mpvPlayer!.setQueue(playerData);
mpvPlayer!.play(); mpvPlayer!.play();
@ -111,7 +111,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId); const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId);
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.setQueueNext(playerData); mpvPlayer!.setQueueNext(playerData);
} }

View file

@ -32,7 +32,7 @@ import {
} from '/@/renderer/store'; } from '/@/renderer/store';
import { import {
useHotkeySettings, useHotkeySettings,
usePlayerType, usePlaybackType,
useSettingsStore, useSettingsStore,
} from '/@/renderer/store/settings.store'; } from '/@/renderer/store/settings.store';
import { PlayerStatus, PlaybackType, PlayerShuffle, PlayerRepeat } from '/@/renderer/types'; import { PlayerStatus, PlaybackType, PlayerShuffle, PlayerRepeat } from '/@/renderer/types';
@ -99,7 +99,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const skip = useSettingsStore((state) => state.general.skipButtons); const skip = useSettingsStore((state) => state.general.skipButtons);
const buttonSize = useSettingsStore((state) => state.general.buttonSize); const buttonSize = useSettingsStore((state) => state.general.buttonSize);
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const player1 = playersRef?.current?.player1; const player1 = playersRef?.current?.player1;
const player2 = playersRef?.current?.player2; const player2 = playersRef?.current?.player2;
const status = useCurrentStatus(); const status = useCurrentStatus();
@ -134,7 +134,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
let interval: any; let interval: any;
if (status === PlayerStatus.PLAYING && !isSeeking) { if (status === PlayerStatus.PLAYING && !isSeeking) {
if (!isElectron() || playerType === PlaybackType.WEB) { if (!isElectron() || playbackType === PlaybackType.WEB) {
interval = setInterval(() => { interval = setInterval(() => {
setCurrentTime(currentPlayerRef.getCurrentTime()); setCurrentTime(currentPlayerRef.getCurrentTime());
}, 1000); }, 1000);
@ -144,7 +144,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
} }
return () => clearInterval(interval); return () => clearInterval(interval);
}, [currentPlayerRef, isSeeking, setCurrentTime, playerType, status]); }, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]);
const [seekValue, setSeekValue] = useState(0); const [seekValue, setSeekValue] = useState(0);

View file

@ -1,7 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import styled from 'styled-components'; 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 { PlaybackType } from '/@/renderer/types';
import { AudioPlayer } from '/@/renderer/components'; import { AudioPlayer } from '/@/renderer/components';
import { import {
@ -64,6 +64,7 @@ const remote = isElectron() ? window.electron.remote : null;
export const Playerbar = () => { export const Playerbar = () => {
const playersRef = PlayersRef; const playersRef = PlayersRef;
const settings = useSettingsStore((state) => state.playback); const settings = useSettingsStore((state) => state.playback);
const playbackType = usePlaybackType();
const volume = useVolume(); const volume = useVolume();
const player1 = usePlayer1Data(); const player1 = usePlayer1Data();
const player2 = usePlayer2Data(); const player2 = usePlayer2Data();
@ -96,7 +97,7 @@ export const Playerbar = () => {
<RightControls /> <RightControls />
</RightGridItem> </RightGridItem>
</PlayerbarControlsGrid> </PlayerbarControlsGrid>
{settings.type === PlaybackType.WEB && ( {playbackType === PlaybackType.WEB && (
<AudioPlayer <AudioPlayer
ref={playersRef} ref={playersRef}
autoNext={autoNextFn} autoNext={autoNextFn}

View file

@ -1,5 +1,3 @@
// import { write, writeFile } from 'fs';
// import { deflate } from 'zlib';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/renderer/types'; import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/renderer/types';
@ -13,7 +11,7 @@ import {
useSetCurrentTime, useSetCurrentTime,
useShuffleStatus, useShuffleStatus,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { usePlayerType, useSettingsStore } from '/@/renderer/store/settings.store'; import { usePlaybackType } from '/@/renderer/store/settings.store';
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { QueueSong } from '/@/renderer/api/types'; import { QueueSong } from '/@/renderer/api/types';
@ -32,7 +30,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { playersRef } = args; const { playersRef } = args;
const settings = useSettingsStore((state) => state.playback);
const currentPlayer = useCurrentPlayer(); const currentPlayer = useCurrentPlayer();
const { setShuffle, setRepeat, play, pause, previous, next, setCurrentIndex, autoNext } = const { setShuffle, setRepeat, play, pause, previous, next, setCurrentIndex, autoNext } =
usePlayerControls(); usePlayerControls();
@ -41,7 +38,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const playerStatus = useCurrentStatus(); const playerStatus = useCurrentStatus();
const repeatStatus = useRepeatStatus(); const repeatStatus = useRepeatStatus();
const shuffleStatus = useShuffleStatus(); const shuffleStatus = useShuffleStatus();
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const player1Ref = playersRef?.current?.player1; const player1Ref = playersRef?.current?.player1;
const player2Ref = playersRef?.current?.player2; const player2Ref = playersRef?.current?.player2;
const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref; const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref;
@ -77,7 +74,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
resetPlayers(); resetPlayers();
}, [player1Ref, player2Ref, resetPlayers]); }, [player1Ref, player2Ref, resetPlayers]);
const isMpvPlayer = isElectron() && settings.type === PlaybackType.LOCAL; const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL;
const mprisUpdateSong = (args?: { const mprisUpdateSong = (args?: {
currentTime?: number; currentTime?: number;
@ -282,13 +279,13 @@ export const useCenterControls = (args: { playersRef: any }) => {
switch (repeatStatus) { switch (repeatStatus) {
case PlayerRepeat.NONE: case PlayerRepeat.NONE:
handleRepeatNone[playerType](); handleRepeatNone[playbackType]();
break; break;
case PlayerRepeat.ALL: case PlayerRepeat.ALL:
handleRepeatAll[playerType](); handleRepeatAll[playbackType]();
break; break;
case PlayerRepeat.ONE: case PlayerRepeat.ONE:
handleRepeatOne[playerType](); handleRepeatOne[playbackType]();
break; break;
default: default:
@ -299,7 +296,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
checkIsLastTrack, checkIsLastTrack,
pause, pause,
play, play,
playerType, playbackType,
repeatStatus, repeatStatus,
resetPlayers, resetPlayers,
setCurrentIndex, setCurrentIndex,
@ -380,13 +377,13 @@ export const useCenterControls = (args: { playersRef: any }) => {
switch (repeatStatus) { switch (repeatStatus) {
case PlayerRepeat.NONE: case PlayerRepeat.NONE:
handleRepeatNone[playerType](); handleRepeatNone[playbackType]();
break; break;
case PlayerRepeat.ALL: case PlayerRepeat.ALL:
handleRepeatAll[playerType](); handleRepeatAll[playbackType]();
break; break;
case PlayerRepeat.ONE: case PlayerRepeat.ONE:
handleRepeatOne[playerType](); handleRepeatOne[playbackType]();
break; break;
default: default:
@ -398,7 +395,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
checkIsLastTrack, checkIsLastTrack,
next, next,
pause, pause,
playerType, playbackType,
repeatStatus, repeatStatus,
resetPlayers, resetPlayers,
setCurrentIndex, setCurrentIndex,
@ -511,13 +508,13 @@ export const useCenterControls = (args: { playersRef: any }) => {
switch (repeatStatus) { switch (repeatStatus) {
case PlayerRepeat.NONE: case PlayerRepeat.NONE:
handleRepeatNone[playerType](); handleRepeatNone[playbackType]();
break; break;
case PlayerRepeat.ALL: case PlayerRepeat.ALL:
handleRepeatAll[playerType](); handleRepeatAll[playbackType]();
break; break;
case PlayerRepeat.ONE: case PlayerRepeat.ONE:
handleRepeatOne[playerType](); handleRepeatOne[playbackType]();
break; break;
default: default:
@ -531,7 +528,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
handleScrobbleFromSongRestart, handleScrobbleFromSongRestart,
isMpvPlayer, isMpvPlayer,
pause, pause,
playerType, playbackType,
previous, previous,
queue.length, queue.length,
repeatStatus, repeatStatus,

View file

@ -1,7 +1,7 @@
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store'; import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
import { usePlayerType } from '/@/renderer/store/settings.store'; import { usePlaybackType } from '/@/renderer/store/settings.store';
import { import {
PlayQueueAddOptions, PlayQueueAddOptions,
Play, Play,
@ -65,7 +65,7 @@ const addToQueue = usePlayerStore.getState().actions.addToQueue;
export const useHandlePlayQueueAdd = () => { export const useHandlePlayQueueAdd = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const playerType = usePlayerType(); const playbackType = usePlaybackType();
const server = useCurrentServer(); const server = useCurrentServer();
const { play } = usePlayerControls(); const { play } = usePlayerControls();
const timeoutIds = useRef<Record<string, ReturnType<typeof setTimeout>> | null>({}); const timeoutIds = useRef<Record<string, ReturnType<typeof setTimeout>> | null>({});
@ -170,7 +170,7 @@ export const useHandlePlayQueueAdd = () => {
const hadSong = usePlayerStore.getState().queue.default.length > 0; const hadSong = usePlayerStore.getState().queue.default.length > 0;
const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs }); const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs });
if (playerType === PlaybackType.LOCAL) { if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.volume(usePlayerStore.getState().volume); mpvPlayer!.volume(usePlayerStore.getState().volume);
if (playType === Play.NEXT || playType === Play.LAST) { if (playType === Play.NEXT || playType === Play.LAST) {
@ -200,7 +200,7 @@ export const useHandlePlayQueueAdd = () => {
return null; return null;
}, },
[play, playerType, queryClient, server, t], [play, playbackType, queryClient, server, t],
); );
return handlePlayQueueAdd; return handlePlayQueueAdd;

View file

@ -161,21 +161,24 @@ export const MpvSettings = () => {
</Button> </Button>
<FileInput <FileInput
placeholder={mpvPath} placeholder={mpvPath}
rightSection={
mpvPath && (
<Button
compact
tooltip={{
label: t('common.clear', { postProcess: 'titleCase' }),
openDelay: 0,
}}
variant="subtle"
onClick={() => handleSetMpvPath(null)}
>
<RiCloseLine />
</Button>
)
}
width={200} width={200}
onChange={handleSetMpvPath} onChange={handleSetMpvPath}
/> />
{mpvPath && (
<Button
tooltip={{
label: t('common.clear', { postProcess: 'titleCase' }),
openDelay: 0,
}}
variant="default"
onClick={() => handleSetMpvPath(null)}
>
<RiCloseLine />
</Button>
)}
</Group> </Group>
), ),
description: t('setting.mpvExecutablePath', { description: t('setting.mpvExecutablePath', {

View file

@ -2,14 +2,16 @@ import { useMemo, useEffect } from 'react';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { Navigate, Outlet } from 'react-router-dom'; import { Navigate, Outlet } from 'react-router-dom';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, useSetPlayerFallback } from '/@/renderer/store';
import { toast } from '/@/renderer/components'; import { toast } from '/@/renderer/components';
const ipc = isElectron() ? window.electron.ipc : null; const ipc = isElectron() ? window.electron.ipc : null;
const utils = isElectron() ? window.electron.utils : null; const utils = isElectron() ? window.electron.utils : null;
const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : null;
export const AppOutlet = () => { export const AppOutlet = () => {
const currentServer = useCurrentServer(); const currentServer = useCurrentServer();
const setFallback = useSetPlayerFallback();
const isActionsRequired = useMemo(() => { const isActionsRequired = useMemo(() => {
const isServerRequired = !currentServer; const isServerRequired = !currentServer;
@ -25,10 +27,15 @@ export const AppOutlet = () => {
toast.show(data); toast.show(data);
}); });
mpvPlayerListener?.rendererPlayerFallback((_event, data) => {
setFallback(data);
});
return () => { return () => {
ipc?.removeAllListeners('toast-from-main'); ipc?.removeAllListeners('toast-from-main');
ipc?.removeAllListeners('renderer-player-fallback');
}; };
}, []); }, [setFallback]);
if (isActionsRequired) { if (isActionsRequired) {
return ( return (

View file

@ -22,6 +22,7 @@ export interface PlayerState {
status: PlayerStatus; status: PlayerStatus;
time: number; time: number;
}; };
fallback: boolean | null;
muted: boolean; muted: boolean;
queue: { queue: {
default: QueueSong[]; default: QueueSong[];
@ -85,6 +86,7 @@ export interface PlayerSlice extends PlayerState {
setCurrentSpeed: (speed: number) => void; setCurrentSpeed: (speed: number) => void;
setCurrentTime: (time: number, seek?: boolean) => void; setCurrentTime: (time: number, seek?: boolean) => void;
setCurrentTrack: (uniqueId: string) => PlayerData; setCurrentTrack: (uniqueId: string) => PlayerData;
setFallback: (fallback: boolean | null) => boolean;
setFavorite: (ids: string[], favorite: boolean) => string[]; setFavorite: (ids: string[], favorite: boolean) => string[];
setMuted: (muted: boolean) => void; setMuted: (muted: boolean) => void;
setRating: (ids: string[], rating: number | null) => string[]; setRating: (ids: string[], rating: number | null) => string[];
@ -806,6 +808,13 @@ export const usePlayerStore = create<PlayerSlice>()(
return get().actions.getPlayerData(); return get().actions.getPlayerData();
}, },
setFallback: (fallback) => {
set((state) => {
state.fallback = fallback;
});
return fallback || false;
},
setFavorite: (ids, favorite) => { setFavorite: (ids, favorite) => {
const { default: queue } = get().queue; const { default: queue } = get().queue;
const foundUniqueIds = []; const foundUniqueIds = [];
@ -953,6 +962,7 @@ export const usePlayerStore = create<PlayerSlice>()(
status: PlayerStatus.PAUSED, status: PlayerStatus.PAUSED,
time: 0, time: 0,
}, },
fallback: null,
muted: false, muted: false,
queue: { queue: {
default: [], default: [],
@ -973,7 +983,7 @@ export const usePlayerStore = create<PlayerSlice>()(
}, },
name: 'store_player', name: 'store_player',
partialize: (state) => { partialize: (state) => {
const notPersisted = ['queue', 'current', 'entry']; const notPersisted = ['queue', 'current', 'entry', 'fallback'];
return Object.fromEntries( return Object.fromEntries(
Object.entries(state).filter(([key]) => !notPersisted.includes(key)), 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 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 useSetCurrentSpeed = () => usePlayerStore((state) => state.actions.setCurrentSpeed);
export const useSetQueueFavorite = () => usePlayerStore((state) => state.actions.setFavorite); export const useSetQueueFavorite = () => usePlayerStore((state) => state.actions.setFavorite);

View file

@ -23,6 +23,7 @@ import {
} from '/@/renderer/types'; } from '/@/renderer/types';
import { randomString } from '/@/renderer/utils'; import { randomString } from '/@/renderer/utils';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { usePlayerStore } from '/@/renderer/store/player.store';
const utils = isElectron() ? window.electron.utils : null; 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 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 = () => export const usePlayButtonBehavior = () =>
useSettingsStore((state) => state.general.playButtonBehavior, shallow); useSettingsStore((state) => state.general.playButtonBehavior, shallow);