/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: ppffplin.cpp,v 1.8.16.1 2004/07/19 21:04:16 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

/////////////////////////////////////////////////////////////////////////////
// 
//  RealAudio Server side plugin that will sink to get live packets, create
//  a virtual .SDP file for the HTTP server to send to clients which will
//  start listening to the live multicast packets formatted in RTP.
//

/****************************************************************************
 *  Defines
 */
// #define _TIMELINE_TRACE
#define _GETPACKET_TRACE

#define INITGUID

#define RA_MINETYPE			"audio/x-pn-realaudio"
#define STANDARD_START_HXOFFSET	200	    // 100 ms
#define HX_COMMON_MIME_TYPE_FRAGMENT	"x-pn-"

#define TRANSPORT_BUFFERING_TIME	10	    // ms

#define MAX_TIME_GAP_FORWARD		20000
#define MAX_TIME_GAP_BACKWARD		5000
#define MAX_SUSPENDED_BUFFER_SIZE	1000

#define FLEX_BUFFER_SWITCHOVER_TIME	40000	    // 40 seconds

#define LISTENINING_TIME_INTERVAL	PP_SYNC_TIMEOUT_MS


/****************************************************************************
 *  Includes
 */
#include "hxtypes.h"
#include "hlxclib/windows.h"
#include "hxcom.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxfiles.h"
#include "hxformt.h"
#include "hxplugn.h"
#include "hxmon.h"
#include "hxerror.h"
#include "hxcore.h"
#include "hxprefs.h"
#include "rtptypes.h"
#include "hxrand.h"
#include "hxslist.h"
#include "hxstring.h"
#include "hxstrutl.h"
#include "chxpckts.h"
#include "hxbuffer.h"
#include "tconverter.h"
#include "asmrulep.h"
#include "hxtick.h"
#include "hxengin.h"
#include "netbyte.h"	// rninet_addr

#include "statinfo.h" // STREAM_STATS
#include "hxmangle.h" // CLIENT_ID_REGNAME
#include "hxurl.h"

// error reporting 
#include "hxxres.h"
#include "hxxrsmg.h"
#include "resid.h"
#include "scalmres.h"   // in coreres module

#include "packetq.h"

#include "carray.h"
#include "ppbin.h"

#include "ppfformat.ver"
#include "rtpwrap.h"
#include "ppffplin.h"
#include "ppffsrc.h"
#include "ppffstrm.h"
#include "ppffhttp.h"
#include "hxver.h"

#include "sdpchunk.h"
#include "sdppyldinfo.h"

#include "hxhyper.h"	// IHXHyperNavigation

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif

#ifdef _AIX
#include "hxtbuf.h"
#include "dllpath.h"
ENABLE_MULTILOAD_DLLACCESS_PATHS(Ppffplin);
#endif

const UINT32	MS_PER_SECOND	= 1000;
const UINT32	MICRO_PER_MS	= 1000;


/***************************************************************************
 *  CPurePlayFileFormat static variables                         ref:  rprxypln.h
 *
 *  These variables are passed to the RMA core to provide information about
 *  this plug-in. They are required to be static in order to remain valid
 *  for the lifetime of the plug-in.
 */
const char*  CPurePlayFileFormat::zm_pDescription	= "RealNetworks Scalable Multicast Plugin";
const char*  CPurePlayFileFormat::zm_pCopyright		= HXVER_COPYRIGHT;
const char*  CPurePlayFileFormat::zm_pMoreInfoURL	= HXVER_MOREINFO;
const char*  CPurePlayFileFormat::zm_pFileMimeTypes[]	= {"application/sdp", NULL};
const char*  CPurePlayFileFormat::zm_pFileExtensions[]	= {"sdp", NULL};
const char*  CPurePlayFileFormat::zm_pFileOpenNames[]	= {"Scalable multicast stream description", NULL};


#ifdef _WINDOWS

#if defined(WIN32_PLATFORM_PSPC)
#define HDLL_TYPE HANDLE
#else
#define HDLL_TYPE HINSTANCE
#endif /* defined(WIN32_PLATFORM_PSPC) */

extern HDLL_TYPE g_hInstance;
#endif


#if defined(_TIMELINE_TRACE) || defined(_GETPACKET_TRACE)
FILE* m_tfile = NULL;
#endif	// _TIMELINE_TRACE || _GETPACKET_TRACE



/****************************************************************************
 * 
 *  Function:
 * 
 *	HXCreateInstance()
 * 
 *  Purpose:
 * 
 *	Function implemented by all plugin DLL's to create an instance of 
 *	any of the objects supported by the DLL. This method is similar to 
 *	Window's CoCreateInstance() in its purpose, except that it only 
 *	creates objects from this plugin DLL.
 *
 *	NOTE: Aggregation is never used. Therefore and outer unknown is
 *	not passed to this function, and you do not need to code for this
 *	situation.
 * 
 */

STDAPI ENTRYPOINT(HXCREATEINSTANCE)
(
    IUnknown**  /*OUT*/	ppIUnknown
)
{
    *ppIUnknown = (IUnknown*)(IHXFileSystemObject*)new CPurePlayFileFormat;
    if(*ppIUnknown)
    {
	(*ppIUnknown)->AddRef();
	return HXR_OK;
    }
    return HXR_OUTOFMEMORY;
}

CPurePlayFileFormat::CPurePlayFileFormat()
    : m_lRefCount(0)
    , m_pContext(NULL)
    , m_pStreamDesc(NULL)
    , m_pRTPInfo(NULL)
    , m_pLog(NULL)
    , m_pFileObject(NULL)
    , m_pFileBufferList(NULL)
    , m_pScheduler(NULL)
    , m_pClassFactory(NULL)
    , m_pFileHeader(NULL)
    , m_pRegistry(NULL)
    , m_pSDPDesc(NULL)
    , m_state(Init)
    , m_pFFResponse(NULL)
    , m_uActiveStreams(0)
    , m_pMasterSyncStream(NULL)
    , m_bInitialPacket(TRUE)
    , m_uStatusCode(HX_STATUS_READY)
    , m_pStatusDesc(NULL)
    , m_pCName(NULL)
    , m_pUserName(NULL)
    , m_pTool(NULL)  
    , m_pEmail(NULL)
    , m_hFileHeader(0)
    , m_bRMPresentation(TRUE)
#ifdef _ALLOW_TS_OFFSETTING
    , m_bDisableOffsetting(FALSE)
#else	// _ALLOW_TS_OFFSETTING
    , m_bDisableOffsetting(TRUE)
#endif	// _ALLOW_TS_OFFSETTING
    , m_bSourceAdmissionClosed(FALSE)
    , m_ulFileHeaderWaitStartTime(0)
    , m_ulListeningWaitStartTime(0)
    , m_ulServerTimeout(0)
    , m_bOneMoreTry(TRUE)
    , m_lSmallestTSStrm(-1)
    , m_tryUnicast(NO_TRY)
    , m_pRequest(NULL)
    , m_lRAStreamNum(NO_EXIST)
    , m_bWantClientStats(TRUE)
    , m_bAttempMulticast(TRUE)
    , m_ulFirstTSSent(0)
    , m_bFirstPktSent(FALSE)
    , m_bFirstPktRcvd(FALSE)
    , m_ulHXOffset(0)
    , m_pInterfaceMgr(NULL)
    , m_ppInterfacesReady(NULL)
    , m_ulNumInterfacesReady(0)
    , m_ulStartTimeMS(0)
#ifdef BW_BASED
    , m_lRuleNumber(-1)
#endif    
#ifdef _DEBUG
    , m_bFileHeaderReadyCalled(FALSE)
#endif    
{
    ;
}


CPurePlayFileFormat::~CPurePlayFileFormat() 
{ 
    HX_RELEASE(m_pContext);
    
    HX_RELEASE(m_pCName);
    HX_RELEASE(m_pUserName);
    HX_RELEASE(m_pTool);
    HX_RELEASE(m_pEmail);
}

// *** IUnknown methods ***
/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::QueryInterface
//  Purpose:
//	Implement this to export the interfaces supported by your 
//	object.
//
STDMETHODIMP CPurePlayFileFormat::QueryInterface(REFIID riid, void** ppvObj)
{
    if (IsEqualIID(riid, IID_IHXTransportSyncServer))
    {
	AddRef();
	*ppvObj = (IHXTransportSyncServer*) this;
	return HXR_OK;
    }
    else if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXPlugin))
    {
	AddRef();
	*ppvObj = (IHXPlugin*)this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXFileFormatObject))
    {
	AddRef();
	*ppvObj = (IHXFileFormatObject*)this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXFileResponse))
    {
	AddRef();
	*ppvObj = (IHXFileResponse*)this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXASMSource))
    {
	AddRef();
	*ppvObj = (IHXASMSource*)this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXPendingStatus))
    {
	AddRef();
	*ppvObj = (IHXPendingStatus*)this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXPropertyAdviser))
    {
	AddRef();
	*ppvObj = (IHXPropertyAdviser*)this;
	return HXR_OK;
    }
    
    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::AddRef
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CPurePlayFileFormat::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::Release
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) CPurePlayFileFormat::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

/****************************************************************************
 *  IHXPlugin::GetPluginInfo                                ref:  hxplugn.h
 *
 *  This routine returns descriptive information about the plug-in, most
 *  of which is used in the About box of the user interface. It is called
     *  when the RMA core application is launched.
 */
STDMETHODIMP
CPurePlayFileFormat::GetPluginInfo
(
    REF(BOOL)	    /*OUT*/ bLoadMultiple,
    REF(const char*)/*OUT*/ pDescription,
    REF(const char*)/*OUT*/ pCopyright,
    REF(const char*)/*OUT*/ pMoreInfoURL,
    REF(UINT32)     /*OUT*/ ulVersionNumber
)
{
    bLoadMultiple = TRUE;   // Must be true for file formats.

    pDescription    = zm_pDescription;
    pCopyright	    = zm_pCopyright;
    pMoreInfoURL    = zm_pMoreInfoURL;
    ulVersionNumber = TARVER_ULONG32_VERSION;

    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::GetFileFormatInfo                  ref:  hxformt.h
 *
 *  This routine returns crucial information required to associate this
 *  plug-in with a given MIME type. This information tells the core which
 *  File Format plug-in to use for a particular URL. The routine is called
 *  when the RMA core application is launched.
 */
STDMETHODIMP
CPurePlayFileFormat::GetFileFormatInfo
(
    REF(const char**) /*OUT*/ pFileMimeTypes,
    REF(const char**) /*OUT*/ pFileExtensions,
    REF(const char**) /*OUT*/ pFileOpenNames
)
{
    pFileMimeTypes  = zm_pFileMimeTypes;
    pFileExtensions = zm_pFileExtensions;
    pFileOpenNames  = zm_pFileOpenNames;

    return HXR_OK;
}


/****************************************************************************
 *  IHXPlugin::InitPlugin                                   ref:  hxplugn.h
 *
 *  This routine performs initialization steps such as determining whether
 *  required interfaces are available. It is called when the RMA core 
 *  application is launched, and whenever an URL associated with this
 *  plug-in is opened.
 */
STDMETHODIMP
CPurePlayFileFormat::InitPlugin(IUnknown* /*IN*/ pContext)
{
    HX_RESULT theErr = HXR_OK;     
    int localizedErrCode = 0;

    m_pContext = pContext;
    
    if (NULL == m_pContext)
    {
    	theErr = HXR_INVALID_PARAMETER;
	localizedErrCode = IDS_ERR_SM_NO_CTXT;
    	goto bail;
    }
    m_pContext->AddRef();

    /*
     * Get the necessary interfaces
     */
    theErr = m_pContext->QueryInterface(IID_IHXErrorMessages,
					(void**)&m_pLog);
    if (theErr != HXR_OK || NULL == m_pLog)
    {
	theErr = HXR_INVALID_PARAMETER;
	localizedErrCode = IDS_ERR_SM_CTXT_QI_FAILED;
	goto bail;
    }

    theErr = m_pContext->QueryInterface(IID_IHXScheduler,
					(void**)&m_pScheduler);
	
    if (theErr != HXR_OK || NULL == m_pScheduler)
    {
	theErr = HXR_INVALID_PARAMETER;
	localizedErrCode = IDS_ERR_SM_CTXT_QI_FAILED;
	goto bail;
    }

    theErr = m_pContext->QueryInterface(IID_IHXRegistry,
					(void**)&m_pRegistry);

    if (theErr != HXR_OK || NULL == m_pRegistry)
    {
	theErr = HXR_INVALID_PARAMETER;
	localizedErrCode = IDS_ERR_SM_CTXT_QI_FAILED;
	goto bail;
    }

    theErr = m_pContext->QueryInterface(IID_IHXCommonClassFactory, 
    					(void**)&m_pClassFactory);

    if (theErr != HXR_OK || NULL == m_pClassFactory)
    {
    	theErr = HXR_INVALID_PARAMETER;
	localizedErrCode = IDS_ERR_SM_CTXT_QI_FAILED;
    	goto bail;
    }

bail:
    if (theErr != HXR_OK)
    {
	ReportError(localizedErrCode, HXLOG_WARNING, HXR_FAIL);
    }
    return theErr;
}


/****************************************************************************
 *  IHXFileFormatObject::InitFileFormat                     ref:  hxformt.h
 *
 *  This routine initializes the file, and stores references to objects used
 *  throughout the example. It is called whenever an URL associated with this
 *  plug-in is opened.
 */
STDMETHODIMP
CPurePlayFileFormat::InitFileFormat
(
    IHXRequest*	    /*IN*/  pRequest, 
    IHXFormatResponse*	    /*IN*/  pFormatResponse,
    IHXFileObject*	    /*IN*/  pFileObject
)
{
    HX_RESULT theErr = HXR_OK;    

    // used to create HTTP POST msg
    m_pRequest = pRequest;
    m_pRequest->AddRef();

#ifdef XXXGo_DEBUG
    m_file = fopen("c:\\live.txt", "wt");
#endif	// XXXGo_DEBUG

#if defined(_TIMELINE_TRACE) || defined(_GETPACKET_TRACE)
    m_tfile = fopen("c:\\live_t.txt", "wt");
#endif	// _TIMELINE_TRACE || _GETPACKET_TRACE

    /*
     * Create and save off the UDP socket, and start reading in 
     * the file object to parse out to get the file and stream headers
     * when we successfully get the headers we will call 
     * IHXFormatResponse::InitDone
     */


    /*
     * Save off the user values for the RTCP SDES messages
     */

    BOOL		bAutoTransport = TRUE;
    IHXPreferences*	pPrefs = NULL;
    IHXBuffer*		pBuf = NULL;
    HX_VERIFY(HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences, 
	(void**)&pPrefs));

    if(HXR_OK == pPrefs->ReadPref("AutoTransport", pBuf))
    {
	bAutoTransport = atoi((const char*) pBuf->GetBuffer());
    }
    HX_RELEASE(pBuf);

    if (bAutoTransport)
    {
	m_bAttempMulticast = TRUE;
    }
    else
    {
	/* check the pref to see if Multicast is disabled */
	if (SUCCEEDED(pPrefs->ReadPref("AttemptRTSPvMulticast", pBuf)))
	{
	    HX_ASSERT(pBuf);	
	    m_bAttempMulticast = (BOOL)atol((const char*)pBuf->GetBuffer());	
	    HX_RELEASE(pBuf);
	}
	if (m_bAttempMulticast && 
	    SUCCEEDED(pPrefs->ReadPref("AttemptPNAvMulticast", pBuf)))
	{
	    HX_ASSERT(pBuf);	
	    m_bAttempMulticast = (BOOL)atol((const char*)pBuf->GetBuffer());	
	    HX_RELEASE(pBuf);
	}
    }

    /*
     *	Handle multiple interface...
     */
    IHXNetworkInterfaceEnumerator* pIFEnum = NULL;
    if (SUCCEEDED(pPrefs->ReadPref("MulticastInterface", pBuf)))
    {	
	m_pInterfaceMgr = new InterfaceManager(1);
	m_pInterfaceMgr->SetInterface
	    (0, DwToHost(HXinet_addr((const char*)pBuf->GetBuffer())));
	HX_RELEASE(pBuf);
    }
    else if (m_pContext->QueryInterface(IID_IHXNetworkInterfaceEnumerator, 
	(void**)&pIFEnum) == HXR_OK)
    {	
	const int initialBufferSize = 56;
	UINT32 ulNumIF = initialBufferSize;
	UINT32* pulIF = new UINT32[ulNumIF];
	theErr = pIFEnum->EnumerateInterfaces(pulIF, ulNumIF);
	if (HXR_BUFFERTOOSMALL == theErr)
	{
	    HX_ASSERT(ulNumIF > initialBufferSize);
	    // well, try it again...We know exactly how many IFs there are
	    HX_VECTOR_DELETE(pulIF);
	    pulIF = new UINT32[ulNumIF];
	    theErr = pIFEnum->EnumerateInterfaces(pulIF, ulNumIF);	    
	}
	if (HXR_OK == theErr)
	{
	    HX_ASSERT(ulNumIF);
	    m_pInterfaceMgr = new InterfaceManager(ulNumIF);
	    for (UINT32 i = 0; i < ulNumIF; i++)
	    {
	    	m_pInterfaceMgr->SetInterface(i, pulIF[i]);    
	    }
	}
	HX_VECTOR_DELETE(pulIF);
	HX_RELEASE(pIFEnum);
    }

    if (!m_pInterfaceMgr)
    {
	// we have no choice...
	m_pInterfaceMgr = new InterfaceManager(1);
	m_pInterfaceMgr->SetInterface(0, HX_INADDR_ANY);
    }
    
    // sometimes, m_pCName gets just "" with size == 1
    // This is not the right string to use.
    // so if the string is <= 1, assign a different one.
    if (HXR_OK != pPrefs->ReadPref(REG_CNAME, m_pCName) ||
	m_pCName->GetSize() <= 1)
    {
	/*
	 * Check to see if "UserAddress" exists and use that
	 */

	CHXString strTemp;
	strTemp.Format("%s.%s",HXREGISTRY_PREFPROPNAME,"UserAddress");
	if (HXR_OK != m_pRegistry->GetStrByName(strTemp, m_pCName) ||
	    m_pCName->GetSize() <= 1)
	{
	    /*
	     * Fill it with junk for now, XXXST Use better junk
	     */

	    char    szNewValue[256]; /* Flawfinder: ignore */
	    UINT16  cbNewValue;
	    
	    m_pCName = new CHXBuffer;
	    m_pCName->AddRef();

	    CMultiplePrimeRandom randGen;

	    cbNewValue = sprintf(szNewValue, "RealNetworks%ul", randGen.GetRandomNumber()); /* Flawfinder: ignore */

	    m_pCName->Set((unsigned char*)szNewValue, cbNewValue + 1);
	}

	pPrefs->WritePref(REG_CNAME, m_pCName);
    }

    if (HXR_OK != pPrefs->ReadPref(REG_USERNAME, m_pUserName))
    {
	m_pUserName = new CHXBuffer;
	m_pUserName->AddRef();

	m_pUserName->Set((unsigned char*)DEFAULT_USERNAME, 
	    strlen(DEFAULT_USERNAME) + 1);

	pPrefs->WritePref(REG_USERNAME, m_pUserName);
    }

    if (HXR_OK != pPrefs->ReadPref(REG_TOOL, m_pTool))
    {
	m_pTool = new CHXBuffer;
	m_pTool->AddRef();

	m_pTool->Set((unsigned char*)DEFAULT_TOOL, strlen(DEFAULT_TOOL) + 1);

	pPrefs->WritePref(REG_TOOL, m_pTool);
    }

    const char*    pDefEmailVal = "Not Set";
    if (HXR_OK != pPrefs->ReadPref(REG_EMAIL, m_pEmail))
    {
	/*
	 * Write an empty key so the user knows that it is available
	 * to be set
	 */
	 
	m_pEmail = new CHXBuffer;
	m_pEmail->AddRef();
	m_pEmail->Set((unsigned char*)pDefEmailVal, strlen(pDefEmailVal) + 1);
	 
	pPrefs->WritePref(REG_EMAIL, m_pEmail);

	m_pEmail->Release();
	m_pEmail = NULL;
    }
    else
    {
	/*
	 * Leave the key there but if there is no value then don't
	 * store the string
	 */

	if (strcasecmp(pDefEmailVal, (char*)m_pEmail->GetBuffer()) == 0)
	{
	    m_pEmail->Release();
	    m_pEmail = NULL;
	}
    }

    IHXBuffer* pBuffer = NULL;
    // Get server timeout, if available
    if (HXR_OK != pPrefs->ReadPref("ServerTimeOut", pBuffer))
    {
	// we need a timeout value
	m_ulServerTimeout = 90000;
    }
    else
    {
	m_ulServerTimeout = (UINT32) (atol((const char*)pBuffer->GetBuffer()) * 1000);	
    }	    
    HX_RELEASE(pBuffer);

    // done with prefs
    HX_RELEASE(pPrefs);

    /*
     * Find a stream description converter
     */

    IHXPluginEnumerator*   pEnum = NULL;
    IUnknown*		    pPlugin = NULL;
    
    m_pContext->QueryInterface(IID_IHXPluginEnumerator,
	(void**)&pEnum);

    HX_ASSERT(pEnum != NULL);
    HX_ASSERT(m_pStreamDesc == NULL);

    for (int i = pEnum->GetNumOfPlugins() - 1; 
	 i >= 0 && (m_pStreamDesc == NULL || m_pRTPInfo == NULL); i--)
    {
	HX_VERIFY(pEnum->GetPlugin(i, pPlugin) == HXR_OK);
	HX_ASSERT(pPlugin != NULL);

	if (!m_pStreamDesc)
	{
	    pPlugin->QueryInterface(IID_IHXStreamDescription,
		(void**)&m_pStreamDesc);

	    if (m_pStreamDesc)
	    {
		((IHXPlugin*) pPlugin)->InitPlugin(m_pContext);
	    }
	}
	if (!m_pRTPInfo)
	{	
	    pPlugin->QueryInterface(IID_IHXRTPPayloadInfo,
		(void**)&m_pRTPInfo);
	}		

	pPlugin->Release();
    }
    pEnum->Release();

    if (m_pStreamDesc == NULL || m_pRTPInfo == NULL)
    {
    	theErr = HXR_INVALID_PARAMETER;
	ReportError(IDS_ERR_SM_NOSDPPLIN, HXLOG_ERR, theErr);
	Close();
	goto bail;
    }

    m_pFFResponse = pFormatResponse;
    if (m_pFFResponse == NULL)
    {
	theErr = HXR_INVALID_PARAMETER;
	goto bail;
    }
    m_pFFResponse->AddRef();

    m_pFileObject = pFileObject;
    if (m_pFileObject == NULL)
    {
	theErr = HXR_INVALID_PARAMETER;
	goto bail;
    }
    m_pFileObject->AddRef();

    m_state = Init;
    m_uStatusCode = HX_STATUS_INITIALIZING;
    
    theErr = m_pFileObject->Init(0, this);
    
bail:    
    if (theErr != HXR_OK) m_pFFResponse->InitDone(theErr);
    
    return theErr;
}


