Decoding audio data of Mediarecorder is failed on chrome - javascript

I using MediaRecorder to record microphone. The default format of MediaRecorder in chrome is video/webm. Here is short example:
navigator.mediaDevices.getUserMedia({audio: true,video: false})
.then(function(stream) {
var recordedChunks = [];
var recorder = new MediaRecorder(stream);
recorder.start(10);
recorder.ondataavailable = function (event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
} else {
// ...
}
}
setTimeout(function(){
recorder.stop();
var blob = new Blob(recordedChunks, {
"type": recordedChunks[0].type
});
var blobUrl = URL.createObjectURL(blob);
var context = new AudioContext();
var request = new XMLHttpRequest();
request.open("GET", blobUrl, true);
request.responseType = "arraybuffer";
request.onload = function () {
context.decodeAudioData(
request.response,
function (buffer) {
if (!buffer) {
alert("buffer is empty!");
}
var dataArray = buffer.getChannelData(0);
//process channel data...
context.close();
},
function (error) {
alert(error);
}
);
};
request.send();
}, 3000);
})
.catch(function(error) {
console.log('error: ' + error);
});
This code is trow error of "Uncaught (in promise) DOMException: Unable to decode audio data" on context.decodeAudioData in chrome only.
What is wrong here and how can i fix it in chrome?
Here is working example in plunker: plunker

Ok... There is no fix for the issue. This is a chrome bug and you can see it here
The fix of decoding audio from MediaRecorder will be available on chrome version 58. I already tested it on 58-beta and it works.

Related

javascript audio api play blob url

I worked out a testing function for Web Audio API to play url blob:
// trigger takes a sound play function
function loadSound(url, trigger) {
let context = new (window.AudioContext || window.webkitAudioContext)();
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
trigger(()=>{
// play sound
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.start();
});
}, e=>{
console.log(e);
});
}
request.send();
}
loadSound(url, fc=>{
window.addEventListener('click', fc);
});
this is just for test, actually, I need a function to call to directly play the sound from url, if any current playing, quit it.
let ac;
function playSound(url) {
if(ac){ac.suspend()}
let context = new (window.AudioContext || window.webkitAudioContext)();
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
// play sound
let source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
// source.noteOn(0); // play the source now
ac = context;
source.start();
}, e=>{
console.log(e);
});
}
request.send();
}
window.addEventListener('click',()=>{
playSound(url);
});
I did not do much modification, however, the second version, triggers works fine, but always produces no sound.
I suspect it may be this variable scope issue, I will be very glad if you can help me debug it.
since the blob url is too long, I put two versions in code pen.
working version
not working version
Instead of calling supsend on the stored AudioContext, save a reference to the AudioBufferSourceNode that is currently playing. Then check if the reference exits and call stop() whenever you play a new sound.
const context = new AudioContext();
let bufferSource = null;
function playSound(url) {
if (bufferSource !== null) {
bufferSource.stop();
bufferSource = null;
}
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, (buffer) => {
bufferSource = context.createBufferSource();
bufferSource.buffer = buffer;
bufferSource.connect(context.destination);
bufferSource.start();
bufferSource.addEventListener('ended', () => {
bufferSource = null;
});
}, (error) => {
console.log(error);
});
}
request.send();
}
window.addEventListener('click', () => {
playSound(url);
});

Download a file using XHR in iOS Safari

