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

feat: conference simulcast support (WPB-11480) #3744

Merged
merged 5 commits into from
Dec 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -177,6 +177,8 @@ fun OngoingCallScreen(
clearVideoPreview = sharedCallingViewModel::clearVideoPreview,
onCollapse = onCollapse,
requestVideoStreams = ongoingCallViewModel::requestVideoStreams,
onSelectedParticipant = ongoingCallViewModel::onSelectedParticipant,
selectedParticipantForFullScreen = ongoingCallViewModel.selectedParticipant,
hideDoubleTapToast = ongoingCallViewModel::hideDoubleTapToast,
onCameraPermissionPermanentlyDenied = onCameraPermissionPermanentlyDenied,
participants = sharedCallingViewModel.participantsState,
@@ -289,6 +291,8 @@ private fun OngoingCallContent(
hideDoubleTapToast: () -> Unit,
onCameraPermissionPermanentlyDenied: () -> Unit,
requestVideoStreams: (participants: List<UICallParticipant>) -> Unit,
onSelectedParticipant: (selectedParticipant: SelectedParticipant) -> Unit,
selectedParticipantForFullScreen: SelectedParticipant,
participants: PersistentList<UICallParticipant>,
inPictureInPictureMode: Boolean,
currentUserId: UserId,
@@ -303,7 +307,6 @@ private fun OngoingCallContent(
)

var shouldOpenFullScreen by remember { mutableStateOf(false) }
var selectedParticipantForFullScreen by remember { mutableStateOf(SelectedParticipant()) }

WireBottomSheetScaffold(
sheetDragHandle = null,
@@ -391,11 +394,14 @@ private fun OngoingCallContent(
selectedParticipant = selectedParticipantForFullScreen,
height = this@BoxWithConstraints.maxHeight - dimensions().spacing4x,
closeFullScreen = {
onSelectedParticipant(SelectedParticipant())
shouldOpenFullScreen = !shouldOpenFullScreen
},
onBackButtonClicked = {
onSelectedParticipant(SelectedParticipant())
shouldOpenFullScreen = !shouldOpenFullScreen
},
requestVideoStreams = requestVideoStreams,
setVideoPreview = setVideoPreview,
clearVideoPreview = clearVideoPreview,
participants = participants
@@ -412,7 +418,7 @@ private fun OngoingCallContent(
requestVideoStreams = requestVideoStreams,
currentUserId = currentUserId,
onDoubleTap = { selectedParticipant ->
selectedParticipantForFullScreen = selectedParticipant
onSelectedParticipant(selectedParticipant)
shouldOpenFullScreen = !shouldOpenFullScreen
},
)
@@ -580,6 +586,8 @@ fun PreviewOngoingCallContent(participants: PersistentList<UICallParticipant>) {
participants = participants,
inPictureInPictureMode = false,
currentUserId = UserId("userId", "domain"),
onSelectedParticipant = {},
selectedParticipantForFullScreen = SelectedParticipant(),
)
}

Original file line number Diff line number Diff line change
@@ -28,8 +28,10 @@ import com.wire.android.appLogger
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.di.CurrentAccount
import com.wire.android.ui.calling.model.UICallParticipant
import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant
import com.wire.kalium.logic.data.call.Call
import com.wire.kalium.logic.data.call.CallClient
import com.wire.kalium.logic.data.call.CallQuality
import com.wire.kalium.logic.data.call.VideoState
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
@@ -63,6 +65,8 @@ class OngoingCallViewModel @AssistedInject constructor(

var state by mutableStateOf(OngoingCallState())
private set
var selectedParticipant by mutableStateOf(SelectedParticipant())
private set

init {
viewModelScope.launch {
@@ -124,20 +128,32 @@ class OngoingCallViewModel @AssistedInject constructor(
.also {
if (it.isNotEmpty()) {
val clients: List<CallClient> = it.map { uiParticipant ->
CallClient(uiParticipant.id.toString(), uiParticipant.clientId)
CallClient(
userId = uiParticipant.id.toString(),
clientId = uiParticipant.clientId,
quality = mapQualityStream(uiParticipant)
)
}
requestVideoStreams(conversationId, clients)
}
}
}
}

private fun mapQualityStream(uiParticipant: UICallParticipant): CallQuality {
return if (uiParticipant.clientId == selectedParticipant.clientId) {
CallQuality.HIGH
} else {
CallQuality.LOW
}
}

private fun startDoubleTapToastDisplayCountDown() {
doubleTapIndicatorCountDownTimer?.cancel()
doubleTapIndicatorCountDownTimer =
object : CountDownTimer(DOUBLE_TAP_TOAST_DISPLAY_TIME, COUNT_DOWN_INTERVAL) {
override fun onTick(p0: Long) {
appLogger.i("startDoubleTapToastDisplayCountDown: $p0")
appLogger.d("$TAG - startDoubleTapToastDisplayCountDown: $p0")
}

override fun onFinish() {
@@ -171,10 +187,16 @@ class OngoingCallViewModel @AssistedInject constructor(
}
}

fun onSelectedParticipant(selectedParticipant: SelectedParticipant) {
appLogger.d("$TAG - Selected participant: ${selectedParticipant.toLogString()}")
this.selectedParticipant = selectedParticipant
}

companion object {
const val DOUBLE_TAP_TOAST_DISPLAY_TIME = 7000L
const val COUNT_DOWN_INTERVAL = 1000L
const val DELAY_TO_SHOW_DOUBLE_TAP_TOAST = 500L
const val TAG = "OngoingCallViewModel"
}

@AssistedFactory
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ fun FullScreenTile(
closeFullScreen: (offset: Offset) -> Unit,
onBackButtonClicked: () -> Unit,
setVideoPreview: (View) -> Unit,
requestVideoStreams: (participants: List<UICallParticipant>) -> Unit,
clearVideoPreview: () -> Unit,
modifier: Modifier = Modifier,
contentPadding: Dp = dimensions().spacing4x,
@@ -119,6 +120,10 @@ fun FullScreenTile(
}
)
}

LaunchedEffect(selectedParticipant.userId) {
requestVideoStreams(listOf(it))
}
}
}

@@ -139,6 +144,7 @@ fun PreviewFullScreenTile() = WireTheme {
closeFullScreen = {},
onBackButtonClicked = {},
setVideoPreview = {},
requestVideoStreams = {},
clearVideoPreview = {},
participants = participants,
)
Original file line number Diff line number Diff line change
@@ -17,10 +17,16 @@
*/
package com.wire.android.ui.calling.ongoing.fullscreen

import com.wire.kalium.logger.obfuscateId
import com.wire.kalium.logic.data.user.UserId

data class SelectedParticipant(
val userId: UserId = UserId("", ""),
val clientId: String = "",
val isSelfUser: Boolean = false
)
) {

fun toLogString(): String {
return "SelectedParticipant(userId=${userId.toLogString()}, clientId=${clientId.obfuscateId()}, isSelfUser=$isSelfUser)"
}
}
Original file line number Diff line number Diff line change
@@ -23,9 +23,11 @@ import com.wire.android.config.NavigationTestExtension
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.ui.calling.model.UICallParticipant
import com.wire.android.ui.calling.ongoing.OngoingCallViewModel
import com.wire.android.ui.calling.ongoing.fullscreen.SelectedParticipant
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.kalium.logic.data.call.Call
import com.wire.kalium.logic.data.call.CallClient
import com.wire.kalium.logic.data.call.CallQuality
import com.wire.kalium.logic.data.call.CallStatus
import com.wire.kalium.logic.data.call.VideoState
import com.wire.kalium.logic.data.conversation.Conversation
@@ -170,6 +172,72 @@ class OngoingCallViewModelTest {
}
}

@Test
fun givenAUserIsSelected_whenRequestedFullScreen_thenSetTheUserAsSelected() =
runTest {
val (_, ongoingCallViewModel) = Arrangement()
.withCall(provideCall().copy(isCameraOn = true))
.withShouldShowDoubleTapToastReturning(false)
.withSetVideoSendState()
.arrange()

ongoingCallViewModel.onSelectedParticipant(selectedParticipant3)

assertEquals(selectedParticipant3, ongoingCallViewModel.selectedParticipant)
}

@Test
fun givenParticipantsList_WhenRequestingVideoStreamForFullScreenParticipant_ThenRequestItInHighQuality() =
runTest {
val expectedClients = listOf(
CallClient(participant1.id.toString(), participant1.clientId, false, CallQuality.LOW),
CallClient(participant3.id.toString(), participant3.clientId, false, CallQuality.HIGH)
)

val (arrangement, ongoingCallViewModel) = Arrangement()
.withCall(provideCall())
.withShouldShowDoubleTapToastReturning(false)
.withSetVideoSendState()
.withRequestVideoStreams(conversationId, expectedClients)
.arrange()

ongoingCallViewModel.onSelectedParticipant(selectedParticipant3)
ongoingCallViewModel.requestVideoStreams(participants)

coVerify(exactly = 1) {
arrangement.requestVideoStreams(
conversationId,
expectedClients
)
}
}

@Test
fun givenParticipantsList_WhenRequestingVideoStreamForAllParticipant_ThenRequestItInLowQuality() =
runTest {
val expectedClients = listOf(
CallClient(participant1.id.toString(), participant1.clientId, false, CallQuality.LOW),
CallClient(participant3.id.toString(), participant3.clientId, false, CallQuality.LOW)
)

val (arrangement, ongoingCallViewModel) = Arrangement()
.withCall(provideCall())
.withShouldShowDoubleTapToastReturning(false)
.withSetVideoSendState()
.withRequestVideoStreams(conversationId, expectedClients)
.arrange()

ongoingCallViewModel.onSelectedParticipant(SelectedParticipant())
ongoingCallViewModel.requestVideoStreams(participants)

coVerify(exactly = 1) {
arrangement.requestVideoStreams(
conversationId,
expectedClients
)
}
}

private class Arrangement {

@MockK
@@ -268,6 +336,7 @@ class OngoingCallViewModelTest {
accentId = -1
)
val participants = listOf(participant1, participant2, participant3)
val selectedParticipant3 = SelectedParticipant(participant3.id, participant3.clientId, false)
}

private fun provideCall(
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 20 files
+38 −4 data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallClient.kt
+46 −0 data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/RecentlyEndedCallMetadata.kt
+10 −10 gradle/libs.versions.toml
+6 −1 logic/src/androidInstrumentedTest/kotlin/com/wire/kalium/logic/feature/call/CallManagerTest.kt
+3 −1 logic/src/appleMain/kotlin/com/wire/kalium/logic/feature/call/GlobalCallManager.kt
+12 −4 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt
+5 −2 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/GlobalCallManager.kt
+9 −2 logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/scenario/OnCloseCall.kt
+34 −17 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt
+11 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+7 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallsScope.kt
+3 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/GlobalCallManager.kt
+99 −0 ...nMain/kotlin/com/wire/kalium/logic/feature/call/usecase/CreateAndPersistRecentlyEndedCallMetadataUseCase.kt
+39 −0 ...src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveRecentlyEndedCallMetadataUseCase.kt
+1 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/search/SearchScope.kt
+16 −6 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/search/SearchUsersUseCase.kt
+281 −0 ...t/kotlin/com/wire/kalium/logic/feature/call/usecase/CreateAndPersistRecentlyEndedCallMetadataUseCaseTest.kt
+1 −1 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/search/SearchUseCaseTest.kt
+27 −2 logic/src/jvmTest/kotlin/com/wire/kalium/logic/feature/call/scenario/OnCloseCallTest.kt
+32 −0 util/src/commonMain/kotlin/com.wire.kalium.util/serialization/LenientJsonSerializer.kt