Thursday, January 14, 2010

how long is an mp3?

I need to know the length (in seconds) of a progressive mp3 before it starts playing. As-far-as I can tell, there are only three ways of getting this information:

1) if it's explicitly stated in some sort of data feed (e.g. an XML playlist), you can, of course, grab that info and hope it's accurate.

2) you can read it from the id3 tags -- the metadata embedded in the mp3 file itself -- and hope THAT'S accurate.

3) you can guestimate length using some simple math.

Option one won't work for me, because my clients are generally not going to enter durations into a data feed.

I spent hours trying to get option two to work, emailing other developers and asking them for advice. I wasn't receiving the id3 data. Someone mentioned to me that I was likely hitting a security wall. It turns out that though a SWF can play mp3s from another domain, it can't access embedded id3 data without permission. See:

I figured this was the problem, so I added a crossdomain file and a SoundLoaderContext object to Sound.load(), but I still didn't receive any id3 data. Why? Because there wasn't id3 data in the mp3s I was using -- even though the client assured me that there was.

I realized that I can't count on there being id3 data. Even if there is, there may not be length data, as it's optional which id3 tags are included in an mp3 file (or if any are included at all).

Which lead me to try option three. I patched it together based on various similar solutions I found online:

This code runs in a loop:

if( _duration == 0 && ( bytesLoaded / bytesTotal ) > 0.1)
_duration = _sound.length / 1000 / bytesLoaded * bytesTotal;

It waits until ten percent (0.1) of the mp3 is cashed and then calculates duration. It's an estimate, because it's based on Sound.length, which is NOT necessarily the length of the whole mp3. It's the length of what's loaded so far.

And you can't simply guess the length from bytesTotal, because depending on the compression/bitrate of the mp3, the same number of bytes could mean different lengths. So the calculation takes into account the known length, the number of bytes loaded so far and the total number of bytes. (Divided by 1000 to convert from milliseconds to seconds.)

How much should I cache before I let Flash do the calculation? Is ten percent enough? (The JW Player uses ten percent.) I tried it with an mp3 which I knew to be 1 minute and 2 seconds. The guestimation was close but not perfect: 00:59 and 1:00 on subsequent runs.

I upped the cached-percent to 0.2, which produced perfect guesses. Also, on my high-bandwidth (but not exceptionally high-bandwidth) connection, it took about a second for 0.2 percent to cache, which is acceptable for my purposes.

But then I tried a one-hour-long mp3. As you might expect, it took quite some time for 20 percent to cache. Totally unacceptable. I changed the percent back to 0.1 and the hour-long mp3 produced a guess in a reasonable amount of time (it took about fifteen seconds to guess). And the guess was only off by one second.

In the end, I am probably going to use a three-pronged approach: first, I will check for duration data in the XML feed, using that if it's there (doubtful). Next, I will try to use id3 data. Failing both those approaches, I will guestimate with a ten-percent cache.

If anyone reading this has a better idea, please let me know!

I am also trying to figure out how to adjust the UI if Flash has to guestimate. When the hour-long mp3 finished playing, the time-duration display said 00:59:00/1:00:00, which doesn't thrill me. Should I fudge the time up to the duration? (if end_time != duratation; then end_time = duration.)

Someone suggested that I keep checking for duration and adjust it as it the guess gets more accurate (which it will as more data gets cached.) I don't know. I think it would be pretty distracting seeing the the duration-readout change. Also, my played-progress bar would jump around, popping to a longer or shorter width.

What do you think?

No comments:

Post a Comment