The question in the title is pretty self-explanatory. I have this site that allows the user to upload multiple files at a time. And then it will go through and retrieve the duration for each one.
getDuration(file) {
return new Promise(resolve => {
let reader = new FileReader();
reader.onload = e => {
let audio = new Audio();
audio.onloadeddata = () => {
this.duration = audio.duration;
resolve();
};
audio.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
But this, in Chrome at least, freezes up the UI pretty bad while it's doing all that processing. Is there anyway I could avoid this or go about this in a different way?
You can already start by not using a FileReader
and data:// URIs
The FileReader will read all the File from the disk into memory, with a lot of Files, that's a lot of IO and your disk (HDD or SSD) will not like it.
Then creating a data:// URL from that file is not free operation, so it will take some CPU time.
Finally, the browser will have to decode back that data: URL to raw binary before it's able to even try to decode that data as audio. Once again a few unnecessary steps here, and a lot of wasted memory.
Instead, you could simply create a blob:// URL from the File your users sent and set your <audio>
's src to that. This will be a simple pointer to the actual File on the disk, so the browser will be able to stream-decode it and append only the chunks of data it needs in memory.
To create such a blob:// URL, you have to use the URL.createObejctURL()
method.
Note that even though for Files from disk it's not that critical, you should get the good habit to revoke such blob:// URLs after you used it, because they'll blob the File from being Garbage Collected.
function getDuration(file) {
return new Promise(resolve => {
const audio = new Audio();
audio.onloadedmetadata = () => {
URL.revokeObjectURL(file)
resolve(audio.duration);
};
audio.src = URL.createObjectURL(file);
});
}
document.querySelector("input").oninput = async (evt) => {
console.log( await getDuration( evt.target.files[0] ) );
}
<input type="file">