upnpavcontroller/upnprenderingstatemachine/src/upnpvolumestatemachine.cpp
branchIOP_Improvements
changeset 40 08b5eae9f9ff
--- /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 <upnpavrenderingsession.h>
+#include <upnpavdevice.h> // 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