// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// source\server\mmfdatapath2.cpp
// 
//

#include <e32math.h>
#include <mmf/common/mmffourcc.h>
#include <mmf/common/mmfpaniccodes.h>
#include <mmf/server/mmfaudiooutput.h>
#include <mmf/server/mmfaudioinput.h>
#include "mmfdatapath2.h"
#include "mmfclientaudiostreamutils.h"
#include <mmf/common/mmfaudio.h>
#include <mmf/plugin/mmfcodecimplementationuids.hrh> // KUidMmfCodecAudioSettings
#include <mmf/server/devsoundstandardcustominterfaces.h>
#include <mmf/server/mmffile.h>
#include <mda/client/resource.h>

static void Panic(TMMFDataPathPanicCode aPanicCode, TInt aSourceLineNumber)
	{
	_LIT(KMMFDataPathPanicCategory, "MMFDataPath2");
	User::Panic(KMMFDataPathPanicCategory, STATIC_CAST(TInt,aPanicCode) + aSourceLineNumber);
	}

/**
Allocates and constructs a data path.

Use this function if the codec UID is not already known by CMMFController
and there is no data path ambiguity - ie only one data path is possible.

Will create codec via fourCC.

@param  aEventHandler
        Installs an event handler to provide message passing between clients and sources/sinks.

@return Newly constructed data path object.
*/

EXPORT_C CMMFDataPath2* CMMFDataPath2::NewL(MAsyncEventHandler& aEventHandler)
	{
	CMMFDataPath2* self = new(ELeave) CMMFDataPath2(TMediaId(), aEventHandler);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}


/**
Allocates and constructs a data path according to the specified media ID.

Use this function if the codec UID is not already known by CMMFController
and there is ambiguity with the data path ie. there is more than one possible data path.

@param  aMediaId
        Optional media ID parameter when there are multiple media types.
@param  aEventHandler
        Installs an event handler to provide message passing between clients and sources/sinks.

@return A newly constructed data path object.
*/

EXPORT_C CMMFDataPath2* CMMFDataPath2::NewL(TMediaId aMediaId, MAsyncEventHandler& aEventHandler)
	{
	CMMFDataPath2* self = new(ELeave) CMMFDataPath2(aMediaId, aEventHandler);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}

/**
Allocates and constructs a data path according to the specified codec UID.

Use this function if the codec UID is already known by CMMFController
and there is no data path ambiguity ie. only one data path is possible
will create codec explicitly using the supplied codec Uid

@param  aCodecUid
        Optional mediaID parameter when there are multiple media types
@param  aEventHandler
        Installs an event handler to provide message passing between clients and sources/sinks.

@return A newly constructed data path object.
*/

EXPORT_C CMMFDataPath2* CMMFDataPath2::NewL(TUid aCodecUid, MAsyncEventHandler& aEventHandler)
	{
	CMMFDataPath2* self = new(ELeave) CMMFDataPath2(TMediaId(), aEventHandler);
	CleanupStack::PushL(self);
	self->ConstructL(aCodecUid);
	CleanupStack::Pop();
	return self;
	}


/**
Allocates and constructs a data path according to the specified codec UID.

Use this function if the codec UID is already known by CMMFController
and there is ambiguity ie. more than one possible data path.
TMediaId used to select the path.

@param  aCodecUid
		The codec UID.
@param  aMediaId
        Optional mediaID parameter when there are multiple media types.
@param  aEventHandler
        Installs an event handler to provide message passing between clients and sources/sinks.

@return A newly constructed data path object.
*/
EXPORT_C CMMFDataPath2* CMMFDataPath2::NewL(TUid aCodecUid, TMediaId aMediaId, MAsyncEventHandler& aEventHandler)
	{
	CMMFDataPath2* self = new(ELeave) CMMFDataPath2(aMediaId, aEventHandler);
	CleanupStack::PushL(self);
	self->ConstructL(aCodecUid);
	CleanupStack::Pop();
	return self;
	}

CMMFDataPath2::CMMFDataPath2(TMediaId aMediaId, MAsyncEventHandler& aEventHandler) 
	: CMMFDataPath(aMediaId, aEventHandler), iTimeLeftToPlayComplete(-1)
	{
	}
		
void CMMFDataPath2::ConstructL(TUid aCodecUid)
	{
	CMMFDataPath::ConstructL(aCodecUid);
	iRepeatTrailingSilenceTimer = CPeriodic::NewL(CActive::EPriorityStandard); 
	}
	
/**
Standard destructor.
*/

CMMFDataPath2::~CMMFDataPath2()
	{
	if(iRepeatTrailingSilenceTimer)
		{
		iRepeatTrailingSilenceTimer->Cancel();
		delete iRepeatTrailingSilenceTimer;
		}
	}

