You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中使用MediaRecorder与MediaStream的多次播放报错问题

解决Next.js录音组件多次回放AbortError问题

错误信息

Unhandled Runtime Error (Uncaught (in promise))
AbortError(DOMException): 
    The fetching process for the media resource was aborted by the user agent at the user's request.

问题根源

  • 未释放对象URL:每次录音生成新的audioUrl时,没有调用URL.revokeObjectURL()释放旧资源,导致浏览器内存占用过高,最终主动中止资源请求
  • 事件绑定顺序错误:stopRecording中先调用stop()再绑定onstop事件,存在事件未触发的风险
  • 冗余变量:startRecording中localAudioChunks未实际使用,属于无效代码

修改后的组件代码

'use client'

import { useState, useRef, useEffect } from "react";

const AudioRecorderPlayer = () => {
  const [permission, setPermission] = useState(false);
  const [stream, setStream] = useState<MediaStream>();
  const [recordingStatus, setRecordingStatus] = useState("inactive");
  const mrRef = useRef<MediaRecorder | null>(null);
  const audioChunksRef = useRef<Blob[]>([]);
  const [audio, setAudio] = useState('');
  const dataType = "video/webm";

  // 释放旧的对象URL,避免内存泄漏
  useEffect(() => {
    return () => {
      if (audio) {
        URL.revokeObjectURL(audio);
      }
    };
  }, [audio]);

  const getMicrophonePermission = async () => {
    if ("MediaRecorder" in window) {
      try {
        const streamData = await navigator.mediaDevices.getUserMedia({
          audio: true,
          video: false,
        });
        setPermission(true);
        setStream(streamData);
      } catch (err) {
        alert((err as Error).message);
      }
    } else {
      alert("The MediaRecorder API is not supported in your browser.");
    }
  };

  const startRecording = () => {
    mrRef.current = null;
    audioChunksRef.current = [];

    setRecordingStatus("recording");
    const mediaRecorder = new MediaRecorder(stream!, {
      mimeType: dataType,
      audioBitsPerSecond: 16 * 44100
    });
    mrRef.current = mediaRecorder;

    mediaRecorder.ondataavailable = (event) => {
      if (typeof event.data === "undefined" || event.data.size === 0) return;
      audioChunksRef.current.push(event.data);
    };

    mediaRecorder.start();
  };

  const stopRecording = () => {
    setRecordingStatus("inactive");
    if (!mrRef.current) return;

    // 先绑定事件再停止录制
    mrRef.current.onstop = () => {
      const audioBlob = new Blob(audioChunksRef.current, { type: dataType });
      const audioUrl = URL.createObjectURL(audioBlob);
      setAudio(audioUrl);
    };

    mrRef.current.stop();
  };

  return (
    <div>
      <main>
        {!permission ? (
          <button onClick={getMicrophonePermission} type="button">
            Get Microphone
          </button>
        ) : null}
        {permission && recordingStatus === "inactive" ? (
          <button onClick={startRecording} type="button">
            Start Recording
          </button>
        ) : null}
        {recordingStatus === "recording" ? (
          <button onClick={stopRecording} type="button">
            Stop Recording
          </button>
        ) : null}
        <audio src={audio} id="audio-player" controls />
      </main>
    </div>
  );
};

export default AudioRecorderPlayer;

关键修改点说明

  • 新增useEffect钩子,在组件卸载或audio状态更新时释放旧的对象URL,解决内存泄漏问题
  • 调整stopRecording中事件绑定与stop()的顺序,确保onstop事件稳定触发
  • 移除无用的localAudioChunks变量
  • 修复错误信息显示逻辑,正确抛出麦克风请求失败的原因

内容的提问来源于stack exchange,提问作者Michel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:45:27