--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/upnpmpxplugins/upnpplaybackplugins/src/upnpplaybackstatemachine.cpp Thu Dec 17 08:52:00 2009 +0200
@@ -0,0 +1,796 @@
+/*
+* Copyright (c) 2008 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: Class for retrieving and selecting media renderers
+*
+*/
+
+
+
+
+
+
+// INCLUDES
+#include <mpxplaybackplugin.h>
+#include <mpxplaybackpluginobserver.h>
+#include "upnpavrenderingsession.h"
+#include "upnpavrenderingsessionobserver.h"
+#include "upnpavdevice.h"
+
+#include "upnpmusicplayer.h" // for parent.Observer()
+#include "upnptrack.h" // for TrackDuration
+#include "upnpvaluestatemachine.h" //
+#include "upnpplaybackstatemachine.h" // myself
+#include "upnppluginserrortranslation.h"
+
+_LIT( KComponentLogfile, "musicplugins.txt");
+#include "upnplog.h"
+
+// CONSTANTS
+_LIT( KStateStoppedText, "Stopped" );
+_LIT( KStatePlayingText, "Playing" );
+_LIT( KStatePausedText, "Paused" );
+_LIT( KStateUnknownText, "Unknown" );
+
+const TInt KDurationErrorMargin = 500; // 0.5 seconds
+const TInt KPlaybackInfoTimeOut = 1000000; // 1s wait until device is ready
+const TInt KPlaybackInfoTimeOutEnd = 2000000; // 2s wait check whether
+ //the playing is ended
+
+// ======== MEMBER FUNCTIONS ========
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::NewL
+// 1st phase constructor.
+// --------------------------------------------------------------------------
+//
+CUPnPPlaybackStateMachine* CUPnPPlaybackStateMachine::NewL(
+ CUPnPMusicPlayer& aParent,
+ MUPnPAVRenderingSession& aRenderingSession )
+ {
+ __LOG( "PlaybackStateMachine::NewL" );
+ CUPnPPlaybackStateMachine* self =
+ new(ELeave) CUPnPPlaybackStateMachine(
+ aParent, aRenderingSession );
+ CleanupStack::PushL( self );
+ self->ConstructL();
+ CleanupStack::Pop( self );
+ return self;
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::CUPnPPlaybackStateMachine
+// Default constructor.
+// --------------------------------------------------------------------------
+//
+CUPnPPlaybackStateMachine::CUPnPPlaybackStateMachine(
+ CUPnPMusicPlayer& aParent,
+ MUPnPAVRenderingSession& aRenderingSession )
+ :iParent( aParent )
+ ,iRendererSession( aRenderingSession )
+ ,iState( EStateStopped )
+ ,iCurrentOperation( EOperationNone )
+ {
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::ConstructL
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::ConstructL()
+ {
+ __LOG( "CUPnPPlaybackStateMachine::ConstructL" );
+
+ // Create timer for observing duration query time out.
+ TInt64 iTime = 0;
+ iPlayMark = TTime( iTime );
+ iPauseMark = TTime( iTime );
+ iPeriodizer = CUPnPMusicPeriodizer::NewL( *this, KPlaybackInfoTimeOut );
+ iPeriodizerEnd = CUPnPMusicPeriodizer::NewL( *this,
+ KPlaybackInfoTimeOutEnd );
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::~CUPnPPlaybackStateMachine
+// Destructor.
+// --------------------------------------------------------------------------
+//
+CUPnPPlaybackStateMachine::~CUPnPPlaybackStateMachine()
+ {
+ __LOG( "PlaybackStateMachine destructor" );
+ iOperationQueue.Close();
+ delete iPeriodizer;
+ delete iPeriodizerEnd;
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::CommandL
+// Executes a command on the selected song.
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::CommandL( TMPXPlaybackCommand aCmd )
+ {
+ if ( aCmd == EPbCmdClose )
+ {
+ // close can be handled parallel to other commands
+ HandleCloseL();
+ return;
+ }
+
+ if ( !iCurrentOperation.None() )
+ {
+ __LOG( "PlaybackStateMachine::CommandL - Append to queue" );
+ iOperationQueue.AppendL( aCmd );
+ return;
+ }
+
+ switch( aCmd )
+ {
+ case EPbCmdPlay:
+ {
+ if( iState == EStatePlaying )
+ {
+ // already playing - ignore
+ CheckOperationInQueueL();
+ }
+ else
+ {
+ __LOG( "PlaybackStateMachine: command play" );
+ iCurrentOperation = aCmd;
+ iRendererSession.PlayL();
+ }
+ }
+ break;
+ case EPbCmdPause:
+ {
+ // Check if pause is supported by device
+ if( iParent.UsedRendererDevice().PauseCapability() )
+ {
+ if( iState == EStatePaused )
+ {
+ // already paused - ignore
+ CheckOperationInQueueL();
+ }
+ else
+ {
+ __LOG( "PlaybackStateMachine: command pause" );
+ iCurrentOperation = aCmd;
+ iRendererSession.PauseL();
+ }
+ }
+ else
+ {
+ __LOG( "PlaybackStateMachine: Pause is not supported by \
+ device!" );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPaused, 0,
+ KErrNotSupported );
+ }
+ }
+ break;
+ case EPbCmdStop:
+ {
+ if( iState == EStateStopped )
+ {
+ // already stopped - ignore
+ CheckOperationInQueueL();
+ }
+ else
+ {
+ __LOG( "PlaybackStateMachine: command stop" );
+ iCurrentOperation = aCmd;
+ iRendererSession.StopL();
+ }
+ }
+ break;
+ case EPbCmdReplay:
+ {
+ iCurrentOperation = aCmd;
+ __LOG( "PlaybackStateMachine: command replay" );
+ if( iState == EStatePlaying || iState == EStatePaused )
+ {
+ // playing -must stop first
+ iRendererSession.StopL();
+ }
+ else
+ {
+ // just play
+ iRendererSession.PlayL();
+ }
+ }
+ break;
+ default: // Given command is not supported
+ {
+ __LOG( "PlaybackStateMachine: command default?" );
+ CheckOperationInQueueL();
+ User::Leave( KErrNotSupported );
+ break;
+ }
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::PositionL
+// Changes the position within the currently playing track
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::PositionL( TInt aPosition )
+ {
+
+ // Ignore if already called.
+ if( iCurrentOperation == EOperationPositionToZero )
+ {
+ __LOG( "PlaybackStateMachine::PositionL - \
+ EOperationPositionToZero called twice -> ignored" );
+ return;
+ }
+
+ if ( !iCurrentOperation.None() )
+ {
+ __LOG( "PlaybackStateMachine::PositionL - Append to queue" );
+ iOperationQueue.AppendL( EOperationPositionToZero );
+ return;
+ }
+ if ( aPosition != 0 )
+ {
+ CheckOperationInQueueL();
+ User::Leave( KErrNotSupported );
+ }
+
+ // handle Position(0)
+ if ( iState == EStatePlaying )
+ {
+ __LOG( "PlaybackStateMachine: position(0) while playing" );
+ iCurrentOperation = EOperationPositionToZero;
+ iRendererSession.StopL();
+ }
+ else if ( iState == EStatePaused )
+ {
+ __LOG( "PlaybackStateMachine: PositionL(0) while paused" );
+ iCurrentOperation = EOperationPositionToZeroDuringPause;
+ iRendererSession.StopL();
+ }
+ else if ( iState == EStateStopped )
+ {
+ __LOG( "PlaybackStateMachine: PositionL(0) while stopped" );
+ CheckOperationInQueueL();
+
+ // immediate response !
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPSetComplete,
+ EPbPropertyPosition, KErrNone );
+
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::SilentStopL
+// Stops playback, does not provide any ACK event
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::SilentStopL()
+ {
+ if ( !iCurrentOperation.None() )
+ {
+ __LOG( "PlaybackStateMachine::SilentStopL - Append to queue" );
+ iOperationQueue.AppendL( EOperationSilentStop );
+ return;
+ }
+
+ // handle stop
+ __LOG( "PlaybackStateMachine: SilentStopL" );
+ if( iState == EStateStopped )
+ {
+ // already stopped - ignore
+ CheckOperationInQueueL();
+ }
+ else
+ {
+ iCurrentOperation = EOperationSilentStop;
+ iRendererSession.StopL();
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::Cancel
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::Cancel()
+ {
+ // reset current operation and empty the queue -> no callbacks.
+ iCurrentOperation.Reset();
+ iOperationQueue.Reset();
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::HandleCloseL
+// Handles the close command
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::HandleCloseL()
+ {
+ if ( iCurrentOperation == EPbCmdStop )
+ {
+ // Stop pending - it can't be completed -> fake callback.
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPStopped,
+ KErrNone, KErrNone );
+ }
+
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPClosed,
+ KErrNone, KErrNone );
+ }
+
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::InteractOperationComplete
+// Response for interaction operation (play, stop, etc.).
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::InteractOperationComplete( TInt aErrorCode,
+ TUPnPAVInteractOperation aOperation )
+ {
+ __LOG1( "CUPnPPlaybackStateMachine::InteractOperationComplete: err:[%d]",
+ aErrorCode );
+ aErrorCode = TUpnpPluginsErrorTranslation::ErrorTranslate( aErrorCode );
+ switch( aOperation )
+ {
+ case EUPnPAVPlayUser:
+ {
+ __LOG1( "PlaybackStateMachine: event play user, state[%d]",iState );
+
+ if ( aErrorCode == KErrNone )
+ {
+ if ( iState == EStatePaused )
+ {
+ TimeContinue();
+ ChangeState( EStatePlaying );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPlaying, KErrNone,
+ aErrorCode );
+ if( iCurrentOperation == EPbCmdPlay )
+ {
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ }
+ }
+ else if ( iState == EStatePlaying )
+ {
+ iPeriodizer->Stop();
+ iParent.HandlePlayStarted();
+ }
+ else if ( iState == EStateStopped )
+ {
+ if ( iCurrentOperation == EPbCmdPlay ||
+ iCurrentOperation == EPbCmdReplay )
+ {
+ TimePlay();
+ iParent.HandlePlayStarted();
+ ChangeState( EStatePlaying );
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPlaying, KErrNone,
+ aErrorCode );
+ }
+ else
+ {
+ // spontaneous change to PLAYING state -> ignore
+ }
+ }
+
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+ break;
+ }
+ case EUPnPAVPlay:
+ {
+ if ( iState == EStatePlaying )
+ {
+ __LOG( "PlaybackStateMachine: event play ignored" );
+ break;
+ }
+
+ __LOG( "PlaybackStateMachine: event play" );
+
+ if ( aErrorCode == KErrNone )
+ {
+ if ( iState == EStateStopped )
+ {
+ TimePlay();
+ iPeriodizer->Start();
+ iPeriodizerEnd->Start();
+ }
+ else if ( iState == EStatePaused )
+ {
+ TimeContinue();
+ iPeriodizer->Stop();
+ iPeriodizer->Start();
+ iPeriodizerEnd->Stop();
+ iPeriodizerEnd->Start();
+ }
+ ChangeState( EStatePlaying );
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ break;
+ }
+ case EUPnPAVPauseUser:
+ {
+ if ( !iCurrentOperation.None() ||
+ iState == EStatePaused )
+ {
+ __LOG( "PlaybackStateMachine: event pause user ignored" );
+ break;
+ }
+ __LOG( "PlaybackStateMachine: event pause user" );
+
+ if( aErrorCode == KErrNone )
+ {
+ TimePause();
+ ChangeState( EStatePaused );
+ iPeriodizerEnd->Stop();
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+
+ // Call callback
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPaused, KErrNone,
+ aErrorCode );
+ break;
+ }
+
+ case EUPnPAVPause:
+ {
+ __LOG( "PlaybackStateMachine: event pause" );
+
+ TInt err = aErrorCode;
+ if( aErrorCode == KErrNone )
+ {
+ TimePause();
+ ChangeState( EStatePaused );
+ iPeriodizerEnd->Stop();
+ }
+ else if( aErrorCode == KErrNotSupported )
+ {
+ // Pause is supported (pause capability checked in CommandL )
+ // but device is not ready.
+ err = KErrNotReady;
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+
+ if ( iCurrentOperation == EPbCmdPause )
+ {
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPaused, KErrNone,
+ err );
+ }
+ else
+ {
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ }
+ break;
+ }
+ case EUPnPAVStopUser:
+ {
+ iPeriodizerEnd->Stop();
+ if ( !iCurrentOperation.None() ||
+ iState == EStateStopped )
+ {
+ __LOG( "PlaybackStateMachine: event stop user ignored" );
+ break;
+ }
+ __LOG( "PlaybackStateMachine: event stop user" );
+
+ TBool trackComplete = TimeStop();
+ if ( aErrorCode == KErrNone )
+ {
+ ChangeState( EStateStopped );
+ iParent.HandlePlayComplete();
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+
+ // Call callback
+ if ( trackComplete )
+ {
+ __LOG( "PlaybackStateMachine: play complete event to user" );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPlayComplete, KErrNone,
+ aErrorCode );
+ }
+ else
+ {
+ __LOG( "PlaybackStateMachine: play stopped event to user" );
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPStopped, KErrNone,
+ aErrorCode );
+ }
+ break;
+ }
+ case EUPnPAVStop:
+ {
+ iPeriodizerEnd->Stop();
+ if ( iState == EStateStopped )
+ {
+ // already in this state - ignore
+ break;
+ }
+ __LOG( "PlaybackStateMachine: event stop" );
+
+ if ( aErrorCode == KErrNone )
+ {
+ ChangeState( EStateStopped );
+ }
+ else
+ {
+ ChangeState( EStateUnknown );
+ }
+
+ if ( iCurrentOperation == EPbCmdStop )
+ {
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ // Call callback
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPStopped, KErrNone,
+ aErrorCode );
+ }
+ else if ( iCurrentOperation == EPbCmdReplay ||
+ iCurrentOperation == EOperationPositionToZero )
+ {
+ // continue with play
+ TRAP_IGNORE( iRendererSession.PlayL() );
+ }
+ else if ( iCurrentOperation ==
+ EOperationPositionToZeroDuringPause )
+ {
+ // position zero complete
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPSetComplete,
+ EPbPropertyPosition, aErrorCode );
+ }
+ else
+ {
+ // Note: covers also EOperationSilentStop
+ iCurrentOperation.Reset();
+ TRAP_IGNORE( CheckOperationInQueueL() );
+ }
+ break;
+ }
+ default:
+ {
+ __LOG( "PlaybackStateMachine: event default?" );
+ __PANICD( __FILE__, __LINE__ );
+ break;
+ }
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::CheckOperationInQueueL
+// Checks if operations are in the queue, and executes
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::CheckOperationInQueueL()
+ {
+ if ( !iCurrentOperation.None() )
+ {
+ // check operation though a current operation exists!
+ __PANICD( __FILE__, __LINE__ );
+ return;
+ }
+
+ if ( iOperationQueue.Count() > 0 )
+ {
+ TOperation op = iOperationQueue[0];
+ iOperationQueue.Remove(0);
+ if ( op == EOperationCommand )
+ {
+ CommandL( op.iCmd );
+ }
+ else if ( op == EOperationPositionToZero )
+ {
+ PositionL( 0 );
+ }
+ else
+ {
+ __PANICD( __FILE__, __LINE__ );
+ }
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::TimePlay
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::TimePlay()
+ {
+ iPlayMark.UniversalTime();
+ iPausetime = 0;
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::TimePause
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::TimePause()
+ {
+ iPauseMark.UniversalTime();
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::TimeContinue
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::TimeContinue()
+ {
+ TTime continueMark;
+ continueMark.UniversalTime();
+ TTimeIntervalMicroSeconds paused =
+ continueMark.MicroSecondsFrom( iPauseMark );
+ iPausetime += ( paused.Int64() / 1000 );
+ }
+
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::TimeStop
+// --------------------------------------------------------------------------
+//
+TBool CUPnPPlaybackStateMachine::TimeStop()
+ {
+ TBool isCompleted = ETrue;
+ if ( !iPlayMark.Int64() )
+ {
+ isCompleted = EFalse;
+ return isCompleted;
+ }
+ TTime stopMark;
+ stopMark.UniversalTime();
+ TTimeIntervalMicroSeconds played =
+ stopMark.MicroSecondsFrom( iPlayMark );
+
+ TInt duration = iParent.Track().TrackDuration();
+ TInt playtime = ( played.Int64() / 1000 ) - iPausetime;
+ __LOG3("PlaybackStateMachine: playtime=%d duration=%d (pausetime=%d)",
+ playtime/1000, duration/1000, iPausetime/1000 );
+
+ if ( playtime >= 0 &&
+ playtime < duration - KDurationErrorMargin )
+ {
+ // [0 - duration-margin]
+ isCompleted= EFalse;
+ }
+ else if ( playtime >= duration - KDurationErrorMargin &&
+ playtime <= duration + KDurationErrorMargin )
+ {
+ // [duration-margin - duration+margin]
+ isCompleted= ETrue;
+ }
+ else
+ {
+ // position either negative or greater than duration ??
+ __LOG2("Time ERROR: play=%d duration=%d ?", playtime, duration );
+ isCompleted= ETrue;
+ }
+
+ return isCompleted;
+ }
+
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::ChangeState
+// Changes the class state
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::ChangeState( TState aNewState )
+ {
+ __LOG2( "PlaybackStateMachine: STATE %S -> %S",
+ State( iState ), State( aNewState ) );
+ iState = aNewState;
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::State
+// --------------------------------------------------------------------------
+//
+const TDesC* CUPnPPlaybackStateMachine::State( TState aState )
+ {
+ switch( aState )
+ {
+ case EStateStopped:
+ return &KStateStoppedText;
+ case EStatePlaying:
+ return &KStatePlayingText;
+ case EStatePaused:
+ return &KStatePausedText;
+ default:
+ return &KStateUnknownText;
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::HandlePeriod
+// Action when timer has expired.
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::HandlePeriod()
+ {
+ __LOG( "CUPnPPlaybackStateMachine::HandlePeriod" );
+
+ // Remote device is ready for give playback information.
+ iPeriodizer->Stop();
+ iParent.HandlePlayStarted();
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::HandlePeriodForEnd
+// Action when timer has expired.
+// --------------------------------------------------------------------------
+//
+void CUPnPPlaybackStateMachine::HandlePeriodForEnd()
+ {
+ TBool trackComplete = TimeStop();
+ if ( trackComplete )
+ {
+ iPeriodizerEnd->Stop();
+ PlayOvertimeEnd();
+ }
+ else
+ {
+ iPeriodizerEnd->Start();
+ }
+ }
+
+// --------------------------------------------------------------------------
+// CUPnPPlaybackStateMachine::PlayOvertimeEnd
+// Stoping the playing song.
+// --------------------------------------------------------------------------
+//
+ void CUPnPPlaybackStateMachine::PlayOvertimeEnd()
+ {
+ if ( !iCurrentOperation.None() ||
+ iState == EStateStopped || iState == EStateUnknown)
+ {
+ return;
+ }
+ ChangeState( EStateStopped );
+ iParent.HandlePlayComplete();
+ iParent.Observer().HandlePluginEvent(
+ MMPXPlaybackPluginObserver::EPPlayComplete, KErrNone,
+ KErrNone );
+ }
+