TInt CMMFDataPath2::RepeatTrailingSilenceTimerComplete(TAny* aDataPath)
	{
	CMMFDataPath2* dataPath = static_cast<CMMFDataPath2*>(aDataPath);
	
	TRAPD(err, dataPath->DoRepeatTrailingSilenceTimerCompleteL());
	if (err != KErrNone)
		{
		dataPath->DoSendEventToClient(KMMFEventCategoryPlaybackComplete, err);
		}	
	return KErrNone;
	}
	
TInt CMMFDataPath2::DoRepeatTrailingSilenceTimerCompleteL()
	{
	//cancel this periodic timer
	iRepeatTrailingSilenceTimer->Cancel();
	if(iTimeLeftToPlayComplete.Int64()>0)
		{
		iTimeLeftToPlayComplete=0;
		}
		
	if (iTrailingSilenceLeftToPlay.Int64() > 0)
		{
		PlaySilence();
		}
	else
		{
		SetPositionL(iPlayWindowStartPosition);
		iTimeLeftToPlayComplete=-1;
		FillSourceBufferL();		
		}
	return KErrNone;
	}
	
void CMMFDataPath2::PlaySilence()
	{		
	// iRepeatTrailingSilenceTimer->After() takes a TTimeIntervalMicroSeconds32
	// so for longer periods of silence call it repeatedly with KMaxTInt lengths
	TTimeIntervalMicroSeconds32 silence;
	if(iTimeLeftToPlayComplete.Int64() > 0)
		{
		silence = I64INT(iTimeLeftToPlayComplete.Int64());
		}
	else if (iTrailingSilenceLeftToPlay.Int64() > KMaxTInt)
		{
		silence = KMaxTInt;
		iTrailingSilenceLeftToPlay = iTrailingSilenceLeftToPlay.Int64() - KMaxTInt;
		}
	else
		{
		silence = I64INT(iTrailingSilenceLeftToPlay.Int64());
		iTrailingSilenceLeftToPlay = 0;
		}
	iRepeatTrailingSilenceTimer->Start(silence, silence , TCallBack(RepeatTrailingSilenceTimerComplete, this));
	}

/* 
 *  FillSourceBufferL
 * 
 *	Function to get data from the datapath's iDataSource
 */

void CMMFDataPath2::DoFillSourceBufferL()
	{
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::FillSourceBufferL tick-%d   (this 0x%x)\n"),User::TickCount(),this);
#endif

	__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording || (iState == EPrimed && iPauseCalled && iIsUsingResumeSupport)), Panic(EMMFDataPathPanicBadState,__LINE__)); 

	// clear the no-more-source flag here (as well as in PlayL()) because 
	// there may have been a re-position since the last call to BufferFilledL()
	iNoMoreSourceData = EFalse;

	if(!iObtainingAsyncSourceBuffer) 
		{//this is a normal request for data. 
		//If we are getting asynchronous buffers, then can't do this as iSourceBuffer == NULL
		iSourceBuffer->SetFrameNumber(++iCurrentSourceFrameNumber); //so source knows which data to load buffer with
		iSourceBuffer->SetStatus(EBeingFilled);
		iSourceBuffer->SetLastBuffer(EFalse);
		}

#ifdef _DP_DEBUG
	RDebug::Print(_L("DP asking for buffer %d  - ptr=0x%x   (this 0x%x)\n"), iCurrentSourceFrameNumber, iSourceBuffer,this);	
#endif

	iSourceBufferWithSource = ETrue;

	// wait for BufferFilled callback from source. Do this here as some sources cause
	//re-entrancy into data path via BufferFilledL
	ChangeDataPathTransferState(EWaitSource);  

	iDataSource->FillBufferL(iSourceBuffer, this, iMediaId);

#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::FillSourceBufferL - DONE tick-%d   (this 0x%x)\n"),User::TickCount(),this);
#endif
	}

/**
Runs the clip depending on the current data path and transfer state.

For example, fills the sink buffer if TDataPathState is EPlaying and TTransferState is ENeedSinkData.
*/
void CMMFDataPath2::RunL()
	{
#ifdef _DP_DEBUG 
	RDebug::Print(_L("DP::RunL transfer state %d, iPausedCalled %d, tick-%d   (this 0x%x)\n"),iTransferState, iPauseCalled, User::TickCount(),this);
#endif
	
	switch (iState)
		{
	case EStopped:
		break;
	case EPrimed: // In the paused state we still continue to feed buffers to the sink. The sink (DevSound) handles the logic of whether the buffers should be emptied
	    {
	    if (!iPauseCalled || !iIsUsingResumeSupport)
	        break;
	    }	    
	    // fall-through
	case EPlaying:
	case ERecording:
	case EConverting:
		switch (iTransferState)
			{
		case EWaitSink:
		case EWaitSource:
			break;
		case EInitializeSink:
			InitializeSinkL();
			break;
		case EInitializeSource:
			InitializeSourceL();
			break;
		case ENeedSourceData:
			FillSourceBufferL();
			break;
		case ENeedSinkData:
			FillSinkBufferL();
			break;
		case ENeedToMatchSourceToSink:
			FillSinkBufferL();
			break;
		case ESendDataToSink:
			EmptySinkBufferL();
			break;
		case EEndOfData:
			EndOfData();
			break;
			}
		break;
	default:
		break;
		}
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::RunL DONE\n"));
#endif
	}
	
/* 
 *  FillSourceBufferL
 * 
 *	Function to get data from the datapath's iDataSource
 */

void CMMFDataPath2::FillSourceBufferL()
	{
	__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording || (iState == EPrimed && iPauseCalled && iIsUsingResumeSupport) ), Panic(EMMFDataPathPanicBadState,__LINE__)); 

	//if the silence timer is active then dont propagate the request
	if(iRepeatTrailingSilenceTimer->IsActive())
		{
		return;
		}
	
	//play the silence period and dont propagate the request
	if(iTrailingSilenceLeftToPlay>0 || iVerifyPlayComplete)
		{
		if(iVerifyPlayComplete)//case when the trailing silence is zero
			{
			if (!*iDisableAutoIntent && iDrmSource)
				{
				CMMFFile* file = static_cast<CMMFFile*>(iDrmSource);
				TInt err = file->ExecuteIntent(ContentAccess::EPlay);
				if (err != KErrNone)
					{
					DoSendEventToClient(KMMFEventCategoryPlaybackComplete, err);
					return;
					}
				}
			
			//Retrieve the current play time and add "duration-currentplaytime" to the silence period
			//This is to ensure that silence timer is not started before the previous play is actually completed by the devsound
			TTimeIntervalMicroSeconds currentTime = CalculateAudioOutputPosition();
			if(currentTime.Int64()>iPlayWindowStartPosition.Int64())
				{
				iTimeLeftToPlayComplete = iPlayWindowEndPosition.Int64()-currentTime.Int64();
				}
			else
				{
				iTimeLeftToPlayComplete = 0;
				}

			iVerifyPlayComplete = EFalse;
			}
		if(iTrailingSilenceLeftToPlay==0 && iTimeLeftToPlayComplete==0)
			{
			SetPositionL(iPlayWindowStartPosition);
			iTimeLeftToPlayComplete=-1;
			}
		else
			{
			PlaySilence();
			return;
			}
		}
	
	DoFillSourceBufferL();
	}


/** 
Indicates the data source has filled the specified buffer.

Called by the CMMFDataPath2's MDataSource when it has filled the buffer.

@param aBuffer
       A pointer to the filled buffer.
*/
void CMMFDataPath2::BufferFilledL(CMMFBuffer* aBuffer)
	{	
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::BufferFilledL src has filled buffer %d (ptr=0x%x) with %d bytes EoF = %d  tick-%d    (this 0x%x)\n"),aBuffer->FrameNumber(),aBuffer, aBuffer->BufferSize(),aBuffer->LastBuffer(), User::TickCount(),this);
#endif
	
	TBool isInTruePause = (iState == EPrimed  && iPauseCalled && iIsUsingResumeSupport);
	//state only used if we are passing data
	__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording || isInTruePause), Panic(EMMFDataPathPanicBadState,__LINE__));

	__ASSERT_DEBUG((!iNoMoreSourceData), Panic(EMMFDataPathPanicBadState,__LINE__)); 
	
	//if we have been asked to repeat and this is the last buffer, reset last buffer flag and send to the device
	if(aBuffer!= NULL && aBuffer->LastBuffer())
		{
		iNumberOfTimesPlayed++;
		if ((iNumberOfTimesPlayed <= iNumberOfTimesToRepeat) || iNumberOfTimesToRepeat == KMdaRepeatForever)
			{
			aBuffer->SetLastBuffer(EFalse);
			
			//this will trigger the trailing silence timer next time a buffer is requested.
			iTrailingSilenceLeftToPlay = iTrailingSilence;
			iVerifyPlayComplete = ETrue;
			}
		}		
		
	iSourceBufferWithSource = EFalse;

	//Has the datapath stopped running, if so were not interested in any callbacks.
	if(iState == EStopped || (iState == EPrimed  && !isInTruePause))
		{
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::BufferFilledL called while not expecting callback iState=%d  iPauseCalled=%d  (this 0x%x)\n"),iState, iPauseCalled,this);
#endif
		return;
		}

#ifdef REPOSITION_SPEEDUP
	// if the source has been re-positioned, then go & get some more source data now
	if (!iObtainingAsyncSourceBuffer && iSourceBuffer->FrameNumber() != iCurrentSourceFrameNumber)
		{
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::BufferFilledL source was re-positioned re-requesting source data (this 0x%x)\n"),this);
#endif
		ChangeDataPathTransferState(ENeedSourceData);
		return;
		}
