converting video src to a single-session blob url - javascript

Let's say I have a video element on my website:
<video src="/video.mp4" controls="" id="video"></video>
How do I go about protecting the original source file (/video.mp4) by converting it to a single-session Blob URL?
I have seen a few posts stating that it needs to be done with JavaScript, though none of them actually expand on the necessary details explaining how to do it (or where you can find out how).
So, what is the best approach for something like this?

Here is a quick and dirty example. Hope it helps.
Make sure to go over the docs of all of the methods being used and check their browser support. This will not protect your video from being downloadable though.
// Request the video using a new XMLHttpRequest() with the
// responseType set to blob.
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(){
var reader = new FileReader();
reader.onloadend = function(){
// Pass this string to atob to decode the base-64 encoded string to a string
// representing each byte of binary data.
var byteCharacters = atob(reader.result.slice(reader.result.indexOf(',') + 1));
// Now you can create an array of byte values using charCodeAt and looping
// over the byte string.
var byteNumbers = new Array(byteCharacters.length);
for(var i = 0; i < byteCharacters.length; i++){
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
// Pass the resulting array to Uint8Array to create a typed array of
// 8-bit, unsigned integers.
var byteArray = new Uint8Array(byteNumbers);
// This can then can be passed to the Blob constructor.
var blob = new Blob([byteArray], {type: 'video/ogg'});
// Now that you have a blob, you can pass it to the createObjectURL method.
var url = URL.createObjectURL(blob);
// The resulting URL can be attached to the src attribute of your video.
document.getElementById('video').src = url;
}
// Pass the response to the FileReader using readAsDataURL.
// This will give you a base-64 encoded string representation of the file.
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Roaring_Burps.ogg');
xhr.send();
<video controls="" id="video"></video>

To make the Blob URL, I found this answer. This will load large files much faster than DavidDomain's answer (which took unacceptably long for my case of a >100MB video file). Although, I believe that this will download the whole video into the browser's memory, and embed the data into the DOM, so larger files might still cause other performance issues.
Why do you want to "[protect] the original source files location" of the video? If something finds the video's location and requests the video file, then that file should be served: that's a server's job.
AFAIK it's practically impossible to load a video file without exposing the URL required to obtain that video file. (It should be technically possible to embed it into the DOM server-side, but that would force the entire video to be loaded before the page shows anything, and would be unusable)

Related

Is there a way to stream data into a blob (or generate a giant blob)

Checking MDN I see there used to be BlobBuilder and that I could call blobBuilder.append to continue adding data to a blob but according to MDN BlobBuilder is deprecated in favor of the Blob constructor. Unfortunately the Blob constructor requires all data in memory at construction time. My data is too large to be in memory at construction time. Looking at the File API see nothing there either.
Is there a way to generate large data client side and put it in a blob? For example say I wanted to render a 16k by 16k image. Uncompressed that's a 1gig image.
I have an algorithm that can generate it 1 or a few scan lines at a time but I need way to write those scan lines into a file/blob and then when finished I can use the standard way to let the user download that blob but, I can't seem to find an API that let's me stream data into a blob.
The only thing I can think of is apparently I can make a Blob from Blobs so I suppose I can write each part of the image to a separate blob and then send all the blobs to another blob to get a big blob.
Is that the only solution? Seems kind of um .... strange. Though if it works then ¯\_(ツ)_/¯
Someone voted to close as they don't understand the question. Here's another explanation.
Write 4 gig to a blob
const arrays = [];
for (let i = 0; i < 4096; ++i) {
arrays.push(new Uint8Array(1024 * 1024)); // 1 meg
}
// arrays now holds 4 gig of data
const blob = new Blob(arrays);
The code above will crash because the browser will kill the page for using too much memory. Using BlobBuilder I could have done something like
const builder = new BlobBuilder();
for (let i = 0; i < 4096; ++i) {
const data = new Uint8Array(1024 * 1024); // 1 meg
builder.append(data);
}
const blob = builder.getBlob(...);
That would not have run out of memory because there is never more than 1meg of data around. The browser can flush the data being appended to the BlobBuilder out to disk.
What's the new way to achieve writing 4 gig to a blob? Is it only writing lots of small blobs and then using those to generate a larger one or is there some more traditional way where traditional means steaming into some object/file/blob/storage.
As you know, the data that the blob will contain must be ready to pass to the constructor. Let us take the example from MDN:
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
var oMyBlob = new Blob(aFileParts, {type : 'text/html'});
Now, we have two options:
We can append data to the array, and then convert it to a blob:
var aFileParts = ['<a id="a"><b id="b">hey!</b></a>'];
aFileParts.push('<p>How are you?</p>');
var oMyBlob = new Blob(aFileParts, {type : 'text/html'});
Alternatively, we can use blobs to create the blob:
var oMyOtherBlob = new Blob([], {type: 'text/html'});
oMyOtherBlob = new Blob([oMyOtherBlob, '<a id="a"><b id="b">hey!</b></a>'], {type : 'text/html'});
oMyOtherBlob= new Blob([oMyOtherBlob, '<p>How are you?</p>'], {type : 'text/html'});
You may build your own BlobBuilder encapsulating that... given that appending to an array seems to lead you to run out of memory, let us encapsulate the second option:
var MyBlobBuilder = function() {
var blob = new Blob([], {type: 'text/html'});
this.append = function(src)
{
blob = new Blob([blob, src], {type: 'text/html'});
};
this.getBlob = function()
{
return blob;
}
};
Note: tested with your code (replaced BlobBuilder with MyBlobBuilder), did not run out of memory on my machine. Windows 10, Chrome 67, 8 GB Ram, Intel Core I3 - 64 bits.

Opening a file with C# as a BLOB, and downloading with JS as a BLOB

I have a PDF on my .NET Core server, which I need to somehow send across the wire as a BLOB, so that my JS AJAX request can convert it back to a PDF so it can be downloaded.
The reason for the indirection is because the file comes from my API, which is only accessed through AJAX. Due to the need for a Bearer token, I can't just use a form behind the scenes, as there's no cookie for the site created. (Weird, I know, but that's how it is presently, and I'm not looking to change that part)
So, on my C# side, I've tried several variations, shown below. ApiResponse is just a container I use that holds a bool and a string (named message) so I can tell if the request was good or not.
These are what I've been trying
return new ApiResponse(true, File.ReadAllText(path));
return new ApiResponse(true, Convert.ToBase64String(File.ReadAllBytes(path)));
return new ApiResponse(true, JsonConvert.SerializeObject(File.ReadAllBytes(path)));
And on the JS side, in the same order to parse it back out, I have:
// Get the response in object form, since it's sent as an ApiResponse
const response = JSON.parse(xmlhttp.response);
const text = response.message;
const text = atob(response.message)
const text = JSON.parse(response.message)
I've also tried things like
const text = atob(JSON.parse(response.message))
Then, with the text I'm doing this:
const blob = new Blob([text], {type: "application/pdf"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
And this does correctly generate a file that's downloaded. However, that file is not valid: it's corrupted.
I'm pretty much stuck at this point, and I haven't been able to find something that goes from start to finish using this method to download files with Javascript. It's either the back side, or the front side, but never tied together.
So, how can I successfully send a PDF BLOB across the wire, and recreate it on the front end so it can be downloaded?
The easy answer to how to do the convert is don't.
Every modern browser supports base64 encoding natively, so there's no need to convert the data back to a BLOB before putting it into download.
Thus, the end code is:
const a = document.createElement("a");
a.href = "data:application/pdf;base64," + response.message;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();

How to download a file generated with JavaScript, avoiding automatic opening of Chome?

I am developing a JavaScript little webmail.
I receive from the server a Base64-encoded string, that represents a file (it could be whatever type). I decode the string, a map it to a Uint8Array, and with it, I generate a Blob object with I create a data URI with
FileReader.readAsDataURL(blob)
Until here is pretty straightforward, but I am having problem with the download part.
I put the DataURI in
window.open(dataURI)
But chrome opens a new window and display my image, or my text. But I need to avoid this behaviour, and download the file instead.
I have red that this could be done with Content-Disposition "attachment" but I am not sure if it is my case, because I am generating the file from a string from the server.
Anyone who can help me understand?
Did you try to use "saveAs" ?
saveAs(blob, "hello.zip");
In the case you need wide browser support you could try polifill. More information
I am pretty sure you can set the type of the blob
var blob = new Blob(["Hello world!"], { type: "application/download" });
Edit:
without FileSaver.js:
var blob = new Blob(["Hi stack"], {type: 'application/download'});
var reader = new FileReader();
reader.onloadend = function(e) {
window.open(reader.result);
}
reader.readAsDataURL(blob);
Edit:
Documentation and browser support information ("Browser compatibility" tab):
FileReader
Blob

How to get duration of video when I am using filereader to read the video file?

I am trying to upload a video to server, and on client end. I am reading it using FileReader's readAsBinaryString().
Now, my problem is, I don't know how to read duration of this video file.
If i try reading the file, and assigning the reader's data to a video tag's source, then none of the events associated to the video tag are fired. I need to find the duration of file uploaded on client end.
Can somebody please suggest me something?
You can do something like this for that to work:
read the file as ArrayBuffer (this can be posted directly to server as a binary stream later)
wrap it in a Blob object
create an object URL for the blob
and finally set the url as the video source.
When the video object triggers the loadedmetadata event you should be able to read the duration.
You could use data-uri too, but notice that browsers may apply size limits (as well as other disadvantages) for them which is essential when it comes to video files, and there is a significant encoding/decoding overhead due to the Base-64 process.
Example
Select a video file you know the browser can handle (in production you should of course filter accepted file types based on video.canPlayType()).
The duration will show after the above steps has performed (no error handling included in the example, adjust as needed).
var fileEl = document.querySelector("input");
fileEl.onchange = function(e) {
var file = e.target.files[0], // selected file
mime = file.type, // store mime for later
rd = new FileReader(); // create a FileReader
rd.onload = function(e) { // when file has read:
var blob = new Blob([e.target.result], {type: mime}), // create a blob of buffer
url = (URL || webkitURL).createObjectURL(blob), // create o-URL of blob
video = document.createElement("video"); // create video element
video.preload = "metadata"; // preload setting
video.addEventListener("loadedmetadata", function() { // when enough data loads
document.querySelector("div")
.innerHTML = "Duration: " + video.duration + "s"; // show duration
(URL || webkitURL).revokeObjectURL(url); // clean up
// ... continue from here ...
});
video.src = url; // start video load
};
rd.readAsArrayBuffer(file); // read file object
};
<input type="file"><br><div></div>
you can do something like below, the trick is to use readAsDataURL:
var reader = new FileReader();
reader.onload = function() {
var media = new Audio(reader.result);
media.onloadedmetadata = function(){
media.duration; // this would give duration of the video/audio file
};
};
reader.readAsDataURL(file);
Fiddle Demo

encode/decode image with base64 breaks image

I am trying to encode and decode an image. I am using the FileReader's readAsDataURL method to convert the image to base64. Then to convert it back I have tried using readAsBinaryString() and atob() with no luck. Is there another way to persist images without base64 encoding them?
readAsBinaryString()
Starts reading the contents of the specified Blob, which may be a
File. When the read operation is finished, the readyState will become
DONE, and the onloadend callback, if any, will be called. At that
time, the result attribute contains the raw binary data from the file.
Any idea what I'm doing wrong here?
Sample Code
http://jsfiddle.net/qL86Z/3/
$("#base64Button").on("click", function () {
var file = $("#base64File")[0].files[0]
var reader = new FileReader();
// callback for readAsDataURL
reader.onload = function (encodedFile) {
console.log("reader.onload");
var base64Image = encodedFile.srcElement.result.split("data:image/jpeg;base64,")[1];
var blob = new Blob([base64Image],{type:"image/jpeg"});
var reader2 = new FileReader();
// callback for readAsBinaryString
reader2.onloadend = function(decoded) {
console.log("reader2.onloadend");
console.log(decoded); // this should contain binary format of the image
// console.log(URL.createObjectURL(decoded.binary)); // Doesn't work
};
reader2.readAsBinaryString(blob);
// console.log(URL.createObjectURL(atob(base64Image))); // Doesn't work
};
reader.readAsDataURL(file);
console.log(URL.createObjectURL(file)); // Works
});
Thanks!
After some more research I found the answer from here
I basically needed to wrap the raw binary in an arraybuffer and convert the binary chars to Unicode.
This is the code that was missing,
var binaryImg = atob(base64Image);
var length = binaryImg.length;
var ab = new ArrayBuffer(length);
var ua = new Uint8Array(ab);
for (var i = 0; i < length; i++) {
ua[i] = binaryImg.charCodeAt(i);
}
The full sample code is here
URL.createObjectURL expects a Blob (which can be a File) as its argument. Not a string. That's why URL.createObjectURL(file) works.
Instead, you are creating a FileReader reader that reads file as a data url, then you use that data url to create another Blob (with the same contents). And then you even create a reader2 to get a binary string from the just constructed blob. However, neither the base64Image url string part (even if btoa-decoded to a larger string) nor the decoded.binary string are vaild arguments to URL.createObjectURL!

Categories