mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +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 -->
|
<!-- 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. -->
|
||||||
|
|
||||||
|
|
|
@ -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)
|
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
|
||||||
|
|
Loading…
Reference in a new issue