Windows Media Player madness
Recently, I tried to write a simple tool using C#/.NET Framework that would allow me to gather information about movie files. As I wanted to be able to get the size, format and length of every movie file that WMP can open, I decided to use its SDK. Read on to see why this is not as easy as it sounds.
Step 0 - Preparation
I downloaded the WMP 10 SDK, including all sample files and I read the documentation :)
Step I - The simple approach
The simple, obvious approach:
WindowsMediaPlayer player = new WindowsMediaPlayer ();
Media = player.newMedia ();
duration = media.duration; // etc.
// more queries using getItemInfo
Doesn’t work, as the player’s openState
must change to MediaOpen
before you can start to query it.
Step II - Being clever
Ok, I thought, that would have been too easy. Reading the documentation, I discovered that the player would trigger the OpenStateChanged
event when it opens a media. Beware, as this will only work for the current file - i.e. player.currentMedia
. Which means you can only wait for one media to open at a time. Great, I thought. I wrote a quick event handler, and ran it inside a foreach
loop. The OpenStateChanged
event was fired exactly once. Time for another approach.
Step III - Threads to the rescue
Obvioulsy, what was needed was a way to start up a load process and let it run until it finishes, and then return to the main process. My aim was to create a process function that would create a new WindowsMediaPlayer
instance, call the newMedia
method and wait until the OpenStateChanged
event occurs. Then, it would get the data about the movie inside the event handler and store them. Finally, it would trigger some signal so the main thread knows the open/read/close cycle ended. I looked into the threading examples that ship with VS.NET 2003. After some reading, I discovered that I was supposed to create a ManualResetEvent
, pass this to the working thread and wait for it using a WaitHandle
.
Side battle field
One thing I tried during this was to use the WaitHandle.WaitAll
method on an array of objects. Don’t do this, enabling this needs some tricks in a Windows Forms application (as it is STAThread
ed but WaitAll
depends on MTAThread
ed code). I created an array of ManualResetEvent
s, passed one to each worker thread and called WaitAll
- hoping this would work. It didn’t, for several reasons: First of all, it created around 800 threads, as every worker thread had it’s own local media player which was multithreaded on its own. This thread count (several thousand threads!) eventually crashed the application. Second, the individual threads did start and end at different times, and it was impossible to nail down when each thread was done - sometimes, they started working after the WaitAll
call!.
Step IV - The end
My last approach was to create a special object that would have a ManualResetEvent
embedded and which would only return when the event handler finished its work. This does indeed work, but, as usual, it’s not possible to use it in production code. As I started working more with this approach, I found out that the media player tries to open sometimes files that are not of type “video”. To address this issue, I added a special call inside the event handler:
if (media.getItemInfo ("MediaType") == "video")
this.valid = true;
Later, I wanted to query this bool to decide whether to trigger the OnValidMovie
or the OnInvalidFile
of my backend. The curious thing was the events were fired before the object finished creating, meaning that it was in an undefined state. Even when I tried to throw an exception inside the object the event handler was already called before the exception had a chance to be thrown, although the object would not return before its internal ManualResetEvent
occured! Moving the reset event outside the object didn’t help, starting the object inside an own thread did also not help. The events would be triggered before the exception was thrown, no matter what I did. The code is:
System.Diagnostics.Trace.WriteLine ("openStateChangedHandler finished");
manualResetEvent.Set ();
Guess what, the output happens after the event was set! That’s it, I’ve got enough of this kind of stuff, it takes far too much time which I don’t want to spend on such a stupid problem.