/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: buffmgr.cpp,v 1.19.18.2 2004/07/09 02:05:57 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 ***** */

#include "hlxclib/stdio.h"

#include "hxcom.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxslist.h"
#include "hxtick.h"
#include "hxcore.h"
#include "chxeven.h"
#include "hxbsrc.h"
#include "hxsrc.h"
#include "hxntsrc.h"
#include "hxstrm.h"
#include "strminfo.h"
#include "buffmgr.h"
#include "hxsmbw.h"

#include "hxgroup.h"
#include "hxplay.h"
#include "errdbg.h"

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

#if defined(__TCS__)
#define MAX_ADDITIONAL_BUFFERING    3000
#else
#define MAX_ADDITIONAL_BUFFERING    10000
#endif /* __TCS__ */

CBufferManager::CBufferManager(HXSource* pParent)
{
    m_ulMaxAdditionalBufferingInMs  = MAX_ADDITIONAL_BUFFERING;

    m_llHighestTimeStamp	= 0;
    m_ulTotalPauseTime		= 0;
    m_ulLastPauseTime		= 0;
    m_ulAdditionalBufferingInMs	= 0;
    m_bBufferStartTimeToBeSet	= TRUE;

    m_ulMinimumInitialAudioPreroll	= 0;
    m_ulMinimumSourcePreroll	= 0;

    m_bPerfectPlay		= FALSE;     
    m_bPaused			= FALSE;
    m_state			= BUFFMGR_READY;

    m_pParent			= pParent;
    m_pParent->AddRef();

    m_bLocalPlayback		= pParent->IsLocalSource();
    m_pStreamInfoTable		= pParent->GetStreamInfoTable();
    m_bFirstResumeDone		= FALSE;
    m_bIsSeekPerformed		= FALSE;
    m_bBufferedPlay		= FALSE;
    m_bBufferCalcToBeChanged	= FALSE;
    m_bIsInitialized		= FALSE;
    m_ulSeekTime		= 0;
}

CBufferManager::~CBufferManager()
{
    HX_RELEASE(m_pParent);
}

HX_RESULT
CBufferManager::Init()
{
    UINT32	    ulPerfectPlayTime = 0;
    UINT32	    ulMinimumPreroll = 0;
    STREAM_INFO*    pStreamInfo = NULL;

    CHXMapLongToObj::Iterator i;

    // There is no buffered or perfect play with MIN_HEAP on.
#if !defined(HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)
    SetPerfectPlay(m_pParent->IsPerfectPlay());

    if (m_bPerfectPlay || m_bBufferedPlay)
    {
	// caculate how much extra preroll for the perfect play
	ulPerfectPlayTime = m_pParent->GetPerfectPlayTime();
    }
#endif

    // ajust buffering information of each stream
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);

	pStreamInfo->BufferingState().Init(ulPerfectPlayTime);

	HX_ASSERT(pStreamInfo->m_EventList.GetHeadPosition() == NULL);
    }

    m_bIsInitialized = TRUE;

    /* Was there a Seek called before Initialization... may happen for a clip
     * with Start time
     */
    if (m_bIsSeekPerformed)
    {
	DoSeek(m_ulSeekTime);
    }

    return HXR_OK;
}

HX_RESULT
CBufferManager::SetMinimumPreroll(BOOL bPerfectPlay, UINT32 ulSourcePreroll, 
				  UINT32 ulInitialAudioPreroll,
				  BOOL	 bModifyStartTime /* = TRUE */)
{
    m_ulMinimumInitialAudioPreroll = ulInitialAudioPreroll;
    m_ulMinimumSourcePreroll	   = ulSourcePreroll;

    SetPerfectPlay(bPerfectPlay);

    UpdateMinimumPreroll(bModifyStartTime);

    return HXR_OK;
}

