diff --git a/vector/build.gradle b/vector/build.gradle
index 496582e41c..a59b41d910 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -322,6 +322,7 @@ dependencies {
     implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
     implementation "androidx.sharetarget:sharetarget:1.0.0"
     implementation 'androidx.core:core-ktx:1.3.2'
+    implementation "androidx.media:media:1.2.1"
 
     implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
     implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
diff --git a/vector/src/main/java/im/vector/app/features/call/CallAudioManager.kt b/vector/src/main/java/im/vector/app/features/call/CallAudioManager.kt
deleted file mode 100644
index 3a24cf6d48..0000000000
--- a/vector/src/main/java/im/vector/app/features/call/CallAudioManager.kt
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.call
-
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothProfile
-import android.content.Context
-import android.content.pm.PackageManager
-import android.media.AudioManager
-import androidx.core.content.getSystemService
-import im.vector.app.core.services.WiredHeadsetStateReceiver
-import org.matrix.android.sdk.api.session.call.CallState
-import org.matrix.android.sdk.api.session.call.MxCall
-import timber.log.Timber
-import java.util.concurrent.Executors
-
-class CallAudioManager(
-        val applicationContext: Context,
-        val configChange: (() -> Unit)?
-) {
-
-    enum class SoundDevice {
-        PHONE,
-        SPEAKER,
-        HEADSET,
-        WIRELESS_HEADSET
-    }
-
-    // if all calls to audio manager not in the same thread it's not working well.
-    private val executor = Executors.newSingleThreadExecutor()
-
-    private var audioManager: AudioManager? = null
-
-    private var savedIsSpeakerPhoneOn = false
-    private var savedIsMicrophoneMute = false
-    private var savedAudioMode = AudioManager.MODE_INVALID
-
-    private var connectedBlueToothHeadset: BluetoothProfile? = null
-    private var wantsBluetoothConnection = false
-
-    private var bluetoothAdapter: BluetoothAdapter? = null
-
-    init {
-        executor.execute {
-            audioManager = applicationContext.getSystemService()
-        }
-        val bm = applicationContext.getSystemService<BluetoothManager>()
-        val adapter = bm?.adapter
-        Timber.d("## VOIP Bluetooth adapter $adapter")
-        bluetoothAdapter = adapter
-        adapter?.getProfileProxy(applicationContext, object : BluetoothProfile.ServiceListener {
-            override fun onServiceDisconnected(profile: Int) {
-                Timber.d("## VOIP onServiceDisconnected $profile")
-                if (profile == BluetoothProfile.HEADSET) {
-                    connectedBlueToothHeadset = null
-                    configChange?.invoke()
-                }
-            }
-
-            override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
-                Timber.d("## VOIP onServiceConnected $profile , proxy:$proxy")
-                if (profile == BluetoothProfile.HEADSET) {
-                    connectedBlueToothHeadset = proxy
-                    configChange?.invoke()
-                }
-            }
-        }, BluetoothProfile.HEADSET)
-    }
-
-    private val audioFocusChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
-
-        // Called on the listener to notify if the audio focus for this listener has been changed.
-        // The |focusChange| value indicates whether the focus was gained, whether the focus was lost,
-        // and whether that loss is transient, or whether the new focus holder will hold it for an
-        // unknown amount of time.
-        Timber.v("## VOIP: Audio focus change $focusChange")
-    }
-
-    fun startForCall(mxCall: MxCall) {
-        Timber.v("## VOIP: AudioManager startForCall ${mxCall.callId}")
-    }
-
-    private fun setupAudioManager(mxCall: MxCall) {
-        Timber.v("## VOIP: AudioManager setupAudioManager ${mxCall.callId}")
-        val audioManager = audioManager ?: return
-        savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
-        savedIsMicrophoneMute = audioManager.isMicrophoneMute
-        savedAudioMode = audioManager.mode
-
-        // Request audio playout focus (without ducking) and install listener for changes in focus.
-
-        // Remove the deprecation forces us to use 2 different method depending on API level
-        @Suppress("DEPRECATION") val result = audioManager.requestAudioFocus(audioFocusChangeListener,
-                AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
-        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            Timber.d("## VOIP Audio focus request granted for VOICE_CALL streams")
-        } else {
-            Timber.d("## VOIP Audio focus request failed")
-        }
-
-        // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
-        // required to be in this mode when playout and/or recording starts for
-        // best possible VoIP performance.
-        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
-
-        // Always disable microphone mute during a WebRTC call.
-        setMicrophoneMute(false)
-
-        adjustCurrentSoundDevice(mxCall)
-    }
-
-    private fun adjustCurrentSoundDevice(mxCall: MxCall) {
-        val audioManager = audioManager ?: return
-        executor.execute {
-            if (mxCall.state == CallState.LocalRinging && !isHeadsetOn()) {
-                // Always use speaker if incoming call is in ringing state and a headset is not connected
-                Timber.v("##VOIP: AudioManager default to SPEAKER (it is ringing)")
-                setCurrentSoundDevice(SoundDevice.SPEAKER)
-            } else if (mxCall.isVideoCall && !isHeadsetOn()) {
-                // If there are no headset, start video output in speaker
-                // (you can't watch the video and have the phone close to your ear)
-                Timber.v("##VOIP: AudioManager default to speaker ")
-                setCurrentSoundDevice(SoundDevice.SPEAKER)
-            } else {
-                // if a wired headset is plugged, sound will be directed to it
-                // (can't really force earpiece when headset is plugged)
-                if (isBluetoothHeadsetConnected(audioManager)) {
-                    Timber.v("##VOIP: AudioManager default to WIRELESS_HEADSET ")
-                    setCurrentSoundDevice(SoundDevice.WIRELESS_HEADSET)
-                    // try now in case already connected?
-                    audioManager.isBluetoothScoOn = true
-                } else {
-                    Timber.v("##VOIP: AudioManager default to PHONE/HEADSET ")
-                    setCurrentSoundDevice(if (isWiredHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE)
-                }
-            }
-        }
-    }
-
-    fun onCallConnected(mxCall: MxCall) {
-        Timber.v("##VOIP: AudioManager call answered, adjusting current sound device")
-        setupAudioManager(mxCall)
-    }
-
-    fun getAvailableSoundDevices(): List<SoundDevice> {
-        return ArrayList<SoundDevice>().apply {
-            if (isBluetoothHeadsetOn()) add(SoundDevice.WIRELESS_HEADSET)
-            add(if (isWiredHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE)
-            add(SoundDevice.SPEAKER)
-        }
-    }
-
-    fun stop() {
-        Timber.v("## VOIP: AudioManager stopCall")
-        executor.execute {
-            // Restore previously stored audio states.
-            setSpeakerphoneOn(savedIsSpeakerPhoneOn)
-            setMicrophoneMute(savedIsMicrophoneMute)
-            audioManager?.mode = savedAudioMode
-
-            connectedBlueToothHeadset?.let {
-                if (audioManager != null && isBluetoothHeadsetConnected(audioManager!!)) {
-                    audioManager?.stopBluetoothSco()
-                    audioManager?.isBluetoothScoOn = false
-                    audioManager?.isSpeakerphoneOn = false
-                }
-                bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, it)
-            }
-
-            audioManager?.mode = AudioManager.MODE_NORMAL
-
-            @Suppress("DEPRECATION")
-            audioManager?.abandonAudioFocus(audioFocusChangeListener)
-        }
-    }
-
-    fun getCurrentSoundDevice(): SoundDevice {
-        val audioManager = audioManager ?: return SoundDevice.PHONE
-        if (audioManager.isSpeakerphoneOn) {
-            return SoundDevice.SPEAKER
-        } else {
-            if (isBluetoothHeadsetConnected(audioManager)) return SoundDevice.WIRELESS_HEADSET
-            return if (isHeadsetOn()) SoundDevice.HEADSET else SoundDevice.PHONE
-        }
-    }
-
-    private fun isBluetoothHeadsetConnected(audioManager: AudioManager) =
-            isBluetoothHeadsetOn()
-                    && !connectedBlueToothHeadset?.connectedDevices.isNullOrEmpty()
-                    && (wantsBluetoothConnection || audioManager.isBluetoothScoOn)
-
-    fun setCurrentSoundDevice(device: SoundDevice) {
-        executor.execute {
-            Timber.v("## VOIP setCurrentSoundDevice $device")
-            when (device) {
-                SoundDevice.HEADSET,
-                SoundDevice.PHONE            -> {
-                    wantsBluetoothConnection = false
-                    if (isBluetoothHeadsetOn()) {
-                        audioManager?.stopBluetoothSco()
-                        audioManager?.isBluetoothScoOn = false
-                    }
-                    setSpeakerphoneOn(false)
-                }
-                SoundDevice.SPEAKER          -> {
-                    setSpeakerphoneOn(true)
-                    wantsBluetoothConnection = false
-                    audioManager?.stopBluetoothSco()
-                    audioManager?.isBluetoothScoOn = false
-                }
-                SoundDevice.WIRELESS_HEADSET -> {
-                    setSpeakerphoneOn(false)
-                    // I cannot directly do it, i have to start then wait that it's connected
-                    // to route to bt
-                    audioManager?.startBluetoothSco()
-                    wantsBluetoothConnection = true
-                }
-            }
-
-            configChange?.invoke()
-        }
-    }
-
-    fun bluetoothStateChange(plugged: Boolean) {
-        executor.execute {
-            if (plugged && wantsBluetoothConnection) {
-                audioManager?.isBluetoothScoOn = true
-            } else if (!plugged && !wantsBluetoothConnection) {
-                audioManager?.stopBluetoothSco()
-            }
-
-            configChange?.invoke()
-        }
-    }
-
-    fun wiredStateChange(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
-        executor.execute {
-            // if it's plugged and speaker is on we should route to headset
-            if (event.plugged && getCurrentSoundDevice() == SoundDevice.SPEAKER) {
-                setCurrentSoundDevice(CallAudioManager.SoundDevice.HEADSET)
-            } else if (!event.plugged) {
-                // if it's unplugged ? always route to speaker?
-                // this is questionable?
-                if (!wantsBluetoothConnection) {
-                    setCurrentSoundDevice(SoundDevice.SPEAKER)
-                }
-            }
-            configChange?.invoke()
-        }
-    }
-
-    private fun isHeadsetOn(): Boolean {
-        return isWiredHeadsetOn() || (audioManager?.let { isBluetoothHeadsetConnected(it) } ?: false)
-    }
-
-    private fun isWiredHeadsetOn(): Boolean {
-        @Suppress("DEPRECATION")
-        return audioManager?.isWiredHeadsetOn ?: false
-    }
-
-    private fun isBluetoothHeadsetOn(): Boolean {
-        Timber.v("## VOIP: AudioManager isBluetoothHeadsetOn")
-        try {
-            if (connectedBlueToothHeadset == null) return false.also {
-                Timber.v("## VOIP: AudioManager no connected bluetooth headset")
-            }
-            if (audioManager?.isBluetoothScoAvailableOffCall == false) return false.also {
-                Timber.v("## VOIP: AudioManager isBluetoothScoAvailableOffCall false")
-            }
-            return true
-        } catch (failure: Throwable) {
-            Timber.e("## VOIP: AudioManager isBluetoothHeadsetOn failure ${failure.localizedMessage}")
-            return false
-        }
-    }
-
-    /** Sets the speaker phone mode.  */
-    private fun setSpeakerphoneOn(on: Boolean) {
-        Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
-        val wasOn = audioManager?.isSpeakerphoneOn ?: false
-        if (wasOn == on) {
-            return
-        }
-        audioManager?.isSpeakerphoneOn = on
-    }
-
-    /** Sets the microphone mute state.  */
-    private fun setMicrophoneMute(on: Boolean) {
-        Timber.v("## VOIP: AudioManager setMicrophoneMute $on")
-        val wasMuted = audioManager?.isMicrophoneMute ?: false
-        if (wasMuted == on) {
-            return
-        }
-        audioManager?.isMicrophoneMute = on
-    }
-
-    /** true if the device has a telephony radio with data
-     * communication support.   */
-    private fun isThisPhone(): Boolean {
-        return applicationContext.packageManager.hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY)
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
index 7c8c4eb465..735ce2fd22 100644
--- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
@@ -27,6 +27,7 @@ import com.airbnb.mvrx.activityViewModel
 import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.databinding.BottomSheetCallControlsBinding
+import im.vector.app.features.call.audio.CallAudioManager
 
 import me.gujun.android.span.span
 
@@ -79,22 +80,22 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
         }
     }
 
