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 STAThreaded but WaitAll depends on MTAThreaded code). I created an array of ManualResetEvents, 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.

Comments

Comments powered by Disqus