问题
I'm trying to write a React Hook to handle streaming audio to an AudioContext which is analysed with Meyda.
https://meyda.js.org/
I have managed to get the stream working and am able to pull out the data I want. However, I'm having trouble de-initialising the audio.
If someone can offer me some guidance on setting up this hook correctly I'd be most grateful.
I'm currently receiving the following error when I navigate away from a page using these hooks:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have attempted to add a cleanup function to the end of my hook, but my attempts often ended with the audio cutting off immediately or any number of other weird bugs.
Microphone Audio Hook with Meyda Analyser
export const useMeydaAnalyser = () => {
const [running, setRunning] = useState(false);
const [features, setFeatures] = useState(null);
const featuresRef = useRef(features);
const audioContext = useRef(new AudioContext());
const getMedia = async() => {
try {
return await navigator
.mediaDevices
.getUserMedia({audio: true, video: false});
} catch(err) {
console.log('Error:', err);
}
};
useEffect(
() => {
const audio = audioContext.current;
let unmounted = false;
if(!running) {
getMedia().then(stream => {
if (unmounted) return;
setRunning(true);
const source = audio.createMediaStreamSource(stream);
const analyser = Meyda.createMeydaAnalyzer({
audioContext: audio,
source: source,
bufferSize: 1024,
featureExtractors: [
'amplitudeSpectrum',
'mfcc',
'rms',
],
callback: nextFeatures => {
if(!isEqual(featuresRef.current, nextFeatures)) {
setFeatures(nextFeatures);
}
},
});
analyser.start();
});
}
return () => {
unmounted = true;
}
},
[running, audioContext],
);
useEffect(
() => {
featuresRef.current = features;
},
[features],
);
return features;
};
Audio View
import React, {useEffect} from 'react';
import { useMeydaAnalyser } from '../hooks/use-meyda-audio';
const AudioViewDemo = () => {
const audioContext = new AudioContext();
const features = useMeydaAnalyser(audioContext);
useEffect(() => {
// Todo: Handle Audio features
console.log(features);
// setAudioData(features);
}, [features]);
return (
<div>
RMS: {features && features.rms}
</div>
);
};
export default AudioViewDemo;
回答1:
The error should be caused by not closing AudioContext
. You need to close AudioContext
in the cleanup functions.
Note that before using AudioContext
, determine if the state is off, because getMedia
is asynchronous, so if the component is unloaded soon after loading, AudioContext
is turned off when it is used.
const getMedia = async () => {
try {
return await navigator.mediaDevices.getUserMedia({
audio: true,
video: false,
})
} catch (err) {
console.log('Error:', err)
}
}
const useMeydaAnalyser = () => {
const [analyser, setAnalyser] = useState(null)
const [running, setRunning] = useState(false)
const [features, setFeatures] = useState(null)
useEffect(() => {
const audioContext = new AudioContext()
let newAnalyser
getMedia().then(stream => {
if (audioContext.state === 'closed') {
return
}
const source = audioContext.createMediaStreamSource(stream)
newAnalyser = Meyda.createMeydaAnalyzer({
audioContext: audioContext,
source: source,
bufferSize: 1024,
featureExtractors: ['amplitudeSpectrum', 'mfcc', 'rms'],
callback: features => {
console.log(features)
setFeatures(features)
},
})
setAnalyser(newAnalyser)
})
return () => {
if (newAnalyser) {
newAnalyser.stop()
}
if (audioContext) {
audioContext.close()
}
}
}, [])
useEffect(() => {
if (analyser) {
if (running) {
analyser.start()
} else {
analyser.stop()
}
}
}, [running, analyser])
return [running, setRunning, features]
}
来源:https://stackoverflow.com/questions/57298567/correct-handling-of-react-hooks-for-microphone-audio