/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: CHXClientPlayer.cpp,v 1.25.2.5 2004/07/09 01:49:47 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 "CHXClientPlayer.h"
#include "CHXClientSink.h"
#include "CHXEQProcessor.h"
#include "CHXClientBuffer.h"
#include "CHXClientRequest.h"
#include "CHXClientDataStream.h"
#include "CHXFlatArray.h"
#include "CHXClientDebug.h"

#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
#include "CHXStatisticTracker.h"
#endif

#ifdef HELIX_FEATURE_VIDEO
#include "CHXClientSiteSupplier.h"
#endif

#include "enter_hx_headers.h"
#include "hxfiles.h" /* IHXRequest */
#include "ihxpckts.h"
#include "hxcore.h"
#include "hxgroup.h"
#include "hxvsrc.h"

#include "hxsmartptr.h"
HX_SMART_POINTER_INLINE( SPIHXBuffer, IHXBuffer );
HX_SMART_POINTER_INLINE( SPIHXValues, IHXValues );
HX_SMART_POINTER_INLINE( SPIHXRequest, IHXRequest );
HX_SMART_POINTER_INLINE( SPIHXAudioPlayer, IHXAudioPlayer );
HX_SMART_POINTER_INLINE( SPIHXPlayer2, IHXPlayer2 );
HX_SMART_POINTER_INLINE( SPIHXGroup, IHXGroup );
HX_SMART_POINTER_INLINE( SPIHXGroupManager, IHXGroupManager );
HX_SMART_POINTER_INLINE( SPIHXViewSourceCommand, IHXViewSourceCommand );
HX_SMART_POINTER_INLINE( SPIHXClientViewRights, IHXClientViewRights );
#include "exit_hx_headers.h"

#include "HXClientConstants.h"
#include "hlxclib/string.h"
#include "hlxclib/stdlib.h"
#include "hlxclib/ctype.h"

static UINT32 const kMinimumDataSize = 3; // Enough for the old RA3 header ( ".ra" ).
static const char* const kUnsupportedMimeTypes[] = { "text/html", NULL };
static const char* const kProtocolMemFS = "mem://"; // XXXSEH: Access from a common header.
static IHXStreamSource* const kCurrentStream = NULL;

CHXClientPlayer::~CHXClientPlayer( void )
{
	Stop();
	HX_RELEASE( m_pIOpenedRequest );
	if ( m_pDataStreams )
	{
		CHXClientDataStream* pDataStream = NULL;
		while ( m_pDataStreams->Pop( &pDataStream ) )
		{
			delete pDataStream;
			pDataStream = NULL;
		}
		delete m_pDataStreams;
		m_pDataStreams = NULL;
	}
#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	if ( m_pStatisticTracker )
	{
		CHXStatisticTracker::DestroyPlayerStatisticTracker( m_pStatisticTracker );
		m_pStatisticTracker = NULL;
	}
#endif
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->UnhookAudio();
		m_pEQProcessor->Release();
		m_pEQProcessor = NULL;
	}
	HX_RELEASE( m_pIClientVolume );
	
	// XXXSEH: There is no way to clear the client context from the Player.
	HX_RELEASE( m_pClientContext );

#ifdef HELIX_FEATURE_VIDEO
	HX_RELEASE( m_SiteSupplier );
#endif

	if ( m_pClipSink )
	{
		SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
		if ( spGroupManager.IsValid() )
		{
			spGroupManager->RemoveSink( m_pClipSink );
		}
		m_pIHXCorePlayer->RemoveAdviseSink( m_pClipSink );
		m_pClipSink->Destroy();
		HX_RELEASE( m_pClipSink );
	}
	m_pIHXClientEngine->ClosePlayer( m_pIHXCorePlayer );
	m_pIHXCorePlayer->Release();
	m_pIHXClientEngine->Release();
}

CHXClientPlayer::CHXClientPlayer( IHXClientEngine* pIHXClientEngine, IHXPlayer* pIHXPlayer )
	: IHXClientPlayer()
	, m_pIHXClientEngine( pIHXClientEngine )
	, m_pIHXCorePlayer( pIHXPlayer )
	, m_pIClientVolume( NULL )
	, m_pClipSink( NULL )
	, m_pClientContext( NULL )
	, m_pEQProcessor( NULL )
#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	, m_pStatisticTracker( NULL )
#endif
#ifdef HELIX_FEATURE_VIDEO
	, m_SiteSupplier( NULL )
#endif
	, m_PendingSeekPosition( kInvalidSeekPosition )
	, m_pDataStreams( NULL )
	, m_pIOpenedRequest( NULL )
	, m_IsActivelySeeking( false )
{
	CHXASSERT( m_pIHXClientEngine );
	CHXASSERT( m_pIHXCorePlayer );

	m_pIHXClientEngine->AddRef();
	m_pIHXCorePlayer->AddRef();
}