void 
CBufferManager::UpdateMinimumPreroll(BOOL bModifyStartTime)
{
    UINT32	    ulMinimumPreroll = 0;
    UINT32	    ulPerfectPlayTime = 0;
    UINT32	    ulStartTime = 0;
    UINT32	    ulEventStartTime = 0;
    UINT32	    ulPacketTime     = 0;
    STREAM_INFO*    pStreamInfo = NULL;
    BOOL	    bPreDataToBeCalculated = FALSE;

    CHXMapLongToObj::Iterator i;

    // There is no buffered or perfect play with MIN_HEAP on.
#if !defined(HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)

    if (m_bPerfectPlay || m_bBufferedPlay)
    {
	m_ulMaxAdditionalBufferingInMs = 0;
	// caculate how much extra preroll for the perfect play
	ulPerfectPlayTime = m_pParent->GetPerfectPlayTime();
    }
    else
    {
	m_ulMaxAdditionalBufferingInMs	= MAX_ADDITIONAL_BUFFERING;

	if (m_ulAdditionalBufferingInMs > m_ulMaxAdditionalBufferingInMs)
	{
	    m_ulAdditionalBufferingInMs = m_ulMaxAdditionalBufferingInMs;
	}

	ulPerfectPlayTime		= m_ulAdditionalBufferingInMs;
    }
#endif

    ulStartTime = m_pParent->GetStartTime();

    // adjust buffering information of each stream
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);
    
	pStreamInfo->BufferingState().SetMinimumPreroll(
	    m_ulMinimumSourcePreroll, 
	    m_ulMinimumInitialAudioPreroll,
	    ulPerfectPlayTime,
	    (m_state == BUFFMGR_REBUFFER));

	if (bModifyStartTime)
	{
	    pStreamInfo->UpdateStartTimes(ulStartTime);
	}
    }

    m_bBufferCalcToBeChanged = FALSE;
}

HX_RESULT
CBufferManager::Stop(void)
{
    STREAM_INFO*    pStreamInfo = NULL;

    CHXMapLongToObj::Iterator i;

    // stop buffering of each stream
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);
    
	pStreamInfo->BufferingState().Stop();
//	DEBUG_OUT(m_pParent->m_pPlayer, (s,
//	    "BufferManager::Stop %p", pStreamInfo));
    }

    return HXR_OK;
}

HX_RESULT
CBufferManager::DoSeek(UINT32 ulSeekTime, BOOL bSeekInsideRecordBuffer)
{
    m_state		= BUFFMGR_SEEK;
    m_bIsSeekPerformed	= TRUE;
    m_ulSeekTime	= ulSeekTime;

    /* We will call Reset during Init() call */
    if (!m_bIsInitialized)
    {
	return HXR_OK;
    }

    // reset all the preroll attributes
    Reset(ulSeekTime, bSeekInsideRecordBuffer);

    return HXR_OK;
}

HX_RESULT
CBufferManager::DoPause(void)
{
    m_bPaused		= TRUE;
    m_ulLastPauseTime	= HX_GET_TICKCOUNT();
    return HXR_OK;
}

HX_RESULT
CBufferManager::DoResume(void)
{
    if (m_bPaused && m_state != BUFFMGR_SEEK && !m_bBufferStartTimeToBeSet)
    {
	m_ulTotalPauseTime += CALCULATE_ELAPSED_TICKS(m_ulLastPauseTime, HX_GET_TICKCOUNT());
    }

    m_bPaused = FALSE;

    if (!m_bFirstResumeDone)
    {
	m_bFirstResumeDone = TRUE;

	UpdateMinimumPreroll(FALSE);
    }

    return HXR_OK;
}

