How to convert a blob URL to a audio file and save it to the server
Asked Answered
J

2

18

I have successfully recorded and added the recorded audio and placed it in the audio tag of my HTML page.

<audio controls="" src="blob:https://localhost:3000/494f62b9-0513-4d1c-9206-6569083a2661"></audio>

Also, I have successfully got the blob source URL from the source tag using this line:

var source = document.getElementById("Audio").src;

And this is my blob URL:

blob:https://localhost:3000/494f62b9-0513-4d1c-9206-6569083a2661

Now how can I convert the blob source URL as an audio file and send it to my server?

Since I am using this recorder API to work on all browsers, I have only this chance by getting the blob source and then converting it into an audio file and sending the audio file to my server using form data.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>FeedBack URL</title>
  <link rel="shortcut icon" href="./favicon.ico">
  <meta content="width=device-width" name="viewport">
  <meta name="theme-color" content="#00e5d2">
  <style>
    * {
      padding: 0;
      margin: 0
    }

    a {
      color: #009387;
      text-decoration: none
    }

    a:visited {
      color: #930087
    }

    body {
      margin: 1rem;
      font-family: sans-serif
    }

    main {
      max-width: 28rem;
      margin: 0 auto;
      position: relative
    }

    #controls {
      display: flex;
      margin-top: 2rem
    }

    button {
      flex-grow: 1;
      height: 2.5rem;
      min-width: 2rem;
      border: none;
      border-radius: .15rem;
      background: blue;
      margin-left: 2px;
      box-shadow: inset 0 -.15rem 0 rgba(0, 0, 0, .2);
      cursor: pointer;
      display: flex;
      justify-content: center;
      align-items: center
    }

    button:focus,
    button:hover {
      outline: none;
      background: blue;
    }

    button::-moz-focus-inner {
      border: 0
    }

    button:active {
      box-shadow: inset 0 1px 0 rgba(0, 0, 0, .2);
      line-height: 3rem
    }

    button:disabled {
      pointer-events: none;
      background: #d3d3d3
    }

    button:first-child {
      margin-left: 0
    }

    button svg {
      transform: translateY(-.05rem);
      fill: #000;
      width: 1.4rem
    }

    button:active svg {
      transform: translateY(0)
    }

    button:disabled svg {
      fill: #9a9a9a
    }

    button text {
      fill: #00e5d2
    }

    button:focus text,
    button:hover text {
      fill: #00ffe9
    }

    button:disabled text {
      fill: #d3d3d3
    }

    #formats,
    #mode {
      margin-top: .5rem;
      font-size: 80%
    }

    #mode {
      float: right
    }

    #support {
      display: none;
      margin-top: 2rem;
      color: red;
      font-weight: 700
    }

    #list {
      margin-top: 1.6rem
    }

    audio {
      display: block;
      width: 100%;
      margin-top: .2rem
    }

    li {
      list-style: none;
      margin-bottom: 1rem
    }

    .popup-position {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      background-color: rgba(0, 0, 0, 0.7);
      width: 100%;
      height: 100%;

      /* // The Modal Wrapper */
    }

    #popup-wrapper {
      text-align: left;
    }

    /* //The Modal Container */
    #popup-container {

      background-color: #fff;
      padding: 20px;
      border-radius: 10px;
      width: 300px;
      margin: 70px auto;
    }

    #closePopup {
      margin-left: 281px;
      margin-top: -18px;
    }
  </style>
</head>

