I am trying to make a nodejs website that will return the length in seconds of any audio file a user chooses. So far I have it working with mp3, wav, and flac files. But it doesn’t work for all .m4a or .aif files
The code for my HTML page with javascript is below:
choose audio file to get length: <input style="cursor: pointer;" type="file" id="file" multiple="multiple" /> <script> //when files are selected: $("#file").change(async function (e) { console.log('file(s) selected') //get files var files = e.currentTarget.files; //get number of files var numberOfFiles = files.length; //for each file for (i = 0; i < numberOfFiles; i++) { console.log(`songs[${i}].type=`, files[i].type) //get file length let songLength = await getSongLength(files[i]); console.log('songLength=', songLength) } }); //recieve audio file, return length function getSongLength(song) { return new Promise(function (resolve, reject) { console.log('getSongLength() begin setup') //create objectURL and audio object for ssong objectURL = URL.createObjectURL(song); mySound = new Audio([objectURL]) console.log('getSongLength() end setup') //when song metadata is loaded: mySound.addEventListener("canplaythrough", function (e) { console.log('getSongLength() canplaythrough') var seconds = e.currentTarget.duration; resolve(seconds) }); }); } </script>
I gathered 6 different files for testing, and after running them on my above code have found out the following results:
- aif: not working
- flac: working
- m4a_file: not working
- m4a_file_from_comments_below:not working
- mp3: working
- wav: working
my test files for download: https://easyupload.io/m/la9xro
It seems like when I input my m4a file sample_M4A_file
that it hangs inside the getSongLength()
function and never enters the .addEventListener("canplaythrough"
function, is there any alternative I can use to consistently get the duration in seconds for every audio file?
Advertisement
Answer
It’s because your browser doesn’t support Apple Lossless codecs. If you try in Safari, it will work.
If you attach an error
event to your media element, you’ll see it fires.
There is no real universal solution, apart from using an heavy machinery like mediainfo.js (2.4MB+) which should support most formats.
const input = document.querySelector( "input" ); input.onchange = async (evt) => { const mediainfo = await new Promise( (res) => MediaInfo(null, res) ); const file = input.files[ 0 ]; const getSize = () => file.size; const readChunk = async (chunkSize, offset) => new Uint8Array( await file.slice(offset, offset + chunkSize).arrayBuffer() ); const info = await mediainfo.analyzeData(getSize, readChunk); // assumes we are only interested in audio duration const audio_track = info.media.track.find( (track) => track[ "@type" ] === "Audio" ); console.log( audio_track.Duration ); };
<script src="https://unpkg.com/mediainfo.js@0.1.4/dist/mediainfo.min.js"></script> <input type="file">
Ps: when using the media element solution, there is no need to wait for canplaythrough
, just wait for loadedmetadata
, and if you get Infinity, try this hack.