VoIP: continue refactoring

This commit is contained in:
ganfra 2020-11-26 12:07:32 +01:00
parent 7620aa4264
commit 1a9b0265dc
14 changed files with 219 additions and 249 deletions

View file

@ -153,7 +153,7 @@ interface VectorComponent {
fun pinLocker(): PinLocker
fun webRtcPeerConnectionManager(): WebRtcCallManager
fun webRtcCallManager(): WebRtcCallManager
@Component.Factory
interface Factory {

View file

@ -63,7 +63,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
override fun onCreate() {
super.onCreate()
notificationUtils = vectorComponent().notificationUtils()
callManager = vectorComponent().webRtcPeerConnectionManager()
callManager = vectorComponent().webRtcCallManager()
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext)
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
wiredHeadsetStateReceiver = WiredHeadsetStateReceiver.createAndRegister(this, this)

View file

@ -23,7 +23,7 @@ import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.session.call.CallState
import im.vector.app.features.call.utils.EglUtils
import org.matrix.android.sdk.api.session.call.MxCall
import im.vector.app.features.call.webrtc.WebRtcCall
import org.webrtc.RendererCommon
import org.webrtc.SurfaceViewRenderer
@ -32,26 +32,28 @@ class ActiveCallViewHolder {
private var activeCallPiP: SurfaceViewRenderer? = null
private var activeCallView: ActiveCallView? = null
private var pipWrapper: CardView? = null
private var activeCall: WebRtcCall? = null
private var activeCallPipInitialized = false
fun updateCall(activeCall: MxCall?, callManager: WebRtcCallManager) {
val hasActiveCall = activeCall?.state is CallState.Connected
fun updateCall(activeCall: WebRtcCall?) {
this.activeCall = activeCall
val hasActiveCall = activeCall?.mxCall?.state is CallState.Connected
if (hasActiveCall) {
val isVideoCall = activeCall?.isVideoCall == true
val isVideoCall = activeCall?.mxCall?.isVideoCall == true
if (isVideoCall) initIfNeeded()
activeCallView?.isVisible = !isVideoCall
pipWrapper?.isVisible = isVideoCall
activeCallPiP?.isVisible = isVideoCall
activeCallPiP?.let {
callManager.attachViewRenderers(null, it, null)
activeCall?.attachViewRenderers(null, it, null)
}
} else {
activeCallView?.isVisible = false
activeCallPiP?.isVisible = false
pipWrapper?.isVisible = false
activeCallPiP?.let {
callManager.detachRenderers(listOf(it))
activeCall?.detachRenderers(listOf(it))
}
}
}
@ -82,9 +84,9 @@ class ActiveCallViewHolder {
)
}
fun unBind(callManager: WebRtcCallManager) {
fun unBind() {
activeCallPiP?.let {
callManager.detachRenderers(listOf(it))
activeCall?.detachRenderers(listOf(it))
}
if (activeCallPipInitialized) {
activeCallPiP?.release()

View file

@ -18,6 +18,7 @@ package im.vector.app.features.call
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.session.call.MxCall
import javax.inject.Inject
@ -26,27 +27,27 @@ class SharedActiveCallViewModel @Inject constructor(
private val callManager: WebRtcCallManager
) : ViewModel() {
val activeCall: MutableLiveData<MxCall?> = MutableLiveData()
val activeCall: MutableLiveData<WebRtcCall?> = MutableLiveData()
val callStateListener = object : MxCall.StateListener {
val callStateListener = object : WebRtcCall.Listener {
override fun onStateUpdate(call: MxCall) {
if (activeCall.value?.callId == call.callId) {
activeCall.postValue(call)
activeCall.postValue(callManager.getCallById(call.callId))
}
}
}
private val listener = object : WebRtcCallManager.CurrentCallListener {
override fun onCurrentCallChange(call: MxCall?) {
activeCall.value?.removeListener(callStateListener)
override fun onCurrentCallChange(call: WebRtcCall?) {
activeCall.value?.mxCall?.removeListener(callStateListener)
activeCall.postValue(call)
call?.addListener(callStateListener)
}
}
init {
activeCall.postValue(callManager.currentCall?.mxCall)
activeCall.postValue(callManager.currentCall)
callManager.addCurrentCallListener(listener)
}

View file

@ -45,6 +45,8 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs
@ -52,8 +54,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_call.*
import org.matrix.android.sdk.api.session.call.CallState
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.session.call.MxCallDetail
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
@ -211,7 +211,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
}
override fun onDestroy() {
callManager.detachRenderers(listOf(pipRenderer, fullscreenRenderer))
callManager.getCallById(callArgs.callId)?.detachRenderers(listOf(pipRenderer, fullscreenRenderer))
if (surfaceRenderersAreInitialized) {
pipRenderer.release()
fullscreenRenderer.release()
@ -234,7 +234,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
callConnectingProgress.isVisible = false
when (callState) {
is CallState.Idle,
is CallState.Dialing -> {
is CallState.Dialing -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_ring)
@ -248,14 +248,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
configureCallInfo(state)
}
is CallState.Answering -> {
is CallState.Answering -> {
callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting)
callConnectingProgress.isVisible = true
configureCallInfo(state)
}
is CallState.Connected -> {
is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (callArgs.isVideoCall) {
callVideoGroup.isVisible = true
@ -276,12 +276,12 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
callConnectingProgress.isVisible = true
}
// ensure all attached?
callManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
callManager.getCallById(callArgs.callId)?.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
}
is CallState.Terminated -> {
is CallState.Terminated -> {
finish()
}
null -> {
null -> {
}
}
}
@ -326,7 +326,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
pipRenderer.setEnableHardwareScaler(true /* enabled */)
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
callManager.attachViewRenderers(pipRenderer, fullscreenRenderer,
callManager.getCallById(callArgs.callId)?.attachViewRenderers(pipRenderer, fullscreenRenderer,
intent.getStringExtra(EXTRA_MODE)?.takeIf { isFirstCreation() })
pipRenderer.setOnClickListener {
@ -338,14 +338,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private fun handleViewEvents(event: VectorCallViewEvents?) {
Timber.v("## VOIP handleViewEvents $event")
when (event) {
VectorCallViewEvents.DismissNoCall -> {
VectorCallViewEvents.DismissNoCall -> {
CallService.onNoActiveCall(this)
finish()
}
is VectorCallViewEvents.ConnectionTimeout -> {
onErrorTimoutConnect(event.turn)
}
null -> {
null -> {
}
}
}

View file

@ -107,7 +107,7 @@ class VectorCallViewModel @AssistedInject constructor(
}
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
override fun onCurrentCallChange(call: MxCall?) {
override fun onCurrentCallChange(call: WebRtcCall?) {
// we need to check the state
if (call == null) {
// we should dismiss, e.g handled by other session?
@ -155,9 +155,9 @@ class VectorCallViewModel @AssistedInject constructor(
otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized,
soundDevice = currentSoundDevice,
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
isFrontCamera = callManager.currentCameraType() == CameraType.FRONT,
canSwitchCamera = callManager.canSwitchCamera(),
isHD = webRtcCall.mxCall.isVideoCall && callManager.currentCaptureFormat() is CaptureFormat.HD
isFrontCamera = call?.currentCameraType() == CameraType.FRONT,
canSwitchCamera = call?.canSwitchCamera() ?: false,
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD
)
}
}

View file

@ -27,17 +27,22 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
companion object {
const val EXTRA_CALL_ACTION_KEY = "EXTRA_CALL_ACTION_KEY"
const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
const val CALL_ACTION_REJECT = 0
}
override fun onReceive(context: Context, intent: Intent?) {
val peerConnectionManager = (context.applicationContext as? HasVectorInjector)
val webRtcCallManager = (context.applicationContext as? HasVectorInjector)
?.injector()
?.webRtcPeerConnectionManager()
?.webRtcCallManager()
?: return
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
CALL_ACTION_REJECT -> onCallRejectClicked(peerConnectionManager)
CALL_ACTION_REJECT -> {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
onCallRejectClicked(webRtcCallManager, callId)
}
}
// Not sure why this should be needed
@ -48,9 +53,9 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
// context.stopService(Intent(context, CallHeadsUpService::class.java))
}
private fun onCallRejectClicked(callManager: WebRtcCallManager) {
private fun onCallRejectClicked(callManager: WebRtcCallManager, callId: String) {
Timber.d("onCallRejectClicked")
callManager.endCall()
callManager.getCallById(callId)?.endCall()
}
// private fun onCallAnswerClicked(context: Context) {

View file

@ -19,6 +19,7 @@ package im.vector.app.features.call.webrtc
import im.vector.app.features.call.CallAudioManager
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
@ -132,9 +133,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall,
* It is, however, possible that the ICE agent did find compatible connections for some components.
*/
PeerConnection.IceConnectionState.FAILED -> {
// I should not hangup here..
// because new candidates could arrive
// webRtcCall.mxCall.hangUp()
webRtcCall.endCall(true, CallHangupContent.Reason.ICE_FAILED)
}
/**
* The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components.

View file

@ -42,6 +42,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
@ -88,9 +89,9 @@ class WebRtcCall(val mxCall: MxCall,
private val dispatcher: CoroutineContext,
private val sessionProvider: Provider<Session?>,
private val peerConnectionFactoryProvider: Provider<PeerConnectionFactory?>,
private val onCallEnded: (WebRtcCall) -> Unit): MxCall.StateListener {
private val onCallEnded: (WebRtcCall) -> Unit) : MxCall.StateListener {
interface Listener: MxCall.StateListener {
interface Listener : MxCall.StateListener {
fun onCaptureStateChanged() {}
fun onCameraChange() {}
}
@ -245,7 +246,7 @@ class WebRtcCall(val mxCall: MxCall,
isVideo = mxCall.isVideoCall,
roomName = name,
roomId = mxCall.roomId,
matrixId = session?.myUserId ?:"",
matrixId = session?.myUserId ?: "",
callId = mxCall.callId)
}
@ -461,8 +462,13 @@ class WebRtcCall(val mxCall: MxCall,
?: backCamera?.also { cameraInUse = backCamera }
?: null.also { cameraInUse = null }
listeners.forEach {
tryOrNull { it.onCameraChange() }
}
if (camera != null) {
val videoCapturer = cameraIterator.createCapturer(camera.name, object : CameraEventsHandlerAdapter() {
override fun onFirstFrameAvailable() {
super.onFirstFrameAvailable()
videoCapturerIsInError = false
@ -470,12 +476,25 @@ class WebRtcCall(val mxCall: MxCall,
override fun onCameraClosed() {
super.onCameraClosed()
Timber.v("onCameraClosed")
// This could happen if you open the camera app in chat
// We then register in order to restart capture as soon as the camera is available again
videoCapturerIsInError = true
val cameraManager = context.getSystemService<CameraManager>()
cameraAvailabilityCallback = object : CameraManager.AvailabilityCallback() {
override fun onCameraUnavailable(cameraId: String) {
super.onCameraUnavailable(cameraId)
Timber.v("On camera unavailable: $cameraId")
}
override fun onCameraAccessPrioritiesChanged() {
super.onCameraAccessPrioritiesChanged()
Timber.v("onCameraAccessPrioritiesChanged")
}
override fun onCameraAvailable(cameraId: String) {
Timber.v("On camera available: $cameraId")
if (cameraId == camera.name) {
videoCapturer?.startCapture(currentCaptureFormat.width, currentCaptureFormat.height, currentCaptureFormat.fps)
cameraManager?.unregisterAvailabilityCallback(this)
@ -505,11 +524,9 @@ class WebRtcCall(val mxCall: MxCall,
}
fun setCaptureFormat(format: CaptureFormat) {
GlobalScope.launch(dispatcher) {
Timber.v("## VOIP setCaptureFormat $format")
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
currentCaptureFormat = format
}
Timber.v("## VOIP setCaptureFormat $format")
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
currentCaptureFormat = format
}
private fun updateMuteStatus() {
@ -565,31 +582,41 @@ class WebRtcCall(val mxCall: MxCall,
}
fun canSwitchCamera(): Boolean {
return availableCamera.size > 0
return availableCamera.size > 1
}
private fun getOppositeCameraIfAny(): CameraProxy? {
val currentCamera = cameraInUse ?: return null
return if (currentCamera.type == CameraType.FRONT) {
availableCamera.firstOrNull { it.type == CameraType.BACK }
} else {
availableCamera.firstOrNull { it.type == CameraType.FRONT }
}
}
fun switchCamera() {
Timber.v("## VOIP switchCamera")
if (!canSwitchCamera()) return
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
videoCapturer?.switchCamera(object : CameraVideoCapturer.CameraSwitchHandler {
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
cameraInUse = availableCamera.first { if (isFrontCamera) it.type == CameraType.FRONT else it.type == CameraType.BACK }
localSurfaceRenderers.forEach {
it.get()?.setMirror(isFrontCamera)
}
listeners.forEach {
tryOrNull { it.onCameraChange() }
}
val oppositeCamera = getOppositeCameraIfAny() ?: return
videoCapturer?.switchCamera(
object : CameraVideoCapturer.CameraSwitchHandler {
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
cameraInUse = oppositeCamera
localSurfaceRenderers.forEach {
it.get()?.setMirror(isFrontCamera)
}
listeners.forEach {
tryOrNull { it.onCameraChange() }
}
}
}
override fun onCameraSwitchError(errorDescription: String?) {
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
}
})
override fun onCameraSwitchError(errorDescription: String?) {
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
}
}, oppositeCamera.name
)
}
}
@ -665,6 +692,9 @@ class WebRtcCall(val mxCall: MxCall,
}
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
if(mxCall.state == CallState.Terminated){
return
}
mxCall.state = CallState.Terminated
//Close tracks ASAP
localVideoTrack?.setEnabled(false)
@ -704,6 +734,7 @@ class WebRtcCall(val mxCall: MxCall,
try {
peerConnection?.awaitSetRemoteDescription(sdp)
} catch (failure: Throwable) {
endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
return@launch
}
if (mxCall.opponentPartyId?.hasValue().orFalse()) {

View file

@ -31,7 +31,6 @@ import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallListener
@ -67,7 +66,7 @@ class WebRtcCallManager @Inject constructor(
get() = activeSessionDataSource.currentValue?.orNull()
interface CurrentCallListener {
fun onCurrentCallChange(call: MxCall?)
fun onCurrentCallChange(call: WebRtcCall?)
fun onCaptureStateChanged() {}
fun onAudioDevicesChange() {}
fun onCameraChange() {}
@ -118,31 +117,32 @@ class WebRtcCallManager @Inject constructor(
set(value) {
field = value
currentCallsListeners.forEach {
tryOrNull { it.onCurrentCallChange(value?.mxCall) }
tryOrNull { it.onCurrentCallChange(value) }
}
}
private val callsByCallId = HashMap<String, WebRtcCall>()
private val callsByRoomId = HashMap<String, ArrayList<WebRtcCall>>()
fun getCallById(callId: String): WebRtcCall? {
return callsByCallId[callId]
}
fun headSetButtonTapped() {
Timber.v("## VOIP headSetButtonTapped")
val call = currentCall?.mxCall ?: return
if (call.state is CallState.LocalRinging) {
// accept call
acceptIncomingCall()
}
if (call.state is CallState.Connected) {
// end call?
endCall()
}
fun getCallsByRoomId(roomId: String): List<WebRtcCall> {
return callsByRoomId[roomId] ?: emptyList()
}
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
currentCall?.attachViewRenderers(localViewRenderer, remoteViewRenderer, mode)
fun headSetButtonTapped() {
Timber.v("## VOIP headSetButtonTapped")
val call = currentCall ?: return
if (call.mxCall.state is CallState.LocalRinging) {
// accept call
call.acceptIncomingCall()
}
if (call.mxCall.state is CallState.Connected) {
// end call?
call.endCall()
}
}
private fun createPeerConnectionFactoryIfNeeded() {
@ -174,20 +174,13 @@ class WebRtcCallManager @Inject constructor(
.createPeerConnectionFactory()
}
fun acceptIncomingCall() {
currentCall?.acceptIncomingCall()
}
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
currentCall?.detachRenderers(renderers)
}
private fun onCallEnded(call: WebRtcCall) {
Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}")
CallService.onNoActiveCall(context)
callAudioManager.stop()
currentCall = null
callsByCallId.remove(call.mxCall.callId)
callsByRoomId[call.mxCall.roomId]?.remove(call)
// This must be done in this thread
executor.execute {
if (currentCall == null) {
@ -231,9 +224,48 @@ class WebRtcCallManager @Inject constructor(
currentCall?.onCallIceCandidateReceived(iceCandidatesContent)
}
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
val webRtcCall = WebRtcCall(
mxCall = mxCall,
callAudioManager = callAudioManager,
rootEglBase = rootEglBase,
context = context,
dispatcher = dispatcher,
peerConnectionFactoryProvider = {
createPeerConnectionFactoryIfNeeded()
peerConnectionFactory
},
sessionProvider = { currentSession },
onCallEnded = this::onCallEnded
)
currentCall = webRtcCall
callsByCallId[mxCall.callId] = webRtcCall
callsByRoomId.getOrPut(mxCall.roomId, { ArrayList() }).add(webRtcCall)
return webRtcCall
}
fun acceptIncomingCall() {
currentCall?.acceptIncomingCall()
}
fun endCall(originatedByMe: Boolean = true) {
currentCall?.endCall(originatedByMe)
}
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
Timber.v("## VOIP onWiredDeviceEvent $event")
currentCall ?: return
// sometimes we received un-wanted unplugged...
callAudioManager.wiredStateChange(event)
}
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
Timber.v("## VOIP onWirelessDeviceEvent $event")
callAudioManager.bluetoothStateChange(event.plugged)
}
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
// to simplify we only treat one call at a time, and ignore others
if (currentCall != null) {
Timber.w("## VOIP receiving incoming call while already in call?")
// Just ignore, maybe we could answer from other session?
@ -267,74 +299,11 @@ class WebRtcCallManager @Inject constructor(
}
}
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
val webRtcCall = WebRtcCall(
mxCall = mxCall,
callAudioManager = callAudioManager,
rootEglBase = rootEglBase,
context = context,
dispatcher = dispatcher,
peerConnectionFactoryProvider = {
createPeerConnectionFactoryIfNeeded()
peerConnectionFactory
},
sessionProvider = { currentSession },
onCallEnded = this::onCallEnded
)
currentCall = webRtcCall
callsByCallId[mxCall.callId] = webRtcCall
return webRtcCall
}
fun muteCall(muted: Boolean) {
currentCall?.muteCall(muted)
}
fun enableVideo(enabled: Boolean) {
currentCall?.enableVideo(enabled)
}
fun switchCamera() {
currentCall?.switchCamera()
}
fun canSwitchCamera(): Boolean {
return currentCall?.canSwitchCamera() ?: false
}
fun currentCameraType(): CameraType? {
return currentCall?.currentCameraType()
}
fun setCaptureFormat(format: CaptureFormat) {
currentCall?.setCaptureFormat(format)
}
fun currentCaptureFormat(): CaptureFormat {
return currentCall?.currentCaptureFormat() ?: CaptureFormat.HD
}
fun endCall(originatedByMe: Boolean = true) {
currentCall?.endCall(originatedByMe)
}
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
Timber.v("## VOIP onWiredDeviceEvent $event")
currentCall ?: return
// sometimes we received un-wanted unplugged...
callAudioManager.wiredStateChange(event)
}
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
Timber.v("## VOIP onWirelessDeviceEvent $event")
callAudioManager.bluetoothStateChange(event.plugged)
}
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
val call = currentCall ?: return
if (call.mxCall.callId != callAnswerContent.callId) return Unit.also {
Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
}
val call = callsByCallId[callAnswerContent.callId]
?: return Unit.also {
Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
}
val mxCall = call.mxCall
// Update service state
val name = currentSession?.getUser(mxCall.opponentUserId)?.getBestName()
@ -351,48 +320,49 @@ class WebRtcCallManager @Inject constructor(
}
override fun onCallHangupReceived(callHangupContent: CallHangupContent) {
val call = currentCall ?: return
// Remote echos are filtered, so it's only remote hangups that i will get here
if (call.mxCall.callId != callHangupContent.callId) return Unit.also {
Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
}
endCall(false)
val call = callsByCallId[callHangupContent.callId]
?: return Unit.also {
Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
}
call.endCall(false)
}
override fun onCallRejectReceived(callRejectContent: CallRejectContent) {
val call = currentCall ?: return
// Remote echos are filtered, so it's only remote hangups that i will get here
if (call.mxCall.callId != callRejectContent.callId) return Unit.also {
Timber.w("onCallRejected for non active call? ${callRejectContent.callId}")
}
endCall(false)
val call = callsByCallId[callRejectContent.callId]
?: return Unit.also {
Timber.w("onCallRejectReceived for non active call? ${callRejectContent.callId}")
}
call.endCall(false)
}
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) {
val call = currentCall ?: return
if (call.mxCall.callId != callSelectAnswerContent.callId) return Unit.also {
Timber.w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
}
val call = callsByCallId[callSelectAnswerContent.callId]
?: return Unit.also {
Timber.w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
}
val selectedPartyId = callSelectAnswerContent.selectedPartyId
if (selectedPartyId != call.mxCall.ourPartyId) {
Timber.i("Got select_answer for party ID ${selectedPartyId}: we are party ID ${call.mxCall.ourPartyId}.");
// The other party has picked somebody else's answer
endCall(false)
call.endCall(false)
}
}
override fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) {
val call = currentCall ?: return
if (call.mxCall.callId != callNegotiateContent.callId) return Unit.also {
Timber.w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
}
val call = callsByCallId[callNegotiateContent.callId]
?: return Unit.also {
Timber.w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
}
call.onCallNegotiateReceived(callNegotiateContent)
}
override fun onCallManagedByOtherSession(callId: String) {
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
currentCall = null
callsByCallId.remove(callId)
val webRtcCall = callsByCallId.remove(callId)
if (webRtcCall != null) {
callsByRoomId[webRtcCall.mxCall.roomId]?.remove(webRtcCall)
}
CallService.onNoActiveCall(context)
// did we start background sync? so we should stop it
@ -405,24 +375,4 @@ class WebRtcCallManager @Inject constructor(
}
}
}
/**
* Indicates whether we are 'on hold' to the remote party (ie. if true,
* they cannot hear us). Note that this will return true when we put the
* remote on hold too due to the way hold is implemented (since we don't
* wish to play hold music when we put a call on hold, we use 'inactive'
* rather than 'sendonly')
* @returns true if the other party has put us on hold
*/
fun isLocalOnHold(): Boolean {
return currentCall?.isLocalOnHold().orFalse()
}
fun isRemoteOnHold(): Boolean {
return currentCall?.remoteOnHold.orFalse()
}
fun setRemoteOnHold(onHold: Boolean) {
currentCall?.updateRemoteOnHold(onHold)
}
}

View file

@ -120,7 +120,7 @@ class HomeDetailFragment @Inject constructor(
sharedCallActionViewModel
.activeCall
.observe(viewLifecycleOwner, Observer {
activeCallViewHolder.updateCall(it, callManager)
activeCallViewHolder.updateCall(it)
invalidateOptionsMenu()
})
}
@ -331,10 +331,10 @@ class HomeDetailFragment @Inject constructor(
VectorCallActivity.newIntent(
context = requireContext(),
callId = call.callId,
roomId = call.roomId,
otherUserId = call.opponentUserId,
isIncomingCall = !call.isOutgoing,
isVideoCall = call.isVideoCall,
roomId = call.mxCall.roomId,
otherUserId = call.mxCall.opponentUserId,
isIncomingCall = !call.mxCall.isOutgoing,
isVideoCall = call.mxCall.isVideoCall,
mode = null
).let {
startActivity(it)

View file

@ -315,7 +315,7 @@ class RoomDetailFragment @Inject constructor(
sharedCallActionViewModel
.activeCall
.observe(viewLifecycleOwner, Observer {
activeCallViewHolder.updateCall(it, callManager)
activeCallViewHolder.updateCall(it)
invalidateOptionsMenu()
})
@ -514,7 +514,7 @@ class RoomDetailFragment @Inject constructor(
}
override fun onDestroy() {
activeCallViewHolder.unBind(callManager)
activeCallViewHolder.unBind()
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
super.onDestroy()
}
@ -712,7 +712,7 @@ class RoomDetailFragment @Inject constructor(
val activeCall = sharedCallActionViewModel.activeCall.value
if (activeCall != null) {
// resume existing if same room, if not prompt to kill and then restart new call?
if (activeCall.roomId == roomDetailArgs.roomId) {
if (activeCall.mxCall.roomId == roomDetailArgs.roomId) {
onTapToReturnToCall()
}
// else {
@ -1961,10 +1961,10 @@ class RoomDetailFragment @Inject constructor(
VectorCallActivity.newIntent(
context = requireContext(),
callId = call.callId,
roomId = call.roomId,
otherUserId = call.opponentUserId,
isIncomingCall = !call.isOutgoing,
isVideoCall = call.isVideoCall,
roomId = call.mxCall.roomId,
otherUserId = call.mxCall.opponentUserId,
isIncomingCall = !call.mxCall.isOutgoing,
isVideoCall = call.mxCall.isVideoCall,
mode = null
).let {
startActivity(it)

View file

@ -565,8 +565,8 @@ class RoomDetailViewModel @AssistedInject constructor(
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> true
R.id.voice_call,
R.id.video_call -> true // always show for discoverability
R.id.hangup_call -> callManager.currentCall != null
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
R.id.search -> true
else -> false
}

View file

@ -296,7 +296,6 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.priority = NotificationCompat.PRIORITY_HIGH
//
val requestId = Random.nextInt(1000)
// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
val contentIntent = VectorCallActivity.newIntent(
@ -326,16 +325,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
)
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
}
// val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = PendingIntent.getBroadcast(
context,
requestId + 1,
rejectCallActionReceiver,
PendingIntent.FLAG_UPDATE_CURRENT
)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
builder.addAction(
NotificationCompat.Action(
@ -375,8 +365,6 @@ class NotificationUtils @Inject constructor(private val context: Context,
.setLights(accentColor, 500, 500)
.setOngoing(true)
val requestId = Random.nextInt(1000)
val contentIntent = VectorCallActivity.newIntent(
context = context,
callId = callId,
@ -390,16 +378,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
}
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
}
val rejectCallPendingIntent = PendingIntent.getBroadcast(
context,
requestId + 1,
rejectCallActionReceiver,
PendingIntent.FLAG_UPDATE_CURRENT
)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
builder.addAction(
NotificationCompat.Action(
@ -446,17 +425,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
builder.setOngoing(true)
}
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
data = Uri.parse("mxcall://end?$callId")
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
}
val rejectCallPendingIntent = PendingIntent.getBroadcast(
context,
System.currentTimeMillis().toInt(),
rejectCallActionReceiver,
PendingIntent.FLAG_UPDATE_CURRENT
)
val rejectCallPendingIntent = buildRejectCallPendingIntent(callId)
builder.addAction(
NotificationCompat.Action(
@ -476,6 +445,19 @@ class NotificationUtils @Inject constructor(private val context: Context,
return builder.build()
}
private fun buildRejectCallPendingIntent(callId: String): PendingIntent {
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ID, callId)
putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
}
return PendingIntent.getBroadcast(
context,
System.currentTimeMillis().toInt(),
rejectCallActionReceiver,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
/**
* Build a temporary (because service will be stopped just after) notification for the CallService, when a call is ended
*/