/****************************************************************************
 *  IHXFileResponse::InitDone                               ref:  hxfiles.h
 *
 *  This routine notifies the RMA core that the initialization of the file is
 *  done. It is called automatically when the initialization of the file
 *  has completed.
 */
STDMETHODIMP
CPurePlayFileFormat::InitDone
(
    HX_RESULT	status
)
{
    HX_ASSERT(m_state == Init);
    
    if (SUCCEEDED(status))
    {
	m_state = InitHeader;
    }

    m_pFFResponse->InitDone(status);
    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::GetFileHeader                      ref:  hxformt.h
 *
 *  This routine returns to the RMA core an object containing the file
 *  header information. Several routines are actually required to complete
 *  the process due to the asynchronous nature of the RMA file system. This
 *  method is called by the RMA core after the file has been initialized.
 */
STDMETHODIMP
CPurePlayFileFormat::GetFileHeader()
{
    if (m_state != InitHeader)
    {
	return m_pFFResponse->FileHeaderReady(HXR_UNEXPECTED, NULL);
    }

    // XXXMEH - previously we had just been reading 0xffff
    // (65535 bytes), and if the SDP file was longer than
    // that, then we just threw a general error. Ack.
    // So now we will continue to read 64K until we have read until
    // we get an error.
    m_state = PendingFileHeader;
    return m_pFileObject->Read(READ_SIZE);
}


HX_RESULT CPurePlayFileFormat::MakeHeader(HX_RESULT status)
{
    if (m_state != PendingFileHeader)
    {
	return HXR_UNEXPECTED;
    }

    if (status != HXR_OK)
    {
	return Close();
    }

    /*
     *  RM:
     *  As soon as we have received packets for all the streams, good to go.
     *  After ServerTimeout and still haven't gotten packets for all the 
     *  streams, NO PRESENTATION
     *  STANDARD:
     *  As soon as we have received a first packet, we will wait extra 5 to 
     *  10 sec to decide the number of streams for this presentation
     *  After ServerTimeout and still haven't gotten any packet, 
     *  NO PRESENTATION: otherwise, go with whatever we've got
     */
    
    // we have to wait for a while, so streams can decide how many senders
    // exist!
    CPurePlayFileFormat::CFileHeaderCallback*	 
	pFHCB = new CPurePlayFileFormat::CFileHeaderCallback(this);
    
    m_ulFileHeaderWaitStartTime = HX_GET_BETTERTICKCOUNT();
    
    pFHCB->AddRef();
    m_hFileHeader = m_pScheduler->RelativeEnter(pFHCB, PP_RM_TIMEOUT_MS);
    pFHCB->Release();
    
    m_uStatusCode = HX_STATUS_CONTACTING;
    
    m_bInitialPacket = TRUE;

    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::GetStreamHeader                    ref:  hxformt.h
 *
 *  This routine returns to the RMA core an object containing the stream
 *  header information for a particular stream. Several routines are actually
 *  required to complete the process due to the asynchronous nature of the
 *  RMA file system. This method is called (after the file header has been
 *  read) by the RMA core for each stream in the file format.
 */
STDMETHODIMP
CPurePlayFileFormat::GetStreamHeader(UINT16 unStreamNumber)
{
    if (unStreamNumber > m_rgStreams.GetSize())
    {
	m_pFFResponse->StreamHeaderReady(HXR_FAIL, NULL);
    }
    else
    {
	// get the right source
	CPurePlaySource* pSrc;
	pSrc = (CPurePlaySource*)m_rgStreams.GetAt(unStreamNumber);
	pSrc->AddRef();

	IHXValues* pHeader = NULL;
	pSrc->GetHeader(pHeader, unStreamNumber);

	HX_ASSERT(pHeader != NULL);

#ifdef XXXGo_DEBUG
	    ULONG32 ulStrmNum = 1000;
	    pHeader->GetPropertyULONG32("StreamNumber", ulStrmNum);
	    if (pSrc->m_pFFLog)
	    {
		fprintf(pSrc->m_pFFLog, "Sending Stream Header for stream %d (%d)\n", 
					unStreamNumber, ulStrmNum);
	    }	
#endif	        

	// it's ready!
//	HX_ASSERT(HXR_OK == m_pFFResponse->StreamHeaderReady(HXR_OK, pHeader));

	if (m_pFFResponse->StreamHeaderReady(HXR_OK, pHeader) != HXR_OK)
	{
	    HX_ASSERT(FALSE);
	}

	pHeader->Release();
	pSrc->Release();
    }
        
    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::GetPacket                          ref:  hxformt.h
 *
 *  This routine returns to the RMA core an object containing the packet
 *  data for a particular stream. Several routines are actually required to
 *  complete the process due to the asynchronous nature of the RMA file
 *  system. This method is called by the RMA core each time it needs another
 *  packet.
 */
STDMETHODIMP
CPurePlayFileFormat::GetPacket(UINT16 unStreamNumber)
{
#ifdef XXXGo_DEBUG
static UINT32 ulCount = 0;
#endif
    /*
     * There be packets wait'n for us on the socket
     */
    if (m_bInitialPacket)
    {
	m_bInitialPacket = FALSE;

	/*
	 * Get the registry ID for this source so we can update the
	 * clip bandwidth info when we get time.
	 */

	IHXStreamSource* pSource = NULL;
	if (HXR_OK == m_pFFResponse->QueryInterface(IID_IHXStreamSource,
	    (void**)&pSource))
	{
	    IHXRegistryID* pSourceRegistryID = NULL;
	    pSource->QueryInterface(IID_IHXRegistryID, (void**)&pSourceRegistryID);
	    if (pSourceRegistryID)
	    {
		UINT32 ulSourceID = 0;
		IHXBuffer* pSourceRegName = NULL;
		IHXBuffer* pTransportName = NULL;
		char		szTransportID[256]; /* Flawfinder: ignore */

		/* Need to come from a resource file */
		char szTransportName[] = "Scalable Multicast";
		
		pSourceRegistryID->GetID(ulSourceID);		    
		m_pRegistry->GetPropName(ulSourceID, pSourceRegName);
		HX_ASSERT(pSourceRegName);

		if (pSourceRegName)
		{
		    SafeSprintf(szTransportID, 256, "%s.TransportMode", pSourceRegName->GetBuffer());
		    pTransportName = new CHXBuffer;
		    pTransportName->AddRef();
		    pTransportName->Set((UCHAR*) szTransportName, sizeof(szTransportName) + 1);
		    m_pRegistry->SetStrByName(szTransportID, pTransportName);
		}

		HX_RELEASE(pTransportName);
		HX_RELEASE(pSourceRegName);

		HX_RELEASE(pSourceRegistryID);
	    }

	    UINT16  cStreams = pSource->GetStreamCount();
	    for (UINT16 iStream = 0; iStream < cStreams; iStream++)
	    {
		IUnknown*	    pSrcUnk = NULL;
	        IHXStream*	    pSrc = NULL;
		IHXRegistryID*	    pRegistryID = NULL;
		IHXBuffer*	    pSrcReg = NULL;
		CPurePlaySource*    pPpSrc = NULL;
		CStream*	    pStream = NULL;
		
		UINT32		ulStreamRegID = 0;
		char		szClipID[256]; /* Flawfinder: ignore */
		UINT32		ulClipID = 0;
		UINT16		uStreamNum = 0;
		
	        if (HXR_OK != pSource->GetStream(iStream, pSrcUnk))
		    goto NextSource;

		if (HXR_OK != pSrcUnk->QueryInterface(IID_IHXStream,
		    (void**)&pSrc))
		    goto NextSource;		    
		    
		if (HXR_OK != pSrc->QueryInterface(IID_IHXRegistryID,
		    (void**)&pRegistryID))
		    goto NextSource;
		    
		if (HXR_OK != pRegistryID->GetID(ulStreamRegID))
		    goto NextSource;

		// save this id so we can get stream stats later.
//		m_ulStreamRegID = ulStreamRegID;

		// deal with bandwidth reg key							
		if (HXR_OK != m_pRegistry->GetPropName(ulStreamRegID, pSrcReg))
		    goto NextSource;
		
		SafeSprintf(szClipID, 256, "%s.ClipBandwidth", pSrcReg->GetBuffer());


		// Get corresponding CPurePlaySource 
		uStreamNum = pSrc->GetStreamNumber();
		pPpSrc = (CPurePlaySource*) m_rgStreams[uStreamNum];
		HX_ASSERT(pPpSrc);
		if (!pPpSrc)
		    goto NextSource;
		    
		pPpSrc->AddRef();

		// Get the CStream
		pStream = pPpSrc->GetStreamByStrmId(uStreamNum);
		HX_ASSERT(pStream);
		if (!pStream)
		    goto NextSource;

		// save the StreamRegID, so we can get stream stats later.
		pStream->m_ulStreamRegId = ulStreamRegID;

		
		// if this is Standard payload, then "AvgBitRate" is hard coded 
		// in sdpplin, and so can't trust them.
		// Also, pPpSrc->m_ulBandwidth of 1 suggest the absence of 
		// "AvgBitRate" in SDP file
		if ((!pPpSrc->IsRMSource()) || 1 == pPpSrc->GetBandwidth())
		{
		    ulClipID = m_pRegistry->GetId(szClipID);    
		    if (ulClipID == 0)
		    	goto NextSource;

		    pStream->m_ulClipBandwidthID = ulClipID;

		}
		else
		{
		    HX_ASSERT(pPpSrc->GetBandwidth() != 1);
		    // trust bandwidth in the header
		    pStream->m_ulClipBandwidthID = 0;
		    m_pRegistry->SetIntByName(szClipID, pPpSrc->GetBandwidth());
		}
    		
NextSource:		
		HX_RELEASE(pPpSrc);
		HX_RELEASE(pSrcReg);
		HX_RELEASE(pRegistryID);
		HX_RELEASE(pSrc);
		HX_RELEASE(pSrcUnk);
	    }
	    
	    pSource->Release();
	}
	
    }

#ifdef XXXGo_DEBUG
if (m_file)
{
	fprintf(m_file, "\t\t\t\t\t%u: GetPacket(): streamNumber = %u\n", 
					    ulCount++, 
					    unStreamNumber);

}
#endif
    
    ULONG32 ulTimeNow = HX_GET_BETTERTICKCOUNT();

#ifdef _GETPACKET_TRACE
    /*
    if (m_tfile)
    {
	fprintf(m_tfile, "**GetPacket strm=%u  RT=%u\n", 
		unStreamNumber, 
		ulTimeNow - m_ulStartTimeMS);
    }
    */
#endif	// _GETPACKET_TRACE

    CPurePlaySource*	pSrc = (CPurePlaySource*)m_rgStreams.GetAt(unStreamNumber);
    pSrc->AddRef();
    pSrc->SetPendingFlag(unStreamNumber, TRUE);
    pSrc->UpdateStatistics(m_pRegistry, ulTimeNow);
    pSrc->Release();
    
    return ProcessPendingPackets(FALSE, ulTimeNow);
}


/****************************************************************************
 *  IHXFileResponse::SeekDone                               ref:  hxfiles.h
 *
 *  This routine is automatically called when the Seek() operation in the
 *  file is complete. Other actions are then taken dependent on the current
 *  processing state.
 */
STDMETHODIMP
CPurePlayFileFormat::SeekDone(HX_RESULT status)
{
    return HXR_NOTIMPL;
}


/****************************************************************************
 *  IHXFileResponse::ReadDone                               ref:  hxfiles.h
 *
 *  This routine is automatically called when the Read() from the file is
 *  done. Other actions are then taken dependent on the current processing
 *  state.
 */
STDMETHODIMP
CPurePlayFileFormat::ReadDone
(
    HX_RESULT		status,
    IHXBuffer*		pBuffer
)
{
    HX_ASSERT(m_state == PendingFileHeader);
    HX_ASSERT(m_pStreamDesc != NULL);

    HX_RESULT	    theErr	= HXR_OK;
    UINT16	    cHeaders	= 0;
    IHXValues**    ppHeaders	= NULL;  // all the headers
    CHXSimpleList   newConns;
    UINT32	    ulAddr	= 0;
    IHXBuffer*	    pFileAddr	= NULL;
    IHXUDPResponse* pResp	= NULL;

    int		    i		= 0;
    IHXPreferences*	pPrefs = NULL;
    IHXBuffer* pBandwidth = NULL;

    IHXValues**    ppRealHeaders = NULL;;// headers of right BW
    UINT32* pulSubscriptionBW = NULL;

    // XXXMEH - we are reading READ_SIZE (64k) bytes
    // at a time. When we finally fail, then we will aggregate
    // all our 64K buffers into one and then continue below.
    IHXBuffer* pAggBuffer = NULL;
    if (SUCCEEDED(status))
    {
        // Do we have a file buffer list?
        if (!m_pFileBufferList)
        {
            m_pFileBufferList = new CHXSimpleList();
        }
        if (m_pFileBufferList)
        {
            // AddRef the buffer before it goes on the list
            pBuffer->AddRef();
            // Put it on the list
            m_pFileBufferList->AddTail((void*) pBuffer);
            // Was this read less than READ_SIZE?
            if (pBuffer->GetSize() < READ_SIZE)
            {
                // We've hit the end of the file, go
                // ahead and aggregate
                AggregateBuffers(pAggBuffer);
            }
            else
            {
                // Set the state
                m_state = PendingFileHeader;
                // Read another READ_SIZE bytes
                return m_pFileObject->Read(READ_SIZE);
            }
        }
    }
    else
    {
        // Aggregate the buffers. The only time we should
        // hit this is if the .sdp file is exactly a multiple
        // of READ_SIZE
        AggregateBuffers(pAggBuffer);
    }

    // Now we can clear the buffer list
    ClearFileBufferList();
    HX_DELETE(m_pFileBufferList);

    if (!pAggBuffer)
    {
        return m_pFFResponse->FileHeaderReady(HXR_FAIL, NULL);
    }
    
    /* NULL terminate the description buffer */
    m_pSDPDesc = new CHXBuffer;
    m_pSDPDesc->AddRef();
    m_pSDPDesc->SetSize(pAggBuffer->GetSize() + 1);
    ::memcpy(m_pSDPDesc->GetBuffer(), pAggBuffer->GetBuffer(), pAggBuffer->GetSize()); /* Flawfinder: ignore */
    m_pSDPDesc->GetBuffer()[pAggBuffer->GetSize()] = '\0';

    // Release the aggregated buffer
    HX_RELEASE(pAggBuffer);

    /*
     * Now that we have a file, create the stream and file headers
     */

    theErr = m_pStreamDesc->GetValues(m_pSDPDesc, cHeaders, ppHeaders);
    if (theErr != HXR_OK || cHeaders == 0 || ppHeaders == NULL)
    {
	theErr = HXR_INVALID_FILE;
	ReportError(IDS_ERR_SM_BADSDPFILE, HXLOG_ERR, theErr);
	goto bail;
    }    
    
    /*
     *	A SDP file open may not contain a stream that we can play at all.
     *  so, make sure there is at least one stream in this header.
     *  If there is no strearm for presentation, tell the user and quit
     */    
    if (1 == cHeaders)
    {	
	// there is only file header
	theErr = HXR_INVALID_FILE;	
	ReportError(IDS_ERR_SM_NOPLAYTYPE, HXLOG_ERR, theErr);
	goto bail;
    }

    /*
     * Save off the headers then try to find our custom file attributes 
     * which will allow us to setup a network socket
     *
     * ppHeaders[0] == File Header
     * ppHeaders[1-N] == Stream Headers
     */

    m_pFileHeader = ppHeaders[0];
    if (m_pFileHeader == NULL)
    {
	theErr = HXR_INVALID_FILE;
	goto bail;
    }
    m_pFileHeader->AddRef();

    /* check to see if we wanna try this feed at all */
    if (!m_bAttempMulticast)
    {	
	m_tryUnicast = NO_MULTICAST;
	goto bail;
    }


    HX_VERIFY(HXR_OK == m_pContext->QueryInterface(IID_IHXPreferences, 
	(void**)&pPrefs));

    // Client stats enabled?
    // m_bWantClientStats defaults to FALSE
    UINT32 ul;
    if (HXR_OK == m_pFileHeader->GetPropertyULONG32("StatsStyle", ul) &&
	HXR_OK == m_pFileHeader->GetPropertyULONG32("StatsMask", ul))
    {
	IHXBuffer* pPrefBuffer = NULL;	
	
    	if (SUCCEEDED(pPrefs->ReadPref("SendStatistics", pPrefBuffer)))
    	{
    	    m_bWantClientStats = (BOOL)atoi((const char*) pPrefBuffer->GetBuffer());    
    	    HX_RELEASE(pPrefBuffer);
	}    
    }

    
    BOOL bFoundBW;    
    if (HXR_OK != pPrefs->ReadPref("Bandwidth", pBandwidth))
    {
	bFoundBW = FALSE;
	HX_ASSERT(FALSE);
	pBandwidth = new CHXBuffer();
	pBandwidth->AddRef();
	pBandwidth->Set((const unsigned char*)"64000", strlen("64000"));
    }
    else
    {
	bFoundBW = TRUE;
    }
    HX_RELEASE(pPrefs);

    /* Timeout */
    if (SUCCEEDED(m_pFileHeader->GetPropertyULONG32("Timeout", ul)))
    {
	// it's in sec
	m_ulFailoverTimeout = ul * 1000;
    }
    else
    {
	m_ulFailoverTimeout = m_ulServerTimeout;
    }
    
    /*
     * The connection line could be stored in the session header
     * rather than the media description so try to get it even if fails
     */

    m_pFileHeader->GetPropertyCString(PROP_MULTI_ADDRESS, pFileAddr);
    
    /*
     * Since we only server live streams make sure the core knows this
     * The stream count may not be included in the SDP file so add it
     * here
     */
    m_pFileHeader->SetPropertyULONG32("LiveStream", 1);

    /*
     * Get a number of streams in this presentation
     */
    ULONG32 ulNumStreams;
    ulNumStreams = 0;
    
    // don't trust the "StreamCount"
    // cHeaders will represent a number of m= line in SDP
    // ulNumStreams is actual number of streams in this presentation
    // They will be different on sure stream presentation becacause we are
    // duplicating headers for each bandwidth.    
    // we have to iterate over the stream headers...        
    if (GetStreamCountNoTrust(&ppHeaders[1], cHeaders - 1, ulNumStreams) != TRUE)
    {
	// Well, This is not a SDP file generated by pplyfobj.cpp.
	// Trust the StreamCount!
	m_pFileHeader->GetPropertyULONG32("StreamCount", ulNumStreams);	    
	ppRealHeaders = new IHXValues*[ulNumStreams];
	for ( i = 0; i < (int)ulNumStreams; i++)
	{
	    ppRealHeaders[i] = ppHeaders[i+1];
	    ppRealHeaders[i]->AddRef();
	}
    }
    else
    {
    	// we should have at least one stream
    	HX_ASSERT(ulNumStreams > 0);
    
        // set the StreamCount
	m_pFileHeader->SetPropertyULONG32("StreamCount", ulNumStreams);	    
    
    	/*
     	* Get a BW for each stream to "subscribe" to
     	*/
    	pulSubscriptionBW = new UINT32[ulNumStreams];
    	memset(pulSubscriptionBW, 0, sizeof(UINT32) * ulNumStreams);

	if (GetSubscriptionBW(m_pFileHeader, 
			  &ppHeaders[1], 
			  cHeaders - 1, 
			  pulSubscriptionBW, 
			  ulNumStreams,
			  pBandwidth) != TRUE)
	{
	    // this should never happen
	    theErr = HXR_FAIL;
	    goto bail;
	}

	// if we have gotten BW info from pref., then check for an actual 
	// BW info.
	if (bFoundBW)
	{
	    UINT32 ulBW = 0;
	    for (UINT32 i = 0; i < ulNumStreams; i++)
	    {
	    	ulBW += pulSubscriptionBW[i];
	    }
	
	    if (ulBW > (UINT32)atol((const char*)pBandwidth->GetBuffer()))
	    {		
		// this will end a presentation	    
		theErr = HXR_FAIL;
		ReportError(IDS_ERR_SM_NOTENOUGHBANDWIDTH, HXLOG_ERR, theErr);
		goto bail;
	    }
	}

	
    	/**************************
     	* Get right stream headers to pass to CPurePlaySource
     	* We have figured out BW to use for each stream
     	* This is expensive, but...hey, it's done only once
     	* Iterate over all the stream headers AGAIN, and Get right stream headers
     	* for each stream depending on subscription BW
     	*/
     	if (GetRightHeaders(ppRealHeaders,
     			ulNumStreams,
     			&ppHeaders[1],
     			cHeaders - 1,
     			pulSubscriptionBW) != TRUE)
     	{
     	    // this should never happen
	    theErr = HXR_FAIL;
	    goto bail;
     	}
    } 

    m_bRMPresentation = DetermineIfRMPresentation(
				 ppHeaders[0],
				 &(ppHeaders[1]),
				 ulNumStreams);
    /* 
     *	Almost done!
     *  Now that we have the right headers, create CPurePlaySource
     *  for each stream
     */    
    UINT32  ulRTPPayload;
    ulRTPPayload = 0;

    for (i = 0; i < (int)ulNumStreams; i++)
    {    
	HX_ASSERT(NULL != ppRealHeaders[i]);
	
	if (NULL == ppRealHeaders[i])
	{
	    theErr = HXR_FAIL;
	    goto bail;
	}
	/*
	 * CPurePlaySource will addref the header
	 */
	 
	IHXValues*	pSrcHeader;
	IHXBuffer*	pSrcAddr;
	UINT32		ulPort;
	UINT32		ulRTPFactor;
	UINT32		ulHXFactor;
	ULONG32		ulPreroll;

	ulRTPFactor = 0;
	ulHXFactor = 0;
	ulPreroll = 0;

	pSrcHeader = ppRealHeaders[i];
	pSrcHeader->AddRef();

	theErr = pSrcHeader->GetPropertyULONG32(PROP_RTP_PAYLOAD,
    						ulRTPPayload);
	if (HXR_OK != theErr)
	{
	    HX_ASSERT(FALSE);
	    ReportError(IDS_ERR_SM_NOTFOUNDPAYLOAD, HXLOG_ERR, theErr);
	    goto bail;
	}
	
	theErr = pSrcHeader->GetPropertyCString(PROP_MULTI_ADDRESS, 
	    pSrcAddr);
    	if (theErr != HXR_OK)
    	{
	    /*
	     * It is OK for now that we don't have the address
	     * we'll use it from the file header if there
	     */
	    if (pFileAddr == NULL)
	    {
    		pSrcHeader->Release();
    		ReportError(IDS_ERR_SM_NOTFOUNDADDRESS, HXLOG_ERR, theErr);
		goto bail;
	    }

	    pSrcAddr = pFileAddr;
	    pSrcAddr->AddRef();
	    
	    theErr = HXR_OK;
    	}
    
    	theErr = pSrcHeader->GetPropertyULONG32(PROP_MULTI_PORT, ulPort);
    	if (theErr != HXR_OK)
    	{
	    /*
    	     * This is a problem.  No port, no play
    	     */
    	    pSrcHeader->Release();
    	    ReportError(IDS_ERR_SM_NOTFOUNDPORT, HXLOG_ERR, theErr);
    	    goto bail;
    	}

    	/*
    	 * Get the RTP->RMA timestamp conversion ratio from the stream
    	 * headers.
    	 */
    	pSrcHeader->GetPropertyULONG32(PROP_RTP_FACTOR, ulRTPFactor);
    	pSrcHeader->GetPropertyULONG32(PROP_HX_FACTOR, ulHXFactor);

    	if ((ulRTPFactor == 0) || (ulHXFactor == 0))
    	{
	    ulHXFactor = 1000;

	    if (!m_bRMPresentation)
	    {
		pSrcHeader->GetPropertyULONG32("SamplesPerSecond", ulRTPFactor);
		
		/*
		* Try to map payload number to sampling rate
		*/
		if (ulRTPFactor == 0)
		{
		    ulRTPFactor = SDPMapPayloadToSamplesPerSecond(ulRTPPayload);
		}
		
		if (ulRTPFactor == 0)
		{
		    IHXBuffer* pMimeType = NULL;
		    
		    pSrcHeader->GetPropertyCString("MimeType", pMimeType);
		    
		    /*
		    * Try to map mime type to sampling rate
		    */
		    if (pMimeType)
		    {
			ulRTPFactor = SDPMapMimeToSamplesPerSecond(pMimeType);
		    }
		    
		    HX_RELEASE(pMimeType);
		}
	    }

	    if (ulRTPFactor == 0)
	    {
		ulRTPFactor = ulHXFactor;
	    }
    	}

	pSrcHeader->GetPropertyULONG32("Preroll", ulPreroll);
	if (ulPreroll == 0)
	{
	    pSrcHeader->GetPropertyULONG32("Predata", ulPreroll);
	}

	if (ulPreroll == 0)
	{
	    IHXBuffer* pSDPData = NULL;

	    if (SUCCEEDED(pSrcHeader->GetPropertyCString("SDPData", pSDPData)))
	    {
		char *pData = (char*) pSDPData->GetBuffer();
		IHXValues *pValues = NULL;

		if (pData &&
		    SUCCEEDED(SDPParseChunk(pData,
					    strlen(pData),
					    pValues,
					    m_pClassFactory,
					    SDPCTX_Renderer)))
		{
		    pValues->GetPropertyULONG32("Preroll", ulPreroll);
		}

		HX_RELEASE(pValues);
	    }

	    if (ulPreroll != 0)
	    {
		pSrcHeader->SetPropertyULONG32("Preroll", ulPreroll);
	    }

	    HX_RELEASE(pSDPData);
	}

	/*
	 *  take care of interfaces
	 */	 
	CHXPtrArray* prgStreams = NULL;
	for (UINT32 j = 0; j < m_pInterfaceMgr->m_ulNumInterfaces; ++j)
	{
	    // only one interface to worry about...
	    CPurePlaySource*  pSrc = new CPurePlaySource(this,
					    	pSrcHeader,
					    	i,
					    	ulRTPFactor,
					    	ulHXFactor,
						m_bRMPresentation);					    
	    pSrc->AddRef();
    
	    theErr = pSrc->Init(m_pContext,
			    	(char*)pSrcAddr->GetBuffer(),
			    	m_pInterfaceMgr->m_pulInterfaces[j],
			    	HX_SAFEUINT16(ulPort));
			    					       	    
    	    if (theErr != HXR_OK) goto bail;

	    // get an array for this interface
	    prgStreams = m_pInterfaceMgr->GetSession(j);
	    if (!prgStreams)
	    {
		HX_ASSERT(0 == i);
		prgStreams = new CHXPtrArray();
	    }

	    // set a source for this interface
	    prgStreams->SetAtGrow(i, pSrc);		
	    m_pInterfaceMgr->SetSession(j, prgStreams);
	}

    	pSrcHeader->Release();
    	pSrcAddr->Release();
    }

bail:

    /*
     * We no longer need the file object so release it on CloseDone
     */
    if (m_pFileObject) 
    {
	m_pFileObject->Close();
	HX_RELEASE(m_pFileObject);
    }	

    HX_VECTOR_DELETE(pulSubscriptionBW);
    HX_RELEASE(pBandwidth);


    // release ppRealHeaders too
    if (NULL != ppRealHeaders)
    {
	for (int i = 0; i < (int)ulNumStreams; i++)
	{
	    HX_RELEASE(ppRealHeaders[i]);
	}
	HX_VECTOR_DELETE(ppRealHeaders);
    }

    if (NULL != ppHeaders)
    {
	for (int i = 0; i < (int)cHeaders; i++)
	{
	    // we didn't AddRef() those
	    HX_RELEASE(ppHeaders[i]);
	}
	HX_VECTOR_DELETE(ppHeaders);
    }
        
    HX_RELEASE(pFileAddr);

    if ((NO_MULTICAST == m_tryUnicast) || (theErr != HXR_OK))
    {
	Close();
	theErr = HXR_CLOSED;
    }
    else
    {
    	/*
     	 * Start listening
     	 */     	 
     	CHXPtrArray* prgStreams = NULL;
     	CPurePlaySource* pSrc = NULL;
    	for (UINT32 j = 0; j < m_pInterfaceMgr->m_ulNumInterfaces; j++)
    	{    	
	    prgStreams =m_pInterfaceMgr->GetSession(j);     	    
    	    for (i = 0; i < prgStreams->GetSize(); i++)    	    
    	    {
		pSrc = (CPurePlaySource*)prgStreams->GetAt(i);
		pSrc->Begin();
	    }		
    	}
    }

    m_state = PendingFileHeader;

    if (theErr != HXR_CLOSED)
    {
	return MakeHeader(theErr);
    }

    return HXR_OK;
}


BOOL
CPurePlayFileFormat::GetStreamCountNoTrust(IHXValues** ppHeaders, 
					   UINT16 unNumHeader,
					   REF(ULONG32) ulNumStreams)					  
{
    HX_ASSERT(NULL != ppHeaders);

    ULONG32	ulID;
    BOOL	rgFound[256];    
    
    memset(rgFound, 0, 256);

    for (UINT16 i = 0; i < unNumHeader; i++)
    {
	HX_ASSERT(ppHeaders[i] != NULL);
	IHXValues*	    pSrcHeader;

	pSrcHeader = ppHeaders[i];
	pSrcHeader->AddRef();

	// "StreamId" is the field that ppfobj.cpp puts for a group of
	// streams 
	if (HXR_OK == pSrcHeader->GetPropertyULONG32("StreamId", ulID))
	{
	    if (!rgFound[ulID])
	    {   
		rgFound[ulID] = TRUE;
		ulNumStreams++;
	    }
	}
	else
	{
	    // OK, trust the "StreamCount".  This is not a SDP file generated
	    // by pplyfobj.cpp
	    ulNumStreams = 0;
	    HX_RELEASE(pSrcHeader);
	    return FALSE;
	}
	HX_RELEASE(pSrcHeader);
    }

    return TRUE;
}


BOOL CPurePlayFileFormat::DetermineIfRMPresentation(
				IHXValues* pFileHeader,
				IHXValues** ppStrmHeaders,
				ULONG32 ulNumStreams)
{
    BOOL bIsRMPresentation = FALSE;

    if (pFileHeader && ppStrmHeaders && ulNumStreams)
    {
	IHXValues* pStrmHdr = NULL;
	IHXBuffer* pASMRuleBook = NULL;
	IHXBuffer* pMimeType = NULL;
	ULONG32 ulRTPPayload = RTP_PAYLOAD_RTSP + 1;
	BOOL bIsRMStream = FALSE;
	ULONG32 ulIdx;

	bIsRMPresentation = TRUE;

	for (ulIdx = 0; bIsRMPresentation && (ulIdx < ulNumStreams); ulIdx++)
	{
	    pStrmHdr = ppStrmHeaders[ulIdx];

	    bIsRMStream = FALSE;

	    if (pStrmHdr)
	    {
		pStrmHdr->GetPropertyULONG32(PROP_RTP_PAYLOAD, ulRTPPayload);

		if (ulRTPPayload == RTP_PAYLOAD_RTSP)
		{
		    bIsRMStream = TRUE;
		}

		ulRTPPayload = RTP_PAYLOAD_RTSP + 1;

		if (bIsRMStream)
		{
		    bIsRMStream = FALSE;
		    
		    pStrmHdr->GetPropertyCString("ASMRuleBook", pASMRuleBook);
		    
		    if (pASMRuleBook)
		    {
			bIsRMStream = TRUE;
		    }
		}

		HX_RELEASE(pASMRuleBook);

		if (bIsRMStream)
		{
		    bIsRMStream = FALSE;

		    pStrmHdr->GetPropertyCString("MimeType", pMimeType);

		    if (pMimeType)
		    {
			if (strstr((const char*) pMimeType->GetBuffer(),
				   HX_COMMON_MIME_TYPE_FRAGMENT))
			{
			    bIsRMStream = TRUE;
			}
		    }
		}

		HX_RELEASE(pMimeType);
	    }

	    bIsRMPresentation = (bIsRMStream && bIsRMPresentation);
	}
    }

    return bIsRMPresentation;
}


BOOL
CPurePlayFileFormat::GetSubscriptionBW(IHXValues* pFileHeader, 
				       IHXValues** ppStrmHeaders,
				       UINT16 unNumStrmHeaders,
				       REF(UINT32*) pulSubscriptionBW,
				       UINT32 ulNumStreams,
				       IHXBuffer* pBandwidth)					  
{
    HX_ASSERT(pFileHeader);
    HX_ASSERT(ppStrmHeaders);
    HX_ASSERT(unNumStrmHeaders >= 1);
    HX_ASSERT(pulSubscriptionBW);
    HX_ASSERT(ulNumStreams >= 1);
    HX_ASSERT(pBandwidth);

    pFileHeader->AddRef();
    pBandwidth->AddRef();

    IHXBuffer*	pRuleBuf = NULL;

    if (HXR_OK != pFileHeader->GetPropertyCString("ASMRuleBook", pRuleBuf))
    {
	// OK, this is a single stream presentation.  Take an ASMRuleBook from 
	// any of stream headers (they are all the same), and use it to decide
	// which stream header to use depending on bit rate
	HX_ASSERT(1 == ulNumStreams);

	// get ASMRuleBook
	HX_ASSERT(NULL != ppStrmHeaders[0]);
	IHXValues* pHeader = ppStrmHeaders[0];
	pHeader->AddRef();

	if (HXR_OK == pHeader->GetPropertyCString("ASMRuleBook", pRuleBuf))
	{
	    ASMRuleBook	    rules((char*)pRuleBuf->GetBuffer());
	    HX_RELEASE(pRuleBuf);
	    IHXBuffer*	    pBuffer	    = NULL;
	    UINT16	    unRules;
    
	    unRules = rules.GetNumRules();
    
	    // get subscriptions for this bandwidth
	    BOOL bSubInfo[256];
	    UINT16 unRuleNum;
	    IHXValues* pValues;
    	    pValues = new CHXHeader();
    	    pValues->AddRef();
	    
	    pValues->SetPropertyCString("Bandwidth", pBandwidth);
	    rules.GetSubscription(bSubInfo, pValues);
	    
	    HX_RELEASE(pValues);
	    // get a rule number that we are interested in
	    int y;
	    for (y = 0; y < (int)unRules; y++)
	    {
	    	if (TRUE == bSubInfo[y])
	    	{
	    	    IHXBuffer* pBw = 0;
		    unRuleNum = y;
		    
		    // make sure AverageBandwidth != 0
		    rules.GetProperties(y, pValues);		    
		    
		    if (HXR_OK == pValues->GetPropertyCString("AverageBandwidth", 
							      pBw))
		    {
		    	pulSubscriptionBW[0] += atol((const char*)pBw->GetBuffer());
		    	HX_RELEASE(pBw);		    	
		    }
		    else
		    {
			// TimeStampDelivery only stream
			pulSubscriptionBW[0] = 0;
		    }
		    HX_RELEASE(pValues);
	    	}
	    }
	}
	else
	{
	    // There is no ASMRuleBook at all...
	    // This should never happen.
	    HX_RELEASE(pFileHeader);
	    HX_RELEASE(pBandwidth);
	    HX_RELEASE(pHeader);
	    HX_ASSERT(FALSE);
	    return FALSE;
	}

	HX_RELEASE(pHeader);
    }
    else // for if (HXR_OK == m_pFileHeader->GetPropertyCString("ASMRuleBook", pRuleBuf))
    {
	// this is a multiple stream presentation.
	// take ASMRuleBook for a file and figure out BW to use for 
	// each stream
	ASMRuleBook	rules((char*)pRuleBuf->GetBuffer());
	HX_RELEASE(pRuleBuf);
	IHXBuffer*	pBuffer	    = NULL;
	UINT16		unRules;

	unRules = rules.GetNumRules();    

	// get subscriptions for this bandwidth
	BOOL bSubInfo[256];
	UINT16 unRuleNum;
	IHXValues* pValues;
    	pValues = new CHXHeader();
    	pValues->AddRef();
	
	pValues->SetPropertyCString("Bandwidth", pBandwidth);
	rules.GetSubscription(bSubInfo, pValues);
	HX_RELEASE(pValues);
	
	// get a rule number that we are interested in
	// Assuming there is only one TRUE
	int y;
	for (y = 0; y < (int)unRules; y++)
	{
	    if (TRUE == bSubInfo[y])
	    {
	    	// there should be only one
		unRuleNum = y;
		break;
	    }
	}

#ifdef BW_BASED
	// unRuleNum is the rule number that we are interested in
	m_lRuleNumber = (INT32)unRuleNum;		
#endif	


	// Get a BW for each stream
        rules.GetProperties((int)unRuleNum, pValues);
        for (int i = 0; i < (int)ulNumStreams; i++)
        {
            char rgStreamBW[32]; /* Flawfinder: ignore */
            sprintf(rgStreamBW, "Stream%dBandwidth", i); /* Flawfinder: ignore */
            if (HXR_OK == pValues->GetPropertyCString((const char*)rgStreamBW, 
						      pBuffer))
	    {
		pulSubscriptionBW[i] = (UINT32)atol((const char*)pBuffer->GetBuffer());
		HX_RELEASE(pBuffer);
	    }
        }
	HX_RELEASE(pValues);	

    }

    HX_RELEASE(pFileHeader);
    HX_RELEASE(pBandwidth);
    return TRUE;
}

BOOL
CPurePlayFileFormat::GetRightHeaders(REF(IHXValues**) ppRealHeaders, // out
     				     UINT32	  ulNumStreams,
     				     IHXValues** ppHeaders,
     				     UINT32	  cHeaders,
     				     UINT32*      pulSubscriptionBW)
{
    HX_ASSERT(ulNumStreams >= 1);
    HX_ASSERT(ppHeaders);
    HX_ASSERT(pulSubscriptionBW);

    
    ppRealHeaders = new IHXValues*[ulNumStreams];
    memset(ppRealHeaders, NULL, sizeof(IHXValues*) * ulNumStreams);
    
    
    for (int i = 0; i < (int)ulNumStreams; i++)
    {
	ULONG32 ulID = 0;
	ULONG32 ulBW = 0;;
	BOOL    bFound = FALSE;
	for (int j = 0; j < (int)cHeaders; j++)
    	{
    	    HX_ASSERT(NULL != ppHeaders[j]);
    	    IHXValues* pSrcH;
    	    pSrcH = ppHeaders[j];
	    pSrcH->AddRef();
	    if ((HXR_OK == pSrcH->GetPropertyULONG32("AvgBitRate", ulBW)) &&
		(ulBW == pulSubscriptionBW[i]))
	    {
		// this one has the right BW, how about stream number?
		if ((HXR_OK == pSrcH->GetPropertyULONG32("StreamId", ulID)) &&
		    ((int)ulID == i))
		{
#ifdef BW_BASED		
		    // if this is more than one stream presentation,
		    // we have to make sure the rule number.
		    // if "RuleNumber" does not exist, this is from the old 
		    // plugin, then, it should ignore this check.
		    UINT32 ulRuleNum = 0;
		    if ((-1 == m_lRuleNumber) ||
			(HXR_OK != pSrcH->GetPropertyULONG32("RuleNumber", ulRuleNum)) ||
			((INT32)ulRuleNum == m_lRuleNumber))
		    {
#endif		    
		    bFound = TRUE;
		    
		    // This is the right heaader, 
		    ppRealHeaders[i] = pSrcH;
		    ppRealHeaders[i]->AddRef();
		    HX_RELEASE(pSrcH);		    
		    break; // we found for this stream, go to next one
#ifdef BW_BASED		    
		    }
#endif		    
		}
	    }
	    HX_RELEASE(pSrcH);
	}

	if (!bFound)
	{
	    // this should never haappen
	    ppRealHeaders[i] = NULL;
	    HX_ASSERT(FALSE);	    
	    return FALSE;
	}
    }

    return TRUE;
}


/****************************************************************************
 *  IHXFileFormatObject::Seek                               ref:  hxformt.h
 *
 *  This routine moves to the packet nearest to the requested time in the
 *  file. It is called, for example, in response to the user moving the
 *  slider to a different location in the playback timeline.
 */
STDMETHODIMP
CPurePlayFileFormat::Seek(UINT32 ulOffset)
{  
    m_pFFResponse->SeekDone(HXR_NOTIMPL);

    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::Close                              ref:  hxformt.h
 *
 *  This routine handles any cleanup required before closing the plug-in. All
 *  references to objects should be released and memory deallocated. This is
 *  called by the RMA core when the playback is finished or stopped.
 */
STDMETHODIMP
CPurePlayFileFormat::Close()
{
    /*
     * Release all of our resources that were allocated in 
     * InitFileFormat and along the way
     */
    AddRef();

    if (m_state != Closed)
    {    
	PurePlayFFState incommingState = m_state;
	BOOL bUnicastLaunched = FALSE;

	m_state = Closed;

    	if (m_tryUnicast != NO_TRY)
    	{
    	    if (HXR_OK == SwitchToUnicast())
	    {
		bUnicastLaunched = TRUE;
	    }
	    else
    	    {
		HX_ASSERT(!m_bFileHeaderReadyCalled);    	    
		switch (m_tryUnicast)		
		{
		case TIMEOUT:
		    ReportError(IDS_ERR_SM_NOACTIVESENDERS, HXLOG_ERR, HXR_FAIL);
		    break;
		case SOCKET_FAIL:
		    // there is no way to put address/port info through 
		    // FileHeaderReady(), so just display error here.
		    ReportErrorWithFormat(IDS_ERR_SM_SOCKFAILED, 
					  m_strLastErr, 
					  HXLOG_ERR, 
					  HXR_FAIL);
		    break;
		case NO_MULTICAST:
		    ReportError(IDS_ERR_SM_NOMULTICAST, HXLOG_ERR, HXR_FAIL);
		    break;
		default:
		    HX_ASSERT(!"Don't know this m_tryUnicast");
		    ReportError(IDS_ERR_SM_GENERAL, HXLOG_ERR, HXR_FAIL);
		}

		HX_ASSERT(m_pFFResponse);		
    	    }

    	    m_tryUnicast = NO_TRY;
    	}

	if (!bUnicastLaunched)
	{
	    if (incommingState == PendingFileHeader)
	    {
		if (m_pFFResponse)
		{
		    m_pFFResponse->FileHeaderReady(HXR_FAIL, NULL);
		}
	    }
	}

	if (m_hFileHeader)
	{
	    m_pScheduler->Remove(m_hFileHeader);
	    m_hFileHeader = 0;
	}

	HX_ASSERT((m_rgStreams.IsEmpty() && m_pInterfaceMgr) ||
		  (!m_rgStreams.IsEmpty() && !m_pInterfaceMgr)); 


	if (m_rgStreams.GetSize() >= 1 && WantClientStats())
	{	    
	    SendStatistics();
	}	    

	for (int i = m_rgStreams.GetSize() - 1; i >= 0; i--)
	{
	    m_pFFResponse->StreamDone(i);
	    CPurePlaySource* pSrc = (CPurePlaySource*)m_rgStreams.GetAt(i);	  
	    pSrc->Close();
	    pSrc->Release();
	}

	m_rgStreams.RemoveAll();

	HX_DELETE(m_pInterfaceMgr);
	HX_VECTOR_DELETE(m_ppInterfacesReady);
	m_ulNumInterfacesReady = 0;
	HX_RELEASE(m_pLog);
	HX_RELEASE(m_pScheduler);
	HX_RELEASE(m_pRegistry);
	HX_RELEASE(m_pClassFactory);

	HX_RELEASE(m_pStreamDesc);
	HX_RELEASE(m_pRTPInfo);
	HX_RELEASE(m_pFFResponse);
	if (m_pFileObject)
	{
	    m_pFileObject->Close();
	    HX_RELEASE(m_pFileObject);
	}
        ClearFileBufferList();
        HX_DELETE(m_pFileBufferList);
	HX_RELEASE(m_pSDPDesc);
	HX_RELEASE(m_pFileHeader);
	HX_RELEASE(m_pStatusDesc);

	HX_RELEASE(m_pRequest);

	HX_RELEASE(m_pCName);
	HX_RELEASE(m_pUserName);
	HX_RELEASE(m_pTool);
	HX_RELEASE(m_pEmail);
    }

#ifdef XXXGo_DEBUG
    if (m_file)
    {
	fclose(m_file); 
    }
#endif	// XXXGo_DEBUG

#if defined(_TIMELINE_TRACE) || defined(_GETPACKET_TRACE)
    if (m_tfile)
    {
	fclose(m_tfile); 
    }
#endif	// _TIMELINE_TRACE || _GETPACKET_TRACE
    
    Release();

    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::CloseDone                          ref:  hxformt.h
 *
 */
STDMETHODIMP
CPurePlayFileFormat::CloseDone(HX_RESULT status)
{
    // it should have been released in ReadDone()
    HX_ASSERT(m_pFileObject);
    return HXR_OK;
}


/****************************************************************************
 *  IHXFileFormatObject::WriteDone                          ref:  hxformt.h
 *
 */
STDMETHODIMP
CPurePlayFileFormat::WriteDone(HX_RESULT status)
{
    return HXR_UNEXPECTED;
}

/****************************************************************************
 *  IHXASMSource::Subscribe                                   ref:  hxasm.h
 *
 */
STDMETHODIMP
CPurePlayFileFormat::Subscribe
(
    UINT16 uStreamNumber,
    UINT16 uRuleNumber
)
{
    return HXR_NOTIMPL;
}


/****************************************************************************
 *  IHXASMSource::Unsubscribe                                 ref:  hxasm.h
 *
 */
STDMETHODIMP
CPurePlayFileFormat::Unsubscribe
(
    UINT16 uStreamNumber,
    UINT16 uRuleNumber
)
{
    return HXR_NOTIMPL;
}

STDMETHODIMP
CPurePlayFileFormat::GetStatus
(				
    REF(UINT16)		uStatusCode, 
    REF(IHXBuffer*)	pStatusDesc, 
    REF(UINT16)		ulPercentDone
)
{
    // The message given here will be deleted by the core
    // "Loading (%)" message once we start returning packets to the core
    uStatusCode = m_uStatusCode;

    if (uStatusCode == HX_STATUS_CONTACTING)
    {
	pStatusDesc = new CHXBuffer();
	pStatusDesc->AddRef();
    
    	CHXString statusDesc;
    	statusDesc.Format("Connecting...");
    	pStatusDesc->Set((UCHAR*)(const char*) statusDesc,
			 strlen((const char*) statusDesc) + 1);

    }
    else if (uStatusCode == HX_STATUS_BUFFERING)
    {
	ulPercentDone = (100 * (HX_GET_BETTERTICKCOUNT() - m_ulListeningWaitStartTime)) /
			LISTENINING_TIME_INTERVAL;

	if (ulPercentDone > 99)
	{
	    ulPercentDone = 99;
	}

	pStatusDesc = new CHXBuffer();
	pStatusDesc->AddRef();

	CHXString statusDesc;
    	statusDesc.Format("Listening");
    	pStatusDesc->Set((UCHAR*)(const char*) statusDesc,
			 strlen((const char*) statusDesc) + 1);
    }

    return HXR_OK;
}


/*
** First figure out the stream with the smallest TS to send to the core.
** If packet is pending for the stream with smallest TS, send it.
*/
HX_RESULT
CPurePlayFileFormat::ProcessPendingPackets(BOOL bUseAutoTime,
					   ULONG32 ulTimeNow)
{
    /* we need to look for a packet with the smallest TS */
    INT32 j;
    UINT32 ulTS = 0;
    HX_RESULT theErr = HXR_OK;
    CPurePlaySource* pSource = NULL;
    CStream* pStream = NULL;

    if (bUseAutoTime)
    {
	ulTimeNow = HX_GET_BETTERTICKCOUNT();
    }

    /*
     * We may be waiting for a packet off the wire or we already have the
     * packet and need to send it to the core.  In GetPacket, the pending
     * flag is set for each stream that the core needs.  DataFromSocket will
     * add the packets for each stream to the pending list.
     */

    if (m_state == Closed)
    {
	// Since we have to send BYE msg right before we stop, and since we get 
	// what we send from the same socket, this pkt is our RTCP_BYE msg.
	return HXR_CLOSED;
    }
 
    if (0 >= m_uActiveStreams)
    {
    	// there is no active stream.  
    	return HXR_OK;
    }    

    HX_ASSERT(m_uActiveStreams == m_rgStreams.GetSize());
    
    if (!m_bFirstPktRcvd)
    {
	m_bFirstPktRcvd = TRUE;
	m_ulStartTimeMS = HX_GET_BETTERTICKCOUNT();
	m_ulHXOffset = 0;
    }

    /* Check if we need to look for a packet with the smallest TS */
    if (m_lSmallestTSStrm == -1)
    {
	INT32 lSmallest = -1;
	ULONG32 ulSmallest = 0;
	INT32 lLargest = -1;
	ULONG32 ulLargest = 0;
	BOOL bInitial = TRUE;
	BOOL bIsLost = FALSE;
	
	// Find the stream with the smallest time stamp
	// iterate through all the streams and compare the TS's
	for (j = 0; j < m_rgStreams.GetSize(); j++)
	{
	    if ((pSource = (CPurePlaySource*)m_rgStreams.GetAt(j)) &&
		(pStream = pSource->GetStreamByStrmId(j)))
	    {	    
		theErr = pStream->GetNextTS(ulTS, ulTimeNow);
		if (HXR_OK == theErr)
		{
		    if (bInitial)
		    {
			ulSmallest = ulTS;
			lSmallest = j;
			ulLargest = ulTS;
			lLargest = j;
			bInitial = FALSE;
		    }
		    else 
		    {
			if (!IS_TS1_LESS_THAN_EQUAL_TS2(ulSmallest, ulTS))
			{
			    ulSmallest = ulTS;
			    lSmallest = j;
			}
			else if (!IS_TS1_LESS_THAN_EQUAL_TS2(ulTS, ulLargest))
			{
			    ulLargest = ulTS;
			    lLargest = j;
			}
		    }
		}
		else if (HXR_BUFFERING == theErr)
		{			    
		    // We must fullfill buffering for all streams before
		    // proceeding to successfully resequence any
		    // out-of-order packets.  However, if we have nothing
		    // in the transport buffer, there is no limit to
		    // how long we may need to wait, so we do not wait
		    // in such case.
		    if (pStream->GetBufferedPacketCount() > 0)
		    {
#ifdef _TIMELINE_TRACE
			if (m_tfile)
			{
			    fprintf(m_tfile, 
				    "BUFFERING #%d  BufSze=%d BufAge=%d\n", 
				    j,
				    pStream->GetBufferedPacketCount(),
				    pStream->GetBufferAge(ulTimeNow));
			}			
#endif	// _TIMELINE_TRACE
			// if (!m_bFirstPktSent)
			{
			    // During initial buffering make sure we wait
			    // long enough to have good selection of the
			    // smallest time stamped packet
			    lSmallest = -1;
			    break;
			}
		    }
#ifdef _TIMELINE_TRACE
		    else if (m_tfile)
		    {
			fprintf(m_tfile, "EMPTY #%d\n", j);
		    }			
#endif	// _TIMELINE_TRACE
		}
		else if (HXR_STREAM_DONE == theErr)
		{
		    pSource->RemoveSource(pStream->m_ulSSRC);
#ifdef _TIMELINE_TRACE
		    if (m_tfile)
		    {
			fprintf(m_tfile, "STREAM_DONE #%d\n", j);
		    }			
#endif	// _TIMELINE_TRACE	    	    		    
		}
		else 
		{
		    // this packet is lost...
		    // Since a TS for next packet is unknown, we will use Last 
		    // TS for this stream -> this stream has to be the one with 
		    // the smallest TS as a packet with this TS has already been sent to 
		    // the core.
		    lSmallest = j;
		    bIsLost = TRUE;
#ifdef _TIMELINE_TRACE
		    if (m_tfile)
		    {
			fprintf(m_tfile, "LOST #%d\n", j);
		    }
#endif	// _TIMELINE_TRACE  
		    break;
		}	   	    	
	    }		
	}
	
	m_lSmallestTSStrm = lSmallest;
	
	// Handle Offset and Resync (only on data of non-lost packets)
	if ((!bIsLost) && (m_lSmallestTSStrm != -1))
	{
#ifdef _ALLOW_TS_OFFSETTING
#ifdef _DO_RESYNC
	    if (m_bFirstPktSent)
	    {
		LONG32 lTSDelta = (LONG32) (ulSmallest - m_ulSmallestTS);
		
		// If there is a jolt in the timeline - resync
		if ((!m_bDisableOffsetting) &&
		    ((lTSDelta > MAX_TIME_GAP_FORWARD) ||
		     (lTSDelta < (-MAX_TIME_GAP_BACKWARD))))
		{
		    // Resync
		    m_ulHXOffset += lTSDelta;
		    
#ifdef _TIMELINE_TRACE
		    if (m_tfile)
		    {
			fprintf(m_tfile, "RESYNC #%d by %dms\n", m_lSmallestTSStrm, lTSDelta);
		    }
#endif	// _TIMELINE_TRACE 
		    
		    if (!m_bRMPresentation)
		    {
			// Time stamps do not have to be ordered
			m_ulHXOffset -= STANDARD_START_HXOFFSET;
		    }
		}
#ifdef _DO_BUFFER_OCCUPANCY_CHECKS
		else
		{

		    lTSDelta = (LONG32) (ulLargest - m_ulSmallestTS);
		    
		    if (lTSDelta > MAX_TIME_GAP_FORWARD)
		    {
			if ((pSource = (CPurePlaySource*) m_rgStreams.GetAt(lLargest)) &&
			    (pStream = pSource->GetStreamByStrmId(lLargest)))
			{
			    if (pStream->GetBufferedPacketCount() > MAX_SUSPENDED_BUFFER_SIZE)
			    {
				// We have large number of packets accumulated in the
				// transport buffer and no chance of reducing the
				// buffer count any time soon.
				// Remove a packet from this stream.
				IHXPacket*	pPacket = NULL;
				pStream->GetPacket(pPacket, ulTimeNow);
				HX_RELEASE(pPacket);
			    }
			}
		    }
		}
#endif	// _DO_BUFFER_OCCUPANCY_CHECKS
	    }
#endif	// _DO_RESYNC
#endif	// _ALLOW_TS_OFFSETTING
	    
	    m_ulSmallestTS = ulSmallest;
	}
    }
	
    // Check if we found the smallest time stamp stream
    if (m_lSmallestTSStrm != -1)
    {	
	pSource = (CPurePlaySource*)m_rgStreams.GetAt(m_lSmallestTSStrm);
	
	if (pSource->GetPendingFlag(m_lSmallestTSStrm))
	{    
	    pStream = pSource->GetStreamByStrmId(m_lSmallestTSStrm);
	    HX_ASSERT(pStream);
	    
	    IHXPacket*	pPacket = NULL;
	    theErr = pStream->GetPacket(pPacket, ulTimeNow);
	    if (HXR_OK == theErr)
	    {	
		/*
		 * Fixup the packet with the correct stream number
		 */
		IHXBuffer* pBuffer = NULL;
		UINT16	    uStreamNumber;
		UINT8	    chASMFlags;
		UINT16	    uASMRule;
		ULONG32	    ulNewTS;
		ULONG32	    ulRTPTS;
		BOOL	    bLostPacket = pPacket->IsLost();
		BOOL	    bIsRMStream = pSource->IsRMSource();
		
		if (bIsRMStream)
		{
		    pPacket->Get(pBuffer,
				 ulTS, 
				 uStreamNumber, 
				 chASMFlags, 
				 uASMRule);
		}
		else
		{
		    ((IHXRTPPacket*) pPacket)->GetRTP(pBuffer,
						       ulTS, 
						       ulRTPTS,
						       uStreamNumber, 
						       chASMFlags, 
						       uASMRule);
		}

		if (bLostPacket)
		{
		    ulNewTS = pStream->GetLastHXTime();
		}
		else
		{
		    ulNewTS = ulTS;

#ifdef _ALLOW_TS_OFFSETTING
		    ulNewTS -= m_ulHXOffset;
		    
		    if ((m_ulHXOffset != 0) && bIsRMStream)
		    {
			IHXBuffer* pSubBuffer = NULL;
			LONG32 lOffset = ulNewTS - ulTS;
			
			if (lOffset != 0)
			{
			    switch (pSource->GetStreamType())
			    {
			    case CStream::STREAM_TYPE_VIDEO:
				OffsetRMVideoPacket(ulTS,
				    pBuffer->GetBuffer(),
				    pBuffer->GetSize(),
				    lOffset,
				    pSubBuffer,
				    m_pClassFactory);
				break;
			    case CStream::STREAM_TYPE_EVENT:
				OffsetRMEventPacket(ulTS,
				    pBuffer->GetBuffer(),
				    pBuffer->GetSize(),
				    lOffset,
				    pSubBuffer,
				    m_pClassFactory);
				break;
			    case CStream::STREAM_TYPE_IMGMAP:
				OffsetRMImageMapPacket(ulTS,
				    pBuffer->GetBuffer(),
				    pBuffer->GetSize(),
				    lOffset,
				    pSubBuffer,
				    m_pClassFactory);
				break;
			    default:
				/* nothing to do */
				break;
			    }
			}
			
			if (pSubBuffer)
			{
			    pBuffer->Release();
			    pBuffer = pSubBuffer;
			    pSubBuffer = NULL;
			}
		    }
#endif	// _ALLOW_TS_OFFSETTING
		}

		if (bIsRMStream)
		{
		    pPacket->Set(pBuffer,
				 ulNewTS,
				 m_lSmallestTSStrm, 
				 chASMFlags,
				 uASMRule);
		}
		else
		{
		    ((IHXRTPPacket*) pPacket)->SetRTP(pBuffer,
						       ulNewTS,
						       ulRTPTS,
						       m_lSmallestTSStrm, 
						       chASMFlags,
						       uASMRule);
		}
		
		if (bLostPacket)
		{
		    pStream->m_ulLost++;
		    
#ifdef XXXGo_DEBUG	
		    if (m_file)
		    {
			fprintf(m_file, "    LOST: Last Time (%u) \n", pStream->GetLastHXTime());
		    }			
		    if (pSource->m_pFFLog)
		    {
			fprintf(pSource->m_pFFLog, "    LOST: Last Time (%u) \n", pStream->GetLastHXTime());
		    }			
#endif
		    if (!m_bFirstPktSent)
		    {
			HX_ASSERT(m_bFirstPktSent);
			return HXR_OK;
		    }
		}
		else
		{
		    pStream->SetLastHXTime(ulNewTS);
#ifdef XXXGo_DEBUG		
		    if (m_file)
		    {
			fprintf(m_file, "        ALLGOOD(Strm=%u): ulTime(%u) - offset(%d) = %u\n", 
			    m_lSmallestTSStrm,
			    ulTS, 
			    m_ulHXOffset, ulTS - m_ulHXOffset);	 
		    }
		    if (pSource->m_pFFLog)
		    {
			fprintf(pSource->m_pFFLog, "%d:  rule(%d): ulTime(%u) - offset(%d) = %u\n", 
			    m_lSmallestTSStrm, uASMRule, ulTS, m_ulHXOffset, 
			    ulTS - m_ulHXOffset);
		    }
#endif	// XXXGo_DEBUG
		    
#ifdef _TIMELINE_TRACE
		    if (m_tfile)
		    {
			fprintf(m_tfile, "        ALLGOOD(Strm=%u): ulTime(%u) - offset(%d) = %u; RT=%u RelTS=%d BufSze=%d BufAge=%d\n", 
			    m_lSmallestTSStrm,
			    ulTS,
			    m_ulHXOffset, ulTS - m_ulHXOffset,
			    HX_GET_BETTERTICKCOUNT() - m_ulStartTimeMS,
			    ulNewTS - m_ulFirstTSSent,
			    pStream->GetBufferedPacketCount(),
			    pStream->GetBufferAge(ulTimeNow));
		    }
#endif	// _TIMELINE_TRACE
		    
		    HX_RELEASE(pBuffer);

		    // We allow over-drawing from the transport buffer once the
		    // the preroll is completed and normal packet-fetch time line
		    // is established.
		    // This is not needed for application to work, but rather
		    // a safety fallback in case preroll is exhausted.
		    if ((!pStream->IsBufferFlexible()) && m_bFirstPktSent)
		    {
			if (((ULONG32) (ulNewTS - m_ulFirstTSSent)) >=
			    FLEX_BUFFER_SWITCHOVER_TIME)
			{
			    pStream->SetBufferFlexible(TRUE);
			}
		    }
		}
		
		pStream->SetPendingFlag(FALSE);   
		if (!m_bFirstPktSent)
		{
		    m_ulFirstTSSent = ulNewTS;
		    m_bFirstPktSent = TRUE;
		}
		
#ifdef _GETPACKET_TRACE
		if (m_tfile)
		{
		    if (bIsRMStream)
		    {
			fprintf(m_tfile, "<<PacketReady strm=%2u  RT=%10u RelTS=%10d BSze=%7d BAge=%7d L=%d\n", 
			    m_lSmallestTSStrm, 
			    HX_GET_BETTERTICKCOUNT() - m_ulStartTimeMS,
			    ulNewTS - m_ulFirstTSSent,
			    pStream->GetBufferedPacketCount(),
			    pStream->GetBufferAge(ulTimeNow),
			    bLostPacket ? 1 : 0);
		    }
		    else
		    {
			fprintf(m_tfile, "<<PacketReady strm=%2u  RT=%10u TS=%10d RTPTS=%10d BSze=%7d BAge=%7d L=%d\n", 
			    m_lSmallestTSStrm, 
			    HX_GET_BETTERTICKCOUNT() - m_ulStartTimeMS,
			    ulNewTS,
			    ulRTPTS,
			    pStream->GetBufferedPacketCount(),
			    pStream->GetBufferAge(ulTimeNow),
			    bLostPacket ? 1 : 0);
		    }
		}			
#endif	// _GETPACKET_TRACE

		m_pFFResponse->PacketReady(HXR_OK, pPacket);
		
		pPacket->Release();
	    }
	    else if (HXR_STREAM_DONE == theErr)
	    {	    
		pSource->RemoveSource(pStream->m_ulSSRC);
		
#ifdef XXXGo_DEBUG		   
		if (pStream->IsMarkedAsEnd())
		{
		    fprintf(pSource->m_pFFLog, "BYE %d \n", i);
		}
#endif
		// it should be buffering.  Look at rtpmisc/packetq.cpp GetPacket()
		HX_ASSERT(pStream->IsMarkedAsEnd());
	    }
	    else
	    {
		HX_ASSERT(!"Should never never get here since GetNextTS is"
			   "checking against buffering");
	    }
	}
    }

    m_lSmallestTSStrm = -1;

    return HXR_OK;
}

HX_RESULT
CPurePlayFileFormat::StreamDone(HX_RESULT status, UINT16 iStream)
{
    if (status != HXR_OK)
    {
	CHXString str;
	str.Format("%d", iStream);
	ReportErrorWithFormat(IDS_ERR_SM_ABNORMALTERMINATION, str, HXLOG_ERR,
			      status);		
    }
    
    if (m_pFFResponse)
    {
	m_pFFResponse->StreamDone(iStream);
    }

    return HXR_OK;
};


HX_RESULT
CPurePlayFileFormat::GetCName(REF(IHXBuffer*) pCName)
{
    pCName = m_pCName;
    if (pCName != NULL)
    {
	pCName->AddRef();
	return HXR_OK;
    }

    return HXR_FAIL;
}

HX_RESULT
CPurePlayFileFormat::GetUserName(REF(IHXBuffer*) pUserName)
{
    pUserName = m_pUserName;
    if (pUserName != NULL)
    {
	pUserName->AddRef();
	return HXR_OK;
    }

    return HXR_FAIL;
}

HX_RESULT
CPurePlayFileFormat::GetTool(REF(IHXBuffer*) pTool)
{
    pTool = m_pTool;
    if (pTool != NULL)
    {
	pTool->AddRef();
	return HXR_OK;
    }

    return HXR_FAIL;
}

HX_RESULT
CPurePlayFileFormat::GetEmailName(REF(IHXBuffer*) pEmail)
{
    pEmail = m_pEmail;
    if (pEmail != NULL)
    {
	pEmail->AddRef();
	return HXR_OK;
    }

    return HXR_FAIL;
}

/*
 *  Iterate throu all interfaces on which we have received multicast pkts on,
 *  and start assigning stream numbers.  After this process m_rgStreams will 
 *  contain all PurePlaySource obj from which interface.
 */
UINT32
CPurePlayFileFormat::AssignStreamNumbers
(
    UINT32 ulNumInterfacesReady,
    CHXPtrArray** ppInterfacesReady)
{
    HX_ASSERT(m_rgStreams.IsEmpty());

    // accross all the IFs
    UINT16  unBaseStreamNum = 0;
    UINT16  unNextStreamNum = 0;    

    // for each IF
    CHXPtrArray* prgStreams = NULL;
    UINT16  unNumStreams = 0;
    CPurePlaySource* pSrc = NULL;
    UINT16  unNumStrm = 0;
    
    // go throu each IF, and consolidate them in one.
    for (UINT32 j = 0; j < ulNumInterfacesReady; j++)
    {
	prgStreams = ppInterfacesReady[j];
	ppInterfacesReady[j] = NULL;
	
    	unNumStreams	    = prgStreams->GetSize();
    	pSrc		    = NULL;
    	unNumStrm	    = 0;
    	
    	HX_ASSERT(1 <= unNumStreams); // got to be at least one stream
            
    	// since the IP in SDP may not contain a source, we may have to remove that
    	// source from the m_rgStreams. 
    
    	for (UINT32 i = 0; i < unNumStreams; i++)
    	{
	    pSrc = (CPurePlaySource*)prgStreams->GetAt(i);
	    
	    unNumStrm = (UINT16)pSrc->GetTrueNumSrc();

	    if (pSrc->IsEncryptedSource() &&
		pSrc->IsRMSource())
	    {
		m_bDisableOffsetting = TRUE;
	    }
	    
	    if (1 <= unNumStrm)
	    {
	    	// make This Src ID
	    	pSrc->MakeSsrc();
    	
	    	// if there is more than one sender source, we have to assign stream
	    	// numbers for them.
	    	unNextStreamNum = pSrc->SetBaseStreamNum(unBaseStreamNum);
	    	// since we have waited for a period of time, if we haven't 
	    	// received RTCP pkt, just set it as synced.
	    	pSrc->StopBuffering();
	    	
	    	// we need to put those streams
	    	for (UINT32 ul = unBaseStreamNum; 
	    	     ul < unNextStreamNum; 
	    	     ul++)
	    	{
	    	    m_rgStreams.SetAtGrow(ul, pSrc);
	    	    pSrc->AddRef();
	    	}
    
	    	// release the original AddRef
	    	HX_RELEASE(pSrc);
	    	unBaseStreamNum = unNextStreamNum;
	    }
	    else
	    {
	    	// the IP listed in SDP file contains no active source at
	    	// this moment.  Delete the source
	    	pSrc->Close();
	    	// this should delete the CPurePlaySource obj
	    	HX_RELEASE(pSrc);
	    }
    	}

    	prgStreams->RemoveAll();
    }

    return unNextStreamNum;
}

/*
 *  If there are multiple sessions in multiple interfaces, we will play them 
 *  all!
 */
HX_RESULT
CPurePlayFileFormat::ProcessFileHeaderCallback()
{
    UINT32 i;
    UINT32  ulNextTimeout = 0;
    UINT32  ulTimeoutVal = 0;
    BOOL    bAtLeastOneReady = FALSE;
    ULONG32 ulWaitTime = HX_GET_BETTERTICKCOUNT() - m_ulFileHeaderWaitStartTime;

    CHXPtrArray* prgStreams = NULL;
    HX_ASSERT(m_pInterfaceMgr && m_pInterfaceMgr->m_ulNumInterfaces);
    BOOL bLastOneMoreTry = m_bOneMoreTry;

    m_hFileHeader = NULL;

    if (m_ppInterfacesReady)
    {
	HX_ASSERT(m_ulNumInterfacesReady > 0);
	
	m_uActiveStreams = 	
	    (UINT16)AssignStreamNumbers(m_ulNumInterfacesReady, m_ppInterfacesReady);
	
	m_pFileHeader->SetPropertyULONG32("StreamCount", m_uActiveStreams);
	
#ifdef _DEBUG	
	m_bFileHeaderReadyCalled = TRUE;
#endif		    
	
	// they are already synched
	m_bInitialPacket = TRUE;
	
	HX_ASSERT(m_uActiveStreams >= 1);
	m_uStatusCode = HX_STATUS_READY; //HX_STATUS_BUFFERING;
	
	m_bSourceAdmissionClosed = TRUE;
	
	// if there RA stream?
	UINT32 ulRAStreamNum = 0;
	if (GetRAStreamNum(ulRAStreamNum))
	{
	    m_lRAStreamNum = (INT32)ulRAStreamNum;
	}
	else
	{
	    // there is no RA stream
	    m_lRAStreamNum = NO_EXIST;
	}
	
	// clean up 
	HX_DELETE(m_pInterfaceMgr);
	HX_VECTOR_DELETE(m_ppInterfacesReady);
	m_ulNumInterfacesReady = 0;

	m_state = Ready;

	return m_pFFResponse->FileHeaderReady(HXR_OK, m_pFileHeader);
    }
    
    // go through each IF
    for (i = 0; i < m_pInterfaceMgr->m_ulNumInterfaces; i++)
    {
    	prgStreams = m_pInterfaceMgr->GetSession(i);
    	if (IsFileHeaderReady(prgStreams, ulTimeoutVal))
    	{	
	    if (!m_ppInterfacesReady)
	    {
	    	m_ppInterfacesReady = new 
		    CHXPtrArray*[m_pInterfaceMgr->m_ulNumInterfaces];
	    	memset(m_ppInterfacesReady, 0, 
		    sizeof (CHXPtrArray*) * m_pInterfaceMgr->m_ulNumInterfaces);	    
	    }
    
	    m_ppInterfacesReady[m_ulNumInterfacesReady++] = prgStreams;
	    bAtLeastOneReady = TRUE;
    	}
    	else 
    	{
    	    // When FileHeader is not ready, ulTimeoutVal is set...
    	    // Take the bigger one
    	    if (ulTimeoutVal > ulNextTimeout)
    	    {
    		ulNextTimeout = ulTimeoutVal;
    	    }

    	    if (bLastOneMoreTry && !m_bOneMoreTry)
    	    {
    		// we are going to wait one more CB for the last time
    		break;
    	    }
    	}
    }

    /*
     * sanity check!!!
     */
    HX_ASSERT(m_ulNumInterfacesReady <= m_pInterfaceMgr->m_ulNumInterfaces);
    HX_ASSERT((bAtLeastOneReady && m_ulNumInterfacesReady && m_ppInterfacesReady) ||
	(!bAtLeastOneReady && !m_ulNumInterfacesReady && !m_ppInterfacesReady && 
	ulNextTimeout));

    if (!bAtLeastOneReady)
    {
	// If we are timed out, Close(), otherwise, schedule a CB
	BOOL bTimedOut = ulWaitTime > m_ulFailoverTimeout;

	if (bTimedOut)
	{
	    // this is it...there is no pkts coming in from any interface at all
	    m_tryUnicast = TIMEOUT;
	    m_bSourceAdmissionClosed = TRUE;
	    // this should be empty
	    HX_ASSERT(m_rgStreams.IsEmpty());
	    Close();	    	    
	}
	else
	{
	    // needs to be some valid number
	    HX_ASSERT(ulNextTimeout && ulNextTimeout < 0xffffffff);
	    
	    // there's got to be some stream, wait some more.
	    CPurePlayFileFormat::CFileHeaderCallback*	 
	    pFHCB = new CPurePlayFileFormat::CFileHeaderCallback(this);
    
    	    pFHCB->AddRef();
    	    m_hFileHeader = m_pScheduler->RelativeEnter(pFHCB, ulNextTimeout);
    	    pFHCB->Release();	
	}

	return HXR_OK;
    }

    // Give transport buffer some time to buffer up
    {
	CPurePlayFileFormat::CFileHeaderCallback*	 
	    pFHCB = new CPurePlayFileFormat::CFileHeaderCallback(this);
    
	pFHCB->AddRef();
	m_hFileHeader = m_pScheduler->RelativeEnter(pFHCB, TRANSPORT_BUFFERING_TIME);
	pFHCB->Release();
    }

    return HXR_OK;
}

/*
 *  returns TRUE iff we have a session to play
 */
BOOL
CPurePlayFileFormat::IsFileHeaderReady(CHXPtrArray* prgStreams, 
				       REF(UINT32) ulTimeoutVal)
{
    HX_ASSERT(prgStreams);

    BOOL		bReady = TRUE;
    UINT16		unNumStreams = prgStreams->GetSize();    
    CPurePlaySource*	pSrc = NULL;
    HX_ASSERT(unNumStreams);

    ulTimeoutVal = PP_RM_TIMEOUT_MS;

    if (m_bRMPresentation)
    {
	// we know exactly how many source is in each session...ONE!
	for (UINT16 i = 0; i < unNumStreams; i++)
	{
	    pSrc = (CPurePlaySource*)prgStreams->GetAt(i);
	    HX_ASSERT(pSrc);
	    
	    if (pSrc->GetStartedSourceCount() == 0)
	    {
		// we haven't received coherent packets yet.
		bReady = FALSE;
		break;
	    }
	}
    }
    else
    {
	bReady = FALSE;
	// we have to decide a number of sources in each session...
	for (UINT16 i = 0; i < unNumStreams; i++)
	{
	    pSrc = (CPurePlaySource*)prgStreams->GetAt(i);
	    HX_ASSERT(pSrc);
	
	    if (pSrc->GetStartedSourceCount() >= 1)
	    {
		m_uStatusCode = HX_STATUS_BUFFERING;
		m_ulListeningWaitStartTime = HX_GET_BETTERTICKCOUNT();

		if (m_bOneMoreTry)
		{
		    // we have recieved at least one packet in this session.
		    // wait a couple more second to decide HOW MANY there are.
		    ulTimeoutVal = PP_SYNC_TIMEOUT_MS;
		    m_bOneMoreTry = FALSE;

		}
		else
		{
		    bReady = TRUE;
		}

		// we've made up our mind!!!, so don't have to go throu the rest...
		break;
	    }
	}    
    }

    return bReady;
}


/* 
*   Error reporting routines
*   Given an error id, it will try to read in a error string from a resource 
*   file.  If it fails, get a default error string.
*   Then, report!
*/ 
void
CPurePlayFileFormat::ReportError(UINT32 ulErrID, const UINT8 chSeverity, HX_RESULT ulHXCode)
{
    if (!m_pLog)
    {
	// try to get it one more time!
	HX_RESULT theErr = HXR_FAIL;
	if (m_pContext)
	{
	    theErr = m_pContext->QueryInterface(IID_IHXErrorMessages, (void**)&m_pLog);
	}	    

	if (HXR_OK != theErr)
	{
	    // there is nothing we can do :(
	    HX_ASSERT(FALSE && "ReportError: no Logger");
	    return;
	}
    }

    HX_ASSERT(m_pLog);
    CHXString strErr;
    HX_ASSERT(strErr.IsEmpty());
    
    if (HXR_OK != GetResourceErrorString(ulErrID, strErr))
    {
	GetDefaultErrorString(ulErrID, strErr);
    }

    HX_ASSERT(!strErr.IsEmpty());
    m_pLog->Report(chSeverity, ulHXCode, 0, (const char*)strErr, 0);
}

void
CPurePlayFileFormat::ReportErrorWithFormat(UINT32 ulErrID, REF(CHXString) strText, 
					   const UINT8 chSeverity, HX_RESULT ulHXCode)
{
    if (!m_pLog)
    {
	// try to get it one more time!
	HX_RESULT theErr = HXR_FAIL;
	if (m_pContext)
	{
	    theErr = m_pContext->QueryInterface(IID_IHXErrorMessages, (void**)&m_pLog);
	}	    

	if (HXR_OK != theErr)
	{
	    // there is nothing we can do :(
	    HX_ASSERT(FALSE && "ReportError: no Logger");
	    return;
	}
    }

    HX_ASSERT(m_pLog);
    CHXString strErr;
    HX_ASSERT(strErr.IsEmpty());
    
    if (HXR_OK != GetResourceErrorString(ulErrID, strErr))
    {
	GetDefaultErrorString(ulErrID, strErr);
    }

    HX_ASSERT(!strErr.IsEmpty());

    // look for %s in theErr
    HX_ASSERT(strErr.Find("%s") != -1);

    CHXString strTheErr;
    strTheErr.Format((const char*)strErr, (const char*)strText);
    
    m_pLog->Report(chSeverity, ulHXCode, 0, (const char*)strTheErr, 0);
}


HX_RESULT
CPurePlayFileFormat::GetResourceErrorString(UINT32 ulErrID, REF(CHXString) strErr)
{
    if (!m_pContext)
    {
	HX_ASSERT(FALSE && "GetResourceErrorString: no context");
	return HXR_FAIL;
    }

    HX_RESULT theErr = HXR_FAIL;
    IHXExternalResourceManager* pResMgr = NULL;
    theErr = m_pContext->QueryInterface(IID_IHXExternalResourceManager, (void**)&pResMgr);
    if (HXR_OK != theErr)
    {
	return theErr;
    }
    
    IHXExternalResourceReader* pResRdr = NULL;
    theErr = pResMgr->CreateExternalResourceReader(CORE_RESOURCE_SHORT_NAME, pResRdr);
    if (HXR_OK != theErr)
    {
	HX_RELEASE(pResMgr);
	return theErr;
    }

    
#ifdef _WINDOWS
    char szDLLPath[1024]; /* Flawfinder: ignore */
    GetModuleFileName((HMODULE)g_hInstance, 
		      OS_STRING2(szDLLPath, sizeof(szDLLPath)), 
		      sizeof(szDLLPath));
    pResRdr->SetDefaultResourceFile(szDLLPath);
#endif

    IHXXResource* pRes = pResRdr->GetResource(HX_RT_STRING, ulErrID);
    if (!pRes)
    {
	HX_RELEASE(pResRdr);
	HX_RELEASE(pResMgr);
	return HXR_FAIL;
    }

    // wirte the error string to the out parameter
    strErr = (const char*)pRes->ResourceData();

    HX_RELEASE(pRes);
    HX_RELEASE(pResRdr);
    HX_RELEASE(pResMgr);

    return HXR_OK;
}


void
CPurePlayFileFormat::GetDefaultErrorString(UINT32 ulErrID, REF(CHXString) strErr)
{
    switch(ulErrID)
    {
	case IDS_ERR_SM_GENERAL:
	    strErr = ERRSTR_SM_GENERAL;
	    break;
	case IDS_ERR_SM_INITFAILED:
	    strErr = ERRSTR_SM_INITFAILED;
	    break;
	case IDS_ERR_SM_SOCKFAILED:
	    strErr = ERRSTR_SM_SOCKFAILED;
	    break;
	case IDS_ERR_SM_ABNORMALTERMINATION:
	    strErr = ERRSTR_SM_ABNORMALTERMINATION;
	    break;
	case IDS_ERR_SM_NOACTIVESENDERS:
	    strErr = ERRSTR_SM_NOACTIVESENDERS;
	    break;
	case IDS_ERR_SM_NOSDPPLIN:
	    strErr = ERRSTR_SM_NOSDPPLIN;
	    break;
	case IDS_ERR_SM_BADSDPFILE:
	    strErr = ERRSTR_SM_BADSDPFILE;
	    break;
	case IDS_ERR_SM_NOPLAYTYPE:
	    strErr = ERRSTR_SM_NOPLAYTYPE;
	    break;
	case IDS_ERR_SM_NOTFOUNDPAYLOAD:
	    strErr = ERRSTR_SM_NOTFOUNDPAYLOAD;
	    break;
	case IDS_ERR_SM_NOTFOUNDADDRESS:
	    strErr = ERRSTR_SM_NOTFOUNDADDRESS;
	    break;
	case IDS_ERR_SM_NOTFOUNDPORT:
	    strErr = ERRSTR_SM_NOTFOUNDPORT;
	    break;
	case IDS_ERR_SM_UNEXPECTEDPAYLOAD:
	    strErr = ERRSTR_SM_UNEXPECTEDPAYLOAD;
	    break;
	case IDS_ERR_SM_NOTENOUGHBANDWIDTH:
	    strErr = ERRSTR_SM_NOTENOUGHBANDWIDTH;
	    break;
	case IDS_ERR_SM_NOMULTICAST:
	    strErr = ERRSTR_SM_NOMULTICAST;
	    break;
	default:
	    strErr = ERRSTR_SM_GENERAL;
	    break;
    }
}

void CPurePlayFileFormat::ClearFileBufferList()
{
    if (m_pFileBufferList)
    {
        LISTPOSITION pos = m_pFileBufferList->GetHeadPosition();
        while (pos)
        {
            IHXBuffer* pBuffer = (IHXBuffer*) m_pFileBufferList->GetNext(pos);
            HX_RELEASE(pBuffer);
        }
        m_pFileBufferList->RemoveAll();
    }
}

HX_RESULT CPurePlayFileFormat::AggregateBuffers(REF(IHXBuffer*) rpBuffer)
{
    HX_RESULT retVal = HXR_FAIL;

    // Do we have a file buffer list?
    if (m_pFileBufferList && m_pFileBufferList->GetCount() > 0)
    {
        // Do we have only 1 buffer?
        if (m_pFileBufferList->GetCount() == 1)
        {
            // Get the only buffer in the list
            IHXBuffer* pAggBuffer = (IHXBuffer*) m_pFileBufferList->GetHead();
            if (pAggBuffer)
            {
                // Assign the out parameter
                HX_RELEASE(rpBuffer);
                rpBuffer = pAggBuffer;
                rpBuffer->AddRef();
                // Clear the return value
                retVal = HXR_OK;
            }
        }
        else
        {
            // We have more than one buffer, must aggregate
            //
            // Get the total size of the buffers
            UINT32 ulTotalSize = 0;
            LISTPOSITION pos = m_pFileBufferList->GetHeadPosition();
            while (pos)
            {
                IHXBuffer* pListBuffer =
                    (IHXBuffer*) m_pFileBufferList->GetNext(pos);
                if (pListBuffer)
                {
                    ulTotalSize += pListBuffer->GetSize();
                }
            }
            if (ulTotalSize)
            {
                // Create an aggregated buffer
                IHXBuffer* pAggBuffer = NULL;
                retVal = m_pClassFactory->CreateInstance(CLSID_IHXBuffer,
                                                         (void**) &pAggBuffer);
                if (SUCCEEDED(retVal))
                {
                    retVal = pAggBuffer->SetSize(ulTotalSize);
                    if (SUCCEEDED(retVal))
                    {
                        // Run through the list, copying in the buffers
                        UINT32 ulOffset = 0;
                        pos = m_pFileBufferList->GetHeadPosition();
                        while (pos)
                        {
                            IHXBuffer* pListBuffer =
                                (IHXBuffer*) m_pFileBufferList->GetNext(pos);
                            if (pListBuffer)
                            {
                                memcpy(pAggBuffer->GetBuffer() + ulOffset, /* Flawfinder: ignore */
                                       pListBuffer->GetBuffer(),
                                       pListBuffer->GetSize());
                                ulOffset += pListBuffer->GetSize();
                            }
                        }
                        // Assign the out parameter
                        HX_RELEASE(rpBuffer);
                        rpBuffer = pAggBuffer;
                        rpBuffer->AddRef();
                    }
                }
                HX_RELEASE(pAggBuffer);
            }
        }
    }

    return retVal;
}

HX_RESULT
CPurePlayFileFormat::SwitchToUnicast()
{
    HX_RESULT theErr = HXR_OK;
    IHXBuffer* pBuf = NULL;
    IHXPlayer* pPlayer = NULL;
    IHXHyperNavigate* pHyperNavigate = NULL;
    CHXURL* pURL = NULL;
    CHXString str;
    int lCount = -1;
    
    HX_ASSERT(m_pContext);
    HX_ASSERT(m_pScheduler);
    HX_ASSERT(m_pFileHeader);
    
    // get a URL from the file header.  No URL, no Unicast!
    theErr = m_pFileHeader->GetPropertyCString("UnicastURL", pBuf);
    if (HXR_OK != theErr || NULL == pBuf) goto bail;

    /* Depends on the URL, we want either IHXPlayer or IHXHyperNavigate */
    // XXXGo - Hack!!!
    // CHXURL thinks http://www.real.com and http://www.real.com/ are
    // invalid because there is no filename at the end, so always append 
    // "name.rm" at the end.
    // DON"T EVER USE RESOURCE OUT OF pURL.
    str = (const char*)pBuf->GetBuffer();
    lCount = str.ReverseFind('/');
    if (lCount != (str.GetLength() - 1))
    {
	str += "/name.rm";
    }
    else
    {
	str += "name.rm";
    }
    
    pURL = new CHXURL((const char*)str);
    if (!pURL) 
    {
	theErr = HXR_OUTOFMEMORY;
	goto bail;
    }	
    else if (FAILED(theErr = pURL->GetLastError()))
    {
	goto bail;
    }

    if (pURL->GetProtocol() == httpProtocol)
    {
	// this is HTTP, get IHXHyperNavigate
	theErr = m_pContext->QueryInterface(IID_IHXHyperNavigate, 
					    (void**)&pHyperNavigate);
	if (HXR_OK != theErr || NULL == pHyperNavigate) 
	    goto bail;
    }
    else
    {
	// this is something else, get IHXPlayer    
    	theErr = m_pContext->QueryInterface(IID_IHXPlayer, (void**)&pPlayer);
    	if (HXR_OK != theErr || NULL == pPlayer) 
    	    goto bail;
    }    	    

    HX_ASSERT((pHyperNavigate && !pPlayer) || (pPlayer && ! pHyperNavigate));

    // in the constractor, it will schedule itself, and by doing so,
    // a scheduler will addRef this obj.
    CUnicastCallback* pCB;
    pCB = new CUnicastCallback(m_pScheduler,
			       pPlayer,
			       pHyperNavigate,
			       (const char*) pBuf->GetBuffer());
    
    if (!pCB)
    {
	theErr = HXR_OUTOFMEMORY;
	goto bail;
    }

    theErr = HXR_OK;

bail:
    HX_RELEASE(pBuf);
    HX_RELEASE(pPlayer);
    HX_RELEASE(pHyperNavigate);
    HX_DELETE(pURL);
    return theErr;
}

BOOL
CPurePlayFileFormat::GetSourceObj(REF(CPurePlaySource*)pSrc, UINT32 i)
{
    if (m_rgStreams.GetSize() <= 0 || i >= (UINT32)m_rgStreams.GetSize())
    {
	// no source or out of range
	return FALSE;
    }

    pSrc = (CPurePlaySource*)m_rgStreams.GetAt(i);   

    return pSrc ? TRUE : FALSE;
}

BOOL
CPurePlayFileFormat::GetStreamObj(REF(CStream*)pStrm, UINT32 i)
{
    if (m_rgStreams.GetSize() <= 0 || i >= (UINT32)m_rgStreams.GetSize())
    {
	// no source or out of range
	return FALSE;
    }

    CPurePlaySource* pSrc = NULL;
    if (GetSourceObj(pSrc, i))
    {
	HX_ASSERT(pSrc);
	pStrm = (CStream*)pSrc->GetStreamByStrmId(i);

	return pStrm ? TRUE : FALSE;
    }

    return FALSE;
}


void 
CPurePlayFileFormat::statistics_cat(char* pStats, UINT32 ulStatsBufLen, LONG32 lData)
{
    char numb[12]; /* Flawfinder: ignore */

    ::sprintf(numb,"%10lu ", lData); /* Flawfinder: ignore */
    SafeStrCat(pStats,numb, ulStatsBufLen);
}

BOOL
CPurePlayFileFormat::IsRAStream(UINT32 ulStrmNum)
{
    if (NO_EXIST == m_lRAStreamNum)
	return FALSE;

    return m_lRAStreamNum == (INT32)ulStrmNum;
}

BOOL
CPurePlayFileFormat::GetRAStreamNum(REF(UINT32)ulRAStreamNum)
{
    HX_ASSERT(TRUE == IsSourceAdmissionClosed());

    CPurePlaySource* pSrc = NULL;
    IHXValues*	pHeader = NULL;
    IHXBuffer*	pCString = NULL;
    
    for (UINT32 i = 0; (UINT32)m_rgStreams.GetSize() > i; i++)
    {
	if (GetSourceObj(pSrc, i))
	{
	    HX_ASSERT(pSrc);
	    pSrc->GetHeader(pHeader, (UINT16)i);

	    if (pHeader)
	    {
		if (HXR_OK == pHeader->GetPropertyCString("MimeType", pCString) ||
		    HXR_OK == pHeader->GetPropertyCString("mimetype", pCString)) 
		{	
		    // case insensitive.
		    if (0 == strcasecmp(RA_MINETYPE, (const char*)pCString->GetBuffer()))
		    {
		    	HX_RELEASE(pHeader);
		    	HX_RELEASE(pCString);
		    	ulRAStreamNum = i;
		    	return TRUE;
		    }
		
		    HX_RELEASE(pHeader);
		    HX_RELEASE(pCString);				    
		}
	    }
	}
	else
	{
	    return FALSE;
	}
    }

    return FALSE;
}


// table for all the registry entries that we are interested.
const char* CPurePlayFileFormat::zm_pRegistryEntryies[] = 
{
    "ResendRequested",
    "CurrentBandwidth",
    "Late",
    "AverageLatency",
    "LowLatency",
    "HightLatency",
    "AverageBandwidth",
    "Received",
    "ClipBandwidth",
    "Lost30",
    "Total30",
    "Renderer",
    "Recovered",
    "Total",
    "Lost",
    "Normal",
    NULL
};


// most of code taken from sned_statistics() in rmacore/rmartsppr.cpp 
/*
*   If any one of "StatsStyle", "StatsMask", and "StatsURL" is absent in SDP
*   file, no stats!!!
*/
BOOL
CPurePlayFileFormat::SendStatistics()
{
    HX_ASSERT(TRUE == m_bWantClientStats);
    BOOL	    bRetVal = TRUE;
    UINT32	    ulStatsLen = 0;
    CHAR*	    pszStats = NULL;
    CHAR*	    pszCodec = NULL;
    STREAM_STATS*   pStreamStats = NULL;
    
    UINT32	    ulLength = 0;
    UINT32	    i = 0;
    UINT32	    ulTransport = 0;

    CHAR	    szRegKeyName[MAX_DISPLAY_NAME] = {0};
    IHXBuffer*	    pParentName = NULL;
    IHXBuffer*	    pValue = NULL;

    CStream*	    pStrm = NULL;

    BOOL	    bSendStats = 0;

    CHXString	    strTemp;
    IHXBuffer*	    pGUID = NULL;
    IHXBuffer*     pClientID = NULL;

    UINT32	    ulStatsMask = 0;
    UINT32	    ulStatsStyle = 0;
    IHXBuffer*     pBuf = NULL;

    if (!m_pRegistry)
    {
	bRetVal = FALSE;
	goto bail;
    }

    // Make sure RA stream exists
    if (NO_EXIST == m_lRAStreamNum)
    {
	// RA stream doesn't exist -> no stats
	bRetVal = FALSE;
	goto bail;
    }

    // if any one of them doesn't exist, no stats!
    if (HXR_OK != m_pFileHeader->GetPropertyULONG32("StatsStyle", ulStatsStyle) ||
	HXR_OK != m_pFileHeader->GetPropertyULONG32("StatsMask", ulStatsMask) ||
	HXR_OK != m_pFileHeader->GetPropertyCString("StatsURL", pBuf))
    {
	// no stats
	bRetVal = FALSE;
	goto bail;
    }
    HX_RELEASE(pBuf);
    
    // we have RA stream, create stats1 & stats2
    // stats1 & stats2 are only for RA    
    HX_ASSERT(m_lRAStreamNum < m_rgStreams.GetSize());
    
    if (!GetStreamObj(pStrm, (UINT32)m_lRAStreamNum))
    {
	    bRetVal = FALSE;
	    goto bail;
    }

    HX_ASSERT(pStrm);

    // get the core stats from registry
    pStreamStats = new STREAM_STATS(m_pRegistry, pStrm->m_ulStreamRegId);

    if (!pStreamStats || !pStreamStats->m_bInitialized)
    {
	bRetVal = FALSE;
	goto bail;
    }

    ulStatsLen = MAX_DISPLAY_NAME + 40;
    pszStats   = new CHAR[ulStatsLen];

    if (!pszStats)
    {
	bRetVal = FALSE;
	goto bail;
    }
    else
    {
	memset(pszStats, 0, ulStatsLen);
    }

    // retreive the pszStats set by the renderer
    if (pStreamStats->m_pRenderer &&
        HXR_OK == m_pRegistry->GetPropName(pStreamStats->m_pRenderer->m_ulRegistryID, pParentName))
    {
	SafeSprintf(szRegKeyName, MAX_DISPLAY_NAME, "%s.Codec", pParentName->GetBuffer());

	if (HXR_OK == m_pRegistry->GetStrByName(szRegKeyName, pValue) && pValue)
	{
	    ulLength = pValue->GetSize();
    
	    pszCodec = new char[ulLength+1];
	    strcpy(pszCodec, (const char*)pValue->GetBuffer()); /* Flawfinder: ignore */

	    // replace space with underscore
	    for (i = 0; i < ulLength; i++)
	    {
		if (pszCodec[i] == ' ')
		{
		pszCodec[i] = '_';
		}
	    }
	    HX_RELEASE(pValue);
	}
    }
    HX_RELEASE(pParentName);

    if (ulStatsMask & 1UL)
    {
	// build statistics string
	SafeStrCat(pszStats, "Stat1:", ulStatsLen);

	// everytime we have a lost packet, we just create one and send it
	// off to the core, so the core thinks there is no lost packet.
	// Therefore, overwrite some stats here.
	
	// packet received -- all the pkt received
	statistics_cat(pszStats, ulStatsLen, pStrm->m_ulReceived);
	// Out of Order -- just out of order
	statistics_cat(pszStats, ulStatsLen, pStrm->m_ulReceived - pStrm->m_ulNormal);
	// Lost
	statistics_cat(pszStats, ulStatsLen, pStrm->m_ulLost);
	// early
	statistics_cat(pszStats, ulStatsLen, 0);	// no packets sent early in 6.0	
	// late	-- too late to send it to core
	statistics_cat(pszStats, ulStatsLen, pStrm->m_pTransBuf->GetLatePktCount());

	// audio_format
	if (!pszCodec)
	{
	    SafeStrCat(pszStats, "N/A", ulStatsLen);	
	}
	else
	{
	    SafeStrCat(pszStats, pszCodec, ulStatsLen);
	}
    }

    if (ulStatsMask & 2UL)
    {
	// divide pszStats levels if necessary
	if (ulStatsMask & 1UL)
	{
	    SafeStrCat(pszStats, "][", ulStatsLen);
	}

	SafeStrCat(pszStats, "Stat2:", ulStatsLen);

	// need src obj for this...
	CPurePlaySource* pSrc = NULL;	
	GetSourceObj(pSrc, (UINT32)m_lRAStreamNum);	

	// Bandwidth info	
	if (pStreamStats->m_pClipBandwidth)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pClipBandwidth->GetInt());
	else if (pSrc)
	    statistics_cat(pszStats, ulStatsLen, pSrc->GetBandwidth());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);

	if (pStreamStats->m_pAvgBandwidth)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pAvgBandwidth->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);
	    
	// Latency info
	if (pStreamStats->m_pHighLatency)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pHighLatency->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);
	    
	if (pStreamStats->m_pLowLatency)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pLowLatency->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);	
	    
	if (pStreamStats->m_pAvgLatency)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pAvgLatency->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);	

	// Resend info
	if (pStreamStats->m_pResendRequested)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pResendRequested->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);	

	if (pStreamStats->m_pResendReceived)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pResendReceived->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);	

	if (pStreamStats->m_pLate)
	    statistics_cat(pszStats, ulStatsLen, pStreamStats->m_pLate->GetInt());
	else
	    statistics_cat(pszStats, ulStatsLen, 0);	

	// rebuffer info (in percent)
	statistics_cat(pszStats, ulStatsLen, 0);


	// Transport
	// 0 or UDP, 1 for TCP, 2 for Multicast...
	// and it is always Multicast
	ulTransport = 2L;
	
	statistics_cat(pszStats, ulStatsLen, ulTransport);

	// Startup latency, b/n join and first pkt	
	if (pSrc)
	{
	    statistics_cat(pszStats, ulStatsLen, CALCULATE_ELAPSED_TICKS(pSrc->m_ulBeginClockTick, 
							     pStrm->m_ulStartTimeMS));
	}							    
	else
	{
	    HX_ASSERT(FALSE);
	    statistics_cat(pszStats, ulStatsLen, 0);
	}		
    }

    // put some extra info
    char duration[128]; /* Flawfinder: ignore */
    sprintf(duration, "#%u", /* Flawfinder: ignore */
	    CALCULATE_ELAPSED_TICKS(pStrm->m_ulStartTimeMS, 
				    HX_GET_TICKCOUNT()) / MILLISECS_PER_SECOND);

    SafeStrCat(pszStats, duration, ulStatsLen);


    // actually POST it
    BeginPost(pszStats);

