How to read large video files in JavaScript using FileReader? - javascript

I want the user of my website to be able to upload a potentially large video file to it using HTML‘s file input. The video should than be processed and played locally in the user‘s browser.
let fileInput = document.createElement("INPUT");
fileInput.setAttribute("type", "file");
fileInput.onChange = onFileSelected;
To read the video file uploaded by the user, I wanted to use a File Reader like this:
function onFileSelected(e) {
// The file uploaded by the user:
let file = e.target.files[0];
// Create a file reader:
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
video.src = e.target.result;
}
}
However, when I uploaded really large video files (≈300 MB), e.target.result was not a URI to the video file, like I expected, but an empty string.
How can I read very large video files using File Reader in JavaScript?

The FileReader class in JavaScript contains multiple methods to read files:
readAsText(): This reads a file and returns its content as text. Suitable for small text files.
readAsBinaryString(): This reads a file and returns its content as a binary string. Suitable for small files of any type.
readAsDataURL(): This reads a file and returns a Data URL referencing it. This is inefficient for large files as the file is loaded into memory as a whole before being processed.
readAsArrayBuffer(): This reads a file and returns an ArrayBuffer containing the input file 'chopped up in smaller pieces'. This works for very large files, too.
In the question, the readAsDataURL() method is used as it is usually most convenient. However, for very large video files (and very large files in general) it does not work for the reason described above leading to an empty result. Instead, you should use readAsArrayBuffer():
let reader = new FileReader();
reader.readAsArrayBuffer(file);
Now, the file reader returns an ArrayBuffer after loading the file. In order to be able to show the video in HTML, we have to convert this buffer to a blob, that can then give us a URL to the video file:
reader.onload = function(e) {
// The file reader gives us an ArrayBuffer:
let buffer = e.target.result;
// We have to convert the buffer to a blob:
let videoBlob = new Blob([new Uint8Array(buffer)], { type: 'video/mp4' });
// The blob gives us a URL to the video file:
let url = window.URL.createObjectURL(videoBlob);
video.src = url;
}

Related

How to view blob data directly using html

How do you view a blob data that is from a sql database file (.db .db3 and others) and view it on web browser by only using a single html file? The blob data are probably meant to be seen as an image file (jpg, png and others)
Let's say I have a blob data like this:
du�� C�BVwv�q8q7k�1�H�asfdasdfasdf�#s;47sk"as��'7hib-�3$asdffdsfa�a�����U�����P������
And I want to put that single blob data directly (without calling the database file, just using the value of the blob itself) inside a html file so I can directly open it from my browser without installing other software or setting up a local server inside my computer.
I'm sorry if I explain this weirdly, I rarely code, I honestly don't know anything about sql or that server thingamajig, I just want to view the blob file.
You could use Blob. Here I construct a blob and then turn it back into a string that I insert in the document.body.
var array = ['<p>Hello World!</p>'];
var blob = new Blob(array, {type : 'text/html'});
const reader = new FileReader();
reader.addEventListener('loadend', e => {
document.body.innerHTML += e.target.result;
});
reader.readAsText(blob);
And I guess that the Filereader can also read a file if needed.

Decoded Binary Audio from DataURL not writing properly to .mp3 file

I'm using the getUserMedia API to allow a user to record a short Audio clip on the client. After finishing the recording, I initialize a FileReader, create a Blob from all the Audio chunks, and read the reader of the blob as a DataURL. That gets appended as Text to a webform that's then sent to a Rails5 API, where I'm having a very difficult time converting the Binary Audio into a working .mp3 file.
Here is what the code looks like:
When the recorder goes inactive, the chunks are collected and used to instantiate a new Blob with the type 'audio/mpeg'.
var chunks = [];
if (recorder.state == 'inactive') {
var blob = new Blob(chunks, { type: 'audio/mpeg' });
App.mediaStream.encodeBase64(blob);
}
The Blob is then passed to a function that creates a base64 encoded DataURL.
encodeBase64: function(blob) {
var reader = new FileReader();
reader.onloadend = function() {
App.mediaStream.appendToForm(reader.result);
};
reader.readAsDataURL(blob);
},
The result from the reader is a dataURL that follows this pattern, data:[type];base64,[data] - so it's a String that looks like:
"data:audio/mpeg;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQRChYECGFOAZwH/////////FUmpZpkq17GDD0JATYCGQ2hyb21lV0GGQ2hyb21lFlSua7uuudeBAXPFh+bmBbc......."
I'm appending this DataURL to a form, that sends it to my Rails 5 API along with some other data. Here is where I'm running into trouble. On the server side, I have a ruby method that parses the dataURL, and writes it to a file as so:
def binary_to_audio_file(dataURL)
data = dataURL.split(',').last
filename = SecureRandom.hex
File.open("public/#{filename}.mp3", 'wb') do |f|
f.write Base64.decode64(data)
end
end
This is successfully creating a new file in the specified directory that has the decoded audio binary written to the file. That file can be opened by QuickTime, Itunes, or any other media play. It even plays for the same duration that the recording lasted. HOWEVER, there is NO sound.
I promise my speakers are not turned off... any hints?

Upload javascript blob wav file and compress

I'm using Recorderjs lib to record audio from browser, so, I have the wav blob on some variable.
To upload to the server, i'm using FormData converting blob to base64 string:
var reader = new FileReader();
reader.onload = function (event) {
toUpload.append("audio", event.target.result);
$.ajax(ajaxConfig); // my ajax call
}
reader.readAsDataURL(myBlob);
When the file reaches to the server, i'm using NAudio.Lame to compress to mp3 format (recordjsmp3 is very slow, want to do it on server-side):
using (var ms = new MemoryStream(myFile))
using (var wav = new WaveFileReader(ms))
.net throws the exception:
FormatException: Not a WAVE file - no RIFF header
Ok, let's try to save the file on disk to play on Windows Media Player:
File.WriteAllBytes("c:\\test.wav", myFile);
When I try to play the file saved on disk, media player says that file is corrupted.
My endpoint is Asp.Net web api, and i'm doing this to take the byte array:
var multipart = await Request.Content.ReadAsMultipartAsync();
multipart.Contents.SingleOrDefault(f => f.Headers.ContentDisposition.Name.ToLower().Contains("audio")).ReadAsByteArrayAsync();
What am I missing?

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

Convert image from readAsDataURL to readAsBinaryString

Using the filereader API it is possible to show a preview of the file, by reading the file with readAsDataURL
What I am trying to do is:
The user selects a file
A preview is shown, so that the user has some feedback.
If the user is satisfied, he submits the data to the backend.
Implementing step 3 can be done by re-reading the file with readAsBinaryString, but this looks problematic because the data could have disappeared or changed on disk. So What I would like is to convert the data returned from readAsDataURL to the format returned by readAsBinaryString. How can I do this?
Another alternative would be to submit the data to the backend as returned by readAsDataURL, but I would like to avoid that, since that would require special handling on the backend in my case.
Like CBroe said, you dont need to read the file twice.
JS :
handleFileSelectThumbFile(evt){
var files = evt.target.files;
var file = files[0];
// You can get the mime type like this.
var thumbMIME = files[0]['name'].split('.').pop();
if (files && file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
// Split the readerEvt.target.result by a ','.
// You can send the binaryString variable to the server.
// Its base64 encoded already.
var binaryString = readerEvt.target.result.split(',')[1];
// Set the image preview to the uploaded image.
$('.img-preview').prop('src', readerEvt.target.result);
}.bind(this);
reader.readAsDataURL(file);
}
}
HTML :
<input type="file" onChange={this.handleFileSelectThumbFile} required/>
<img src='http://placehold.it/300' class='img-preview'/>
You can read the MIME type from the first part of readerEvt as well. Look at CBroe's comment above.

Categories