mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 05:37:47 +00:00
feat: add call stats report on frontend
This commit is contained in:
@@ -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?) {
|
||||||
|
@@ -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 -> {
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user