HX_RESULT
CBufferManager::ReBuffer(void)
{
    // recaculate only if it is out of the buffering stage
    // and not in perfect play mode
    m_state = BUFFMGR_REBUFFER;
    
    /* go back in buffering mode...
     * each time we come in buffering state, increase the
     * number of packets we buffer by 1 second worth
     * to a max of m_ulMaxAdditionalBuffering secs.
     */
#ifndef HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES
    // increase by 1 sec
    m_ulAdditionalBufferingInMs += 1000;

    if (m_pParent->IsLive())
    {
	m_bPerfectPlay	= FALSE;
	m_bBufferedPlay = FALSE;
    }
#endif // HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES

    UpdateMinimumPreroll(FALSE);

    return HXR_OK;
}

HX_RESULT
CBufferManager::Reset(UINT32 ulSeekTime, BOOL bSeekInsideRecordBuffer)
{
    STREAM_INFO*    pStreamInfo = NULL;

    CHXEventList*  pEventList = NULL;
    CHXEvent*	    pEvent = NULL;

    CHXMapLongToObj::Iterator i;

    if (m_bBufferCalcToBeChanged)
    {
	UpdateMinimumPreroll(FALSE);
    }

    m_ulBufferingStartTime = 0;

    // reset each stream
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);
	
	// reset
	if (!bSeekInsideRecordBuffer)
	{
	    pStreamInfo->m_bSrcStreamDone = FALSE;
	    pStreamInfo->m_bPacketRequested = FALSE;
	}

	pStreamInfo->m_bSrcStreamFillingDone = FALSE;

	// Reset buffering state
	pStreamInfo->BufferingState().Reset((m_state == BUFFMGR_SEEK),
					    ulSeekTime);

	

	HX_ASSERT(m_state == BUFFMGR_SEEK);

	if (m_state == BUFFMGR_SEEK)
	{
	    // remove all pending packets 
	    pEventList = &pStreamInfo->m_EventList;
	    while (pEventList->GetNumEvents() > 0)
	    {
		pEvent = pEventList->RemoveHead();

		// Mark it as a pre-seek event and send it to the player
		pEvent->SetPreSeekEvent();
		m_pParent->EventReady(pEvent);
	    }
	}
    }

    m_llHighestTimeStamp	= 0;
    m_ulTotalPauseTime		= 0;
    m_bBufferStartTimeToBeSet	= TRUE;
    return HXR_OK;
}
					
HX_RESULT
CBufferManager::UpdateCounters(IHXPacket* pPacket)
{
    HX_RESULT	    hr = HXR_OK;
    UINT32	    ulStreamNum = 0;
    UINT32	    ulBufferSize = 0;
    UINT32	    ulBufferTime = 0;
    UINT32	    ulElapsedTime = 0;
    INT64	    llActualTimeStamp = 0;
    IHXBuffer*	    pBuffer = NULL;
    STREAM_INFO*    pStreamInfo = NULL;
    STREAM_INFO*    pThisStreamInfo = NULL;
    UINT32	    ulCurrentTime   = HX_GET_TICKCOUNT();


    CHXMapLongToObj::Iterator i;
   
    if (!pPacket)
    {
	return HXR_INVALID_PARAMETER;
    }

    if (pPacket->IsLost() &&
	!(pPacket->GetASMFlags() & HX_ASM_DROPPED_PKT))
    {
	// Ignore all lost packets that aren't dropped packets
	return hr;
    }

    ulStreamNum = pPacket->GetStreamNumber();

    if (!m_pStreamInfoTable->Lookup(ulStreamNum, (void*&)pStreamInfo))
    {
	return HXR_INVALID_PARAMETER;
    }

    pThisStreamInfo = pStreamInfo;

    pBuffer = pPacket->GetBuffer();
    if (pBuffer)
    {
	ulBufferSize = pBuffer->GetSize();
    }
    HX_RELEASE(pBuffer);

    ulBufferTime = pPacket->GetTime();

    /* Set Buffering start time for all streams */
    if (m_bBufferStartTimeToBeSet)
    {
	m_bBufferStartTimeToBeSet = FALSE;
	m_ulBufferingStartTime = ulCurrentTime;
    }

    ulElapsedTime = GetElapsedTime(ulCurrentTime);

    BOOL bIsBufferedPlayMode = (m_bPerfectPlay || m_bBufferedPlay || 
			      (m_bLocalPlayback && !m_pParent->IsLive()));

    pStreamInfo->BufferingState().OnPacket(ulBufferTime, ulBufferSize,
					   ulElapsedTime,
					   m_pParent->IsLive(),
					   bIsBufferedPlayMode);

    llActualTimeStamp = 
	pStreamInfo->BufferingState().CreateINT64Timestamp(ulBufferTime);

    
    // time based preroll by default
    UpdateHighestTimestamps(llActualTimeStamp, pStreamInfo);

    INT64 llLowRefTS = pThisStreamInfo->BufferingState().LowTS();

    // adjust each stream preroll based on the hightest time stamp
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);
	
	BOOL bIsTimestampDelivery = FALSE;

	if (pStreamInfo->m_pStream &&
	    pStreamInfo->m_pStream->IsTimeStampDelivery())
	{
	    bIsTimestampDelivery = TRUE;
	}
	    
	pStreamInfo->BufferingState().UpdateBufferingInMs(llLowRefTS,
							  m_llHighestTimeStamp,
							  bIsBufferedPlayMode,
							  bIsTimestampDelivery,
							  ulElapsedTime);
    }

    return hr;
}

