使用XMLHttpRequest发送录制音频仅首次成功,后续服务器收空文件的问题
句子识别器二次录音后服务器文件为空的问题
问题背景
我用JavaScript+Python(Flask)构建句子识别器,流程如下:
- 通过
navigator.mediaDevices.getUserMedia获取音频媒体流 - 结合
MediaRecorder和hark库,实现用户停止说话时自动停止录音 - 将录制的
.ogg音频通过FormData发送到后端服务器
前端JavaScript代码
let recorder; let stream; let hk; let chunks = []; function recoverAudio(cks) { const blob = new Blob(cks, {type: "audio/ogg"}); const http = new XMLHttpRequest(); const fd = new FormData(); fd.set("audio", blob); http.open("POST", "/api/recognize"); http.onreadystatechange = () => { }; http.send(fd); chunks = []; } function record(e) { chunks.push(e.data); console.log(e); } window.addEventListener("load", async () => { let el = document.querySelector(".footer-main-button"); [stream, recorder] = await prepareRecorder(); recorder.ondataavailable = record; hk = hark(stream); hk.on("stopped_speaking", () => { console.log("stopped speaking"); el.style.removeProperty("color") setTimeout(() => { hk.suspend(); recorder.stop(); recoverAudio(chunks); }, 1000); // Wait for the last chunk to be recorded }); el.addEventListener("click", e => { if (recorder.state !== "recording") { recorder.start(1000); hk.resume(); el.style.color = "red"; } }); }, true);
后端Flask代码
@app.route("/api/recognize", methods=["POST"]) def recognize(): audio = flask.request.files["audio"] if not audio: return flask.abort(400) audio.save("./resources/temp/audio.ogg")
出现的问题
首次录音发送完全正常,但第二次录音后,服务器端的./resources/temp/audio.ogg文件为空。已通过开发者工具确认请求中包含有效音频,但刷新页面后首次发送又恢复正常,后续仍会重复出现问题。
问题原因
核心问题是**MediaRecorder实例无法被重复启动/停止**:根据浏览器API规范,MediaRecorder调用stop()后,该实例就会进入不可用状态,后续再次调用start()不会正常生成新的音频数据。虽然前端代码中看似触发了录音流程,但实际上没有新的音频片段被写入chunks数组,最终发送的是空白音频文件。
解决方案
每次录音结束后,重新创建一个全新的MediaRecorder实例,确保每次录音都使用可用的Recorder对象。
修改后的前端代码
let recorder; let stream; let hk; let chunks = []; function recoverAudio(cks) { const blob = new Blob(cks, {type: "audio/ogg"}); const http = new XMLHttpRequest(); const fd = new FormData(); fd.set("audio", blob); http.open("POST", "/api/recognize"); http.onreadystatechange = () => { }; http.send(fd); } function record(e) { chunks.push(e.data); console.log(e); } // 单独封装Recorder初始化逻辑 async function initRecorder() { recorder = new MediaRecorder(stream, { mimeType: 'audio/ogg' }); recorder.ondataavailable = record; } window.addEventListener("load", async () => { let el = document.querySelector(".footer-main-button"); // 先单独获取媒体流 [stream] = await prepareRecorder(); await initRecorder(); hk = hark(stream); hk.on("stopped_speaking", () => { console.log("stopped speaking"); el.style.removeProperty("color") setTimeout(async () => { hk.suspend(); recorder.stop(); // 传递chunks副本,避免异步过程中数据被污染 recoverAudio([...chunks]); chunks = []; // 重新初始化Recorder await initRecorder(); }, 1000); }); el.addEventListener("click", e => { if (recorder.state !== "recording") { recorder.start(1000); hk.resume(); el.style.color = "red"; } }); }, true);
后端辅助排查(可选)
可以在后端添加日志,确认接收的文件大小,验证前端发送的内容是否有效:
@app.route("/api/recognize", methods=["POST"]) def recognize(): audio = flask.request.files["audio"] if not audio: return flask.abort(400) # 打印接收的文件大小,排查是否为空 print(f"接收音频文件大小: {audio.content_length} 字节") audio.save("./resources/temp/audio.ogg") return "处理完成"
内容的提问来源于stack exchange,提问作者anom907 zat
相关产品推荐
相关产品推荐