bool
CHXClientPlayer::Init( HXxWindow* pHXxWindow, void* userInfo, const HXClientCallbacks* pClientCallbacks )
{
	m_pClipSink = new CHXClientSink( m_pIHXCorePlayer, userInfo, pClientCallbacks );
	m_pClipSink->AddRef();
	m_pClipSink->Init();
	m_pIHXCorePlayer->AddAdviseSink( m_pClipSink );
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	if ( spGroupManager.IsValid() )
	{
		spGroupManager->AddSink( m_pClipSink );
	}
	IUnknown* pIUnkSiteSupplier = NULL;
#ifdef HELIX_FEATURE_VIDEO
	m_SiteSupplier = new CHXClientSiteSupplier( m_pIHXCorePlayer, pHXxWindow, userInfo, pClientCallbacks );
	m_SiteSupplier->AddRef();
	pIUnkSiteSupplier = m_SiteSupplier;
#endif
	m_pClientContext = CreateClientContext( this, pIUnkSiteSupplier, m_pClipSink->GetUnknown(), userInfo, pClientCallbacks );
	m_pIHXCorePlayer->SetClientContext( m_pClientContext->GetUnknown() );

	SPIHXAudioPlayer spAudioPlayer = m_pIHXCorePlayer;
	if ( spAudioPlayer.IsValid() )
	{
		// XXXSEH: GetDeviceVolume() affects the entire application, but its effects are instant.
		// GetAudioVolume() affects each player instance separately, but their effect is delayed.
		//m_pIClientVolume = spAudioPlayer->GetAudioVolume();
		m_pIClientVolume = spAudioPlayer->GetDeviceVolume();
	}
	m_pEQProcessor = new CHXEQProcessor( m_pIHXCorePlayer );
	m_pEQProcessor->AddRef();
	m_pEQProcessor->HookAudio();

#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	m_pStatisticTracker = CHXStatisticTracker::CreatePlayerStatisticTracker( m_pIHXCorePlayer );
#endif
	return true; // XXXSEH: Could make this return a more relevant value.
}

IHXClientPlayer*
CHXClientPlayer::Create( IHXClientEngine* pIHXClientEngine, IHXPlayer* pIHXPlayer, HXxWindow* pHXxWindow, void* userInfo, const HXClientCallbacks* pClientCallbacks )
{
	CHXClientPlayer* pClientPlayer = new CHXClientPlayer( pIHXClientEngine, pIHXPlayer );
	if ( pClientPlayer )
	{
		if ( pClientPlayer->Init( pHXxWindow, userInfo, pClientCallbacks ) )
		{
			pClientPlayer->AddRef();
			return pClientPlayer;
		}
		delete pClientPlayer;
	}
	return NULL;
}

CHXClientContext*
CHXClientPlayer::CreateClientContext( IHXClientPlayer* pIClientPlayer, IUnknown* pIUnkSiteSupplier, IUnknown* pIUnkErrorSink, void* userInfo, const HXClientCallbacks* pClientCallbacks )
{
	CHXClientContext* pClientContext = new CHXClientContext( pIClientPlayer, pIUnkSiteSupplier, pIUnkErrorSink, userInfo, pClientCallbacks );
	if ( pClientContext )
	{
		pClientContext->AddRef();
	}
	return pClientContext;
}

HX_RESULT
CHXClientPlayer::GetHXClientEngine( IHXClientEngine** ppIClientEngine )
{
	if ( !ppIClientEngine ) return HXR_INVALID_PARAMETER;
	
	*ppIClientEngine = m_pIHXClientEngine;
	if ( *ppIClientEngine )
	{
		( *ppIClientEngine )->AddRef();
		return HXR_OK;
	}
	return HXR_FAIL;
}

HX_RESULT
CHXClientPlayer::GetHXPlayer( IHXPlayer** ppIPlayer )
{
	if ( !ppIPlayer ) return HXR_INVALID_PARAMETER;
	
	*ppIPlayer = m_pIHXCorePlayer;
	if ( *ppIPlayer )
	{
		( *ppIPlayer )->AddRef();
		return HXR_OK;
	}
	return HXR_FAIL;
}

void
CHXClientPlayer::RemoveOpenedDataStream( void )
{
	if ( m_pIOpenedRequest && m_pDataStreams )
	{
		UINT32 numStreams = m_pDataStreams->GetCount();
		for ( UINT32 index = 0; index < numStreams; ++index )
		{
			CHXClientDataStream* pDataStream = NULL;
			m_pDataStreams->GetAt( index, &pDataStream );
			CHXASSERT( pDataStream );
			if ( pDataStream->GetRequest() == m_pIOpenedRequest )
			{
				m_pDataStreams->Remove( index );
				delete pDataStream;
				break;
			}
		}
	}
}

HX_RESULT
CHXClientPlayer::OpenRequest( IHXRequest* pIRequest )
{
	if ( pIRequest == m_pIOpenedRequest ) return HXR_OK;
		
	Stop();
	RemoveOpenedDataStream();
	HX_RELEASE( m_pIOpenedRequest );

	HX_RESULT outResult = LoadRequest( pIRequest );
	if ( SUCCEEDED( outResult ) )
	{
		m_pIOpenedRequest = pIRequest;
		m_pIOpenedRequest->AddRef();
	}
	return outResult;
}

