mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-17 11:48:51 +03:00
Experimental implementation of Telecom API.
This commit is contained in:
parent
a1fc0db8a2
commit
4a4edcf82a
14 changed files with 176 additions and 42 deletions
|
@ -22,12 +22,10 @@ import org.webrtc.SessionDescription
|
|||
|
||||
interface CallService {
|
||||
|
||||
|
||||
fun getTurnServer(callback: MatrixCallback<TurnServer?>)
|
||||
|
||||
fun isCallSupportedInRoom(roomId: String) : Boolean
|
||||
|
||||
|
||||
/**
|
||||
* Send offer SDP to the other participant.
|
||||
*/
|
||||
|
@ -48,10 +46,7 @@ interface CallService {
|
|||
*/
|
||||
fun sendLocalIceCandidateRemovals(callId: String, roomId: String, candidates: List<IceCandidate>)
|
||||
|
||||
|
||||
fun addCallListener(listener: CallsListener)
|
||||
|
||||
fun removeCallListener(listener: CallsListener)
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -46,5 +46,4 @@ interface CallsListener {
|
|||
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent)
|
||||
|
||||
fun onCallHangupReceived(callHangupContent: CallHangupContent)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
///*
|
||||
// /*
|
||||
// * Copyright (c) 2020 New Vector Ltd
|
||||
// *
|
||||
// * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -14,13 +14,13 @@
|
|||
// * limitations under the License.
|
||||
// */
|
||||
//
|
||||
//package im.vector.matrix.android.api.session.call
|
||||
// package im.vector.matrix.android.api.session.call
|
||||
//
|
||||
//import im.vector.matrix.android.api.MatrixCallback
|
||||
//import org.webrtc.IceCandidate
|
||||
//import org.webrtc.SessionDescription
|
||||
// import im.vector.matrix.android.api.MatrixCallback
|
||||
// import org.webrtc.IceCandidate
|
||||
// import org.webrtc.SessionDescription
|
||||
//
|
||||
//interface PeerSignalingClient {
|
||||
// interface PeerSignalingClient {
|
||||
//
|
||||
// val callID: String
|
||||
//
|
||||
|
@ -63,4 +63,4 @@
|
|||
// */
|
||||
// fun onRemoteIceCandidatesRemoved(candidates: List<IceCandidate>)
|
||||
// }
|
||||
//}
|
||||
// }
|
||||
|
|
|
@ -24,5 +24,4 @@ internal interface VoipApi {
|
|||
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer")
|
||||
fun getTurnServer(): Call<TurnServer>
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import im.vector.matrix.android.internal.database.model.EventEntity
|
|||
import im.vector.matrix.android.internal.database.query.whereTypes
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationTask
|
||||
import io.realm.OrderedCollectionChangeSet
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.RealmResults
|
||||
|
|
|
@ -51,11 +51,11 @@ internal class DefaultCallService @Inject constructor(
|
|||
private val callListeners = ArrayList<CallsListener>()
|
||||
|
||||
override fun getTurnServer(callback: MatrixCallback<TurnServer?>) {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun isCallSupportedInRoom(roomId: String): Boolean {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun sendOfferSdp(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback<String>) {
|
||||
|
|
|
@ -184,7 +184,7 @@ class DebugMenuActivity : VectorBaseActivity() {
|
|||
@OnClick(R.id.debug_scan_qr_code)
|
||||
fun scanQRCode() {
|
||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||
//doScanQRCode()
|
||||
// doScanQRCode()
|
||||
startActivity(VectorCallActivity.newIntent(this, "!cyIJhOLwWgmmqreHLD:matrix.org"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
|
@ -20,8 +21,10 @@ package im.vector.riotx.core.services
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import androidx.core.content.ContextCompat
|
||||
import im.vector.riotx.core.extensions.vectorComponent
|
||||
import im.vector.riotx.features.call.CallConnection
|
||||
import im.vector.riotx.features.notifications.NotificationUtils
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -30,6 +33,8 @@ import timber.log.Timber
|
|||
*/
|
||||
class CallService : VectorService() {
|
||||
|
||||
private val connections = mutableMapOf<String, CallConnection>()
|
||||
|
||||
/**
|
||||
* call in progress (foreground notification)
|
||||
*/
|
||||
|
@ -154,6 +159,10 @@ class CallService : VectorService() {
|
|||
myStopSelf()
|
||||
}
|
||||
|
||||
fun addConnection(callConnection: CallConnection) {
|
||||
connections[callConnection.callId] = callConnection
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 6480
|
||||
|
||||
|
@ -214,4 +223,10 @@ class CallService : VectorService() {
|
|||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
inner class CallServiceBinder : Binder() {
|
||||
fun getCallService(): CallService {
|
||||
return this@CallService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,24 +19,113 @@ package im.vector.riotx.features.call
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.telecom.Connection
|
||||
import android.telecom.DisconnectCause
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.webrtc.Camera1Enumerator
|
||||
import org.webrtc.Camera2Enumerator
|
||||
import org.webrtc.IceCandidate
|
||||
import org.webrtc.MediaStream
|
||||
import org.webrtc.PeerConnection
|
||||
import org.webrtc.SessionDescription
|
||||
import org.webrtc.VideoTrack
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M) class CallConnection(
|
||||
private val context: Context,
|
||||
private val roomId: String,
|
||||
private val callId: String
|
||||
) : Connection() {
|
||||
val callId: String
|
||||
) : Connection(), WebRtcPeerConnectionManager.Listener {
|
||||
|
||||
@Inject lateinit var peerConnectionManager: WebRtcPeerConnectionManager
|
||||
@Inject lateinit var callViewModel: VectorCallViewModel
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
connectionProperties = PROPERTY_SELF_MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The telecom subsystem calls this method when you add a new incoming call and your app should show its incoming call UI.
|
||||
*/
|
||||
override fun onShowIncomingCallUi() {
|
||||
super.onShowIncomingCallUi()
|
||||
Timber.i("onShowIncomingCallUi")
|
||||
/*
|
||||
VectorCallActivity.newIntent(context, roomId).let {
|
||||
context.startActivity(it)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
override fun onAnswer() {
|
||||
super.onAnswer()
|
||||
// startCall()
|
||||
Timber.i("onShowIncomingCallUi")
|
||||
}
|
||||
|
||||
override fun onStateChanged(state: Int) {
|
||||
super.onStateChanged(state)
|
||||
Timber.i("onStateChanged${stateToString(state)}")
|
||||
}
|
||||
|
||||
override fun onReject() {
|
||||
super.onReject()
|
||||
Timber.i("onReject")
|
||||
close()
|
||||
}
|
||||
|
||||
override fun onDisconnect() {
|
||||
onDisconnect()
|
||||
Timber.i("onDisconnect")
|
||||
close()
|
||||
}
|
||||
|
||||
private fun close() {
|
||||
setDisconnected(DisconnectCause(DisconnectCause.CANCELED))
|
||||
destroy()
|
||||
}
|
||||
|
||||
private fun startCall() {
|
||||
peerConnectionManager.createPeerConnectionFactory()
|
||||
peerConnectionManager.listener = this
|
||||
|
||||
val cameraIterator = if (Camera2Enumerator.isSupported(context)) Camera2Enumerator(context) else Camera1Enumerator(false)
|
||||
val frontCamera = cameraIterator.deviceNames
|
||||
?.firstOrNull { cameraIterator.isFrontFacing(it) }
|
||||
?: cameraIterator.deviceNames?.first()
|
||||
?: return
|
||||
val videoCapturer = cameraIterator.createCapturer(frontCamera, null)
|
||||
|
||||
val iceServers = ArrayList<PeerConnection.IceServer>().apply {
|
||||
listOf("turn:turn.matrix.org:3478?transport=udp", "turn:turn.matrix.org:3478?transport=tcp", "turns:turn.matrix.org:443?transport=tcp").forEach {
|
||||
add(
|
||||
PeerConnection.IceServer.builder(it)
|
||||
.setUsername("xxxxx")
|
||||
.setPassword("xxxxx")
|
||||
.createIceServer()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
peerConnectionManager.createPeerConnection(videoCapturer, iceServers)
|
||||
peerConnectionManager.startCall()
|
||||
}
|
||||
|
||||
override fun addLocalIceCandidate(candidates: IceCandidate) {
|
||||
}
|
||||
|
||||
override fun addRemoteVideoTrack(videoTrack: VideoTrack) {
|
||||
}
|
||||
|
||||
override fun addLocalVideoTrack(videoTrack: VideoTrack) {
|
||||
}
|
||||
|
||||
override fun removeRemoteVideoStream(mediaStream: MediaStream) {
|
||||
}
|
||||
|
||||
override fun sendOffer(sessionDescription: SessionDescription) {
|
||||
callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
|
|||
|
||||
// private var peerConnectionFactory: PeerConnectionFactory? = null
|
||||
|
||||
//private var peerConnection: PeerConnection? = null
|
||||
// private var peerConnection: PeerConnection? = null
|
||||
|
||||
// private var remoteVideoTrack: VideoTrack? = null
|
||||
|
||||
|
@ -152,12 +152,11 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
|
|||
fullscreenRenderer.init(rootEglBase!!.eglBaseContext, null)
|
||||
fullscreenRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
|
||||
|
||||
|
||||
pipRenderer.setZOrderMediaOverlay(true);
|
||||
pipRenderer.setEnableHardwareScaler(true /* enabled */);
|
||||
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */);
|
||||
pipRenderer.setZOrderMediaOverlay(true)
|
||||
pipRenderer.setEnableHardwareScaler(true /* enabled */)
|
||||
fullscreenRenderer.setEnableHardwareScaler(true /* enabled */)
|
||||
// Start with local feed in fullscreen and swap it to the pip when the call is connected.
|
||||
//setSwappedFeeds(true /* isSwappedFeeds */);
|
||||
// setSwappedFeeds(true /* isSwappedFeeds */);
|
||||
|
||||
if (isFirstCreation()) {
|
||||
peerConnectionManager.createPeerConnectionFactory()
|
||||
|
@ -374,9 +373,9 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
|
|||
// if (requestCode != CAPTURE_PERMISSION_REQUEST_CODE) {
|
||||
// super.onActivityResult(requestCode, resultCode, data)
|
||||
// }
|
||||
//// mediaProjectionPermissionResultCode = resultCode;
|
||||
//// mediaProjectionPermissionResultData = data;
|
||||
//// startCall();
|
||||
// // mediaProjectionPermissionResultCode = resultCode;
|
||||
// // mediaProjectionPermissionResultData = data;
|
||||
// // startCall();
|
||||
// }
|
||||
|
||||
companion object {
|
||||
|
@ -426,7 +425,6 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis
|
|||
}
|
||||
|
||||
override fun removeRemoteVideoStream(mediaStream: MediaStream) {
|
||||
|
||||
}
|
||||
|
||||
override fun onDisconnect() {
|
||||
|
|
|
@ -70,7 +70,6 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun onCallInviteReceived(signalingRoomId: String, callInviteContent: CallInviteContent) {
|
||||
|
||||
}
|
||||
|
||||
override fun onCallHangupReceived(callHangupContent: CallHangupContent) {
|
||||
|
|
|
@ -16,12 +16,20 @@
|
|||
|
||||
package im.vector.riotx.features.call
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.telecom.Connection
|
||||
import android.telecom.ConnectionRequest
|
||||
import android.telecom.ConnectionService
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telecom.StatusHints
|
||||
import android.telecom.TelecomManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import im.vector.riotx.core.services.CallService
|
||||
|
||||
/**
|
||||
* No active calls in other apps
|
||||
|
@ -49,4 +57,35 @@ import androidx.annotation.RequiresApi
|
|||
val roomId = request.extras.getString("MX_CALL_ROOM_ID") ?: return null
|
||||
return CallConnection(applicationContext, roomId, callId)
|
||||
}
|
||||
|
||||
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest?): Connection {
|
||||
val roomId = request?.extras?.getString("MX_CALL_ROOM_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
|
||||
val callId = request.extras.getString("MX_CALL_CALL_ID") ?: return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
|
||||
|
||||
val connection = CallConnection(applicationContext, roomId, callId)
|
||||
connection.connectionCapabilities = Connection.CAPABILITY_MUTE
|
||||
connection.audioModeIsVoip = true
|
||||
connection.setAddress(Uri.fromParts("tel", "+905000000000", null), TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.setCallerDisplayName("RiotX Caller", TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.statusHints = StatusHints("Testing Hint...", null, null)
|
||||
|
||||
bindService(Intent(applicationContext, CallService::class.java), CallServiceConnection(connection), 0)
|
||||
connection.setInitializing()
|
||||
return CallConnection(applicationContext, roomId, callId)
|
||||
}
|
||||
|
||||
inner class CallServiceConnection(private val callConnection: CallConnection) : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
||||
val callSrvBinder = binder as CallService.CallServiceBinder
|
||||
callSrvBinder.getCallService().addConnection(callConnection)
|
||||
unbindService(this)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "TComService"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.os.Bundle
|
|||
import android.telecom.PhoneAccount
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telecom.TelecomManager
|
||||
import android.telecom.VideoProfile
|
||||
import androidx.core.content.ContextCompat
|
||||
import im.vector.matrix.android.api.session.call.CallsListener
|
||||
import im.vector.matrix.android.api.session.call.EglUtils
|
||||
|
@ -80,8 +81,11 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
val componentName = ComponentName(BuildConfig.APPLICATION_ID, VectorConnectionService::class.java.name)
|
||||
val appName = context.getString(R.string.app_name)
|
||||
phoneAccountHandle = PhoneAccountHandle(componentName, appName)
|
||||
val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, appName)
|
||||
val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, BuildConfig.APPLICATION_ID)
|
||||
.setIcon(Icon.createWithResource(context, R.drawable.riotx_logo))
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)
|
||||
.setCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)
|
||||
.build()
|
||||
ContextCompat.getSystemService(context, TelecomManager::class.java)
|
||||
?.registerPhoneAccount(phoneAccount)
|
||||
|
@ -96,7 +100,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
// Executor thread is started once and is used for all
|
||||
// peer connection API calls to ensure new peer connection factory is
|
||||
// created on the same thread as previously destroyed factory.
|
||||
private val executor = Executors.newSingleThreadExecutor();
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
private val rootEglBase by lazy { EglUtils.rootEglBase }
|
||||
|
||||
|
@ -139,7 +143,6 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
true)
|
||||
val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(eglBaseContext)
|
||||
|
||||
|
||||
Timber.v("## VOIP PeerConnectionFactory.createPeerConnectionFactory ...")
|
||||
peerConnectionFactory = PeerConnectionFactory.builder()
|
||||
.setOptions(options)
|
||||
|
@ -152,7 +155,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
fun createPeerConnection(videoCapturer: VideoCapturer, iceServers: List<PeerConnection.IceServer>) {
|
||||
executor.execute {
|
||||
Timber.v("## VOIP PeerConnectionFactory.createPeerConnection ${peerConnectionFactory}...")
|
||||
Timber.v("## VOIP PeerConnectionFactory.createPeerConnection $peerConnectionFactory...")
|
||||
// Following instruction here: https://stackoverflow.com/questions/55085726/webrtc-create-peerconnectionfactory-object
|
||||
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext)
|
||||
|
||||
|
@ -165,8 +168,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP Local video track created")
|
||||
listener?.addLocalVideoTrack(it)
|
||||
// localSurfaceRenderer?.get()?.let { surface ->
|
||||
//// it.addSink(surface)
|
||||
//// }
|
||||
// // it.addSink(surface)
|
||||
// // }
|
||||
}
|
||||
|
||||
// create a local audio track
|
||||
|
@ -282,7 +285,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
peerConnection?.setLocalDescription(object : SdpObserverAdapter() {
|
||||
override fun onSetSuccess() {
|
||||
listener?.sendOffer(sessionDescription)
|
||||
//callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
|
||||
// callViewModel.handle(VectorCallViewActions.SendOffer(sessionDescription))
|
||||
}
|
||||
}, sessionDescription)
|
||||
}
|
||||
|
@ -361,10 +364,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Bundle().apply {
|
||||
putString("MX_CALL_ROOM_ID", signalingRoomId)
|
||||
putString("MX_CALL_CALL_ID", callInviteContent.callId)
|
||||
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
|
||||
putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
|
||||
putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -376,5 +381,3 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@ import im.vector.riotx.core.platform.VectorViewModel
|
|||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.call.VectorCallActivity
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
|
|
Loading…
Add table
Reference in a new issue