ReactJS中使用getUserMedia录音报错:mediaRecorder.start不是函数
Hey there, let's break down what's going wrong and fix your audio recorder step by step. That TypeError is happening because your mediaRecorder state isn't actually holding a valid MediaRecorder instance when you try to call start() on it. Let's work through the issues one by one:
1. Broken this context in the getUserMedia callback
In your componentDidMount, you used a regular function for the .then() callback—this breaks the this reference to your React component. So when you tried this.setState({ mediaRecorder: stream }), it never actually updated the component state, leaving mediaRecorder as the initial empty array [].
Fix this with an arrow function (which preserves the component's this context) and stop overwriting the original media stream with the recorder instance:
componentDidMount() { if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { console.log('getUserMedia supported'); navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { // Arrow function preserves component `this` const mediaRecorder = new MediaRecorder(stream); // Bind recorder events once here, not in startRecord let chunks = []; mediaRecorder.ondataavailable = (e) => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" }); this.setState({ audioURL: window.URL.createObjectURL(blob) }); chunks = []; }; this.setState({ mediaRecorder }); }) .catch(err => console.log("error : " + err)); } else { console.log("getUserMedia : missing"); } }
2. Misusing setState when calling mediaRecorder.start()
Your original startRecord method tried to overwrite the mediaRecorder state with the return value of start()—but start() doesn't return a valid recorder instance (it returns undefined). You just need to call the method on the existing instance in state, not replace the state with its output.
Simplify the startRecord method:
startRecord() { const { mediaRecorder } = this.state; if (mediaRecorder && mediaRecorder.state !== 'recording') { mediaRecorder.start(); console.log(mediaRecorder.state); // Logs "recording" console.log("recorder started"); alert(`Recording started! MediaRecorder state: ${mediaRecorder.state}`); } }
3. stopRecord wasn't actually stopping the recorder
You were calling recordOnStop() directly, but you need to trigger mediaRecorder.stop() to properly end the recording and fire the onstop event we bound earlier. Fix it like this:
stopRecord() { const { mediaRecorder } = this.state; if (mediaRecorder && mediaRecorder.state === 'recording') { mediaRecorder.stop(); console.log("recorder stopped"); } }
4. Unmanaged chunks variable
Your original code declared chunks as a local variable inside startRecord, which would be lost when the function finished. We moved chunk management to the componentDidMount callback so it persists throughout the recording session.
5. Missing deleteRecord method and binding
Your render method references this.deleteRecord, but it wasn't defined or bound. Add this to your constructor:
this.deleteRecord = this.deleteRecord.bind(this);
Then add the method itself:
deleteRecord() { this.setState({ audioURL: null }); alert("Recording deleted"); }
Full Fixed Component
Here's the complete working code, including an audio player to preview recordings:
import React from "react"; import "./mediaDevices-getUserMedia-polyfill.js"; class RecorderAPI extends React.Component { constructor(props) { super(props); this.handleDelete = this.handleDelete.bind(this); this.startRecord = this.startRecord.bind(this); this.stopRecord = this.stopRecord.bind(this); this.deleteRecord = this.deleteRecord.bind(this); this.state = { mediaRecorder: null, // Initialize as null instead of array audioURL: null }; } componentDidMount() { if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { console.log('getUserMedia supported'); navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { const mediaRecorder = new MediaRecorder(stream); let chunks = []; mediaRecorder.ondataavailable = (e) => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" }); this.setState({ audioURL: window.URL.createObjectURL(blob) }); chunks = []; }; this.setState({ mediaRecorder }); }) .catch(err => console.log("error : " + err)); } else { console.log("getUserMedia : missing"); } } startRecord() { const { mediaRecorder } = this.state; if (mediaRecorder && mediaRecorder.state !== 'recording') { mediaRecorder.start(); console.log(mediaRecorder.state); console.log("recorder started"); alert(`Recording started! MediaRecorder state: ${mediaRecorder.state}`); } } stopRecord() { const { mediaRecorder } = this.state; if (mediaRecorder && mediaRecorder.state === 'recording') { mediaRecorder.stop(); console.log("recorder stopped"); } } deleteRecord() { this.setState({ audioURL: null }); alert("Recording deleted"); } handleDelete(e) { const evtTgt = e.target; evtTgt.parentNode.parentNode.removeChild(evtTgt.parentNode); } render() { const { audioURL } = this.state; return ( <div> <button className="dashboard"> Dashboard</button> <span className="controlsBar"> <button onClick={this.startRecord} className="start"> Start recording </button> <button onClick={this.stopRecord} className="stop"> Stop recording</button> <button onClick={this.deleteRecord} className="delete"> Delete recording </button> </span> {/* Add audio player for recorded files */} {audioURL && ( <div className="audio-preview"> <h3>Recorded Audio</h3> <audio src={audioURL} controls /> </div> )} </div> ); } } export default RecorderAPI;
Key Takeaways
- Use arrow functions in promise callbacks to keep access to the component's
this. - Never overwrite state with the return value of recorder methods like
start()—call the method on the existing instance in state. - Bind recorder events (like
ondataavailableandonstop) when initializing the recorder, not when starting recording. - Manage temporary data like
chunksin a scope that persists during the recording session.
内容的提问来源于stack exchange,提问作者Diagathe Josué

