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.

Comments

  • Anonymous
    April 21, 2005
    What happens when I'm listening to a track during the afternoon of my 50th day of uptime and the tick count rolls over?
  • Anonymous
    April 21, 2005
    Drew,
    Then my stupid little application crashes and burns.

    I did say it wasn't production code, didn't I?
  • Anonymous
    April 21, 2005
    Sorry. I sometimes forget that I'm not looking for bugs in every piece of code I see.

    TODO: Curb my overactive tester Spidey-sense.
  • Anonymous
    April 21, 2005
    Drew, keep it up, it keeps me on my toes :)
  • Anonymous
    April 21, 2005
    So how can i make this little app pull up the track names if the CD has never been played before on WMP10?

    I noticed that the tracks show up as

    Track 1 (Track 1)
    Track 2 (Track 2)
    ...

    if the CD has never been played before in media player. But if I run the app after openign it once in media player, I get the track names just fine...

    BTW, I don't know the mechanism behind what kind of web service provides Audio CD's with track names and more metadata - It would be nice if you can talk a bit about this in your blog.
  • Anonymous
    April 21, 2005
    This is what I did for the timing loop:

    System.Threading.AutoResetEvent evt = new System.Threading.AutoResetEvent(false);
    evt.WaitOne((int)(duration * 1000), false);

    he he :)
  • Anonymous
    April 21, 2005
    Vatsan, I don't know, I don't have any CDs that fall into that category. Let me check a second...


    From the documentation for the media library:
    If the user plays a digital media file that is not added to Media Library, you have access to the attributes that are in the file.

    If the user plays a digital media file that has been added to Media Library, you have access to the attributes that are stored only in Media Library, the attributes that are stored only in the file, and the attributes that are stored in both Media Library and the file.

    Somehow you have to add the CD to the library, but I'm not sure how to do that... Sorry :(