HX_RESULT
CHXClientPlayer::LoadRequest( IHXRequest* pIRequest )
{
	CHXASSERT( pIRequest );
	
	m_pClientContext->ResetHasRequestedUpgradeFlag();
	m_pClientContext->ClearPendingAuthentication();
	
	HX_RESULT outResult = HXR_FAIL;
	SPIHXPlayer2 spPlayer2 = m_pIHXCorePlayer;
	if ( spPlayer2.IsValid() )
	{
		outResult = spPlayer2->OpenRequest( pIRequest );
	}
	else
	{
		const char* pURL = NULL;
		pIRequest->GetURL( pURL );
		outResult = m_pIHXCorePlayer->OpenURL( pURL );
	}
	return outResult;
}

bool
CHXClientPlayer::IsUnsupportedMimeType( const char* pMimeType ) const
{
	if ( pMimeType && *pMimeType )
	{
		int index = 0;
		while ( kUnsupportedMimeTypes[ index ] )
		{
			if ( 0 == strcmp( pMimeType, kUnsupportedMimeTypes[ index ] ) )
			{
				return true;
			}
			++index;
		}
	}
	return false;
}

HX_RESULT
CHXClientPlayer::OpenURL( const char* pURL, const char* pMimeType )
{
	if ( !pURL || !*pURL ) return HXR_INVALID_PARAMETER;
	if ( IsUnsupportedMimeType( pMimeType ) ) return HXR_FAIL;
	
	SPIHXRequest spRequest = new CHXClientRequest( pURL, pMimeType );
	if ( !spRequest.IsValid() ) return HXR_OUTOFMEMORY;
	
	return OpenRequest( spRequest.Ptr() );
}

bool
CHXClientPlayer::IsRequestOpenOrPending( IHXRequest* pIRequest ) const
{
	const char* pURL = NULL;
	pIRequest->GetURL( pURL );
	if ( m_pIOpenedRequest )
	{
		const char* pOpenedURL = NULL;
		m_pIOpenedRequest->GetURL( pOpenedURL );
		if ( 0 == strcasecmp( pURL, pOpenedURL ) ) return true;
	}
	if ( m_pDataStreams )
	{
		UINT32 numStreams = m_pDataStreams->GetCount();
		for ( UINT32 index = 0; index < numStreams; ++index )
		{
			CHXClientDataStream* pDataStream = NULL;
			m_pDataStreams->GetAt( index, &pDataStream );
			CHXASSERT( pDataStream );
			
			const char* pDataURL = NULL;
			pDataStream->GetRequest()->GetURL( pDataURL );
			
			if ( 0 == strcasecmp( pURL, pDataURL ) ) return true;
		}
	}
	return false;
}

bool
CHXClientPlayer::FindDataStream( CHXClientDataStream* pFindDataStream, UINT32* pRecordNum ) const
{
	if ( m_pDataStreams )
	{
		UINT32 numStreams = m_pDataStreams->GetCount();
		for ( UINT32 index = 0; index < numStreams; ++index )
		{
			CHXClientDataStream* pDataStream = NULL;
			m_pDataStreams->GetAt( index, &pDataStream );
			CHXASSERT( pDataStream );
			if ( pDataStream == pFindDataStream )
			{
				if ( pRecordNum )
				{
					*pRecordNum = index;
				}
				return true;
			}
		}
	}
	return false;
}

HX_RESULT
CHXClientPlayer::OpenData( const char* pURL, const char* pMimeType, UINT32 dataLength, bool autoPlay, void** ppOutData )
{
	if ( !pURL || !*pURL ) return HXR_INVALID_PARAMETER;
	if ( !ppOutData ) return HXR_INVALID_PARAMETER;
	if ( ( 0 < dataLength ) && ( dataLength < kMinimumDataSize ) ) return HXR_FAIL;
	if ( IsUnsupportedMimeType( pMimeType ) ) return HXR_FAIL;
	
	CHXClientDataStream* pDataStream = new CHXClientDataStream( this, pURL, pMimeType, dataLength, autoPlay );
	if ( !pDataStream ) return HXR_OUTOFMEMORY;

	// Can't open a data stream twice or in addition to a standard OpenURL.
	HX_RESULT outResult = HXR_FAIL;
	if ( !IsRequestOpenOrPending( pDataStream->GetRequest() ) )
	{
		outResult = HXR_OUTOFMEMORY;
		if ( !m_pDataStreams )
		{
			m_pDataStreams = new CHXFlatArray( sizeof( pDataStream ) );
		}
		if (  m_pDataStreams )
		{
			m_pDataStreams->Push( &pDataStream );
			*ppOutData = pDataStream;
			return HXR_OK;
		}
	}
	delete pDataStream;
	return outResult;
}

HX_RESULT
CHXClientPlayer::WriteData( void* pData, UINT32 bufferLength, unsigned char* pBuffer )
{
	UINT32 recordNum;
	CHXClientDataStream* pDataStream = ( CHXClientDataStream* ) pData;
	if ( !FindDataStream( pDataStream, &recordNum ) ) return HXR_FAIL;
	
	HX_RESULT outResult = pDataStream->WriteData( bufferLength, pBuffer );
	if ( FAILED( outResult ) )
	{
		m_pDataStreams->Remove( recordNum );
		delete pDataStream;
	}
	return outResult;
}