-    private fun showSoundDeviceChooser(available: List<CallAudioManager.SoundDevice>, current: CallAudioManager.SoundDevice) {
+    private fun showSoundDeviceChooser(available: Set<CallAudioManager.Device>, current: CallAudioManager.Device) {
         val soundDevices = available.map {
             when (it) {
-                CallAudioManager.SoundDevice.WIRELESS_HEADSET -> span {
+                CallAudioManager.Device.WIRELESS_HEADSET -> span {
                     text = getString(R.string.sound_device_wireless_headset)
                     textStyle = if (current == it) "bold" else "normal"
                 }
-                CallAudioManager.SoundDevice.PHONE -> span {
+                CallAudioManager.Device.PHONE            -> span {
                     text = getString(R.string.sound_device_phone)
                     textStyle = if (current == it) "bold" else "normal"
                 }
-                CallAudioManager.SoundDevice.SPEAKER -> span {
+                CallAudioManager.Device.SPEAKER          -> span {
                     text = getString(R.string.sound_device_speaker)
                     textStyle = if (current == it) "bold" else "normal"
                 }
-                CallAudioManager.SoundDevice.HEADSET -> span {
+                CallAudioManager.Device.HEADSET          -> span {
                     text = getString(R.string.sound_device_headset)
                     textStyle = if (current == it) "bold" else "normal"
                 }
@@ -106,16 +107,16 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
                     when (soundDevices[n].toString()) {
                         // TODO Make an adapter and handle multiple Bluetooth headsets. Also do not use translations.
                         getString(R.string.sound_device_phone) -> {
-                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.PHONE))
+                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.Device.PHONE))
                         }
                         getString(R.string.sound_device_speaker) -> {
-                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.SPEAKER))
+                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.Device.SPEAKER))
                         }
                         getString(R.string.sound_device_headset) -> {
-                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.HEADSET))
+                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.Device.HEADSET))
                         }
                         getString(R.string.sound_device_wireless_headset) -> {
-                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.SoundDevice.WIRELESS_HEADSET))
+                            callViewModel.handle(VectorCallViewActions.ChangeAudioDevice(CallAudioManager.Device.WIRELESS_HEADSET))
                         }
                     }
                 }