bail:

    HX_DELETE(pStreamStats);
    
    HX_VECTOR_DELETE(pszStats);
    HX_VECTOR_DELETE(pszCodec);

    HX_RELEASE(pParentName);
    HX_RELEASE(pValue);

    HX_RELEASE(pGUID);
    HX_RELEASE(pClientID);

    HX_RELEASE(pBuf);

    return bRetVal;
    
}


HX_RESULT
CPurePlayFileFormat::BeginPost(CHAR* pszStats)
{
    HX_RESULT theErr = HXR_OK;

    // Keep-Alive Header text
    //
    const char szKeepAlive[] = "\r\nConnection: Keep-Alive";

    const char szUserAgent[] = "\r\nUser-Agent: RMA/1.0 (compatible; RealMedia)";

    const char szHostHeader[] = "\r\nHost: ";

    const char szAcceptLang[] = "\r\nAccept-Language: ";

    IHXValues*	pURLProp = NULL;
    CHXURL*	pURL = NULL;

    UINT32      ulPort = 0;
    IHXBuffer* pLanguage = NULL;
    IHXBuffer* pHost = NULL;
    IHXValues* pRequestHeaders = NULL;
    IHXBuffer* pResource = NULL;

    CHXString	strRequest;

    IHXValues*	pHeaders = 0;
    char*	pOutBuffer = 0;

    CScalableHTTP* pHTTP = NULL;

    /*
    *	Get everything we need from the stats url
    */
    IHXBuffer* pBuf = NULL;
    HX_VERIFY(SUCCEEDED(m_pFileHeader->GetPropertyCString("StatsURL", pBuf)));        

    pURL = new CHXURL((const char*)pBuf->GetBuffer());
    if (!pURL) 
    {
	theErr = HXR_OUTOFMEMORY;
	goto exit;
    }	
    else if (FAILED(theErr = pURL->GetLastError()))
    {
	goto exit;
    }
    
    pURLProp = pURL->GetProperties();

    theErr = pURLProp->GetPropertyBuffer(PROPERTY_HOST, pHost);
    if (HXR_OK != theErr)
    {
    	theErr = HXR_INVALID_URL_HOST;
	goto exit;
    }	

    theErr = pURLProp->GetPropertyULONG32(PROPERTY_PORT, ulPort);
    if (HXR_OK != theErr) goto exit;

    theErr = pURLProp->GetPropertyBuffer(PROPERTY_RESOURCE, pResource);
    if (HXR_OK != theErr) 
    {
    	theErr = HXR_INVALID_URL_PATH;
	goto exit;
    }

    if (HXR_OK == m_pRequest->GetRequestHeaders(pRequestHeaders))
    {
	pRequestHeaders->GetPropertyCString("Language", pLanguage);
    }        
    

    /*
     * Get the RFC822 headers from the IHXRequest object
     */

    if (m_pRequest &&
	m_pRequest->GetRequestHeaders(pHeaders) == HXR_OK)
    {
	if (pHeaders)
	{
	    IHXValues* pValuesRequestHeaders = new CHXHeader();
	    pValuesRequestHeaders->AddRef();

	    CHXHeader::mergeHeaders(pValuesRequestHeaders, pHeaders);

	    /*
	     * First spin through the headers to see how much space they
	     * require (the extra 4 bytes per header is for overhead)
	     */

	    const char* pName;
	    IHXBuffer* pValue;
	    HX_RESULT result;
	    UINT32 uBufferSize = 0;
	    UINT32 uBufferPtr = 0;

	    result = pValuesRequestHeaders->GetFirstPropertyCString(pName, pValue);

	    while (result == HXR_OK)
	    {
		uBufferSize += strlen(pName);
		uBufferSize += pValue->GetSize() - 1;
		uBufferSize += 4;

		pValue->Release();

		result = pValuesRequestHeaders->GetNextPropertyCString(pName, pValue);
	    }

	    /*
	     * Allocate space for trailing '\0'
	     */

	    uBufferSize++;

	    pOutBuffer = new char[uBufferSize];

	    /*
	     * Now spin through and built the outgoing string
	     */

	    result = pValuesRequestHeaders->GetFirstPropertyCString(pName, pValue);

	    while (result == HXR_OK)
	    {
		UINT32 uValueLength = pValue->GetSize() - 1;

		memcpy(&pOutBuffer[uBufferPtr], "\r\n", 2); /* Flawfinder: ignore */
		uBufferPtr += 2;
		memcpy(&pOutBuffer[uBufferPtr], pName, strlen(pName)); /* Flawfinder: ignore */
		uBufferPtr += strlen(pName);
		memcpy(&pOutBuffer[uBufferPtr], ": ", 2); /* Flawfinder: ignore */
		uBufferPtr += 2;
		memcpy(&pOutBuffer[uBufferPtr], /* Flawfinder: ignore */
		       pValue->GetBuffer(),
		       uValueLength);
		uBufferPtr += uValueLength;

		pValue->Release();

		result = pValuesRequestHeaders->GetNextPropertyCString(pName, pValue);
	    }

	    HX_ASSERT(uBufferPtr == uBufferSize - 1);

	    pOutBuffer[uBufferPtr] = '\0';

	    HX_RELEASE(pValuesRequestHeaders);
	    HX_RELEASE(pHeaders);
	}
    }


    // The request is a standard HTTP based request created from the resource...
    // since string.Format() has a max length of 512 chars, we'll build up the string manually
    const char* pc;
    pc = (const char*)pResource->GetBuffer();

    strRequest = "POST ";
    if ('/' != *pc)
    {
	// we need '/' as the first thing
	strRequest += '/';
    }
    strRequest += pc;
    strRequest += " HTTP/1.0\r\nAccept: */*";
    strRequest += szUserAgent;
    strRequest += (pOutBuffer ? pOutBuffer : "");
    strRequest += szKeepAlive;    

    strRequest += szHostHeader;
    // Use actual host, even when going through proxy..
    strRequest += (const char*)pHost->GetBuffer();
    if(ulPort != 80)
    {
	    strRequest += ":";
	    strRequest.AppendULONG(ulPort);
    }

    if (pLanguage)
    {
        strRequest += szAcceptLang;
	strRequest += pLanguage->GetBuffer();
    }



    // for the body of POST
    strRequest += "\r\n";
    strRequest += "Content-Length: ";


    char szLength[256]; /* Flawfinder: ignore */
    itoa(strlen(pszStats), szLength, 10);
    strRequest += szLength;

    // set the body
    strRequest += "\r\n\r\n";
    strRequest += pszStats;


#ifdef XXXGo_DEBUG
    if (m_file)
    {
    	fprintf(m_file, "\n********************\n");    
    	fprintf(m_file, (const char*)strRequest);
    	fprintf(m_file, "\n********************");
    }
#endif    

    // this is to create the TCP socket and actually send msg
    pHTTP = new CScalableHTTP();
    if (pHTTP)
    {
	pHTTP->AddRef();
	theErr = pHTTP->Init(m_pContext, m_ulServerTimeout);
	if (HXR_OK != theErr) goto exit;

	theErr = pHTTP->Post(strRequest, (const char*)pHost->GetBuffer(), (UINT16)ulPort);
	HX_RELEASE(pHTTP);
    }
   
exit:
    HX_RELEASE(pURLProp);
    HX_RELEASE(pBuf);
    
    HX_DELETE(pURL);

    HX_RELEASE(pLanguage);    
    HX_RELEASE(pHost);
    HX_RELEASE(pRequestHeaders);
    HX_RELEASE(pResource);

    HX_VECTOR_DELETE(pOutBuffer);

    HX_RELEASE(pHTTP);
    
    return theErr;
}


