用 React Hooks 构建的振荡器不会停止

Oscillator built with react hooks won't stop

我第一次尝试使用 WebAudio API 和 React。

我的想法是构建一个简单的按钮,单击该按钮即可启动或停止声音。

使用以下代码,我总是收到错误消息“无法在 'AudioNode' 上执行 'disconnect':给定的目的地未连接。”

我该如何解决? 谢谢!

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;

这是我解决连接错误的尝试。

  1. 在组件外部创建 AudioContext
  2. 使用 useRef 挂钩存储音频上下文以通过重新呈现持续存在。
  3. 使用 useEffect 挂钩实例化振荡器并管理音频上下文连接。
  4. 使用 start/stop 切换器暂停或恢复上下文,而不是 connect/disconnect 从中暂停或恢复上下文。

更新代码

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>
  );
}