Restart capture when camera is back to available
This commit is contained in:
parent
4966bef9c3
commit
eabb0bb41d
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 org.webrtc.CameraVideoCapturer
|
||||
import timber.log.Timber
|
||||
|
||||
open class CameraEventsHandlerAdapter : CameraVideoCapturer.CameraEventsHandler {
|
||||
override fun onCameraError(p0: String?) {
|
||||
Timber.v("## VOIP onCameraError $p0")
|
||||
}
|
||||
|
||||
override fun onCameraOpening(p0: String?) {
|
||||
Timber.v("## VOIP onCameraOpening $p0")
|
||||
}
|
||||
|
||||
override fun onCameraDisconnected() {
|
||||
Timber.v("## VOIP onCameraOpening")
|
||||
}
|
||||
|
||||
override fun onCameraFreezed(p0: String?) {
|
||||
Timber.v("## VOIP onCameraFreezed $p0")
|
||||
}
|
||||
|
||||
override fun onFirstFrameAvailable() {
|
||||
Timber.v("## VOIP onFirstFrameAvailable")
|
||||
}
|
||||
|
||||
override fun onCameraClosed() {
|
||||
Timber.v("## VOIP onCameraClosed")
|
||||
}
|
||||
}
|
|
@ -37,6 +37,10 @@ class SharedActiveCallViewModel @Inject constructor(
|
|||
override fun onCurrentCallChange(call: MxCall?) {
|
||||
activeCall.postValue(call)
|
||||
}
|
||||
|
||||
override fun onCaptureStateChanged(captureInError: Boolean) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
|
|
@ -264,6 +264,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
|
|||
if (callArgs.isVideoCall) {
|
||||
callVideoGroup.isVisible = true
|
||||
callInfoGroup.isVisible = false
|
||||
pip_video_view.isVisible = !state.isVideoCaptureInError
|
||||
} else {
|
||||
callVideoGroup.isInvisible = true
|
||||
callInfoGroup.isVisible = true
|
||||
|
|
|
@ -41,6 +41,7 @@ data class VectorCallViewState(
|
|||
val isVideoCall: Boolean,
|
||||
val isAudioMuted: Boolean = false,
|
||||
val isVideoEnabled: Boolean = true,
|
||||
val isVideoCaptureInError: Boolean = false,
|
||||
val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE,
|
||||
val otherUserMatrixItem: Async<MatrixItem> = Uninitialized,
|
||||
val callState: Async<CallState> = Uninitialized
|
||||
|
@ -83,12 +84,25 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private val currentCallListener = object : WebRtcPeerConnectionManager.CurrentCallListener {
|
||||
override fun onCurrentCallChange(call: MxCall?) {
|
||||
}
|
||||
|
||||
override fun onCaptureStateChanged(captureInError: Boolean) {
|
||||
setState {
|
||||
copy(isVideoCaptureInError = captureInError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
autoReplyIfNeeded = args.autoAccept
|
||||
|
||||
initialState.callId?.let {
|
||||
|
||||
webRtcPeerConnectionManager.addCurrentCallListener(currentCallListener)
|
||||
|
||||
session.callSignalingService().getCallWithId(it)?.let { mxCall ->
|
||||
this.call = mxCall
|
||||
mxCall.otherUserId
|
||||
|
@ -109,6 +123,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
|
||||
override fun onCleared() {
|
||||
// session.callService().removeCallListener(callServiceListener)
|
||||
webRtcPeerConnectionManager.removeCurrentCallListener(currentCallListener)
|
||||
this.call?.removeListener(callStateListener)
|
||||
super.onCleared()
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
package im.vector.riotx.features.call
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.tryThis
|
||||
import im.vector.matrix.android.api.session.call.CallState
|
||||
|
@ -71,6 +74,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: MxCall?)
|
||||
fun onCaptureStateChanged(captureInError: Boolean)
|
||||
}
|
||||
|
||||
private val currentCallsListeners = emptyList<CurrentCallListener>().toMutableList()
|
||||
|
@ -118,6 +122,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
var remoteCandidateSource: ReplaySubject<IceCandidate>? = null
|
||||
var remoteIceCandidateDisposable: Disposable? = null
|
||||
|
||||
// We register an availability callback if we loose access to camera
|
||||
var cameraAvailabilityCallback: CameraRestarter? = null
|
||||
|
||||
fun release() {
|
||||
remoteIceCandidateDisposable?.dispose()
|
||||
iceCandidateDisposable?.dispose()
|
||||
|
@ -149,6 +156,14 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
private var videoCapturer: VideoCapturer? = null
|
||||
|
||||
var capturerIsInError = false
|
||||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCaptureStateChanged(value) }
|
||||
}
|
||||
}
|
||||
|
||||
var localSurfaceRenderer: WeakReference<SurfaceViewRenderer>? = null
|
||||
var remoteSurfaceRenderer: WeakReference<SurfaceViewRenderer>? = null
|
||||
|
||||
|
@ -355,7 +370,27 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
?.firstOrNull { cameraIterator.isFrontFacing(it) }
|
||||
?: cameraIterator.deviceNames?.first()
|
||||
|
||||
val videoCapturer = cameraIterator.createCapturer(frontCamera, null)
|
||||
// TODO detect when no camera or no front camera
|
||||
|
||||
val videoCapturer = cameraIterator.createCapturer(frontCamera, object : CameraEventsHandlerAdapter() {
|
||||
override fun onFirstFrameAvailable() {
|
||||
super.onFirstFrameAvailable()
|
||||
capturerIsInError = false
|
||||
}
|
||||
|
||||
override fun 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
|
||||
Timber.v("## VOIP onCameraClosed")
|
||||
this@WebRtcPeerConnectionManager.capturerIsInError = true
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val restarter = CameraRestarter(frontCamera ?: "", callContext.mxCall.callId)
|
||||
callContext.cameraAvailabilityCallback = restarter
|
||||
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
cameraManager.registerAvailabilityCallback(restarter, null)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val videoSource = peerConnectionFactory!!.createVideoSource(videoCapturer.isScreencast)
|
||||
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext)
|
||||
|
@ -374,14 +409,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
callContext.localVideoSource = videoSource
|
||||
callContext.localVideoTrack = localVideoTrack
|
||||
|
||||
// localViewRenderer?.let { localVideoTrack?.addSink(it) }
|
||||
localMediaStream?.addTrack(localVideoTrack)
|
||||
// callContext.localMediaStream = localMediaStream
|
||||
// remoteVideoTrack?.setEnabled(true)
|
||||
// remoteVideoTrack?.let {
|
||||
// it.setEnabled(true)
|
||||
// it.addSink(remoteViewRenderer)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,6 +570,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
}
|
||||
|
||||
fun endCall() {
|
||||
currentCall?.cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||
}
|
||||
}
|
||||
currentCall?.mxCall?.hangUp()
|
||||
currentCall = null
|
||||
audioManager.stop()
|
||||
|
@ -746,4 +780,18 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP StreamObserver onAddTrack")
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
inner class CameraRestarter(val cameraId: String, val callId: String) : CameraManager.AvailabilityCallback() {
|
||||
|
||||
override fun onCameraAvailable(cameraId: String) {
|
||||
if (this.cameraId == cameraId && currentCall?.mxCall?.callId == callId) {
|
||||
// re-start the capture
|
||||
// TODO notify that video is enabled
|
||||
videoCapturer?.startCapture(1280, 720, 30)
|
||||
(context.getSystemService(Context.CAMERA_SERVICE) as? CameraManager)
|
||||
?.unregisterAvailabilityCallback(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue