//------------------------------------------------------------------------
//
//  Microsoft Windows 
//  Copyright (C) Microsoft Corporation, 2001
//
//  File:      mbBehave.cpp
//
//  Contents:  mediaBar player behavior
//
//  Classes:   CMediaBehavior
//
//------------------------------------------------------------------------

#include "priv.h"
#define INITGUID        // pull in additional declaration for private mediabar IIDs
#include "initguid.h"
#include "mbBehave.h"
#undef INITGUID
#include "mediaBand.h"
#include "mediautil.h"
#include "varutil.h"
#include <mluisupp.h>
#include "resource.h"

//================================================================================================
//  CMediaBehavior
//================================================================================================

#define NO_COOKIE   -1


// event names fired from this behavior:
// NOTE: need to match the enums in the mbBehave.h with this array!!!
struct _eventInfo {
    LONG        levtCookie;
    LPWSTR      pwszName;
} s_behaviorEvents[] =
{
    NO_COOKIE,  L"OnOpenStateChange",
    NO_COOKIE,  L"OnPlayStateChange",
    NO_COOKIE,  L"OnShow",
    NO_COOKIE,  L"OnHide",
};

#ifndef WMPCOREEVENT_BASE
// ISSUE/010430/davidjen  should be pulled in from wmp.idl, but this file is not part of source tree
#define INITGUID        // define GUID, not only declare it
#include "initguid.h"
DEFINE_GUID(DIID__WMPOCXEvents,0x6BF52A51,0x394A,0x11d3,0xB1,0x53,0x00,0xC0,0x4F,0x79,0xFA,0xA6);
#define WMPCOREEVENT_BASE                       5000
#define DISPID_WMPCOREEVENT_OPENSTATECHANGE     (WMPCOREEVENT_BASE + 1)
#define WMPCOREEVENT_CONTROL_BASE               5100
#define DISPID_WMPCOREEVENT_PLAYSTATECHANGE     (WMPCOREEVENT_CONTROL_BASE + 1)
#undef INITGUID
#endif



// class factories
//------------------------------------------------------------------------
CMediaBehavior *
    CMediaBehavior_CreateInstance(CMediaBand* pHost)
{
    return new CMediaBehavior(pHost);
}

//------------------------------------------------------------------------
CMediaItem *
    CMediaItem_CreateInstance(CMediaBehavior* pHost)
{
    return new CMediaItem(pHost);
}

//------------------------------------------------------------------------
CMediaItemNext *
    CMediaItemNext_CreateInstance(CMediaBehavior* pHost)
{
    return new CMediaItemNext(pHost);
}

//------------------------------------------------------------------------
CPlaylistInfo *
    CPlaylistInfo_CreateInstance(CMediaBehavior* pHost)
{
    return new CPlaylistInfo(pHost);
}



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// class CMediaBehavior
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------
CMediaBehavior::CMediaBehavior(CMediaBand* pHost)
  : CImpIDispatch(LIBID_BrowseUI, 1, 0, IID_IMediaBehavior),
    _cRef(0),
    _dwcpCookie(0),
    _fDisabledUI(FALSE),
    _fPlaying(FALSE)
{
    ASSERT(pHost);
    _pHost = pHost;
    if (_pHost)
    {
        _pHost->AddRef();
        HRESULT hr = _pHost->addProxy((IContentProxy*)this);
        ASSERT(SUCCEEDED(hr));
    }
}

//------------------------------------------------------------------------
CMediaBehavior::~CMediaBehavior()
{
    Detach();   // to be sure...
    if (_pHost)
        _pHost->Release();
}


//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CMediaBehavior, IElementBehavior),
        QITABENT(CMediaBehavior, IMediaBehavior),
        QITABENT(CMediaBehavior, IDispatch),
        QITABENTMULTI2(CMediaBehavior, DIID__WMPOCXEvents, IDispatch),
        QITABENT(CMediaBehavior, IContentProxy),
        QITABENT(CMediaBehavior, IMediaBehaviorContentProxy),
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}



