Building a Zune Playlist (Matt Gertz)

Well, that was… intense.


You may have noticed the lack of articles coming from my direction.  I have been so buried in work, and so far behind, that when I look forwards all I see is backwards.  I work, I drive home, I work some more, and it all seems to keep piling up.  I would like to say that this is going to change soon, but alas, that’d be a lie.  Even though my immediate fire drills in engineering process have died down, I’m going to be flying a lot in October visiting our teams around the world, and then right back into the fires when I get home.


One of the things that have been getting me through the crazy times at work is my new Zune.  I *love* my Zune.  Now, I’ve never used an iPod, so I can’t compare the two, but my Zune is glued to me pretty much all the time I’m in the car, at work, mowing the lawn, etc.  (I drive a Ford Focus, so I have the Microsoft Sync system, and that’s also been a joy to use with my Zune.)  I have the 80 GB Zune 2 model.  I figure I’ve got 600+ CDs on the thing, all of the home movies I’ve taken of my family (which I like to watch while flying on business trips), and still have a lot of room left over.  With the recent updates to allow for gapless playing and the 3.0 firmware, I’m pretty happy right now, media-wise.


Now, of course, my dilemma with shuffled playlists that I first discussed earlier this year still applies, just on a new device.  That is, if I shuffle my enormous “Favorite songs” list, I inevitably break apart songs that belong together on one arc.  I solved this for Windows Media Player in an earlier blog series, by creating my own on-the-fly shuffled WPL list which kept songs together.  (Go ahead & read part 1 and part 2 now if you haven’t already; this blog won’t make much sense without them.) However, Zunes don’t play WPL playlist files; they play ZPL playlist files.  So, what to do?


Well, there’s one not-so-secret secret:  ZPL files are exactly like WPL files.  They have the same format.  The only difference is the file extension.  So, you might think that all I have to do is copy the generated WPL file to a ZPL file, right?  Well… it’s not that easy.


If you look at the original code, the line that saves the new playlist looks something like this:


    Private Sub SavePlaylistBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SavePlaylistBtn.Click


        ‘ Save new playlist to library and Music\Playlists


        Player.playlistCollection.importPlaylist(newplaylist)


    End Sub


 


This creates a WPL file in the “Playlists” directory, so, in theory, after calling “importPlaylist,” I’d do something like:


My.Computer.FileSystem.CopyFile(newWPLFilename, newZPLFilename)


where the two arguments are simply strings pointing to the file locations (e.g., “C:\Users\Matt\ Music\Playlists\Favorites.wpl” and  “C:\Users\Matt\ Music\Playlists\Favorites.zpl”).  However, if you do this, it won’t work – your ZPL file will be empty of everything except boilerplate code (at best).


Why is this?  Well, importPlaylist is an asynchronous call.  The VB program fires the call and moves on to the next instruction.  It will try to copy the file immediately, even if importPlaylist() isn’t finished.  importPlaylist(), in turn, creates the playlist in two steps – it creates the boilerplate XML, and then it injects the music entries into the playlist.  The trick here is to wait until the importPlaylist() call has completed the creation of the file before copying it.  But how to do that?


You could, in theory, keep looping until the size of the file changed twice, but that’s seems sort of, well, silly.  Fortunately, there’s a better way.  The Windows Media Player object has an event model associated with it – WMPLib.IWMPEvents – and it’s already supported by the WMP object we added to the original project.  We can handle the events really easily.  In the code editor, go up to the left-hand drop-down at the top of the editor and select “Player” (or whatever you called it in your copy).  Now, in the right-hand dropdown, choose “PlaylistCollectionChange”.  This will generate the event handler, and we can fill it in with our code.  (Yes, I know there’s a “PlaylistCollectionPlaylistAdded” also.  I tried it; it’s not useful for this case, since we’re importing, not adding, and so it doesn’t get called.) 


Here’s the event handler that gets generated:


    Private Sub Player_PlaylistCollectionChange(ByVal sender As Object, _


ByVal e As System.EventArgs) Handles Player.PlaylistCollectionChange


 


    End Sub


 


The value “e” is always empty.  That’s kind of unfortunate, because the event will be called 3-6 times after importPlaylist() is called, and it would be kind of nice to know which call was due to music injection vs. WMP-specific stuff.  So, we’ll do it the hard way.  There are two possible states that we care about – the file has been populated with the boilerplate code, and the file has been populated with the music.  Let’s add a value to our form class to track the size of the file as it changes:


    Public initialSize As Long = 0


 


We also want to ignore changes except when we explicitly do an import ourselves (i.e., account for someone adding a playlist outside of this program).  Because we’ll be checking for specific file changes, it wouldn’t actually harm us if other playlists were changed outside (unless coincidentally the same name was created), but I dislike having my event code run unless it’s meaningful to do so, as it wastes cycles.  You could also use AddHandler/RemoveHandler here, which would be even more performant, but I’m feeling lazy and so will just go with a simple Boolean here :


    Public listenForEvents As Boolean = False


 


Now, we’ll make sure these are initialized properly whenever we do an import of the playlist:


    Private Sub SavePlaylistBtn_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles SavePlaylistBtn.Click


        ‘ Save new playlist to library and Music\Playlists


        initialSize = 0 ‘ File starts at zero


        listenForEvents = True ‘ Start listening


        Player.playlistCollection.importPlaylist(newplaylist)


    End Sub


 


And now we handle the event itself:


    Private Sub Player_PlaylistCollectionChange(ByVal sender As Object, _


ByVal e As System.EventArgs) Handles Player.PlaylistCollectionChange


 


        If Not listenForEvents Then Return ‘ Pay no attention; we don’t care about this change


 


       ‘ If we’re here, then we care.  What’s the current size of the file?


        Dim fileSize As Long


        fileSize = My.Computer.FileSystem.GetFileInfo(newwplfilename).Length


        If fileSize > initialSize Then ‘ Filesize change


            If initialSize = 0 Then


                initialSize = fileSize ‘ First step – boilerplate added.  Don’t copy yet!


            Else


               ‘ Jump from one non-zero number to a larger non-zero number – must be the music.


                My.Computer.FileSystem.CopyFile(newWPLFilename, newWPLFilename)


                listenForEvents = False


            End If


        End If


    End Sub


 


The first time this gets called (at times when we care), the file will exist, but it will have some small size, indicating the first phase of creating the boilerplate XML is complete.  At some subsequent call (not necessarily the next one), the size will jump due to the music being added – that’s when we copy, and then tell our event handler not to check anymore.


The next time I hook up my Zune and open the Zune player, it will automatically resync the updated playlist, and I’ll have a new order to play for a while without the songs being broken up.


By the way, I’m not totally happy with this solution; I think it’s a bit hacky, and I’ve been searching for a better way to know when the save is completed.  This is new territory for me, so if you have a better way, let me know!


‘Til next time,


  –Matt–*

VBJukebox.zip