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
相关产品推荐
相关产品推荐

