mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 18:05:59 +03:00
VoIP: fix bunch of issues
This commit is contained in:
parent
a16086db6f
commit
c53111a85a
7 changed files with 90 additions and 125 deletions
|
@ -52,7 +52,7 @@ class CurrentCallsView @JvmOverloads constructor(
|
|||
it.mxCall.state is CallState.Connected
|
||||
}
|
||||
val heldCalls = connectedCalls.filter {
|
||||
it.isLocalOnHold() || it.remoteOnHold
|
||||
it.isLocalOnHold || it.remoteOnHold
|
||||
}
|
||||
if (connectedCalls.size == 1) {
|
||||
if (heldCalls.size == 1) {
|
||||
|
|
|
@ -42,6 +42,9 @@ class KnownCallsViewHolder {
|
|||
}
|
||||
|
||||
fun updateCall(currentCall: WebRtcCall?, calls: List<WebRtcCall>) {
|
||||
activeCallPiP?.let {
|
||||
this.currentCall?.detachRenderers(listOf(it))
|
||||
}
|
||||
this.currentCall?.removeListener(tickListener)
|
||||
this.currentCall = currentCall
|
||||
this.currentCall?.addListener(tickListener)
|
||||
|
|
|
@ -16,82 +16,45 @@
|
|||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class CountUpTimer(private val intervalInMs: Long) {
|
||||
|
||||
private var startTimestamp: Long = 0
|
||||
private var delayTime: Long = 0
|
||||
private var lastPauseTimestamp: Long = 0
|
||||
private var isRunning: Boolean = false
|
||||
private val elapsedTime: AtomicLong = AtomicLong()
|
||||
private val resumed: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
private val disposable = Observable.interval(intervalInMs, TimeUnit.MILLISECONDS)
|
||||
.filter { _ -> resumed.get() }
|
||||
.doOnNext { _ -> elapsedTime.addAndGet(intervalInMs) }
|
||||
.subscribe {
|
||||
tickListener?.onTick(elapsedTime.get())
|
||||
}
|
||||
|
||||
var tickListener: TickListener? = null
|
||||
|
||||
private val tickHandler: Handler = Handler()
|
||||
private val tickSelector = Runnable {
|
||||
if (isRunning) {
|
||||
tickListener?.onTick(time)
|
||||
startTicking()
|
||||
}
|
||||
fun elapsedTime(): Long{
|
||||
return elapsedTime.get()
|
||||
}
|
||||
|
||||
init {
|
||||
reset()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the timer, also clears all laps information. Running status will not affected
|
||||
*/
|
||||
fun reset() {
|
||||
startTimestamp = SystemClock.elapsedRealtime()
|
||||
delayTime = 0
|
||||
lastPauseTimestamp = startTimestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the timer
|
||||
*/
|
||||
fun pause() {
|
||||
if (isRunning) {
|
||||
lastPauseTimestamp = SystemClock.elapsedRealtime()
|
||||
isRunning = false
|
||||
stopTicking()
|
||||
}
|
||||
resumed.set(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the timer
|
||||
*/
|
||||
fun resume() {
|
||||
if (!isRunning) {
|
||||
val currentTime: Long = SystemClock.elapsedRealtime()
|
||||
delayTime += currentTime - lastPauseTimestamp
|
||||
isRunning = true
|
||||
startTicking()
|
||||
}
|
||||
}
|
||||
val time: Long
|
||||
get() = if (isRunning) {
|
||||
SystemClock.elapsedRealtime() - startTimestamp - delayTime
|
||||
} else {
|
||||
lastPauseTimestamp - startTimestamp - delayTime
|
||||
}
|
||||
|
||||
private fun startTicking() {
|
||||
tickHandler.removeCallbacksAndMessages(null)
|
||||
val time = time
|
||||
val remainingTimeInInterval = intervalInMs - time % intervalInMs
|
||||
tickHandler.postDelayed(tickSelector, remainingTimeInInterval)
|
||||
resumed.set(true)
|
||||
}
|
||||
|
||||
private fun stopTicking() {
|
||||
tickHandler.removeCallbacksAndMessages(null)
|
||||
fun stop() {
|
||||
disposable.dispose()
|
||||
}
|
||||
|
||||
|
||||
interface TickListener {
|
||||
fun onTick(milliseconds: Long)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
|
|||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import okhttp3.internal.concurrent.formatDuration
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCallDetail
|
||||
|
@ -166,7 +165,8 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
views.smallIsHeldIcon.isVisible = false
|
||||
when (callState) {
|
||||
is CallState.Idle,
|
||||
is CallState.Dialing -> {
|
||||
is CallState.CreateOffer,
|
||||
is CallState.Dialing -> {
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callStatusText.setText(R.string.call_ring)
|
||||
|
@ -187,9 +187,9 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
views.callConnectingProgress.isVisible = true
|
||||
configureCallInfo(state)
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
is CallState.Connected -> {
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (state.isLocalOnHold) {
|
||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
views.smallIsHeldIcon.isVisible = true
|
||||
views.callVideoGroup.isInvisible = true
|
||||
views.callInfoGroup.isVisible = true
|
||||
|
@ -226,13 +226,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
views.callStatusText.setText(R.string.call_connecting)
|
||||
views.callConnectingProgress.isVisible = true
|
||||
}
|
||||
// ensure all attached?
|
||||
callManager.getCallById(callArgs.callId)?.attachViewRenderers(views.pipRenderer, views.fullscreenRenderer, null)
|
||||
}
|
||||
is CallState.Terminated -> {
|
||||
is CallState.Terminated -> {
|
||||
finish()
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +253,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen)
|
||||
avatarRenderer.renderBlur(state.otherKnownCallInfo.otherUserItem, views.otherKnownCallAvatarView, sampling = 20, rounded = false, colorFilter = colorFilter)
|
||||
views.otherKnownCallLayout.isVisible = true
|
||||
views.otherSmallIsHeldIcon.isVisible = otherCall?.let { it.isLocalOnHold() || it.remoteOnHold }.orFalse()
|
||||
views.otherSmallIsHeldIcon.isVisible = otherCall?.let { it.isLocalOnHold || it.remoteOnHold }.orFalse()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
override fun onHoldUnhold() {
|
||||
setState {
|
||||
copy(
|
||||
isLocalOnHold = call?.isLocalOnHold() ?: false,
|
||||
isLocalOnHold = call?.isLocalOnHold ?: false,
|
||||
isRemoteOnHold = call?.remoteOnHold ?: false
|
||||
)
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
callState = Success(webRtcCall.mxCall.state),
|
||||
callInfo = VectorCallViewState.CallInfo(callId, item),
|
||||
soundDevice = currentSoundDevice,
|
||||
isLocalOnHold = webRtcCall.isLocalOnHold(),
|
||||
isLocalOnHold = webRtcCall.isLocalOnHold,
|
||||
isRemoteOnHold = webRtcCall.remoteOnHold,
|
||||
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
|
||||
isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT,
|
||||
|
|
|
@ -135,7 +135,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD
|
||||
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null
|
||||
|
||||
private val timer = CountUpTimer(1000).apply {
|
||||
private val timer = CountUpTimer(Duration.ofSeconds(1).toMillis()).apply {
|
||||
tickListener = object : CountUpTimer.TickListener {
|
||||
override fun onTick(milliseconds: Long) {
|
||||
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
|
||||
|
@ -146,7 +146,6 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Mute status
|
||||
var micMuted = false
|
||||
private set
|
||||
|
@ -154,6 +153,11 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
private set
|
||||
var remoteOnHold = false
|
||||
private set
|
||||
var isLocalOnHold = false
|
||||
private set
|
||||
|
||||
// This value is used to track localOnHold when changing remoteOnHold value
|
||||
private var wasLocalOnHold = false
|
||||
|
||||
var offerSdp: CallInviteContent.Offer? = null
|
||||
|
||||
|
@ -228,7 +232,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
|
||||
fun formattedDuration(): String {
|
||||
return formatDuration(
|
||||
Duration.ofMillis(timer.time)
|
||||
Duration.ofMillis(timer.elapsedTime())
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -257,21 +261,9 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
|
||||
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
||||
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
||||
// this.localSurfaceRenderer = WeakReference(localViewRenderer)
|
||||
// this.remoteSurfaceRenderer = WeakReference(remoteViewRenderer)
|
||||
localSurfaceRenderers.addIfNeeded(localViewRenderer)
|
||||
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
|
||||
|
||||
// The call is going to resume from background, we can reduce notif
|
||||
mxCall
|
||||
.takeIf { it.state is CallState.Connected }
|
||||
?.let { mxCall ->
|
||||
// Start background service with notification
|
||||
CallService.onPendingCall(
|
||||
context = context,
|
||||
callId = mxCall.callId)
|
||||
}
|
||||
|
||||
GlobalScope.launch(dispatcher) {
|
||||
when (mode) {
|
||||
VectorCallActivity.INCOMING_ACCEPT -> {
|
||||
|
@ -301,9 +293,8 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
}
|
||||
|
||||
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) = synchronized(this) {
|
||||
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
|
||||
Timber.v("## VOIP detachRenderers")
|
||||
// currentCall?.localMediaStream?.let { currentCall?.peerConnection?.removeStream(it) }
|
||||
if (renderers.isNullOrEmpty()) {
|
||||
// remove all sinks
|
||||
localSurfaceRenderers.forEach {
|
||||
|
@ -545,7 +536,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
* rather than 'sendonly')
|
||||
* @returns true if the other party has put us on hold
|
||||
*/
|
||||
fun isLocalOnHold(): Boolean = synchronized(this) {
|
||||
private fun computeIsLocalOnHold(): Boolean {
|
||||
if (mxCall.state !is CallState.Connected) return false
|
||||
var callOnHold = true
|
||||
// We consider a call to be on hold only if *all* the tracks are on hold
|
||||
|
@ -558,38 +549,46 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
return callOnHold
|
||||
}
|
||||
|
||||
fun updateRemoteOnHold(onHold: Boolean) = synchronized(this) {
|
||||
if (remoteOnHold == onHold) return
|
||||
remoteOnHold = onHold
|
||||
if (!onHold) {
|
||||
onCallBecomeActive(this)
|
||||
fun updateRemoteOnHold(onHold: Boolean) {
|
||||
GlobalScope.launch(dispatcher) {
|
||||
if (remoteOnHold == onHold) return@launch
|
||||
val direction: RtpTransceiver.RtpTransceiverDirection
|
||||
if (onHold) {
|
||||
wasLocalOnHold = isLocalOnHold
|
||||
remoteOnHold = true
|
||||
isLocalOnHold = true
|
||||
direction = RtpTransceiver.RtpTransceiverDirection.INACTIVE
|
||||
} else {
|
||||
remoteOnHold = false
|
||||
isLocalOnHold = wasLocalOnHold
|
||||
onCallBecomeActive(this@WebRtcCall)
|
||||
direction = RtpTransceiver.RtpTransceiverDirection.SEND_RECV
|
||||
}
|
||||
for (transceiver in peerConnection?.transceivers ?: emptyList()) {
|
||||
transceiver.direction = direction
|
||||
}
|
||||
updateMuteStatus()
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onHoldUnhold() }
|
||||
}
|
||||
}
|
||||
val direction = if (onHold) {
|
||||
RtpTransceiver.RtpTransceiverDirection.INACTIVE
|
||||
} else {
|
||||
RtpTransceiver.RtpTransceiverDirection.SEND_RECV
|
||||
}
|
||||
for (transceiver in peerConnection?.transceivers ?: emptyList()) {
|
||||
transceiver.direction = direction
|
||||
}
|
||||
updateMuteStatus()
|
||||
}
|
||||
|
||||
fun muteCall(muted: Boolean) = synchronized(this) {
|
||||
fun muteCall(muted: Boolean) {
|
||||
micMuted = muted
|
||||
updateMuteStatus()
|
||||
}
|
||||
|
||||
fun enableVideo(enabled: Boolean) = synchronized(this) {
|
||||
fun enableVideo(enabled: Boolean) {
|
||||
videoMuted = !enabled
|
||||
updateMuteStatus()
|
||||
}
|
||||
|
||||
fun canSwitchCamera(): Boolean = synchronized(this) {
|
||||
fun canSwitchCamera(): Boolean {
|
||||
return availableCamera.size > 1
|
||||
}
|
||||
|
||||
private fun getOppositeCameraIfAny(): CameraProxy? = synchronized(this) {
|
||||
private fun getOppositeCameraIfAny(): CameraProxy? {
|
||||
val currentCamera = cameraInUse ?: return null
|
||||
return if (currentCamera.type == CameraType.FRONT) {
|
||||
availableCamera.firstOrNull { it.type == CameraType.BACK }
|
||||
|
@ -598,7 +597,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
}
|
||||
|
||||
fun switchCamera() = synchronized(this) {
|
||||
fun switchCamera() {
|
||||
Timber.v("## VOIP switchCamera")
|
||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
||||
|
@ -641,17 +640,18 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
}
|
||||
|
||||
fun currentCameraType(): CameraType? = synchronized(this) {
|
||||
fun currentCameraType(): CameraType? {
|
||||
return cameraInUse?.type
|
||||
}
|
||||
|
||||
fun currentCaptureFormat(): CaptureFormat = synchronized(this) {
|
||||
fun currentCaptureFormat(): CaptureFormat {
|
||||
return currentCaptureFormat
|
||||
}
|
||||
|
||||
private fun release() {
|
||||
listeners.clear()
|
||||
mxCall.removeListener(this)
|
||||
timer.reset()
|
||||
timer.stop()
|
||||
timer.tickListener = null
|
||||
videoCapturer?.stopCapture()
|
||||
videoCapturer?.dispose()
|
||||
|
@ -703,10 +703,11 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
}
|
||||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) = synchronized(this) {
|
||||
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||
if (mxCall.state == CallState.Terminated) {
|
||||
return
|
||||
}
|
||||
val wasConnected = mxCall.state is CallState.Connected
|
||||
mxCall.state = CallState.Terminated
|
||||
// Close tracks ASAP
|
||||
localVideoTrack?.setEnabled(false)
|
||||
|
@ -715,12 +716,13 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
val cameraManager = context.getSystemService<CameraManager>()!!
|
||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||
}
|
||||
release()
|
||||
listeners.clear()
|
||||
GlobalScope.launch(dispatcher) {
|
||||
release()
|
||||
}
|
||||
onCallEnded(this)
|
||||
if (originatedByMe) {
|
||||
// send hang up event
|
||||
if (mxCall.state is CallState.Connected) {
|
||||
if (wasConnected) {
|
||||
mxCall.hangUp(reason)
|
||||
} else {
|
||||
mxCall.reject()
|
||||
|
@ -783,7 +785,7 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
Timber.i("Ignoring colliding negotiate event because we're impolite")
|
||||
return@launch
|
||||
}
|
||||
val prevOnHold = isLocalOnHold()
|
||||
val prevOnHold = computeIsLocalOnHold()
|
||||
try {
|
||||
val sdp = SessionDescription(type.asWebRTC(), sdpText)
|
||||
peerConnection.awaitSetRemoteDescription(sdp)
|
||||
|
@ -795,8 +797,10 @@ class WebRtcCall(val mxCall: MxCall,
|
|||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Failed to complete negotiation")
|
||||
}
|
||||
val nowOnHold = isLocalOnHold()
|
||||
val nowOnHold = computeIsLocalOnHold()
|
||||
wasLocalOnHold = nowOnHold
|
||||
if (prevOnHold != nowOnHold) {
|
||||
isLocalOnHold = nowOnHold
|
||||
if (nowOnHold) {
|
||||
timer.pause()
|
||||
} else {
|
||||
|
|
|
@ -297,11 +297,6 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
.setLights(accentColor, 500, 500)
|
||||
.setOngoing(true)
|
||||
|
||||
// Compat: Display the incoming call notification on the lock screen
|
||||
builder.priority = NotificationCompat.PRIORITY_HIGH
|
||||
|
||||
//
|
||||
// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val contentIntent = VectorCallActivity.newIntent(
|
||||
context = context,
|
||||
|
@ -340,9 +335,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
answerCallPendingIntent
|
||||
)
|
||||
)
|
||||
|
||||
builder.setFullScreenIntent(contentPendingIntent, true)
|
||||
|
||||
if (fromBg) {
|
||||
// Compat: Display the incoming call notification on the lock screen
|
||||
builder.priority = NotificationCompat.PRIORITY_HIGH
|
||||
builder.setFullScreenIntent(contentPendingIntent, true)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue