XMLHttpRequest and http streaming - javascript

My goal is to read an HTTP MP3 audio stream from the browser and have access to the raw audio data.
HTML5 < audio > lets me easily play the stream, but, as far as I know, does not grant access to the raw audio data. It just plays it.
JS XMLHTTPRequest can download files through HTTP and process the raw audio data. It seems to be a good candidate, but it suffers from a limitation: it does not grant access to the binary data until the download is finished (readystate = 4). In my case, the stream is unlimited, so the readystate stays permanently at 3 and the XHR response is null (this behavior is detailed in the mozilla documentation). Note that the cross-origin policy of the server I am connecting to is Access-Control-Allow-Origin: *
Code sample that works for local regular files, but not for streams. I get a null pointer exception at request.response.length
request = new XMLHttpRequest();
//request.open('GET', 'test.mp3', true);
request.open('GET', 'http://domain.com/stream.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function() {
console.log("request onload");
var audioData = request.response;
audioCtx.decodeAudioData(audioData,
function(buffer) { myBuffer = buffer; source.buffer = myBuffer; },
function(e){"Error with decoding audio data" + e.err}
);
}
request.onreadystatechange = function() {
console.log("ready state = " + request.readyState);
console.log(request.response.length);
}
request.send();
Does anybody know alternatives or workarounds to those options, so that the raw binary packets can be read while downloading the stream?
Note that I don't have control on the server. It's an icecast http stream.
Also, on the browser side, I'd like to avoid using Flash.
Thank you
Edit: to clarify possible cross-origin questions, the JS is run on a page hosted in a localhost server.

The following workaround worked:
As stated in MDN https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data, it is possible to override the MIME type of http request, setting it to custom, and call responseText.
function load_binary_resource(url) {
var req = new XMLHttpRequest();
req.open('GET', url, false);
//XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
req.overrideMimeType('text\/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
return req.responseText;
}
The point is that req.responseText does not suffer from the same limitation of req.response. It is not null in the state readystate=3.
Then, the binary responseText is accessed with
var filestream = load_binary_resource(url);
var abyte = filestream.charCodeAt(x) & 0xff; // throw away high-order byte (f7)
A significant drawback is that req.responseText keeps growing as the stream is downloaded. The request should be reset from time to time to avoid excessive RAM consumption.

Related

Javascript delete blobs from xmlhttprequest

I have a web app that pre-loads videos by requesting blobs and creating blob urls.
// Get video Blob URL
const url = 'https://www.some-video-url.com/video.mp4';
const xhr = new XMLHttpRequest();
xhr.open('GET', sourceUrl, true);
xhr.responseType = 'blob';
xhr.onprogress = () => {console.log('downloading')};
const blobUrl;
xhr.onload = () => {
blobUrl = URL.createObjectURL(req.response);
console.log('done');
};
xhr.send();
...
// After using video
URL.revokeObjectURL(blobUrl);
I would send such requests multiple times. It work fine in the beginning, however, when I send too many of these requests I get this error.
VM1853:10 GET https://www.some-video-url.com/video.mp4 net::ERR_FAILED 200
I am running this on Chrome and it here is my hypothesis.
According to this documentation it seems like Blob storage limit for chrome is 10% of total disk space. I confirmed this by sending xhr request to GET a 2 GB file until I hit the error. It got an error when I sent request for 20 of the 2GB files. It seems like the blobs are not garbage collected because it is not properly de-referenced. This mysterious reference seems to be related to the xhr.
In my application, I would like to
1. Start downloading videos in the background
2. Client watches a video
3. When the video playback is done, remove the video
Is there a reliable way to execute step 3? I would like the Blob to be deleted after its use.

Post complex data using ajax and open returned PDF in new window

I need to use JQuery ajax to post a complex and sensitive data object (nested objects, arrays, and Personally Identifiable Information) to my server, where a PDF is generated and returned to the client. The client browser then should open the PDF in a new window.
Because of the nature of the data the request neither can nor should be an encoded URL - it must include the data as a JSON body.
The other questions/answers on this subject did not solve the problem in my case or do not do so completely.
Solution
POST with the data in the body as JSON.
Set the expected Content-Type of the response to arraybuffer (on the client and server).
When the request has complete successfully, convert the response to a Blob.
Create an object url to the Blob and open it in a new window.
Notes
JQuery ajax does not support the arraybuffer Content-Type so the base JavaScript xhr must be used (if you don't have any other options).
Internet Explorer has its own functionality for handling and displaying Blob's, so a special case is needed.
Supported browsers does not include IE9
Code
RequestPdf = function (url, data) {
var request = new XMLHttpRequest(), file, fileURL;
request.open("POST", url);
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
request.responseType = "arraybuffer";
request.send(data);
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
file = new Blob([request.response], { type: 'application/pdf' });
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // IE
window.navigator.msSaveOrOpenBlob(file);
} else {
fileURL = URL.createObjectURL(file);
window.open(fileURL);
}
}
};
};

Get image data from another domain with AJAX request

