As someone who remembers when MP3s became a de facto standard for audio files (*), I'm pretty familiar with the format used to embed metadata within them - ID3. If you've ever wondered how your favorite MP3 player displayed data about your music (artist, album, year, etc.), most likely it came from the ID3 tags embedded in the file. Almost ten years ago I even blogged about parsing them with ColdFusion. I thought it would be interesting to take a look at how ID3 parsing could be done within a PhoneGap/Cordova application.
For my testing, I decided to use an open source JavaScript ID3 project at GitHub: JavaScript-ID3-Reader. My biggest concern was performance. In the ReadMe for the project, they mention that if your web server supports the HTTP Range feature, it will only grab the bits it need. (If I remember right, the ID3 data is all at the end of the MP3 file.) Otherwise it reads in the entire file which - obviously - won't perform well. I decided to give it a shot anyway and see what my results would be.
I decided to test this on my HTC M8. I've got a few MP3s there on my SD card and wrote a quick proof of concept that would scan one hard coded directory. One cool thing about the JavaScript-ID3-Reader is that it supports FileEntry objects and you can get FileEntry objects using Cordova's File plugin. Let's take a look at my first stab at this. I'm sharing just the JavaScript as the HTML is literally just a div block for me to write crap out too. In the next post I'll be adding a proper UI to this.
document.addEventListener("deviceready", init, false);
//A hard coded folder, to keep things simple
var mp3Folder = "Music/Depeche_Mode/101_(1_of_2)/";
var result;
function init() {
window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory + mp3Folder,
function(dir) {
var reader = dir.createReader();
//read it
reader.readEntries(function(entries) {
console.log("readEntries");
console.dir(entries);
entries.forEach(function(entry) {
var name = entry.name;
console.log(name);
entry.file(function(file) {
ID3.loadTags(name,function() {
var tags = ID3.getAllTags(name);
console.log("got tags for "+name, tags);
},{
dataReader:FileAPIReader(file)
});
});
});
});
}, function(err) {
});
}
Ok, so let's take this from the top. I'm using a Cordova File plugin alias for the SD card. This is an Android only alias but as I said - I'm testing on my phone. Speaking of - I used an app to browse to one particular folder (in this case, disc one of Depeche Mode's excellent live album, 101). Once I convert that path into a directory object, it is then a simple matter of reading the entries from the directory. Once I've got the list of entries, I then loop over them and use the Javascript library's API to get the ID3 tags. If you check their docs, you can see additional options, but for the most part I just wanted all the tags so that what I did.
This promptly crashed and crapped the bed with an out of memory error. I then figured that the entry.file calls - being async - were running the ID3 parser for multiple mp3 files at the same time. I then rewrote the logic to handle the calls in order. This isn't necessarily pretty, but it worked right away:
document.addEventListener("deviceready", init, false);
//A hard coded folder, to keep things simple
var mp3Folder = "Music/Depeche_Mode/101_(1_of_2)/";
var result;
function init() {
result = document.querySelector("#results");
result.innerHTML = "Stand by, parsing MP3s...<br/>";
window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory + mp3Folder,
function(dir) {
var reader = dir.createReader();
//read it
reader.readEntries(function(entries) {
console.log("readEntries");
console.dir(entries);
result.innerHTML += entries.length + " files to process.<br/>";
var data = [];
var process = function(index, cb) {
console.log("doing index "+index);
var entry = entries[index];
var name = entry.name;
entry.file(function(file) {
ID3.loadTags(entry.name,function() {
var tags = ID3.getAllTags(name);
data.push({name:entry.name, tags:tags});
console.log("got tags for "+entry.name, tags);
result.innerHTML += "*";
if(index+1 < entries.length) {
process(++index, cb);
} else {
cb(data);
}
},{
dataReader:FileAPIReader(file)
});
});
};
process(0, function(data) {
console.log("Done processing");
console.dir(data);
//make a simple str to show stuff
var s = "";
for(var i=0; i<data.length; i++) {
s += "<p>";
s += "<b>"+data[i].tags.title+"</b><br/>";
s += "By "+data[i].tags.artist+"<br/>";
s += "Album: "+data[i].tags.album+"<br/>";
s += "</p>";
}
result.innerHTML = s;
});
});
}, function(err) {
});
}
In this version, I've used a simple counter and made a loop that calls itself until all the entries have processed. I then added simple code that writes out to the DOM the results.
So how well does it work? I fired up Chrome Remote Debug and watched it process. I'd say it takes about 1 second for each file to parse. That's not speedy - but you could easily cache these results so you aren't reparsing the MP3 on every request. You could also quickly display the file name (soandso.mp3) until you get the proper title from the ID3 info. That way a user could see the names, play the files, etc and then your code could update the display as it gets them.
In the next version of this project, I'll be adding an Ionic front end to the code and making it a bit prettier. I'll also make it more generic so it can work on iOS and Android. I'll be sharing the full source code, but I want to complete the second version before I push to my Cordova Examples repo.
* I'm old enough to remember downloading music files in... AIFF format I think... back in 96 or so. I can remember thinking how cool it was (and illegal) that I could download 5-6 meg files of - if I remember right - Journey. Oh, and it took about 15 minutes for these files to download at my college's Sun something or another computer lab. Yeah - I'm old.
Archived Comments
Hey i just wanted to thank you for posting this guide. It helped me out a lot but for the longest time my application was crashing when trying to load some ID3 tags. I discovered finally that i was able to use the file.slice method to get the last 300 bytes and this works but i have a feeling that i may be missing out on some tags. Do you know much about ID3 tag parsing? Is there any way that I might be able to read the file in chunks grabbing the tags from each for a full set?
I read the spec about ten years ago and used slice via Java to parse tags. All I can tell you is that it worked then, and if you read the spec carefully, you should be ok doing it. Any reason why you can't just use the ID3 library I used here?
I am using the ID3 library but my application would always crash after calling loadtags on the third song in a list. It does not crash if i slice it. My guess was that it occurred as a result of a file being to big but i'm not convinced that is the case because it behaves the same way on any list of mp3 files.
Are you reading them at the same time? Remember I had that issue with my own code until I made the reads singular. (Ie one at a time)
no I wrapped it such that they would get read serially.
http://pastie.org/10195568
Ah well shoot - not sure what to suggest then.
This might be a little bit unrelated, but looks like you know what you are talking about!
I am trying to read the currently playing song using Cordova on a Android phone, similar to what they are doing here:
http://stackoverflow.com/qu...
Do you know if it is possible to read the currently playing song (default android player) with Cordova? Do you perhaps have a link to some reading material?
If you can get it with Java, then you can write a plugin for it for Cordova. :)
Awesome thats good news. Will have to learn exactly how cordova plugin works first. Will post the result here if I ever get it to work
why its take so long?
are there anothers method for fast get mp3 id3 tags for multiple mp3?
It depends I guess. If you have a large number of MP3s it's going to take time to read them.
3 mp3 with 9 sec
Could it be an older device? Unfortunately there isn't much I can do here. You would need to ping the project owner to get their thoughts on it.
im test it on samsung note 3 yes it is work well but loading is really long when you have 100 or more mp3
you can test it :
https://www.dropbox.com/s/n...
places the mp3 on /Music folder
on sdcard
Oh a 100 MP3s I'd definitely think would be a bit slow. I'm not going to test though - as I said - to me this is expected. If you think it should be quicker, again, I'd reach out to the person behind the code and see if there are any optimizations that can be made. It is an open source project.
thanks i will analyze the code too
i will tell you if i found some thing
you want to test my project?
im using this git too
https://github.com/arielfau...
this is the link :
https://www.dropbox.com/s/z...
the plugin im add in on ionic is :
syntax :
ionic plugin add plugin-name
cordova-plugin-media
cordova-plugin-file
cordova-plugin-spinner-dialog
cordova-plugin-device
cordova-plugin-console
You found something else? To be clear, I don't just take folks projects and work on them normally unless it is a paid engagement.
hi sir
i'm still analyze i will post if found something :)
Thanks! Very useful tutorial.
Hi,
How did you use the slice method on the mp3 files?