#endif //REPOSITION_SPEEDUP

	//bufer is NULL, indicating no more source data.
	if (!aBuffer)
		{
		//If we only hold a reference to the source buffer, set that to NULL
		if(iSnkBufRef)
			{
			iSourceBuffer = NULL;
			}
		
		iNoMoreSourceData = ETrue;

		if(!iCodec || //there's only one buffer and that has been returned as NULL, so must be end of data
		  iSinkBufferWithSink) //buffer is with sink, we don't have any more data to put in it, so must be end of data
			{
			ChangeDataPathTransferState(EEndOfData);
			}
		else //sink buffer is with datapath, see if there is anything to send to sink
			{
			ChangeDataPathTransferState(ENeedToMatchSourceToSink);
			}
		
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::BufferFilledL DONE aBuffer==NULL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
		return;
		} 

	
	//We were waiting for a response from the source to get an asynchronous buffer.
	//We now have it, and we proceed to transfer this data to the sink.
	if	(iObtainingAsyncSourceBuffer)
		{
		iObtainingAsyncSourceBuffer = EFalse;
		}
	

	aBuffer->SetStatus(EFull);

	if(iSourceBuffer != aBuffer)
		{//buffer has been changed by the source
		iSourceBuffer = aBuffer;
		if (!(iBuffersToUse & ENeedSinkBuffer))
			{//we only need one buffer and use source
			iSinkBuffer = iSourceBuffer;
			iSnkBufRef = ETrue;
			}
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::BufferFilledL - iSourceBuffer=0x%x ref=%d   iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif	
		}
	//Is this the last buffer from the source (0 length or LastBuffer flag set)
	//or have reached the end of the play window; we only look at the play window here
	//if we are converting. For conversion we look at the data we have read. This is then passed onto 
	//the source
	if (!iSourceBuffer->BufferSize() || iSourceBuffer->LastBuffer() ||
		(((iState == EConverting) || (iState == EPlaying)) && (iPlayWindowEndPosition < iCachedSourceDuration) && ( InputPosition() >= iPlayWindowEndPosition ))) 
		{
		iNumberOfTimesPlayed++;
		if ((iNumberOfTimesPlayed <= iNumberOfTimesToRepeat) || iNumberOfTimesToRepeat == KMdaRepeatForever)
			{
			iSourceBuffer->SetLastBuffer(EFalse);
			//this will trigger the trailing silence timer next time a buffer is requested.
			iTrailingSilenceLeftToPlay = iTrailingSilence;
			iVerifyPlayComplete = ETrue;
			}
		else
			{		
	#ifdef _DP_DEBUG
			RDebug::Print(_L("DP::BufferFilledL end of input data  tick-%d   (this 0x%x)\n"),User::TickCount(),this);
			RDebug::Print(_L("iSourceBuffer->BufferSize()=%d\n"),iSourceBuffer->BufferSize());
			RDebug::Print(_L("iSourceBuffer->LastBuffer()=%d\n"),iSourceBuffer->LastBuffer());
			RDebug::Print(_L("InputPosition()=%d  >= iPlayWindowEndPosition=%d\n"),I64INT(InputPosition().Int64()),I64INT(iPlayWindowEndPosition.Int64()));
	#endif
			iNoMoreSourceData = ETrue;
			iSourceBuffer->SetLastBuffer(ETrue); //just in-case we are terminating on BufferSize == 0 or play window
			}
		}


	if (!iCodec)
		{
		ChangeDataPathTransferState(ESendDataToSink);
		}
	else if(!iSinkBufferWithSink) //sink buffer is with data path, can try to fill it
		{
		ChangeDataPathTransferState(ENeedToMatchSourceToSink);	
		}
	//else wait for sink to return buffer BufferEmptied will send us into ENeedToMatchSourceToSink state

#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::BufferFilledL - DONE tick-%d   (this 0x%x)\n"),User::TickCount(),this);
#endif
	}
	