void
CHXClientPlayer::CloseData( void* pData )
{
	UINT32 recordNum;
	CHXClientDataStream* pDataStream = ( CHXClientDataStream* ) pData;
	if ( !FindDataStream( pDataStream, &recordNum ) ) return;

	HX_RESULT result = pDataStream->CloseData();
	if ( FAILED( result ) )
	{
		m_pDataStreams->Remove( recordNum );
		delete pDataStream;
	}
}

bool
CHXClientPlayer::GetOpenedURL( char* pURLBuffer, UINT32 bufferLength, UINT32* pUsedBufferLength ) const
{
	if ( pUsedBufferLength )
	{
		*pUsedBufferLength = 0;
	}
	if ( !m_pIOpenedRequest ) return false;
	
	const char* pOpenedURL = NULL;
	if ( FAILED( m_pIOpenedRequest->GetURL( pOpenedURL ) ) ) return false;
	
	UINT32 sizeOfMemFSProtocol = strlen( kProtocolMemFS );
	if ( 0 == strncmp( pOpenedURL, kProtocolMemFS, sizeOfMemFSProtocol ) )
	{
		pOpenedURL += sizeOfMemFSProtocol;
	}
	UINT32 desiredBufferLength = strlen( pOpenedURL ) + 1;
	if ( pUsedBufferLength )
	{
		*pUsedBufferLength = desiredBufferLength;
	}
	if ( !pURLBuffer || ( bufferLength == 0 ) ||
		 ( desiredBufferLength > bufferLength ) ) // Makes no sense to copy a partial URL.
	{
		return false;
	}
	memcpy( pURLBuffer, pOpenedURL, desiredBufferLength );
	return true;
}

bool
CHXClientPlayer::CanViewSource( void )
{
	SPIHXViewSourceCommand spViewSourceCommand = m_pIHXCorePlayer;
	return spViewSourceCommand.IsValid() ?
		   ( ( 0 != spViewSourceCommand->CanViewSource( kCurrentStream ) ) ? true : false ) : false;
}

void
CHXClientPlayer::ViewSource( void )
{
	SPIHXViewSourceCommand spViewSourceCommand = m_pIHXCorePlayer;
	if ( spViewSourceCommand.IsValid() )
	{
		( void ) spViewSourceCommand->DoViewSource( kCurrentStream );
	}
}

bool
CHXClientPlayer::CanViewRights( void )
{
	SPIHXClientViewRights spClientViewRights = m_pIHXCorePlayer;
	return spClientViewRights.IsValid() ?
		   ( ( 0 != spClientViewRights->CanViewRights() ) ? true : false ) : false;
}

void
CHXClientPlayer::ViewRights( void )
{
	SPIHXClientViewRights spClientViewRights = m_pIHXCorePlayer;
	if ( spClientViewRights.IsValid() )
	{
		( void ) spClientViewRights->ViewRights( m_pIHXCorePlayer );
	}
}

HX_RESULT
CHXClientPlayer::Authenticate( bool shouldValidateUser, const char* pUsername, const char* pPassword )
{
	return m_pClientContext->Authenticate( shouldValidateUser, pUsername, pPassword );
}

int
CHXClientPlayer::GetContentState( void ) const
{
	return ( m_pClipSink && m_pIOpenedRequest ) ? m_pClipSink->GetContentState() : kContentStateNotLoaded;
}

bool
CHXClientPlayer::HasContentBegun( void ) const
{
	return m_pClipSink ? m_pClipSink->HasContentBegun() : false;
}

bool
CHXClientPlayer::SetStatus( const char* pStatus )
{
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		return m_SiteSupplier->SetStatus( pStatus );
	}
	else
#endif
	{
		return false;
	}
}

void
CHXClientPlayer::Play( void )
{
	if ( m_pIOpenedRequest /* && !HasContentBegun() */ )
	{
		if ( !m_pClientContext->IsAuthenticationPending() )
		{
			HX_RESULT result = HXR_OK;
			
			// XXXSEH: Added IsDone() check to deal with a Helix bug.
			//         Once a clip has been stopped, Helix cannot play it until it's been Opened again.
			if ( m_pClientContext->HasRequestedUpgrade() ||
				 m_pIHXCorePlayer->IsDone() )
			{
				result = LoadRequest( m_pIOpenedRequest );
				if ( FAILED( result ) ) return;
			}
			if ( m_PendingSeekPosition != kInvalidSeekPosition )
			{
				UINT32 beginPosition = m_PendingSeekPosition;
				m_PendingSeekPosition = kInvalidSeekPosition;
				if ( m_pClipSink )
				{
					m_pClipSink->SetBeginPosition( beginPosition );
				}
			}
			result = m_pIHXCorePlayer->Begin();
			CHXASSERT( SUCCEEDED( result ) );
		}
	}
}

void
CHXClientPlayer::Pause( void )
{
	// if ( HasContentBegun() )
	{
		HX_RESULT result = m_pIHXCorePlayer->Pause();
		CHXASSERT( SUCCEEDED( result ) );
	}
}