HX_RESULT
CBufferManager::GetStatus(REF(UINT16)	    uStatusCode,
			  REF(IHXBuffer*)  pStatusDesc,
			  REF(UINT16)	    uPercentDone)
{
    STREAM_INFO*	pStreamInfo = NULL;

    CHXMapLongToObj::Iterator i;

    uStatusCode = HX_STATUS_READY;
    pStatusDesc = NULL;
    uPercentDone = 0;

    // collect from each streams
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);

	uPercentDone += pStreamInfo->BufferingState().GetPercentDone(m_bIsSeekPerformed);
    }

    // average them
    uPercentDone = uPercentDone / m_pStreamInfoTable->GetCount();

    if (uPercentDone >= 100)
    {
	uPercentDone = 100;
	m_state = BUFFMGR_READY;
    }
    else
    {
	uStatusCode = HX_STATUS_BUFFERING;
    }

//    DEBUG_OUT(m_pParent->m_pPlayer, DOL_GENERIC, (s,
//	"BuffMgr::GetStatus: %p Status: %s uPercentDone: %lu ", this, 
//	(uStatusCode == HX_STATUS_BUFFERING ? "HX_STATUS_BUFFERING" : "HX_STATUS_READY" ),
//		    uPercentDone));

    return HXR_OK;
}

HX_RESULT
CBufferManager::GetRemainToBuffer(REF(UINT32)	ulRemainToBufferInMs,
				  REF(UINT32)	ulRemainToBuffer)
{
    STREAM_INFO*    pStreamInfo = NULL;
    
    ulRemainToBufferInMs = 0;
    ulRemainToBuffer = 0;

    CHXMapLongToObj::Iterator i;

    // caculate each stream
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);

	BOOL bHasPreroll = 
	    pStreamInfo->BufferingState().HasPreroll(m_bIsSeekPerformed);
	BOOL bHasPredata =
	    pStreamInfo->BufferingState().HasPredata(m_bIsSeekPerformed);
	
	UINT32 ulRemainInMs;
	UINT32 ulRemain;
	pStreamInfo->BufferingState().GetRemainToBuffer(ulRemainInMs, 
							ulRemain);

	// satisfy the preroll (by default + pre-set)
	if ((!bHasPredata || bHasPreroll) &&
	    (ulRemainToBufferInMs < ulRemainInMs))
	{
	    ulRemainToBufferInMs = ulRemainInMs;
	}
	
	// satisfy the predata
	if (bHasPredata)
	{
	    ulRemainToBuffer += ulRemain;
	}
    }

    return HXR_OK;
}