I am trying to add the functionality to download a file hosted in a server. To access the file I have to send the Authorization header, thus I have to send an XHR request to get the file from the server. Since the file content is in a variable, I have to create a data url to make it available as the href attribute of an anchor tag and click it programmatically to download the file.
It's working good in almost all the browser (Except IE11, for which I have written a separate code), but in iOS Safari (in some versions of iOS), it's giving errors. Here's the code that I am using -
var isBrowserIE = window.navigator && window.navigator.msSaveOrOpenBlob;
var dataHref = 'https://example.com/doc.pdf';
var xhr = new XMLHttpRequest();
xhr.open('GET', dataHref, true);
xhr.setRequestHeader('Content-Type', 'application/pdf');
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.responseType = isBrowserIE ? 'blob' : 'arraybuffer';
xhr.onload = function (e) {
if (this.status == 200) {
//For IE11
if (isBrowserIE) {
// Create a new Blob object using the response data of the onload object
var blob = new Blob([this.response], { type: 'application/pdf' });
var bool = window.navigator.msSaveOrOpenBlob(blob, docName);
if (!bool) {
alert("Download failed, Please try again later");
}
} else {
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
var data = binaryString.join('');
var base64 = window.btoa(data);
var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
var element = document.createElement('a');
element.setAttribute('href', dataUrl);
element.setAttribute('download', 'doc.pdf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
} else {
alert("Download failed, Please try again later");
closeWindow();
}
};
xhr.send();
Here's the possible error I am getting which is related -
Safari cannot open the page.<br><br>The error was: “Data URL decoding failed”.
Is there anything I missed which is the reason for this error? The error is occuring only in iPad 4 and iPad 5, but working in iPad mini and iPhone XR. Not sure why is it working in some versions of iOS devices and not in others.
So, I finally figured it out. Here's my final code with explanations in comments (Sorry for ES5 code, I needed to support IE11 and the current project is not using babel yet) -
/* exported DownloadHandler */
/* global Uint8Array*/
var DownloadHandler = (function() {
function isMobileDevice() {
return navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|Opera Mini|IEMobile/i);
}
function isChromeBrowser() {
return navigator.userAgent.match(/Crios|Chrome/i);
}
function isIEBrowser() {
return window.navigator && window.navigator.msSaveOrOpenBlob;
}
function isSafariBrowser() {
return navigator.userAgent.match(/Safari/i);
}
function getResponseType() {
// Both Desktop Chrome and IE supports blob properly
// Chrome also supports Data URI way, but it fails miserably when the file size is more than 2 MB (Not sure about the exact limit though).
if (isIEBrowser() || isChromeBrowser()) {
return 'blob';
} else if (isMobileDevice()) {
return 'arraybuffer';
}
return 'blob';
}
function getBlobUriFromResponse(response) {
var blob = new Blob([response], { type: 'application/pdf' });
var downloadUrl = URL.createObjectURL(blob);
return downloadUrl;
}
function getDataUriFromResponse(response) {
var uInt8Array = new Uint8Array(response);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
var data = binaryString.join('');
var base64 = window.btoa(data);
var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
return dataUrl;
}
function downloadFileUsingXHR(fileName, fileUrl, fileMimeType, requestType, headersList) {
var xhr = new XMLHttpRequest();
xhr.open(requestType, fileUrl, true);
xhr.setRequestHeader('Content-Type', fileMimeType);
for (var i = 0; i < headersList.length; i++) {
var header = headersList[i];
xhr.setRequestHeader(header.key, header.value);
}
xhr.responseType = getResponseType();
xhr.onload = function() {
if (this.status == 200) {
//For IE11
//IE uses blob with vendor specific code
if (isIEBrowser()) {
// Create a new Blob object using the response data of the onload object
var blob = new Blob([this.response], { type: fileMimeType });
var bool = window.navigator.msSaveOrOpenBlob(blob, fileName);
if (!bool) {
alert('Download failed, Please try again later');
}
} else {
var dataUrl;
if (this.responseType === 'blob') {
dataUrl = getBlobUriFromResponse(this.response);
} else {
dataUrl = getDataUriFromResponse(this.response);
}
var element = document.createElement('a');
// Safari doesn't work well with blank targets
if (!isSafariBrowser()) {
element.setAttribute('target', '_blank');
}
element.setAttribute('href', dataUrl);
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
} else {
alert('Download failed, Please try again later');
}
};
xhr.send();
}
return {
downloadFileUsingXHR: downloadFileUsingXHR
};
})();
Here's how to use the above code:
DownloadHandler.downloadFileUsingXHR('example.pdf', 'https://example.com/doc.pdf', 'application/pdf','GET',[{key:'Authorization',value:'Bearer ' + token}]);
I'll probably convert it into a library later and post a link here. I'll get the chance to refine the code too

Loading audio files through URLs from remote locations (Opera)?

Maybe someone knows why codepend does not load audio files from URLs? (I do not have pro codepen, so I can't use direct uploading of files to pen).
I have this Audio "loader" implementation in my program:
// Audio loader implementation.
window.onload = init;
let context;
let bufferLoader;
let greenBuffer = null;
let redBuffer = null;
let blueBuffer = null;
let yellowBuffer = null;
let dohBuffer = null;
let woohooBuffer = null;
let excellentBuffer = null;
let superDohBuffer = null;
// Buffer loader class taken from https://www.html5rocks.com/en/tutorials/webaudio/intro/
function BufferLoader(context, urlList, callback) {
this.context = context;
this.urlList = urlList;
this.onload = callback;
this.bufferList = new Array();
this.loadCount = 0;
}
BufferLoader.prototype.loadBuffer = function(url, index) {
// Load buffer asynchronously
let request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
let loader = this;
request.onload = function() {
// Asynchronously decode the audio file data in request.response
loader.context.decodeAudioData(
request.response,
function(buffer) {
if (!buffer) {
alert('error decoding file data: ' + url);
return;
}
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
},
function(error) {
console.error('decodeAudioData error', error);
}
);
}
request.onerror = function() {
alert('BufferLoader: XHR error');
}
request.send();
}
BufferLoader.prototype.load = function() {
for (let i = 0; i < this.urlList.length; ++i)
this.loadBuffer(this.urlList[i], i);
}
function init() {
try {
// Fix up for prefixing
window.AudioContext = window.AudioContext||window.webkitAudioContext;
context = new AudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
bufferLoader = new BufferLoader(
context,
[
'https://cors-anywhere.herokuapp.com/https://s3.amazonaws.com/freecodecamp/simonSound1.mp3',
'https://cors-anywhere.herokuapp.com/https://s3.amazonaws.com/freecodecamp/simonSound2.mp3',
'https://cors-anywhere.herokuapp.com/https://s3.amazonaws.com/freecodecamp/simonSound3.mp3',
'https://cors-anywhere.herokuapp.com/https://s3.amazonaws.com/freecodecamp/simonSound4.mp3',
'https://cors-anywhere.herokuapp.com/http://www.springfieldfiles.com/sounds/homer/doh.mp3',
'https://cors-anywhere.herokuapp.com/http://www.springfieldfiles.com/sounds/homer/woohoo.mp3',
'https://cors-anywhere.herokuapp.com/http://springfieldfiles.com/sounds/burns/excellnt.mp3',
'https://cors-anywhere.herokuapp.com/http://www.springfieldfiles.com/sounds/homer/doheth.mp3',
],
setBuffers
);
bufferLoader.load();
}
function setBuffers(bufferList){
greenBuffer = bufferList[0];
redBuffer = bufferList[1];
blueBuffer = bufferList[2];
yellowBuffer = bufferList[3];
dohBuffer = bufferList[4];
woohooBuffer = bufferList[5];
excellentBuffer = bufferList[6];
superDohBuffer = bufferList[7];
}
If I use this code locally (not on codepen), it works fine. It loads those files and later I can play those audio files how I want. But if I run it on codepen, it throws this (note I also prepended https://cors-anywhere.herokuapp.com/ to URLs to bypass CORS):
console_runner-079c09a….js:1 decodeAudioData error DOMException: Unable to decode audio data
(anonymous) # console_runner-079c09a….js:1
(anonymous) # pen.js:80
index.html:1 Uncaught (in promise) DOMException: Unable to decode audio data
index.html:1 Uncaught (in promise) DOMException: Unable to decode audio data
Full pen can be checked here: https://codepen.io/andriusl/pen/proxKj
Update.
It seems this is related with browsers. AudioContext does not properly work with Opera browser, so this question is more oriented to browser than codepen itself.

Uncaught (in promise) DOMException: Unable to decode audio data

I'm having an issue with decodeAudioData method using Web Audio API to playback in Chrome (it works fine in Firefox)-
I am sending the audio buffer recorded by media recorder back from the server.
Server side
wss = new WebSocketServer({server: server}, function () {});
wss.on('connection', function connection(ws) {
ws.binaryType = "arraybuffer";
ws.on('message', function incoming(message) {
if ((typeof message) == 'string') {
console.log("string message: ", message);
} else {
console.log("not string: ", message);
ws.send(message);
}
});
});
Client side
window.AudioContext = window.AudioContext||window.webkitAudioContext;
navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
var context = new AudioContext();
var mediaRecorder;
var chunks = [];
var startTime = 0;
ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
ws.onmessage = function(message) {
if (message.data instanceof ArrayBuffer) {
context.decodeAudioData(message.data, function(soundBuffer){
playBuffer(soundBuffer);
},function(x) {
console.log("decoding failed", x)
});
} else {
console.log("not arrayBuffer", message.data);
}
};
createMediaRecorder();
function createMediaRecorder() {
if (navigator.getUserMedia) {
console.log('getUserMedia supported.');
var constraints = {
"audio": true
};
var onSuccess = function(stream) {
var options = {
audioBitsPerSecond : 128000,
mimeType : 'audio/webm\;codecs=opus'
};
mediaRecorder = new MediaRecorder(stream, options);
};
var onError = function(err) {
console.log('The following error occured: ' + err);
};
navigator.getUserMedia(constraints, onSuccess, onError);
} else {
alert('getUserMedia not supported on your browser!');
}
}
function playBuffer(buf) {
var source = context.createBufferSource();
source.buffer = buf;
source.connect(context.destination);
if (startTime == 0)
startTime = context.currentTime + 0.1; // add 50ms latency to work well across systems
source.start(startTime);
startTime = startTime + source.buffer.duration;
}
function startRecording() {
mediaRecorder.start();
getRecordedData();
}
function getRecordedData() {
mediaRecorder.ondataavailable = function(e) {
console.log('ondataavailable: ', e.data);
chunks.push(e.data);
};
}
function sendRecordedData() {
var superBuffer = new Blob(chunks, {type: 'audio/ogg'});
ws.send(superBuffer);
}
function stopRecording() {
mediaRecorder.stop();
mediaRecorder.onstop = function(e) {
sendRecordedData();
chunks = [];
};
}
While testing with firefox working fine but with chrome generate the following error:
Uncaught (in promise) DOMException: Unable to decode audio data
Any suggestion will be helpful, thanks in advance.
I encountered the same error. Updating Chrome did not fix it. However, debugging in Firefox instead gave me a much more descriptive error:
The buffer passed to decodeAudioData contains an unknown content type.
Uncaught (in promise) DOMException: MediaDecodeAudioDataUnknownContentType
Uncaught (in promise) DOMException: The given encoding is not supported.
Which by the way was occurring because the .mp3 file I wanted to load wasn't found. So I was making a web request, receiving the 404 HTML page, and trying to load that as an mp3, which failed as an 'unsupported audio format'.
I encountered the same issue. Upgrading Chrome to the latest release, eg 85.0.4183, resolved the issue for me.
I Had the same error with createjs (I used to load up the files).
It was integration problem with Internet Download Manager (IDM) ... I've solved it by disabling IDM !

JavaScript Web Audio: cannot properly decode audio data?

I'm trying to use the Web Audio API in JavaScript to load a sound into a buffer and play it. Unfortunately it doesn't work and I get the following error:
Uncaught TypeError: Failed to set the 'buffer' property on 'AudioBufferSourceNode':
The provided value is not of type 'AudioBuffer'.
I can pinpoint which line is giving me the error, but I don't know why. Here is the relevant code if it helps:
var audioContext;
var playSoundBuffer;
function init() {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContext();
loadNote();
}
function loadNote() {
var request = new XMLHttpRequest();
request.open("GET", "./sounds/topE.wav", true);
request.responseType = "arraybuffer";
request.onload = function() {
audioContext.decodeAudioData(request.response, function(buffer) {
playSoundBuffer = buffer;
}, function(error) {
console.error("decodeAudioData error", error);
});
};
request.send();
playSound();
}
function playSound() {
var source = audioContext.createBufferSource();
source.buffer = playSoundBuffer; // This is the line that generates the error
source.connect(audioContext.destination);
source.start(0);
}
I believe the decodeAudioData method returns an AudioBuffer to its first callback function (its second parameter). I tried to save this AudioBuffer to the "playSoundBuffer" and then play it, but I get that error and I'm not sure why. Any help would be greatly appreciated.
The reason you get that error is because you are ignoring the asynchronous nature of your code and treat it as if it were synchronous. If you always log the contents of all relevant parts as the first step in debugging you will realize that at the time you try to process your buffer it's undefined and not an AudioBuffer at all. Tip: Always console.log everything until you know exactly how it behaves at any point.
function loadNote() {
var request = new XMLHttpRequest();
request.open("GET", "./sounds/topE.wav", true);
request.responseType = "arraybuffer";
request.onload = function() {
audioContext.decodeAudioData(request.response, function(buffer) {
playSoundBuffer = buffer;
playSound(); // don't start processing it before the response is there!
}, function(error) {
console.error("decodeAudioData error", error);
});
};
request.send();//start doing something async
}

Categories

Resources