How can I add predefined length to audio recorded from MediaRecorder in Chrome?
This is a chrome bug.
FF does expose the duration of the recorded media, and if you do set the currentTime
of the recorded media to more than its actual duration
, then the property is available in 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-activationdocument.getElementById('button').onclick = function(evt){ getData(); this.remove();}
<button id="button">start</button><audio id="aud" controls></audio><span id="log"></span>
So the advice here would be to star the bug report so that chromium's team takes some time to fix it, even if this workaround can do the trick...
Thanks to @Kaiido for identifying bug and offering the working fix.
I prepared an npm package called get-blob-duration that you can install to get a nice Promise-wrapped function to do the dirty work.
Usage is as follows:
// Returns Promise<Number>getBlobDuration(blob).then(function(duration) { console.log(duration + ' seconds');});
Or ECMAScript 6:
// yada yada asyncconst duration = await getBlobDuration(blob)console.log(duration + ' seconds')
A bug in Chrome, detected in 2016, but still open today (March 2019), is the root cause behind this behavior. Under certain scenarios audioElement.duration
will return Infinity
.
The following code provides a workaround to avoid the bug.
Usage : Create your audioElement
, and call this function a single time, providing a reference of your audioElement
. When the returned promise
resolves, the audioElement.duration
property should contain the right value. ( It also fixes the same problem with 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 ) });