mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
VoIP: show duration
This commit is contained in:
parent
a5736efc75
commit
81f7932cb7
9 changed files with 207 additions and 42 deletions
|
@ -18,11 +18,13 @@ package im.vector.app.core.ui.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.databinding.ViewCallControlsBinding
|
||||||
|
import im.vector.app.databinding.ViewCurrentCallsBinding
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import kotlinx.android.synthetic.main.view_current_calls.view.*
|
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
import org.matrix.android.sdk.api.session.call.CallState
|
||||||
|
|
||||||
class CurrentCallsView @JvmOverloads constructor(
|
class CurrentCallsView @JvmOverloads constructor(
|
||||||
|
@ -35,19 +37,17 @@ class CurrentCallsView @JvmOverloads constructor(
|
||||||
fun onTapToReturnToCall()
|
fun onTapToReturnToCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val views: ViewCurrentCallsBinding
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupView() {
|
|
||||||
inflate(context, R.layout.view_current_calls, this)
|
inflate(context, R.layout.view_current_calls, this)
|
||||||
|
views = ViewCurrentCallsBinding.bind(this)
|
||||||
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
||||||
setOnClickListener { callback?.onTapToReturnToCall() }
|
setOnClickListener { callback?.onTapToReturnToCall() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(calls: List<WebRtcCall>) {
|
fun render(calls: List<WebRtcCall>, formattedDuration: String) {
|
||||||
val connectedCalls = calls.filter {
|
val connectedCalls = calls.filter {
|
||||||
it.mxCall.state is CallState.Connected
|
it.mxCall.state is CallState.Connected
|
||||||
}
|
}
|
||||||
|
@ -56,17 +56,17 @@ class CurrentCallsView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
if (connectedCalls.size == 1) {
|
if (connectedCalls.size == 1) {
|
||||||
if (heldCalls.size == 1) {
|
if (heldCalls.size == 1) {
|
||||||
currentCallsInfo.setText(R.string.call_only_paused)
|
views.currentCallsInfo.setText(R.string.call_only_paused)
|
||||||
} else {
|
} else {
|
||||||
currentCallsInfo.setText(R.string.call_only_active)
|
views.currentCallsInfo.text = resources.getString(R.string.call_only_active, formattedDuration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (heldCalls.size > 1) {
|
if (heldCalls.size > 1) {
|
||||||
currentCallsInfo.text = resources.getString(R.string.call_only_multiple_paused , heldCalls.size)
|
views.currentCallsInfo.text = resources.getString(R.string.call_only_multiple_paused , heldCalls.size)
|
||||||
} else if (heldCalls.size == 1) {
|
} else if (heldCalls.size == 1) {
|
||||||
currentCallsInfo.setText(R.string.call_active_and_single_paused)
|
views.currentCallsInfo.text = resources.getString(R.string.call_active_and_single_paused, formattedDuration)
|
||||||
} else {
|
} else {
|
||||||
currentCallsInfo.text = resources.getString(R.string.call_active_and_multiple_paused, "00:00", heldCalls.size)
|
views.currentCallsInfo.text = resources.getString(R.string.call_active_and_multiple_paused, formattedDuration, heldCalls.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,34 +25,44 @@ import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
import org.webrtc.RendererCommon
|
import org.webrtc.RendererCommon
|
||||||
import org.webrtc.SurfaceViewRenderer
|
import org.webrtc.SurfaceViewRenderer
|
||||||
|
|
||||||
class ActiveCallViewHolder {
|
class KnownCallsViewHolder {
|
||||||
|
|
||||||
private var activeCallPiP: SurfaceViewRenderer? = null
|
private var activeCallPiP: SurfaceViewRenderer? = null
|
||||||
private var activeCallView: CurrentCallsView? = null
|
private var currentCallsView: CurrentCallsView? = null
|
||||||
private var pipWrapper: CardView? = null
|
private var pipWrapper: CardView? = null
|
||||||
private var activeCall: WebRtcCall? = null
|
private var currentCall: WebRtcCall? = null
|
||||||
|
private var calls: List<WebRtcCall> = emptyList()
|
||||||
|
|
||||||
private var activeCallPipInitialized = false
|
private var activeCallPipInitialized = false
|
||||||
|
|
||||||
fun updateCall(activeCall: WebRtcCall?, calls: List<WebRtcCall>) {
|
private val tickListener = object : WebRtcCall.Listener {
|
||||||
this.activeCall = activeCall
|
override fun onTick(formattedDuration: String) {
|
||||||
val hasActiveCall = activeCall?.mxCall?.state is CallState.Connected
|
currentCallsView?.render(calls, formattedDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCall(currentCall: WebRtcCall?, calls: List<WebRtcCall>) {
|
||||||
|
this.currentCall?.removeListener(tickListener)
|
||||||
|
this.currentCall = currentCall
|
||||||
|
this.currentCall?.addListener(tickListener)
|
||||||
|
this.calls = calls
|
||||||
|
val hasActiveCall = currentCall?.mxCall?.state is CallState.Connected
|
||||||
if (hasActiveCall) {
|
if (hasActiveCall) {
|
||||||
val isVideoCall = activeCall?.mxCall?.isVideoCall == true
|
val isVideoCall = currentCall?.mxCall?.isVideoCall == true
|
||||||
if (isVideoCall) initIfNeeded()
|
if (isVideoCall) initIfNeeded()
|
||||||
activeCallView?.isVisible = !isVideoCall
|
currentCallsView?.isVisible = !isVideoCall
|
||||||
activeCallView?.render(calls)
|
currentCallsView?.render(calls, currentCall?.formattedDuration() ?: "")
|
||||||
pipWrapper?.isVisible = isVideoCall
|
pipWrapper?.isVisible = isVideoCall
|
||||||
activeCallPiP?.isVisible = isVideoCall
|
activeCallPiP?.isVisible = isVideoCall
|
||||||
activeCallPiP?.let {
|
activeCallPiP?.let {
|
||||||
activeCall?.attachViewRenderers(null, it, null)
|
currentCall?.attachViewRenderers(null, it, null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activeCallView?.isVisible = false
|
currentCallsView?.isVisible = false
|
||||||
activeCallPiP?.isVisible = false
|
activeCallPiP?.isVisible = false
|
||||||
pipWrapper?.isVisible = false
|
pipWrapper?.isVisible = false
|
||||||
activeCallPiP?.let {
|
activeCallPiP?.let {
|
||||||
activeCall?.detachRenderers(listOf(it))
|
currentCall?.detachRenderers(listOf(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,27 +82,29 @@ class ActiveCallViewHolder {
|
||||||
|
|
||||||
fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: CurrentCallsView, pipWrapper: CardView, interactionListener: CurrentCallsView.Callback) {
|
fun bind(activeCallPiP: SurfaceViewRenderer, activeCallView: CurrentCallsView, pipWrapper: CardView, interactionListener: CurrentCallsView.Callback) {
|
||||||
this.activeCallPiP = activeCallPiP
|
this.activeCallPiP = activeCallPiP
|
||||||
this.activeCallView = activeCallView
|
this.currentCallsView = activeCallView
|
||||||
this.pipWrapper = pipWrapper
|
this.pipWrapper = pipWrapper
|
||||||
this.activeCallView?.callback = interactionListener
|
this.currentCallsView?.callback = interactionListener
|
||||||
pipWrapper.setOnClickListener(
|
pipWrapper.setOnClickListener(
|
||||||
DebouncedClickListener({ _ ->
|
DebouncedClickListener({ _ ->
|
||||||
interactionListener.onTapToReturnToCall()
|
interactionListener.onTapToReturnToCall()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
this.currentCall?.addListener(tickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unBind() {
|
fun unBind() {
|
||||||
activeCallPiP?.let {
|
activeCallPiP?.let {
|
||||||
activeCall?.detachRenderers(listOf(it))
|
currentCall?.detachRenderers(listOf(it))
|
||||||
}
|
}
|
||||||
if (activeCallPipInitialized) {
|
if (activeCallPipInitialized) {
|
||||||
activeCallPiP?.release()
|
activeCallPiP?.release()
|
||||||
}
|
}
|
||||||
this.activeCallView?.callback = null
|
this.currentCallsView?.callback = null
|
||||||
|
this.currentCall?.removeListener(tickListener)
|
||||||
pipWrapper?.setOnClickListener(null)
|
pipWrapper?.setOnClickListener(null)
|
||||||
activeCallPiP = null
|
activeCallPiP = null
|
||||||
activeCallView = null
|
currentCallsView = null
|
||||||
pipWrapper = null
|
pipWrapper = null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.app.core.utils
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.SystemClock
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
var tickListener: TickListener? = null
|
||||||
|
|
||||||
|
private val tickHandler: Handler = Handler()
|
||||||
|
private val tickSelector = Runnable {
|
||||||
|
if (isRunning) {
|
||||||
|
tickListener?.onTick(time)
|
||||||
|
startTicking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopTicking() {
|
||||||
|
tickHandler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface TickListener {
|
||||||
|
fun onTick(milliseconds: Long)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import okhttp3.internal.concurrent.formatDuration
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
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.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxCallDetail
|
import org.matrix.android.sdk.api.session.call.MxCallDetail
|
||||||
|
@ -205,7 +206,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
views.callStatusText.text = null
|
views.callStatusText.text = state.formattedDuration
|
||||||
if (callArgs.isVideoCall) {
|
if (callArgs.isVideoCall) {
|
||||||
views.callVideoGroup.isVisible = true
|
views.callVideoGroup.isVisible = true
|
||||||
views.callInfoGroup.isVisible = false
|
views.callInfoGroup.isVisible = false
|
||||||
|
|
|
@ -79,6 +79,12 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTick(formattedDuration: String) {
|
||||||
|
setState {
|
||||||
|
copy(formattedDuration = formattedDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateUpdate(call: MxCall) {
|
override fun onStateUpdate(call: MxCall) {
|
||||||
val callState = call.state
|
val callState = call.state
|
||||||
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||||
|
@ -176,8 +182,9 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
isLocalOnHold = webRtcCall.isLocalOnHold(),
|
isLocalOnHold = webRtcCall.isLocalOnHold(),
|
||||||
isRemoteOnHold = webRtcCall.remoteOnHold,
|
isRemoteOnHold = webRtcCall.remoteOnHold,
|
||||||
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
|
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
|
||||||
isFrontCamera = call?.currentCameraType() == CameraType.FRONT,
|
isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT,
|
||||||
canSwitchCamera = call?.canSwitchCamera() ?: false,
|
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
||||||
|
formattedDuration = webRtcCall.formattedDuration(),
|
||||||
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD
|
isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,8 @@ data class VectorCallViewState(
|
||||||
val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
|
val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
|
||||||
val callState: Async<CallState> = Uninitialized,
|
val callState: Async<CallState> = Uninitialized,
|
||||||
val otherKnownCallInfo: CallInfo? = null,
|
val otherKnownCallInfo: CallInfo? = null,
|
||||||
val callInfo: CallInfo = CallInfo(callId)
|
val callInfo: CallInfo = CallInfo(callId),
|
||||||
|
val formattedDuration: String = ""
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
data class CallInfo(
|
data class CallInfo(
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import im.vector.app.core.services.CallService
|
import im.vector.app.core.services.CallService
|
||||||
|
import im.vector.app.core.utils.CountUpTimer
|
||||||
import im.vector.app.features.call.CallAudioManager
|
import im.vector.app.features.call.CallAudioManager
|
||||||
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
||||||
import im.vector.app.features.call.CameraProxy
|
import im.vector.app.features.call.CameraProxy
|
||||||
|
@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.CallState
|
||||||
import org.matrix.android.sdk.api.session.call.MxCall
|
import org.matrix.android.sdk.api.session.call.MxCall
|
||||||
|
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
||||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||||
|
@ -53,6 +55,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
import org.matrix.android.sdk.api.session.room.model.call.SdpType
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
|
import org.threeten.bp.Duration
|
||||||
import org.webrtc.AudioSource
|
import org.webrtc.AudioSource
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
import org.webrtc.Camera1Enumerator
|
import org.webrtc.Camera1Enumerator
|
||||||
|
@ -96,6 +99,8 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
fun onCaptureStateChanged() {}
|
fun onCaptureStateChanged() {}
|
||||||
fun onCameraChanged() {}
|
fun onCameraChanged() {}
|
||||||
fun onHoldUnhold() {}
|
fun onHoldUnhold() {}
|
||||||
|
fun onTick(formattedDuration: String) {}
|
||||||
|
override fun onStateUpdate(call: MxCall) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val listeners = CopyOnWriteArrayList<Listener>()
|
private val listeners = CopyOnWriteArrayList<Listener>()
|
||||||
|
@ -130,6 +135,18 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD
|
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD
|
||||||
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null
|
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null
|
||||||
|
|
||||||
|
private val timer = CountUpTimer(1000).apply {
|
||||||
|
tickListener = object : CountUpTimer.TickListener {
|
||||||
|
override fun onTick(milliseconds: Long) {
|
||||||
|
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
|
||||||
|
listeners.forEach {
|
||||||
|
tryOrNull { it.onTick(formattedDuration) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Mute status
|
// Mute status
|
||||||
var micMuted = false
|
var micMuted = false
|
||||||
private set
|
private set
|
||||||
|
@ -209,6 +226,12 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formattedDuration(): String {
|
||||||
|
return formatDuration(
|
||||||
|
Duration.ofMillis(timer.time)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createPeerConnection(turnServerResponse: TurnServerResponse?) {
|
private fun createPeerConnection(turnServerResponse: TurnServerResponse?) {
|
||||||
val peerConnectionFactory = peerConnectionFactoryProvider.get() ?: return
|
val peerConnectionFactory = peerConnectionFactoryProvider.get() ?: return
|
||||||
val iceServers = mutableListOf<PeerConnection.IceServer>().apply {
|
val iceServers = mutableListOf<PeerConnection.IceServer>().apply {
|
||||||
|
@ -547,7 +570,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
return callOnHold
|
return callOnHold
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRemoteOnHold(onHold: Boolean) = synchronized(this){
|
fun updateRemoteOnHold(onHold: Boolean) = synchronized(this) {
|
||||||
if (remoteOnHold == onHold) return
|
if (remoteOnHold == onHold) return
|
||||||
remoteOnHold = onHold
|
remoteOnHold = onHold
|
||||||
if (!onHold) {
|
if (!onHold) {
|
||||||
|
@ -574,11 +597,11 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
updateMuteStatus()
|
updateMuteStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canSwitchCamera(): Boolean = synchronized(this){
|
fun canSwitchCamera(): Boolean = synchronized(this) {
|
||||||
return availableCamera.size > 1
|
return availableCamera.size > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getOppositeCameraIfAny(): CameraProxy? = synchronized(this){
|
private fun getOppositeCameraIfAny(): CameraProxy? = synchronized(this) {
|
||||||
val currentCamera = cameraInUse ?: return null
|
val currentCamera = cameraInUse ?: return null
|
||||||
return if (currentCamera.type == CameraType.FRONT) {
|
return if (currentCamera.type == CameraType.FRONT) {
|
||||||
availableCamera.firstOrNull { it.type == CameraType.BACK }
|
availableCamera.firstOrNull { it.type == CameraType.BACK }
|
||||||
|
@ -587,7 +610,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchCamera() = synchronized(this){
|
fun switchCamera() = synchronized(this) {
|
||||||
Timber.v("## VOIP switchCamera")
|
Timber.v("## VOIP switchCamera")
|
||||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||||
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
||||||
|
@ -630,7 +653,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentCameraType(): CameraType? = synchronized(this){
|
fun currentCameraType(): CameraType? = synchronized(this) {
|
||||||
return cameraInUse?.type
|
return cameraInUse?.type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,8 +661,10 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
return currentCaptureFormat
|
return currentCaptureFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun release() {
|
private fun release() {
|
||||||
mxCall.removeListener(this)
|
mxCall.removeListener(this)
|
||||||
|
timer.reset()
|
||||||
|
timer.tickListener = null
|
||||||
videoCapturer?.stopCapture()
|
videoCapturer?.stopCapture()
|
||||||
videoCapturer?.dispose()
|
videoCapturer?.dispose()
|
||||||
videoCapturer = null
|
videoCapturer = null
|
||||||
|
@ -784,6 +809,11 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
val nowOnHold = isLocalOnHold()
|
val nowOnHold = isLocalOnHold()
|
||||||
if (prevOnHold != nowOnHold) {
|
if (prevOnHold != nowOnHold) {
|
||||||
|
if (nowOnHold) {
|
||||||
|
timer.pause()
|
||||||
|
} else {
|
||||||
|
timer.resume()
|
||||||
|
}
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
tryOrNull { it.onHoldUnhold() }
|
tryOrNull { it.onHoldUnhold() }
|
||||||
}
|
}
|
||||||
|
@ -791,9 +821,26 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatDuration(duration: Duration): String {
|
||||||
|
val hours = duration.seconds / 3600
|
||||||
|
val minutes = (duration.seconds % 3600) / 60
|
||||||
|
val seconds = duration.seconds % 60
|
||||||
|
return if (hours > 0) {
|
||||||
|
String.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
} else {
|
||||||
|
String.format("%02d:%02d", minutes, seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MxCall.StateListener
|
// MxCall.StateListener
|
||||||
|
|
||||||
override fun onStateUpdate(call: MxCall) {
|
override fun onStateUpdate(call: MxCall) {
|
||||||
|
val state = call.state
|
||||||
|
if (state is CallState.Connected && state.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||||
|
timer.resume()
|
||||||
|
} else {
|
||||||
|
timer.pause()
|
||||||
|
}
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
tryOrNull { it.onStateUpdate(call) }
|
tryOrNull { it.onStateUpdate(call) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import im.vector.app.core.platform.ToolbarConfigurable
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.ui.views.CurrentCallsView
|
import im.vector.app.core.ui.views.CurrentCallsView
|
||||||
import im.vector.app.core.ui.views.ActiveCallViewHolder
|
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||||
|
@ -83,7 +83,7 @@ class HomeDetailFragment @Inject constructor(
|
||||||
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val activeCallViewHolder = ActiveCallViewHolder()
|
private val activeCallViewHolder = KnownCallsViewHolder()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
|
@ -90,7 +90,7 @@ import im.vector.app.core.intent.getMimeTypeFromUri
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.ui.views.CurrentCallsView
|
import im.vector.app.core.ui.views.CurrentCallsView
|
||||||
import im.vector.app.core.ui.views.ActiveCallViewHolder
|
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||||
import im.vector.app.core.ui.views.ActiveConferenceView
|
import im.vector.app.core.ui.views.ActiveConferenceView
|
||||||
import im.vector.app.core.ui.views.JumpToReadMarkerView
|
import im.vector.app.core.ui.views.JumpToReadMarkerView
|
||||||
import im.vector.app.core.ui.views.NotificationAreaView
|
import im.vector.app.core.ui.views.NotificationAreaView
|
||||||
|
@ -295,7 +295,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
|
private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView
|
||||||
|
|
||||||
private var lockSendButton = false
|
private var lockSendButton = false
|
||||||
private val activeCallViewHolder = ActiveCallViewHolder()
|
private val activeCallViewHolder = KnownCallsViewHolder()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
Loading…
Reference in a new issue