I am trying to get binary data of an image from another domain with an AJAX request. I tried various methods, but there was no working solution. I found some code on the internet that looked good, but even with this calls I get errors.
What do I wrong? Is there a standardized way to do this?
Here is what I tried until now:
var request = this.createCORSRequest('GET', 'http://url/to/image.png');
request.onload = function () {
var text = request.response;
};
request.onerror = function (error) {
alert('Woops, there was an error making the request.');
};
request.send();
private createCORSRequest(method, url) {
var xhr: XMLHttpRequest = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
var xdhr = new XDomainRequest();
xdhr.open(method, url);
} else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
I even found this solution without ajax here on stackoverflow, but it does not work for me:
Asynchronously load images with jQuery
Here a screen of the properties the error event contains:
My goal is to get the binary of an image from a url which I get from an atom feed . I need the binaries to copy the picture to MS SharePoint.
You cannot get data from another domain unless :
the remote server allows it using CORS
you run your browser in an unsafe mode.
Reason : otherwise site A would be able to (maliciously) read the user data from site B
You must add headers to the method to allow cross domain request.
For example, if you are trying to get data from www.example.com/main.php , then you must add headers to allow those method to be called from different domain.

File API - HTML5 App to store videos

I'd like to know if HTML5 API may fit this use case:
some videos are present on a public server (say http://videosanbox.me/video.mpg)
the JS/html5 app should store the videos locally in order to be able to play them also off-line (videos are public, there are no security
warnings)
In my initial tests I am unable to go past the "No 'Access-Control-Allow-Origin'" error.
In my understanding the following script should:
request with a get the content located at the given URL
prepare 1Mb file somewhere (I assume I'll have other errors here, but I'll get there when I'll see them:))
for now I'm interested in understanding why this error is happening, wouldn't it be normal for a client (a mobile browser) to query for resources which are not already on it?
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://videosanbox.me/video.mpg', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
fs.root.getFile('video.mpg', {create: true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) { alert('writing'); };
writer.onerror = function(e) { alert('error'); };
var blob = new Blob([xhr.response], {type: 'video/mpg'});
writer.write(blob);
}, onError);
}, onError);
}, onError);
};
xhr.send();
onError is just doing something in case of error: function onError(e) {console.log('Error', e);}
Solution 1
On the server side, you need this header to be passed:
header('Access-Control-Allow-Origin: *');
Unfortunately, that may not be possible if you don't have control over videosanbox.me. If not, contact them and see if they're willing to allow this. If not, don't worry, there's a second solution:
Solution 2
Create a web page on your own server and allow cross site scripting (but use security to restrict who can use this page). In that page's code, it will take the request and open an HTTP connection to http://videosanbox.me, retrieve the mpg file and spit it back to the PhoneGap app as a Blob. Your PhoneGap would connect to this page (on your server) via Ajax instead of http://videosanbox.me.

Javascript binary file download, and ajax file POST upload in Chrome Extension

I'm writing a chrome extension content script which will embed itself on some pages, and when there are certain file type links (.doc, .torrent, etc) it will download that file, and then do a file POST to a python web server which will save that file. The python server is working, and handles a normal multipart/form-data POST request, and successfully saves the file when I use the html interface I wrote for it.
I have javascript downloading the file properly:
var req = new XMLHttpRequest();
req.open('GET', 'http://foo.com/bar.torrent', false);
req.overrideMimeType('text/plain; charset=x-user-defined');
req.send(null);
if (req.status != 200) return '';
var response = req.responseText;
And then when I try to create a POST request and upload it
// Define a boundary, I stole this from IE but you can use any string AFAIK
var boundary = "---------------------------7da24f2e50046";
var xhr = new XMLHttpRequest();
var body = '--' + boundary + '\r\n'
// Parameter name is "file" and local filename is "temp.txt"
+ 'Content-Disposition: form-data; name="upfile";'
+ 'filename="temp.torrent"\r\n'
// Add the file's mime-type
+ 'Content-type: application/bittorrent\r\n\r\n'
+ response + '\r\n';
//+ boundary + '--';
xhr.open("POST", "http://python.server/", true);
xhr.setRequestHeader(
"Content-type", "multipart/form-data; boundary="+boundary
);
xhr.onreadystatechange = function ()
{
if (xhr.readyState == 4 && xhr.status == 200)
alert("File uploaded!");
}
xhr.send(body);
It thinks that it uploaded successfully, but when I try to open the file it says the data is corrupted. I think this is some kind of encoding issue, but I'm not 100% sure.
Any thoughts would be very helpful.
Your upload method does not work, because all binary characters are encoded as UTF-8. I posted the explanation and solution in an answer at this question.
In your case, you don't need to manually create the post data. Request the initial data in a smart way, and use the FormData object to post the binary data. For instance:
var x = new XMLHttpRequest();
x.onload = function() {
// Create a form
var fd = new FormData();
fd.append("upfile", x.response); // x.response is a Blob object
// Upload to your server
var y = new XMLHttpRequest();
y.onload = function() {
alert('File uploaded!');
};
y.open('POST', 'http://python/server/');
y.send(fd);
};
x.responseType = 'blob'; // <-- This is necessary!
x.open('GET', 'http://foo.com/bar.torrent', true);
x.send();
Note: I replaced false with true at the initial request. Avoid using synchronous XMLHttpRequest when it's also possible to asynchronously create the request.
If you don't understand the answer, here are more examples with thorough explanations:
XMLHttpRequest: Multipart/Related POST with XML and image as payload - FormData is not used, but the post data is manually created instead.
Upload a File in a Google Chrome Extension - A sample Chrome extension which uses Web Workers (with a FormData polyfill) to upload files
Google chrome rehost image extension - Scrapes an image from the page, and upload the image to imgur using a Chrome extension.

Categories