void
CHXClientPlayer::Stop( void )
{
	// if ( HasContentBegun() )
	{
		HX_RESULT result = m_pIHXCorePlayer->Stop();
		CHXASSERT( SUCCEEDED( result ) );
		if ( m_pClipSink )
		{
			m_pClipSink->ProcessPendingStateChange();
		}
	}
}

bool
CHXClientPlayer::StartSeeking( void )
{
	bool outSeekingHasStarted = m_IsActivelySeeking;
	if ( !outSeekingHasStarted )
	{
		bool hasContentBegun = HasContentBegun();
		if ( hasContentBegun || ( GetContentState() == kContentStatePaused ) )
		{
			m_ShouldPlayOnStopSeeking = hasContentBegun;
			Pause();
			m_PendingSeekPosition = GetPosition();
			m_IsActivelySeeking = true;
			outSeekingHasStarted = true;
		}
	}
	return outSeekingHasStarted;
}

HX_RESULT
CHXClientPlayer::SetPosition( UINT32 position )
{
	HX_RESULT outResult = HXR_FAIL;
	if ( m_IsActivelySeeking /*|| ( GetContentState() == kContentStateStopped )*/ ) // XXXSEH: See comment in CHXClientSink::SetBeginPosition()
	{
		m_PendingSeekPosition = position;
		outResult = HXR_OK;
	}
	else
	{
		outResult = m_pIHXCorePlayer->Seek( position );
	}
	return outResult;
}

void
CHXClientPlayer::StopSeeking( void )
{
	if ( m_IsActivelySeeking )
	{
		m_IsActivelySeeking = false;
		if ( m_ShouldPlayOnStopSeeking )
		{
			Play();
		}
	}
}

UINT32
CHXClientPlayer::GetPosition( void ) const
{
	// return m_pIHXCorePlayer->GetCurrentPlayTime(); // XXXSEH: Would this be better?
	return ( m_PendingSeekPosition != kInvalidSeekPosition ) ?
		   m_PendingSeekPosition : ( m_pClipSink ? m_pClipSink->GetPosition() : 0 );
}

UINT32
CHXClientPlayer::GetLength( void ) const
{
	return m_pClipSink ? m_pClipSink->GetLength() : 0;
}

bool
CHXClientPlayer::IsLive( void ) const
{
	return ( ( 0 != m_pIHXCorePlayer->IsLive() ) ? true : false );
}

const char*
CHXClientPlayer::GetTitle( void ) const
{
	return m_pClipSink ? m_pClipSink->GetTitle() : NULL;
}

const char*
CHXClientPlayer::GetContextURL( void ) const
{
	return m_pClipSink ? m_pClipSink->GetContextURL() : NULL;
}

INT32
CHXClientPlayer::GetClipBandwidth( void ) const
{
	return m_pClipSink ? m_pClipSink->GetClipBandwidth() : 0;
}

UINT16
CHXClientPlayer::GetSourceCount( void ) const
{
	return m_pIHXCorePlayer ? m_pIHXCorePlayer->GetSourceCount() : 0;
}

UINT16
CHXClientPlayer::GetGroupCount( void ) const
{
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	return spGroupManager.IsValid() ? spGroupManager->GetGroupCount() : 0;
}

UINT16
CHXClientPlayer::GetCurrentGroup( void ) const
{
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	if ( spGroupManager.IsValid() )
	{
		UINT16 currentGroupIndex = 0;
		if ( SUCCEEDED( spGroupManager->GetCurrentGroup( currentGroupIndex ) ) )
		{
			return currentGroupIndex;
		}
	}
	return 0; // XXXSEH: Is this a reasonable default or failed value?
}

HX_RESULT
CHXClientPlayer::SetCurrentGroup( UINT16 groupIndex )
{
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	return spGroupManager.IsValid() ? spGroupManager->SetCurrentGroup( groupIndex ) : HXR_FAIL;
}

static bool
BufferContainsText( const SPIHXBuffer& spBuff )
{
	if ( !spBuff.IsValid() ) return false;
	
	const unsigned char* pString = spBuff->GetBuffer();
	while ( *pString )
	{
		if ( !isspace( *pString++ ) )
		{
			return true;
		}
	}
	return false;
}

bool
CHXClientPlayer::GetGroupURLBuffer( UINT16 groupIndex, IHXBuffer** ppIURL ) const
{
	SPIHXBuffer spBuffer;
	SPIHXGroup spGroup;
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	if ( spGroupManager.IsValid() && SUCCEEDED( spGroupManager->GetGroup( groupIndex, *spGroup.AsInOutParam() ) ) && ( spGroup.IsValid() ) )
	{
		// try to get an URL; if that fails, look for a track with an URL
		SPIHXValues spProperties = spGroup->GetGroupProperties();
		if ( spProperties.IsValid() )
		{
			spProperties->GetPropertyCString( "url", *spBuffer.AsInOutParam() );
			if ( BufferContainsText( spBuffer ) )
			{
				spBuffer.AsPtr( ppIURL );
				return true;
			}
		}
		SPIHXValues spProps2;
		for ( UINT16 i = 0 ; i < spGroup->GetTrackCount() ; ++i )
		{
			spGroup->GetTrack( i, *spProps2.AsInOutParam() );
			if ( spProps2.IsValid() )
			{
				spProps2->GetPropertyCString( "url", *spBuffer.AsInOutParam() );
				if ( BufferContainsText( spBuffer ) )
				{
					spBuffer.AsPtr( ppIURL );
					return true;
				}
				spProps2->GetPropertyCString( "src", *spBuffer.AsInOutParam() );
				if ( BufferContainsText( spBuffer ) )
				{
					spBuffer.AsPtr( ppIURL );
					return true;
				}
			}
		}
    }
	// we failed to get a group or track URL, so use one for the overall file
	SPIHXValues spValues = m_pIHXCorePlayer;
	if ( spValues.IsValid() )
	{
		spValues->GetPropertyCString( "url", *spBuffer.AsInOutParam() );
		if ( BufferContainsText( spBuffer ) )
		{
			spBuffer.AsPtr( ppIURL );
			return true;
		}
	}
	return false;
}

