diff -r 6369bfd1b60d -r 08b5eae9f9ff upnpavcontroller/upnprenderingstatemachine/src/upnpvolumestatemachine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/upnpavcontroller/upnprenderingstatemachine/src/upnpvolumestatemachine.cpp Wed Nov 03 11:45:09 2010 +0200 @@ -0,0 +1,835 @@ +/* +* Copyright (c) 2007,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: Implementation of generic upnp volume state machine +* +*/ + + +// INCLUDES +// avcontroller api +#include +#include // for device.VolumeCapability() +// volume state machine +#include "upnprenderingstatemachineconstants.h" // option flags +#include "upnpvolumestatemachineobserver.h" +#include "upnpvolumestatemachine.h" + +_LIT( KComponentLogfile, "upnprenderingengine.txt"); +#include "upnplog.h" + +// CONSTANTS +const TInt KCancelTimeMaximum = 3000000; +const TInt KCancelTimeResolution = 500000; + +// ======== MEMBER FUNCTIONS ======== + + +TUpnpVSMQueueItem::TUpnpVSMQueueItem( + const TUpnpVSMQueueItem::TPropertyType aProperty, const TInt aValue ) + : iProperty( aProperty ) + , iValue( aValue ) + { + } + +TUpnpVSMQueueItem::TPropertyType TUpnpVSMQueueItem::Property() const + { + return iProperty; + } + +TInt TUpnpVSMQueueItem::Value() const + { + return iValue; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::NewL +// Static constructor. +// -------------------------------------------------------------------------- +// +EXPORT_C CUpnpVolumeStateMachine* CUpnpVolumeStateMachine::NewL( + MUPnPAVRenderingSession& aSession ) + { + CUpnpVolumeStateMachine* self = + new (ELeave) CUpnpVolumeStateMachine( aSession ); + // no 2nd phase construction needed + return self; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::CUpnpVolumeStateMachine +// Default constructor. +// -------------------------------------------------------------------------- +// +CUpnpVolumeStateMachine::CUpnpVolumeStateMachine( + MUPnPAVRenderingSession& aSession ) + : iSession( aSession ) + { + __LOG( "VolumeStateMachine: constructor" ); + iVolumeCapability = iSession.Device().VolumeCapability(); + iMuteCapability = iSession.Device().MuteCapability(); + iState = EOffSync; + iCurrentVolume = KErrNotFound; + iVolumeToSetAfterMute = KErrNotFound; + iCurrentMute = KErrNotFound; + iMuteRequestedByClient = EFalse; + iCachedVolume = KErrNotFound; + iCachedMute = KErrNotFound; + } + + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::~CUpnpVolumeStateMachine +// Destructor. +// -------------------------------------------------------------------------- +// +EXPORT_C CUpnpVolumeStateMachine::~CUpnpVolumeStateMachine() + { + __LOG( "VolumeStateMachine: destructor" ); + } + + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SetOptions +// sets the option flags for this state machine +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SetOptions( TInt aOptions ) + { + iOptions = aOptions; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::Options +// -------------------------------------------------------------------------- +// +EXPORT_C TInt CUpnpVolumeStateMachine::Options() const + { + return iOptions; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SetObserver +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SetObserver( + MUpnpVolumeStateMachineObserver& aObserver ) + { + iObserver = &aObserver; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::RemoveObserver +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::RemoveObserver() + { + iObserver = NULL; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SyncL +// Synchronises this state machine with the renderer +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SyncL() + { + __LOG( "VolumeStateMachine: SyncL" ); + + if ( iState != EOffSync && iState != EIdle ) + { + __LOG( "VolumeStateMachine: not synchronizing" ); + return; + } + + if ( !iVolumeCapability ) + { + User::Leave( KErrNotSupported ); + } + + iSession.GetVolumeL(); + iState = ESyncing; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SetOffSync +// Forces the state machine off sync +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SetOffSync() + { + __ASSERTD( !IsBusy(), __FILE__, __LINE__ ); + iState = EOffSync; + iCurrentVolume = KErrNotFound; + iVolumeToSetAfterMute = KErrNotFound; + iCurrentMute = KErrNotFound; + iMuteRequestedByClient = EFalse; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::IsInSync +// -------------------------------------------------------------------------- +// +EXPORT_C TBool CUpnpVolumeStateMachine::IsInSync() const + { + return iState != EOffSync; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SetVolumeL +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SetVolumeL( TInt aVolume ) + { + __LOG1( "VolumeStateMachine: SetVolumeL %d", aVolume ); + __ASSERTD( IsInSync(), __FILE__, __LINE__ ); + if ( !iVolumeCapability ) + { + User::Leave( KErrNotSupported ); + } + + if ( IsBusy() ) + { + PushIntoQueueL( TUpnpVSMQueueItem::EVolume, aVolume ); + return; + } + + // check volume is between given limits + aVolume = Max( aVolume, KVolumeMin ); + aVolume = Min( aVolume, KVolumeMax ); + + if ( aVolume == KVolumeMin && iCurrentVolume > 0 && + iMuteCapability && iCurrentMute == KMuteOff && + iOptions & Upnp::EConvertVolumeZeroToMute ) + { + // adjust volume to zero (using mute) + iSession.SetMuteL( ETrue ); + iState = EAdjustingVolumeToZero; + } + else if ( aVolume == KVolumeMin && iCurrentMute == KMuteOn ) + { + // no need to change volume. Call back directly. + if ( iObserver ) + { + iObserver->VolumeChanged( + KErrNone, iCurrentVolume, ETrue ); + } + } + else if ( aVolume > KVolumeMin && iCurrentMute == KMuteOn && + !iMuteRequestedByClient ) + { + // volume is at zero, and renderer is muted. + // unmute and after that change volume if needed + iVolumeToSetAfterMute = aVolume; + iSession.SetMuteL( EFalse ); + iState = EAdjustingVolumeFromZero; + } + else if ( aVolume != iCurrentVolume ) + { + // adjust volume normally + iSession.SetVolumeL( aVolume ); + iState = EAdjustingVolume; + } + else + { + // no need to change volume. Call back directly. + if ( iObserver ) + { + iObserver->VolumeChanged( + KErrNone, iCurrentVolume, ETrue ); + } + } + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::Volume +// -------------------------------------------------------------------------- +// +EXPORT_C TInt CUpnpVolumeStateMachine::Volume() const + { + __ASSERTD( IsInSync(), __FILE__, __LINE__ ); + TInt vol = iCurrentVolume; + if ( iCurrentMute && (iOptions & Upnp::EConvertVolumeZeroToMute) ) + { + vol = KVolumeMin; + } + return vol; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::SetMuteL +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::SetMuteL( TBool aMuteState ) + { + __LOG1( "VolumeStateMachine: SetMuteL", aMuteState ); + __ASSERTD( IsInSync(), __FILE__, __LINE__ ); + if ( !iMuteCapability ) + { + // Could implement here FAKE MUTE SUPPORT by converting mute + // to Volume(0). However there is no need to that since no clients + // use the mute feature. + User::Leave( KErrNotSupported ); + } + if ( IsBusy() ) + { + PushIntoQueueL( TUpnpVSMQueueItem::EMute, aMuteState ); + return; + } + + // check mute state is between given limits + aMuteState = Max( aMuteState, KMuteOff ); + aMuteState = Min( aMuteState, KMuteOn ); + + if ( aMuteState && !iCurrentMute ) + { + // mute + iSession.SetMuteL( ETrue ); + iState = EAdjustingMute; + } + else if ( !aMuteState && iCurrentMute ) + { + // unmute + iSession.SetMuteL( EFalse ); + iState = EAdjustingMute; + } + else + { + // no change in mute state. Call back directly. + if ( iObserver ) + { + iObserver->MuteChanged( + KErrNone, iCurrentMute, ETrue ); + } + } + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::Mute +// -------------------------------------------------------------------------- +// +EXPORT_C TBool CUpnpVolumeStateMachine::Mute() const + { + __ASSERTD( IsInSync(), __FILE__, __LINE__ ); + return ( (iCurrentMute == KMuteOn) ? KMuteOn : KMuteOff ); + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::IsBusy +// -------------------------------------------------------------------------- +// +EXPORT_C TBool CUpnpVolumeStateMachine::IsBusy() const + { + return iState != EIdle; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::HasVolumeCapability +// tests if renderer has volume capability +// -------------------------------------------------------------------------- +// +EXPORT_C TBool CUpnpVolumeStateMachine::HasVolumeCapability() const + { + return iVolumeCapability; + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::Cancel +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::Cancel() + { + // actually we can not cancel anything, but providing the interface for + // future purposes + if ( iState != EIdle ) + { + __LOG1( "VolumeStateMachine: Canceling in state %d", iState ); + iState = ECancelled; + for ( TInt t = KCancelTimeMaximum; + t > 0 && iState != EIdle; + t -= KCancelTimeResolution ) + { + User::After( TTimeIntervalMicroSeconds32( + KCancelTimeResolution ) ); + } + __LOG1( "VolumeStateMachine: state after cancel: %d", iState ); + } + } + +void CUpnpVolumeStateMachine::HandleVolumeResultInSyncState( + TInt aError, TInt aVolumeLevel, TBool /*aActionResponse*/ ) + { + if ( aError == KErrNone ) + { + iCurrentVolume = aVolumeLevel; + if ( iMuteCapability ) + { + // continue to sync mute + TRAPD( muteError, iSession.GetMuteL() ); + if ( muteError != KErrNone ) + { + // error syncing mute - callback + iState = EIdle; + if ( iObserver ) + { + iObserver->VolumeSyncReady( muteError ); + } + } + } + else + { + // mute not needed - callback + iCurrentMute = KMuteOff; // assume always off + iState = EIdle; + // notify sync initially and volume if changed + if ( iObserver && iCachedVolume == KErrNotFound ) + { + iObserver->VolumeSyncReady( KErrNone ); + } + else if ( iObserver && iCachedVolume != iCurrentVolume ) + { + iObserver->VolumeChanged( + aError, iCurrentVolume, EFalse ); + } + } + } + else + { + // error callback + iState = EIdle; + if ( iObserver ) + { + iObserver->VolumeSyncReady( aError ); + } + } + + } + + + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::VolumeResult +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::VolumeResult( + TInt aError, TInt aVolumeLevel, TBool aActionResponse ) + { + __LOG3( "VolumeStateMachine: VolumeResult %d, %d state=%d", + aError, aVolumeLevel, iState ); + + // + // RESPONSE TO A REQUEST + // + if ( aActionResponse ) + { + // + // SYNC + // + if( iState == ESyncing ) + { + HandleVolumeResultInSyncState(aError, aVolumeLevel, aActionResponse); + } + // + // VOLUME ADJUST + // + else if ( iState == EAdjustingVolume || + iState == EAdjustingVolumeFromZero ) + { + if ( aError == KErrNone ) + { + iCurrentVolume = aVolumeLevel; + } + iState = EIdle; + // volume adjust done, call back + if ( iObserver ) + { + iObserver->VolumeChanged( + aError, iCurrentVolume, ETrue ); + } + } + // + // CANCEL + // + else if( iState == ECancelled ) + { + iState = EIdle; + } + } + + // + // UNSOLICTED VOLUME EVENT + // + else + { + if ( iState == EIdle ) + { + if ( aVolumeLevel != iCurrentVolume ) + { + if ( iCurrentMute == KMuteOn ) + { + __LOG("unsolicted volume event during muted"); + } + iCurrentVolume = aVolumeLevel; + if ( iObserver ) + { + iObserver->VolumeChanged( + KErrNone, iCurrentVolume, EFalse ); + } + } + } + else + { + __LOG("ignoring unsolicted volume event in this state"); + } + } + + iCachedVolume = iCurrentVolume; + ProcessNextQueuedProperty(); + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::MuteResult +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::MuteResult( + TInt aError, TBool aMute, TBool aActionResponse ) + { + __LOG3( "VolumeStateMachine: MuteResult %d, %d, %d", + aError, aMute, aActionResponse ); + __LOG1( "VolumeStateMachine: MuteResult in state=%d", iState ); + + // + // RESPONSE TO A REQUEST + // + if( aActionResponse ) + { + // + // SYNC + // + if ( iState == ESyncing ) + { + if ( aError == KErrNone ) + { + iCurrentMute = aMute; + iMuteRequestedByClient = EFalse; + } + iState = EIdle; + // notify sync initially and volume and mute if changed + if ( iObserver && iCachedMute == KErrNotFound ) + { + iObserver->VolumeSyncReady( aError ); + } + else if ( iObserver ) + { + if( iCachedVolume != iCurrentVolume ) + { + iObserver->VolumeChanged( + aError, iCurrentVolume, EFalse ); + } + if( iCachedMute != iCurrentMute ) + { + iObserver->MuteChanged( + aError, iCurrentMute, EFalse ); + } + } + } + // + // MUTE ADJUST + // + if ( iState == EAdjustingMute ) + { + if ( aError == KErrNone ) + { + iCurrentMute = aMute; + iMuteRequestedByClient = aMute; + } + iState = EIdle; + if ( iObserver ) + { + iObserver->MuteChanged( + aError, iCurrentMute, ETrue ); + } + } + // + // VOLUME ADJUST -> 0 + // + if ( iState == EAdjustingVolumeToZero ) + { + if ( aError == KErrNone ) + { + iCurrentMute = aMute; + iMuteRequestedByClient = EFalse; + } + iState = EIdle; + if ( iObserver ) + { + iObserver->VolumeChanged( + aError, KVolumeMin, ETrue ); + } + } + // + // VOLUME ADJUST 0 -> up + // + if ( iState == EAdjustingVolumeFromZero ) + { + if ( aError == KErrNone ) + { + iCurrentMute = aMute; + iMuteRequestedByClient = EFalse; + TRAP( aError, iSession.SetVolumeL( + iVolumeToSetAfterMute ) ); + } + if ( aError != KErrNone ) + { + // error callback + iVolumeToSetAfterMute = KErrNotFound; + iState = EIdle; + if ( iObserver ) + { + iObserver->VolumeChanged( + aError, iCurrentVolume, ETrue ); + } + } + } + // + // CANCEL + // + else if( iState == ECancelled ) + { + iState = EIdle; + } + } + + // + // UNSOLICITED MUTE EVENT + // + else + { + HandleUnsolicitedMuteEvent(aError, aMute, aActionResponse ); + } + + iCachedMute = iCurrentMute; + ProcessNextQueuedProperty(); + } + +void CUpnpVolumeStateMachine::HandleUnsolicitedMuteEvent( + TInt aError, TBool aMute, TBool aActionResponse ) + { + if ( iState == EIdle ) + { + HandleUnsolicitedMuteEventInIdle(aError, aMute, aActionResponse); + } + else + { + __LOG("ignoring unsolicted mute event in this state"); + } + + } + +void CUpnpVolumeStateMachine::HandleUnsolicitedMuteEventInIdle( + TInt /*aError*/, TBool aMute, TBool /*aActionResponse*/ ) + { + __LOG3( "VolumeStateMachine: HandleUnsolicitedMuteEventInIdle %d, %d, %d", + aMute, iCurrentMute, iMuteRequestedByClient ); + + if ( iCurrentMute && !aMute && iMuteRequestedByClient ) + { + // client has requested mute, renderer unmuted + iCurrentMute = aMute; + // check if client has changed volume during mute + if ( iVolumeToSetAfterMute > 0 && + iVolumeToSetAfterMute != iCurrentVolume ) + { + TRAP_IGNORE( + iSession.SetVolumeL( iVolumeToSetAfterMute ); + iVolumeToSetAfterMute = KErrNotFound; + iState = EAdjustingVolume; + ); + } + // notify observer about mute state change + if ( iObserver ) + { + iObserver->MuteChanged( + KErrNone, KMuteOff, EFalse ); + } + } + else if ( iCurrentMute && !aMute || + !iCurrentMute && aMute ) + { + // renderer unmuted - notify volume state back + iCurrentMute = aMute; + // check if client has changed volume during mute + if ( iVolumeToSetAfterMute > 0 && + iVolumeToSetAfterMute != iCurrentVolume && + !aMute && + ( ( (iOptions & Upnp::EConvertVolumeZeroToMute) == 0 ) || + iMuteRequestedByClient ) ) + { + __LOG2( "After unmute adjusting volume %d->%d", + iCurrentVolume, iVolumeToSetAfterMute ); + TRAP_IGNORE( + iSession.SetVolumeL( iVolumeToSetAfterMute ); + iVolumeToSetAfterMute = KErrNotFound; + iState = EAdjustingVolume; + ); + } + // notify observer + if ( iObserver ) + { + NotifyObserver(aMute); + } + } + else + { + // no need to report status change + } + } + +void CUpnpVolumeStateMachine::NotifyObserver(TBool aMute) + { + __LOG2( "VolumeStateMachine: NotifyObserver %d, %d", + aMute, iOptions ); + + if ( ( (iOptions & Upnp::EConvertVolumeZeroToMute) == 0 ) || + iMuteRequestedByClient ) + { + // client prefers to use mute, or client has + // explicitely requested this mute session. + iObserver->MuteChanged( + KErrNone, aMute, EFalse ); + } + else + { + // convert mute signal to volume callback + TInt givenvol = + ( aMute ? KVolumeMin : iCurrentVolume ); + iObserver->VolumeChanged( + KErrNone, givenvol, EFalse ); + } + } + + + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::CopyValues +// -------------------------------------------------------------------------- +// +EXPORT_C void CUpnpVolumeStateMachine::CopyValues( + const CUpnpVolumeStateMachine& aOther ) + { + iCurrentVolume = aOther.Volume(); + iCurrentMute = aOther.Mute(); + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::PushIntoQueueL +// -------------------------------------------------------------------------- +// +void CUpnpVolumeStateMachine::PushIntoQueueL( + const TUpnpVSMQueueItem::TPropertyType aPropery, const TInt aValue ) + { + __LOG( "VolumeStateMachine: PushIntoQueueL" ); + TUpnpVSMQueueItem queued( aPropery, aValue ); + iQueue.AppendL( queued ); + + CompressQueue(); + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::CompressQueueL +// -------------------------------------------------------------------------- +// +void CUpnpVolumeStateMachine::CompressQueue() + { + TInt queueCount( iQueue.Count() ); + + if( queueCount > 1 ) + { + __LOG1( "VolumeStateMachine: CompressQueue (queue count: %d)", queueCount ); + + TInt lastIndex( queueCount - 1 ); + + if( iQueue[lastIndex].Property() == TUpnpVSMQueueItem::EVolume ) + { + // remove all other except the last 'set volume' request + for( TInt i( queueCount - 2 ); i >= 0; --i ) + { + __LOG2( "VolumeStateMachine Queue: Removing %d from queue index %d", + iQueue[i].Property(), i ); + + iQueue.Remove( i ); + } + } + else + { + // remove all other except (the last 'set volume' request and) + // the last 'set mute' request + TBool setVolumeFound( EFalse ); + TBool setMuteFound( EFalse ); + for( TInt i( queueCount - 1 ); i >= 0; --i ) + { + if( iQueue[i].Property() == TUpnpVSMQueueItem::EVolume && + !setVolumeFound ) + { + setVolumeFound = ETrue; + } + else if( iQueue[i].Property() == TUpnpVSMQueueItem::EMute && + !setMuteFound ) + { + setMuteFound = ETrue; + } + else + { + __LOG2( "VolumeStateMachine Queue: Removing %d from queue index %d", + iQueue[i].Property(), i ); + + iQueue.Remove( i ); + } + } + } + } + } + +// -------------------------------------------------------------------------- +// CUpnpVolumeStateMachine::ProcessNextQueuedProperty +// -------------------------------------------------------------------------- +// +void CUpnpVolumeStateMachine::ProcessNextQueuedProperty() + { + if( iQueue.Count() > 0 ) + { + __LOG( "VolumeStateMachine: ProcessNextQueuedPropertyL" ); + + TUpnpVSMQueueItem queuedItem( iQueue[0] ); + iQueue.Remove( 0 ); + + if( queuedItem.Property() == TUpnpVSMQueueItem::EVolume ) + { + TRAPD( err, SetVolumeL( queuedItem.Value() ) ); + if( err ) + { + iObserver->VolumeChanged( err, iCurrentVolume, ETrue ); + } + } + else + { + TRAPD( err, SetMuteL( queuedItem.Value() ) ); + if( err ) + { + iObserver->MuteChanged( err, iCurrentMute, ETrue ); + } + } + } + } + +// end of file