如何让React应用中的音频录制功能正常运行?
React语音录制onstop回调无输出问题排查与修复
我正尝试为React应用添加语音录制功能,但未达到预期效果。在ondataavailable方法中使用console.log()能看到每秒持续增长的blobs数组,但在onstop方法中执行console.log()却无任何输出。相关代码如下:
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { const mediaRecorder = new MediaRecorder(stream) const audio = document.querySelector('audio') const microphoneBtn = document.querySelector('.microphone-btn') microphoneBtn?.addEventListener('click', function () { mediaRecorder.start(1000) setRecordingState(mediaRecorder.state) }) //start recording let chunks = [] mediaRecorder.ondataavailable = e => { chunks.push(e.data) } console.log(chunks) //stop recording const stopBtn = document.querySelector('.stop-audio-recording') stopBtn?.addEventListener('click', function () { mediaRecorder.stop() setRecordingState(mediaRecorder.state) }) mediaRecorder.addEventListener('stop', e => { console.log(chunks) const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" }) chunks = [] const audioUrl = window.URL.createObjectURL(blob) audio.src = audioUrl }) mediaRecorder.onstop = e => { console.log(chunks) const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" }) chunks = [] const audioUrl = window.URL.createObjectURL(blob) audio.src = audioUrl } }).catch(err => console.log(err.message))
问题原因
- DOM引用失效:React组件更新时会重新渲染DOM,通过
document.querySelector获取的按钮/音频元素引用会失效,导致stop按钮的点击事件未正确触发mediaRecorder.stop(),自然onstop回调不会执行。 - 事件绑定冗余与作用域问题:同时使用
addEventListener('stop')和onstop赋值属于冗余操作;且chunks仅初始化一次,多次录制会累积旧数据,也未在每次录制前重置。 - 实例状态丢失:MediaRecorder实例直接定义在
getUserMedia的回调中,组件重新渲染后可能丢失实例引用,导致后续操作失效。
修复方案
改用React的useRef管理DOM元素、MediaRecorder实例和数据缓存,结合useEffect处理媒体设备初始化与清理,符合React组件生命周期规范:
import { useState, useRef, useEffect } from 'react'; function AudioRecorder() { const [recordingState, setRecordingState] = useState('inactive'); // 用useRef保存跨渲染周期的引用 const mediaRecorderRef = useRef(null); const chunksRef = useRef([]); const audioRef = useRef(null); useEffect(() => { let stream; const initMedia = async () => { if (!navigator.mediaDevices?.getUserMedia) return; try { stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream); mediaRecorderRef.current = mediaRecorder; // 监听数据片段 mediaRecorder.ondataavailable = (e) => { chunksRef.current.push(e.data); console.log('当前录制片段数:', chunksRef.current.length); }; // 监听录制停止事件 mediaRecorder.onstop = () => { console.log('录制结束,所有片段:', chunksRef.current); const audioBlob = new Blob(chunksRef.current, { type: "audio/ogg; codecs=opus" }); const audioUrl = window.URL.createObjectURL(audioBlob); if (audioRef.current) audioRef.current.src = audioUrl; // 重置片段缓存 chunksRef.current = []; }; } catch (err) { console.error('媒体设备初始化失败:', err.message); } }; initMedia(); // 组件卸载时清理资源 return () => { if (mediaRecorderRef.current?.state === 'recording') { mediaRecorderRef.current.stop(); } stream?.getTracks().forEach(track => track.stop()); }; }, []); const startRecording = () => { if (!mediaRecorderRef.current) return; chunksRef.current = []; // 每次录制前清空缓存 mediaRecorderRef.current.start(1000); setRecordingState('recording'); }; const stopRecording = () => { if (!mediaRecorderRef.current || mediaRecorderRef.current.state !== 'recording') return; mediaRecorderRef.current.stop(); setRecordingState('inactive'); }; return ( <div> <audio ref={audioRef} controls /> <button onClick={startRecording} disabled={recordingState === 'recording'}> {recordingState === 'recording' ? '录制中' : '开始录制'} </button> <button onClick={stopRecording} disabled={recordingState !== 'recording'}> 停止录制 </button> </div> ); } export default AudioRecorder;
修复要点
- useRef管理引用:用
useRef存储DOM元素、MediaRecorder实例和chunks数组,确保组件重新渲染时引用不丢失。 - useEffect初始化与清理:在
useEffect中完成媒体设备初始化和事件绑定,仅执行一次;组件卸载时停止录制并释放媒体流,避免内存泄漏。 - React事件绑定:用JSX的
onClick替代原生addEventListener,适配React的渲染机制。 - 录制状态重置:每次开始录制前清空chunks缓存,避免旧数据干扰。
内容的提问来源于stack exchange,提问作者Chisom Ozor
相关产品推荐
相关产品推荐

