mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-14 04:20:07 +02:00
add web audio, replaygain, visualizer (#1289)
* add web audio, replaygain, visualizer * remove volume multiplication in gain --------- Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||
@@ -11,18 +12,30 @@ import { useMPRIS } from '/@/renderer/features/player/hooks/use-mpris';
|
||||
import { usePlaybackHotkeys } from '/@/renderer/features/player/hooks/use-playback-hotkeys';
|
||||
import { usePowerSaveBlocker } from '/@/renderer/features/player/hooks/use-power-save-blocker';
|
||||
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
|
||||
import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio';
|
||||
import {
|
||||
updateQueueFavorites,
|
||||
updateQueueRatings,
|
||||
useCurrentServerId,
|
||||
usePlaybackSettings,
|
||||
usePlaybackType,
|
||||
useSettingsStoreActions,
|
||||
} from '/@/renderer/store';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { PlayerType } from '/@/shared/types/types';
|
||||
|
||||
export const AudioPlayers = () => {
|
||||
const playbackType = usePlaybackType();
|
||||
const serverId = useCurrentServerId();
|
||||
const { resetSampleRate } = useSettingsStoreActions();
|
||||
|
||||
const {
|
||||
audioDeviceId,
|
||||
mpvProperties: { audioSampleRateHz },
|
||||
webAudio,
|
||||
} = usePlaybackSettings();
|
||||
const { setWebAudio, webAudio: audioContext } = useWebAudio();
|
||||
|
||||
useScrobble();
|
||||
usePowerSaveBlocker();
|
||||
@@ -32,6 +45,54 @@ export const AudioPlayers = () => {
|
||||
useMediaSession();
|
||||
usePlaybackHotkeys();
|
||||
|
||||
useEffect(() => {
|
||||
if (webAudio && 'AudioContext' in window) {
|
||||
let context: AudioContext;
|
||||
|
||||
try {
|
||||
context = new AudioContext({
|
||||
latencyHint: 'playback',
|
||||
sampleRate: audioSampleRateHz || undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
// In practice, this should never be hit because the UI should validate
|
||||
// the range. However, the actual supported range is not guaranteed
|
||||
toast.error({ message: (error as Error).message });
|
||||
context = new AudioContext({ latencyHint: 'playback' });
|
||||
resetSampleRate();
|
||||
}
|
||||
|
||||
const gains = [context.createGain(), context.createGain()];
|
||||
for (const gain of gains) {
|
||||
gain.connect(context.destination);
|
||||
}
|
||||
|
||||
setWebAudio!({ context, gains });
|
||||
}
|
||||
|
||||
// Intentionally ignore the sample rate dependency, as it makes things really messy
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Not standard, just used in chromium-based browsers. See
|
||||
// https://developer.chrome.com/blog/audiocontext-setsinkid/.
|
||||
// If the isElectron() check is every removed, fix this.
|
||||
if (isElectron() && audioContext && 'setSinkId' in audioContext.context && audioDeviceId) {
|
||||
const setSink = async () => {
|
||||
try {
|
||||
if (audioContext.context.state !== 'closed') {
|
||||
await (audioContext.context as any).setSinkId(audioDeviceId);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error({ message: `Error setting sink: ${(error as Error).message}` });
|
||||
}
|
||||
};
|
||||
|
||||
setSink();
|
||||
}
|
||||
}, [audioContext, audioDeviceId]);
|
||||
|
||||
// Listen to favorite and rating events to update queue songs
|
||||
useEffect(() => {
|
||||
const handleFavorite = (payload: UserFavoriteEventPayload) => {
|
||||
|
||||
@@ -13,8 +13,8 @@ export const Visualizer = () => {
|
||||
const [motion, setMotion] = useState<AudioMotionAnalyzer>();
|
||||
|
||||
useEffect(() => {
|
||||
const { context, gain } = webAudio || {};
|
||||
if (gain && context && canvasRef.current && !motion) {
|
||||
const { context, gains } = webAudio || {};
|
||||
if (gains && context && canvasRef.current && !motion) {
|
||||
const audioMotion = new AudioMotionAnalyzer(canvasRef.current, {
|
||||
ansiBands: true,
|
||||
audioCtx: context,
|
||||
@@ -27,7 +27,7 @@ export const Visualizer = () => {
|
||||
smoothing: 0.8,
|
||||
});
|
||||
setMotion(audioMotion);
|
||||
audioMotion.connectInput(gain);
|
||||
for (const gain of gains) audioMotion.connectInput(gain);
|
||||
}
|
||||
|
||||
return () => {};
|
||||
|
||||
Reference in New Issue
Block a user