move SurfaceViewRenderer into WebRTCVideoView

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2025-05-06 15:03:28 +02:00
parent aacc013485
commit 35c777e70d
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
6 changed files with 60 additions and 22 deletions

View File

@ -959,7 +959,8 @@ class CallActivity : CallBaseActivity() {
binding!!.composeParticipantGrid.setContent { binding!!.composeParticipantGrid.setContent {
MaterialTheme { MaterialTheme {
ParticipantGrid( ParticipantGrid(
participants = participantUiStates.toList() participants = participantUiStates.toList(),
eglBase = rootEglBase!!
) { ) {
animateCallControls(true, 0) animateCallControls(true, 0)
} }

View File

@ -148,7 +148,7 @@ class ParticipantDisplayItem(
isStreamEnabled = isStreamEnabled, isStreamEnabled = isStreamEnabled,
raisedHand = raisedHand?.state == true, raisedHand = raisedHand?.state == true,
avatarUrl = urlForAvatar, avatarUrl = urlForAvatar,
surfaceViewRenderer = surfaceViewRenderer mediaStream = mediaStream,
) )
} }

View File

@ -7,7 +7,7 @@
package com.nextcloud.talk.call package com.nextcloud.talk.call
import org.webrtc.SurfaceViewRenderer import org.webrtc.MediaStream
data class ParticipantUiState( data class ParticipantUiState(
val sessionKey: String, val sessionKey: String,
@ -17,5 +17,5 @@ data class ParticipantUiState(
val isStreamEnabled: Boolean, val isStreamEnabled: Boolean,
val raisedHand: Boolean, val raisedHand: Boolean,
val avatarUrl: String?, val avatarUrl: String?,
val surfaceViewRenderer: SurfaceViewRenderer? = null val mediaStream: MediaStream?
) )

View File

@ -27,9 +27,15 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.nextcloud.talk.call.ParticipantUiState import com.nextcloud.talk.call.ParticipantUiState
import org.webrtc.EglBase
@Composable @Composable
fun ParticipantGrid(modifier: Modifier = Modifier, participants: List<ParticipantUiState>, onClick: () -> Unit) { fun ParticipantGrid(
modifier: Modifier = Modifier,
eglBase: EglBase?,
participants: List<ParticipantUiState>,
onClick: () -> Unit
) {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
@ -44,7 +50,8 @@ fun ParticipantGrid(modifier: Modifier = Modifier, participants: List<Participan
participant = participants[0], participant = participants[0],
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.clickable { onClick() } .clickable { onClick() },
eglBase = eglBase
) )
} }
} }
@ -63,7 +70,8 @@ fun ParticipantGrid(modifier: Modifier = Modifier, participants: List<Participan
participant = it, participant = it,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.fillMaxWidth() .fillMaxWidth(),
eglBase = eglBase
) )
} }
} }
@ -80,7 +88,8 @@ fun ParticipantGrid(modifier: Modifier = Modifier, participants: List<Participan
participant = it, participant = it,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.fillMaxHeight() .fillMaxHeight(),
eglBase = eglBase
) )
} }
} }
@ -102,7 +111,8 @@ fun ParticipantGrid(modifier: Modifier = Modifier, participants: List<Participan
participant = participant, participant = participant,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1.5f) .aspectRatio(1.5f),
eglBase = eglBase
) )
} }
} }
@ -118,7 +128,8 @@ const val numberOfParticipants = 4
@Composable @Composable
fun ParticipantGridPreview() { fun ParticipantGridPreview() {
ParticipantGrid( ParticipantGrid(
participants = getTestParticipants(numberOfParticipants) participants = getTestParticipants(numberOfParticipants),
eglBase = null
) {} ) {}
} }
@ -130,7 +141,8 @@ fun ParticipantGridPreview() {
@Composable @Composable
fun ParticipantGridPreviewPortrait2() { fun ParticipantGridPreviewPortrait2() {
ParticipantGrid( ParticipantGrid(
participants = getTestParticipants(numberOfParticipants) participants = getTestParticipants(numberOfParticipants),
eglBase = null
) {} ) {}
} }
@ -142,7 +154,8 @@ fun ParticipantGridPreviewPortrait2() {
@Composable @Composable
fun ParticipantGridPreviewLandscape1() { fun ParticipantGridPreviewLandscape1() {
ParticipantGrid( ParticipantGrid(
participants = getTestParticipants(numberOfParticipants) participants = getTestParticipants(numberOfParticipants),
eglBase = null
) {} ) {}
} }
@ -157,7 +170,7 @@ fun getTestParticipants(numberOfParticipants: Int): List<ParticipantUiState> {
isStreamEnabled = true, isStreamEnabled = true,
raisedHand = true, raisedHand = true,
avatarUrl = "", avatarUrl = "",
surfaceViewRenderer = null mediaStream = null
) )
participantList.add(participant) participantList.add(participant)
} }

View File

@ -9,6 +9,7 @@ package com.nextcloud.talk.call.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -25,18 +26,25 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.call.ParticipantUiState import com.nextcloud.talk.call.ParticipantUiState
import org.webrtc.EglBase
import org.webrtc.SurfaceViewRenderer
@Composable @Composable
fun ParticipantTile(participant: ParticipantUiState, modifier: Modifier = Modifier) { fun ParticipantTile(
participant: ParticipantUiState,
eglBase: EglBase?,
modifier: Modifier = Modifier
) {
Box( Box(
modifier = modifier modifier = modifier
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.background(Color.DarkGray) .background(Color.DarkGray)
) { ) {
if (participant.isStreamEnabled && participant.surfaceViewRenderer != null) { if (participant.isStreamEnabled && participant.mediaStream != null) {
WebRTCVideoView(participant.surfaceViewRenderer) WebRTCVideoView(participant, eglBase)
} else { } else {
AvatarWithFallback(participant) AvatarWithFallback(participant)
} }
@ -94,11 +102,12 @@ fun ParticipantTilePreview() {
isStreamEnabled = true, isStreamEnabled = true,
raisedHand = true, raisedHand = true,
avatarUrl = "", avatarUrl = "",
surfaceViewRenderer = null mediaStream = null,
) )
ParticipantTile( ParticipantTile(
participant = participant, participant = participant,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth(),
eglBase = null
) )
} }

View File

@ -11,13 +11,28 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import com.nextcloud.talk.call.ParticipantUiState
import org.webrtc.EglBase
import org.webrtc.SurfaceViewRenderer import org.webrtc.SurfaceViewRenderer
@Composable @Composable
fun WebRTCVideoView(surfaceViewRenderer: SurfaceViewRenderer) { fun WebRTCVideoView(
participant: ParticipantUiState,
eglBase: EglBase?,
) {
AndroidView( AndroidView(
factory = { surfaceViewRenderer }, factory = { context ->
update = { /* No-op, renderer is already initialized and reused */ }, SurfaceViewRenderer(context).apply {
modifier = Modifier.fillMaxSize() init(eglBase?.eglBaseContext, null)
setEnableHardwareScaler(true)
setMirror(false)
participant.mediaStream?.videoTracks?.firstOrNull()?.addSink(this)
}
},
modifier = Modifier.fillMaxSize(),
onRelease = {
participant.mediaStream?.videoTracks?.firstOrNull()?.removeSink(it)
it.release()
}
) )
} }