提前取消 setTimeout

Canceling setTimeout early

我正在制作一段录音 class,它可以运行一段指定的时间(例如 5 秒),也可以由用户提前停止。

我正在使用 setTimeout 来定义录制长度,这很有效。但是,我无法让 setTimeout 使用“停止”按钮。错误如下:

Cannot read properties of null (reading 'stop')

当 startRecording 函数执行时,会调用 handleStopRecording 函数,它使用“stopRecording”函数设置计时器。如果在时间到之前调用了“stopRecording”函数(通过按下“stop”按钮),那么最初在setTimeout中的函数调用仍然会在定时器到期时执行,从而导致错误。

我尝试使用 clearTimeout 解决这个问题,但是原始函数调用的“上下文”丢失了,我们得到了同样的错误:

Cannot read properties of null (reading 'stop')

除非我弄错了,否则我认为这是关闭 setTimeout 函数的问题 - 但是我不确定如何使用停止按钮提前清除该函数并限制录制时间。

提前致谢!

App.js (React.js)

import AudioRecorder from "./audioRecorder";
const App = () => {
  const [recordedNameClipURL, setRecordedNameClipURL] = useState(null);
  const [timeoutId, setTimeoutId] = useState(null);

  const recorder = new AudioRecorder();

  const startRecording = () => {
    recorder.start();
    handleStopRecording();
  };

  const handleStopRecording = async () => {
    const id = setTimeout(stopRecording, 3000);
    setTimeoutId(id);
  };

  const stopRecording = async () => {
    clearTimeout(timeoutId);

    const response = await recorder.stop();
    setRecordedNameClipURL(response);
  };

  return (
    ...
  );
};

audioRecorder.js

class AudioRecorder {
  constructor() {
    this.audioRecorder = null;
    this.audioChunks = [];
  }

  initialize = async () => {
    try {
      await this.isSupported();

      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      this.audioRecorder = new MediaRecorder(stream);

      this.audioRecorder.addEventListener("dataavailable", event => {
        this.audioChunks.push(event.data);
      });
    } catch (err) {
      console.log(err.message);
    }
  };

  start = async () => {
    try {
      await this.initialize();

      this.audioRecorder.start();
    } catch (err) {
      console.log(err.message);
    }
  };

  stop = async () => {
    try {
      this.audioRecorder.stop();
      const blob = await this.stopStream();

      return URL.createObjectURL(blob);
    } catch (err) {
      console.log(err.message);
    }
  };

  stopStream = () => {
    return new Promise(resolve => {
      this.audioRecorder.addEventListener("stop", () => {
        const audioBlob = new Blob(this.audioChunks, {
          type: this.audioRecorder.mimeType,
        });
        resolve(audioBlob);
      });
    });
  };

  isSupported = () => {
    return new Promise((resolve, reject) => {
      if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        resolve(true);
      }
      reject(new Error("getUserMedia not supported on this browser!"));
    });
  };
}

export default AudioRecorder;

您可以尝试使用布尔值来检查进程是否已停止。您可以将其存储在状态中并在启动或停止时更改其值

  const [isStopped, setIsStopped] = useState(false);

  const handleStopRecording = async () => {
    const id = setTimeout(() => {
      if(!isStopped){
        stopRecording
      }
    }, 3000);
    setTimeoutId(id);
  };

改为将计时器存储在 React Ref 中

我通常将 timeout/interval ID 存储在 React ref 中,因为存储句柄并不是像其他东西那样真正的“应用程序状态”。有时需要避免渲染抖动。

这是它的样子:

let timerRef = React.useRef(null)

const handleStopRecording = async () => {
  timerRef.current = setTimeout(stopRecording, 3000)
}

const stopRecording = async () => {
  clearTimeout(timerRef.current)
  timerRef.current = null // good idea to clean up your data
  
  const response = await recorder.stop()
  setRecordedNameClipURL(response)
}

需要知道定时器是否运行的代码应该参考timerRef;不需要额外的状态:

let timerIsRunning = !!timerRef.current