improve/fix contents for picture in picture mode

depending on amount of participants, voiceOnly call and enabled/disabled own video, the contents of PIP windows are updated.

This will be further improved when speaker-view is implemented.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2025-05-13 11:27:55 +02:00
parent eaed93087b
commit 6a048fde08
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
4 changed files with 70 additions and 58 deletions

View File

@ -938,35 +938,17 @@ class CallActivity : CallBaseActivity() {
binding!!.pipSelfVideoRenderer.release()
}
private fun initSelfVideoViewForPipMode() {
if (!isVoiceOnlyCall) {
binding!!.pipSelfVideoRenderer.visibility = View.VISIBLE
try {
binding!!.pipSelfVideoRenderer.init(rootEglBase!!.eglBaseContext, null)
} catch (e: IllegalStateException) {
Log.d(TAG, "pipGroupVideoRenderer already initialized", e)
}
binding!!.pipSelfVideoRenderer.setZOrderMediaOverlay(true)
// disabled because it causes some devices to crash
binding!!.pipSelfVideoRenderer.setEnableHardwareScaler(false)
binding!!.pipSelfVideoRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
localVideoTrack?.addSink(binding?.pipSelfVideoRenderer)
} else {
binding!!.pipSelfVideoRenderer.visibility = View.GONE
}
}
private fun initGrid() {
Log.d(TAG, "initGrid")
binding!!.composeParticipantGrid.visibility = View.VISIBLE
binding!!.composeParticipantGrid.setContent {
MaterialTheme {
val participantUiStates = participantItems.map { it.uiStateFlow.collectAsState().value }
ParticipantGrid(
participantUiStates = participantUiStates,
eglBase = rootEglBase!!,
isVoiceOnlyCall = isVoiceOnlyCall
isVoiceOnlyCall = isVoiceOnlyCall,
isInPipMode = isInPipMode
) {
animateCallControls(true, 0)
}
@ -3206,33 +3188,38 @@ class CallActivity : CallBaseActivity() {
override fun updateUiForPipMode() {
Log.d(TAG, "updateUiForPipMode")
val params = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.setMargins(0, 0, 0, 0)
binding!!.composeParticipantGrid.layoutParams = params
binding!!.callControls.visibility = View.GONE
binding!!.callInfosLinearLayout.visibility = View.GONE
binding!!.selfVideoViewWrapper.visibility = View.GONE
binding!!.callStates.callStateRelativeLayout.visibility = View.GONE
binding!!.pipCallConversationNameTextView.text = conversationName
if (isVoiceOnlyCall) {
if (participantItems.size > 1) {
binding!!.pipOverlay.visibility = View.VISIBLE
binding!!.pipSelfVideoRenderer.visibility = View.GONE
} else {
binding!!.pipOverlay.visibility = View.GONE
}
} else {
binding!!.selfVideoRenderer.clearImage()
binding!!.selfVideoRenderer.release()
if (participantItems.size > 1) {
binding!!.pipOverlay.visibility = View.VISIBLE
initSelfVideoViewForPipMode()
} else {
if (participantItems.size == 1) {
binding!!.pipOverlay.visibility = View.GONE
} else {
binding!!.composeParticipantGrid.visibility = View.GONE
if (localVideoTrack?.enabled() == true) {
binding!!.pipOverlay.visibility = View.VISIBLE
binding!!.pipSelfVideoRenderer.visibility = View.VISIBLE
try {
binding!!.pipSelfVideoRenderer.init(rootEglBase!!.eglBaseContext, null)
} catch (e: IllegalStateException) {
Log.d(TAG, "pipGroupVideoRenderer already initialized", e)
}
binding!!.pipSelfVideoRenderer.setZOrderMediaOverlay(true)
// disabled because it causes some devices to crash
binding!!.pipSelfVideoRenderer.setEnableHardwareScaler(false)
binding!!.pipSelfVideoRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
localVideoTrack?.addSink(binding?.pipSelfVideoRenderer)
} else {
binding!!.pipOverlay.visibility = View.VISIBLE
binding!!.pipSelfVideoRenderer.visibility = View.GONE
}
}
}
@ -3240,6 +3227,7 @@ class CallActivity : CallBaseActivity() {
override fun updateUiForNormalMode() {
Log.d(TAG, "updateUiForNormalMode")
binding!!.pipOverlay.visibility = View.GONE
binding!!.composeParticipantGrid.visibility = View.VISIBLE
if (isVoiceOnlyCall) {
binding!!.callControls.visibility = View.VISIBLE

View File

@ -10,7 +10,6 @@ package com.nextcloud.talk.call.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -19,7 +18,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.nextcloud.talk.adapters.ParticipantUiState
@ -34,7 +32,6 @@ fun AvatarWithFallback(participant: ParticipantUiState, modifier: Modifier = Mod
Box(
modifier = modifier
.size(150.dp)
.clip(CircleShape),
contentAlignment = Alignment.Center
) {

View File

@ -29,12 +29,14 @@ import com.nextcloud.talk.adapters.ParticipantUiState
import org.webrtc.EglBase
import kotlin.math.ceil
@Suppress("LongParameterList")
@Composable
fun ParticipantGrid(
modifier: Modifier = Modifier,
eglBase: EglBase?,
participantUiStates: List<ParticipantUiState>,
isVoiceOnlyCall: Boolean,
isInPipMode: Boolean,
onClick: () -> Unit
) {
val configuration = LocalConfiguration.current
@ -58,7 +60,7 @@ fun ParticipantGrid(
val rows = ceil(participantUiStates.size / columns.toFloat()).toInt()
val heightForNonGridComponents = if (isVoiceOnlyCall) {
val heightForNonGridComponents = if (isVoiceOnlyCall && !isInPipMode) {
// this is a workaround for now. It should ~summarize the height of callInfosLinearLayout and callControls
// Once everything is migrated to jetpack, this workaround should be obsolete or solved in a better way
240.dp
@ -97,6 +99,7 @@ fun ParticipantGrid(
.height(itemHeight)
.fillMaxWidth(),
eglBase = eglBase,
isInPipMode = isInPipMode,
isVoiceOnlyCall = isVoiceOnlyCall
)
}
@ -109,7 +112,8 @@ fun ParticipantGridPreview() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -119,7 +123,8 @@ fun TwoParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -129,7 +134,8 @@ fun ThreeParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -139,7 +145,8 @@ fun FourParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -149,7 +156,8 @@ fun FiveParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(5),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -159,7 +167,8 @@ fun SevenParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -169,7 +178,8 @@ fun FiftyParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -183,7 +193,8 @@ fun OneParticipantLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -197,7 +208,8 @@ fun TwoParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -211,7 +223,8 @@ fun ThreeParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -225,7 +238,8 @@ fun FourParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -239,7 +253,8 @@ fun SevenParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -253,7 +268,8 @@ fun FiftyParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}

View File

@ -36,10 +36,12 @@ import org.webrtc.EglBase
const val NICK_OFFSET = 4f
const val NICK_BLUR_RADIUS = 4f
@Suppress("Detekt.LongMethod")
@Composable
fun ParticipantTile(
participantUiState: ParticipantUiState,
eglBase: EglBase?,
isInPipMode: Boolean,
modifier: Modifier = Modifier,
isVoiceOnlyCall: Boolean
) {
@ -53,9 +55,17 @@ fun ParticipantTile(
if (!isVoiceOnlyCall && participantUiState.isStreamEnabled && participantUiState.mediaStream != null) {
WebRTCVideoView(participantUiState, eglBase)
} else {
val avatarSize = if (isInPipMode) {
100.dp
} else {
150.dp
}
AvatarWithFallback(
participant = participantUiState,
modifier = Modifier.align(Alignment.Center)
modifier = Modifier
.align(Alignment.Center)
.size(avatarSize)
)
}
@ -125,6 +135,7 @@ fun ParticipantTilePreview() {
.fillMaxWidth()
.height(300.dp),
eglBase = null,
isInPipMode = false,
isVoiceOnlyCall = false
)
}