/** 
Sets the data path position.

@param  aPosition
		The data path position.
*/
void CMMFDataPath2::SetPositionL(const TTimeIntervalMicroSeconds& aPosition)
	{//need to map to source position to frame position 
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::SetPositionL = %d  ticks-%d   (this 0x%x)\n"),I64INT(aPosition.Int64()), User::TickCount(),this);
#endif

	if (iState == EStopped)
		{
		User::Leave(KErrNotReady); //can only set position if primed
		}
	
	if(iGetTimePlayedSupported)
		{
		TTimeIntervalMicroSeconds timePlayed(0);
		if(iState == EPlaying && iDataSink->DataSinkType() == KUidMmfAudioOutput)
			{
			CMMFAudioOutput* audioOutput = STATIC_CAST(CMMFAudioOutput*,iDataSink);
			CMMFDevSound& devSound = audioOutput->SoundDevice();
			TInt err= devSound.GetTimePlayed(timePlayed);
			if(err == KErrNone)
				{
				iDevSoundRepositionTime = timePlayed.Int64();
				}
			}
		else
			{
			iDevSoundRepositionTime = 0;
			}
		}
	else //roll back to samplesplayed
		{
		//As this will affect the position, we need to know how many bytes were 
		//played when position was updated. Future Position() requests will
		//then use this refernce to determine the current position.
		iReferenceAudioSamplesPlayed = AudioSamplesPlayed();
		}
	
	// Force the new position to be inside the play window (also within the file duration)
	if ( aPosition < iPlayWindowStartPosition )
		{
		iStartPosition = iPlayWindowStartPosition;
		}
	else if ( aPosition > iPlayWindowEndPosition )
		{
		iStartPosition = iPlayWindowEndPosition; //clearly this will cause nothing to be played
		}
	else
		{
		iStartPosition = aPosition;
		}
	
	TTimeIntervalMicroSeconds interval;

	//can only set the position on an MDataSource that is a format object
	//Note: position defaults to source if both source & sink are clips
	if (iDataSource->DataSourceType() == KUidMmfFormatDecode)
		{
		//position is not beyond the end of file
		interval = ((CMMFFormatDecode*)iDataSource)->FrameTimeInterval(iMediaId);

		// for some reason this code won't compile without these intermediate steps
		TInt64 position = iStartPosition.Int64();
		TInt64 interval64 = interval.Int64();
		if (interval64 == 0)
			User::Leave(KErrDivideByZero); 
		TInt64 datapos64 = position/interval64; 
		iCurrentSourceFrameNumber = I64LOW(datapos64);


        // Try to set the position directly on the format
        TRAP_IGNORE(((CMMFFormatDecode*)iDataSource)->SetPositionL(iStartPosition));
        //NB: don't worry about error, since we'll reposition anyway when we get the next buffer
		}
	else if (iDataSink->DataSinkType() == KUidMmfFormatEncode)
		{			
		//position is not beyond the end of file
		interval = ((CMMFFormatEncode*)iDataSink)->FrameTimeInterval(iMediaId);

		//convert to TUint - for some reason it won't compile without these intermediate steps
		TInt64 position = iStartPosition.Int64();
		TInt64 interval64 = interval.Int64();
		if (interval64 == 0)
			User::Leave(KErrDivideByZero); 
		TInt64 datapos64 = position/interval64; 
		iCurrentSinkFrameNumber = I64LOW(datapos64);


        // Try to set the position directly on the format
        TRAP_IGNORE(((CMMFFormatEncode*)iDataSink)->SetPositionL(iStartPosition));
        //NB: don't worry about error, since we'll reposition anyway when we get the next buffer
		}
	else
		{//can only set position if source or sink is a format
		//If both source and sink are formats position is relative to the source
		User::Leave(KErrNotSupported); //can't set position if neither source nor sink are clips
		}

	if(iCodec) //we have a real codec, must reset it
		{
		iCodec->ResetL(); // Need to preserve sync when resuming play
		}
		
	// Once we've sent the last buffer to the sink it's too late to start
	// changing the state since we may get a RunError(KErrUnderflow) at any time.
	// Once this happens, the sound driver may have unloaded etc..and recovery
	// would be complicated.
	if (iAllDataSentToSink)
		{
		return;
		}
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::SetPosition - Done iCurrentSourceFrameNumber=%d  iStartPosition=%d  ticks-%d   (this 0x%x)\n"),iCurrentSourceFrameNumber, I64INT(iStartPosition.Int64()), User::TickCount(),this);	
#endif
	}

/** 
Gets the data path position.

@return The data path position.
*/
TTimeIntervalMicroSeconds CMMFDataPath2::Position() const
	{
	if ((iState == ERecording) || (iState == EConverting))
		{
		return InputPosition();
		}
	else if(iState == EPlaying)
		{
		return OutputPosition();
		}
	else
		{
		return iStartPosition;
		}
	}

TTimeIntervalMicroSeconds CMMFDataPath2::OutputPosition() const
	{
	TTimeIntervalMicroSeconds interval;
	TTimeIntervalMicroSeconds position;

    if (iDataSink->DataSinkType() == KUidMmfAudioOutput)
        {
		position = CalculateAudioOutputPosition();
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::OutputPosition from audio output= %d\n"),I64INT(position.Int64()));
#endif
        }
	else if (iDataSink->DataSinkType() == KUidMmfFormatEncode)
		{
		//note Encode format position takes priority if both source & sink are formats?
        // try to get the position directly from the format. If that fails, work it out here
        TRAPD(error, position = ((CMMFFormatEncode*)iDataSink)->PositionL());
        if (error)//getting the position from the format didn't work so calculate it here
            {
		    interval = ((CMMFFormatEncode*)iDataSink)->FrameTimeInterval(iMediaId);
			TInt64 position64 = interval.Int64() * iCurrentSinkFrameNumber;
			position = position64;
            }

		TTimeIntervalMicroSeconds duration = CONST_CAST(CMMFDataPath2*,this)->Duration(); //need this to check position doesn't exceed duration
		if (position > duration)//this can happen on last buffer 
			{
			position = duration;
			}
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::OutputPosition  from format= %d\n"),I64INT(position.Int64()));
#endif
		}
	else
		{//can only read output position if sink is a format or an audio output
		return TTimeIntervalMicroSeconds(0);
		}

	return position;
	}
			