/**************************************************************************
 *  CPurePlayFileFormat::CFileHeaderCallback
 *
 *  Timeout class to send file header to the core with the right number of 
 *  stream count
 */

CPurePlayFileFormat::CFileHeaderCallback::CFileHeaderCallback
(
    CPurePlayFileFormat* pFF
)
    : m_lRefCount(0)
    , m_pFF(pFF)
{
    m_pFF->AddRef();
}

CPurePlayFileFormat::CFileHeaderCallback::~CFileHeaderCallback()
{
    m_pFF->Release();
}

STDMETHODIMP 
CPurePlayFileFormat::CFileHeaderCallback::QueryInterface
(
    REFIID riid, 
    void** ppvObj
)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXCallback))
    {
	AddRef();
	*ppvObj = (IHXCallback*)this;
	return HXR_OK;
    }

    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::AddRef
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) 
CPurePlayFileFormat::CFileHeaderCallback::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

/////////////////////////////////////////////////////////////////////////
//  Method:
//	IUnknown::Release
//  Purpose:
//	Everyone usually implements this the same... feel free to use
//	this implementation.
//
STDMETHODIMP_(ULONG32) 
CPurePlayFileFormat::CFileHeaderCallback::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

STDMETHODIMP
CPurePlayFileFormat::CFileHeaderCallback::Func()
{
    if (m_pFF)
    {
	return m_pFF->ProcessFileHeaderCallback();
    }
    else
    {
	return HXR_UNEXPECTED;
    }
}


HX_RESULT 
CPurePlayFileFormat::GetLogger(REF(IHXErrorMessages*) pLog)
{
    pLog = m_pLog;
    pLog->AddRef();

    return HXR_OK;
}


inline
PurePlayFFState
CPurePlayFileFormat::GetCurrentState(void)
{
    return m_state;
}


/************************************************************************
 *  IHXTransportSyncServer methods		    ref:  rtspif.h
 */
STDMETHODIMP
CPurePlayFileFormat::DistributeSyncAnchor(ULONG32 ulHXTime, 
					  ULONG32 ulNTPTime)
{
    return Distribute(DISTRIBUTE_SYNCANCHOR, 
		      ulHXTime, 
		      ulNTPTime);
}

STDMETHODIMP
CPurePlayFileFormat::DistributeSync(ULONG32 ulHXTime, 
				    LONG32 lHXTimeOffset)
{
    return Distribute(DISTRIBUTE_SYNC, 
		      ulHXTime, 
		      (ULONG32) lHXTimeOffset);
}

STDMETHODIMP
CPurePlayFileFormat::DistributeStartTime(ULONG32 ulHXRefTime)
{
    return Distribute(DISTRIBUTE_STARTTIME, 
		      ulHXRefTime);
}

HX_RESULT CPurePlayFileFormat::Distribute(ULONG32 ulDistributionMode,
					  ULONG32 ulVal1,
					  ULONG32 ulVal2)
{
    INT32 i = 0;
    INT32 j = 0;
    CPurePlaySource* pSource = NULL;
    CStream* pStream = NULL;
    HX_RESULT retVal = HXR_UNEXPECTED;

    if (m_rgStreams.GetSize() != 0)
    {
	retVal = HXR_OK;

	do
	{
	    if ((pSource = (CPurePlaySource*) m_rgStreams.GetAt(j)) &&
		(pStream = pSource->GetStreamByStrmId(j)))
	    {
		CPurePlaySource::DistributeToStream(pStream,
						    ulDistributionMode,
						    ulVal1,
						    ulVal2);
	    }

	    j++;
	} while (j < m_rgStreams.GetSize());
    }
    else if (m_pInterfaceMgr && 
	     (m_pInterfaceMgr->m_ulNumInterfaces != 0))
    {	
	CHXPtrArray* prgStreams = NULL;
	ULONG32 ulNumStreams;

	retVal = HXR_OK;

    	do
    	{    	
	    prgStreams = m_pInterfaceMgr->GetSession(j);
	    ulNumStreams = 0;

	    if (prgStreams)
	    {
		ulNumStreams = prgStreams->GetSize();
	    }

    	    for (i = 0; i < ulNumStreams; i++)    	    
    	    {
		pSource = (CPurePlaySource*) prgStreams->GetAt(i);
		pSource->Distribute(ulDistributionMode,
				    ulVal1,
				    ulVal2);
	    }	
	    
	    j++;
	} while (j < m_pInterfaceMgr->m_ulNumInterfaces);
    }

    return retVal;
}


