From 95dc1d9f54ab2c7ccf43d79ef28824adb9271e19 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 5 Jul 2024 19:09:40 +1000 Subject: [PATCH] WebRTC rework Only using two sinks and swapping between them Reworked the device rotation logic as it didn't work well with pitch ( you could tip the device front to back and the rotation went out of whack, so had to resort to more robust calculation for the device orientation. Had to use a deprecated sensor setting but it's the only one I could use that works. --- .../securesms/calls/WebRtcCallActivity.kt | 138 ++++++++++-------- .../securesms/webrtc/CallManager.kt | 106 +++++++------- .../securesms/webrtc/CallViewModel.kt | 36 ++--- .../securesms/webrtc/Orientation.kt | 8 + .../securesms/webrtc/PeerConnectionWrapper.kt | 2 +- .../securesms/webrtc/data/CallUtils.kt | 11 -- .../video/RemoteRotationVideoProxySink.kt | 5 +- .../webrtc/video/RotationVideoSink.kt | 15 +- app/src/main/res/layout/activity_webrtc.xml | 32 ++-- 9 files changed, 172 insertions(+), 181 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/Orientation.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index 4caa83d265..522ef10140 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -5,11 +5,15 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager import android.media.AudioManager import android.os.Build import android.os.Bundle +import android.provider.Settings import android.view.MenuItem -import android.view.OrientationEventListener import android.view.WindowManager import androidx.activity.viewModels import androidx.core.content.ContextCompat @@ -21,7 +25,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive -import android.provider.Settings import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityWebrtcBinding @@ -43,11 +46,13 @@ import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_OUTGOING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_PRE_INIT import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RECONNECTING import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_RINGING +import org.thoughtcrime.securesms.webrtc.Orientation import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE +import kotlin.math.asin @AndroidEntryPoint -class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { +class WebRtcCallActivity : PassphraseRequiredActionBarActivity(), SensorEventListener { companion object { const val ACTION_PRE_OFFER = "pre-offer" @@ -71,16 +76,9 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } private var hangupReceiver: BroadcastReceiver? = null - private val rotationListener by lazy { - object : OrientationEventListener(this) { - override fun onOrientationChanged(orientation: Int) { - if ((orientation + 15) % 90 < 30) { - viewModel.deviceRotation = orientation -// updateControlsRotation(orientation.quadrantRotation() * -1) - } - } - } - } + private lateinit var sensorManager: SensorManager + private var rotationVectorSensor: Sensor? = null + private var lastOrientation = Orientation.UNKNOWN override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { @@ -104,9 +102,11 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { // Only enable auto-rotate if system auto-rotate is enabled if (isAutoRotateOn()) { - rotationListener.enable() - } else { - rotationListener.disable() + // Initialize the SensorManager + sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager + + // Initialize the sensors + rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) } binding = ActivityWebrtcBinding.inflate(layoutInflater) @@ -138,7 +138,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { binding.floatingRendererContainer.setOnClickListener { val swapVideoViewIntent = - WebRtcCallService.swapVideoViews(this, viewModel.videoViewSwapped) + WebRtcCallService.swapVideoViews(this, viewModel.toggleVideoSwap()) startService(swapVideoViewIntent) } @@ -207,12 +207,54 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { ) == 1 } + override fun onResume() { + super.onResume() + rotationVectorSensor?.also { sensor -> + sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI) + } + } + + override fun onPause() { + super.onPause() + sensorManager.unregisterListener(this) + } + + override fun onSensorChanged(event: SensorEvent) { + if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR) { + // Get the quaternion from the rotation vector sensor + val quaternion = FloatArray(4) + SensorManager.getQuaternionFromVector(quaternion, event.values) + + // Calculate Euler angles from the quaternion + val pitch = asin(2.0 * (quaternion[0] * quaternion[2] - quaternion[3] * quaternion[1])) + + // Convert radians to degrees + val pitchDegrees = Math.toDegrees(pitch).toFloat() + + // Determine the device's orientation based on the pitch and roll values + val currentOrientation = when { + pitchDegrees > 45 -> Orientation.LANDSCAPE + pitchDegrees < -45 -> Orientation.REVERSED_LANDSCAPE + else -> Orientation.PORTRAIT + } + + if (currentOrientation != lastOrientation) { + lastOrientation = currentOrientation + Log.d("", "*********** orientation: $currentOrientation") + viewModel.deviceOrientation = currentOrientation + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + override fun onDestroy() { super.onDestroy() hangupReceiver?.let { receiver -> LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } - rotationListener.disable() + + rotationVectorSensor = null } private fun answerCall() { @@ -354,62 +396,31 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { launch { viewModel.localVideoEnabledState.collect { isEnabled -> - binding.localFloatingRenderer.removeAllViews() - binding.localRenderer.removeAllViews() + binding.floatingRenderer.removeAllViews() if (isEnabled) { - viewModel.localRenderer?.let { surfaceView -> + viewModel.floatingRenderer?.let { surfaceView -> surfaceView.setZOrderOnTop(true) - - // Mirror the video preview of the person making the call to prevent disorienting them - surfaceView.setMirror(true) - - binding.localRenderer.addView(surfaceView) - } - viewModel.localFloatingRenderer?.let { surfaceView -> - surfaceView.setZOrderOnTop(true) - binding.localFloatingRenderer.addView(surfaceView) - + binding.floatingRenderer.addView(surfaceView) } } - binding.localFloatingRenderer.isVisible = isEnabled && !viewModel.videoViewSwapped - binding.localRenderer.isVisible = isEnabled && viewModel.videoViewSwapped + + binding.floatingRenderer.isVisible = isEnabled binding.enableCameraButton.isSelected = isEnabled - binding.floatingRendererContainer.isVisible = binding.localFloatingRenderer.isVisible - binding.videocamOffIcon.isVisible = !binding.localFloatingRenderer.isVisible - binding.remoteRecipient.isVisible = !(binding.remoteRenderer.isVisible || binding.localRenderer.isVisible) - binding.swapViewIcon.bringToFront() + //binding.swapViewIcon.bringToFront() } } launch { viewModel.remoteVideoEnabledState.collect { isEnabled -> - binding.remoteRenderer.removeAllViews() - binding.remoteFloatingRenderer.removeAllViews() + binding.fullscreenRenderer.removeAllViews() if (isEnabled) { - viewModel.remoteRenderer?.let { surfaceView -> - binding.remoteRenderer.addView(surfaceView) - } - viewModel.remoteFloatingRenderer?.let { surfaceView -> - surfaceView.setZOrderOnTop(true) - binding.remoteFloatingRenderer.addView(surfaceView) + viewModel.fullscreenRenderer?.let { surfaceView -> + binding.fullscreenRenderer.addView(surfaceView) } } - binding.remoteRenderer.isVisible = isEnabled && !viewModel.videoViewSwapped - binding.remoteFloatingRenderer.isVisible = isEnabled && viewModel.videoViewSwapped - binding.videocamOffIcon.isVisible = !binding.remoteFloatingRenderer.isVisible - binding.floatingRendererContainer.isVisible = binding.remoteFloatingRenderer.isVisible - binding.remoteRecipient.isVisible = !(binding.remoteRenderer.isVisible || binding.localRenderer.isVisible) - binding.swapViewIcon.bringToFront() - } - } - - launch { - viewModel.videoViewSwappedState.collect{ isSwapped -> - binding.remoteRenderer.isVisible = !isSwapped && viewModel.remoteVideoEnabled - binding.remoteFloatingRenderer.isVisible = isSwapped && viewModel.remoteVideoEnabled - binding.localFloatingRenderer.isVisible = !isSwapped && viewModel.videoEnabled - binding.localRenderer.isVisible = isSwapped && viewModel.videoEnabled - binding.floatingRendererContainer.isVisible = binding.localFloatingRenderer.isVisible || binding.remoteFloatingRenderer.isVisible + binding.fullscreenRenderer.isVisible = isEnabled + binding.remoteRecipient.isVisible = !isEnabled + //binding.swapViewIcon.bringToFront() } } } @@ -424,8 +435,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { override fun onStop() { super.onStop() uiJob?.cancel() - binding.remoteFloatingRenderer.removeAllViews() - binding.remoteRenderer.removeAllViews() - binding.localRenderer.removeAllViews() + binding.fullscreenRenderer.removeAllViews() + binding.floatingRenderer.removeAllViews() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 4259971794..3c057c2a71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -3,10 +3,7 @@ package org.thoughtcrime.securesms.webrtc import android.content.Context import android.content.pm.PackageManager import android.telephony.TelephonyManager -import android.view.SurfaceView -import android.view.View import androidx.core.content.ContextCompat -import androidx.core.view.isVisible import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow 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.RecipientUpdate 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.OutgoingRinger import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager @@ -114,8 +110,6 @@ class CallManager( val videoEvents = _videoEvents.asSharedFlow() private val _remoteVideoEvents = MutableStateFlow(VideoEnabled(false)) val remoteVideoEvents = _remoteVideoEvents.asSharedFlow() - private val _videoViewSwappedEvents = MutableStateFlow(VideoSwapped(false)) - val videoViewSwappedEvents = _videoViewSwappedEvents.asSharedFlow() private val stateProcessor = StateProcessor(CallState.Idle) @@ -158,13 +152,14 @@ class CallManager( private val outgoingIceDebouncer = Debouncer(200L) - var localRenderer: SurfaceViewRenderer? = null - var localFloatingRenderer: SurfaceViewRenderer? = null + var floatingRenderer: SurfaceViewRenderer? = null var remoteRotationSink: RemoteRotationVideoProxySink? = null - var remoteRenderer: SurfaceViewRenderer? = null - var remoteFloatingRenderer: SurfaceViewRenderer? = null + var fullscreenRenderer: SurfaceViewRenderer? = 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() { pendingOutgoingIceUpdates.clear() pendingIncomingIceUpdates.clear() @@ -225,31 +220,16 @@ class CallManager( Util.runOnMainSync { val base = EglBase.create() eglBase = base - localRenderer = SurfaceViewRenderer(context).apply { -// setScalingType(SCALE_ASPECT_FIT) - } + floatingRenderer = SurfaceViewRenderer(context) - localFloatingRenderer = SurfaceViewRenderer(context).apply { -// setScalingType(SCALE_ASPECT_FIT) - } - remoteRenderer = SurfaceViewRenderer(context).apply { -// setScalingType(SCALE_ASPECT_FIT) - } - - remoteFloatingRenderer = SurfaceViewRenderer(context).apply { -// setScalingType(SCALE_ASPECT_FIT) - } + fullscreenRenderer = SurfaceViewRenderer(context) remoteRotationSink = RemoteRotationVideoProxySink() - localRenderer?.init(base.eglBaseContext, null) - localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT) - localFloatingRenderer?.init(base.eglBaseContext, null) - localFloatingRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT) - remoteRenderer?.init(base.eglBaseContext, null) - remoteFloatingRenderer?.init(base.eglBaseContext, null) - remoteRotationSink!!.setSink(remoteRenderer!!) + floatingRenderer?.init(base.eglBaseContext, null) + fullscreenRenderer?.init(base.eglBaseContext, null) + remoteRotationSink!!.setSink(fullscreenRenderer!!) val encoderFactory = DefaultVideoEncoderFactory(base.eglBaseContext, true, true) val decoderFactory = DefaultVideoDecoderFactory(base.eglBaseContext) @@ -403,17 +383,13 @@ class CallManager( peerConnection?.dispose() peerConnection = null - localFloatingRenderer?.release() - localRenderer?.release() + floatingRenderer?.release() remoteRotationSink?.release() - remoteFloatingRenderer?.release() - remoteRenderer?.release() + fullscreenRenderer?.release() eglBase?.release() - localFloatingRenderer = null - localRenderer = null - remoteFloatingRenderer = null - remoteRenderer = null + floatingRenderer = null + fullscreenRenderer = null eglBase = null localCameraState = CameraState.UNKNOWN @@ -425,7 +401,6 @@ class CallManager( _audioEvents.value = AudioEnabled(false) _videoEvents.value = VideoEnabled(false) _remoteVideoEvents.value = VideoEnabled(false) - _videoViewSwappedEvents.value = VideoSwapped(false) pendingOutgoingIceUpdates.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 // 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) { @@ -494,7 +469,7 @@ class CallManager( val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null")) val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null")) val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null")) - val local = localFloatingRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null")) + val local = floatingRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null")) val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null")) val connection = PeerConnectionWrapper( context, @@ -540,7 +515,7 @@ class CallManager( ?: return Promise.ofFail(NullPointerException("recipient is null")) val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null")) - val local = localFloatingRenderer + val local = floatingRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null")) val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null")) @@ -635,13 +610,16 @@ class CallManager( } fun handleSwapVideoView(swapped: Boolean) { - _videoViewSwappedEvents.value = VideoSwapped(!swapped) + videoSwapped = swapped + if (!swapped) { - peerConnection?.rotationVideoSink?.setSink(localRenderer) - remoteRotationSink?.setSink(remoteFloatingRenderer!!) + peerConnection?.rotationVideoSink?.apply { + setSink(floatingRenderer) + } + fullscreenRenderer?.let{ remoteRotationSink?.setSink(it) } } else { - peerConnection?.rotationVideoSink?.setSink(localFloatingRenderer) - remoteRotationSink?.setSink(remoteRenderer!!) + peerConnection?.rotationVideoSink?.setSink(fullscreenRenderer) + floatingRenderer?.let{remoteRotationSink?.setSink(it) } } } @@ -650,8 +628,24 @@ class CallManager( 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) { _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 connection.setVideoEnabled(!muted) dataChannel?.let { channel -> @@ -687,9 +681,19 @@ class CallManager( } } - fun setDeviceRotation(newRotation: Int) { - peerConnection?.setDeviceRotation(newRotation) - remoteRotationSink?.rotation = newRotation + fun setDeviceOrientation(orientation: Orientation) { + // set rotation to the video based on the device's orientation and the camera facing direction + val rotation = when{ + orientation == Orientation.PORTRAIT -> 0 + orientation == Orientation.LANDSCAPE && isCameraFrontFacing() -> 90 + orientation == Orientation.LANDSCAPE && !isCameraFrontFacing() -> -90 + orientation == Orientation.REVERSED_LANDSCAPE -> 270 + else -> 0 + } + + // apply the rotation to the streams + peerConnection?.setDeviceRotation(rotation) + remoteRotationSink?.rotation = rotation } fun handleWiredHeadsetChanged(present: Boolean) { @@ -786,6 +790,8 @@ class CallManager( fun isInitiator(): Boolean = peerConnection?.isInitiator() == true + fun isCameraFrontFacing() = localCameraState.activeDirection == CameraState.Direction.FRONT + interface WebRtcListener: PeerConnection.Observer { fun onHangup() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index c6cfb37888..e2e82a418a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -29,17 +29,11 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V UNTRUSTED_IDENTITY, } - val localRenderer: SurfaceViewRenderer? - get() = callManager.localRenderer + val floatingRenderer: SurfaceViewRenderer? + get() = callManager.floatingRenderer - val localFloatingRenderer: SurfaceViewRenderer? - get() = callManager.localFloatingRenderer - - val remoteRenderer: SurfaceViewRenderer? - get() = callManager.remoteRenderer - - val remoteFloatingRenderer: SurfaceViewRenderer? - get() = callManager.remoteFloatingRenderer + val fullscreenRenderer: SurfaceViewRenderer? + get() = callManager.fullscreenRenderer private var _videoEnabled: Boolean = false @@ -48,14 +42,8 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V private var _remoteVideoEnabled: Boolean = false - val remoteVideoEnabled: Boolean - get() = _remoteVideoEnabled - private var _videoViewSwapped: Boolean = false - val videoViewSwapped: Boolean - get() = _videoViewSwapped - private var _microphoneEnabled: Boolean = true val microphoneEnabled: Boolean @@ -85,15 +73,10 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V .map { it.isEnabled } .onEach { _remoteVideoEnabled = it } - val videoViewSwappedState - get() = callManager.videoViewSwappedEvents - .map { it.isSwapped } - .onEach { _videoViewSwapped = it } - - var deviceRotation: Int = 0 + var deviceOrientation: Orientation = Orientation.UNKNOWN set(value) { field = value - callManager.setDeviceRotation(value) + callManager.setDeviceOrientation(value) } val currentCallState @@ -108,4 +91,11 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V val callStartTime: Long get() = callManager.callStartTime + /** + * Toggles the video swapped state, and return the value post toggle + */ + fun toggleVideoSwap(): Boolean { + _videoViewSwapped = !_videoViewSwapped + return _videoViewSwapped + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/Orientation.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/Orientation.kt new file mode 100644 index 0000000000..05370fda4a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/Orientation.kt @@ -0,0 +1,8 @@ +package org.thoughtcrime.securesms.webrtc + +enum class Orientation { + PORTRAIT, + LANDSCAPE, + REVERSED_LANDSCAPE, + UNKNOWN +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt index 7e1305f497..b61edbb6d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt @@ -103,7 +103,7 @@ class PeerConnectionWrapper(private val context: Context, context, rotationVideoSink ) - rotationVideoSink.mirrored = newCamera.activeDirection == CameraState.Direction.FRONT + rotationVideoSink.setSink(localRenderer) newVideoTrack.setEnabled(false) mediaStream.addTrack(newVideoTrack) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt deleted file mode 100644 index dc9f07d051..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/data/CallUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.thoughtcrime.securesms.webrtc.data - -// get the video rotation from a specific rotation, locked into 90 degree -// chunks offset by 45 degrees -fun Int.quadrantRotation() = when (this % 360) { - in 315 .. 360, - in 0 until 45 -> 0 - in 45 until 135 -> 90 - in 135 until 225 -> 180 - else -> 270 -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt index 2b0caef89c..62b78d6ec8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RemoteRotationVideoProxySink.kt @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.webrtc.video -import org.thoughtcrime.securesms.webrtc.data.quadrantRotation + import org.webrtc.VideoFrame import org.webrtc.VideoSink @@ -14,8 +14,7 @@ class RemoteRotationVideoProxySink: VideoSink { val thisSink = targetSink ?: return val thisFrame = frame ?: return - val quadrantRotation = rotation.quadrantRotation() - val modifiedRotation = thisFrame.rotation - quadrantRotation + val modifiedRotation = thisFrame.rotation - rotation val newFrame = VideoFrame(thisFrame.buffer, modifiedRotation, thisFrame.timestampNs) thisSink.onFrame(newFrame) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt index ec43daf2ef..3522f06f9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.webrtc.video -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.webrtc.data.quadrantRotation + import org.webrtc.CapturerObserver import org.webrtc.VideoFrame import org.webrtc.VideoProcessor @@ -12,7 +11,6 @@ import java.util.concurrent.atomic.AtomicBoolean class RotationVideoSink: CapturerObserver, VideoProcessor { var rotation: Int = 0 - var mirrored = false private val capturing = AtomicBoolean(false) private var capturerObserver = SoftReference(null) @@ -31,13 +29,14 @@ class RotationVideoSink: CapturerObserver, VideoProcessor { val observer = capturerObserver.get() if (videoFrame == null || observer == null || !capturing.get()) return - val quadrantRotation = rotation.quadrantRotation() - - val newFrame = VideoFrame(videoFrame.buffer, (videoFrame.rotation + quadrantRotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1) % 360, videoFrame.timestampNs) - val localFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation * if (mirrored && quadrantRotation in listOf(90,270)) -1 else 1, videoFrame.timestampNs) + // cater for frame rotation so that the video is always facing up as we rotate pas a certain point + val newFrame = VideoFrame(videoFrame.buffer, videoFrame.rotation - rotation, videoFrame.timestampNs) + // the frame we are sending to our contact needs to cater for rotation observer.onFrameCaptured(newFrame) - sink.get()?.onFrame(localFrame) + + // the frame we see on the user's phone doesn't require changes + sink.get()?.onFrame(videoFrame) } override fun setSink(sink: VideoSink?) { diff --git a/app/src/main/res/layout/activity_webrtc.xml b/app/src/main/res/layout/activity_webrtc.xml index ebef19ce5d..3aea4ffa11 100644 --- a/app/src/main/res/layout/activity_webrtc.xml +++ b/app/src/main/res/layout/activity_webrtc.xml @@ -8,7 +8,7 @@ xmlns:tools="http://schemas.android.com/tools"> - - @@ -126,22 +120,17 @@ app:layout_constraintWidth_percent="0.2" android:layout_height="0dp" android:layout_width="0dp" - android:visibility="invisible" - android:background="@color/black"> + android:background="?backgroundSecondary"> + android:layout_gravity="center" + app:tint="?android:textColorPrimary"/> -