Before I go any further, I’d like to link to KeyJ’s blog about “balanced shuffle”, a thing I ran into many years ago and found to be quite sensible. It works good, too. If you wondered I meant by “been a while since I last generated a playlist” — this is what. One pass through all my music and I have a playlist that will do me for a long time. I just play it straight through.
So, code. The API to read files in was surprisingly straightforward, but kind of roundabout. You have to create a FileReader object, bind an event to it, and then use it on the File object given by a file input form element. Once you’ve got the data, it’s not in a very useful form; blobs and typed arrays are pretty damn cool but they don’t have a lot of utility to them. I did a bunch of array shenanigans to get what I wanted, learning a few things in the process (Array.split works with a Regexp!).
Now I had my data and it was time to output it in some convenient way. When testing, I just set it as text in a <pre> element, but that’s pretty messy if you have more than a couple albums. I figured there must be a complementary FileWriter to go with the FileReader.
It turns out that FileReader has decent support but FileWriter doesn’t even work in Firefox.
Okay, no big deal. What else can I do? IRC suggests data URLs — perfect! I just need to convert my data into base64. Turns out there’s a function to do that for you! btoa and atob aren’t commonly supported, but I’m writing bleeding edge technology here, so screw IE. I make a link element, assign it some properties and –
Error: InvalidCharacterError: DOM Exception 5
Apparently btoa doesn’t accept just any old input. Which is pretty dumb, since the purpose of base64 encoding is to take unsafe binary data and make it safe. What the hell’s the point of such a function that can’t take arbitrary data? Well okay. If all else fails I can write my own damn base64 encoder, but I’d rather there was an easier way. Which, it turns out, there is.
FileReaders have a method called ‘readAsDataURL’ which, as the name implies*, reads some input and outputs data URL. But my data isn’t a file, it’s a string. And this isn’t a StringReader; there is no such thing. I have to jump through another hoop.
* To me it implies that it’s reading data that it expects to be in the form of a data url and decoding it, which is the opposite of what it does. But I guess this sort of makes sense too.
Blob objects are pretty handy. Aptly named, too. You pretty much just throw whatever at it and you get a bag of binary bits that you can do Data Stuff with. It so happens that FileReaders will work with Blobs, so all I need to do is make a Blob. I try the obvious:
var blob = new Blob(string); > TypeError: First argument of the constructor is not of type Array;
Of course it doesn’t work. I mistakenly assumed that blobs would take any kind of input. Silly me, they only accept any kind of input when it’s in an array.
var blob = new Blob([string]);
Success! Now I just have to, uh, bind an on complete event, read the data, dig into the event object for the result, and I’ve got a data URL that holds my new playlist. But I ain’t mad, because I’ve got what I want, and it works! Check it out yourself, assuming you have a modern enough browser to support the code:
(Select a .m3u file or any file with paths, one per line and click shuffle. #EXT lines are removed, and shuffling is based on folder hierarchy. Additionally, there’s a filter to return only .mp3 files — you can edit to your needs if you like.)