@ -3,10 +3,7 @@ package org.thoughtcrime.securesms.webrtc
import android.content.Context
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager
import android.view.SurfaceView
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json
@ -34,7 +31,6 @@ import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioDeviceUpdat
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioEnabled
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioEnabled
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.RecipientUpdate
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.RecipientUpdate
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoEnabled
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoEnabled
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoSwapped
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
@ -114,8 +110,6 @@ class CallManager(
val videoEvents = _videoEvents . asSharedFlow ( )
val videoEvents = _videoEvents . asSharedFlow ( )
private val _remoteVideoEvents = MutableStateFlow ( VideoEnabled ( false ) )
private val _remoteVideoEvents = MutableStateFlow ( VideoEnabled ( false ) )
val remoteVideoEvents = _remoteVideoEvents . asSharedFlow ( )
val remoteVideoEvents = _remoteVideoEvents . asSharedFlow ( )
private val _videoViewSwappedEvents = MutableStateFlow ( VideoSwapped ( false ) )
val videoViewSwappedEvents = _videoViewSwappedEvents . asSharedFlow ( )
private val stateProcessor = StateProcessor ( CallState . Idle )
private val stateProcessor = StateProcessor ( CallState . Idle )
@ -158,13 +152,14 @@ class CallManager(
private val outgoingIceDebouncer = Debouncer ( 200L )
private val outgoingIceDebouncer = Debouncer ( 200L )
var localRenderer : SurfaceViewRenderer ? = null
var floatingRenderer : SurfaceViewRenderer ? = null
var localFloatingRenderer : SurfaceViewRenderer ? = null
var remoteRotationSink : RemoteRotationVideoProxySink ? = null
var remoteRotationSink : RemoteRotationVideoProxySink ? = null
var remoteRenderer : SurfaceViewRenderer ? = null
var fullscreenRenderer : SurfaceViewRenderer ? = null
var remoteFloatingRenderer : SurfaceViewRenderer ? = null
private var peerConnectionFactory : PeerConnectionFactory ? = null
private var peerConnectionFactory : PeerConnectionFactory ? = null
// false when the user's video is in the floating render and true when it's in fullscreen
private var videoSwapped : Boolean = false
fun clearPendingIceUpdates ( ) {
fun clearPendingIceUpdates ( ) {
pendingOutgoingIceUpdates . clear ( )
pendingOutgoingIceUpdates . clear ( )
pendingIncomingIceUpdates . clear ( )
pendingIncomingIceUpdates . clear ( )
@ -225,31 +220,16 @@ class CallManager(
Util . runOnMainSync {
Util . runOnMainSync {
val base = EglBase . create ( )
val base = EglBase . create ( )
eglBase = base
eglBase = base
localRenderer = SurfaceViewRenderer ( context ) . apply {
floatingRenderer = SurfaceViewRenderer ( context )
// setScalingType(SCALE_ASPECT_FIT)
}
localFloatingRenderer = SurfaceViewRenderer ( context ) . apply {
// setScalingType(SCALE_ASPECT_FIT)
}
remoteRenderer = SurfaceViewRenderer ( context ) . apply {
// setScalingType(SCALE_ASPECT_FIT)
}
remoteFloatingRenderer = SurfaceViewRenderer ( context ) . apply {
fullscreenRenderer = SurfaceViewRenderer ( context )
// setScalingType(SCALE_ASPECT_FIT)
}
remoteRotationSink = RemoteRotationVideoProxySink ( )
remoteRotationSink = RemoteRotationVideoProxySink ( )
localRenderer ?. init ( base . eglBaseContext , null )
floatingRenderer ?. init ( base . eglBaseContext , null )
localRenderer ?. setMirror ( localCameraState . activeDirection == CameraState . Direction . FRONT )
fullscreenRenderer ?. init ( base . eglBaseContext , null )
localFloatingRenderer ?. init ( base . eglBaseContext , null )
remoteRotationSink !! . setSink ( fullscreenRenderer !! )
localFloatingRenderer ?. setMirror ( localCameraState . activeDirection == CameraState . Direction . FRONT )
remoteRenderer ?. init ( base . eglBaseContext , null )
remoteFloatingRenderer ?. init ( base . eglBaseContext , null )
remoteRotationSink !! . setSink ( remoteRenderer !! )
val encoderFactory = DefaultVideoEncoderFactory ( base . eglBaseContext , true , true )
val encoderFactory = DefaultVideoEncoderFactory ( base . eglBaseContext , true , true )
val decoderFactory = DefaultVideoDecoderFactory ( base . eglBaseContext )
val decoderFactory = DefaultVideoDecoderFactory ( base . eglBaseContext )
@ -403,17 +383,13 @@ class CallManager(
peerConnection ?. dispose ( )
peerConnection ?. dispose ( )
peerConnection = null
peerConnection = null
localFloatingRenderer ?. release ( )
floatingRenderer ?. release ( )
localRenderer ?. release ( )
remoteRotationSink ?. release ( )
remoteRotationSink ?. release ( )
remoteFloatingRenderer ?. release ( )
fullscreenRenderer ?. release ( )
remoteRenderer ?. release ( )
eglBase ?. release ( )
eglBase ?. release ( )
localFloatingRenderer = null
floatingRenderer = null
localRenderer = null
fullscreenRenderer = null
remoteFloatingRenderer = null
remoteRenderer = null
eglBase = null
eglBase = null
localCameraState = CameraState . UNKNOWN
localCameraState = CameraState . UNKNOWN
@ -425,7 +401,6 @@ class CallManager(
_audioEvents . value = AudioEnabled ( false )
_audioEvents . value = AudioEnabled ( false )
_videoEvents . value = VideoEnabled ( false )
_videoEvents . value = VideoEnabled ( false )
_remoteVideoEvents . value = VideoEnabled ( false )
_remoteVideoEvents . value = VideoEnabled ( false )
_videoViewSwappedEvents . value = VideoSwapped ( false )
pendingOutgoingIceUpdates . clear ( )
pendingOutgoingIceUpdates . clear ( )
pendingIncomingIceUpdates . clear ( )
pendingIncomingIceUpdates . clear ( )
}
}
@ -436,7 +411,7 @@ class CallManager(
// If the camera we've switched to is the front one then mirror it to match what someone
// If the camera we've switched to is the front one then mirror it to match what someone
// would see when looking in the mirror rather than the left<-->right flipped version.
// would see when looking in the mirror rather than the left<-->right flipped version.
localRenderer ?. setMirror ( localCameraState . activeDirection == CameraState . Direction . FRONT )
handleUserMirroring ( )
}
}
fun onPreOffer ( callId : UUID , recipient : Recipient , onSuccess : ( ) -> Unit ) {
fun onPreOffer ( callId : UUID , recipient : Recipient , onSuccess : ( ) -> Unit ) {
@ -494,7 +469,7 @@ class CallManager(
val recipient = recipient ?: return Promise . ofFail ( NullPointerException ( " recipient is null " ) )
val recipient = recipient ?: return Promise . ofFail ( NullPointerException ( " recipient is null " ) )
val offer = pendingOffer ?: return Promise . ofFail ( NullPointerException ( " pendingOffer is null " ) )
val offer = pendingOffer ?: return Promise . ofFail ( NullPointerException ( " pendingOffer is null " ) )
val factory = peerConnectionFactory ?: return Promise . ofFail ( NullPointerException ( " peerConnectionFactory is null " ) )
val factory = peerConnectionFactory ?: return Promise . ofFail ( NullPointerException ( " peerConnectionFactory is null " ) )
val local = localF loatingRenderer ?: return Promise . ofFail ( NullPointerException ( " localRenderer is null " ) )
val local = f loatingRenderer ?: return Promise . ofFail ( NullPointerException ( " localRenderer is null " ) )
val base = eglBase ?: return Promise . ofFail ( NullPointerException ( " eglBase is null " ) )
val base = eglBase ?: return Promise . ofFail ( NullPointerException ( " eglBase is null " ) )
val connection = PeerConnectionWrapper (
val connection = PeerConnectionWrapper (
context ,
context ,
@ -540,7 +515,7 @@ class CallManager(
?: return Promise . ofFail ( NullPointerException ( " recipient is null " ) )
?: return Promise . ofFail ( NullPointerException ( " recipient is null " ) )
val factory = peerConnectionFactory
val factory = peerConnectionFactory
?: return Promise . ofFail ( NullPointerException ( " peerConnectionFactory is null " ) )
?: return Promise . ofFail ( NullPointerException ( " peerConnectionFactory is null " ) )
val local = localF loatingRenderer
val local = f loatingRenderer
?: return Promise . ofFail ( NullPointerException ( " localRenderer is null " ) )
?: return Promise . ofFail ( NullPointerException ( " localRenderer is null " ) )
val base = eglBase ?: return Promise . ofFail ( NullPointerException ( " eglBase is null " ) )
val base = eglBase ?: return Promise . ofFail ( NullPointerException ( " eglBase is null " ) )
@ -635,13 +610,16 @@ class CallManager(
}
}
fun handleSwapVideoView ( swapped : Boolean ) {
fun handleSwapVideoView ( swapped : Boolean ) {
_videoViewSwappedEvents . value = VideoSwapped ( ! swapped )
videoSwapped = swapped
if ( ! swapped ) {
if ( ! swapped ) {
peerConnection ?. rotationVideoSink ?. setSink ( localRenderer )
peerConnection ?. rotationVideoSink ?. apply {
remoteRotationSink ?. setSink ( remoteFloatingRenderer !! )
setSink ( floatingRenderer )
}
fullscreenRenderer ?. let { remoteRotationSink ?. setSink ( it ) }
} else {
} else {
peerConnection ?. rotationVideoSink ?. setSink ( localFloatingRenderer )
peerConnection ?. rotationVideoSink ?. setSink ( fullscreen Renderer)
remoteRotationSink?. setSink ( remoteRenderer !! )
floatingRenderer?. let { remoteRotationSink?. setSink ( it ) }
}
}
}
}
@ -650,8 +628,24 @@ class CallManager(
peerConnection ?. setAudioEnabled ( ! muted )
peerConnection ?. setAudioEnabled ( ! muted )
}
}
/ * *
* Returns the renderer currently showing the user ' s video , not the contact ' s
* /
private fun getUserRenderer ( ) = if ( videoSwapped ) fullscreenRenderer else floatingRenderer
/ * *
* Makes sure the user ' s renderer applies mirroring if necessary
* /
private fun handleUserMirroring ( ) = getUserRenderer ( ) ?. setMirror ( isCameraFrontFacing ( ) )
fun handleSetMuteVideo ( muted : Boolean , lockManager : LockManager ) {
fun handleSetMuteVideo ( muted : Boolean , lockManager : LockManager ) {
_videoEvents . value = VideoEnabled ( ! muted )
_videoEvents . value = VideoEnabled ( ! muted )
// if we have video and the camera is not front facing, make sure to mirror stream
if ( ! muted ) {
handleUserMirroring ( )
}
val connection = peerConnection ?: return
val connection = peerConnection ?: return
connection . setVideoEnabled ( ! muted )
connection . setVideoEnabled ( ! muted )
dataChannel ?. let { channel ->
dataChannel ?. let { channel ->
@ -687,9 +681,19 @@ class CallManager(
}
}
}
}
fun setDeviceRotation ( newRotation : Int ) {
fun setDeviceOrientation ( orientation : Orientation ) {
peerConnection ?. setDeviceRotation ( newRotation )
// set rotation to the video based on the device's orientation and the camera facing direction
remoteRotationSink ?. rotation = newRotation
val rotation = when {
orientation == Orientation . PORTRAIT -> 0
orientation == Orientation . LANDSCAPE && isCameraFrontFacing ( ) -> 90
orientation == Orientation . LANDSCAPE && !is CameraFrontFacing ( ) -> - 90
orientation == Orientation . REVERSED _LANDSCAPE -> 270
else -> 0
}
// apply the rotation to the streams
peerConnection ?. setDeviceRotation ( rotation )
remoteRotationSink ?. rotation = rotation
}
}
fun handleWiredHeadsetChanged ( present : Boolean ) {
fun handleWiredHeadsetChanged ( present : Boolean ) {
@ -786,6 +790,8 @@ class CallManager(
fun isInitiator ( ) : Boolean = peerConnection ?. isInitiator ( ) == true
fun isInitiator ( ) : Boolean = peerConnection ?. isInitiator ( ) == true
fun isCameraFrontFacing ( ) = localCameraState . activeDirection == CameraState . Direction . FRONT
interface WebRtcListener : PeerConnection . Observer {
interface WebRtcListener : PeerConnection . Observer {
fun onHangup ( )
fun onHangup ( )
}
}