Merge pull request #3587 from vector-im/feature/fga/fix_video_call_crash
Fix crash after video call
This commit is contained in:
commit
a8bef415b7
|
@ -0,0 +1 @@
|
||||||
|
Fix crash after video call.
|
|
@ -83,9 +83,9 @@ import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
private const val STREAM_ID = "ARDAMS"
|
private const val STREAM_ID = "userMedia"
|
||||||
private const val AUDIO_TRACK_ID = "ARDAMSa0"
|
private const val AUDIO_TRACK_ID = "${STREAM_ID}a0"
|
||||||
private const val VIDEO_TRACK_ID = "ARDAMSv0"
|
private const val VIDEO_TRACK_ID = "${STREAM_ID}v0"
|
||||||
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
|
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
|
||||||
|
|
||||||
class WebRtcCall(
|
class WebRtcCall(
|
||||||
|
@ -274,12 +274,77 @@ class WebRtcCall(
|
||||||
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this))
|
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
/**
|
||||||
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
* Without consultation
|
||||||
localSurfaceRenderers.addIfNeeded(localViewRenderer)
|
*/
|
||||||
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
|
fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
||||||
|
|
||||||
sessionScope?.launch(dispatcher) {
|
sessionScope?.launch(dispatcher) {
|
||||||
|
mxCall.transfer(
|
||||||
|
targetUserId = targetUserId,
|
||||||
|
targetRoomId = targetRoomId,
|
||||||
|
createCallId = CallIdGenerator.generate(),
|
||||||
|
awaitCallId = null
|
||||||
|
)
|
||||||
|
endCall(sendEndSignaling = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With consultation
|
||||||
|
*/
|
||||||
|
fun transferToCall(transferTargetCall: WebRtcCall) {
|
||||||
|
sessionScope?.launch(dispatcher) {
|
||||||
|
val newCallId = CallIdGenerator.generate()
|
||||||
|
transferTargetCall.mxCall.transfer(
|
||||||
|
targetUserId = mxCall.opponentUserId,
|
||||||
|
targetRoomId = null,
|
||||||
|
createCallId = null,
|
||||||
|
awaitCallId = newCallId
|
||||||
|
)
|
||||||
|
mxCall.transfer(
|
||||||
|
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
||||||
|
targetRoomId = null,
|
||||||
|
createCallId = newCallId,
|
||||||
|
awaitCallId = null
|
||||||
|
)
|
||||||
|
endCall(sendEndSignaling = false)
|
||||||
|
transferTargetCall.endCall(sendEndSignaling = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun acceptIncomingCall() {
|
||||||
|
sessionScope?.launch {
|
||||||
|
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
||||||
|
if (mxCall.state == CallState.LocalRinging) {
|
||||||
|
internalAcceptIncomingCall()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DTMF digit to the other party
|
||||||
|
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
|
||||||
|
*/
|
||||||
|
fun sendDtmfDigit(digit: String) {
|
||||||
|
sessionScope?.launch {
|
||||||
|
for (sender in peerConnection?.senders.orEmpty()) {
|
||||||
|
if (sender.track()?.kind() == "audio" && sender.dtmf()?.canInsertDtmf() == true) {
|
||||||
|
try {
|
||||||
|
sender.dtmf()?.insertDtmf(digit, 100, 70)
|
||||||
|
return@launch
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.v("Fail to send Dtmf digit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
||||||
|
sessionScope?.launch(dispatcher) {
|
||||||
|
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
||||||
|
localSurfaceRenderers.addIfNeeded(localViewRenderer)
|
||||||
|
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
|
||||||
when (mode) {
|
when (mode) {
|
||||||
VectorCallActivity.INCOMING_ACCEPT -> {
|
VectorCallActivity.INCOMING_ACCEPT -> {
|
||||||
internalAcceptIncomingCall()
|
internalAcceptIncomingCall()
|
||||||
|
@ -299,67 +364,31 @@ class WebRtcCall(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private suspend fun attachViewRenderersInternal() = withContext(dispatcher) {
|
||||||
* Without consultation
|
// render local video in pip view
|
||||||
*/
|
localSurfaceRenderers.forEach { renderer ->
|
||||||
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
renderer.get()?.let { pipSurface ->
|
||||||
mxCall.transfer(
|
pipSurface.setMirror(cameraInUse?.type == CameraType.FRONT)
|
||||||
targetUserId = targetUserId,
|
// no need to check if already added, addSink is checking that
|
||||||
targetRoomId = targetRoomId,
|
localVideoTrack?.addSink(pipSurface)
|
||||||
createCallId = CallIdGenerator.generate(),
|
|
||||||
awaitCallId = null
|
|
||||||
)
|
|
||||||
endCall(sendEndSignaling = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* With consultation
|
|
||||||
*/
|
|
||||||
suspend fun transferToCall(transferTargetCall: WebRtcCall) {
|
|
||||||
val newCallId = CallIdGenerator.generate()
|
|
||||||
transferTargetCall.mxCall.transfer(
|
|
||||||
targetUserId = mxCall.opponentUserId,
|
|
||||||
targetRoomId = null,
|
|
||||||
createCallId = null,
|
|
||||||
awaitCallId = newCallId
|
|
||||||
)
|
|
||||||
mxCall.transfer(
|
|
||||||
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
|
||||||
targetRoomId = null,
|
|
||||||
createCallId = newCallId,
|
|
||||||
awaitCallId = null
|
|
||||||
)
|
|
||||||
endCall(sendEndSignaling = false)
|
|
||||||
transferTargetCall.endCall(sendEndSignaling = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun acceptIncomingCall() {
|
|
||||||
sessionScope?.launch {
|
|
||||||
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
|
||||||
if (mxCall.state == CallState.LocalRinging) {
|
|
||||||
internalAcceptIncomingCall()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// If remote track exists, then sink it to surface
|
||||||
* Sends a DTMF digit to the other party
|
remoteSurfaceRenderers.forEach { renderer ->
|
||||||
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
|
renderer.get()?.let { participantSurface ->
|
||||||
*/
|
remoteVideoTrack?.addSink(participantSurface)
|
||||||
fun sendDtmfDigit(digit: String) {
|
|
||||||
for (sender in peerConnection?.senders.orEmpty()) {
|
|
||||||
if (sender.track()?.kind() == "audio" && sender.dtmf()?.canInsertDtmf() == true) {
|
|
||||||
try {
|
|
||||||
sender.dtmf()?.insertDtmf(digit, 100, 70)
|
|
||||||
return
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.v("Fail to send Dtmf digit")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
|
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
|
||||||
|
sessionScope?.launch(dispatcher) {
|
||||||
|
detachRenderersInternal(renderers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun detachRenderersInternal(renderers: List<SurfaceViewRenderer>?) = withContext(dispatcher) {
|
||||||
Timber.v("## VOIP detachRenderers")
|
Timber.v("## VOIP detachRenderers")
|
||||||
if (renderers.isNullOrEmpty()) {
|
if (renderers.isNullOrEmpty()) {
|
||||||
// remove all sinks
|
// remove all sinks
|
||||||
|
@ -452,24 +481,6 @@ class WebRtcCall(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun attachViewRenderersInternal() {
|
|
||||||
// render local video in pip view
|
|
||||||
localSurfaceRenderers.forEach { renderer ->
|
|
||||||
renderer.get()?.let { pipSurface ->
|
|
||||||
pipSurface.setMirror(this.cameraInUse?.type == CameraType.FRONT)
|
|
||||||
// no need to check if already added, addSink is checking that
|
|
||||||
localVideoTrack?.addSink(pipSurface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If remote track exists, then sink it to surface
|
|
||||||
remoteSurfaceRenderers.forEach { renderer ->
|
|
||||||
renderer.get()?.let { participantSurface ->
|
|
||||||
remoteVideoTrack?.addSink(participantSurface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getTurnServer(): TurnServerResponse? {
|
private suspend fun getTurnServer(): TurnServerResponse? {
|
||||||
return tryOrNull {
|
return tryOrNull {
|
||||||
sessionProvider.get()?.callSignalingService()?.getTurnServer()
|
sessionProvider.get()?.callSignalingService()?.getTurnServer()
|
||||||
|
@ -580,9 +591,11 @@ class WebRtcCall(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCaptureFormat(format: CaptureFormat) {
|
fun setCaptureFormat(format: CaptureFormat) {
|
||||||
Timber.v("## VOIP setCaptureFormat $format")
|
sessionScope?.launch(dispatcher) {
|
||||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
Timber.v("## VOIP setCaptureFormat $format")
|
||||||
currentCaptureFormat = format
|
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||||
|
currentCaptureFormat = format
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMuteStatus() {
|
private fun updateMuteStatus() {
|
||||||
|
@ -645,13 +658,17 @@ class WebRtcCall(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun muteCall(muted: Boolean) {
|
fun muteCall(muted: Boolean) {
|
||||||
micMuted = muted
|
sessionScope?.launch(dispatcher) {
|
||||||
updateMuteStatus()
|
micMuted = muted
|
||||||
|
updateMuteStatus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableVideo(enabled: Boolean) {
|
fun enableVideo(enabled: Boolean) {
|
||||||
videoMuted = !enabled
|
sessionScope?.launch(dispatcher) {
|
||||||
updateMuteStatus()
|
videoMuted = !enabled
|
||||||
|
updateMuteStatus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canSwitchCamera(): Boolean {
|
fun canSwitchCamera(): Boolean {
|
||||||
|
@ -668,28 +685,30 @@ class WebRtcCall(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchCamera() {
|
fun switchCamera() {
|
||||||
Timber.v("## VOIP switchCamera")
|
sessionScope?.launch(dispatcher) {
|
||||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
Timber.v("## VOIP switchCamera")
|
||||||
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||||
videoCapturer?.switchCamera(
|
val oppositeCamera = getOppositeCameraIfAny() ?: return@launch
|
||||||
object : CameraVideoCapturer.CameraSwitchHandler {
|
videoCapturer?.switchCamera(
|
||||||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
object : CameraVideoCapturer.CameraSwitchHandler {
|
||||||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
||||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||||
cameraInUse = oppositeCamera
|
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||||
localSurfaceRenderers.forEach {
|
cameraInUse = oppositeCamera
|
||||||
it.get()?.setMirror(isFrontCamera)
|
localSurfaceRenderers.forEach {
|
||||||
|
it.get()?.setMirror(isFrontCamera)
|
||||||
|
}
|
||||||
|
listeners.forEach {
|
||||||
|
tryOrNull { it.onCameraChanged() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
listeners.forEach {
|
|
||||||
tryOrNull { it.onCameraChanged() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCameraSwitchError(errorDescription: String?) {
|
override fun onCameraSwitchError(errorDescription: String?) {
|
||||||
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
||||||
}
|
}
|
||||||
}, oppositeCamera.name
|
}, oppositeCamera.name
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -718,11 +737,12 @@ class WebRtcCall(
|
||||||
return currentCaptureFormat
|
return currentCaptureFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun release() {
|
private suspend fun release() {
|
||||||
listeners.clear()
|
listeners.clear()
|
||||||
mxCall.removeListener(this)
|
mxCall.removeListener(this)
|
||||||
timer.stop()
|
timer.stop()
|
||||||
timer.tickListener = null
|
timer.tickListener = null
|
||||||
|
detachRenderersInternal(null)
|
||||||
videoCapturer?.stopCapture()
|
videoCapturer?.stopCapture()
|
||||||
videoCapturer?.dispose()
|
videoCapturer?.dispose()
|
||||||
videoCapturer = null
|
videoCapturer = null
|
||||||
|
@ -736,6 +756,8 @@ class WebRtcCall(
|
||||||
localAudioTrack = null
|
localAudioTrack = null
|
||||||
localVideoSource = null
|
localVideoSource = null
|
||||||
localVideoTrack = null
|
localVideoTrack = null
|
||||||
|
remoteAudioTrack = null
|
||||||
|
remoteVideoTrack = null
|
||||||
cameraAvailabilityCallback = null
|
cameraAvailabilityCallback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -745,7 +767,7 @@ class WebRtcCall(
|
||||||
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
|
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
|
||||||
Timber.e("## VOIP StreamObserver weird looking stream: $stream")
|
Timber.e("## VOIP StreamObserver weird looking stream: $stream")
|
||||||
// TODO maybe do something more??
|
// TODO maybe do something more??
|
||||||
mxCall.hangUp()
|
endCall(true)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
if (stream.audioTracks.size == 1) {
|
if (stream.audioTracks.size == 1) {
|
||||||
|
@ -774,27 +796,27 @@ class WebRtcCall(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||||
if (mxCall.state == CallState.Terminated) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Close tracks ASAP
|
|
||||||
localVideoTrack?.setEnabled(false)
|
|
||||||
localVideoTrack?.setEnabled(false)
|
|
||||||
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
|
||||||
val cameraManager = context.getSystemService<CameraManager>()!!
|
|
||||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
|
||||||
}
|
|
||||||
val wasRinging = mxCall.state is CallState.LocalRinging
|
|
||||||
mxCall.state = CallState.Terminated
|
|
||||||
sessionScope?.launch(dispatcher) {
|
sessionScope?.launch(dispatcher) {
|
||||||
|
if (mxCall.state == CallState.Terminated) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
// Close tracks ASAP
|
||||||
|
localVideoTrack?.setEnabled(false)
|
||||||
|
localVideoTrack?.setEnabled(false)
|
||||||
|
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||||
|
val cameraManager = context.getSystemService<CameraManager>()!!
|
||||||
|
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||||
|
}
|
||||||
|
val wasRinging = mxCall.state is CallState.LocalRinging
|
||||||
|
mxCall.state = CallState.Terminated
|
||||||
release()
|
release()
|
||||||
onCallEnded(callId)
|
onCallEnded(callId)
|
||||||
}
|
if (sendEndSignaling) {
|
||||||
if (sendEndSignaling) {
|
if (wasRinging) {
|
||||||
if (wasRinging) {
|
mxCall.reject()
|
||||||
mxCall.reject()
|
} else {
|
||||||
} else {
|
mxCall.hangUp(reason)
|
||||||
mxCall.hangUp(reason)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue