如何将 blob URL 转换为音频文件并将其保存到服务器

Posted

技术标签:

【中文标题】如何将 blob URL 转换为音频文件并将其保存到服务器【英文标题】:How to convert a blob URL to a audio file and save it to the server 【发布时间】:2020-06-11 09:12:46 【问题描述】:

我已经成功录制并添加了录制的音频,并将其放置在我的 html 页面的音频标签中。

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

另外,我已经使用这一行成功地从源标记中获取了 blob 源 url。 var source = document.getElementById("Audio").src; 这是我的 Blob 网址

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

现在如何将 blob 源 URL 转换为音频文件并将其发送到我的服务器。我已经尝试了很多,帮助做到这一点,我是这个 blob 的新手。由于我使用此记录器 api 在所有浏览器上工作,我只有这个机会通过获取 blob 源然后将其转换为音频文件并使用表单数据将音频文件发送到我的服务器。

<!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"  ></rect>
                <rect x="62" y="10"  ></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"  ></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>

我已经尝试过获取代码是

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

但它给了我原始数据,我不知道如何发送和接收原始数据。

【问题讨论】:

真的很难看到缩小后的变量发生了什么。您能否仅包含问题的相关部分并使用未缩小的脚本?澄清一下,您想同时下载和解码,以及编码和上传您的音频 blob? @Emiel Zuurbier:缩小后的变量是我从 github 链接github.com/ai/audio-recorder-polyfill 获得的纯脚本,因为他让我将代码打包,但我是新手。所以我直接从浏览器中获取原始代码并在我的代码中实现。该代码适用于 safari 和边缘,但我必须将音频下载到我的服务器。在这一点上,我被打击了一个月。 【参考方案1】:

首先,您需要一个适当的函数来发送数据。您最初的 fetch 方法很接近,但并不完美。

让我们考虑下面的函数。它在file 参数中接收Blob。这个Blob 将在后面的答案中创建。在sendAudioFile 函数中创建一个新的FormData 对象。将Blob 附加到formData。

现在将带有POST 方法的formData 发送到您的服务器,并为formData 使用body 属性。

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

现在要创建文件,您需要捕获录制的流。现在您直接将录音设置为您的音频元素,但这对您获取录制的数据没有用。

getUserMedia 的回调中添加一个空数组,我们称之为data。该数组将捕获所有记录的数据并使用它来创建Blob

dataavailable 事件处理程序中,将e.data(即记录的数据)推送到data 数组。

添加另一个侦听stop 事件的事件侦听器。每当录制停止并收集所有数据时,请在 stop 事件回调中创建 Blob。您可以指定文件的MIME type 是什么来告诉它的格式。

现在您有了带有记录数据的Blob,可以将其传递给sendAudioFile 函数,该函数会将您的Blob 发送到服务器。

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

【讨论】:

你的代码可以在 edge、safari 和 ios mobile safari 中运行吗?如果是,那么如何接收音频并将其存储在我的后端页面的服务器存储中。我正在使用这个const downloadFile = (async (file, fileLocation) =&gt; const res = mainFile; const fileStream = fs.createWriteStream(fileLocation); await new Promise((resolve, reject) =&gt; res.body.pipe(fileStream); res.body.on("error", (err) =&gt; reject(err); ); fileStream.on("finish", function () resolve(); ););); 这似乎是一个新问题。我建议您创建一个新问题并在使该线程过于广泛之前再次提问。并且这段代码应该可以在所有最新的浏览器中运行,包括最新的 Edge,但不是 pre-chromium。如果有疑问,我建议您使用像 Babel 工具这样的工具将您的代码转换为 ES5 以支持旧版浏览器。 感谢您的努力和立即回复,但 navigator.mediaDevices 在 safari 和 edge 上没有浏览器支持。我的代码在没有任何后页支持的情况下工作,因此您可以直接通过 npm start 运行。我使用了const blob = new Blob(data, 'type': 'audio/mp3' );sendAudioFile(blob);,但在Failed to construct 'Blob': The object must have a callable @@iterator property. 上显示错误请帮助我并提前感谢 navigator.mediaDevices.getUserMedia() 应该根据caniuse 得到支持。 MediaRecorder 但是在 safari 中不受支持,但你得到了 polyfill,对吧?我无法重现 Failed to construct 'Blob': The object must have a callable @@iterator property. 错误。当我运行它时,它可以工作。在 Chrome 和 Safari 中测试。 好的,谢谢,我得到了一个构造的 blob,我在浏览器中有一个 blob 的控制台,并且 blob 看起来像这样 Blob size: 95819, type: "audio/webm" size: 95819 type: "audio/webm" __proto__: Blob 如果这是正确的,那么我如何从我的后台表单数据?【参考方案2】:

添加到@Emiel Zuubier 很好的答案,如果您需要通过 base64 编码的数据 URI 发送数据。在这种情况下;

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

然后像这样发送到服务器;

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

【讨论】:

以上是关于如何将 blob URL 转换为音频文件并将其保存到服务器的主要内容,如果未能解决你的问题,请参考以下文章

如何在给定 blob url 的情况下将大型音频文件上传到 django 服务器?

如何在 django 中将 blob url 转换为 mp3 音频

保存录制的音频 blob [JS]

使用 Node Multer 缓冲区获取 Blob 并将 Blob 转换为 Base 64

如何将blob url数据保存在目录中

如何获取和显示 blob 图像