@@ -125,11 +126,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
 
     private fun renderState(state: VectorCallViewState) {
         views.callControlsSoundDevice.title = getString(R.string.call_select_sound_device)
-        views.callControlsSoundDevice.subTitle = when (state.soundDevice) {
-            CallAudioManager.SoundDevice.PHONE            -> getString(R.string.sound_device_phone)
-            CallAudioManager.SoundDevice.SPEAKER          -> getString(R.string.sound_device_speaker)
-            CallAudioManager.SoundDevice.HEADSET          -> getString(R.string.sound_device_headset)
-            CallAudioManager.SoundDevice.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
+        views.callControlsSoundDevice.subTitle = when (state.device) {
+            CallAudioManager.Device.PHONE            -> getString(R.string.sound_device_phone)
+            CallAudioManager.Device.SPEAKER          -> getString(R.string.sound_device_speaker)
+            CallAudioManager.Device.HEADSET          -> getString(R.string.sound_device_headset)
+            CallAudioManager.Device.WIRELESS_HEADSET -> getString(R.string.sound_device_wireless_headset)
         }
 
         views.callControlsSwitchCamera.isVisible = state.isVideoCall && state.canSwitchCamera
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
index adb8897a51..32a1deb266 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.call
 
 import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.call.audio.CallAudioManager
 
 sealed class VectorCallViewActions : VectorViewModelAction {
     object EndCall : VectorCallViewActions()
@@ -25,7 +26,7 @@ sealed class VectorCallViewActions : VectorViewModelAction {
     object ToggleMute : VectorCallViewActions()
     object ToggleVideo : VectorCallViewActions()
     object ToggleHoldResume: VectorCallViewActions()
-    data class ChangeAudioDevice(val device: CallAudioManager.SoundDevice) : VectorCallViewActions()
+    data class ChangeAudioDevice(val device: CallAudioManager.Device) : VectorCallViewActions()
     object SwitchSoundDevice : VectorCallViewActions()
     object HeadSetButtonPressed : VectorCallViewActions()
     object ToggleCamera : VectorCallViewActions()
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
index 832c4fe944..5ffeca6f66 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.call
 
 import im.vector.app.core.platform.VectorViewEvents
+import im.vector.app.features.call.audio.CallAudioManager
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
 
 sealed class VectorCallViewEvents : VectorViewEvents {
@@ -24,8 +25,8 @@ sealed class VectorCallViewEvents : VectorViewEvents {
     object DismissNoCall : VectorCallViewEvents()
     data class ConnectionTimeout(val turn: TurnServerResponse?) : VectorCallViewEvents()
     data class ShowSoundDeviceChooser(
-            val available: List<CallAudioManager.SoundDevice>,
-            val current: CallAudioManager.SoundDevice
+            val available: Set<CallAudioManager.Device>,
+            val current: CallAudioManager.Device
     ) : VectorCallViewEvents()
     object ShowCallTransferScreen: VectorCallViewEvents()
 //    data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
index 0c621dcfe4..d12add5014 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
@@ -25,6 +25,7 @@ import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
 import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.call.audio.CallAudioManager
 import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.call.webrtc.WebRtcCallManager
 import org.matrix.android.sdk.api.MatrixCallback
@@ -133,17 +134,16 @@ class VectorCallViewModel @AssistedInject constructor(
         }
 
         override fun onAudioDevicesChange() {
-            val currentSoundDevice = callManager.callAudioManager.getCurrentSoundDevice()
-            if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
+            val currentSoundDevice = callManager.audioManager.selectedDevice ?: return
+            if (currentSoundDevice == CallAudioManager.Device.PHONE) {
                 proximityManager.start()
             } else {
                 proximityManager.stop()
             }
-
             setState {
                 copy(
-                        availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
-                        soundDevice = currentSoundDevice
+                        availableDevices = callManager.audioManager.availableDevices,
+                        device = currentSoundDevice
                 )
             }
         }
@@ -174,8 +174,8 @@ class VectorCallViewModel @AssistedInject constructor(
             callManager.addCurrentCallListener(currentCallListener)
             val item: MatrixItem? = session.getUser(webRtcCall.mxCall.opponentUserId)?.toMatrixItem()
             webRtcCall.addListener(callListener)
-            val currentSoundDevice = callManager.callAudioManager.getCurrentSoundDevice()
-            if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
+            val currentSoundDevice = callManager.audioManager.selectedDevice
+            if (currentSoundDevice == CallAudioManager.Device.PHONE) {
                 proximityManager.start()
             }
             setState {
@@ -183,10 +183,10 @@ class VectorCallViewModel @AssistedInject constructor(
                         isVideoCall = webRtcCall.mxCall.isVideoCall,
                         callState = Success(webRtcCall.mxCall.state),
                         callInfo = VectorCallViewState.CallInfo(callId, item),
-                        soundDevice = currentSoundDevice,
+                        device = currentSoundDevice ?: CallAudioManager.Device.PHONE,
                         isLocalOnHold = webRtcCall.isLocalOnHold,
                         isRemoteOnHold = webRtcCall.remoteOnHold,
-                        availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
+                        availableDevices = callManager.audioManager.availableDevices,
                         isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT,
                         canSwitchCamera = webRtcCall.canSwitchCamera(),
                         formattedDuration = webRtcCall.formattedDuration(),
@@ -242,16 +242,11 @@ class VectorCallViewModel @AssistedInject constructor(
                 call?.updateRemoteOnHold(!isRemoteOnHold)
             }
             is VectorCallViewActions.ChangeAudioDevice -> {
-                callManager.callAudioManager.setCurrentSoundDevice(action.device)
-                setState {
-                    copy(
-                            soundDevice = callManager.callAudioManager.getCurrentSoundDevice()
-                    )
-                }
+                callManager.audioManager.setAudioDevice(action.device)
             }
             VectorCallViewActions.SwitchSoundDevice -> {
                 _viewEvents.post(
-                        VectorCallViewEvents.ShowSoundDeviceChooser(state.availableSoundDevices, state.soundDevice)
+                        VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
                 )
             }
             VectorCallViewActions.HeadSetButtonPressed -> {
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
index 1d12b780b1..cdd002114a 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.call
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.Uninitialized
+import im.vector.app.features.call.audio.CallAudioManager
 import org.matrix.android.sdk.api.session.call.CallState
 import org.matrix.android.sdk.api.util.MatrixItem
 
@@ -34,8 +35,8 @@ data class VectorCallViewState(
         val isHD: Boolean = false,
         val isFrontCamera: Boolean = true,
         val canSwitchCamera: Boolean = true,
-        val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE,
-        val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
+        val device: CallAudioManager.Device = CallAudioManager.Device.PHONE,
+        val availableDevices: Set<CallAudioManager.Device> = emptySet(),
         val callState: Async<CallState> = Uninitialized,
         val otherKnownCallInfo: CallInfo? = null,
         val callInfo: CallInfo = CallInfo(callId),
diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt
new file mode 100644
index 0000000000..8649c755f4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("DEPRECATION")
+
+package im.vector.app.features.call.audio
+
+import android.media.AudioAttributes
+import android.media.AudioDeviceCallback
+import android.media.AudioDeviceInfo
+import android.media.AudioFocusRequest
+import android.media.AudioManager
+import android.media.AudioManager.OnAudioFocusChangeListener
+import android.os.Build
+import androidx.annotation.RequiresApi
+import timber.log.Timber
+import java.util.HashSet
+
+@RequiresApi(Build.VERSION_CODES.M)
+internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
+                                        private val callAudioManager: CallAudioManager
+) : CallAudioManager.AudioDeviceDetector{
+
+    private val onAudioDeviceChangeRunner = Runnable {
+        val devices: MutableSet<CallAudioManager.Device> = HashSet()
+        val deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL)
+        for (info in deviceInfos) {
+            when (info.type) {
+                AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WIRELESS_HEADSET)
+                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> devices.add(CallAudioManager.Device.PHONE)
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> devices.add(CallAudioManager.Device.SPEAKER)
+                AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_USB_HEADSET -> devices.add(CallAudioManager.Device.HEADSET)
+            }
+        }
+        callAudioManager.replaceDevices(devices)
+        Timber.i(" Available audio devices: $devices")
+        callAudioManager.updateAudioRoute()
+    }
+    private val audioDeviceCallback: AudioDeviceCallback = object : AudioDeviceCallback() {
+        override fun onAudioDevicesAdded(
+                addedDevices: Array<AudioDeviceInfo>) {
+            Timber.d(" Audio devices added")
+            onAudioDeviceChange()
+        }
+
+        override fun onAudioDevicesRemoved(
+                removedDevices: Array<AudioDeviceInfo>) {
+            Timber.d(" Audio devices removed")
+            onAudioDeviceChange()
+        }
+    }
+
+    /**
+     * Helper method to trigger an audio route update when devices change. It
+     * makes sure the operation is performed on the audio thread.
+     */
+    private fun onAudioDeviceChange() {
+        callAudioManager.runInAudioThread(onAudioDeviceChangeRunner)
+    }
+
+    override fun start() {
+        Timber.i("Using $this as the audio device handler")
+        audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
+        onAudioDeviceChange()
+    }
+
+    override fun stop() {
+        audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
+    }
+
+    companion object {
+        /**
+         * Constant defining a USB headset. Only available on API level >= 26.
+         * The value of: AudioDeviceInfo.TYPE_USB_HEADSET
+         */
+        private const val TYPE_USB_HEADSET = 22
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
new file mode 100644
index 0000000000..9518ab443f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.call.audio
+
+import android.content.Context
+import android.media.AudioManager
+import org.matrix.android.sdk.api.extensions.orFalse
+import timber.log.Timber
+import java.util.HashSet
+import java.util.concurrent.Executors
+
+class CallAudioManager(context: Context, val configChange: (() -> Unit)?) {
+
+    private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+    private var audioDeviceDetector: AudioDeviceDetector? = null
+    private var audioDeviceRouter: AudioDeviceRouter? = null
+
+    enum class Device {
+        PHONE,
+        SPEAKER,
+        HEADSET,
+        WIRELESS_HEADSET
+    }
+
+    enum class Mode {
+        DEFAULT,
+        AUDIO_CALL,
+        VIDEO_CALL
+    }
+
+    private var mode = Mode.DEFAULT
+    private var _availableDevices: MutableSet<Device> = HashSet()
+    val availableDevices: Set<Device>
+        get() = _availableDevices
+
+    var selectedDevice: Device? = null
+        private set
+    private var userSelectedDevice: Device? = null
+
+    init {
+        runInAudioThread { setup() }
+    }
+
+    private fun setup() {
+        audioDeviceDetector?.stop()
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+            audioDeviceDetector = API23AudioDeviceDetector(audioManager, this)
+        }
+        audioDeviceRouter = DefaultAudioDeviceRouter(audioManager, this)
+        audioDeviceDetector?.start()
+    }
+
+    fun runInAudioThread(runnable: Runnable) {
+        executor.execute(runnable)
+    }
+
+    /**
+     * Sets the user selected audio device as the active audio device.
+     *
+     * @param device the desired device which will become active.
+     */
+    fun setAudioDevice(device: Device) {
+        runInAudioThread(Runnable {
+            if (!_availableDevices.contains(device)) {
+                Timber.w(" Audio device not available: $device")
+                userSelectedDevice = null
+                return@Runnable
+            }
+            if (mode != Mode.DEFAULT) {
+                Timber.i(" User selected device set to: $device")
+                userSelectedDevice = device
+                updateAudioRoute(mode, false)
+            }
+        })
+    }
+
+    /**
+     * Public method to set the current audio mode.
+     *
+     * @param mode the desired audio mode.
+     * could be updated successfully, and it will be rejected otherwise.
+     */
+    fun setMode(mode: Mode) {
+        runInAudioThread {
+            var success: Boolean
+            try {
+                success = updateAudioRoute(mode, false)
+            } catch (e: Throwable) {
+                success = false
+                Timber.e(e, " Failed to update audio route for mode: " + mode)
+            }
+            if (success) {
+                this@CallAudioManager.mode = mode
+            }
+        }
+    }
+
+    /**
+     * Updates the audio route for the given mode.
+     *
+     * @param mode the audio mode to be used when computing the audio route.
+     * @return `true` if the audio route was updated successfully;
+     * `false`, otherwise.
+     */
+    private fun updateAudioRoute(mode: Mode, force: Boolean): Boolean {
+        Timber.i(" Update audio route for mode: " + mode)
+        if (!audioDeviceRouter?.setMode(mode).orFalse()) {
+            return false
+        }
+        if (mode == Mode.DEFAULT) {
+            selectedDevice = null
+            userSelectedDevice = null
+            return true
+        }
+        val bluetoothAvailable = _availableDevices.contains(Device.WIRELESS_HEADSET)
+        val headsetAvailable = _availableDevices.contains(Device.HEADSET)
+
+        // Pick the desired device based on what's available and the mode.
+        var audioDevice: Device
+        audioDevice = if (bluetoothAvailable) {
+            Device.WIRELESS_HEADSET
+        } else if (headsetAvailable) {
+            Device.HEADSET
+        } else if (mode == Mode.VIDEO_CALL) {
+            Device.SPEAKER
+        } else {
+            Device.PHONE
+        }
+        // Consider the user's selection
+        if (userSelectedDevice != null && _availableDevices.contains(userSelectedDevice)) {
+            audioDevice = userSelectedDevice!!
+        }
+
+        // If the previously selected device and the current default one
+        // match, do nothing.
+        if (!force && selectedDevice != null && selectedDevice == audioDevice) {
+            return true
+        }
+        selectedDevice = audioDevice
+        Timber.i(" Selected audio device: " + audioDevice)
+        audioDeviceRouter?.setAudioRoute(audioDevice)
+        configChange?.invoke()
+        return true
+    }
+
+    /**
+     * Resets the current device selection.
+     */
+    fun resetSelectedDevice() {
+        selectedDevice = null
+        userSelectedDevice = null
+    }
+
+    /**
+     * Adds a new device to the list of available devices.
+     *
+     * @param device The new device.
+     */
+    fun addDevice(device: Device) {
+        _availableDevices.add(device)
+        resetSelectedDevice()
+    }
+
+    /**
+     * Removes a device from the list of available devices.
+     *
+     * @param device The old device to the removed.
+     */
+    fun removeDevice(device: Device) {
+        _availableDevices.remove(device)
+        resetSelectedDevice()
+    }
+
+    /**
+     * Replaces the current list of available devices with a new one.
+     *
+     * @param devices The new devices list.
+     */
+    fun replaceDevices(devices: MutableSet<Device>) {
+        _availableDevices = devices
+        resetSelectedDevice()
+    }
+
+    /**
+     * Re-sets the current audio route. Needed when devices changes have happened.
+     */
+    fun updateAudioRoute() {
+        if (mode != Mode.DEFAULT) {
+            updateAudioRoute(mode, false)
+        }
+    }
+
+    /**
+     * Re-sets the current audio route. Needed when focus is lost and regained.
+     */
+    fun resetAudioRoute() {
+        if (mode != Mode.DEFAULT) {
+            updateAudioRoute(mode, true)
+        }
+    }
+
+    /**
+     * Interface for the modules implementing the actual audio device management.
+     */
+    interface AudioDeviceDetector {
+        /**
+         * Start detecting audio device changes.
+         */
+        fun start()
+
+        /**
+         * Stop audio device detection.
+         */
+        fun stop()
+    }
+
+    interface AudioDeviceRouter {
+        /**
+         * Set the appropriate route for the given audio device.
+         *
+         * @param device Audio device for which the route must be set.
+         */
+        fun setAudioRoute(device: Device)
+
+        /**
+         * Set the given audio mode.
+         *
+         * @param mode The new audio mode to be used.
+         * @return Whether the operation was successful or not.
+         */
+        fun setMode(mode: Mode): Boolean
+    }
+
+    companion object {
+        // Every audio operations should be launched on single thread
+        private val executor = Executors.newSingleThreadExecutor()
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt
new file mode 100644
index 0000000000..c252cc9f89
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.call.audio
+
+import android.media.AudioManager
+import androidx.media.AudioAttributesCompat
+import androidx.media.AudioFocusRequestCompat
+import androidx.media.AudioManagerCompat
+import timber.log.Timber
+
+class DefaultAudioDeviceRouter(private val audioManager: AudioManager,
+                               private val callAudioManager: CallAudioManager
+) : CallAudioManager.AudioDeviceRouter, AudioManager.OnAudioFocusChangeListener {
+
+    private var audioFocusLost = false
+
+    private var focusRequestCompat: AudioFocusRequestCompat? = null
+
+    override fun setAudioRoute(device: CallAudioManager.Device) {
+        audioManager.isSpeakerphoneOn = device === CallAudioManager.Device.SPEAKER
+        setBluetoothAudioRoute(device === CallAudioManager.Device.WIRELESS_HEADSET)
+    }
+
+    override fun setMode(mode: CallAudioManager.Mode): Boolean {
+        if (mode === CallAudioManager.Mode.DEFAULT) {
+            audioFocusLost = false
+            audioManager.mode = AudioManager.MODE_NORMAL
+            focusRequestCompat?.also {
+                AudioManagerCompat.abandonAudioFocusRequest(audioManager, it)
+            }
+            focusRequestCompat = null
+            audioManager.isSpeakerphoneOn = false
+            setBluetoothAudioRoute(false)
+            return true
+        }
+        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
+        audioManager.isMicrophoneMute = false
+
+        val audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
+                .setAudioAttributes(
+                        AudioAttributesCompat.Builder()
+                                .setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
+                                .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
+                                .build()
+                )
+                .setOnAudioFocusChangeListener(this)
+                .build()
+                .also {
+                    focusRequestCompat = it
+                }
+
+        val gotFocus = AudioManagerCompat.requestAudioFocus(audioManager, audioFocusRequest)
+        if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+            Timber.w(" Audio focus request failed")
+            return false
+        }
+        return true
+    }
+
+    /**
+     * Helper method to set the output route to a Bluetooth device.
+     *
+     * @param enabled true if Bluetooth should use used, false otherwise.
+     */
+    private fun setBluetoothAudioRoute(enabled: Boolean) {
+        if (enabled) {
+            audioManager.startBluetoothSco()
+            audioManager.isBluetoothScoOn = true
+        } else {
+            audioManager.isBluetoothScoOn = false
+            audioManager.stopBluetoothSco()
+        }
+    }
+
+    /**
+     * [AudioManager.OnAudioFocusChangeListener] interface method. Called
+     * when the audio focus of the system is updated.
+     *
+     * @param focusChange - The type of focus change.
+     */
+    override fun onAudioFocusChange(focusChange: Int) {
+        callAudioManager.runInAudioThread {
+            when (focusChange) {
+                AudioManager.AUDIOFOCUS_GAIN -> {
+                    Timber.d(" Audio focus gained")
+                    if (audioFocusLost) {
+                        callAudioManager.resetAudioRoute()
+                    }
+                    audioFocusLost = false
+                }
+                AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
+                    Timber.d(" Audio focus lost")
+                    audioFocusLost = true
+                }
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/telecom/CallConnectionService.java b/vector/src/main/java/im/vector/app/features/call/telecom/CallConnectionService.java
new file mode 100644
index 0000000000..f3fbfc9ac4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/call/telecom/CallConnectionService.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.call.telecom;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import org.jitsi.meet.sdk.ConnectionService;
+
+@RequiresApi(api = Build.VERSION_CODES.O)
+public class CallConnectionService extends ConnectionService {
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt b/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt
index 410a4621e8..e289537177 100644
--- a/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt
+++ b/vector/src/main/java/im/vector/app/features/call/telecom/VectorConnectionService.kt
@@ -71,7 +71,7 @@ import im.vector.app.core.services.CallService
 
         bindService(Intent(applicationContext, CallService::class.java), CallServiceConnection(connection), 0)
         connection.setInitializing()
-        return CallConnection(applicationContext, roomId, callId)
+        return connection
     }
 
     inner class CallServiceConnection(private val callConnection: CallConnection) : ServiceConnection {
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
index f6e2caf72c..b476d7dbe6 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
@@ -16,7 +16,7 @@
 
 package im.vector.app.features.call.webrtc
 
-import im.vector.app.features.call.CallAudioManager
+import im.vector.app.features.call.audio.CallAudioManager
 import org.matrix.android.sdk.api.session.call.CallState
 import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
 import org.webrtc.DataChannel
@@ -26,8 +26,7 @@ import org.webrtc.PeerConnection
 import org.webrtc.RtpReceiver
 import timber.log.Timber
 
-class PeerConnectionObserver(private val webRtcCall: WebRtcCall,
-                             private val callAudioManager: CallAudioManager) : PeerConnection.Observer {
+class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnection.Observer {
 
     override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
         Timber.v("## VOIP StreamObserver onConnectionChange: $newState")
@@ -38,7 +37,6 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall,
              */
             PeerConnection.PeerConnectionState.CONNECTED -> {
                 webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CONNECTED)
-                callAudioManager.onCallConnected(webRtcCall.mxCall)
             }
             /**
              * One or more of the ICE transports on the connection is in the "failed" state.
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
index 28ca503507..95e7e92646 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
@@ -21,7 +21,7 @@ import android.hardware.camera2.CameraManager
 import androidx.core.content.getSystemService
 import im.vector.app.core.services.CallService
 import im.vector.app.core.utils.CountUpTimer
-import im.vector.app.features.call.CallAudioManager
+import im.vector.app.features.call.audio.CallAudioManager
 import im.vector.app.features.call.CameraEventsHandlerAdapter
 import im.vector.app.features.call.CameraProxy
 import im.vector.app.features.call.CameraType
@@ -86,7 +86,6 @@ private const val VIDEO_TRACK_ID = "ARDAMSv0"
 private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
 
 class WebRtcCall(val mxCall: MxCall,
-                 private val callAudioManager: CallAudioManager,
                  private val rootEglBase: EglBase?,
                  private val context: Context,
                  private val dispatcher: CoroutineContext,
@@ -256,7 +255,7 @@ class WebRtcCall(val mxCall: MxCall,
         val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply {
             sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN
         }
-        peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this, callAudioManager))
+        peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this))
     }
 
     fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
@@ -317,6 +316,9 @@ class WebRtcCall(val mxCall: MxCall,
     }
 
     private suspend fun setupOutgoingCall() = withContext(dispatcher) {
+        tryOrNull {
+            onCallBecomeActive(this@WebRtcCall)
+        }
         val turnServer = getTurnServer()
         mxCall.state = CallState.CreateOffer
         // 1. Create RTCPeerConnection
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
index 18720a6c19..f7f50b4681 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
@@ -24,8 +24,8 @@ import im.vector.app.ActiveSessionDataSource
 import im.vector.app.core.services.BluetoothHeadsetReceiver
 import im.vector.app.core.services.CallService
 import im.vector.app.core.services.WiredHeadsetStateReceiver
-import im.vector.app.features.call.CallAudioManager
 import im.vector.app.features.call.VectorCallActivity
+import im.vector.app.features.call.audio.CallAudioManager
 import im.vector.app.features.call.utils.EglUtils
 import im.vector.app.push.fcm.FcmHelper
 import kotlinx.coroutines.asCoroutineDispatcher
@@ -79,10 +79,12 @@ class WebRtcCallManager @Inject constructor(
         currentCallsListeners.remove(listener)
     }
 
-    val callAudioManager = CallAudioManager(context) {
+    val audioManager = CallAudioManager(context) {
         currentCallsListeners.forEach {
             tryOrNull { it.onAudioDevicesChange() }
         }
+    }.apply {
+        setMode(CallAudioManager.Mode.DEFAULT)
     }
 
     private var peerConnectionFactory: PeerConnectionFactory? = null
@@ -180,13 +182,13 @@ class WebRtcCallManager @Inject constructor(
         Timber.v("## VOIP WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}")
         val currentCall = getCurrentCall().takeIf { it != call }
         currentCall?.updateRemoteOnHold(onHold = true)
+        audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL)
         this.currentCall.setAndNotify(call)
     }
 
     private fun onCallEnded(call: WebRtcCall) {
         Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}")
         CallService.onCallTerminated(context, call.callId)
-        callAudioManager.stop()
         callsByCallId.remove(call.mxCall.callId)
         callsByRoomId[call.mxCall.roomId]?.remove(call)
         if (getCurrentCall() == call) {
@@ -199,6 +201,7 @@ class WebRtcCallManager @Inject constructor(
                 Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
                 peerConnectionFactory?.dispose()
                 peerConnectionFactory = null
+                audioManager.setMode(CallAudioManager.Mode.DEFAULT)
             }
             Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done")
         }
@@ -222,7 +225,7 @@ class WebRtcCallManager @Inject constructor(
         val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
         val webRtcCall = createWebRtcCall(mxCall)
         currentCall.setAndNotify(webRtcCall)
-        callAudioManager.startForCall(mxCall)
+        //callAudioManager.startForCall(mxCall)
 
         CallService.onOutgoingCallRinging(
                 context = context.applicationContext,
@@ -244,7 +247,6 @@ class WebRtcCallManager @Inject constructor(
     private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
         val webRtcCall = WebRtcCall(
                 mxCall = mxCall,
-                callAudioManager = callAudioManager,
                 rootEglBase = rootEglBase,
                 context = context,
                 dispatcher = dispatcher,
@@ -270,12 +272,12 @@ class WebRtcCallManager @Inject constructor(
         Timber.v("## VOIP onWiredDeviceEvent $event")
         getCurrentCall() ?: return
         // sometimes we received un-wanted unplugged...
-        callAudioManager.wiredStateChange(event)
+        //callAudioManager.wiredStateChange(event)
     }
 
     fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
         Timber.v("## VOIP onWirelessDeviceEvent $event")
-        callAudioManager.bluetoothStateChange(event.plugged)
+        //callAudioManager.bluetoothStateChange(event.plugged)
     }
 
     override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
@@ -292,7 +294,7 @@ class WebRtcCallManager @Inject constructor(
         createWebRtcCall(mxCall).apply {
             offerSdp = callInviteContent.offer
         }
-        callAudioManager.startForCall(mxCall)
+        //callAudioManager.startForCall(mxCall)
         // Start background service with notification
         CallService.onIncomingCallRinging(
                 context = context,