/******************************************************************************
*
*   Callback to switch to Unicast
*   Either pPlayer or pHyperNavigate has to be NULL...that's how it will know
*   which one to use.  
*/
CUnicastCallback::CUnicastCallback(
    IHXScheduler*  pScheduler, 
    IHXPlayer*	    pPlayer,
    IHXHyperNavigate* pHyperNavigate,
    const char*     pchURL
)
    : m_lRefCount(0)
    , m_pScheduler(pScheduler)
    , m_pPlayer(pPlayer)
    , m_pHyperNavigate(pHyperNavigate)
    , m_strURL(pchURL)
{
    HX_ASSERT(m_pScheduler);
    HX_ASSERT((m_pPlayer && !m_pHyperNavigate) || (m_pHyperNavigate && !m_pPlayer));
    HX_ASSERT(!m_strURL.IsEmpty());
    
    m_pScheduler->AddRef();
    if (m_pPlayer)
    {
	m_pPlayer->AddRef();
    }
    else
    {
	HX_ASSERT(m_pHyperNavigate);
	m_pHyperNavigate->AddRef();
    }
    

    // Need to schedule itself
    m_pScheduler->RelativeEnter(this, 1000);    
}

CUnicastCallback::~CUnicastCallback()
{
    HX_RELEASE(m_pScheduler);
    HX_RELEASE(m_pPlayer);
    HX_RELEASE(m_pHyperNavigate);
}

