Documenting code seems to be the last thing coders are interested in. However, documenting code can de very handy in early stages. Once you have your design and start coding then it helps debugging to document every piece of code you write. In a case of a method I start describing what it should do, what its parameters are used for and what its return value will be upon success and failure. Sometimes I add additional remarks which should describe its usage better. Once I've done that I'll compare it to what it actually does. It turns out that I do find a lot of bugs in this stage when the advertized behaviour is not the one found. But the disadvantage is that the overall architecture never gets this treatment. Erm... Well, I tend to "forget" that.
This is an attempt to decribe the history and overall design of ObjectMedia and DXPlayer. It also describes why ObjectMedia popped up, eventhough nobody (including me) wanted it.
Brad wanted a plugin for DesktopX that played MP3 files natively. He knew I was busy writing a DX plugin and he thought it would be cool (and probably a good plan for longterm product continuation) to make it an official Stardock project. Another ex-SD coder once tried to write a player using MCI only and it was his assumption that this would make a very fast player with a minimum overhead. Due to a hard disk crash that coder lost his code, so I should start from scratch. Looking back writing from scratch was the best thing that could ever happen to OM.
When I started to write DXPlayer I started to look at how I had written WBAmp. WBAmp is a WindowBlinds plugin that is a simple remote control for WinAmp and CoolPlayer. That didn't bring me very far, because the WinAmp and CoolPlayer plugin interfaces are very limited. WBAmp is written in pure C and is not the greatest invention since sliced bread.
Some time before DXPlayer there wasn't a Stardock plugin interface like we have now for DesktopX and ObjectBar. Stardock realized it needed one. The idea was to design a plugin interface that worked for all Stardock products, including WindowBlinds. Neil and Ian got together to design such an interface. Alberto was not involved yet (I think). Because of my WBAmp fame (hehe) I was asked to join too. Somehow that initiative died. I think the main reason was that WB was too different to use a general plugin interface. Sometime later DesktopX needed a plugin interface anyway, so Alberto designed his own. Sometime later he redesigned it with Ian. When ObjectBar came along changes were made that allowed Jeff's stuff to be incorporated too.
Because the plugin interfaces were not very clear when I started there were only a few assumptions I could make. A plugin was not able to do subclassing without tricks. In WindowBlinds subclassing was taboo and in DesktopX it was impossible without doing some nasty tricks. Even now, in a time in which DX plugins can do subclassing, it is not recommended.
A Windows based player needs subclassing because the several low level layers send messages to the player which it must handle.
So a player needs to do subclassing. There is no way to write one without it. My solution was to write a standalone player with no user interface of its own. Besides, making a player a part of a plugin itself is also a very bad idea, because it will be part of each plugin and each instance that you need. I was asked to use the common plugin interface used by DesktopX for the player itself, but that was impossible. I had a hard time explaining that, because of the subclassing limitations and because a plugin (which is usually a DLL) cannot serve two masters at the same time. Meaning: A plugin can only be part of one process. The DX plugin interface used callback functions and for DXPlayer it was impossible to call both the DX and player callback fumctions.
This lead to the OM plugin interface decribed in
ObjectMedia Visualization..
It looked like nobody understood these problems, but I continued with the plan to write a stand alone player. I can remember discussions in which I was not able to defend the design. Nobody understood the problems involved on a practical level. Afterall, I was also involved in the design of the plugin interface, so people assumed that I would use it myself. At least that was my impression of the events at the time. I have always been a pain in the ass so I continued with the plan, whether Stardock liked it or not. I had no alternative, unless I wanted to sell Brad a simpele no. I would never do that. To make things simple: ObjectMedia is a standalone player without (or with a minimum) user interface and DXPlayer (or WBAmp) should provide that use rinterface.
At the same time I was working on a games engine and that engine was using DirectShow for its video and sound clips. DirectShow is incredibly elegant and the same code is used to write audio and video functionality. Brad wanted a MP3 player only, but there wouldn't be very much extra effort to make the player handle any DirectShow supported format. That was probably something else that was frowned upon, because if you do not know the internals then one would expect that you would make an extensive extra effort to support a whole range of clip formats. MCI was encapsulated by DirectShow and it also hid hardware specific details. Another reason to use DirectShow. Looking back it wasn't a bad decision at all. Microsoft greatly improved DirectShow and built layers like Windows Media and Digital Rights Management on it. This will not change the in the near future and it just means that DIrectShow is here to stay for the foreseeable future.
Here you are: We needed ObjectMedia (which was previously known as SDPlayer). Just for practical reasons. I still have that uneasy feeling that Stardock thinks I pushed a standalone player just because I liked it. Frankly, I don't care anymore. It's time to move on.
OK. Now that DirectShow was chosen, how should ObjectMedia and DXPlayer be implemented. I love an object oriented approach and I am also very pragmatic. If you look at the player part (the server) then all of its functionality should be available to the plugin part (the client) too. So, I designed a singleton that should work for both the client and the server. This singleton is called tGenericPlayer. It has methods like Play(), Pause(), Load(), etc. The client implementation of the methods just contains interprocess communication functionality and the server side the actual implemetation. So, tGenericPlayer is an abstract singleton which can be used for specializations for the client and server side.
DXPlayer not only supports OM, but it also supports WA and CP. To promote polymorphism all these clients should have a common class which encapsulates detecting, running, closing, showing and hiding a player. So from tGenericPlayer a class was derived called tAnyPlayer. All the clients communicate with the player by sending WM_COMMAND and WM_COPYDATA messages. From tAnyPlayer a class was derived called tMessagePlayer in which that kind of interprocess communication was encapsulated. Not all players have the same functionality and also the details can vary from player to player. To encapsulate these details the tStardockPlayer, tWinAmp and tCoolPlayer classes were derived from tMessagePlayer.
This design greatly promotes polymorphism. To make it easier for a plugin writer to hide the details of which player is actually used a simple standard C function was designed which creates the correct instance of the player actually used. Here is a simple example to play a clip:
tAnyPlayer* Player=GetActivePlayer(true);
if(Player)
{
Player->Play();
}
The GetActivePlayer() function returns an instance of the currently active player. This can be either an instance of tStardockPlayer, tWinAmp or tCoolPlayer. If no player was running at the time then the "true" argument indicates that one should be started. Depending on what players are installed the correct player will be started. If no player can be found then the function returns 0. Therefore one should check for a non-zero value before using the player. The Play() method simply cause ObjectMedia, WinAmp or CoolPlayer to play the current clip. Brad can be called a visionary even when (in this case) he didn't intend to be one. Two weeks ago or so he wanted ObjectMedia to open a clip when one pressed play and there was no clip loaded. This has a cool side effect when the instance returned is a tStardockPlayer class. When no clips are in the playlist then Play() opens a file dialog box and asks which clip to play. Hehe. Cool, hey?
There is one additional function we need to initialize the client system. At the beginning of the plugin (or program) one should call the InitializePlayer() function. Passing it a true Boolean argument (or ommitting it) causing it to support all players. Passing false causes it to support ObjectMedia only. Before the plugin is unloaded or before the program is terminated one must call the FinalizePlayer() function.
There is no need to destroy the objects returned by GetActivePlayer(), because the actual object creation is done by InitializePlayer() and it desctruction is done by FinalizePlayer().
Polymorphism and player encapsulation is near perfect. We do not need to know what player we are using. All methods return a value which tells us if the function succeeded or not. It can return:
command_Success: The method succeed.
command_Failure: The method failed because of some error.
command_Unknown: The method is not supported by the given player.
If you need to check this value then in practice you should only check for command_Success and never for command_Failure or command_Unknown. So you check for the result being equal to or the result being not equal to command_Success.
Chances are slim that GetActivePlayer() will ever return 0, because it can also return an instance of an auxiliary class called tDummyPlayer. That class contains default handling. It greatly reduced the complexity involved in writing DXPlayer in situations where a player was busy starting and not available yet or when it was shutting down. Afterall we do not want DesktopX to wait for a player, do we?
In ObjectMedia a class called tStardockPlayer is derived from tGenericPlayer. This class obviously contains the actual implementation. And no, it does not handle WinAmp or CoolPlayer. The client classes in DXPlayer work directly with the players and ObjectMedia is not in anyway connected to WA and CP. Note that tStardockPlayer does not have a name clash (although both the client and server are using the same name) because the OM's tStardockPlayer is part of a different library (and therefore a different namespace).
OM has several other auxiliary classes which are called drivers. Each driver is (directly or indirectly) derived from tMediaDriver. That class contains a lot of abstract methods, but it also implements several MCI based functions to control volume, balance, mute, bass, treble, CD-ROM eject/insert, etc. It has abstract functions like Play(), Pause(), SetPosition(), FullScreen(), etc. Depending on the clip format a class is created (derived from tMediaDriver) which contains the impelemtation details for that format. If a MP3 file is loaded that instance will be either of class tDirectShow or tDMO. The tDMO class is derived from tDirectShow and implements the special sound effects like the parametric equalizer. This allows to ommit the extra overhead involved when the user does not want these sound effects. The system can also be extended by classes specialized in CD-audio, DVD or anything else we haven't thought of yet.
All the classes mentioned in this article are singletons. All their methods could easily be plain C functions, but that would make the overall design so much harder and less elegant. Elegant is subjective. tGenericPlayer contains about 650 methods and each new version of OM adds new methods to it. For that reason I maybe should call it a mutant and should not be proud of it.