Very basic audio speaker support

This commit is contained in:
Valere 2020-06-12 19:10:20 +02:00
parent 39f3a1c697
commit 248b9ff1e1
3 changed files with 124 additions and 1 deletions

View file

@ -14,6 +14,8 @@
<!-- Call feature --> <!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" /> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_CALL_LOG" />
<!-- Needed for voice call to toggle speaker on or off -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE` <!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE`
intent action. --> intent action. -->

View file

@ -0,0 +1,115 @@
/*
* 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.riotx.features.call
import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioManager
import im.vector.matrix.android.api.session.call.MxCall
import timber.log.Timber
class CallAudioManager(
val applicationContext: Context
) {
private val audioManager: AudioManager = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var savedIsSpeakerPhoneOn = false
private var savedIsMicrophoneMute = false
private var savedAudioMode = AudioManager.MODE_INVALID
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}")
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)
// TODO check if there are headsets?
if (mxCall.isVideoCall) {
setSpeakerphoneOn(true)
}
}
fun stop() {
Timber.v("## VOIP: AudioManager stopCall")
// Restore previously stored audio states.
setSpeakerphoneOn(savedIsSpeakerPhoneOn)
setMicrophoneMute(savedIsMicrophoneMute)
audioManager.mode = savedAudioMode
@Suppress("DEPRECATION")
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
/** Sets the speaker phone mode. */
private fun setSpeakerphoneOn(on: Boolean) {
Timber.v("## VOIP: AudioManager setSpeakerphoneOn $on")
val wasOn = audioManager.isSpeakerphoneOn
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
if (wasMuted == on) {
return
}
audioManager.isMicrophoneMute = on
audioManager.isMusicActive
}
/** true if the device has a telephony radio with data
* communication support. */
private fun isThisPhone(): Boolean {
return applicationContext.packageManager.hasSystemFeature(
PackageManager.FEATURE_TELEPHONY)
}
}

View file

@ -82,6 +82,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCallsListeners.remove(listener) currentCallsListeners.remove(listener)
} }
val audioManager = CallAudioManager(context.applicationContext)
data class CallContext( data class CallContext(
val mxCall: MxCall, val mxCall: MxCall,
@ -457,6 +459,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall") Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
val createdCall = sessionHolder.getSafeActiveSession()?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return val createdCall = sessionHolder.getSafeActiveSession()?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
val callContext = CallContext(createdCall) val callContext = CallContext(createdCall)
audioManager.startForCall(createdCall)
currentCall = callContext currentCall = callContext
executor.execute { executor.execute {
@ -489,11 +493,13 @@ class WebRtcPeerConnectionManager @Inject constructor(
if (currentCall != null) { if (currentCall != null) {
Timber.w("## VOIP TODO: Automatically reject incoming call?") Timber.w("## VOIP TODO: Automatically reject incoming call?")
mxCall.hangUp() mxCall.hangUp()
audioManager.stop()
return return
} }
val callContext = CallContext(mxCall) val callContext = CallContext(mxCall)
currentCall = callContext currentCall = callContext
audioManager.startForCall(mxCall)
executor.execute { executor.execute {
callContext.remoteCandidateSource = ReplaySubject.create() callContext.remoteCandidateSource = ReplaySubject.create()
} }
@ -538,6 +544,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun endCall() { fun endCall() {
currentCall?.mxCall?.hangUp() currentCall?.mxCall?.hangUp()
currentCall = null currentCall = null
audioManager.stop()
close() close()
} }
@ -602,7 +609,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
* property until the May 13, 2016 draft of the specification. * property until the May 13, 2016 draft of the specification.
*/ */
PeerConnection.PeerConnectionState.CLOSED -> { PeerConnection.PeerConnectionState.CLOSED -> {
} }
/** /**
* At least one of the ICE transports for the connection is in the "disconnected" state and none of * At least one of the ICE transports for the connection is in the "disconnected" state and none of