Using Web Audio API decodeAudioData with external binary data - javascript

I've searched related questions but wasn't able to find any relevant info.
I'm trying to get the Web Audio API to play an mp3 file which is encoded in another file container, so what I'm doing so far is parsing said container, and feeding the result binary data (arraybuffer) to the audioContext.decodeAudioData method, which supposedly accepts any kind of arraybuffer containing audio data. However, it always throws the error callback.
I only have a faint grasp of what I'm doing so probably the whole approach is wrong. Or maybe it's just not possible.
Has any of you tried something like this before? Any help is appreciated!
Here's some of the code to try to illustrate this better. The following just stores the arraybuffer:
newFile: function(filename){
var that=this;
var oReq = new XMLHttpRequest();
oReq.open("GET", filename, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
var arrayBuffer = oReq.response; //
if (arrayBuffer) {
that.arrayBuffer=arrayBuffer;
that.parsed=true;
}
};
oReq.send(null);
And this is what I'm doing in the decoding part:
newTrack: function(tracknumber){
var that=this;
var arraybuffer=Parser.arrayBuffer;
that.audioContext.decodeAudioData(arraybuffer,function(buffer){
var track={};
track.trackBuffer=buffer;
track.isLoaded=true;
track.trackSource=null;
track.gainNode=that.audioContext.createGainNode();
that.tracklist.push(track);
},alert('error'));
Where Parser is an object literal that I've used to parse and store the arraybuffer (which has the newFile function)
So, to sum up, I don't know if I'm doing something wrong or it simply cannot be done.

Without the container, I'm not sure how decodeAudioData would know that it's an MP3. Or what the bitrate is. Or how many channels it has. Or a lot of other pretty important information. Basically, you need to tell decodeAudioData how to interpret that ArrayBuffer.
The only thing I could think of on the client side is trying to use a Blob. You'd basically have to write the header yourself, and then readAsArrayBuffer before passing it in to decodeAudioData.
If you're interested in trying that out, here's a spec:
http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
And here's RecorderJS, which would show you how to create the Blob (although it writes RIFF/WAV headers instead of MP3):
https://github.com/mattdiamond/Recorderjs/blob/master/recorderWorker.js
You'd want to look at the encodeWAV method.
Anyway, I would strongly recommend getting this sorted out on the server instead, if you can.

Related

JavaScript XMLHttpRequest.responseType as "arraybuffer" -- how to get current arraybuffer chunk

I'm trying to stream a from the client side to a server side, to later stream the video back to another client.
The point is:
How can I get chunks of a video from client-side JavaScript (that can be sent to a server)?
using this code, for example:
var x = new XMLHttpRequest();
var url = location.href;
x.onreadystatechange = function(){
if(x.readyState == 200) {
console.log("done");
} else {
console.log("chunk",x.response); //this is null until readyState is 200 anyway
}
}
x.onprogress = e => {
console.log("EE",e.target.response); //also null if resposneType is arraybuffer
};
x.responseType="arraybuffer";
x.open("GET","http://localhost:88/videoplayback.mp4",true);
x.send("");
When I try to print the response before its finished loading (to get it by chunks) then its simply null; the arraybuffer only returns as the respnse when its finished loading.
If I take out the responsetype and just leave it as plain text, then indeed some unicode-characters get printed to the screen for each readystatechange even before its finished, only an arraybuffer doesn't.
So: is this the best way to stream a video from the client to server, and if so, how can I actually do it? and if not, what's a better way?
Indeed, arraybuffer or blob responseTypes do not allow access of chunk of data until the download is complete.
Now, there are ways... For instance in newest browsers, you could start a ReadableStream from the fetch API, or from Firefox you could set the responseType to "moz-chunked-buffer", but that's not what you need at all here.
What you are describing is exactly what WebRTC has been made for. So the best way is to run a STUN/TURN server, and to stream your media through it using the MediaStream API.

DOMException when playing audio with blob as source

Whenever I run this code
var blob = new Blob(["ninja.mp3"], {type:"audio/mp3"});
var audio = new Audio(URL.createObjectURL(blob));
audio.play().catch(err => console.log(err));
I am given the following error
DOMException index.html:3
I expect it to play the audio file ninja.mp3 but instead I'm faced with this error. Any help would be greatly appreciated.
When you do
var blob = new Blob(["ninja.mp3"], {type:"audio/mp3"});
What you just created is a Binary file in your browser's memory which holds the USVString ninja.mp3, and for which the browser will send a Content-Type: audio/mp3 header in some network actions.
Id est, you just created an UTF-8 text file. And yes, the MediaElement is not able to read that.
var blob = new Blob(["ninja.mp3"], {type:"audio/mp3"});
// read as text
new Response(blob).text().then(console.log);
For a comparison, here is what a real mp3 file looks like when read as text:
fetch("https://dl.dropboxusercontent.com/s/agepbh2agnduknz/camera.mp3")
.then(resp => resp.text())
.then(console.log)
Blob constructor doesn't expect an URL, but a list of Blob parts (which are either USVStrings, Blobs or ArrayBuffers), but in no way will it ever fetch anything.
So what you want seems to be as simple as
var audio = new Audio("ninja.mp3");
audio.play().catch(console.log);
But if one day you need to build a Blob (which you don't now), then be sure that what you pass in the Blob() constructor is actually the binary content of your file.
The DOMException interface represents an abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API. This is basically how error conditions are described in web APIs.
I think you call the method wrongly. Pls Check it.

Read a local xml file to a string in Javascript

I'm surprised to see that this is hard to do, but i haven't found a single way to do that.
Basically i have a directory that contains:
--index.html
--script.js
--file.xml
And i want to read the content of the file.xml to a JS string for parsing.
The only method i found of doing so was by using a synchronous xmlhttp object which is disabled by default in my browser.
Is there another (preferably easy) way of reading a file to a string in js?
So, I might be a little late to the party, but this is to help anybody else who's been ripping his/her hair out looking for a solution to this.
First of all, CORS needs to be allowed in the browser if you're not running your HTML file off a server. Second, I found that the code snippets most people refer to in these kind of threads don't work for loading local XML-files. Try this (example taken from the official docs):
var xhr = new XMLHttpRequest();
xhr.open('GET', 'file.xml', true);
xhr.timeout = 2000; // time in milliseconds
xhr.onload = function () {
// Request finished. Do processing here.
var xmlDoc = this.responseXML; // <- Here's your XML file
};
xhr.ontimeout = function (e) {
// XMLHttpRequest timed out. Do something here.
};
xhr.send(null);
The method (1st arg) is ignored in xhr.open if you're referring to a local file, and async (3rd arg) is true by default, so you really just need to point to your file and then parse the result! =)
Good luck!

Saving text from website using Firefox extension, wrong characters saved

Sorry about the vague title but I'm a bit lost so it's hard to be specific. I've started playing around with Firefox extensions using the add-on SDK. What I'm trying to to is to watch a page for changes, a Twitch.tv chat window in this case, and save those changes to a file.
I've gotten this to work, every time something changes on the page it gets saved. But, "unusual" characters like for example something in Korean doesn't get saved properly. I think this has to do with encoding of the file/string? I tried saving the same characters by copy-pasting them into notepad, it asked me to save in Unicode and when I did everything worked fine. So I figured, ok, I'll change the encoding of the log file to unicode as well before writing to it. Didn't exactly work... Now all the characters were in some kind of foreign language.
The code I'm using to write to the file is this:
var {Cc, Ci, Cu} = require("chrome");
var {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
var file = FileUtils.getFile("Desk", ["mylogfile.txt"]);
var stream = FileUtils.openFileOutputStream(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND);
stream.write(data, data.length);
stream.close();
I looked at the description of FileUtils.jsm over at MDN and as far as I can tell there's no way to tell it which encoding I want to use?
If you don't know a fix could you give me some good search terms because I seem to be coming up short on that front. Since I know basically nothing on the subject I'm flailing around in the dark a bit at the moment.
edit:
This is what I ended up with (for now) to get this thing working:
var {Cc, Ci, Cu} = require("chrome");
var {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
var file = Cc['#mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
file.initWithPath('C:\\temp\\temp.txt');
if(!file.exists()){
file.create(file.NORMAL_FILE_TYPE, 0666);
}
var charset = 'UTF-8';
var fileStream = Cc['#mozilla.org/network/file-output-stream;1']
.createInstance(Ci.nsIFileOutputStream);
fileStream.init(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND, 0x200, false);
var converterStream = Cc['#mozilla.org/intl/converter-output-stream;1']
.createInstance(Ci.nsIConverterOutputStream);
converterStream.init(fileStream, charset, data.length,
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
converterStream.writeString(data);
converterStream.close();
fileStream.close();
Dumping just the raw bytes (well, raw jschars actually) won't work. You need to first convert the data into some sensible encoding.
See e.g. the File I/O Snippets. Here are the crucial bits of creating a converter output stream wrapper:
var converter = Components.classes["#mozilla.org/intl/converter-output-stream;1"].
createInstance(Components.interfaces.nsIConverterOutputStream);
converter.init(foStream, "UTF-8", 0, 0);
converter.writeString(data);
converter.close(); // this closes foStream
Another way is to use OS.File + TextConverter:
let encoder = new TextEncoder(); // This encoder can be reused for several writes
let array = encoder.encode("This is some text"); // Convert the text to an array
let promise = OS.File.writeAtomic("file.txt", array, // Write the array atomically to "file.txt", using as temporary
{tmpPath: "file.txt.tmp"}); // buffer "file.txt.tmp".
It might be even possible to mix both. OS.File has the benefit that it will write data and access files off the main thread (so it won't block the UI while the file is being written).

WebSocket JavaScript: Sending complex objects

I am using WebSockets as the connection between a Node.js server and my client JS code.
I want to send a number of different media types (Text, Audio, Video, Images) through the socket.
This is not difficult of course. message.data instanceof Blob separates text from media files. The problem is, that I want to include several additional attributes to those media files.
F.e.:
Dimension of an image
Name of the image
. . .
Now I could send one message containing these informations in text form and follow it up with another message containing the blob.
I would very much prefer though, to be able to build an object:
imageObject = {
xDimension : '50px',
yDimension : '50px',
name : 'PinkFlowers.jpg'
imageData : fs.readFileSync(".resources/images/PinkFlowers.jpg")
}
And send this object as it is via socket.send(imageObject).
So far so good, this actually works, but how do I collect the object and make its fields accessible in the client again?
I have been tampering with it for a while now and I would be grateful for any ideas.
Best regards,
Sticks
Well I did get it to work using base64.
On the server side I am running this piece of code:
var imageObject = newMessageObject('img', 'flower.png');
imageObject.image = new Buffer(fs.readFileSync('./resources/images/flower.png'), 'binary').toString('base64');
imageObject.datatype = 'png';
connection.send(JSON.stringify(imageObject));
The new Buffer() is necessary to ensure a valid utf encoding. If used without, Chrome(dont know about Firefox and others) throws an error, that invalid utf8 encoding was detected and shuts down the execution after JSON.parse(message).
Note: newMessageObject is just an object construction method with two fields, type and name which I use.
On the client side its really straight forward:
websocketConnection.onmessage = function(evt) {
var message = JSON.parse(evt.data);
... // Some app specific stuff
var image = new Image();
image.onload = function() {
canvas.getContext("2d").drawImage(image, 0, 0);
}
image.src = "data:image/" + message.datatype + ";base64," + message.image;
}
This draws the image on the canvas.
I am not convinced, that this is practicable for audio or video files, but for images it does the job.
I will probably fall back to simply sending an obfuscated URL instead of audio/video data and read the files directly from the server. I dont like the security implications though.

Categories