Correct handling of React Hooks for microphone audio

人盡茶涼 提交于 2019-12-11 17:05:26

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!