Search code examples
reactjsaudioreact-hooksweb-audio-apijavascript-oscillator

Oscillator built with react hooks won't stop


I'm trying to play around for the first time with the WebAudio API together with React.

My idea was to build a simple button that, once clicked, would start or stop a sound.

With the following code, I'm always getting the error "Failed to execute 'disconnect' on 'AudioNode': the given destination is not connected."

How can I fix it? Thanks!

import { useState } from 'react';

function App() {
  const [ dataPlaying, setDataPlaying ] = useState(false) 

  const AudioContext = window.AudioContext || window.webkitAudioContext
  const audioContext = new AudioContext()

  let osc = audioContext.createOscillator()
    osc.type = 'sine'
    osc.frequency.value = 880

  const createOscillator = () => {
    if (dataPlaying === false) {
      osc.start()
      osc.connect(audioContext.destination)
      setDataPlaying(true)
    } else {
      osc.disconnect(audioContext.destination)
      setDataPlaying(false)
    }
  }
    
  return (
    <div className="App">
      <button 
        onClick={() => createOscillator() }
        data-playing={ dataPlaying }>
        <span>Play/Pause</span>
      </button>  
    </div>
  );
}

export default App;

Solution

  • Here's my attempt to resolve the connection error.

    1. Create the AudioContext external to the component.
    2. Use an useRef hook to store an audio context to persist through rerenders.
    3. Use an useEffect hook to instantiate an oscillator and manage audo context connection.
    4. Use start/stop toggler to suspend or resume the context instead of connect/disconnect from it.

    Updated Code

    import React, { useEffect, useRef, useState } from "react";
    
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    
    export default function App() {
      const [dataPlaying, setDataPlaying] = useState(false);
      const audioContextRef = useRef();
    
      useEffect(() => {
        const audioContext = new AudioContext();
        const osc = audioContext.createOscillator();
        osc.type = "sine";
        osc.frequency.value = 880;
    
        // Connect and start
        osc.connect(audioContext.destination);
        osc.start();
    
        // Store context and start suspended
        audioContextRef.current = audioContext;
        audioContext.suspend();
    
        // Effect cleanup function to disconnect
        return () => osc.disconnect(audioContext.destination);
      }, []);
    
      const toggleOscillator = () => {
        if (dataPlaying) {
          audioContextRef.current.suspend();
        } else {
          audioContextRef.current.resume();
        }
        setDataPlaying((play) => !play);
      };
    
      return (
        <div className="App">
          <button onClick={toggleOscillator} data-playing={dataPlaying}>
            <span>{dataPlaying ? "Pause" : "Play"}</span>
          </button>
        </div>
      );
    }
    

    Edit oscillator-built-with-react-hooks-wont-stop