Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SES-1652] Swap video views in calls #1533

Merged
merged 24 commits into from
Jul 10, 2024
Merged

[SES-1652] Swap video views in calls #1533

merged 24 commits into from
Jul 10, 2024

Conversation

ThomasSession
Copy link
Collaborator

@ThomasSession ThomasSession commented Jul 8, 2024

Fixing:
SES-1652
SES-560

Added the ability to swap videos during a call.
I changed Ryan's approach to only use two renderer instead of four for optimisation.

I found several issues with the video that I hopefully addressed in this PR:

  • Proper device rotation handling. The old code was relying on OrientationEventListener > onOrientationChanged, which gives us a rotation angle that doesn't take into account pitch vs roll, so tipping the device from front to back would trigger the video rotation logic, while we really only want it when the device is in portrait or landscape.
  • Floating video inset state when the camera is turned off
  • Fixed aspect ratio of video streams. The videos now scale appropriately based on the device's orientation, including the incoming video.
  • Animated control rotation. With the new device orientation detection, I added animated control rotation when the device changes its orientation
  • Fixed styling inconsistencies

Known issue:

  • There is a system limitation due to the webrtc library we are using: We can't round the corners of the floating video inset when the fullscreen video stream is also enabled. That is because we can't have two surfaceViews on at the same time while still doing clipping work on them. We also can't use a TextureView as we are limited by what WebRTC supports.

RyanRory and others added 21 commits February 16, 2023 16:03
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.
Rounded corners for floating inset
Proper handling of video scaling based on video proportions
Proper handling of mirroring logic for floating/fullscreen videos depending on whether they are the user or the remote video and whether the camera is front facing or not
…ream active, hiding it when both are inactive
@ThomasSession ThomasSession changed the title https:/oxen-io/session-android/pull/1127 [SES-1652] Swap video views in calls Jul 8, 2024
Copy link

@simophin simophin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bemusementpark bemusementpark self-requested a review July 8, 2024 04:54
Copy link

@bemusementpark bemusementpark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Maybe we should...

  • follow the users portrait-lock
  • fix that lateinit
  • refactor the sensorListener
  • Fix the rounded corners when you both have video on (might just be )

super.onPause()
try {
sensorManager.unregisterListener(this)
} catch (e: Exception) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a sensorManager::isInitialized property on lateinits so you don't need to catch the NPE... but it's probably a code smell to use it, or to catch this exception.

_videoState.value = _videoState.value.copy(swapped = videoSwapped)
handleMirroring()

if (!videoSwapped) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invert if?

@@ -721,7 +790,7 @@ class CallManager(
connection.setCommunicationMode()
setAudioEnabled(true)
dataChannel?.let { channel ->
val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
val toSend = if (!_videoState.value.userVideoEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val toSend = if (!_videoState.value.userVideoEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
val toSend = if (_videoState.value.userVideoEnabled) VIDEO_ENABLED_JSON else VIDEO_DISABLED_JSON

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d'you want to do this one? it's easier to read and reason about without the negation. It's easy to miss a !.

@ThomasSession
Copy link
Collaborator Author

  • follow the users portrait-lock

It should already follow the portrait lock from the isAutoRotateOn().
It will only check once but I don't think there is a way to know the device orientation otherwise.
We could of course add the config change handling form the manifest, and request to self handle orientation changes.
This would void the need for the sensor as you get that information for free, and which is what I initially started the code with.
But it also means we need a separate layout for landscape instead of forcing to portrait and rotating only certain elements like we are doing now.

@bemusementpark
Copy link

It should already follow the portrait lock from the isAutoRotateOn().

yeah, but we could just add the listener, and then check this in the listener as the dead simple solution, no? possible issue if you're landscape and then you set portrait-lock. Depends how pedantic we want to get. But you could foresee that a user would have portrait lock (on/off) and start a call, and realise they want it (off/on) change the system setting and think it doesn't work, but they just have to press back and enter the call again for it to take effect... :/

The new OrientationManager encapsulate the orientation logic and sends out a mutable state flow
Copy link

@bemusementpark bemusementpark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

Comment on lines 405 to 411
if(!state.userVideoEnabled && !state.remoteVideoEnabled){
binding.floatingRendererContainer.isVisible = false
binding.swapViewIcon.isVisible = false
} else {
binding.floatingRendererContainer.isVisible = true
binding.swapViewIcon.isVisible = true
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(!state.userVideoEnabled && !state.remoteVideoEnabled){
binding.floatingRendererContainer.isVisible = false
binding.swapViewIcon.isVisible = false
} else {
binding.floatingRendererContainer.isVisible = true
binding.swapViewIcon.isVisible = true
}
state.run { userVideoEnabled || remoteVideoEnabled }.let {
binding.floatingRendererContainer.isVisible = it
binding.swapViewIcon.isVisible = it
}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove this and just let the State be the source of truth?

@@ -721,7 +790,7 @@ class CallManager(
connection.setCommunicationMode()
setAudioEnabled(true)
dataChannel?.let { channel ->
val toSend = if (!_videoEvents.value.isEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON
val toSend = if (!_videoState.value.userVideoEnabled) VIDEO_DISABLED_JSON else VIDEO_ENABLED_JSON

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d'you want to do this one? it's easier to read and reason about without the negation. It's easy to miss a !.

@ThomasSession ThomasSession merged commit b510b06 into dev Jul 10, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants