Playing Audio CDs, part 2

Yesterday, I looked a a simple application for dumping the track information on a CD.
Since I'm going to be working on this concept (playing a CD) for a while, I figured I'd restructure the app to make it somewhat more generic.
First off, lets define a pure virtual base class for the player...
class CCDPlayer<br>{<br>public:<br>    virtual ~CCDPlayer(void) {};<br>    virtual HRESULT Initialize() = 0;<br>    virtual HRESULT DumpTrackList() = 0;<br>    virtual HRESULT PlayTrack(int TrackNumber) = 0;<br>};  
It's a really trivial class, it defines an Initialization step, dumping the tracks and playing a track. 
Now, lets take the core application and rewrite it using this base class.
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])<br>{<br>    int nRetCode = 0;<br>    CWMPCDPlayer wmpCdPlayer;<br>    CCDPlayer *player = &wmpCdPlayer;<br>    HRESULT hr;<br>    // initialize MFC and print and error on failure<br>    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))<br>    {<br>        // TODO: change error code to suit your needs<br>        _tprintf(_T("Fatal Error: MFC initialization failed\n"));<br>        nRetCode = 1;<br>    }<br>    else<br>    {<br>        hr = player->Initialize();<br>        if (hr != S_OK)<br>        {<br>            printf("Couldn't initialize CD player: %x\n", hr);<br>            return 1;<br>        }<br>        hr = player->DumpTrackList();<br>        if (hr != S_OK)<br>        {<br>            printf("Couldn't dump track database: %x\n", hr);<br>            return 1;<br>        }<br>        int trackNumber;<br>        printf("Pick a track: ");<br>        scanf("%d", &trackNumber);<br>        hr = player->PlayTrack(trackNumber);<br>    }<br>    return nRetCode;<br>}
Much better (simpler).  Now we can concentrate on the meat of the problem, the actual player implementation.
Here's a complete implementation of the CWMPCDPlayer class:
CWMPCDPlayer::CWMPCDPlayer(void)<br>{<br>}<br>CWMPCDPlayer::~CWMPCDPlayer(void)<br>{<br>    CoUninitialize();<br>}<br>HRESULT CWMPCDPlayer::Initialize()<br>{<br>    HRESULT hr;<br>    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);<br>    if (hr != S_OK)<br>    {<br>        printf("Couldn't initialize COM: %x\n", hr);<br>        return hr;<br>    }<br>    hr = _CdRomCollection.CoCreateInstance(__uuidof(WMPLib::WindowsMediaPlayer));<br>    if (hr != S_OK)<br>    {<br>        printf("Couldn't instantiate CDRom player: %x\n", hr);<br>        return hr;<br>    }<br>    long driveCount;<br>    hr = _CdRomCollection->get_count(&driveCount);<br>    if (hr != S_OK)<br>    {<br>        printf("Couldn't get drive count: %x\n", hr);<br>        return hr;<br>    }<br>    if (driveCount == 0)<br>    {<br>        printf("Machine has no CDROM drives\n");<br>        return E_FAIL;<br>    }<br>    return S_OK;<br>}<br>HRESULT CWMPCDPlayer::DumpTrackList()<br>{<br>    HRESULT hr;<br>    CComPtr<WMPLib::IWMPCdrom> cdromDrive;<br>    cdromDrive = _CdRomCollection->Item(0);<br>    hr = cdromDrive->get_Playlist(&_Playlist);<br>    if (hr != S_OK)<br>    {    <br>        printf("Couldn't get playlist for CDRom drive: %x\n", hr);<br>        return hr;<br>    }<br>    for (int i = 0 ; i < _Playlist->count ; i += 1)<br>    {<br>        CComPtr<WMPLib::IWMPMedia> item;<br>        hr = _Playlist->get_Item(i, &item);<br>        if (hr != S_OK)<br>        {<br>            printf("Couldn't get playlist item %d: %x\n", i, hr);<br>            return hr;<br>        }<br>        printf(_T("Track %d (%S)\n"), i, item->name.GetBSTR());<br>    }<br>    return S_OK;<br>}<br>HRESULT CWMPCDPlayer::PlayTrack(int TrackNumber)<br>{<br>    HRESULT hr;<br>    CComPtr<WMPLib::IWMPPlayer> player;<br>    CComPtr<WMPLib::IWMPControls> controls;<br>    CComPtr<WMPLib::IWMPMedia> media;<br>    hr = player.CoCreateInstance(__uuidof(WMPLib::WindowsMediaPlayer));<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't create player instance: %x\n"), hr);<br>        return hr;<br>    }<br>    hr = player->put_currentPlaylist(_Playlist);<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't set player playlist: %x\n"), hr);<br>        return hr;<br>    }<br>    hr = player->get_controls(&controls);<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't get player controls: %x\n"), hr);<br>        return hr;<br>    }<br>    hr = _Playlist ->get_Item(TrackNumber, &media);<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't get playlist item: %x\n"), hr);<br>        return hr;<br>    }<br>    hr = controls->put_currentItem(media);<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't set player control item: %x\n"), hr);<br>        return hr;<br>    }<br>    hr = controls->play();<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't get playlist item: %x\n"), hr);<br>        return hr;<br>    }<br>    double duration;<br>    hr = media->get_duration(&duration);<br>    if (hr != S_OK)<br>    {<br>        printf(_T("Couldn't get track duration: %x\n"), hr);<br>        return hr;<br>    }<br>    //<br>    // Wait for the track to stop playing.<br>    //<br>    MSG msg;<br>    DWORD tickCountStart = ::GetTickCount();<br>    while (GetMessage(&msg, NULL, 0, 0) && GetTickCount() < tickCountStart+(duration*1000))<br>    {<br>        TranslateMessage(&msg);<br>        DispatchMessage(&msg);<br>    }<br>    return S_OK;<br>}
That's it! I left out the WMP Cdplayer class definition, it consists of a derivation of the base class, and the addition of two member variables:
    CComPtr<WMPLib::IWMPCdromCollection> _CdRomCollection;
    CComPtr<WMPLib::IWMPPlaylist> _Playlist;
These exist to carry information across the various abstract methods.
Most of the code above is a repackaging of the code from yesterdays example, the new bits are the PlayTrack method.  There looks like a lot of code in the playback code, but it's actually pretty straightforward.  You allocate a player object to actually perform the playback, and set the current playlist on the player.  You get access to the controls of the player (that's the controls object), tell the control what track to play, and then ask the control to start playing.
I then wait until the track completes before returning to the caller.  Please note that the wait loop pumps windows messages.  This is because we're an STA application and when you're in an STA application, you need to pump messages instead of blocking, because STA COM objects use windows messages to communicate (and the WMP class is an STA object).
Tomorrow, I'll talk about the next method for doing CD playback, the MCI command set.