<body>
  <a href="javascript:void(0)" onclick="toggle_visibility('contact-popup');">Open Popup</a>
  <div class="popup-position" id="contact-popup">
    <div class="popup-wrapper">
      <div id="popup-container">
        <h5>Feedback</h5>
        <p id="closePopup"><a href="javascript:void(0)" style="color: red;" title="Close"
            onclick="toggle_visibility('contact-popup');">X</a></p>
        <main>
          <div id="controls">
            <button id="record" disabled="" autocomplete="off" title="Record">
              <svg viewBox="0 0 100 100" id="recordButton">
                <circle cx="50" cy="50" r="46"></circle>
              </svg>
            </button>
            <button id="pause" disabled="" autocomplete="off" title="Pause">
              <svg viewBox="0 0 100 100">
                <rect x="14" y="10" width="25" height="80"></rect>
                <rect x="62" y="10" width="25" height="80"></rect>
              </svg>
            </button><button id="resume" disabled="" autocomplete="off" title="Resume">
              <svg viewBox="0 0 100 100">
                <polygon points="10,10 90,50 10,90"></polygon>
              </svg>
            </button><button id="stop" autocomplete="off" disabled="" title="Stop">
              <svg viewBox="0 0 100 100">
                <rect x="12" y="12" width="76" height="76"></rect>
              </svg>
            </button>
          </div>
          <div id="mode">
            Native support,<a href="?polyfill">force polyfill</a>
          </div>
          <div id="formats"></div>
          <div id="support">
            Your browser doesn’t support MediaRecorder
            So please use chrome or edge or mozilla
          </div>
          <ul id="list"></ul>
          <form enctype="multipart/form-data"></form>
            <input id="image-file" type="file" hidden />
            <button type="button" id="formSubmit" onclick="sendto();">Submit</button>
          </form>
        </main>
        <div class="modal-footer">
        </div>
      </div>
    </div>
  </div>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

  <script>
    (function () {
      var a, i, b, d, f, g, l = ["start", "stop", "pause", "resume"],
        m = ["audio/webm", "audio/ogg", "audio/wav"],
        j = 1024,
        k = 1 << 20;

      function n(e) {
        var r, $ = Math.abs(e);
        return $ >= k ? (r = "MB", e /= k) : $ >= j ? (r = "KB", e /= j) : r = "B", e.toFixed(0).replace(
          /(?:\.0*|(\.[^0]+)0+)$/, "$1") + " " + r;
      }

      function e(e) {
        i.innerHTML = "", navigator.mediaDevices.getUserMedia({
          audio: !0
        }).then(function (r) {
          a = new MediaRecorder(r), l.forEach(function (e) {
            a.addEventListener(e, t.bind(null, e));
          }), a.addEventListener("dataavailable", s), "full" === e ? a.start() : a.start(1e3);
        }), b.blur(), setTimeout(myFunction, 16000);
      }

      function o() {
        a.stop(), a.stream.getTracks()[0].stop(), g.blur();
      }

      function p() {
        a.pause(), d.blur();
      }

      function q() {
        a.resume(), f.blur();
      }

      function s(e) {
        var r = document.createElement("li"),
          $ = document.createElement("strong");
        $.innerText = "dataavailable: ", r.appendChild($);
        var a = document.createElement("span");
        a.innerText = e.data.type + ", " + n(e.data.size), r.appendChild(a), a.setAttribute("id", "span");
        var o = document.createElement("audio");
        o.controls = !0, o.src = URL.createObjectURL(e.data), o.setAttribute("id", "Audio"), r.appendChild(o), i
          .appendChild(r);
      }

      function t(e) {

        var r = document.createElement("li");
        r.innerHTML = "<strong>" + e + ": </strong>" + a.state, "start" === e && (r.innerHTML += ", " + a
            .mimeType), i.appendChild(r), "recording" === a.state ? (b.disabled = !0,
            f.disabled = !0, d.disabled = !1, g.disabled = !1) : "paused" === a.state ? (b
            .disabled = !0, f.disabled = !1, d.disabled = !0, g.disabled = !1) : "inactive" === a
            .state && (b.disabled = !1, f.disabled = !0, d.disabled = !0, g
            .disabled = !0);
      }
      i = document.getElementById("list"),
        b = document.getElementById("record"),
        f = document.getElementById("resume"),
        d = document.getElementById("pause"),
        g = document.getElementById("stop"),
        MediaRecorder.notSupported ? (i.style.display = "none",
          document.getElementById("controls").style.display = "none",
          document.getElementById("formats").style.display = "none",
          document.getElementById("mode").style.display = "none",
          document.getElementById("support").style.display = "block") : (document.getElementById("formats")
          .innerText = "Format: " + m
          .filter(function (e) {
            return MediaRecorder.isTypeSupported(e);
          }).join(", "), b.addEventListener("click", e.bind(null,
            "full")), f.addEventListener("click", q), d.addEventListener("click", p),
          g.addEventListener("click", o), b.disabled = !1);
    })();

    function myFunction() {
      document.getElementById("stop").click();
    }

    function toggle_visibility(id) {
      var element = document.getElementById(id);

      if (element.style.display == 'block')
        element.style.display = 'none';
      else
        element.style.display = 'block';
    }

    async function sendto() {
      var source = document.getElementById("Audio").src;

      $.ajax({
        type: 'POST',
        url: "http://localhost:3000/audioUpload",
        data: data,
        cache: false,
        processData: false,
        contentType: false,
        success: function(result) {
        }
      })

  </script>

</body>

</html>

I have tried the fetch code that is

let file = await fetch(source).then(r => r.blob()).then(blobFile => new File([blobFile], fileName, {
                   type: res[0]
               }));

But it gives me the raw data. How can I send and receive the raw data?

Jacki answered 27/2, 2020 at 11:5 Comment(3)
It is really hard to see what is going on with the minified variables. Could you include only the relevant parts for your question and use your unminified script? To clarify, you want to both download and decode, and encode and upload your audio blobs?Immunoreaction
@Emiel Zuurbier: The minified variables is plain script that I got from a github link github.com/ai/audio-recorder-polyfill, Since he asked me make a parcel of the code, but I am new to make parcels. So I took the raw code from the browsers directly and Implemented in my code. The code works in both safari and edge but I have to download the audio to my server. At this point that I got struck for a month.Jacki
Just by inspection, async function sendto() seems to be missing the end }Dantedanton
I
21

