mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-12 15:22:35 +02:00
add system audio loopback for webaudio
This commit is contained in:
@@ -3,9 +3,10 @@ import { createRef, useCallback, useEffect, useMemo, useRef, useState } from 're
|
||||
import styles from './visualizer.module.css';
|
||||
|
||||
import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio';
|
||||
import { getVisualizerAudioNodes } from '/@/renderer/features/player/utils/get-visualizer-audio-nodes';
|
||||
import { openVisualizerSettingsModal } from '/@/renderer/features/player/utils/open-visualizer-settings-modal';
|
||||
import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
|
||||
import { useAccent, useSettingsStore } from '/@/renderer/store';
|
||||
import { useAccent, usePlaybackType, useSettingsStore } from '/@/renderer/store';
|
||||
import {
|
||||
useFullScreenPlayerStore,
|
||||
useFullScreenPlayerStoreActions,
|
||||
@@ -18,6 +19,7 @@ const VisualizerInner = () => {
|
||||
const canvasRef = createRef<HTMLDivElement>();
|
||||
const accent = useAccent();
|
||||
const visualizer = useSettingsStore((store) => store.visualizer);
|
||||
const playbackType = usePlaybackType();
|
||||
const opacity = useSettingsStore((store) => store.visualizer.audiomotionanalyzer.opacity);
|
||||
const [motion, setMotion] = useState<any>();
|
||||
const [libraryLoaded, setLibraryLoaded] = useState(false);
|
||||
@@ -214,9 +216,10 @@ const VisualizerInner = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { context, gains } = webAudio || {};
|
||||
const { context } = webAudio || {};
|
||||
const inputNodes = getVisualizerAudioNodes(webAudio, playbackType);
|
||||
let audioMotion: any | undefined;
|
||||
if (gains && context && canvasRef.current && !motion && libraryLoaded) {
|
||||
if (inputNodes.length > 0 && context && canvasRef.current && !motion && libraryLoaded) {
|
||||
const AudioMotionAnalyzer = AudioMotionAnalyzerRef.current;
|
||||
if (!AudioMotionAnalyzer) return;
|
||||
|
||||
@@ -249,7 +252,7 @@ const VisualizerInner = () => {
|
||||
registerCustomGradients(audioMotion);
|
||||
|
||||
setMotion(audioMotion);
|
||||
for (const gain of gains) audioMotion.connectInput(gain);
|
||||
for (const node of inputNodes) audioMotion.connectInput(node);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -262,6 +265,7 @@ const VisualizerInner = () => {
|
||||
accent,
|
||||
canvasRef,
|
||||
registerCustomGradients,
|
||||
playbackType,
|
||||
webAudio,
|
||||
visualizer,
|
||||
options,
|
||||
|
||||
@@ -3,11 +3,13 @@ import { createRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import styles from './visualizer.module.css';
|
||||
|
||||
import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio';
|
||||
import { getVisualizerAudioNodes } from '/@/renderer/features/player/utils/get-visualizer-audio-nodes';
|
||||
import { openVisualizerSettingsModal } from '/@/renderer/features/player/utils/open-visualizer-settings-modal';
|
||||
import { ComponentErrorBoundary } from '/@/renderer/features/shared/components/component-error-boundary';
|
||||
import {
|
||||
subscribeButterchurnPreset,
|
||||
useButterchurnSettings,
|
||||
usePlaybackType,
|
||||
useSettingsStore,
|
||||
useSettingsStoreActions,
|
||||
} from '/@/renderer/store';
|
||||
@@ -19,7 +21,7 @@ import { usePlayerStatus } from '/@/renderer/store/player.store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { PlayerStatus } from '/@/shared/types/types';
|
||||
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
||||
|
||||
// Ignore presets that are erroring out
|
||||
const IGNORED_PRESETS = ['Flexi + Martin - astral projection'];
|
||||
@@ -56,9 +58,14 @@ const VisualizerInner = () => {
|
||||
const initialPresetLoadedRef = useRef(false);
|
||||
const butterchurnSettings = useButterchurnSettings();
|
||||
const opacity = useSettingsStore((store) => store.visualizer.butterchurn.opacity);
|
||||
const playbackType = usePlaybackType();
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
const playerStatus = usePlayerStatus();
|
||||
const isPlaying = playerStatus === PlayerStatus.PLAYING;
|
||||
const [webInitGeneration, setWebInitGeneration] = useState(0);
|
||||
const wasPlayingRef = useRef(false);
|
||||
const isFirstWebMountRef = useRef(true);
|
||||
const prevPlaybackTypeRef = useRef(playbackType);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@@ -89,6 +96,32 @@ const VisualizerInner = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const prevType = prevPlaybackTypeRef.current;
|
||||
|
||||
if (playbackType !== PlayerType.WEB) {
|
||||
prevPlaybackTypeRef.current = playbackType;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFirstWebMountRef.current) {
|
||||
isFirstWebMountRef.current = false;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
prevPlaybackTypeRef.current = playbackType;
|
||||
return;
|
||||
}
|
||||
|
||||
const wasPlaying = wasPlayingRef.current;
|
||||
wasPlayingRef.current = isPlaying;
|
||||
|
||||
if (isPlaying && (!wasPlaying || prevType !== PlayerType.WEB)) {
|
||||
setWebInitGeneration((g) => g + 1);
|
||||
}
|
||||
|
||||
prevPlaybackTypeRef.current = playbackType;
|
||||
}, [playbackType, isPlaying]);
|
||||
|
||||
const cleanupVisualizer = () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
@@ -118,17 +151,20 @@ const VisualizerInner = () => {
|
||||
|
||||
// Initialize butterchurn instance
|
||||
useEffect(() => {
|
||||
const { context, gains } = webAudio || {};
|
||||
const { context } = webAudio || {};
|
||||
const inputNodes = getVisualizerAudioNodes(webAudio, playbackType);
|
||||
const canvas = canvasRef.current;
|
||||
const container = containerRef.current;
|
||||
|
||||
const shouldRunForWebPlayback = playbackType === PlayerType.WEB && isPlaying;
|
||||
const shouldRunForMpvLoopback = playbackType === PlayerType.LOCAL && inputNodes.length > 0;
|
||||
|
||||
const needsInitialization =
|
||||
context &&
|
||||
gains &&
|
||||
gains.length > 0 &&
|
||||
inputNodes.length > 0 &&
|
||||
canvas &&
|
||||
container &&
|
||||
isPlaying &&
|
||||
(shouldRunForWebPlayback || shouldRunForMpvLoopback) &&
|
||||
librariesLoaded &&
|
||||
(!isInitializedRef.current || !visualizerRef.current);
|
||||
|
||||
@@ -159,7 +195,8 @@ const VisualizerInner = () => {
|
||||
}
|
||||
|
||||
async function initializeVisualizer(width: number, height: number) {
|
||||
if (!gains || gains.length === 0 || !canvas || !context || !librariesLoaded) return;
|
||||
const nodes = getVisualizerAudioNodes(webAudio, playbackType);
|
||||
if (!nodes.length || !canvas || !context || !librariesLoaded) return;
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
@@ -173,8 +210,8 @@ const VisualizerInner = () => {
|
||||
width,
|
||||
}) as ButterchurnVisualizer;
|
||||
|
||||
for (const gain of gains) {
|
||||
butterchurnInstance.connectAudio(gain);
|
||||
for (const node of nodes) {
|
||||
butterchurnInstance.connectAudio(node);
|
||||
}
|
||||
|
||||
visualizerRef.current = butterchurnInstance;
|
||||
@@ -192,10 +229,18 @@ const VisualizerInner = () => {
|
||||
cleanupVisualizer();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [webAudio, isPlaying, librariesLoaded]);
|
||||
}, [webAudio, playbackType, librariesLoaded, webInitGeneration]);
|
||||
|
||||
// Kill visualizer after 5 seconds of pause
|
||||
useEffect(() => {
|
||||
if (playbackType === PlayerType.LOCAL) {
|
||||
if (pauseTimerRef.current) {
|
||||
clearTimeout(pauseTimerRef.current);
|
||||
pauseTimerRef.current = undefined;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
// Clear pause timer if player resumes
|
||||
if (pauseTimerRef.current) {
|
||||
@@ -220,7 +265,7 @@ const VisualizerInner = () => {
|
||||
pauseTimerRef.current = undefined;
|
||||
}
|
||||
};
|
||||
}, [isPlaying]);
|
||||
}, [isPlaying, playbackType]);
|
||||
|
||||
// Handle resize
|
||||
useEffect(() => {
|
||||
@@ -460,7 +505,7 @@ const VisualizerInner = () => {
|
||||
};
|
||||
}, [isVisualizerReady, librariesLoaded, butterchurnSettings.blendTime]);
|
||||
|
||||
const shouldRenderContainer = isPlaying || isVisualizerReady;
|
||||
const shouldRenderContainer = isPlaying || isVisualizerReady || !!webAudio;
|
||||
|
||||
if (!shouldRenderContainer) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { useVisualizerSystemAudio } from '/@/renderer/features/player/hooks/use-visualizer-system-audio';
|
||||
|
||||
export function VisualizerSystemAudioBridge() {
|
||||
useVisualizerSystemAudio();
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user