Want to Know What I'm Listening To?
Duncan Mackenzie
Microsoft Developer Network
April 30, 2003
Summary: Duncan Mackenzie describes how to use a new PowerToy for Windows Media Player to tell the world what you are listening to. (10 printed pages)
Applies to:
Microsoft® Visual Basic® .NET
Download the source code for this article.
Extensible Software Is Great
Considering I program in Microsoft® Visual Basic®, this shouldn't come as a big surprise, but I just love it when software supports some form of extensibility model. Call them add-ins in Microsoft® Office, or plug-ins in Adobe Photoshop® and Microsoft® Windows Media® Player, it is all the same to me. All of these terms indicate some way that I can add my own little bit of functionality to an already existing piece of software.
Now, as I said in my first column, I often write complete applications for my own use, even when a perfectly good piece of software already exists. Often, I write these applications for fun, but sometimes I end up writing a full-blown application just because I want to add a single new feature. It is much easier when I can write a small chunk of add-in, plug-in, or even macro code to add that single feature I want without having to rebuild the complete system. In this article, I'm going to show you how I used a new PowerToy for Windows Media Player, in conjunction with a few different programs of my own, to let the world know what music I am listening to at any given time.
Blogging as a Motivator
I have recently started blogging, which is a curious practice rather akin to writing a diary or a journal, except that every entry is public the moment after you post it. This experience soon had me thinking about writing some blog-related code. Instead of writing a blog engine or a blog reader, I started thinking about a blogging-related utility that would add info about the song I was playing to each post I made. This seemed like a really cool idea, and I knew that similar software had been written before and was well received, so I set to work trying to understand the plug-in architecture of Windows Media Player.
After digging through the SDKs and looking at the interfaces I would need to implement, I realized that this was a relatively simple task in Microsoft® Visual C++® but not all that easy to accomplish using managed code. My C++ skills are quite rusty, so I wasn't really interested in writing anything in Visual C++, but implementing the necessary interface would be difficult (although not impossible) in Visual Basic .NET or C#. I kept working at it, though, and had started creating my own wrapper in managed code for the IWMPUIPlugIn interface, when I decided to ask the Windows Media Player team for help and advice.
Their answer was quite helpful, though it promptly killed my development project. They would be releasing a "blogging plug-in" for Windows Media Player (Zach Robinson, the writer of the add-in gives details on its intended use in his column) that does exactly what I was trying to do—expose the currently playing media item's information to other programs. Sure enough, it is out and available as part of the new Windows Media Player 9 Series Fun Pack, along with some other cool (and free!) PowerToys. But it isn't intended to be a complete solution on its own. It is designed to make the currently playing item information available to other programs by saving it to the registry (updating a set of four keys whenever the current music item changes), but that is it. You need to have or write a program that consumes this musical metadata before you can do anything with it, which leaves me something to demonstrate!
How I Decided to Use the MetaData
Several blog-posting software writers are incorporating the plug-in on their own, so I decided I had better write something else for my samples. First, I focused on Microsoft® Windows Messenger; wouldn't it be cool if everyone on my Messenger list could see what song I was listening to? I will warn you ahead of time, the reaction to this was not so great. My friends and family had a wide range of reactions from "you are a freak" to "you are weird" (yes, I know… my friends and family aren't very nice people). A few of my geekier associates thought it was cool, but for the most part the response was roughly, "What makes you think I want to know what you are listening to?" Still, I will show you the very simple code to get this to work, and you can find out just how polite your own friends are. After playing with Windows Messenger, I moved onto messing with my Microsoft® Outlook® e-mail, and then finally created a system-wide keyboard shortcut.
Writing One Big Utility
Instead of writing each sample as its own application, I decided to use the basic framework of a system tray utility (as shown in my previous column) and just add the code for each sample onto that foundation.
Accessing the Registry
As a first step, I decided that I should make the registry-accessing code independent of any little applications I build to use the information exposed by the plug-in. By breaking this code out into its own library, it will be pretty easy to add this functionality into any application where it would be useful (or at least fun). For a start, I created the library in Visual Basic .NET, but I also built it as a Microsoft® ActiveX® object using Visual Basic 6. If you aren't using .NET yet, or if you want to access this information from a non-.NET application, I thought creating a native COM library would make it as easy as possible for you. In both cases, the code is fairly simple, although I wrote them slightly differently.
In Visual Basic 6.0, I could have used Win32 API calls to access the registry, but instead I pulled down a little COM library from the Microsoft download center that handles it for me. Using this library, the registry access code wasn't all that different from what I wrote in Visual Basic .NET.
'Visual Basic6 Code
Public Sub RefreshMetaData()
Dim newMetadataAvailable As Boolean
Dim newAuthor As String
Dim newTitle As String
Dim newAlbum As String
Dim newDuration As String
Dim CurrentMetadata As RegObj.RegKey
CurrentMetadata = _
RegObj.RegKeyFromHKey( _
RegObj.HKEY_CURRENT_USER)
CurrentMetadata = _
CurrentMetadata.ParseKeyName( _
"Software\Microsoft\MediaPlayer\CurrentMetadata")
If CurrentMetadata.Values.Count = 0 Then
newMetadataAvailable = False
Else
newMetadataAvailable = True
With CurrentMetadata
newAlbum = CStr(.Values("Album").Value)
newAuthor = CStr(.Values("Author").Value)
newDuration = CStr(.Values("durationString").Value)
newTitle = CStr(.Values("Title").Value)
End With
End If
If (newMetadataAvailable <> m_MetadataAvailable) Or _
(newAlbum <> Me.Album) Or _
(newAuthor <> Me.Author) Or _
(newTitle <> Me.Title) Or _
(newDuration <> Me.durationString) Then
'new data
m_MetadataAvailable = newMetadataAvailable
m_Album = newAlbum
m_Author = newAuthor
m_Title = newTitle
m_durationString = newDuration
m_LastRefresh = Now
OnMediaChange()
End If
End Sub
In Visual Basic .NET, I used the Microsoft.Registry classes to access the four registry keys and store them into some class properties.
Public Sub RefreshMetaData()
Dim newMetadataAvailable As Boolean
Dim newAuthor, newTitle, newAlbum, newDuration As String
Dim CurrentMetadata As RegistryKey
CurrentMetadata = Registry.CurrentUser.OpenSubKey _
("Software\Microsoft\MediaPlayer\CurrentMetadata", _
False)
If CurrentMetadata.GetValueNames.GetLength(0) = 0 Then
newMetadataAvailable = False
Else
newMetadataAvailable = True
End If
With CurrentMetadata
newAlbum = .GetValue("Album", "No Album")
newAuthor = .GetValue("Author", "No Author")
newDuration = .GetValue("durationString", "No Duration")
newTitle = .GetValue("Title", "No Title")
End With
If (newMetadataAvailable <> Me.m_MetadataAvailable) OrElse _
(newAlbum <> Me.Album) OrElse _
(newAuthor <> Me.Author) OrElse _
(newTitle <> Me.Title) OrElse _
(newDuration <> Me.durationString) Then
'new data
m_MetadataAvailable = newMetadataAvailable
m_Album = newAlbum
m_Author = newAuthor
m_Title = newTitle
m_durationString = newDuration
m_LastRefresh = Now
Dim mcArgs As New _
MediaChangedEventArgs(Me.Album, _
Me.Author, Me.Title, _
Me.durationString)
OnMediaChange(mcArgs)
End If
End Sub
From your actual application(s) you create and use this component (by trapping the MediaChanged event), instead of performing any direct registry access yourself. Using the Visual Basic 6 component works about the same as the .NET component, but their implementation is quite different. The Visual Basic 6 version uses a timer to poll the registry to trap changes, which creates a lot of unnecessary registry access. When I built the Visual Basic .NET version, I was able to make it a bit better by using a background thread combined with a Win32 API call (RegNotifyChangeKeyValue) to monitor the appropriate registry key, so no polling was required. I wrapped up the .NET code for monitoring the registry key into its own class so that you can use it in other projects.
Imports Microsoft.Win32
Imports MonitorRegistry.NativeMethods
Public Class MonitorKey
Public Event KeyChanged As EventHandler
Dim m_SubKey As String
Dim m_RootKey As RegistryKey
Public Sub New(ByVal key As String, _
ByVal rootkey As Microsoft.Win32.RegistryKey)
m_SubKey = key
m_RootKey = rootkey
End Sub
Public Sub StartLoop()
Do
WaitForChange()
Loop
End Sub
Private Function WaitForChange() As Boolean
Dim notifyEvent As New Threading.AutoResetEvent(False)
Dim KeyHandle As IntPtr
Dim KeyToMonitor As RegistryKey
KeyToMonitor = Me.m_RootKey.OpenSubKey _
(Me.m_SubKey, False)
Dim regKeyType As Type = GetType(RegistryKey)
Dim field As System.Reflection.FieldInfo
field = regKeyType.GetField("hkey", _
Reflection.BindingFlags.Instance Or _
Reflection.BindingFlags.NonPublic)
KeyHandle = CType(field.GetValue(KeyToMonitor), IntPtr)
Dim err As Integer
err = NativeMethods.RegNotifyChangeKeyValue(KeyHandle, _
True, NotifyFilterFlags.REG_NOTIFY_CHANGE_ATTRIBUTES _
Or NotifyFilterFlags.REG_NOTIFY_CHANGE_LAST_SET _
Or NotifyFilterFlags.REG_NOTIFY_CHANGE_NAME _
Or NotifyFilterFlags.REG_NOTIFY_CHANGE_SECURITY, _
notifyEvent.Handle, True)
If err = 0 Then
notifyEvent.WaitOne()
RaiseEvent KeyChanged(CObj(Me), New EventArgs())
Return True
Else
Debug.WriteLine(err)
Return False
End If
End Function
End Class
If you download the complete code sample, you can see the registry access and monitoring code in context, which makes it quite a bit easier to understand.
Manipulating Messenger
To enable everyone to share in my current musical experience, at least in their imagination, I wrote a little system tray application that connects to the Messenger client using the client's exposed COM library.
Note I have to provide a clear caveat here; using this automation library is not supported and may cease to function with a future version of Messenger.
In my previous column, I built an application that didn't really have any UI except for an icon in the notification area of the Windows taskbar, and I am going to reuse that same concept for "ListeningTo," my music information utility. Just like in the previous column, I need a little bit of UI in the form of an Options dialog. Using this dialog, I can set up the format for my music-enabled Messenger name, and for my Messenger name for when nothing is playing.
Figure 1. The Option Dialog allows the user to set up their desired Messenger name format.
The code consists of four main sections:
- Serializing/Deserializing settings to and from a file on the user's machine.
- Handling menu clicks.
- Showing the Options dialog.
- Changing the user's Messenger name.
I will only cover the code for the last section, a routine that is called in response to the MediaChanged event of my library, and then changes the user's Messenger name if needed. For the rest of the functional areas, check out last month's column and the downloadable source for this application.
Imports Messenger
Public Class NameChanger
Public Shared Sub ChangeName(ByVal newName As String)
Try
Dim myMsg As New MsgrObject()
If Not (myMsg.LocalState = MSTATE.MSTATE_UNKNOWN _
Or myMsg.LocalState = MSTATE.MSTATE_OFFLINE) Then
Dim primaryService As IMsgrService
primaryService = myMsg.Services.PrimaryService
If primaryService.FriendlyName <> newName Then
primaryService.FriendlyName = newName
End If
End If
Catch ex As Exception
Debug.WriteLine(ex.Message)
End Try
End Sub
End Class
To make this code work, I had to add a reference to the Messenger 1.0 Type Library (shown in the COM references dialog) to my project. That is all the Messenger-related code I needed to write. I then used this NameChange class from my main program. Remember, it's not my fault if people think you are crazy once your Messenger name starts changing.
On To E-Mail
The easiest way for me to add "ListeningTo" information into your e-mail was to directly write to your signature files, assuming your e-mail program uses a text or HTML file on disk to store your signature. I'm using Outlook, so I have an RTF, HTML, and text version of my signature stored in my personal profile directory under "C:\Documents and Settings\Duncanma\Application Data\Microsoft\Signatures". The specific location of your signature files will depend on the e-mail program you are using and your personal machine setup. My little utility will work with them wherever they are. I didn't try this sample out with any RTF signatures, but since RTF is essentially just text, it would certainly be possible. Instead of creating a completely new sample, I decided to add some settings to the Option dialog (see Figure 2) of my first utility so that you can specify which signature files you wish to keep updated and what format should be used for information added to each file.
Figure 2. The Signatures section of the Option dialog allows you to configure the program to update one or more signature files.
The settings dialog is more complicated than the actual code in this example, tracking n files and associated settings took a little bit of work. I created a custom collection, like I so often do, to store (and serialize/deserialize) a set of SignatureFile objects. Binding that to a set of controls on my Option dialog was easy enough, and I used an OpenFileDialog to allow you to find your signature file. For simplicity, I replaced the entire signature file's contents with the output of this utility, instead of allowing you to specify what part of the file should be replaced. Whenever I receive a MediaChanged event from the ListeningTo library, I cycle through all of the signature files you have added through the Option dialog and tamper with each one. Yes, you read it right—I said tamper. This isn't a nice friendly touch here; I'm essentially blowing away the contents of your file and rewriting it. So unless you trust me implicitly, I'd back them up before running this.
Public Sub UpdateSignatureFiles()
Dim sigFile As SignatureFile
For Each sigFile In appSettings.sigFiles
Dim newSignature As String
If musicInfo.MetadataAvailable Then
newSignature = _
musicInfo.ToString(sigFile.NameFormat)
Else
newSignature = sigFile.NoMusicName
End If
sigFile.UpdateFile(newSignature)
Next
End Sub
If you want to have other content in your signature, just include the rest of your signature text right in the format field. I'm doing exactly that now to produce my signature in this format:
Duncan Mackenzie (MSDN)
(Listening To: Clint Eastwood [Gorillaz / Big Shiny Tunes 6])
mail: duncanma@microsoft.com
web: www.duncanmackenzie.net
blog: dotnetweblogs.com/duncanma
Hot Hot Keys
If e-mail and messenger are not enough for you, I think I've figured out the sample that will handle your needs no matter what you work with: a system-wide keyboard shortcut that copies your current musical experience onto the clipboard for insertion into whatever program you are using. Of course, if you are using a program that doesn't support pasting in from the clipboard, you are out of luck, but that shouldn't be a common issue. To create a keyboard shortcut that can be used at any time involves the use of the Win32 API, specifically the RegisterHotKey API call, and you need a Form available to receive messages through Windows Messenger.
Public Class HotKeyHandler
Implements IDisposable
Public Event HotKeyPressed As EventHandler
Dim m_HotKeyID As Int32
Dim WithEvents myForm As hkHandler
Dim atom_String As String
Public Sub New(ByVal Key As Integer, _
ByVal ALT As Boolean, _
ByVal CTRL As Boolean, _
ByVal SHIFT As Boolean, _
ByVal WINKEY As Boolean)
myForm = New hkHandler()
myForm.Hide()
Dim atomGUID As Guid = Guid.NewGuid
m_HotKeyID = NativeMethods.GlobalAddAtom( _
atomGUID.ToString)
Dim modifiers As NativeMethods.hkModifiers
If ALT Then
modifiers = NativeMethods.hkModifiers.MOD_ALT
End If
If CTRL Then
modifiers = modifiers Or _
NativeMethods.hkModifiers.MOD_CONTROL
End If
If SHIFT Then
modifiers = modifiers Or _
NativeMethods.hkModifiers.MOD_SHIFT
End If
If WINKEY Then
modifiers = modifiers Or _
NativeMethods.hkModifiers.MOD_WIN
End If
NativeMethods.RegisterHotKey(myForm.Handle, _
m_HotKeyID, modifiers, Key)
End Sub
Public Sub Dispose() _
Implements System.IDisposable.Dispose
NativeMethods.UnregisterHotKey( _
myForm.Handle, _
m_HotKeyID)
NativeMethods.GlobalDeleteAtom( _
m_HotKeyID)
myForm.Close()
End Sub
Private Sub myForm_HotKeyPressed(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles myForm.HotKeyPressed
RaiseEvent HotKeyPressed(CObj(Me), New EventArgs())
End Sub
End Class
When the keyboard shortcut is pressed, the form you registered will receive a WM_HOTKEY message (which you can catch by overriding WndProc) and then you can use Clipboard.SetDataObject to push your music information onto the clipboard. In my code I have the Form class raise an event up to the HotKeyHandler class, which in turn raises its own event that is then trapped by my main program.
In the hkHandler Form
Public Event HotKeyPressed As EventHandler
Protected Overrides Sub WndProc( _
ByRef m As System.Windows.Forms.Message)
If m.Msg = NativeMethods.WM_HOTKEY Then
RaiseEvent HotKeyPressed(CObj(Me), _
New EventArgs())
End If
MyBase.WndProc(m)
End Sub
In my main program
Dim WithEvents hotKeyHandler As HotKeyUtils.HotKeyHandler
Public Sub hotKeyHandler_HotKeyPressed( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles hotKeyHandler.HotKeyPressed
If Not musicInfo Is Nothing Then
Clipboard.SetDataObject( _
musicInfo.ToString(appSettings.HotKeyFormat))
End If
End Sub
By abstracting the creation and handling of the keyboard shortcut from the clipboard code, the HotKeyHandler class should be useable in other projects without any changes to its code.
Need More Windows Media Player Fun?
That is it for this article's sample, but I hope it gives you some ideas for your own code. If you want to see more music-related code, I have uploaded the complete source to my own personal music playing system (built around Windows Media Player) as a GotDotNet Workspace and a User Sample. Download it, check it, and then go build your own... its fun!
Coding Challenge
At the end of some of my Coding4Fun columns, I will have a little coding challenge—something for you to work on if you are interested. For this article, the challenge is to create anything related to Windows Media Player (not just Windows Media files). Managed code is preferred (Visual Basic .NET, C#, J#, or Managed C++ please), but an unmanaged component that exposes a COM interface would also be good. Just post whatever you produce to GotDotNet and send me an e-mail message (at duncanma@microsoft.com) with an explanation of what you have done and why you feel it is interesting. You can send me your ideas whenever you like, but please just send me links to code samples, not the samples themselves (my inbox thanks you in advance).
Resources
See last month's column for a list of samples from GotDotNet that I used for the system tray icon and for creating a strongly typed collection, but here is a list of selected samples that you might find interesting:
- A very cool Data-based TreeView by MadsNissen
- A much-needed FTP component by Vick
- Some great TimeZone code by MichaelRBrumm
Have your own ideas for hobbyist content? Let me know at duncanma@microsoft.com, and happy coding!
Coding4Fun
Duncan Mackenzie is the Microsoft Visual Basic .NET Content Strategist for MSDN during the day and a dedicated coder late at night. It has been suggested that he wouldn't be able to do any work at all without his Earl Grey tea, but let's hope we never have to find out. For more on Duncan, see his site.