bool
CHXClientPlayer::GetGroupURL( UINT16 groupIndex, char* pURLBuffer, UINT32 bufferLength, UINT32* pUsedBufferLength ) const
{
	if ( pUsedBufferLength )
	{
		*pUsedBufferLength = 0;
	}
	SPIHXBuffer spURL;
	if ( GetGroupURLBuffer( groupIndex, spURL.AsInOutParam() ) )
	{
		const unsigned char* pURLString = spURL->GetBuffer();
		UINT32 desiredBufferLength = spURL->GetSize();

		// If no buffer was provided, just return the string's size.
		if ( !pURLString || ( bufferLength == 0 ) )
		{
			if ( pUsedBufferLength )
			{
				*pUsedBufferLength = desiredBufferLength;
			}
			return false;
		}
		else
		{
			UINT32 usedBufferLength = ( desiredBufferLength <= bufferLength ) ? desiredBufferLength : bufferLength;
			memcpy( pURLBuffer, pURLString, usedBufferLength );
			if ( usedBufferLength < desiredBufferLength )
			{
				pURLBuffer[ usedBufferLength - 1 ] = '\0';
			}
			if ( pUsedBufferLength )
			{
				*pUsedBufferLength = usedBufferLength;
			}
			return true;
		}
	}
	return false;
}

bool
CHXClientPlayer::GetGroupTitleBuffer( UINT16 groupIndex, IHXBuffer** ppIDisplayName, bool& isURL ) const
{
	isURL = false;

	SPIHXBuffer spBuffer;
	SPIHXGroup spGroup;
	SPIHXGroupManager spGroupManager = m_pIHXCorePlayer;
	if ( spGroupManager.IsValid() && SUCCEEDED( spGroupManager->GetGroup( groupIndex, *spGroup.AsInOutParam() ) ) && ( spGroup.IsValid() ) )
	{
		// try to get a group name or URL; if that fails, look for a track with a title or URL
		SPIHXValues spProperties = spGroup->GetGroupProperties();
		if ( spProperties.IsValid() )
		{
			spProperties->GetPropertyCString( "title", *spBuffer.AsInOutParam() );
			if ( BufferContainsText( spBuffer ) )
			{
				spBuffer.AsPtr( ppIDisplayName );
				return true;
			}
			spProperties->GetPropertyCString( "url", *spBuffer.AsInOutParam() );
			if ( BufferContainsText( spBuffer ) )
			{
				spBuffer.AsPtr( ppIDisplayName );
				isURL = true;
				return true;
			}
		}
		SPIHXValues spProps2;
		for ( UINT16 i = 0 ; i < spGroup->GetTrackCount() ; ++i )
		{
			spGroup->GetTrack( i, *spProps2.AsInOutParam() );
			if ( spProps2.IsValid() )
			{
				spProps2->GetPropertyCString( "title", *spBuffer.AsInOutParam() );
				if ( BufferContainsText( spBuffer ) )
				{
					spBuffer.AsPtr( ppIDisplayName );
					return true;
				}
				spProps2->GetPropertyCString( "src", *spBuffer.AsInOutParam() );
				if ( BufferContainsText( spBuffer ) )
				{
					spBuffer.AsPtr( ppIDisplayName );
					isURL = true;
					return true;
				}
			}
		}
    }
	// we failed to get a group or track name or URL, so use one for the overall file
	SPIHXValues spValues = m_pIHXCorePlayer;
	if ( spValues.IsValid() )
	{
		spValues->GetPropertyCString( "url", *spBuffer.AsInOutParam() );
		if ( BufferContainsText( spBuffer ) )
		{
			spBuffer.AsPtr( ppIDisplayName );
			isURL = true;
			return true;
		}
	}
	return false;
}

