diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a208db84..228672de9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -298,6 +298,7 @@ android:theme="@style/Theme.Session.DayNight.NoActionBar" /> 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 4a70e3310..b722d3612 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -8,7 +8,10 @@ import android.content.IntentFilter import android.media.AudioManager import android.os.Build import android.os.Bundle -import android.view.* +import android.view.MenuItem +import android.view.OrientationEventListener +import android.view.Surface +import android.view.WindowManager import androidx.activity.viewModels import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -34,9 +37,13 @@ import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.CallViewModel -import org.thoughtcrime.securesms.webrtc.CallViewModel.State.* -import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.* -import java.util.* +import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_CONNECTED +import org.thoughtcrime.securesms.webrtc.CallViewModel.State.CALL_INCOMING +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_RINGING +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.EARPIECE +import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.SPEAKER_PHONE @AndroidEntryPoint class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { @@ -63,6 +70,14 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } private var hangupReceiver: BroadcastReceiver? = null + private val rotationListener by lazy { + object : OrientationEventListener(this) { + override fun onOrientationChanged(orientation: Int) { + viewModel.setDeviceRotation(orientation) + } + } + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { finish() @@ -81,6 +96,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) + rotationListener.enable() binding = ActivityWebrtcBinding.inflate(layoutInflater) setContentView(binding.root) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { @@ -166,6 +182,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { hangupReceiver?.let { receiver -> LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } + rotationListener.disable() } private fun answerCall() { @@ -174,7 +191,7 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } private fun updateControls(state: CallViewModel.State? = null) { - with (binding) { + with(binding) { if (state == null) { if (wantsToAnswer) { controlGroup.isVisible = true @@ -309,13 +326,6 @@ class WebRtcCallActivity : PassphraseRequiredActionBarActivity() { } } - launch { - viewModel.cameraRotations.collect { (localRotation, remoteRotation) -> - val screenRotation = getDeviceRotation() - Log.d("Loki", "local rotation: $localRotation, remote rotation: $remoteRotation, screen rotation: $screenRotation") - } - } - launch { viewModel.remoteVideoEnabledState.collect { isEnabled -> binding.remoteRenderer.removeAllViews() 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 4afef2e93..8fa50111c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.webrtc.video.CameraEventListener import org.thoughtcrime.securesms.webrtc.video.CameraState import org.webrtc.* import org.webrtc.PeerConnection.IceConnectionState +import org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FILL import org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FIT import java.nio.ByteBuffer import java.util.* @@ -100,9 +101,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va val recipientEvents = _recipientEvents.asSharedFlow() private var localCameraState: CameraState = CameraState.UNKNOWN - private val _cameraRotations = MutableStateFlow(0 to 0) - val cameraRotations = _cameraRotations.asSharedFlow() - private val _audioDeviceEvents = MutableStateFlow(AudioDeviceUpdate(AudioDevice.NONE, setOf())) val audioDeviceEvents = _audioDeviceEvents.asSharedFlow() @@ -182,35 +180,25 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va Util.runOnMainSync { val base = EglBase.create() eglBase = base - _cameraRotations.value = 0 to 0 localRenderer = SurfaceViewRenderer(context).apply { setEnableHardwareScaler(true) } remoteRenderer = SurfaceViewRenderer(context).apply { setEnableHardwareScaler(true) - setScalingType(SCALE_ASPECT_FIT, SCALE_ASPECT_FIT) } - localRenderer?.init(base.eglBaseContext, object : RendererCommon.RendererEvents { - override fun onFirstFrameRendered() { - localCameraState - } - - override fun onFrameResolutionChanged(p0: Int, p1: Int, rotation: Int) { - _cameraRotations.value = _cameraRotations.value.copy(first = rotation) - } - }) + localRenderer?.init(base.eglBaseContext, null) localRenderer?.setMirror(localCameraState.activeDirection == CameraState.Direction.FRONT) - remoteRenderer?.init(base.eglBaseContext, object : RendererCommon.RendererEvents { + remoteRenderer?.init(base.eglBaseContext, object: RendererCommon.RendererEvents { override fun onFirstFrameRendered() { - localCameraState } - override fun onFrameResolutionChanged(p0: Int, p1: Int, rotation: Int) { - _cameraRotations.value = _cameraRotations.value.copy(second = rotation) + override fun onFrameResolutionChanged(p0: Int, p1: Int, p2: Int) { + Log.d("Loki", "remote rotation: $p2") } }) + remoteRenderer?.setScalingType(SCALE_ASPECT_FIT) val encoderFactory = DefaultVideoEncoderFactory(base.eglBaseContext, true, true) val decoderFactory = DefaultVideoDecoderFactory(base.eglBaseContext) @@ -591,6 +579,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va } } + fun setDeviceRotation(newRotation: Int) { + peerConnection?.setDeviceRotation(newRotation) + } + fun handleWiredHeadsetChanged(present: Boolean) { if (currentConnectionState in arrayOf(CallState.STATE_CONNECTED, CallState.STATE_DIALING, 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 a13892f20..0df75cadd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -66,8 +66,9 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V val remoteVideoEnabledState get() = callManager.remoteVideoEvents.map { it.isEnabled } - val cameraRotations - get() = callManager.cameraRotations + fun setDeviceRotation(newRotation: Int) { + callManager.setDeviceRotation(newRotation) + } val currentCallState get() = callManager.currentCallState 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 6e332db53..5f54d6064 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/PeerConnectionWrapper.kt @@ -5,6 +5,7 @@ import org.session.libsignal.utilities.SettableFuture import org.thoughtcrime.securesms.webrtc.video.Camera import org.thoughtcrime.securesms.webrtc.video.CameraEventListener import org.thoughtcrime.securesms.webrtc.video.CameraState +import org.thoughtcrime.securesms.webrtc.video.RotationVideoSink import org.webrtc.* import java.security.SecureRandom import java.util.concurrent.ExecutionException @@ -24,6 +25,7 @@ class PeerConnectionWrapper(context: Context, private val camera: Camera private val videoSource: VideoSource? private val videoTrack: VideoTrack? + private val rotationVideoSink = RotationVideoSink() val readyForIce get() = peerConnection.localDescription != null && peerConnection.remoteDescription != null @@ -66,13 +68,12 @@ class PeerConnectionWrapper(context: Context, if (camera.capturer != null) { videoSource = factory.createVideoSource(false) videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource) - + rotationVideoSink.setObserver(videoSource.capturerObserver) camera.capturer.initialize( SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.eglBaseContext), context, - videoSource.capturerObserver + rotationVideoSink ) - videoTrack.addSink(localRenderer) videoTrack.setEnabled(false) mediaStream.addTrack(videoTrack) @@ -276,6 +277,10 @@ class PeerConnectionWrapper(context: Context, audioTrack.setEnabled(isEnabled) } + fun setDeviceRotation(rotation: Int) { + rotationVideoSink.rotation = rotation + } + fun setVideoEnabled(isEnabled: Boolean) { videoTrack?.let { track -> track.setEnabled(isEnabled) 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 new file mode 100644 index 000000000..e79c3dda6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/video/RotationVideoSink.kt @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.webrtc.video + +import org.session.libsignal.utilities.Log +import org.webrtc.CapturerObserver +import org.webrtc.VideoFrame +import java.lang.ref.SoftReference +import java.util.concurrent.atomic.AtomicBoolean + +class RotationVideoSink: CapturerObserver { + + var rotation: Int = 0 + + private val capturing = AtomicBoolean(false) + private var capturerObserver = SoftReference(null) + + override fun onCapturerStarted(ignored: Boolean) { + capturing.set(true) + } + + override fun onCapturerStopped() { + capturing.set(false) + } + + override fun onFrameCaptured(videoFrame: VideoFrame?) { + // rotate if need + val observer = capturerObserver.get() + if (videoFrame == null || observer == null || !capturing.get()) return + + val quadrantRotation = when (rotation % 360) { + in 0 until 90 -> 90 + in 90 until 180 -> 180 + in 180 until 270 -> 270 + else -> 0 + } + + val newFrame = VideoFrame(videoFrame.buffer, quadrantRotation, videoFrame.timestampNs) + observer.onFrameCaptured(newFrame) + } + + fun setObserver(videoSink: CapturerObserver?) { + capturerObserver = SoftReference(videoSink) + } +} \ No newline at end of file