upnpmpxplugins/upnpplaybackplugins/src/upnpplaybackstatemachine.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 17 Dec 2009 08:52:00 +0200
changeset 0 7f85d04be362
permissions -rw-r--r--
Revision: 200947 Kit: 200951

/*
* 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 ); 
     }