TTimeIntervalMicroSeconds CMMFDataPath2::CalculateAudioOutputPosition() const
    {
	//This operation can only be carried out on an Audio Output
	__ASSERT_ALWAYS(iDataSink->DataSinkType() == KUidMmfAudioOutput, Panic(EMMFDataPathPanicProgrammingError,__LINE__));


	//If we are not playing, simply return where we will play from
	if(iState != EPlaying || iCurrentSinkFrameNumber == 0 || iStartPosition == iPlayWindowEndPosition )
		{
		return iStartPosition;
		}
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::CalculateAudioOutputDuration from %d\n"),iReferenceAudioSamplesPlayed);
#endif

	CMMFAudioOutput* audioOutput = STATIC_CAST(CMMFAudioOutput*,iDataSink);
	CMMFDevSound& devSound = audioOutput->SoundDevice();
	
	TTimeIntervalMicroSeconds devSoundTimePlayed(0);
	TInt64 timePlayed(0);
	TInt err = KErrNone;
	if(iGetTimePlayedSupported)
		{
		err= devSound.GetTimePlayed(devSoundTimePlayed);
		if(err == KErrNone)
			{
			timePlayed = devSoundTimePlayed.Int64()-iDevSoundRepositionTime.Int64();
			}
		}
	else //Roll back to SamplesPlayed
		{
		TReal samplesPlayed = AudioSamplesPlayed() - iReferenceAudioSamplesPlayed;
		TMMFCapabilities devSoundConfig = devSound.Config();
		TInt samplingFreq = StreamUtils::SampleRateAsValue(devSoundConfig);
	#ifdef _DP_DEBUG
		RDebug::Print(_L("samplingFreq %d\n"),samplingFreq);
	#endif
		
		TReal timePlayedSeconds = 0;
		if(samplesPlayed)
			{
			timePlayedSeconds = samplesPlayed/samplingFreq;
			}
		timePlayed = I64DOUBLECAST(timePlayedSeconds * 1000000);

	#ifdef _DP_DEBUG
		RDebug::Print(_L("timePlayed %d\n"), I64LOW(timePlayed));
	#endif
		}
	if(err == KErrNone)
		{
		// When Resume is not supported. Datapath simulates Pause() through DevSound's Stop
		// the time played is lost. So we need to saved the last position
		// On the opposite, when Resume is supported the time played returned by DevSound 
		// reflects the real position, so there is no needed to recalculate at least playwindow is being used
		// Finally, if Resume is supported but is not used the position also need to be saved
		if(!iIsUsingResumeSupport || iPlayWindowStartPosition.Int64() > 0)
			{
			timePlayed = timePlayed + iStartPosition.Int64();	
			}
		
		//During repeats. we need to reset the positin manually to playstart once playend is reached
		//this is because the bytes returned by devsound are not accurate in all the cases
		if(iRepeatTrailingSilenceTimer->IsActive() || iTimeLeftToPlayComplete==0)//loop play
			{
			if(iTimeLeftToPlayComplete==0)
				{
				timePlayed = iPlayWindowStartPosition.Int64();
				}
			}
		else if(timePlayed>=(iPlayWindowEndPosition.Int64()+1))
			{
			timePlayed = iPlayWindowStartPosition.Int64();
			}
		}
	
	return TTimeIntervalMicroSeconds(timePlayed);
    }
   
/** 
Stops playing.

Resets datapath position - currently does not clean up buffers. Sends KMMFErrorCategoryDataPathGeneralError 
to the client if an error occurs.
*/
void CMMFDataPath2::Stop()
	{ 
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::Stop current state=%d  tick-%d   (this 0x%x)\n"), iTransferState, User::TickCount(),this);
#endif
	if ((iDataPathCreated)  && (iState != EStopped))
		{ 
		TRAPD(err, DoStopL());
		if (err)
			{
			DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
			}
		}
	}

void CMMFDataPath2::DoStopL()
	{
	CMMFDataPath::DoStopL();
	iRepeatTrailingSilenceTimer->Cancel();
	iIsUsingResumeSupport = EFalse;
	}
	
