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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:58:16