STDMETHODIMP 
CUnicastCallback::QueryInterface
(
    REFIID riid, 
    void** ppvObj
)
{
    if (IsEqualIID(riid, IID_IUnknown))
    {
	AddRef();
	*ppvObj = this;
	return HXR_OK;
    }
    else if(IsEqualIID(riid, IID_IHXCallback))
    {
	AddRef();
	*ppvObj = (IHXCallback*)this;
	return HXR_OK;
    }

    *ppvObj = NULL;
    return HXR_NOINTERFACE;
}

STDMETHODIMP_(ULONG32) 
CUnicastCallback::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

STDMETHODIMP_(ULONG32) 
CUnicastCallback::Release()
{
    if (InterlockedDecrement(&m_lRefCount) > 0)
    {
        return m_lRefCount;
    }

    delete this;
    return 0;
}

STDMETHODIMP
CUnicastCallback::Func()
{
    if (m_pHyperNavigate)
    {
	m_pHyperNavigate->GoToURL(m_strURL, NULL);
    }
    else if (m_pPlayer)
    {
	 // if URL was not HTTP, then we need to stop the player first...
	// we don't wanna stop the player twice    
	if (!m_pPlayer->IsDone())
	{
	    m_pPlayer->Stop();
	}

	if (HXR_OK == m_pPlayer->OpenURL(m_strURL))
	{
	    m_pPlayer->Begin();  
	}	
    }
#ifdef _DEBUG
    else
    {
	HX_ASSERT(!"This can't be happening...");
    }
#endif    
    return HXR_OK;
}

