feat: add call stats report on frontend

This commit is contained in:
Harris
2021-09-07 16:12:37 +10:00
parent 459dfa72c2
commit 148a71a378
3 changed files with 113 additions and 38 deletions

View File

@@ -1,10 +1,12 @@
package org.thoughtcrime.securesms.calls package org.thoughtcrime.securesms.calls
import android.Manifest
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_webrtc_tests.* import kotlinx.android.synthetic.main.activity_webrtc_tests.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -18,6 +20,7 @@ import org.session.libsession.utilities.Debouncer
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.permissions.Permissions
import org.webrtc.* import org.webrtc.*
@@ -63,6 +66,26 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
private val candidates: MutableList<IceCandidate> = mutableListOf() private val candidates: MutableList<IceCandidate> = mutableListOf()
private val iceDebouncer = Debouncer(2_000) private val iceDebouncer = Debouncer(2_000)
private var localCandidateType: String? = null
set(value) {
field = value
if (value != null) {
// show it
local_candidate_info.isVisible = true
local_candidate_info.text = "local: $value"
}
}
private var remoteCandidateType: String? = null
set(value) {
field = value
if (value != null) {
remote_candidate_info.isVisible = true
remote_candidate_info.text = "remote: $value"
}
// update text
}
private lateinit var callAddress: Address private lateinit var callAddress: Address
private val peerConnection by lazy { private val peerConnection by lazy {
// TODO: in a lokinet world, ice servers shouldn't be needed as .loki addresses should suffice to p2p // TODO: in a lokinet world, ice servers shouldn't be needed as .loki addresses should suffice to p2p
@@ -80,14 +103,20 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
setContentView(R.layout.activity_webrtc_tests) setContentView(R.layout.activity_webrtc_tests)
//TODO: better handling of permissions
Permissions.with(this)
.request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
.onAllGranted {
setupStreams()
}
.execute()
local_renderer.run { local_renderer.run {
setMirror(true)
setEnableHardwareScaler(true) setEnableHardwareScaler(true)
init(eglBase.eglBaseContext, null) init(eglBase.eglBaseContext, null)
} }
remote_renderer.run { remote_renderer.run {
setMirror(true)
setEnableHardwareScaler(true) setEnableHardwareScaler(true)
init(eglBase.eglBaseContext, null) init(eglBase.eglBaseContext, null)
} }
@@ -104,24 +133,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
} }
val videoSource = connectionFactory.createVideoSource(false)
videoCapturer?.initialize(surfaceHelper, local_renderer.context, videoSource.capturerObserver) ?: run {
finish()
return
}
videoCapturer?.startCapture(HD_VIDEO_WIDTH, HD_VIDEO_HEIGHT, 10)
val audioTrack = connectionFactory.createAudioTrack(LOCAL_TRACK_ID + "_audio", audioSource)
val videoTrack = connectionFactory.createVideoTrack(LOCAL_TRACK_ID, videoSource)
videoTrack.addSink(local_renderer)
val stream = connectionFactory.createLocalMediaStream(LOCAL_STREAM_ID)
stream.addTrack(videoTrack)
stream.addTrack(audioTrack)
peerConnection.addStream(stream)
// create either call or answer // create either call or answer
if (intent.action == ACTION_ANSWER) { if (intent.action == ACTION_ANSWER) {
callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return } callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return }
@@ -175,11 +186,27 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
override fun onStatsDelivered(statsReport: RTCStatsReport?) { override fun onStatsDelivered(statsReport: RTCStatsReport?) {
statsReport?.let { report -> statsReport?.let { report ->
val usedConnection = report.statsMap.filter { (_,v) -> v.type == "candidate-pair" && v.members["writable"] == true }.asIterable().firstOrNull()?.value ?: return@let
usedConnection.members["remoteCandidateId"]?.let { candidate ->
runOnUiThread {
remoteCandidateType = report.statsMap[candidate]?.members?.get("candidateType") as? String
}
}
usedConnection.members["localCandidateId"]?.let { candidate ->
runOnUiThread {
localCandidateType = report.statsMap[candidate]?.members?.get("candidateType") as? String
}
}
Log.d("Loki-RTC", "report is: $report") Log.d("Loki-RTC", "report is: $report")
} }
} }
private fun endCall() { private fun endCall() {
if (isFinishing) return
MessageSender.sendNonDurably( MessageSender.sendNonDurably(
CallMessage.endCall(), CallMessage.endCall(),
callAddress callAddress
@@ -193,6 +220,26 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
endCall() endCall()
} }
private fun setupStreams() {
val videoSource = connectionFactory.createVideoSource(false)
videoCapturer?.initialize(surfaceHelper, local_renderer.context, videoSource.capturerObserver) ?: run {
finish()
return
}
videoCapturer?.startCapture(HD_VIDEO_WIDTH, HD_VIDEO_HEIGHT, 10)
val audioTrack = connectionFactory.createAudioTrack(LOCAL_TRACK_ID + "_audio", audioSource)
val videoTrack = connectionFactory.createVideoTrack(LOCAL_TRACK_ID, videoSource)
videoTrack.addSink(local_renderer)
val stream = connectionFactory.createLocalMediaStream(LOCAL_STREAM_ID)
stream.addTrack(videoTrack)
stream.addTrack(audioTrack)
peerConnection.addStream(stream)
}
private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? { private fun createCameraCapturer(enumerator: CameraEnumerator): CameraVideoCapturer? {
val deviceNames = enumerator.deviceNames val deviceNames = enumerator.deviceNames
@@ -237,16 +284,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) { override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
Log.d("Loki-RTC", "onIceGatheringChange: $p0") Log.d("Loki-RTC", "onIceGatheringChange: $p0")
p0 ?: return
Log.d("Loki-RTC","sending IceCandidates of size: ${candidates.size}")
MessageSender.sendNonDurably(
CallMessage(SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES,
candidates.map { it.sdp },
candidates.map { it.sdpMLineIndex },
candidates.map { it.sdpMid }
),
callAddress
)
} }
override fun onIceCandidate(iceCandidate: IceCandidate?) { override fun onIceCandidate(iceCandidate: IceCandidate?) {

View File

@@ -14,6 +14,7 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
@@ -150,13 +151,15 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
when (message.type) { when (message.type) {
OFFER -> { OFFER -> {
// show bottom sheet // show bottom sheet
CallBottomSheet().apply { if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
arguments = bundleOf( CallBottomSheet().apply {
CallBottomSheet.ARGUMENT_ADDRESS to sender, arguments = bundleOf(
CallBottomSheet.ARGUMENT_SDP to message.sdps.toTypedArray(), CallBottomSheet.ARGUMENT_ADDRESS to sender,
CallBottomSheet.ARGUMENT_TYPE to message.type!!.number CallBottomSheet.ARGUMENT_SDP to message.sdps.toTypedArray(),
) CallBottomSheet.ARGUMENT_TYPE to message.type!!.number
show(this@HomeActivity.supportFragmentManager,"call-sheet") )
show(this@HomeActivity.supportFragmentManager,"call-sheet")
}
} }
} }
END_CALL -> { END_CALL -> {

View File

@@ -2,7 +2,42 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:padding="@dimen/small_spacing"
android:elevation="8dp"
tools:visibility="visible"
android:textColor="@color/white"
tools:text="relay"
android:visibility="gone"
android:background="@drawable/pill"
android:backgroundTint="@color/black"
android:layout_marginTop="@dimen/medium_spacing"
android:id="@+id/local_candidate_info"
app:layout_constraintEnd_toStartOf="@id/remote_candidate_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:padding="@dimen/small_spacing"
android:elevation="8dp"
tools:visibility="visible"
tools:text="relay"
android:visibility="gone"
android:textColor="@color/white"
android:background="@drawable/pill"
android:backgroundTint="@color/black"
android:layout_marginTop="@dimen/medium_spacing"
android:id="@+id/remote_candidate_info"
app:layout_constraintStart_toEndOf="@id/local_candidate_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<org.webrtc.SurfaceViewRenderer <org.webrtc.SurfaceViewRenderer