javascriptrustactix-web

Sending an audio recording from js to an actix-web server to save to disk


Currently trying to send some recorded audio from the browser to an actix-web server, and trying to save it to disk in a manner that results in a playable track from a generic media player. I am finding this difficult to do, especially because I have never worked with media before. I am basing whatever I am doing on. a combination of some googling and some GPTing. The problem: A file does seem to be saved to disk. It is a non-zero size file however it does not seem playable in VLC media player. The script to send the recording to the actix-web server:

const blob = new Blob(chunks, { type: "audio/mp3" });
  const audioURL = URL.createObjectURL(blob);
  const audio = new Audio(audioURL);

  // create a button
  let button = document.createElement("BUTTON");
  // creating text to be
  //displayed on button
  let text = document.createTextNode("Play recording ", audio_rec_num + 1);
  button.appendChild(text);
  button.setAttribute("num", audio_rec_num);

  audio_rec_num++;
  document.body.appendChild(button);
  button.addEventListener("click", function () {
    let num = Number(button.getAttribute("num"));

    // audio_chunks[num].play();

    // Create FormData object to send the audio data
    const formData = new FormData();
    formData.append("audio", blob, "recorded_audio.mp3");

    // Send the audio data to the server using fetch or XMLHttpRequest
    fetch("http://127.0.0.1:8080/track_sample", {
      method: "POST",
      body: formData,
    })
      .then((response) => {
        if (response.ok) {
          console.log("Audio sent successfully");
        } else {
          console.error("Failed to send audio");
        }
      })
      .catch((error) => {
        console.error("Error sending audio: ", error);
      });
  });

The handler receiving the data:

async fn track_sampler(req_body: web::Bytes) -> impl Responder {
println!("track sampler hit");
let audio_data = req_body.to_vec();
println!("Received audio data with length: {}", audio_data.len());

// Save the audio data to a WAV file
let file_path = "received_audio.mp3";
let mut file = match File::create(file_path) {
    Ok(file) => file,
    Err(_) => return HttpResponse::InternalServerError().finish(),
};

// Write the bytes of the audio file to the file on disk
if let Err(_) = file.write_all(&audio_data) {
    return HttpResponse::InternalServerError().finish();
}
println!("done");
// Return a success response
HttpResponse::Ok().body("Audio file uploaded successfully")

}

I just need some clear pointers to be able to implement this. I don't necessarily need code, but that could be super helpful as well. What am I doing wrong ? I know I am saving it incorrectly, but how do it 'correctly' ?

Note : I do not wish to save the audio to disk directly from the JS


Solution

  • The JavaScript FormData type encodes the body as "multipart/form-data". It is not simply your raw file bytes. To decode this body in your Actix-Web handler, you'll want to use actix-multipart.

    The simple way to use it would be to use a derive macro on a struct reflecting the form data.

    And then use it in your handler like so:

    use actix_multipart::form::MultipartForm;
    
    async fn track_sampler(MultipartForm(upload): MultipartForm<TrackUpload>) -> impl Responder {
        // use upload.audio
    }
    

    There is also the option to accept actix_multipart::Multipart into your handler to handle the field and content streaming more manually than the above technique if that is required.