HX_RESULT	
CBufferManager::GetMaximumPreroll(REF(UINT32)	ulMaximumPrerollInMs)
{
    STREAM_INFO*    pStreamInfo = NULL;

    CHXMapLongToObj::Iterator i;

    ulMaximumPrerollInMs = 0;

    // get max. preroll among the streams
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);
    
	UINT32 ulMinPrerollInMs = 
	    pStreamInfo->BufferingState().GetMinPrerollInMs();

	if (ulMaximumPrerollInMs < ulMinPrerollInMs)
	{
	    ulMaximumPrerollInMs = ulMinPrerollInMs;
	}
    }
    
    return HXR_OK;
}

HX_RESULT
CBufferManager::GetExcessBufferInfo(REF(UINT32)	ulRemainToBufferInMs,
				  REF(UINT32)	ulRemainToBuffer,
				  REF(UINT32)	ulExcessBufferInMs,
				  REF(UINT32)	ulExcessBuffer,
				  REF(BOOL)	bValidInfo,
				  REF(UINT32)	ulActualExcessBufferInMs,
				  REF(UINT32)   ulActualExcessBuffer)
{
    STREAM_INFO*    pStreamInfo		= NULL;
    INT64	    llTheLowestTS	= 0;
    INT64	    llTheHighestTS	= 0;
    BOOL	    bIsFirst		= TRUE;
    STREAM_INFO*    pNonEmptyStreamInfo = NULL;
    
    ulRemainToBufferInMs    = 0;
    ulRemainToBuffer	    = 0;
    ulExcessBufferInMs	    = 0;	
    ulExcessBuffer	    = 0;
    bValidInfo		    = FALSE;
    ulActualExcessBufferInMs= 0;	
    ulActualExcessBuffer    = 0;

    CHXMapLongToObj::Iterator i;

    /*
     * - Update transport stats for each stream.
     * - Find the lowest and highest timestamps across all streams
     * - Keep track of the stream with the lowest timestamp and is 
     *   buffering data
     */
    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);

	INT64 llLowestTimestampAtTransport = 0;
	INT64 llHighestTimestampAtTransport = 0;
	ULONG32 ulNumBytesAtTransport = 0;
	BOOL bDoneAtTransport = FALSE;

	m_pParent->GetCurrentBuffering(pStreamInfo->m_uStreamNumber, 
				       llLowestTimestampAtTransport,
				       llHighestTimestampAtTransport,
				       ulNumBytesAtTransport,
				       bDoneAtTransport);

	pStreamInfo->BufferingState().UpdateTransportStats(
	    llLowestTimestampAtTransport,
	    llHighestTimestampAtTransport,
	    ulNumBytesAtTransport,
	    bDoneAtTransport);

	if (ulNumBytesAtTransport > 0)
	{	    
	    if (bIsFirst)
	    {
		bIsFirst	    = FALSE;
		llTheLowestTS	    = llLowestTimestampAtTransport;
		llTheHighestTS	    = llHighestTimestampAtTransport;
		pNonEmptyStreamInfo = pStreamInfo;
	    }
	    else 
	    {
		if (llTheLowestTS > llLowestTimestampAtTransport)
		{
		    llTheLowestTS = llLowestTimestampAtTransport;

		    /* This is the stream with the lowest timestamp */
		    pNonEmptyStreamInfo = pStreamInfo;
		}

		if (llTheHighestTS < llHighestTimestampAtTransport)
		{
		    llTheHighestTS = llHighestTimestampAtTransport;
		}
	    }

	}
    }

    /* If none of the streams have any data buffered at the transport layer,
     * return.
     */
    if (!pNonEmptyStreamInfo)
    {
	return HXR_OK;
    }

    for (i = m_pStreamInfoTable->Begin(); i != m_pStreamInfoTable->End(); ++i)
    {
	pStreamInfo = (STREAM_INFO*) (*i);

	UINT32 ulExcessForThisStreamInMs = 0;
	UINT32 ulExcessForThisStream     = 0;

	pStreamInfo->BufferingState().GetExcessBufferInfo(llTheLowestTS,
							  llTheHighestTS,
							  m_bIsSeekPerformed,
							  ulRemainToBufferInMs,
							  ulRemainToBuffer,
							  ulExcessBufferInMs,
							  ulExcessBuffer,
							  ulExcessForThisStreamInMs,
							  ulExcessForThisStream);

	/* Update Actual Values Regardless of Preroll / PreData */
	ulActualExcessBuffer += ulExcessForThisStream;
	if (ulActualExcessBufferInMs < ulExcessForThisStreamInMs)
	{
	    ulActualExcessBufferInMs = ulExcessForThisStreamInMs;
	}
    }

    bValidInfo = TRUE;

    if (ulRemainToBufferInMs >= ulExcessBufferInMs)
    {
	ulRemainToBufferInMs -= ulExcessBufferInMs;
	ulExcessBufferInMs    = 0;
    }
    else 
    {
	ulExcessBufferInMs	-= ulRemainToBufferInMs;
	ulRemainToBufferInMs	 = 0;
    }

    if (ulRemainToBuffer > 0)
    {
	ulExcessBuffer    = 0;
    }

    return HXR_OK;
}