First you need a proper function to send your data. Your initial fetch approach was close, but not perfect.

Let's consider the function below. It takes in a Blob in the file parameter. This Blob will be created later in the answer. In the sendAudioFile function create a new FormData object. Append the Blob to the the formData.

Now send the formData with the POST method to your server and use the body property for the formData.

const sendAudioFile = file => {
  const formData = new FormData();
  formData.append('audio-file', file);
  return fetch('http://localhost:3000/audioUpload', {
    method: 'POST',
    body: formData
  });
};

Now to create your file you need to capture the recorded stream. Right now you are directly setting the recording to your audio element, but that’s not of any use for you to get the recorded data.

Add an empty array inside the callback of getUserMedia and let’s call it data. This array will capture all the recorded data and use it to create a Blob.

In the dataavailable event handler, push the e.data (which is the recorded data) to the data array.

Add another event listener that listens for the stop event. Whenever the recording has stopped and all data is collected, create a Blob in the stop event callback. You can specify what the MIME type of the file is to tell it's format.

Now you have your Blob with recorded data and can pass that to the sendAudioFile function which will send your Blob to the server.

navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
  // Collection for recorded data.
  let data = [];

  // Recorder instance using the stream.
  // Also set the stream as the src for the audio element.
  const recorder = new MediaRecorder(stream);
  audio.srcObject = stream;

  recorder.addEventListener('start', e => {
    // Empty the collection when starting recording.
    data.length = 0;
  });

  recorder.addEventListener('dataavailable', event => {
    // Push recorded data to collection.
    data.push(event.data);
  });

  // Create a Blob when recording has stopped.
  recorder.addEventListener('stop', () => {
    const blob = new Blob(data, {
      'type': 'audio/mp3'
    });
    sendAudioFile(blob);
  });

  // Start the recording.
  recorder.start();
});
Immunoreaction answered 27/2, 2020 at 12:48 Comment(9)
Is that, your code will work in edge, safari, and ios mobile safari? If yes then how to receive the audio and store in my server storage in my back page. I am using thisconst downloadFile = (async (file, fileLocation) => { const res = mainFile; const fileStream = fs.createWriteStream(fileLocation); await new Promise((resolve, reject) => { res.body.pipe(fileStream); res.body.on("error", (err) => { reject(err); }); fileStream.on("finish", function () { resolve(); });});});Jacki
That seems like a new question. I'd suggest that you create a new question and ask again before making this thread is too broad. And this code should work in all the latest browsers, including the latest Edge, but not pre-chromium. If in doubt I'd recommend that you use a tool like Babel tool transpile your code to ES5 to support older browsers.Immunoreaction
Thanks for your effort and immediate reply, but the navigator.mediaDevices has no browser support on safari and edge. My code works without any back page support so you can directly run via npm start. I have used the const blob = new Blob(data, { 'type': 'audio/mp3' });sendAudioFile(blob); but shows error on Failed to construct 'Blob': The object must have a callable @@iterator property. Help me with that and thanks in advanceJacki
navigator.mediaDevices.getUserMedia() should have support according to caniuse in both. MediaRecorder however is not supported in safari, but you got that polyfilled, right? I can't reproduce the Failed to construct 'Blob': The object must have a callable @@iterator property. error. When I run it, it works. Tested in Chrome and Safari.Immunoreaction
Ok, Thanks I got a constructed blob, I have a console of the blob in browser and is that a blob looks like this Blob {size: 95819, type: "audio/webm"} size: 95819 type: "audio/webm" __proto__: Blob if this is correct then I how do I receive it from the form data of my back page?Jacki
That blob seems correct. I suggest that you open a new question on how to read a blob sent in FormData. And do some research on how to store a file in your chosen database.Immunoreaction
Where did you get that audio.src = URL.createObjectURL(e.data); That woudl work only for the first ondataavailable event, all subsequents ones would fail, and anyway, if you wish to monitor the MediaStream in your <audio>, then just set its .srcObject = stream;Stellular
This is great, @EmielZuurbier - thank you!Syne
Defining the type of the Blob audio/mp3 doesn't automatically convert the native video/webm to the real mp3 formatRevengeful
O
2

Adding to Emiel Zuubier's great answer, if you need to send the data via a Base64-encoded data URI. In this case,

blobToBase64(blob) {
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    return new Promise(resolve => {
        reader.onloadend = () => {
            resolve(reader.result);
        };
    });
};

And then send it to the server like so:

blobToBase64(audioBlob)
    .then(base64Data => {
        const file = "data:audio/webm;base64," + base64Data;
        const formData = new FormData();
        formData.append('file', file);
        return fetch(url, {
            method: 'POST',
            body: formData
        }).then(res => res.json())
    })
Osman answered 20/3, 2021 at 18:8 Comment(1)
not working in firefox and safariViveca

© 2022 - 2024 — McMap. All rights reserved.