如何为 Chrome 中从 MediaRecorder 录制的音频添加预定义的长度?

IT技术 javascript google-chrome html5-audio mediarecorder recordrtc
2021-01-30 15:17:07

我正在用内置的 MediaRecorder 替换 RecordRTC,以便在 Chrome 中录制音频。录制的音频然后在带有音频 api 的程序中播放。我无法让 audio.duration 属性正常工作。它说

如果视频(音频)是流式传输的并且没有预定义的长度,则返回“Inf”(无限)。

使用 RecordRTC,我不得不使用 ffmpeg_asm.js 将音频从 wav 转换为 ogg。我的猜测是在 RecordRTC 设置预定义音频长度的过程中。有没有办法使用 MediaRecorder 设置预定义长度?

4个回答

这是一个chrome 错误

FF 确实公开了录制媒体的持续时间,如果您确实将currentTime录制媒体的 设置为超过其实际duration,则该属性在 chrome 中可用...

var recorder,
  chunks = [],
  ctx = new AudioContext(),
  aud = document.getElementById('aud');

function exportAudio() {
  var blob = new Blob(chunks);
  aud.src = URL.createObjectURL(new Blob(chunks));

  aud.onloadedmetadata = function() {
    // it should already be available here
    log.textContent = ' duration: ' + aud.duration;
    // handle chrome's bug
    if (aud.duration === Infinity) {
      // set it to bigger than the actual duration
      aud.currentTime = 1e101;
      aud.ontimeupdate = function() {
        this.ontimeupdate = () => {
          return;
        }
        log.textContent += ' after workaround: ' + aud.duration;
        aud.currentTime = 0;
      }
    }
  }
}

function getData() {
  var request = new XMLHttpRequest();
  request.open('GET', 'https://upload.wikimedia.org/wikipedia/commons/4/4b/011229beowulf_grendel.ogg', true);
  request.responseType = 'arraybuffer';
  request.onload = decodeAudio;
  request.send();
}


function decodeAudio(evt) {
  var audioData = this.response;
  ctx.decodeAudioData(audioData, startRecording);
}

function startRecording(buffer) {

  var source = ctx.createBufferSource();
  source.buffer = buffer;
  var dest = ctx.createMediaStreamDestination();
  source.connect(dest);

  recorder = new MediaRecorder(dest.stream);
  recorder.ondataavailable = saveChunks;
  recorder.onstop = exportAudio;
  source.start(0);
  recorder.start();
  log.innerHTML = 'recording...'
  // record only 5 seconds
  setTimeout(function() {
    recorder.stop();
  }, 5000);
}

function saveChunks(evt) {
  if (evt.data.size > 0) {
    chunks.push(evt.data);
  }

}

// we need user-activation
document.getElementById('button').onclick = function(evt){
  getData();
  this.remove();
}
<button id="button">start</button>
<audio id="aud" controls></audio><span id="log"></span>

因此,这里的建议是为错误报告加星标,以便 Chromium 的团队需要一些时间来修复它,即使此解决方法可以解决问题...

@miguelao,是的,我提交了这个已经合并到你提到的那个。
2021-03-16 15:17:07
这里提到的chrome bug 已经被WontFixchromium 团队标记为,所以看起来如果我们要编辑实际文件,我们需要依赖像ts-ebml这样的库
2021-03-23 15:17:07
有一个错误:crbug.com/642012我建议我们对其“加星标”,以便开发人员可以相应地确定优先级。
2021-03-27 15:17:07
stackoverflow.com/questions/67936072/...请对此提供帮助。谢谢。
2021-04-07 15:17:07

感谢@Kaiido 识别错误并提供工作修复。

我准备了一个名为get-blob-duration的 npm 包,您可以安装它以获得一个漂亮的 Promise 包装函数来完成这些脏活。

用法如下:

// Returns Promise<Number>
getBlobDuration(blob).then(function(duration) {
  console.log(duration + ' seconds');
});

或者 ECMAScript 6:

// yada yada async
const duration = await getBlobDuration(blob)
console.log(duration + ' seconds')

Chrome 中的一个错误在 2016 年检测到,但今天(2019 年 3 月)仍然存在,是这种行为背后的根本原因。在某些情况下audioElement.duration会返回Infinity

Chrome 错误信息在这里这里

以下代码提供了避免该错误的解决方法

用法:创建您的audioElement,并单次调用此函数,提供您的audioElement. 当返回的promise解析时,该audioElement.duration属性应包含正确的值。(它也解决了同样的问题videoElements

/**
 *  calculateMediaDuration() 
 *  Force media element duration calculation. 
 *  Returns a promise, that resolves when duration is calculated
 **/
function calculateMediaDuration(media){
  return new Promise( (resolve,reject)=>{
    media.onloadedmetadata = function(){
      // set the mediaElement.currentTime  to a high value beyond its real duration
      media.currentTime = Number.MAX_SAFE_INTEGER;
      // listen to time position change
      media.ontimeupdate = function(){
        media.ontimeupdate = function(){};
        // setting player currentTime back to 0 can be buggy too, set it first to .1 sec
        media.currentTime = 0.1;
        media.currentTime = 0;
        // media.duration should now have its correct value, return it...
        resolve(media.duration);
      }
    }
  });
}

// USAGE EXAMPLE :  
calculateMediaDuration( yourAudioElement ).then( ()=>{ 
  console.log( yourAudioElement.duration ) 
});
经过数小时的各种修复后,这是唯一一个没有进一步问题的方法。太感谢了!
2021-03-16 15:17:07

感谢@colxi提供实际解决方案,我添加了一些验证步骤(因为该解决方案运行良好,但在处理长音频文件时存在问题)。

我花了大约 4 个小时才让它处理长音频文件,结果证明验证是解决方案

        function fixInfinity(media) {
          return new Promise((resolve, reject) => {
            //Wait for media to load metadata
            media.onloadedmetadata = () => {
              //Changes the current time to update ontimeupdate
              media.currentTime = Number.MAX_SAFE_INTEGER;
              //Check if its infinite NaN or undefined
              if (ifNull(media)) {
                media.ontimeupdate = () => {
                  //If it is not null resolve the promise and send the duration
                  if (!ifNull(media)) {
                    //If it is not null resolve the promise and send the duration
                    resolve(media.duration);
                  }
                  //Check if its infinite NaN or undefined //The second ontime update is a fallback if the first one fails
                  media.ontimeupdate = () => {
                    if (!ifNull(media)) {
                      resolve(media.duration);
                    }
                  };
                };
              } else {
                //If media duration was never infinity return it
                resolve(media.duration);
              }
            };
          });
        }
        //Check if null
        function ifNull(media) {
          if (media.duration === Infinity || media.duration === NaN || media.duration === undefined) {
            return true;
          } else {
            return false;
          }
        }

    //USAGE EXAMPLE
            //Get audio player on html
            const AudioPlayer = document.getElementById('audio');
            const getInfinity = async () => {
              //Await for promise
              await fixInfinity(AudioPlayer).then(val => {
                //Reset audio current time
                AudioPlayer.currentTime = 0;
                //Log duration
                console.log(val)
              })
            }