void		
CBufferManager::EnterBufferedPlay(void)
{
#if !defined(HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)
    if (!m_bBufferedPlay && !m_bPerfectPlay)
    {
	m_bBufferedPlay		    = TRUE;

	UINT32	ulRemainToBufferInMs	= 0;
	UINT32	ulRemainToBuffer	= 0;
	
	GetRemainToBuffer(ulRemainToBufferInMs, ulRemainToBuffer);
	
	/* If we are not done with buffering yet, update the buffering 
	 * calc now!
	 */ 
	if (ulRemainToBufferInMs > 0 ||
	    ulRemainToBuffer > 0)
	{
	    UpdateMinimumPreroll(FALSE);
	}
	else
	{
	    m_bBufferCalcToBeChanged    = TRUE;
	}
    }
#endif // (HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)
}

void		
CBufferManager::LeaveBufferedPlay(void)
{
#if !defined(HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)
    if (m_bBufferedPlay)
    {
	m_bBufferCalcToBeChanged    = TRUE;
	m_bBufferedPlay		    = FALSE;
    }
#endif // (HELIX_CONFIG_MIN_PCM_PUSHDOWN_BYTES)
}

UINT32 CBufferManager::GetElapsedTime(UINT32 ulCurrentTime)
{
    UINT32 ulElapsedTime =
	CALCULATE_ELAPSED_TICKS(m_ulBufferingStartTime, ulCurrentTime);
    
    if (m_ulTotalPauseTime > 0)
    {
	HX_ASSERT(ulElapsedTime >= m_ulTotalPauseTime);
	ulElapsedTime = ulElapsedTime > m_ulTotalPauseTime ? 
			ulElapsedTime - m_ulTotalPauseTime : 0;
    }

    return ulElapsedTime;
}

void CBufferManager::UpdateHighestTimestamps(INT64 llActualTimeStamp,
					     STREAM_INFO* pStreamInfo)
{
    // we apply the highest packet time-stamp to every stream
    // to solve infinite buffering
    if (m_llHighestTimeStamp < llActualTimeStamp)
    {
	m_llHighestTimeStamp = llActualTimeStamp;
    }
}

void CBufferManager::SetPerfectPlay(BOOL bPerfectPlay)
{
    m_bPerfectPlay = bPerfectPlay;

    if (m_pParent->IsLive())
    {
	m_bPerfectPlay	= FALSE;
	m_bBufferedPlay	= FALSE;
    }
}
