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
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_webrtc_tests.*
import kotlinx.coroutines.delay
@@ -18,6 +20,7 @@ import org.session.libsession.utilities.Debouncer
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.permissions.Permissions
import org.webrtc.*
@@ -63,6 +66,26 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
private val candidates: MutableList<IceCandidate> = mutableListOf()
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 val peerConnection by lazy {
// 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)
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 {
setMirror(true)
setEnableHardwareScaler(true)
init(eglBase.eglBaseContext, null)
}
remote_renderer.run {
setMirror(true)
setEnableHardwareScaler(true)
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
if (intent.action == ACTION_ANSWER) {
callAddress = intent.getParcelableExtra(EXTRA_ADDRESS) ?: run { finish(); return }
@@ -175,11 +186,27 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
override fun onStatsDelivered(statsReport: RTCStatsReport?) {
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")
}
}
private fun endCall() {
if (isFinishing) return
MessageSender.sendNonDurably(
CallMessage.endCall(),
callAddress
@@ -193,6 +220,26 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
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? {
val deviceNames = enumerator.deviceNames
@@ -237,16 +284,6 @@ class WebRtcTestsActivity: PassphraseRequiredActionBarActivity(), PeerConnection
override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
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?) {

View File

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

View File

@@ -2,7 +2,42 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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