javascriptflaskweb-mediarecorder

Sending recorded audio data using XMLHttpRequest only works once


I'm building a sentence recognizer in javascript / python. The process is simple:

Here is the code:

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);

On the server side (with flask), i read the file as so:

@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")

But while the first time I record something, everything works fine, as soon as I rerecord something, the file ./resources/temp/audio.ogg is empty. I've checked the DevTools Network, the request contains the valid audio. Even weirder, if I reload the window the code works fine again the first time but not after.

Any ideas why? (As a reference here is the github containing the whole project: https://github.com/atzitz-amos/TM)


Solution

  • I made your code works ok as follows:

    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 = []; // EVEN WITH THIS AFTER FIRST RECORDING THERE IS 1 Blob WHEN START THE ENXT RECORDING (REMOVE THIS LINE OF CODE)
    }
    
    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") {
                console.log(`chunks.length before recorder.start: ${chunks.length}`) // <== Second time ==> chunks.length before recorder.start: 1
                chunks = []; // RESET chunks HERE // <== THIS MADE FOLLOWING RECORDS WORKS OK
                recorder.start(1000);
                hk.resume();
                el.style.color = "red";
            }
        });
    }, true);