mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Very basic audio speaker support
This commit is contained in:
parent
39f3a1c697
commit
248b9ff1e1
3 changed files with 124 additions and 1 deletions
|
@ -14,6 +14,8 @@
|
|||
<!-- Call feature -->
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||
<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`
|
||||
intent action. -->
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -82,6 +82,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
currentCallsListeners.remove(listener)
|
||||
}
|
||||
|
||||
val audioManager = CallAudioManager(context.applicationContext)
|
||||
|
||||
data class CallContext(
|
||||
val mxCall: MxCall,
|
||||
|
||||
|
@ -457,6 +459,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
|
||||
val createdCall = sessionHolder.getSafeActiveSession()?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
||||
val callContext = CallContext(createdCall)
|
||||
|
||||
audioManager.startForCall(createdCall)
|
||||
currentCall = callContext
|
||||
|
||||
executor.execute {
|
||||
|
@ -489,11 +493,13 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
if (currentCall != null) {
|
||||
Timber.w("## VOIP TODO: Automatically reject incoming call?")
|
||||
mxCall.hangUp()
|
||||
audioManager.stop()
|
||||
return
|
||||
}
|
||||
|
||||
val callContext = CallContext(mxCall)
|
||||
currentCall = callContext
|
||||
audioManager.startForCall(mxCall)
|
||||
executor.execute {
|
||||
callContext.remoteCandidateSource = ReplaySubject.create()
|
||||
}
|
||||
|
@ -538,6 +544,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
fun endCall() {
|
||||
currentCall?.mxCall?.hangUp()
|
||||
currentCall = null
|
||||
audioManager.stop()
|
||||
close()
|
||||
}
|
||||
|
||||
|
@ -602,7 +609,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
* property until the May 13, 2016 draft of the specification.
|
||||
*/
|
||||
PeerConnection.PeerConnectionState.CLOSED -> {
|
||||
|
||||
}
|
||||
/**
|
||||
* At least one of the ICE transports for the connection is in the "disconnected" state and none of
|
||||
|
|
Loading…
Reference in a new issue