Fallback to web player if mpv fails to run
This commit is contained in:
parent
fb08502e51
commit
9b0c9ba3ac
14 changed files with 115 additions and 70 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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', {
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Reference in a new issue