/** 
Pauses playing.

Sends KMMFErrorCategoryDataPathGeneralError to the client if an error occurs.
*/
void CMMFDataPath2::Pause()
	{
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::Pause, on src buff %d  sink buf %d   (this 0x%x)\n"), iCurrentSourceFrameNumber, iCurrentSinkFrameNumber,this);			
	RDebug::Print(_L("DP::Pause current state=%d  tick-%d    (this 0x%x)\n"),iTransferState, User::TickCount(),this);
#endif

	TRAPD(err, DoPauseL());
	
	if (err)
		{
		DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
		}
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::Pause - DONE tick-%d   (this 0x%x)\n"),User::TickCount(),this);
#endif
	}

EXPORT_C void CMMFDataPath2::PreEmptionPause()
    {
    TRAPD(err, DoPreEmptionPauseL());
    
    if (err)
        {
        DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
        }
    }

/** 
Pauses playing.

Sends KMMFErrorCategoryDataPathGeneralError to the client if an error occurs.
*/
void CMMFDataPath2::DoPreEmptionPauseL()
	{

    if ((iDataPathCreated) && ((iState == EPlaying) || (iState == EConverting) && ( iDataSink->DataSinkType() == KUidMmfAudioOutput )))
        {
        iDataSource->SourcePauseL();        // propagate state change to source
        SetPositionL(Position());
        iIsUsingResumeSupport = EFalse;

        iDataSink->SinkStopL();
	    iPauseCalled = ETrue;               // indicate pause is called
     
	    iState = EPrimed;                   // go back to primed state (we're not playing)
		
		iSinkBufferWithSink = EFalse;
		iSourceBufferWithSource = EFalse;
  
		ResetRefBuffers();                  // buffer references may not be valid any more
  
		Cancel(); //Stop the state machine		
	    }
	if(iState == ERecording)
	     {
	     User::Leave(KErrNotSupported);
	     }
	 iRepeatTrailingSilenceTimer->Cancel();
	}
	
void CMMFDataPath2::DoPauseL()
	{
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::DoPauseL tick-%d   (this 0x%x)\n"),User::TickCount(),this);
#endif


	if ((iDataPathCreated) && ((iState == EPlaying) || (iState == EConverting)))
		{
		if (iDataSink->DataSinkType() == KUidMmfAudioOutput && iIsResumeSupported)
			{
			iDataSink->SinkPauseL();
			// When true pause is supported only the datapath's position 
			// should be updated, MDataSource position should be changed
			iStartPosition = Position();
			iIsUsingResumeSupport = ETrue;
			// If we wait for the sink to complete play, then we do not proceed with supplying the buffers to the sink
			// In this case we need to reset the buffers so that InitializeSinkL won't attempt bringing in new ones
			if (iTransferState == EWaitSink)
				{
				ResetRefBuffers();
				}
			}
		else
			{
			// If we use resume support, then there is no need to pause source as we would continue to supply buffers to the sink
			// Here we are not using resume support, thus we're pausing the source
			iDataSource->SourcePauseL();		
			SetPositionL(Position());
			iDataSink->SinkStopL();
			ResetRefBuffers();                  // buffer references may not be valid any more
  
			Cancel(); //Stop the state machine					
			}
		iPauseCalled = ETrue;				// indicate pause is called
		
		iState = EPrimed;					// go back to primed state (we're not playing)
		}
	else if(iState == ERecording)
		{
		iPauseCalled = ETrue;
#ifdef _DP_DEBUG
		RDebug::Print(_L("DP::DoPauseL Recording, pause datasource\n"));
#endif
		iDataSource->SourcePauseL();
		}
	iRepeatTrailingSilenceTimer->Cancel();
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::DoPauseL - Done iReferenceAudioSamplesPlayed = %d\n"),iReferenceAudioSamplesPlayed);
	RDebug::Print(_L("DP::DoPauseL - Done restart at %d tick-%d   (this 0x%x)\n"),I64INT(iStartPosition.Int64()), User::TickCount(),this);
#endif
	}

/**
Cancels the silence timer.
*/
void CMMFDataPath2::DoCancel()
	{
	//cancel repeats
	iRepeatTrailingSilenceTimer->Cancel();
	iNumberOfTimesToRepeat=0;
	}

/**
Allocates buffers in preparation to play.

Must be called before calling PlayL().

iSnkBufRef and iSrcBufRef contain ETrue if these buffers are created and owned by a MDataSource or MDataSink
For clean-up purposes, datapath only cleans up buffers allocated directly by PrimeL().
*/
void CMMFDataPath2::PrimeL()
	{
	CMMFDataPath::PrimeL();
	if(iDataSink->DataSinkType() == KUidMmfAudioOutput)
		{
		CMMFAudioOutput* audioOutput = STATIC_CAST(CMMFAudioOutput*,iDataSink);
		CMMFDevSound& devSound = audioOutput->SoundDevice();
		iGetTimePlayedSupported = devSound.IsGetTimePlayedSupported();
		iIsResumeSupported = devSound.IsResumeSupported();
		iIsUsingResumeSupport = EFalse;
		}
	}

