A few of you have emailed, wondering if I'm still alive, or if I'm working more D3DImage video player. The answer is "YES!".
I have been very busy with work, but I found some time here and there to work on what I am calling (for the lack of a better name), the "WPF Multimedia Library". Sounds pretty self explanatory, no? The quick synopsis, it's a lib to quickly and easily create custom video/audio players in WPF. And hopefully some XAML enabled sound processing controls, though I have to check to see if I can release that part(it may be part of a closed source app).
So, yeah, the multimedia library, I mentioned quick and easy? I suppose those are both subjective terms, but I have been spending a lot of time refactoring and massaging the architecture to make it as easy as possible. Let me explain a little of where I'm at.
COM and DispatcherObject sitting in a tree...
The first thing I wanted to tackled was a tidy way of communicating with our DirectShow COM objects. See, COM is subject to threading apartment rules of Single-Threaded-Apartment or Multi-Threaded-Apartment. To be said simply an object created in STA can only be accessed via the same thread that created it. An object created in MTA can be called from any *MTA* thread. An STA thread can not talk to an MTA object and vise-versa.
So this MTA/STA ish sounds like a problem in WPF because it uses an STA UI thread and you don't want to create the DirectShow COM objects on the UI thread for threat of blocking the UI thread (bad!) when you make blocking calls to the COM objects. But couldn't we create all the COM objects under an MTA thread? Yes, but the WPF STA thread won't be able to access them (see previous paragraph). The problem is solved by sub-classing DispatcherObject and running the Dispatcher on it's own thread. To access your COM safely, you use the familiar WPF Dispatcher->Invoke/BeginInoke. So now all COM functionality (DirectShow in this case) is neatly wrapped up in it's own DispatcherObject. Here is an example factory method:
///
/// Creates a new DirectShowEngine,
/// running on it's own Dispatcher
///
public static DirectShowEngine CreateDirectShowEngine()
{
DirectShowEngine engine = null;
var reset = new ManualResetEvent(false);
var t = new Thread((ThreadStart) delegate
{
engine = new DirectShowEngine();
/* We queue up a method to execute
* when the Dispatcher is ran.
* This will wake up the calling thread. */
engine.Dispatcher.Invoke((Action)(() => reset.Set()));
Dispatcher.Run();
}) {Name = "DirectShow Graph"};
/* We can run in MTA also */
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
/* Starts the thread and creates the object */
t.Start();
/* We wait until our object is created and
* the new Dispatcher is running */
reset.WaitOne();
return engine;
}
Interfaces and Base Classes
Now that the COM crap was taken care of, I had to come up with a extensible way to make custom WPF DirectShow players. This was sort of tough considering the different ways one would want to use or implement DirectShow. So I started writing a WPF abstract control (eh, FrameworkElement), that was similar to the MediaElement API (minus the Source DP). I chose to start there because MediaElement's usage is pretty simple and it covered the most common usages of media playback in WPF. Next, I refactored around the aforementioned DispatcherObject base class to encapsulate all the functionality of my FrameworkElement control. To put it simply, the FrameworkElement control is a wrapper for the custom DispatcherObject that houses the COM.
Putting it Together
So now that I have the FrameworkElement, MultimediaElementBase, and the DispatcherObject, MediaPlayerBase, that it wraps, I was ready to build a player that plays media files. The first step was to sub-class MediaPlayerBase and create a property to send the Uri name and the DirectShow functionality to play the Uri. Next I sub-classed the MultimediaElementBase and added a "Source" Uri, registered the custom MediaPlayerBase with the MultimediaElementBase. Wired in the "Source" Uri DP to change the source on the custom MediaPlayerBase, and viola! It may sound like a lot, but really it's ~70 lines of code (not counting comments) for your own custom "MediaElement".
Not quite there...
There's still a bit more work to do, lots of refactoring left. There's still other features I need to add, such as being able to select the audio output device or getting a list of available audio devices, etc. But I'll get this out as soon as I can and it'll be cool. I promise!
-Jer