bool
CHXClientPlayer::GetGroupTitle( UINT16 groupIndex, char* pTitleBuffer, UINT32 bufferLength, UINT32* pUsedBufferLength ) const
{
	if ( pUsedBufferLength )
	{
		*pUsedBufferLength = 0;
	}
	bool isURL = false;
	SPIHXBuffer spDisplayName;
	if ( GetGroupTitleBuffer( groupIndex, spDisplayName.AsInOutParam(), isURL ) )
	{
		const unsigned char* pTitleString = spDisplayName->GetBuffer();
		const unsigned char* pEndOfTitle = pTitleString + spDisplayName->GetSize() - 1;
		while ( pTitleString < pEndOfTitle )
		{
			if ( !isspace( *pTitleString ) ) break;
			pTitleString++;
		}
		while ( pEndOfTitle > pTitleString )
		{
			if ( !isspace( pEndOfTitle[ -1 ] ) ) break;
			pEndOfTitle--;
		}
		if ( pTitleString >= pEndOfTitle ) return false;

		UINT32 desiredBufferLength = pEndOfTitle - pTitleString + 1;
		
// XXXSEH: Need to implement this post processing on non Mac platforms.
#ifdef _MAC_MACHO
		if ( isURL )
		{
			CFURLRef kNoBaseURL = NULL;
			CFURLRef urlRef = CFURLCreateWithBytes( kCFAllocatorDefault, pTitleString, desiredBufferLength, kCFStringEncodingUTF8, kNoBaseURL );
			CFStringRef filenameString = ::CFURLCopyLastPathComponent( urlRef );
			CFRelease( urlRef );
			if ( !filenameString ) return false;
				
			CFStringRef displayName = ::CFURLCreateStringByReplacingPercentEscapes( kCFAllocatorDefault, filenameString, nil );
			if ( !displayName )
			{
				displayName = ( CFStringRef ) CFRetain( filenameString );
			}
			CFRelease( filenameString );
			
			// If no buffer was provided, just return the string's size.
			if ( !pTitleBuffer || ( bufferLength == 0 ) )
			{
				CFStringGetBytes( displayName, CFRangeMake( 0, CFStringGetLength( displayName ) ), kCFStringEncodingUTF8, '?', false, NULL, 0, ( CFIndex* ) pUsedBufferLength );
				if ( pUsedBufferLength )
				{
					*pUsedBufferLength = *pUsedBufferLength + 1;
				}
				CFRelease( displayName );
				return false;
			}
			else
			{
				// XXXSEH: What will this produce if the buffer isn't large enough? Will it properly terminate the truncated string?
				if ( CFStringGetCString( displayName, pTitleBuffer, bufferLength, kCFStringEncodingUTF8 ) && pUsedBufferLength )
				{
					*pUsedBufferLength = strlen( pTitleBuffer ) + 1;
				}
				CFRelease( displayName );
				return true;
			}
		}
		else
#endif
		{			
			// If no buffer was provided, just return the string's size.
			if ( !pTitleBuffer || ( bufferLength == 0 ) )
			{
				if ( pUsedBufferLength )
				{
					*pUsedBufferLength = desiredBufferLength;
				}
				return false;
			}
			else
			{
				UINT32 usedBufferLength = ( desiredBufferLength <= bufferLength ) ? desiredBufferLength : bufferLength;
				memcpy( pTitleBuffer, pTitleString, usedBufferLength );
				if ( usedBufferLength < desiredBufferLength )
				{
					pTitleBuffer[ usedBufferLength - 1 ] = '\0';
				}
				if ( pUsedBufferLength )
				{
					*pUsedBufferLength = usedBufferLength;
				}
				return true;
			}
		}
	}
	return false;
}

#pragma mark -

void
CHXClientPlayer::SetVolume( UINT16 volume )
{
	if ( m_pIClientVolume )
	{
		m_pIClientVolume->SetVolume( volume );
	}
}

UINT16
CHXClientPlayer::GetVolume( void ) const
{
	return m_pIClientVolume ? m_pIClientVolume->GetVolume() : 0;
}

void
CHXClientPlayer::Mute( bool shouldMute )
{
	if ( m_pIClientVolume )
	{
		m_pIClientVolume->SetMute( shouldMute ? 1 : 0 );
	}
}

bool
CHXClientPlayer::IsMuted( void ) const
{
	return m_pIClientVolume ? ( ( 0 != m_pIClientVolume->GetMute() ) ? true : false ) : false;
}

#pragma mark -

void
CHXClientPlayer::EnableEQ( bool enable )
{
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->Enable( enable );
	}
}

bool
CHXClientPlayer::IsEQEnabled( void ) const
{
	return m_pEQProcessor ? m_pEQProcessor->IsEnabled() : false;
}

void
CHXClientPlayer::SetEQGain( int band, INT32 gain )
{
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->SetGain( band, gain );
	}
}

INT32
CHXClientPlayer::GetEQGain( int band ) const
{
	return m_pEQProcessor ? m_pEQProcessor->GetGain( band ) : 0;
}

void
CHXClientPlayer::SetEQPreGain( INT32 preGain )
{
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->SetPreGain( preGain );
	}
}

INT32
CHXClientPlayer::GetEQPreGain( void ) const
{
	return m_pEQProcessor ? m_pEQProcessor->GetPreGain() : 0;
}

void
CHXClientPlayer::EnableEQAutoPreGain( bool enable )
{
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->EnableAutoPreGain( enable );
	}
}

bool
CHXClientPlayer::IsEQAutoPreGainEnabled( THIS ) const
{
	return m_pEQProcessor ? m_pEQProcessor->IsAutoPreGainEnabled() : false;
}