CPurePlayFileFormat::InterfaceManager::InterfaceManager
(
    UINT32 ulNumInterfaces
)
    : m_ulNumInterfaces(ulNumInterfaces)
    , m_pulInterfaces(NULL)
{
    // better be at least one.
    HX_ASSERT(m_ulNumInterfaces);
    m_pulInterfaces = new UINT32[m_ulNumInterfaces];
    memset(m_pulInterfaces, 0, sizeof(UINT32) * m_ulNumInterfaces);

    // we are expecting this many...
    m_rgInterfaceSessions.SetSize(m_ulNumInterfaces);
}


CPurePlayFileFormat::InterfaceManager::~InterfaceManager()
{
    HX_VECTOR_DELETE(m_pulInterfaces);

    UINT32 ulSize = m_rgInterfaceSessions.GetSize();
    UINT32 j;
    UINT32 ulNumStreams;
    CHXPtrArray* pSession;
    CPurePlaySource* pSrc;
    for (UINT32 i = 0; i < ulSize; ++i)
    {
	// interface session that will be used should have been removed
	// from this array, so close and release ALL that's left	
	pSession = (CHXPtrArray*)m_rgInterfaceSessions.GetAt(i);
	if (!pSession)
	{
	    continue;
	}
	
	ulNumStreams = pSession->GetSize();
	for (j = 0; j < ulNumStreams; ++j)
	{
	    pSrc = (CPurePlaySource*)pSession->GetAt(j);
	    if (pSrc)
	    {
		pSrc->Close();
		pSrc->Release();
		pSrc = NULL;
	    }
	}	    
	pSession->RemoveAll();
	HX_DELETE(pSession);
    }
    m_rgInterfaceSessions.RemoveAll();
}

/* interfaceNo starts from 0 */
void
CPurePlayFileFormat::InterfaceManager::SetInterface
(
    UINT32 ulInterfaceNo, UINT32 ulInterface
)
{
    HX_ASSERT(m_ulNumInterfaces > 0);
    HX_ASSERT(ulInterfaceNo < m_ulNumInterfaces);
    HX_ASSERT(!m_pulInterfaces[ulInterfaceNo]);

    m_pulInterfaces[ulInterfaceNo] = ulInterface;
}

CHXPtrArray*
CPurePlayFileFormat::InterfaceManager::GetSession
(
    UINT32 ulInterfaceNo
)
{
    HX_ASSERT(m_ulNumInterfaces > 0);
    HX_ASSERT(ulInterfaceNo < m_ulNumInterfaces);

    return (CHXPtrArray*)m_rgInterfaceSessions.GetAt(ulInterfaceNo);
}

void
CPurePlayFileFormat::InterfaceManager::SetSession
(
    UINT32 ulInterfaceNo, 
    CHXPtrArray* pSession
)
{
    HX_ASSERT(m_ulNumInterfaces > 0);
    HX_ASSERT(ulInterfaceNo < m_ulNumInterfaces);
    HX_ASSERT(pSession);

    m_rgInterfaceSessions.SetAtGrow(ulInterfaceNo, pSession);
}


/************************************************************************
 *  IHXPropertyAdviser Interface Methods		ref:  hxfwrtr.h
 */
STDMETHODIMP CPurePlayFileFormat::GetPropertyULONG32(const char* pPropertyName,
						     REF(ULONG32) ulPropertyValue)
{
    HX_RESULT retVal = HXR_FAIL;

    if (pPropertyName &&
	(strcasecmp(pPropertyName, "IsPseudoFile") == 0))
    {
	ulPropertyValue = 1;
	retVal = HXR_OK;
    }

    return retVal;
}

STDMETHODIMP CPurePlayFileFormat::GetPropertyBuffer(const char* pPropertyName,
						    REF(IHXBuffer*) pPropertyValue)
{
    return HXR_FAIL;
}

STDMETHODIMP CPurePlayFileFormat::GetPropertyCString(const char* pPropertyName,
						     REF(IHXBuffer*) pPropertyValue)
{
    return HXR_FAIL;
}

STDMETHODIMP CPurePlayFileFormat::GetPropertySet(const char* pPropertySetName,
						 REF(IHXValues*) pPropertySet)
{
    return HXR_FAIL;
}
