{"id":4003,"date":"2008-09-20T02:11:00","date_gmt":"2008-09-20T02:11:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2008\/09\/20\/building-a-zune-playlist-matt-gertz\/"},"modified":"2024-07-05T14:07:59","modified_gmt":"2024-07-05T21:07:59","slug":"building-a-zune-playlist-matt-gertz","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/building-a-zune-playlist-matt-gertz\/","title":{"rendered":"Building a Zune Playlist (Matt Gertz)"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Well, that was\u2026 intense.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">You may have noticed the lack of articles coming from my direction.<span>&nbsp; <\/span>I have been so buried in work, and so far behind, that when I look forwards all I see is backwards.<span>&nbsp; <\/span>I work, I drive home, I work some more, and it all seems to keep piling up.<span>&nbsp; <\/span>I would like to say that this is going to change soon, but alas, that\u2019d be a lie.<span>&nbsp; <\/span>Even though my immediate fire drills in engineering process have died down, I\u2019m 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.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">One of the things that have been getting me through the crazy times at work is my new Zune.<span>&nbsp; <\/span>I *love* my Zune.<span>&nbsp; <\/span>Now, I\u2019ve never used an iPod, so I can\u2019t compare the two, but my Zune is glued to me pretty much all the time I\u2019m in the car, at work, mowing the lawn, etc.<span>&nbsp; <\/span>(I drive a Ford Focus, so I have the Microsoft Sync system, and that\u2019s also been a joy to use with my Zune.)<span>&nbsp; <\/span>I have the 80 GB Zune 2 model.<span>&nbsp; <\/span>I figure I\u2019ve got 600+ CDs on the thing, all of the home movies I\u2019ve taken of my family (which I like to watch while flying on business trips), and still have a lot of room left over.<span>&nbsp; <\/span>With the recent updates to allow for gapless playing and the 3.0 firmware, I\u2019m pretty happy right now, media-wise.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, of course, my dilemma with shuffled playlists that I first discussed earlier this year still applies, just on a new device.<span>&nbsp; <\/span>That is, if I shuffle my enormous \u201cFavorite songs\u201d list, I inevitably break apart songs that belong together on one arc.<span>&nbsp; <\/span>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.<span>&nbsp; <\/span>(Go ahead &amp; read <\/font><a href=\"http:\/\/blogs.msdn.com\/vbteam\/archive\/2007\/10\/30\/let-the-music-play-matt-gertz.aspx\"><font size=\"3\" face=\"Calibri\">part 1<\/font><\/a><font size=\"3\" face=\"Calibri\"> and <\/font><a href=\"http:\/\/blogs.msdn.com\/vbteam\/archive\/2007\/11\/13\/one-is-the-loneliest-number-matt-gertz.aspx\"><font size=\"3\" face=\"Calibri\">part 2<\/font><\/a><font size=\"3\" face=\"Calibri\"> now if you haven\u2019t already; this blog won\u2019t make much sense without them.) However, Zunes don\u2019t play WPL playlist files; they play ZPL playlist files.<span>&nbsp; <\/span>So, what to do?<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Well, there\u2019s one not-so-secret secret:<span>&nbsp; <\/span>ZPL files are exactly like WPL files.<span>&nbsp; <\/span>They have the same format.<span>&nbsp; <\/span>The only difference is the file extension.<span>&nbsp; <\/span>So, you might think that all I have to do is copy the generated WPL file to a ZPL file, right?<span>&nbsp; <\/span>Well\u2026 it\u2019s not that easy.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">If you look at the original code, the line that saves the new playlist looks something like this:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> SavePlaylistBtn_Click(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> SavePlaylistBtn.Click<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216; Save new playlist to library and Music\\Playlists<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Player.playlistCollection.importPlaylist(newplaylist)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">This creates a WPL file in the \u201cPlaylists\u201d directory, so, in theory, after calling \u201cimportPlaylist,\u201d I\u2019d do something like:<\/font><\/p>\n<p class=\"MsoNormal\"><span>My<\/span><span>.Computer.FileSystem.CopyFile(newWPLFilename, newZPLFilename)<\/span><span><font face=\"Calibri\"> <\/font><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">where the two arguments are simply strings pointing to the file locations (e.g., <\/font><span>&#8220;C:\\Users\\Matt\\ Music\\Playlists\\Favorites.wpl&#8221; <\/span><font size=\"3\"><font face=\"Calibri\">and<span>&nbsp; <\/span><\/font><\/font><span>&#8220;C:\\Users\\Matt\\ Music\\Playlists\\Favorites.zpl&#8221;)<\/span><font size=\"3\" face=\"Calibri\">.<span>&nbsp; <\/span>However, if you do this, it won\u2019t work \u2013 your ZPL file will be empty of everything except boilerplate code (at best).<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Why is this?<span>&nbsp; <\/span>Well, importPlaylist is an asynchronous call.<span>&nbsp; <\/span>The VB program fires the call and moves on to the next instruction.<span>&nbsp; <\/span>It will try to copy the file immediately, even if <\/font><span>importPlaylist()<\/span><font size=\"3\"><font face=\"Calibri\"> isn\u2019t finished.<span>&nbsp; <\/span><\/font><\/font><span>importPlaylist()<\/span><font size=\"3\" face=\"Calibri\">, in turn, creates the playlist in two steps \u2013 it creates the boilerplate XML, and then it injects the music entries into the playlist.<span>&nbsp; <\/span>The trick here is to wait until the <\/font><span>importPlaylist()<\/span><font size=\"3\" face=\"Calibri\"> call has completed the creation of the file before copying it.<span>&nbsp; <\/span>But how to do that?<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">You could, in theory, keep looping until the size of the file changed twice, but that\u2019s seems sort of, well, silly.<span>&nbsp; <\/span>Fortunately, there\u2019s a better way.<span>&nbsp; <\/span>The Windows Media Player object has an event model associated with it \u2013 WMPLib.IWMPEvents \u2013 and it\u2019s already supported by the WMP object we added to the original project.<span>&nbsp; <\/span>We can handle the events really easily.<span>&nbsp; <\/span>In the code editor, go up to the left-hand drop-down at the top of the editor and select \u201cPlayer\u201d (or whatever you called it in your copy).<span>&nbsp; <\/span>Now, in the right-hand dropdown, choose \u201cPlaylistCollectionChange\u201d.<span>&nbsp; <\/span>This will generate the event handler, and we can fill it in with our code.<span>&nbsp; <\/span>(Yes, I know there\u2019s a \u201cPlaylistCollectionPlaylistAdded\u201d also.<span>&nbsp; <\/span>I tried it; it\u2019s not useful for this case, since we\u2019re importing, not adding, and so it doesn\u2019t get called.)<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Here\u2019s the event handler that gets generated:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> Player_PlaylistCollectionChange(<span>ByVal<\/span> sender <span>As<\/span> <span>Object<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>ByVal<\/span><span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> Player.PlaylistCollectionChange<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The value \u201ce\u201d is always empty.<span>&nbsp; <\/span>That\u2019s kind of unfortunate, because the event will be called 3-6 times after <\/font><span>importPlaylist()<\/span><font size=\"3\" face=\"Calibri\"> is called, and it would be kind of nice to know which call was due to music injection vs. WMP-specific stuff.<span>&nbsp; <\/span>So, we\u2019ll do it the hard way.<span>&nbsp; <\/span>There are two possible states that we care about \u2013 the file has been populated with the boilerplate code, and the file has been populated with the music.<span>&nbsp; <\/span>Let\u2019s add a value to our form class to track the size of the file as it changes:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> initialSize <span>As<\/span> <span>Long<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">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).<span>&nbsp; <\/span>Because we\u2019ll be checking for specific file changes, it wouldn\u2019t 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\u2019s meaningful to do so, as it wastes cycles.<span>&nbsp; <\/span>You could also use AddHandler\/RemoveHandler here, which would be even more performant, but I\u2019m feeling lazy and so will just go with a simple Boolean here :<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> listenForEvents <span>As<\/span> <span>Boolean<\/span> = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, we\u2019ll make sure these are initialized properly whenever we do an import of the playlist:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> SavePlaylistBtn_Click(<span>ByVal<\/span> sender <span>As<\/span> System.Object, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>ByVal<\/span><span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> SavePlaylistBtn.Click<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216; Save new playlist to library and Music\\Playlists<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>initialSize = 0 <span>&#8216; File starts at zero<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>listenForEvents = <span>True <\/span><span>&#8216; Start listening<\/span><span><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Player.playlistCollection.importPlaylist(newplaylist)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">And now we handle the event itself:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> Player_PlaylistCollectionChange(<span>ByVal<\/span> sender <span>As<\/span> <span>Object<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>ByVal<\/span><span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> Player.PlaylistCollectionChange<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>Not<\/span> listenForEvents <span>Then<\/span> <span>Return <\/span><span>&#8216; Pay no attention; we don&#8217;t care about this change<\/span><span><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><span>&#8216; If we&#8217;re here, then we care.<span>&nbsp; <\/span>What\u2019s the current size of the file?<\/span><span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> fileSize <span>As<\/span> <span>Long<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>fileSize = <span>My<\/span>.Computer.FileSystem.GetFileInfo(newwplfilename).Length<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> fileSize &gt; initialSize <span>Then <\/span><span>&#8216; Filesize change<\/span><span><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> initialSize = 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>initialSize = fileSize <span>&#8216; First step \u2013 boilerplate added.<span>&nbsp; <\/span>Don\u2019t copy yet!<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;<\/span><\/span><span>&#8216; Jump from one non-zero number to a larger non-zero number \u2013 must be the music.<\/span><span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Computer.FileSystem.CopyFile(newWPLFilename, newWPLFilename)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>listenForEvents = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">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.<span>&nbsp; <\/span>At some subsequent call (not necessarily the next one), the size will jump due to the music being added \u2013 that\u2019s when we copy, and then tell our event handler not to check anymore. <\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The next time I hook up my Zune and open the Zune player, it will automatically resync the updated playlist, and I\u2019ll have a new order to play for a while without the songs being broken up.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">By the way, I\u2019m not totally happy with this solution; I think it\u2019s a bit hacky, and I\u2019ve been searching for a better way to know when the save is completed.<span>&nbsp; <\/span>This is new territory for me, so if you have a better way, let me know! <\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">\u2018Til next time,<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><span>&nbsp; <\/span>&#8211;Matt&#8211;*<\/font><\/font><\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/08\/95\/97\/62\/VBJukebox.zip\">VBJukebox.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Well, that was\u2026 intense. You may have noticed the lack of articles coming from my direction.&nbsp; I have been so buried in work, and so far behind, that when I look forwards all I see is backwards.&nbsp; I work, I drive home, I work some more, and it all seems to keep piling up.&nbsp; I [&hellip;]<\/p>\n","protected":false},"author":258,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[22,195],"tags":[101,165,166],"class_list":["post-4003","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-matt-gertz","category-visual-basic","tag-matt-gertz","tag-vb2005","tag-vb2008"],"acf":[],"blog_post_summary":"<p>Well, that was\u2026 intense. You may have noticed the lack of articles coming from my direction.&nbsp; I have been so buried in work, and so far behind, that when I look forwards all I see is backwards.&nbsp; I work, I drive home, I work some more, and it all seems to keep piling up.&nbsp; I [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/4003","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/258"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=4003"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/4003\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=4003"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=4003"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=4003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}