void
CHXClientPlayer::SetEQReverb( INT32 roomSize, INT32 reverb )
{
	if ( m_pEQProcessor )
	{
		m_pEQProcessor->SetReverb( roomSize, reverb );
	}
}

void
CHXClientPlayer::GetEQReverb( INT32* pRoomSize, INT32* pReverb ) const
{
	CHXASSERT( pRoomSize );
	CHXASSERT( pReverb );
	if ( m_pEQProcessor )
	{
		int roomSize, reverb;
		m_pEQProcessor->GetReverb( &roomSize, &reverb );
		*pRoomSize = roomSize;
		*pReverb = reverb;
	}
	else
	{
		*pRoomSize = 0;
		*pReverb = 0;
	}
}

#pragma mark -

bool
CHXClientPlayer::HasVisualContent( void ) const
{
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		return m_SiteSupplier->HasVisualContent();
	}
	else
#endif
	{
		return false;
	}
}

void
CHXClientPlayer::GetIdealSize( INT32* pSiteIdealWidth, INT32* pSiteIdealHeight ) const
{
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		HXxSize siteIdealSize = { 0, 0 };
		m_SiteSupplier->GetIdealSize( siteIdealSize );
		*pSiteIdealWidth = siteIdealSize.cx;
		*pSiteIdealHeight = siteIdealSize.cy;
	}
	else
#endif
	{
		*pSiteIdealWidth = 0;
		*pSiteIdealHeight = 0;
	}
}

void
CHXClientPlayer::SetSize( INT32 siteWidth, INT32 siteHeight )
{
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		HXxSize siteSize;
		siteSize.cx = siteWidth;
		siteSize.cy = siteHeight;
		m_SiteSupplier->SetSize( siteSize );
	}
#endif
}

void
CHXClientPlayer::DrawSite( const HXxRect* pSiteRect )
{
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		m_SiteSupplier->DrawSite( *pSiteRect );
	}
#endif
}

bool
CHXClientPlayer::GetVideoAttribute( int attributeKey, float* pAttributeValue ) const
{
	CHXASSERT( pAttributeValue );
	
	bool outOK = false;
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		SPIHXVideoControl spVideoControl = m_SiteSupplier->GetVideoControl();
		if ( spVideoControl.IsValid() )
		{
			outOK = true;
			switch ( attributeKey )
			{
				case kVideoAttrBrightness:
					*pAttributeValue = spVideoControl->GetBrightness();
					break;
				case kVideoAttrContrast:
					*pAttributeValue = spVideoControl->GetContrast();
					break;
				case kVideoAttrSaturation:
					*pAttributeValue = spVideoControl->GetSaturation();
					break;
				case kVideoAttrHue:
					*pAttributeValue = spVideoControl->GetHue();
					break;
				case kVideoAttrSharpness:
					*pAttributeValue = spVideoControl->GetSharpness();
					break;
				default:
					outOK = false;
					break;
			}
		}
	}
#endif
	return outOK;
}

bool
CHXClientPlayer::SetVideoAttribute( int attributeKey, float attributeValue )
{
	bool outOK = false;
#ifdef HELIX_FEATURE_VIDEO
	if ( m_SiteSupplier )
	{
		SPIHXVideoControl spVideoControl = m_SiteSupplier->GetVideoControl();
		if ( spVideoControl.IsValid() )
		{
			outOK = true;
			switch ( attributeKey )
			{
				case kVideoAttrBrightness:
					spVideoControl->SetBrightness( attributeValue );
					break;
				case kVideoAttrContrast:
					spVideoControl->SetContrast( attributeValue );
					break;
				case kVideoAttrSaturation:
					spVideoControl->SetSaturation( attributeValue );
					break;
				case kVideoAttrHue:
					spVideoControl->SetHue( attributeValue );
					break;
				case kVideoAttrSharpness:
					spVideoControl->SetSharpness( attributeValue );
					break;
				default:
					outOK = false;
					break;
			}
		}
	}
#endif
	return outOK;
}

bool
CHXClientPlayer::GetStatistic( const char* pStatisticKey, unsigned char* pValueBuffer, UINT32 bufferLength, int* pValueType, UINT32* pUsedBufferLength )
{
#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	if ( m_pStatisticTracker )
	{
		return m_pStatisticTracker->GetStatisticsFor( pStatisticKey, pValueBuffer, bufferLength, pValueType, pUsedBufferLength );
	}
	else
#endif
	{
		return false;
	}
}

bool
CHXClientPlayer::AddStatisticObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	if ( m_pStatisticTracker )
	{
		return m_pStatisticTracker->AddObserver( pStatisticKey, pStatisticsCallbacks, observerInfo );
	}
	else
#endif
	{
		return false;
	}
}

void
CHXClientPlayer::RemoveStatisticObserver( const char* pStatisticKey, const HXStatisticsCallbacks* pStatisticsCallbacks, void* observerInfo )
{
#if defined(HELIX_FEATURE_REGISTRY) && defined(HELIX_FEATURE_STATS)
	if ( m_pStatisticTracker )
	{
		m_pStatisticTracker->RemoveObserver( pStatisticKey, pStatisticsCallbacks, observerInfo );
	}
#endif
}
