A group blog from members of the VB team
Oh, boy. Sorry I haven’t written any posts lately, but I’ve transitioned to a new job within Visual Studio and have been getting my sea legs there. One of the job tasks is getting Visual Studio 2008 out to you folks, and while I’ve always been involved with that aspect of the product in the past, it was always to a lesser degree. I could best describe each day now as “23 hours of nervous tension followed by one hour of utter panic” as we knock down the last few things that would otherwise keep us from shipping on time.
(Incidentally, in the spirit of making sure that the product works properly before we ship it, I’ve switched to writing code against Visual Studio 2008 using a build from last week. Everything in this post, though, will still work using Visual Studio 2005, provided you have the current Windows Media Player.)
So, anyway, going forward I hope to do a post or two each month. We’ll see how it goes, but it’s awfully hard to keep me from writing about Visual Basic, so I’m optimistic. J And on that note, on to today’s topic!
I’m a big music fan, with quite a large collection of songs that I’ve ripped onto my computer. One of the things that I really appreciate about playing music on my PC is that I can listen to random tracks as if I were listening to a radio station (a radio station that always plays what I like and has no chatter) – I don’t hear the same old thing in the same old order every time. The shuffle function gets a lot of use from me.
There’s a difference between “shuffle” and “random” function, of course. “Shuffle” generates a random list from an existing list and then plays that temporary list from beginning to end (so as to avoid replays), whereas with “random,” the next song is calculated on the fly. Unfortunately, statistics being what they are, you’ve got a good chance in the latter case of hearing the same song twice (or more) in a session before all of the songs have been heard.
Most good playback engines on PCs and cars do “shuffle” instead of “random” these days because of this, but shuffle isn’t a panacea, either. The problem is that there are songs which are meant to be played adjacent to another song or else they don’t make sense. The disc authors will break up two songs on a track which (in the listener’s opinion) are really the part of same song. Take, for example, Pink Floyd’s excellent “Dark Side of the Moon” album, which is replete with this sort of issue. Really, is there anyone out there who would listen to track 8 (“Brain Damage”) without listening to track 9 (“Eclipse”)? Not likely; it’s a jarring experience to just have the first track end abruptly.
So, what happens? I throw my favorite songs from various artists into a playlist. I sync that to my music player and head out to mow the lawn, hitting “shuffle” so I'm not always hearing the same thing. Sure enough, after a few minutes, I'm enjoying the last few lines of Jackson Browne’s “The Load-Out”:
But we’ll be scheduled to appear
A thousand miles away from here…
Is this the real life? Is this just fantasy?
Caught in a landslide…
Mentally, I was expecting the track to continue into Jackson Browne’s “Stay” instead of randomly switching to Queen’s “Bohemian Rhapsody,” and, although I do like the latter song, the transition is pretty darn jarring.
What I really want is a way to shuffle my tracks to get that nice sense of randomness, but also keep certain songs together to prevent any jarring playback from inappropriate segues. I’ll code that solution up in this blog, but first I’ll need to cover some of the basics of how you write code against the Windows Media Player.
The Windows Media Player control is a very powerful control with a lot of functionality, but understanding its object model involves a bit of a learning curve because there’s quite a lot to it. Fortunately, once you understand the basics, it’s pretty easy to work with. There are four really important concepts:
1. The player itself. Being a COM control, it gets wrapped for .NET so that you can write code against it without resorting to Declares, etc., and ends up being an object of type AxWMPLib.AxWindowsMediaPlayer. Properties on the player include which of its controls show and its visibility, whether it auto-plays (via its “Settings” property), actions such as Play and Stop (via the Ctlcontrols property), and so on.
2. The playlist. This is the object that tells the player what media it should play. You don’t “Dim” or “New” a playlist; instead, you ask the player for a new or existing playlist, and it returns an interface of type WMPLib.IWMPPlaylist to the result, on which you can then call methods.
3. The media. Again, these are not objects that you create yourself. There are methods on the player and the playlist which will return interface values (WMPLib.IWMPMedia) for them. The playlist is made up of media objects.
Once you’ve got that in hand, the rest is pretty easy – honest! Let’s run through some examples here.
The “Hello, world!” of a music player would be, of course, playing a song. First, create a new Windows Application and make sure that the toolbox is displayed for the resulting form. We’ll need to add the Windows Media control to the toolbox, so right-click on the toolbox and select “Choose Items…” In the resulting dialog, navigate to the “COM Components” tab, scroll down towards the bottom, and check the box to the left of the Windows Media Player. Press “OK,” and the Windows Media Player (which I will henceforth refer to as the WMP) will be added to your toolbox. Drag an instance of it over to your form and size it however you like. (By the way, I’m using the most recent version of WMP, which is version 11 and which you can download for free from Microsoft if you don’t already have it. Earlier versions of the Windows Media Player might not support this all of this code, depending on how old they are.)
While the WMP is still selected, go to the property grid and change its name to “Player” (as the default name is quite a mouthful otherwise). Now, double-click on the form background (not the WMP) to create the “Form1_Load” event.
In Form1_Load, we’ll create a new playlist for the WMP to play:
Dim playlist As WMPLib.IWMPPlaylist
playlist = Me.Player.newPlaylist("My groovy playlist", "")
The first argument to newPlaylist is the name of the new playlist, and the second is a URL to an existing Playlist with whose contents we want to initialize it. I’ve left it as the empty string because in this example I want to start with an empty playlist. (Note that the new playlist is not automatically added to your library, so don’t worry about cluttering up your library here. To actually make the playlist permanent, you’d need to call either importPlaylist or newPlaylist from the IWMPPlaylistCollection returned from Player.playlistCollection property.)
Now, I want to add a song to the new playlist:
Dim item As WMPLib.IWMPMedia = Player.newMedia("file:///C:\Users\Matt\Music\Dead Can Dance\Spleen and Ideal\08 Avatar.wma")
The argument to newMedia is a URL, so if I’m using a file from disk, I need to use a URL format (basically, prepend “file:///” to the absolute path in this case). Of course, I normally wouldn’t hardcode this path; I’d read the file name from a file dialog, but I’ve already covered file dialog usage in a previous post so I’ll skip it here for clarity’s sake.
Next, I’ll add the playlist to the player:
Player.currentPlaylist = playlist
Now, let’s press F5. The application launches and, assuming that there’s no mistake in URL we specified, the music automatically starts playing. You can use the WMP controls to control volume and so forth.
Now, it’s possible that you don’t want the music to play until the user actually pushes the “Play” button. This is easy enough to do – simply add this line somewhere *before* you assign the playlist to the player:
Player.settings.autoStart = False
And now you’ve got a way to play any media file (or set of media files) from inside your Windows application, with the user in full control of the playback. Cool, huh?
Let’s start out with the following code in Form1_Load:
Player.settings.autoStart = False ' Otherwise, playlists will automatically play when added to the player
' Create a new playlist
Dim oldplaylist As WMPLib.IWMPPlaylist
Dim newplaylist As WMPLib.IWMPPlaylist
oldplaylist = Me.Player.newPlaylist("Original Sorted Playlist", "file:///c:\Users\Matt\Music\Playlists\One True Playlist.wpl")
newplaylist = Me.Player.newPlaylist("Smart Shuffled Playlist", "")
Note that I’m creating two playlists here – one which identically matches a favorite playlist of mine, and one which is empty. I’ll use the empty one to store my smartly-shuffled playlist. Note that I could use Player.playlistCollection.getByName(“One True Playlist”).Item(0) to point to the existing playlist instead of a copy, but since I’m going to be removing media items from one list and moving them to another, that would be destructive to the original. (I could also have sorted within one copied list like I did with card shuffling in an earlier blog post, and avoid even using a second list, but since I’m reusing most of the memory here anyway – that is, the media objects – I’m opting for a more readable implementation this time. Either would work.) Again, I would normally use a file dialog to browse to the playlist rather than hard-coding it – I’m just trying to keep it simple here.
I’ll need a random number generator to pick songs to pull over. If you’ve read my earlier Euchre blog post, you’ll know I usually use a complex one to guarantee the best distribution I can get; however, this being just for a music player, I’ll go with plain-old Random() for brevity’s sake. For the range of the random number, I’ll need to know how many songs we’ll be moving, which I can get from the playlist count. For this first attempt, I’ll just shuffle without regard to disjointed songs:
' Randomize the values using system time as a seed
' Get the number of songs to use
Dim numberOfSongs As Integer = oldplaylist.count
' The value i will keep track of the number of songs left to copy,
' which in turn helps us keep track of the range for valid random numbers.
For songsRemaining As Integer = numberOfSongs - 1 To 0 Step -1
' Pick a random song from whatever remains in the old list:
Dim SongToCopy As Integer = Microsoft.VisualBasic.Rnd() * songsRemaining
Dim mediaItem = oldplaylist.Item(SongToCopy)
' Append it to the new list
' Remove it from the old list, which will have its count decrease
Player.currentPlaylist = newplaylist
The variable “songsRemaining” is pulling double-duty here – it makes sure (via the For loop) that I copy over exactly as many songs as possible, and it also constrains the random variable to however songs are remaining in the initial list.
That code works fine for a simple shuffle (go ahead & try it, using one of your own playlists!), but it doesn’t do anything that Windows Media Player can’t already do. So, now I want to massage this code into something which keeps certain songs together. My general plan will be to have some sort of “tag” on songs to indicate that they below with another song, and then if I encounter a random media item which is part of that duo (or trio, or whatever), I’ll bring the others along as well, in the proper order. The trick will be figuring out what tag to use. Fortunately, there are some custom fields associated with media items that I can leverage here.
Ideally, the information that gets downloaded with songs would pre-populate some field which would indicate that one song always belongs with another, but that unfortunately isn’t the case. So, we’ll have to do it ourselves. If you navigate to your music collection in File Explorer, you can right-click on a song and bring up a property sheet for it. On the "Details" tab, towards the bottom, you’ll notice a property called “Part of set”. We’re going to leverage that property to track that “togetherness” for an arc of songs. (I could just as easily have leveraged the “Comments” property, for example, but using this one made more sense to me and as far as I could tell, none of my other songs were using it for something else.) To set the property, first find an album which has songs that should play together. For example, I’m going to use The Moody Blues’ release “Long Distance Voyager ,” which includes an arc of three songs at the end of the album which should always go together (“Painted Smile,” “Reflective Smile,” and “Veteran Cosmic Rocker”). Right-clicking on the first song in the arc, I’ll set its “Part of set” field to 1 and apply the changes, then bring up the properties for the next song and set the field to 2, and then finally 3 for the last song of the arc.
I’ll then go into Windows Media (the desktop one, not the control you added) and create a playlist called “Test” which includes all of the songs from that album for my experiment. You can do this by clicking “Create playlist” on the left side of the player, giving it the title "Test," dragging the appropriate songs to the resulting playlist editor on the right, and then clicking "Save playlist." That’s the playlist I’ll be copying from – you can, of course, start with any playlist you like, but I wanted to keep the number of songs small while debugging the app. (The playlist I used in the previous example does include the aforementioned Moody Blues tracks, but it also has about 600 other songs and thus is not the best choice to use when stepping through the debugger, as you might imagine.)
Now, in my code, I can check for the presence of one of the “Part of set” values and react by copying the adjacent songs in order as well as the randomly selected song. Note that I’m assuming that the original list is sorted in track order per each album (or partial album) included on the playlist – if not, you’ve got to do some additional work to look up the album information and see what should be kept adjacent to each other, whether it's all in the playlist somewhere, etc., all of which is an exercise that I’ll leave to the reader. For my part, I’ll just check to make sure that the numbers are used contiguously and, if not, default to the “normal” copy behavior. Here’s the final code; I’ll let the comments speak for themselves:
Dim oldplaylist As WMPLib.IWMPPlaylist
' You should really use a file dialog to get this path to the existing playlist.
oldplaylist = Me.Player.newPlaylist("Original Sorted Playlist", _
' This playlist is initially empty, and we’ll fill it with songs.
' You could give the user the option of picking the name for it
' by reading it from a label control.
' Randomize the values using system time as a seed
' The value songsRemaining will keep track of
' the number of songs left to copy,
' which in turn helps us keep track of the range
' for valid random numbers.
Dim songToCopy As Integer = _
Microsoft.VisualBasic.Rnd() * songsRemaining
Dim mediaItem As WMPLib.IWMPMedia = oldplaylist.Item(songToCopy)
' Check the "Part of set" attribute -- see
' for a list of attributes for different media types.
Dim sPartOfSet As String = mediaItem.getItemInfo("WM/PartOfSet")
' See if the value is a number.
If sPartOfSet <> "" AndAlso IsNumeric(sPartOfSet) Then
' It's a number. We may have an arc of songs here.
' Get the number and rewind to the first one.
' If we run into anything unexpected then just
' do a normal copy.
Dim iPartOfSet As Integer = sPartOfSet
' Make sure we don't go past the beginning of the list!
If songToCopy - (iPartOfSet - 1) >= 0 Then
' Rewind to what should be the beginning of
' the arc and get the song. (Hopefully, they’re all
' there, but I’m not going to count on them being all
' all there and initially in the right order. My
' default when confused will be to just copy the
' originally picked song as if it wasn’t part of an arc.)
Dim currentSongToCopy As Integer = _
songToCopy - (iPartOfSet - 1)
Dim currentMediaItem As WMPLib.IWMPMedia = _
Dim sCurrentPartOfSet As String = _
' Do some error checking here -- the attribute had
' better be "1" – otherwise, do a normal copy instead
If Not sCurrentPartOfSet = "1" Then GoTo NormalCopy
' OK, we're probably good to go, unless we coincidentally
' got the beginning of another arc instead
' due to discontinuous numbers. Worse thing that happens
' in that case is that we just copy a different
' arc, and we’ll pick up these pieces later.
iPartOfSet = 0
While sCurrentPartOfSet <> "" _
AndAlso IsNumeric(sCurrentPartOfSet) _
AndAlso CInt(sCurrentPartOfSet) = iPartOfSet + 1
iPartOfSet = iPartOfSet + 1 ' Copied one song
' Check next song if there are any remaining
If currentSongToCopy = oldplaylist.count Then
currentMediaItem = _ oldplaylist.Item(currentSongToCopy)
sCurrentPartOfSet = currentMediaItem.getItemInfo("WM/PartOfSet")
If iPartOfSet > 0 Then
' We may have copied more than one song.
' Update the For loop variable
' appropriately to compensate, since For loop
' will only decrement by one.
songsRemaining = songsRemaining - (iPartOfSet - 1)
' Didn't copy anything -- must have been a
' discontinuity. Just copy the original song,
' since user apparently doesn't care.
' Error -- starting songs not available. Just copy
' the song, since user apparently doesn't care.
' Just copy like normal
' Point player at the new playlist so it can be played
Immediately on hitting F5, you can play the songs in the new order on your PC, without fear of bizarre transitions from one song to the other. However, to "take it on the road," you could also use the following code to add the playlist to your library and your file system:
' Save new playlist to library and Music\Playlists
You could then use that playlist to sync to your music player or, if your music is already on a flash card, just copy that resulting WPL file to your flash card so that your Windows Media supporting device can play your songs in the new shuffled order.
So, play your party mixes or head out to jog knowing that you can get a pleasant randomness in your playlists without those jarring segues between unconnected songs! ‘Til next time…
PingBack from http://musicarticles.info/?p=46628
Matt Gertz over at the Visual Basic Team talks about his big problem with shuffling music and starts
Now if you could just come up with an algorythm that plays only the "Good" songs....
I realize you're probably being tongue-in-cheek, but actually, implementing what you suggest wouldn't be too hard. One of the attributes for a song is the rating (which you can modify per song). The attribute in this case would be "UserRating" (discussed at http://msdn2.microsoft.com/en-us/library/bb249557.aspx), and you could query that value to select songs of only a certain rating, or move them forward in the list, or whatever. (WMP already uses this attribute to populate some of its automatic lists.) Or, if the thought of verifying all of those ratings doesn't appeal to you, you could use the "UserPlaycount" attribute as a sorting value instead, which would probably be more reflective of what you actually listen to. You could further do something fun like convolving two attributes in a "Songs I like that I haven't heard in a while" query by leveraging the "UserLastPlayedTime" attribute.
You can find a list of all the attributes for audio items at http://msdn2.microsoft.com/en-us/library/bb248412.aspx.
Thanks for reading the blog!
how to recieve all file names double clicked
to open in my program (like ctrl + a enter)
and how to recieve the file name double clicked when my media player is working
i used command$ to recieve file names but it receives just the name for one file
So, for your first question: double-click only works on one object, whether you're in the file explorer or in your own application. This is "by design" for double-click, since the double-click essentially changes the selection to be the item under the click. To enact a verb on multiple selections, the only way is to to the multi-selection and then press "Enter" (which invokes the default verb, which is usually "Open" or "Edit"), or to right-click on one of the selected items and choose the appropriate action from the context menu.
For the second part of the question, I think you're asking how to make double-clicking a file launch your application to use that file. First, you need to associate the application with the file type. The happens one of two ways:
(1) In Windows XP, open file explorer and from one of the menus (I forget which one) select "File and folder options." You'll get a property sheet which has a tab that allows you to set the default program for a given file type (like WMA or WAV, for example). Just point it at your program.
(2) In Windows Vista, you can do the same thing from the "Default Programs" control panel.
(If you create an installer Setup program for your app, you can also set it up to make that association automatically, but bear in mind that customers don't usually like programs which quietly change file associations. You use the File Types editor in the Setup program you create to do that.)
Now, when you double-click the file (or choose "open" by right-clicking on multiple selected files), it should launch your program. The program can read the names of the files using the "My.Application.CommandLineArgs" functionality -- you can easily iterate through the file names using it.
Hope this helps,
i think i did not make clear my question...
the question is
i made a mediaplayer with visual basic.net 2005
i made it single instance application
i also made setup for my program with file types mp3,wav,wmv,.... etc you know
the problem i found was
when i double click on myfile1.mp3
it is working (because i determined mp3 files in my setup and with special icon too)
but when i double click myfile2.mp3
while the first is being played
my program just activat and continue playing
the first file
what do i have to do to play the new file double clicked
for recieving all file names double clicked
i used commandlineargs to get them
but it seems like an array of one item
i mean it just gives me a name of one file among
the double clicked files
thank you for every thing Matt
i want to tell you that i am vb.net biginner
i am working on it for about 3 months
i am sorry if my english is not very good
if you want to answer me can you send somthing like code or any thing to make clear your soloutions
and than you again
No problem. For question #1: Yes, in the case where the single-instance application is already running, the default behavior is just to give focus to that app without altering anything, because Form_Load wouldn't be called (the form is already loaded). Instead, you need to handle the "StartupNextInstance" event. To do this, go to the project property page (the one where you set the "Make single instance application" checkbox) and click the "View Application Events" button. This will take you to the code editor so that you can add event handlers to the MyApplication friend class. Add an event handler similar to the one at the bottom of the topic at http://msdn2.microsoft.com/en-us/library/b9z4eyh8(VS.80).aspx -- this demonstrates how to examine the command-line arguments passed in. Modify that example to stop the player (vis the Ctlcontrols collection on the Player object) and restart it with a different playlist based on the arguments you read. (You can get to your player via the My.Forms collection -- i.e., My.Forms.Form1.Player.)
For question #2: Yes, double-clicking when you have a bunch of files selected will only send one filename to the application. That's by design in windows -- double-clicking changes the selection to one thing. In order to have multiple file names passed to the application, you need to right-click the multi-selection and choose "open," or just press "Enter" while they are all selected -- that is the only way to get all of those file names passed to your application.
(Note that the hyperlink in my last response got underlined incorrectly by the blog software, so clicking it won't work -- the address should include everything up to the .aspx suffix, so you'll need to copy and paste the address into your browser to correctly navigate to the example.)
thank you matt for everything
for my 2nd question
what i did is..
i selected many files
and then pressed enter
then my application works
in my application form load
and this gave me the number (1) in my messagebox
when i check the lenght of
it's lenght is one item whether i select a file and press enter
or select many files and press enter
i tried to get the item name of the array
and in both cases was a name of one of the selected files
what do you thin the problem is
Note:can i get your email please ..?
-can you send information about you to my email
i liked you and want to recognise..if you have time
-can i send files to your email like my project files or somthing like
because i have many another problems and i need you help please
and thank you again
i asked help from other websites befor but i got no responding
you are the first who answer me
so thank you very much
thank you matt again
but for my 1st question i want to tell you
that i tried to handle startup next instance
i tried to messagebox(the file name which was double clicked when my application was playing a file)
i used messagebox.show(my.application.commandlineargs........tostring ...etc )
the problem is
this message gaved me the name of the file is being played
not the new double clicked file name..
why do you think this happens ?
I think the best thing to do is have me write a blog post that demonstrates all of this in code. I'll try to do that this week.
Ah, never mind -- I see what the issue is. It turns out that I was mistaken about how multiple files are handled in Windows. I thought that they were handled one one command line, but that's not quite correct. Also, it looks like the sample in the link I pointed to you is misleading.
(1) If you have a bunch of files selected and use Open (or whatever) to launch your application with them, they don't all appear as a command-line argument to FormLoad. Only one of them will (generally the last one selected). The other files will go through the StartupNextInstance handler, one at a time. Essentially, Windows is trying to launch your application once for *each* selected object -- it doesn't combine them into one command-line argument. Because your application is a single-instance application, that means that the other files will get routed, one at a time, to calls to StartupNextInstance.
(2) Ignore the "/input=" string in the sample I pointed you to. It doesn't exist. Instead, you get a fully-qualified path to the file, and you can use the argument by simply prepending "file:///" to it.
Now, knowing those two things, the rest is easy. FOr my example, I would move all of the code in FormLoad (the code which loads and randomizes the files) to a separate function -- let's call it "MergePlaylist." FormLoad should just create the new playlist but leave it empty, and then call MergeList with the file argument that it knows about. StartupNewInstance will also just call MergeList with the argument that it knows about. MergeList will then prepend the "file:///" marker and then merge in the playlist with the total new playlist in a random fashion. You'll have to add code in MergePlaylist to make sure that you're not breaking up any existing arcs already in there, but that should be easy.
*That* should solve all of your problems. Good luck!
P.S. I'll still write this up in a blog, so you'll be able to see my final coding solution.