diff -r 000000000000 -r 7f85d04be362 upnpmpxplugins/upnpplaybackplugins/src/upnpplaybackstatemachine.cpp --- /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 +#include +#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 ); + } +