Working with MP3s, ID3, and PhoneGap/Cordova

This post is more than 2 years old.

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.

device-2015-04-29-170149

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.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Kevin Brown posted on 5/18/2015 at 8:16 PM

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?

Comment 2 (In reply to #1) by Raymond Camden posted on 5/18/2015 at 9:05 PM

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?

Comment 3 (In reply to #2) by Kevin Brown posted on 5/18/2015 at 9:09 PM

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.

Comment 4 (In reply to #3) by Raymond Camden posted on 5/18/2015 at 9:22 PM

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)

Comment 5 (In reply to #4) by Kevin Brown posted on 5/18/2015 at 9:42 PM

no I wrapped it such that they would get read serially.
http://pastie.org/10195568

Comment 6 (In reply to #5) by Raymond Camden posted on 5/18/2015 at 9:44 PM

Ah well shoot - not sure what to suggest then.

Comment 7 by driekwartappel posted on 9/8/2015 at 6:32 AM

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?

Comment 8 (In reply to #7) by Raymond Camden posted on 9/9/2015 at 2:33 AM

If you can get it with Java, then you can write a plugin for it for Cordova. :)

Comment 9 (In reply to #8) by driekwartappel posted on 9/10/2015 at 11:34 AM

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

Comment 10 by hinata kagerou posted on 1/3/2017 at 5:05 AM

why its take so long?
are there anothers method for fast get mp3 id3 tags for multiple mp3?

Comment 11 (In reply to #10) by Raymond Camden posted on 1/3/2017 at 1:54 PM

It depends I guess. If you have a large number of MP3s it's going to take time to read them.

Comment 12 (In reply to #11) by hinata kagerou posted on 1/5/2017 at 1:48 AM

3 mp3 with 9 sec

Comment 13 (In reply to #12) by Raymond Camden posted on 1/5/2017 at 2:09 AM

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.

Comment 14 (In reply to #13) by hinata kagerou posted on 1/5/2017 at 2:21 AM

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

Comment 15 (In reply to #14) by Raymond Camden posted on 1/5/2017 at 2:42 AM

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.

Comment 16 (In reply to #15) by hinata kagerou posted on 1/5/2017 at 3:14 AM

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

Comment 17 (In reply to #16) by Raymond Camden posted on 1/5/2017 at 3:23 PM

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.

Comment 18 (In reply to #12) by hinata kagerou posted on 1/8/2017 at 1:46 AM

hi sir
i'm still analyze i will post if found something :)

Comment 19 by Deadboy posted on 9/13/2017 at 5:08 PM

Thanks! Very useful tutorial.

Comment 20 (In reply to #1) by Deadboy posted on 9/13/2017 at 5:08 PM

Hi,
How did you use the slice method on the mp3 files?