//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::Detach(void)
{
    _ConnectToWmpEvents(FALSE);

    // detach from behavior site
    if (_pHost)
    {
        _pHost->removeProxy(SAFECAST(this, IContentProxy*));    // optimize: _pHost saves ptr as IContentProxy, this saves a QI
        _pHost->Release();
        _pHost = NULL;
    }
    _fPlaying = FALSE;

    if (_apMediaItems != NULL)
    {
        int cnt = _apMediaItems.GetPtrCount();
        for (int i = 0; i < cnt; i++)
        {
            CMediaItem* pItem = _apMediaItems.GetPtr(i);
            if (pItem) 
                pItem->Release();
        }
        _apMediaItems.Destroy();
    }

    _pBehaviorSite.Release();
    _pBehaviorSiteOM.Release();
    for (int i = 0; i < ARRAYSIZE(s_behaviorEvents); i++)
    {
        s_behaviorEvents[i].levtCookie = NO_COOKIE;
    }
    
    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::Init(IElementBehaviorSite* pBehaviorSite)
{
    ASSERT(pBehaviorSite);
    if (pBehaviorSite == NULL)  return E_POINTER;

    _pBehaviorSite = pBehaviorSite;

    pBehaviorSite->QueryInterface(IID_PPV_ARG(IElementBehaviorSiteOM, &_pBehaviorSiteOM));
    ASSERT(_pBehaviorSiteOM != NULL);

    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::Notify(LONG lEvent, VARIANT* pVar)
{
// ISSUE/000923/davidjen 
// these enums require behavior.idl; this idl is only available in inetcore,
// might have to be moved to shell/published or mergedcomponents
/*
    switch (lEvent) {
    case BEHAVIOREVENT_CONTENTCHANGE:
        break;
    case BEHAVIOREVENT_DOCUMENTREADY:
        break;
    }
*/
    return S_OK;
}


// *** IDispatch ***
//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr)
{
    if (!_ProcessEvent(dispidMember, pdispparams->cArgs, pdispparams->rgvarg))
    {
        return CImpIDispatch::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr);
    }
    return S_OK;
}

//------------------------------------------------------------------------
BOOL CMediaBehavior::_ProcessEvent(DISPID dispid, long lCount, VARIANT varParams[])
{
    BOOL fHandled = FALSE;
    switch (dispid)
    {
      case DISPID_WMPCOREEVENT_PLAYSTATECHANGE:
          ASSERT(lCount == 1);
          ASSERT(V_VT(&varParams[0]) == VT_I4);
          fireEvent(OnPlayStateChange);
          fHandled = TRUE;
          break;
      case DISPID_WMPCOREEVENT_OPENSTATECHANGE:
          ASSERT(lCount == 1);
          ASSERT(V_VT(&varParams[0]) == VT_I4);
          fireEvent(OnOpenStateChange);
          fHandled = TRUE;
          break;
      default:
          fHandled = FALSE;
          break;
    }
    return fHandled;
}


// *** IMediaBehavior ***
//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::playURL(BSTR bstrURL, BSTR bstrMIME)
{
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }
    _fPlaying = TRUE;
    _pHost->playURL(bstrURL, bstrMIME);
    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::stop()
{
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }
    return IUnknown_Exec(SAFECAST(_pHost, IMediaBar*), &CLSID_MediaBand, FCIDM_MEDIABAND_STOP, 0, NULL, NULL);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::playNext()
{
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }
    return IUnknown_Exec(SAFECAST(_pHost, IMediaBar*), &CLSID_MediaBand, FCIDM_MEDIABAND_NEXT, 0, NULL, NULL);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_currentItem(IMediaItem **ppMediaItem)
{
    if (ppMediaItem == NULL)
        return E_POINTER;
    *ppMediaItem = NULL;

    HRESULT hr = S_OK;
    if (_apMediaItems == NULL) {
        _apMediaItems.Create(2);
    }
    if (_apMediaItems == NULL)
        return E_OUTOFMEMORY;

    CMediaItem *pItem = CMediaItem_CreateInstance(this);
    if (pItem)
    {
        pItem->AddRef();    // keep object alive with ref count >= 1
        hr = pItem->AttachToWMP();
        if (SUCCEEDED(hr))
        {
            hr = pItem->QueryInterface(IID_PPV_ARG(IMediaItem, ppMediaItem));
//            pItem->AddRef();
//            _apMediaItems.AppendPtr(pItem);     // keep a ref for us
        }
        pItem->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_nextItem(IMediaItem **ppMediaItem)
{
    if (ppMediaItem == NULL)
        return E_POINTER;
    *ppMediaItem = NULL;

    HRESULT hr = S_OK;
    if (_apMediaItems == NULL) {
        _apMediaItems.Create(2);
    }
    if (_apMediaItems == NULL)
        return E_OUTOFMEMORY;

    CMediaItemNext *pItem = CMediaItemNext_CreateInstance(this);
    if (pItem)
    {
        pItem->AddRef();    // keep object alive with ref count >= 1
        hr = pItem->AttachToWMP();
        if (SUCCEEDED(hr))
        {
            hr = pItem->QueryInterface(IID_PPV_ARG(IMediaItem, ppMediaItem));
//            pItem->AddRef();
//            _apMediaItems.AppendPtr(pItem);     // keep a ref for us
        }
        pItem->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_playlistInfo(IPlaylistInfo **ppPlaylistInfo)
{
    if (ppPlaylistInfo == NULL)
        return E_POINTER;
    *ppPlaylistInfo = NULL;

    HRESULT hr = S_OK;
    if (_apMediaItems == NULL) {
        _apMediaItems.Create(2);
    }
    if (_apMediaItems == NULL)
        return E_OUTOFMEMORY;

    CPlaylistInfo *pInfo = CPlaylistInfo_CreateInstance(this);
    if (pInfo)
    {
        pInfo->AddRef();    // keep object alive with ref count >= 1
        hr = pInfo->AttachToWMP();
        if (SUCCEEDED(hr))
        {
            hr = pInfo->QueryInterface(IID_PPV_ARG(IPlaylistInfo, ppPlaylistInfo));
//            pItem->AddRef();
//            _apMediaItems.AppendPtr(pInfo);     // keep a ref for us
        }
        pInfo->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}


//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_hasNextItem(VARIANT_BOOL *pfhasNext)
{
    if (pfhasNext == NULL)
    {
        return E_POINTER;
    }
    *pfhasNext = VARIANT_FALSE;
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }

    LONG currTrack = 0;
    LONG cntTracks = 0;
    HRESULT hr = getPlayListIndex(&currTrack, &cntTracks);
    if (SUCCEEDED(hr))
    {
        *pfhasNext = ((currTrack >= 0) && (currTrack < cntTracks - 1)) ? VARIANT_TRUE : VARIANT_FALSE;
    }

    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_playState(mbPlayState *pps)
{
    if (pps == NULL)
        return E_POINTER;
    *pps = mbpsUndefined;

    CComDispatchDriver pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (FAILED(hr) || !pwmpPlayer)
    {
        // player not created yet, state undefined
        *pps = mbpsUndefined;
        return S_OK;
    }

    CComVariant vtPlayState;
    hr = pwmpPlayer.GetPropertyByName(L"playState", &vtPlayState);
    if (SUCCEEDED(hr) && (V_VT(&vtPlayState) == VT_I4))
    {
        *pps = (mbPlayState) V_I4(&vtPlayState);
    }
    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_openState(mbOpenState *pos)
{
    if (pos == NULL)
        return E_POINTER;
    *pos = mbosUndefined;

    CComDispatchDriver pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (FAILED(hr) || !pwmpPlayer)
    {
        // player not created yet, state undefined
        *pos = mbosUndefined;
        return S_OK;
    }

    CComVariant vtOpenState;
    hr = pwmpPlayer.GetPropertyByName(L"openState", &vtOpenState);
    if (SUCCEEDED(hr) && (V_VT(&vtOpenState) == VT_I4))
    {
        *pos = (mbOpenState) V_I4(&vtOpenState);
    }
    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_enabled(VARIANT_BOOL *pbEnabled)
{
    if (pbEnabled == NULL)
        return E_POINTER;
    *pbEnabled = VARIANT_FALSE;

    CComDispatchDriver pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (FAILED(hr) || !pwmpPlayer)
    {
        // player not created yet, state undefined
        *pbEnabled = VARIANT_FALSE;
        return S_FALSE;
    }

    CComVariant vtEnabled;
    hr = pwmpPlayer.GetPropertyByName(L"enabled", &vtEnabled);
    if (SUCCEEDED(hr) && (V_VT(&vtEnabled) == VT_BOOL))
    {
        *pbEnabled = V_BOOL(&vtEnabled);
    }
    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::put_enabled(VARIANT_BOOL bEnabled)
{
    CComDispatchDriver pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (FAILED(hr) || !pwmpPlayer)
    {
        // player not created yet, fire exception to let scrip know it has no control
        return E_UNEXPECTED;
    }

    CComVariant vtEnabled = bEnabled;
    return pwmpPlayer.PutPropertyByName(L"enabled", &vtEnabled);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::get_disabledUI(VARIANT_BOOL *pbDisabled)
{
    if (pbDisabled == NULL)
        return E_POINTER;
    *pbDisabled = _fDisabledUI ? VARIANT_TRUE : VARIANT_FALSE;

    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::put_disabledUI(VARIANT_BOOL bDisable)
{
    _fDisabledUI = bDisable;
    // tell mediaband
    if (_pHost)
    {
        _pHost->OnDisableUIChanged(_fDisabledUI);
    }
    return S_OK;
}


// 
// *** IMediaBehaviorContentProxy ***
//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::OnCreatedPlayer(void)
{
    return _ConnectToWmpEvents(TRUE);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::fireEvent(enum contentProxyEvent event)
{
    ASSERT(_pBehaviorSiteOM != NULL);   // called too early, must have received Init() call from Trident first!
    if (!_pBehaviorSiteOM)
        return E_UNEXPECTED;

    if ((event < 0) || (event >= ARRAYSIZE(s_behaviorEvents)))
        return E_INVALIDARG;

    struct _eventInfo *pEvtInfo = &s_behaviorEvents[event];

    HRESULT hr = S_OK;
    // don't have cookie yet, need to register event first!
    if (pEvtInfo->levtCookie == NO_COOKIE)
    {
        // register event with behavior site
        hr = _pBehaviorSiteOM->RegisterEvent(pEvtInfo->pwszName, 0, &pEvtInfo->levtCookie);
        ASSERT(SUCCEEDED(hr));
        if (FAILED(hr))
            return hr;
    }
    if (pEvtInfo->levtCookie == NO_COOKIE)
        return E_UNEXPECTED;

    CComPtr<IHTMLEventObj>  pEvt;
    hr = _pBehaviorSiteOM->CreateEventObject(&pEvt);
    if (FAILED(hr))
        return hr;

    // fire into script:
     return _pBehaviorSiteOM->FireEvent(pEvtInfo->levtCookie, pEvt);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::detachPlayer(void)
{
    return _ConnectToWmpEvents(FALSE);
}

// *** IMediaBehaviorContentProxy ***
//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::OnUserOverrideDisableUI()
{
    return put_disabledUI(VARIANT_FALSE);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::IsDisableUIRequested(BOOL *pfRequested)
{
    if (!pfRequested)
    {
        return E_POINTER;
    }
    *pfRequested = _fDisabledUI;
    return S_OK;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaBehavior::IsNextEnabled(BOOL *pfEnabled)
{
    if (!pfEnabled)
    {
        return E_POINTER;
    }
    *pfEnabled = FALSE;

    CComDispatchDriverEx    pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (SUCCEEDED(hr) && pwmpPlayer)
    {
        CComVariant vtControls;
        hr = pwmpPlayer.GetPropertyByName(L"controls", &vtControls);
        if (SUCCEEDED(hr))
        {
            CComDispatchDriverEx pwmpControls;
            pwmpControls = vtControls;
            
            CComVariant vtNext = "Next";
            CComVariant vtEnabled;
            hr = pwmpControls.GetPropertyByName1(L"isAvailable", &vtNext, &vtEnabled);
            if (SUCCEEDED(hr) && (V_VT(&vtEnabled) == VT_BOOL))
            {
                *pfEnabled = (V_BOOL(&vtEnabled) == VARIANT_TRUE);
            }
        }
    }
    return S_OK;
}

//------------------------------------------------------------------------
HRESULT CMediaBehavior::getWMP(IDispatch **ppPlayer)
{
    if (ppPlayer == NULL)
        return E_POINTER;
    *ppPlayer = NULL;
    if (!CMediaBarUtil::IsWMP7OrGreaterCapable() || !_pHost)
    {
        return E_UNEXPECTED;
    }
    if (!_fPlaying)
    {
        return E_ACCESSDENIED;
    }

    HRESULT hr = E_UNEXPECTED;
    CComPtr<IUnknown>    pMediaPlayer;
    hr = _pHost->getMediaPlayer(&pMediaPlayer);
    // getMediaPlayer can return NULL and S_FALSE if player isn't loaded yet!
    if (SUCCEEDED(hr) && pMediaPlayer)
    {
        CComQIPtr<ITIMEMediaElement, &IID_ITIMEMediaElement> pMediaElem = pMediaPlayer;
        if (pMediaElem)
        {
            return pMediaElem->get_playerObject(ppPlayer);
        }
        else
        {
            hr = E_NOINTERFACE;
        }
    }
    return hr;
}

//------------------------------------------------------------------------
HRESULT CMediaBehavior::getPlayListIndex(LONG *plIndex, LONG *plCount)
{
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }
    CComPtr<IUnknown>    pMediaPlayer;
    HRESULT hr = _pHost->getMediaPlayer(&pMediaPlayer);
    // getMediaPlayer can return NULL and S_FALSE if player isn't loaded yet!
    if (SUCCEEDED(hr) && pMediaPlayer)
    {
        CComQIPtr<ITIMEMediaElement, &IID_ITIMEMediaElement> pMediaElem = pMediaPlayer;
        if (pMediaElem)
        {
            CComPtr<ITIMEPlayList> pPlayList;
            if (SUCCEEDED(pMediaElem->get_playList(&pPlayList)) && pPlayList)
            {
                // current track index
                if (plIndex)
                {
                    CComPtr<ITIMEPlayItem> pPlayItem;
                    if (SUCCEEDED(pPlayList->get_activeTrack(&pPlayItem)) && pPlayItem)
                    {
                        hr = pPlayItem->get_index(plIndex);
                    }
                }
                // number of tracks in playlist
                if (plCount)
                {
                    hr = pPlayList->get_length(plCount);
                }
            }
        }
    }

    return hr;
}


//------------------------------------------------------------------------
HRESULT CMediaBehavior::_ConnectToWmpEvents(BOOL fConnect)
{
    if (   (fConnect && (_dwcpCookie != 0))
        || (!fConnect && (_dwcpCookie == 0))
        || !_pHost)
    {
        return S_FALSE; // no change in connection or no host
    }

    CComPtr<IDispatch>    pwmpPlayer;
    HRESULT hr = getWMP(&pwmpPlayer);
    if (SUCCEEDED(hr) && pwmpPlayer)
    {
        return ConnectToConnectionPoint(SAFECAST(this, IDispatch*), 
                DIID__WMPOCXEvents, fConnect, pwmpPlayer, &_dwcpCookie, NULL);
    }
    return hr;
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// class CWMPWrapper
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------
CWMPWrapper::CWMPWrapper(CMediaBehavior* pHost)
  : _cRef(0),
    _fStale(FALSE)
{
    ASSERT(pHost);
    _pHost = pHost;
    if (_pHost)
        _pHost->AddRef();
}

//------------------------------------------------------------------------
CWMPWrapper::~CWMPWrapper()
{
    if (_pHost)
        _pHost->Release();
}

//------------------------------------------------------------------------
HRESULT CWMPWrapper::_getVariantProp(LPCOLESTR pwszPropName, VARIANT *pvtParam, VARIANT *pvtValue, BOOL fCallMethod)
{
    if (pvtValue == NULL)
        return E_POINTER;

    HRESULT hr = S_FALSE;
    VariantInit(pvtValue);
    if (!_fStale && _pwmpWrapper)
    {
        if (fCallMethod)
        {
            if (pvtParam != NULL)
            {
                hr = _pwmpWrapper.Invoke1(pwszPropName, pvtParam, pvtValue);
            }
            else
            {
                hr = _pwmpWrapper.Invoke0(pwszPropName, pvtValue);
            }
        }
        else
        {
            if (pvtParam != NULL)
            {
                hr = _pwmpWrapper.GetPropertyByName1(pwszPropName, pvtParam, pvtValue);
            }
            else
            {
                hr = _pwmpWrapper.GetPropertyByName(pwszPropName, pvtValue);
            }
        }
    }

    return hr;
}

//------------------------------------------------------------------------
HRESULT CWMPWrapper::_getStringProp(LPCOLESTR pwszPropName, VARIANT *pvtParam, OUT BSTR *pbstrValue, BOOL fCallMethod)
{
    if (pbstrValue == NULL)
        return E_POINTER;
    *pbstrValue = NULL;

    CComVariant vtValue;
    HRESULT hr = _getVariantProp(pwszPropName, pvtParam, &vtValue, fCallMethod);
    if (SUCCEEDED(hr) && (V_VT(&vtValue) == VT_BSTR))
    {
        *pbstrValue = SysAllocString(V_BSTR(&vtValue));
    }

    // always return string, even if empty one (e.g. when media object is stale)
    if (SUCCEEDED(hr) && (*pbstrValue == NULL))
    {
        *pbstrValue = SysAllocString(L"");
        hr = S_OK;
    }

    return hr;
}


//------------------------------------------------------------------------
HRESULT CWMPWrapper::AttachToWMP()
{
    HRESULT hr = E_UNEXPECTED;
    if (_pHost)
    {
        CComDispatchDriver pwmpPlayer;
        hr = _pHost->getWMP(&pwmpPlayer);
        if (SUCCEEDED(hr) && pwmpPlayer)
        {
            // walk to WMP media object as signaled by requested type
            CComVariant vtMedia;
            hr = FetchWmpObject(pwmpPlayer, &vtMedia);
            if (SUCCEEDED(hr))
            {
                _pwmpWrapper = vtMedia;
            }
        }
    }
    return hr;
}



///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// class CMediaItem
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------
CMediaItem::CMediaItem(CMediaBehavior* pHost)
  : CWMPWrapper(pHost),
    CImpIDispatch(LIBID_BrowseUI, 1, 0, IID_IMediaItem)
{
}

//------------------------------------------------------------------------
CMediaItem::~CMediaItem()
{
}


//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::QueryInterface(REFIID riid, void **ppvObj)
{
    if (ppvObj == NULL)
        return E_POINTER;

    if (   IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IMediaItem))
    {
        *ppvObj = (IMediaItem*) this;
    }
    else if (IsEqualIID(riid, IID_IDispatch))
    {
        *ppvObj = (IDispatch*) this;
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}



// *** IMediaItem
//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::get_sourceURL(BSTR *pbstrSourceURL)
{
    return _getStringProp(L"sourceURL", NULL, pbstrSourceURL);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::get_name(BSTR *pbstrName)
{
    return _getStringProp(L"name", NULL, pbstrName);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::get_duration(double * pDuration)
{
    if (pDuration == NULL)
        return E_POINTER;
    *pDuration = 0.0;

    CComVariant vtValue;
    HRESULT hr = _getVariantProp(L"duration", NULL, &vtValue);
    if (SUCCEEDED(hr) && (V_VT(&vtValue) == VT_R8))
    {
        *pDuration = V_R8(&vtValue);
    }

    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::get_attributeCount(long *plCount)
{
    if (plCount == NULL)
        return E_POINTER;
    *plCount = 0;

    CComVariant vtValue;
    HRESULT hr = _getVariantProp(L"attributeCount", NULL, &vtValue);
    if (SUCCEEDED(hr) && (V_VT(&vtValue) == VT_I4))
    {
        *plCount = V_I4(&vtValue);
    }

    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::getAttributeName(long lIndex, BSTR *pbstrItemName)
{
    CComVariant vtIndex = lIndex;
    return _getStringProp(L"getAttributeName", &vtIndex, pbstrItemName, TRUE);
}

//------------------------------------------------------------------------
STDMETHODIMP CMediaItem::getItemInfo(BSTR bstrItemName, BSTR *pbstrVal)
{
    CComVariant vtItemName = bstrItemName;
    return _getStringProp(L"getItemInfo", &vtItemName, pbstrVal, TRUE);
}


//------------------------------------------------------------------------
HRESULT CMediaItem::FetchWmpObject(IDispatch *pdispWmpPlayer, OUT VARIANT *pvtWrapperObj)
{
    CComDispatchDriver pwmpPlayer;
    pwmpPlayer = pdispWmpPlayer;
    return pwmpPlayer.GetPropertyByName(L"currentMedia", pvtWrapperObj);
}


//////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// class CMediaItemNext
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------
CMediaItemNext::CMediaItemNext(CMediaBehavior* pHost)
  : CMediaItem(pHost)
{
}

//------------------------------------------------------------------------
CMediaItemNext::~CMediaItemNext()
{
}

//------------------------------------------------------------------------
HRESULT CMediaItemNext::FetchWmpObject(IDispatch *pdispWmpPlayer, OUT VARIANT *pvtWrapperObj)
{
    if (!_pHost)
    {
        return E_UNEXPECTED;
    }
    CComDispatchDriver pwmpPlayer;
    pwmpPlayer = pdispWmpPlayer;
    
    HRESULT hr = E_UNEXPECTED;
    CComVariant vtCurrPlayList;
    hr = pwmpPlayer.GetPropertyByName(L"currentPlaylist", &vtCurrPlayList);
    if (SUCCEEDED(hr))
    {
        CComDispatchDriverEx pwmpCurrPlayList;
        pwmpCurrPlayList = vtCurrPlayList;
            
        // what's the index of the current item in play?
        CComPtr<IMediaBarPlayer>    pMediaPlayer;
        LONG cnt = 0;
        LONG currIndex = 0;
        hr = _pHost->getPlayListIndex(&currIndex, &cnt);
        if (SUCCEEDED(hr))
        {
            if (currIndex + 1 < cnt)
            {
                CComVariant vtNext = currIndex + 1;
                return pwmpCurrPlayList.GetPropertyByName1(L"item", &vtNext, pvtWrapperObj);
            }
            else
            {
                return E_UNEXPECTED;
            }
        }
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// class CPlaylistInfo
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------
CPlaylistInfo::CPlaylistInfo(CMediaBehavior* pHost)
  : CWMPWrapper(pHost),
    CImpIDispatch(LIBID_BrowseUI, 1, 0, IID_IPlaylistInfo)
{
}

//------------------------------------------------------------------------
CPlaylistInfo::~CPlaylistInfo()
{
}


//------------------------------------------------------------------------
STDMETHODIMP CPlaylistInfo::QueryInterface(REFIID riid, void **ppvObj)
{
    if (ppvObj == NULL)
        return E_POINTER;

    if (   IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IPlaylistInfo))
    {
        *ppvObj = (IPlaylistInfo*) this;
    }
    else if (IsEqualIID(riid, IID_IDispatch))
    {
        *ppvObj = (IDispatch*) this;
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}


// *** IPlaylistInfo
//------------------------------------------------------------------------
//------------------------------------------------------------------------
STDMETHODIMP CPlaylistInfo::get_name(BSTR *pbstrName)
{
    return _getStringProp(L"name", NULL, pbstrName);
}

//------------------------------------------------------------------------
STDMETHODIMP CPlaylistInfo::get_attributeCount(long *plCount)
{
    if (plCount == NULL)
        return E_POINTER;
    *plCount = 0;

    CComVariant vtValue;
    HRESULT hr = _getVariantProp(L"attributeCount", NULL, &vtValue);
    if (SUCCEEDED(hr) && (V_VT(&vtValue) == VT_I4))
    {
        *plCount = V_I4(&vtValue);
    }

    return hr;
}

//------------------------------------------------------------------------
STDMETHODIMP CPlaylistInfo::getAttributeName(long lIndex, BSTR *pbstrItemName)
{
    CComVariant vtIndex = lIndex;
    return _getStringProp(L"attributeName", &vtIndex, pbstrItemName);
}

//------------------------------------------------------------------------
STDMETHODIMP CPlaylistInfo::getItemInfo(BSTR bstrItemName, BSTR *pbstrVal)
{
    CComVariant vtItemName = bstrItemName;
    return _getStringProp(L"getItemInfo", &vtItemName, pbstrVal, TRUE);
}

//------------------------------------------------------------------------
HRESULT CPlaylistInfo::FetchWmpObject(IDispatch *pdispWmpPlayer, OUT VARIANT *pvtWrapperObj)
{
    CComDispatchDriver pwmpPlayer;
    pwmpPlayer = pdispWmpPlayer;
    return pwmpPlayer.GetPropertyByName(L"currentPlaylist", pvtWrapperObj);
}