/**
Starts an active scheduler 'play' loop.

Can only play from the primed state.
*/
void CMMFDataPath2::PlayL()
	{

#if defined(__PROFILING)
	RDebug::ProfileEnd(1);
#endif  // defined(__PROFILING)

#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::PlayL, on src buff %d  sink buf %d (this 0x%x)\n"), iCurrentSourceFrameNumber, iCurrentSinkFrameNumber,this);		
	RDebug::Print(_L("iStartPosition = %d\n"), I64INT(iStartPosition.Int64()));		
#endif

	if ((iDataPathCreated) && (iState == EPrimed))
		{
		//can only play from the primed state

		if(iPauseCalled) //sink and source will have been stopped, and we will not have been re-primed
			{
			iDataSink->SinkPrimeL(); //propagate change down to sink
			iPauseCalled = EFalse;
			}

		iCurrentSourceFrameNumber = 0; //reset to beginning
		iCurrentSinkFrameNumber = 0; //reset to beginning

		iSourceBufferWithSource = EFalse;
		iSinkBufferWithSink = EFalse;

		iNoMoreSourceData = EFalse;
		iAllDataSentToSink=EFalse;
		iDataPathCompletedErrorCode=KErrNone;

		if(!iIsResumeSupported || !iIsUsingResumeSupport)
			{
			SetPositionL( iStartPosition );
			}
		iReferenceAudioSamplesPlayed = 0;
		iReferenceAudioSamplesRecorded = 0;
		
		//complete a request on iStatus to invoke play code
		iDataSource->SourcePlayL(); //propagate state change to source

		// This need to be done always since CMMFAudioOutput::EmptyBuffer
		// doesn't start playback anymore
		iDataSink->SinkPlayL(); //propogate state change to sink

		if (iDataSink->DataSinkType() == KUidMmfAudioOutput)
			iState = EPlaying;
		else if(iDataSource->DataSourceType() == KUidMmfAudioInput)
			iState = ERecording;
		else
			iState = EConverting;

		//need to re-initialize any buffer(s) that we only own references to
		ChangeDataPathTransferState(EInitializeSink);
		}
	iDevSoundRepositionTime = 0;
	if(!iRetainRepeatInfo)
		{
		iNumberOfTimesPlayed = 0;
		iTimeLeftToPlayComplete = -1;
		iVerifyPlayComplete = EFalse;
		}
	iRetainRepeatInfo = EFalse;
#ifdef _DP_DEBUG
	RDebug::Print(_L("DP::Play - DONE\n"));		
#endif
	}

/**
Deletes buffers if this datapath's sources and sinks own the buffers returned by PrimeL().
Typically if buffers are created asychronously, the datapath doesn't own the buffer
so leaves cleanup handling to the owner sources/sinks.

Called when source and sink needs to be de-referenced. Sets iDataPathCreated, iSinkCanReceive, 
iSnkBufRef and iSrcBufRef to EFalse; sets iState to EStopped.
*/
void CMMFDataPath2::ResetL()
	{
	CMMFDataPath::ResetL();
	iDrmSource = NULL;
	}

/**
Sets the number of times the audio sample is to be repeated during the
playback operation.

A period of silence can follow each playing of the sample. The audio
sample can be repeated indefinitely.

@param   aRepeatNumberOfTimes
         The number of times the audio sample, together with
         the trailing silence, is to be repeated. If this is
         set to KMdaRepeatForever, then the audio
         sample, together with the trailing silence, is
         repeated indefinitely or until Stop() is
         called. If this is set to zero, then the audio sample
         is not repeated.
@param   aTrailingSilence
         The time interval of the trailing silence in microseconds.

*/
EXPORT_C void CMMFDataPath2::SetRepeats(TInt aRepeatNumberOfTimes, const TTimeIntervalMicroSeconds& aTrailingSilence)
	{
	iNumberOfTimesToRepeat=aRepeatNumberOfTimes;
	iTrailingSilence=aTrailingSilence;
	}
	
/**
Sets the Drm file source and the automatic execute intent flag. This method is used by the controller plugin
to pass these to the datapath in order to execute the play intent during loop play.

@param  aSource
        Data Source on which the play intent needs to be executed. This is usually the CMMFFile source
@param  aDisableAutoIntent
		Boolean variable which states whether the controller plugin or the datapath needs to execute play intent
		automatically or not.
*/	
EXPORT_C void CMMFDataPath2::SetDrmProperties(MDataSource* aSource, TBool *aDisableAutoIntent)
	{
	iDrmSource = aSource;
	iDisableAutoIntent = aDisableAutoIntent;
	}

/**
This call indicates PlayL not to reset the iNumberOfTimesPlayed property. This method is used by the controller plugin
during repositioning. PlayL call during seeking should not reset the iNumberOfTimesPlayed property.
*/
EXPORT_C void CMMFDataPath2::RetainRepeatInfo()
	{
	iRetainRepeatInfo = ETrue;
	}
