mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-20 15:20:23 +01:00
Improve screen share capture dimension calculation and use remote config.
This commit is contained in:
committed by
Michelle Tang
parent
4dd57460de
commit
9dcf68581d
-1
@@ -13,5 +13,4 @@ sealed interface LabsSettingsEvents {
|
||||
data class ToggleBetterSearch(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleAutoLowerHand(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleStarredMessages(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleScreenShare(val enabled: Boolean) : LabsSettingsEvents
|
||||
}
|
||||
|
||||
-9
@@ -151,15 +151,6 @@ private fun LabsSettingsContent(
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleStarredMessages(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.screenShare,
|
||||
text = "Screen Sharing",
|
||||
label = "Share your screen during calls. Adds a screen share option to the overflow menu in 1:1 and group calls.",
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleScreenShare(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -15,6 +15,5 @@ data class LabsSettingsState(
|
||||
val groupSuggestionsForMembers: Boolean = false,
|
||||
val betterSearch: Boolean = false,
|
||||
val autoLowerHand: Boolean = false,
|
||||
val starredMessages: Boolean = false,
|
||||
val screenShare: Boolean = false
|
||||
val starredMessages: Boolean = false
|
||||
)
|
||||
|
||||
+1
-7
@@ -46,10 +46,6 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
SignalStore.labs.starredMessages = event.enabled
|
||||
_state.value = _state.value.copy(starredMessages = event.enabled)
|
||||
}
|
||||
is LabsSettingsEvents.ToggleScreenShare -> {
|
||||
SignalStore.labs.screenShare = event.enabled
|
||||
_state.value = _state.value.copy(screenShare = event.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +57,7 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
groupSuggestionsForMembers = SignalStore.labs.groupSuggestionsForMembers,
|
||||
betterSearch = SignalStore.labs.betterSearch,
|
||||
autoLowerHand = SignalStore.labs.autoLowerHand,
|
||||
|
||||
starredMessages = SignalStore.labs.starredMessages,
|
||||
screenShare = SignalStore.labs.screenShare
|
||||
starredMessages = SignalStore.labs.starredMessages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public final class WebRtcControls {
|
||||
|
||||
public boolean displayOverflow() {
|
||||
boolean connectedGroupCall = isGroupCall() && groupCallState == GroupCallState.CONNECTED && hasAtLeastOneRemote;
|
||||
boolean connected1to1Call = !isGroupCall() && callState == CallState.ONGOING && SignalStore.labs().getScreenShare();
|
||||
boolean connected1to1Call = !isGroupCall() && callState == CallState.ONGOING && RemoteConfig.screenSharing();
|
||||
return isAtLeastOutgoing() && (connectedGroupCall || connected1to1Call);
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ public final class WebRtcControls {
|
||||
return callState.isAtLeast(CallState.OUTGOING);
|
||||
}
|
||||
|
||||
private boolean isGroupCall() {
|
||||
public boolean isGroupCall() {
|
||||
return groupCallState != GroupCallState.NONE;
|
||||
}
|
||||
|
||||
|
||||
+42
-10
@@ -48,6 +48,7 @@ data class AdditionalActionsState(
|
||||
val isSelfHandRaised: Boolean = false,
|
||||
val isScreenSharing: Boolean = false,
|
||||
val displayScreenShareToggle: Boolean = false,
|
||||
val isGroupCall: Boolean = true,
|
||||
@Stable val listener: AdditionalActionsListener = AdditionalActionsListener.Empty
|
||||
)
|
||||
|
||||
@@ -85,15 +86,18 @@ private fun AdditionalActionsPopupContent(
|
||||
Column(
|
||||
verticalArrangement = spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.width(IntrinsicSize.Min)
|
||||
.width(IntrinsicSize.Max)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
CallReactionScrubber(
|
||||
reactions = state.reactions,
|
||||
listener = state.listener
|
||||
)
|
||||
if (state.isGroupCall) {
|
||||
CallReactionScrubber(
|
||||
reactions = state.reactions,
|
||||
listener = state.listener
|
||||
)
|
||||
}
|
||||
|
||||
CallScreenMenu(
|
||||
isGroupCall = state.isGroupCall,
|
||||
onRaiseHandClick = state.listener::onRaiseHandClick,
|
||||
isSelfHandRaised = state.isSelfHandRaised,
|
||||
isScreenSharing = state.isScreenSharing,
|
||||
@@ -141,6 +145,7 @@ private fun CallReactionScrubber(
|
||||
|
||||
@Composable
|
||||
private fun CallScreenMenu(
|
||||
isGroupCall: Boolean,
|
||||
isSelfHandRaised: Boolean,
|
||||
onRaiseHandClick: (Boolean) -> Unit,
|
||||
isScreenSharing: Boolean = false,
|
||||
@@ -152,11 +157,13 @@ private fun CallScreenMenu(
|
||||
.fillMaxWidth()
|
||||
.background(SignalTheme.colors.colorSurface2, RoundedCornerShape(18.dp))
|
||||
) {
|
||||
CallScreenMenuOption(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_raise_hand_24),
|
||||
title = if (isSelfHandRaised) stringResource(R.string.CallOverflowPopupWindow__lower_hand) else stringResource(R.string.CallOverflowPopupWindow__raise_hand),
|
||||
onClick = { onRaiseHandClick(!isSelfHandRaised) }
|
||||
)
|
||||
if (isGroupCall) {
|
||||
CallScreenMenuOption(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_raise_hand_24),
|
||||
title = if (isSelfHandRaised) stringResource(R.string.CallOverflowPopupWindow__lower_hand) else stringResource(R.string.CallOverflowPopupWindow__raise_hand),
|
||||
onClick = { onRaiseHandClick(!isSelfHandRaised) }
|
||||
)
|
||||
}
|
||||
|
||||
if (displayScreenShareToggle) {
|
||||
CallScreenMenuOption(
|
||||
@@ -242,3 +249,28 @@ private fun CallScreenAdditionalActionsPopupPreview() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@NightPreview
|
||||
@Composable
|
||||
private fun CallScreenAdditionalActionsScreenSharingPreview() {
|
||||
Previews.Preview {
|
||||
AdditionalActionsPopupContent(
|
||||
state = AdditionalActionsState(
|
||||
isGroupCall = false,
|
||||
displayScreenShareToggle = true,
|
||||
isShown = false,
|
||||
reactions = persistentListOf(
|
||||
"\u2764\ufe0f",
|
||||
"\ud83d\udc4d",
|
||||
"\ud83d\udc4e",
|
||||
"\ud83d\ude02",
|
||||
"\ud83d\ude2e",
|
||||
"\ud83d\ude22"
|
||||
),
|
||||
isSelfHandRaised = false,
|
||||
listener = AdditionalActionsListener.Empty,
|
||||
triggerAlignedPopupState = TriggerAlignedPopupState.rememberTriggerAlignedPopupState()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ data class CallControlsState(
|
||||
val displayGroupRingingToggle: Boolean = false,
|
||||
val isGroupRingingEnabled: Boolean = false,
|
||||
val isGroupRingingAllowed: Boolean = false,
|
||||
val isGroupCall: Boolean = false,
|
||||
val displayAdditionalActions: Boolean = false,
|
||||
val displayStartCallButton: Boolean = false,
|
||||
val startCallButtonText: Int = R.string.WebRtcCallView__start_call,
|
||||
@@ -252,6 +253,7 @@ data class CallControlsState(
|
||||
displayMicToggle = webRtcControls.displayMuteAudio(),
|
||||
isMicEnabled = callParticipantsState.localParticipant.isMicrophoneEnabled,
|
||||
displayGroupRingingToggle = webRtcControls.displayRingToggle(),
|
||||
isGroupCall = webRtcControls.isGroupCall,
|
||||
isGroupRingingEnabled = callParticipantsState.ringGroup,
|
||||
isGroupRingingAllowed = groupMemberCount <= RemoteConfig.maxGroupCallRingSize,
|
||||
displayAdditionalActions = webRtcControls.displayOverflow(),
|
||||
|
||||
@@ -77,11 +77,11 @@ import org.thoughtcrime.securesms.events.CallParticipantId
|
||||
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
|
||||
import org.thoughtcrime.securesms.events.GroupCallReactionEvent
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState
|
||||
import org.thoughtcrime.securesms.service.webrtc.PendingParticipantCollection
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import kotlin.math.max
|
||||
import kotlin.math.round
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -181,7 +181,8 @@ fun CallScreen(
|
||||
reactions = callScreenState.reactions,
|
||||
isSelfHandRaised = localParticipant.isHandRaised,
|
||||
isScreenSharing = callScreenState.isLocalScreenSharing,
|
||||
displayScreenShareToggle = callControlsState.displayEndCallButton && SignalStore.labs.screenShare,
|
||||
displayScreenShareToggle = callControlsState.displayEndCallButton && RemoteConfig.screenSharing,
|
||||
isGroupCall = callControlsState.isGroupCall,
|
||||
listener = additionalActionsListener,
|
||||
triggerAlignedPopupState = additionalActionsPopupState
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
const val BETTER_SEARCH: String = "labs.better_search"
|
||||
const val AUTO_LOWER_HAND: String = "labs.auto_lower_hand"
|
||||
const val STARRED_MESSAGES: String = "labs.starred_messages"
|
||||
const val SCREEN_SHARE: String = "labs.screen_share"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
@@ -32,8 +31,6 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
|
||||
var starredMessages by booleanValue(STARRED_MESSAGES, true).falseForExternalUsers()
|
||||
|
||||
var screenShare by booleanValue(SCREEN_SHARE, true).falseForExternalUsers()
|
||||
|
||||
private fun SignalStoreValueDelegate<Boolean>.falseForExternalUsers(): SignalStoreValueDelegate<Boolean> {
|
||||
return this.map { actualValue -> RemoteConfig.internalUser && actualValue }
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ class OutgoingVideoSourceRouter(
|
||||
|
||||
override fun setOrientation(orientation: Int?) {
|
||||
camera.setOrientation(orientation)
|
||||
screenShareCapturer?.onConfigurationChanged()
|
||||
}
|
||||
|
||||
val cameraState: CameraState
|
||||
|
||||
@@ -2,7 +2,12 @@ package org.thoughtcrime.securesms.ringrtc
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.media.projection.MediaProjection
|
||||
import android.os.Build
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.Display
|
||||
import android.view.WindowManager
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.thoughtcrime.securesms.components.webrtc.EglBaseWrapper
|
||||
@@ -22,6 +27,27 @@ class ScreenShareCapturer(
|
||||
private val sink: CapturerObserver
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val TAG = tag(ScreenShareCapturer::class.java)
|
||||
|
||||
private const val MAX_DIMENSION = 1280
|
||||
private const val FRAME_RATE = 15
|
||||
}
|
||||
|
||||
private val displayManager: DisplayManager by lazy {
|
||||
context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
}
|
||||
|
||||
private val displayListener = object : DisplayManager.DisplayListener {
|
||||
override fun onDisplayChanged(displayId: Int) {
|
||||
if (displayId == Display.DEFAULT_DISPLAY) {
|
||||
onDisplayChangedInternal()
|
||||
}
|
||||
}
|
||||
override fun onDisplayAdded(displayId: Int) = Unit
|
||||
override fun onDisplayRemoved(displayId: Int) = Unit
|
||||
}
|
||||
|
||||
private var screenCapturer: ScreenCapturerAndroid? = null
|
||||
private var surfaceHelper: SurfaceTextureHelper? = null
|
||||
private var captureWidth: Int = 0
|
||||
@@ -45,10 +71,15 @@ class ScreenShareCapturer(
|
||||
override fun onStop() {
|
||||
Log.i(TAG, "MediaProjection stopped")
|
||||
}
|
||||
|
||||
override fun onCapturedContentResize(width: Int, height: Int) {
|
||||
Log.i(TAG, "onCapturedContentResize($width, $height)")
|
||||
applyCaptureFormat(width, height)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val (width, height) = computeCaptureDimensions()
|
||||
val (width, height) = scaleForEncoder(readDisplayBounds())
|
||||
captureWidth = width
|
||||
captureHeight = height
|
||||
|
||||
@@ -57,27 +88,37 @@ class ScreenShareCapturer(
|
||||
surfaceHelper = SurfaceTextureHelper.create("WebRTC-ScreenShareHelper", base!!.getEglBaseContext())
|
||||
screenCapturer!!.initialize(surfaceHelper, context, sink)
|
||||
screenCapturer!!.startCapture(width, height, FRAME_RATE)
|
||||
|
||||
if (Build.VERSION.SDK_INT < 34) {
|
||||
displayManager.registerDisplayListener(displayListener, surfaceHelper!!.handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onConfigurationChanged() {
|
||||
private fun onDisplayChangedInternal() {
|
||||
if (!isCapturing) return
|
||||
applyCaptureFormat(readDisplayBounds())
|
||||
}
|
||||
|
||||
val (width, height) = computeCaptureDimensions()
|
||||
private fun applyCaptureFormat(rawDimensions: Pair<Int, Int>) {
|
||||
applyCaptureFormat(rawDimensions.first, rawDimensions.second)
|
||||
}
|
||||
|
||||
private fun applyCaptureFormat(rawWidth: Int, rawHeight: Int) {
|
||||
val (width, height) = scaleForEncoder(rawWidth to rawHeight)
|
||||
if (width == captureWidth && height == captureHeight) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "onConfigurationChanged(): capture dimensions " + width + "x" + height)
|
||||
Log.i(TAG, "applyCaptureFormat(): capture dimensions " + width + "x" + height)
|
||||
captureWidth = width
|
||||
captureHeight = height
|
||||
screenCapturer?.changeCaptureFormat(width, height, FRAME_RATE)
|
||||
}
|
||||
|
||||
private fun computeCaptureDimensions(): Pair<Int, Int> {
|
||||
val metrics = context.resources.displayMetrics
|
||||
var width = metrics.widthPixels
|
||||
var height = metrics.heightPixels
|
||||
private fun scaleForEncoder(raw: Pair<Int, Int>): Pair<Int, Int> {
|
||||
var width = raw.first
|
||||
var height = raw.second
|
||||
|
||||
val maxDimension = max(width, height)
|
||||
if (maxDimension > MAX_DIMENSION) {
|
||||
@@ -93,6 +134,21 @@ class ScreenShareCapturer(
|
||||
return width to height
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun readDisplayBounds(): Pair<Int, Int> {
|
||||
return if (Build.VERSION.SDK_INT >= 30) {
|
||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val bounds = windowManager.maximumWindowMetrics.bounds
|
||||
bounds.width() to bounds.height()
|
||||
} else {
|
||||
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
||||
val metrics = DisplayMetrics()
|
||||
display.getRealMetrics(metrics)
|
||||
metrics.widthPixels to metrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (!isCapturing) {
|
||||
return
|
||||
@@ -100,6 +156,10 @@ class ScreenShareCapturer(
|
||||
|
||||
Log.i(TAG, "stop()")
|
||||
|
||||
if (Build.VERSION.SDK_INT < 34) {
|
||||
displayManager.unregisterDisplayListener(displayListener)
|
||||
}
|
||||
|
||||
if (screenCapturer != null) {
|
||||
screenCapturer!!.stopCapture()
|
||||
screenCapturer!!.dispose()
|
||||
@@ -119,11 +179,4 @@ class ScreenShareCapturer(
|
||||
fun dispose() {
|
||||
stop()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = tag(ScreenShareCapturer::class.java)
|
||||
|
||||
private const val MAX_DIMENSION = 1280
|
||||
private const val FRAME_RATE = 15
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1376,5 +1376,16 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Whether screen sharing is available during calls.
|
||||
*/
|
||||
@JvmStatic
|
||||
@get:JvmName("screenSharing")
|
||||
val screenSharing: Boolean by remoteBoolean(
|
||||
key = "android.calling.screenSharing",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -2706,9 +2706,9 @@
|
||||
<!-- A negative button for a dialog confirming the user wants to lower their hand (withdraw a raised hand) -->
|
||||
<string name="CallOverflowPopupWindow__cancel">Cancel</string>
|
||||
<!-- A clickable button to share your screen in a call -->
|
||||
<string name="CallOverflowPopupWindow__share_screen" translatable="false">Share screen (Labs)</string>
|
||||
<string name="CallOverflowPopupWindow__share_screen">Share screen</string>
|
||||
<!-- A clickable button to stop sharing your screen in a call -->
|
||||
<string name="CallOverflowPopupWindow__stop_screen_share" translatable="false">Stop sharing (Labs)</string>
|
||||
<string name="CallOverflowPopupWindow__stop_screen_share">Stop sharing</string>
|
||||
<!-- A button to take you to a list of participants with raised hands -->
|
||||
<string name="CallOverflowPopupWindow__view">View</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user