This commit is contained in:
Pavel Prystinka 2024-11-20 00:17:25 -08:00
Родитель c91d18afac
Коммит 7c59a60ddb
10 изменённых файлов: 141 добавлений и 215 удалений

Просмотреть файл

@ -49,6 +49,7 @@ import com.azure.android.communication.ui.calling.redux.reducer.CallDiagnosticsR
import com.azure.android.communication.ui.calling.redux.reducer.CallScreenInformationHeaderReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.CallStateReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.CaptionsReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.DeviceConfigurationReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.ErrorReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.LifecycleReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.LocalParticipantStateReducerImpl
@ -58,7 +59,6 @@ import com.azure.android.communication.ui.calling.redux.reducer.PermissionStateR
import com.azure.android.communication.ui.calling.redux.reducer.PipReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.Reducer
import com.azure.android.communication.ui.calling.redux.reducer.RttReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.DeviceConfigurationReducerImpl
import com.azure.android.communication.ui.calling.redux.reducer.ToastNotificationReducerImpl
import com.azure.android.communication.ui.calling.redux.state.AppReduxState
import com.azure.android.communication.ui.calling.redux.state.ReduxState
@ -92,7 +92,11 @@ internal class DependencyInjectionContainerImpl(
}
override val captionsDataManager by lazy {
CaptionsDataManager(callingService, appStore)
CaptionsDataManager(
callingService,
appStore,
avatarViewManager,
)
}
override val navigationRouter by lazy {

Просмотреть файл

@ -77,6 +77,7 @@ internal class CallCompositeActivityViewModel(
debugInfoManager = container.debugInfoManager,
capabilitiesManager = container.capabilitiesManager,
updatableOptionsManager = container.updatableOptionsManager,
captionsDataManager = container.captionsDataManager,
showSupportFormOption = container.configuration.callCompositeEventsHandler.getOnUserReportedHandlers().any(),
enableMultitasking = container.configuration.enableMultitasking,
isTelecomManagerEnabled = container.configuration.telecomManagerOptions != null,

Просмотреть файл

@ -212,14 +212,10 @@ internal class CallingViewModel(
captionsLanguageSelectionListViewModel.init(state.captionsState, state.visibilityState)
isCaptionsVisibleMutableFlow.value = shouldShowCaptionsUI(state.visibilityState, state.captionsState, state.rttState)
captionsLayoutViewModel.init(
coroutineScope,
state.captionsState,
isCaptionsVisibleMutableFlow.value,
captionsDataManager,
localUserIdentifier,
avatarViewManager,
state.deviceConfigurationState,
)
)
moreCallOptionsListViewModel.init(state.visibilityState, state.buttonState)
super.init(coroutineScope)

Просмотреть файл

@ -1,14 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.calling.presentation.fragment.calling.captions
import android.graphics.Bitmap
internal data class CaptionsRttEntryModel(
val displayName: String,
val displayText: String,
val avatarBitmap: Bitmap?,
val speakerRawId: String,
val languageCode: String?,
)

Просмотреть файл

@ -4,8 +4,6 @@
package com.azure.android.communication.ui.calling.presentation.fragment.calling.captions
import android.graphics.Bitmap
import com.azure.android.communication.common.CommunicationIdentifier
import com.azure.android.communication.ui.calling.presentation.manager.AvatarViewManager
import java.util.Date
internal enum class CaptionsRttType {
@ -13,7 +11,8 @@ internal enum class CaptionsRttType {
RTT,
}
internal data class CaptionsRecord(
internal data class CaptionsRttRecord(
val avatarBitmap: Bitmap?,
val displayName: String,
val displayText: String,
val speakerRawId: String,
@ -22,26 +21,3 @@ internal data class CaptionsRecord(
val timestamp: Date,
val type: CaptionsRttType,
)
internal fun CaptionsRecord.into(avatarViewManager: AvatarViewManager, identifier: CommunicationIdentifier?): CaptionsRttEntryModel {
var speakerName = this.displayName
var bitMap: Bitmap? = null
val remoteParticipantViewData = avatarViewManager.getRemoteParticipantViewData(this.speakerRawId)
if (remoteParticipantViewData != null) {
speakerName = remoteParticipantViewData.displayName
bitMap = remoteParticipantViewData.avatarBitmap
}
val localParticipantViewData = avatarViewManager.callCompositeLocalOptions?.participantViewData
if (localParticipantViewData != null && identifier?.rawId == this.speakerRawId) {
speakerName = localParticipantViewData.displayName
bitMap = localParticipantViewData.avatarBitmap
}
return CaptionsRttEntryModel(
displayName = speakerName,
displayText = this.displayText,
avatarBitmap = bitMap,
speakerRawId = this.speakerRawId,
languageCode = this.languageCode
)
}

Просмотреть файл

@ -12,7 +12,7 @@ import com.azure.android.communication.ui.calling.implementation.R
import com.microsoft.fluentui.persona.AvatarView
internal class CaptionsRecyclerViewAdapter(
private val captionsData: List<CaptionsRttEntryModel>
private val captionsData: List<CaptionsRttRecord>
) : RecyclerView.Adapter<CaptionsRecyclerViewAdapter.CaptionsViewHolder>() {
class CaptionsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val messageTextView: TextView =

Просмотреть файл

@ -24,11 +24,10 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.azure.android.communication.ui.calling.implementation.R
import com.azure.android.communication.ui.calling.presentation.fragment.calling.CallingFragment
import com.azure.android.communication.ui.calling.utilities.LocaleHelper
import com.azure.android.communication.ui.calling.utilities.isTablet
import com.azure.android.communication.ui.calling.utilities.launchAll
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@ -48,7 +47,7 @@ internal class CaptionsView : FrameLayout {
private lateinit var captionsStartProgressLayout: LinearLayout
private lateinit var recyclerViewAdapter: CaptionsRecyclerViewAdapter
private val captionsData = mutableListOf<CaptionsRttEntryModel>()
// private val captionsData = mutableListOf<CaptionsRttEntryModel>()
private var isAtBottom = true
private var isMaximized = false
@ -115,7 +114,7 @@ internal class CaptionsView : FrameLayout {
viewModel: CaptionsViewModel,
) {
this.viewModel = viewModel
recyclerViewAdapter = CaptionsRecyclerViewAdapter(captionsData)
recyclerViewAdapter = CaptionsRecyclerViewAdapter(viewModel.captionsAndRttData)
recyclerView.adapter = recyclerViewAdapter
recyclerView.layoutManager = LinearLayoutManager(this.context)
@ -126,95 +125,75 @@ internal class CaptionsView : FrameLayout {
}
})
viewModel.captionsAndRttDataCache.let { data ->
captionsData.addAll(data)
recyclerViewAdapter.notifyDataSetChanged()
}
recyclerView.post {
recyclerView.scrollToPosition(recyclerViewAdapter.itemCount - 1)
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isVisibleFlow.collect {
if (it) {
captionsLinearLayout.visibility = View.VISIBLE
captionsLinearLayout.post { scrollToBottom() }
} else {
captionsLinearLayout.visibility = View.GONE
viewLifecycleOwner.lifecycleScope.launchAll(
{
viewModel.isVisibleFlow.collect {
if (it) {
captionsLinearLayout.visibility = View.VISIBLE
captionsLinearLayout.post { scrollToBottom() }
} else {
captionsLinearLayout.visibility = View.GONE
}
}
},
{
viewModel.recordUpdatedAtPositionSharedFlow.collect {
onItemUpdated(it)
}
},
{
viewModel.recordInsertedAtPositionSharedFlow.collect {
onItemAdded(it)
}
},
{
viewModel.recordRemovedAtPositionSharedFlow.collect {
onItemRemoved(it)
}
},
{
viewModel.captionsStartProgressStateFlow.collect {
captionsStartProgressLayout.isVisible = it
}
},
{
viewModel.softwareKeyboardStateFlow.collect {
scrollToBottom()
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.onLastCaptionsDataUpdatedStateFlow.collect {
it?.let {
updateLastCaptionsData(it)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.onNewCaptionsDataAddedStateFlow.collect { it ->
it?.let {
applyLayoutDirection(it)
addNewCaptionsData(it)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.captionsStartProgressStateFlow.collect {
captionsStartProgressLayout.isVisible = it
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.softwareKeyboardStateFlow.collect {
scrollToBottom()
}
}
)
}
private fun updateLastCaptionsData(lastCaptionsData: CaptionsRttEntryModel) {
val index = captionsData.size - 1
if (index >= 0 && captionsData[index].speakerRawId == lastCaptionsData.speakerRawId) {
private fun onItemUpdated(index: Int) {
if (index >= 0) {
val shouldScrollToBottom = isAtBottom
captionsData[index] = lastCaptionsData
updateRecyclerViewItem(index)
recyclerViewAdapter.notifyItemChanged(index)
requestAccessibilityFocus(index)
if (shouldScrollToBottom) {
scrollToBottom()
}
}
}
private fun addNewCaptionsData(newCaptionsData: CaptionsRttEntryModel) {
if (captionsData.size >= CallingFragment.MAX_CAPTIONS_DATA_SIZE) {
captionsData.removeAt(0)
recyclerViewAdapter.notifyItemRemoved(0)
}
private fun onItemRemoved(index: Int) {
recyclerViewAdapter.notifyItemRemoved(index)
}
private fun onItemAdded(index: Int) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val shouldScrollToBottom = isAtBottom || layoutManager.findLastVisibleItemPosition() == layoutManager.itemCount - 1
captionsData.add(newCaptionsData)
insertRecyclerViewItem(captionsData.size - 1)
recyclerViewAdapter.notifyItemInserted(index)
requestAccessibilityFocus(index)
if (shouldScrollToBottom) {
scrollToBottom()
}
}
private fun updateRecyclerViewItem(position: Int) {
recyclerViewAdapter.notifyItemChanged(position)
requestAccessibilityFocus(position)
}
private fun insertRecyclerViewItem(position: Int) {
recyclerViewAdapter.notifyItemInserted(position)
requestAccessibilityFocus(position)
}
private fun requestAccessibilityFocus(position: Int) {
val accessibilityManager = this.context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
if (accessibilityManager.isEnabled) {
@ -230,7 +209,7 @@ internal class CaptionsView : FrameLayout {
}
// required when RTL language is selected for captions text
private fun applyLayoutDirection(captionsRecord: CaptionsRttEntryModel) {
private fun applyLayoutDirection(captionsRecord: CaptionsRttRecord) {
if (LocaleHelper.isRTL(captionsRecord.languageCode) && layoutDirection != LAYOUT_DIRECTION_RTL) {
captionsLinearLayout.layoutDirection = LAYOUT_DIRECTION_RTL
} else if (!LocaleHelper.isRTL(captionsRecord.languageCode) && layoutDirection != LAYOUT_DIRECTION_LTR) {
@ -239,7 +218,6 @@ internal class CaptionsView : FrameLayout {
}
fun stop() {
captionsData.clear()
recyclerView.adapter = null
recyclerView.layoutManager = null
recyclerView.removeAllViews()

Просмотреть файл

@ -3,34 +3,28 @@
package com.azure.android.communication.ui.calling.presentation.fragment.calling.captions
import com.azure.android.communication.common.CommunicationIdentifier
import com.azure.android.communication.ui.calling.presentation.manager.AvatarViewManager
import com.azure.android.communication.ui.calling.presentation.manager.CaptionsDataManager
import com.azure.android.communication.ui.calling.redux.action.Action
import com.azure.android.communication.ui.calling.redux.action.RttAction
import com.azure.android.communication.ui.calling.redux.state.CaptionsState
import com.azure.android.communication.ui.calling.redux.state.CaptionsStatus
import com.azure.android.communication.ui.calling.redux.state.DeviceConfigurationState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
internal class CaptionsViewModel(
private val dispatch: (Action) -> Unit,
captionsDataManager: CaptionsDataManager,
) {
private lateinit var isVisibleMutableFlow: MutableStateFlow<Boolean>
private lateinit var captionsStartInProgressStateMutableFlow: MutableStateFlow<Boolean>
private lateinit var softwareKeyboardStateMutableFlow: MutableStateFlow<Boolean>
private val captionsData = mutableListOf<CaptionsRttEntryModel>()
private val onLastCaptionsDataUpdatedMutableStateFlow = MutableStateFlow<CaptionsRttEntryModel?>(null)
private val onNewCaptionsDataAddedMutableStateFlow = MutableStateFlow<CaptionsRttEntryModel?>(null)
val captionsAndRttData = captionsDataManager.captionsAndRttData
val recordUpdatedAtPositionSharedFlow = captionsDataManager.recordUpdatedAtPositionSharedFlow
val recordInsertedAtPositionSharedFlow = captionsDataManager.recordInsertedAtPositionSharedFlow
val recordRemovedAtPositionSharedFlow = captionsDataManager.recordRemovedAtPositionSharedFlow
val captionsAndRttDataCache: List<CaptionsRttEntryModel> = captionsData
val onLastCaptionsDataUpdatedStateFlow: StateFlow<CaptionsRttEntryModel?> = onLastCaptionsDataUpdatedMutableStateFlow
val onNewCaptionsDataAddedStateFlow: StateFlow<CaptionsRttEntryModel?> = onNewCaptionsDataAddedMutableStateFlow
val softwareKeyboardStateFlow: StateFlow<Boolean>
get() = softwareKeyboardStateMutableFlow
@ -50,38 +44,13 @@ internal class CaptionsViewModel(
}
fun init(
coroutineScope: CoroutineScope,
captionsState: CaptionsState,
isVisible: Boolean,
captionsDataManager: CaptionsDataManager,
localParticipantIdentifier: CommunicationIdentifier?,
avatarViewManager: AvatarViewManager,
deviceConfigurationState: DeviceConfigurationState,
) {
isVisibleMutableFlow = MutableStateFlow(isVisible)
captionsStartInProgressStateMutableFlow = MutableStateFlow(canShowCaptionsStartInProgressUI(captionsState))
softwareKeyboardStateMutableFlow = MutableStateFlow(deviceConfigurationState.isSoftwareKeyboardVisible)
captionsData.addAll(
captionsDataManager.captionsDataCache.map { it.into(avatarViewManager, localParticipantIdentifier) }
)
captionsDataManager.resetFlows()
coroutineScope.launch {
captionsDataManager.getOnLastCaptionsDataUpdatedStateFlow().collect { data ->
data?.let {
onLastCaptionsDataUpdatedMutableStateFlow.value = it.into(avatarViewManager, localParticipantIdentifier)
}
}
}
coroutineScope.launch {
captionsDataManager.getOnNewCaptionsDataAddedStateFlow().collect { data ->
data?.let {
onNewCaptionsDataAddedMutableStateFlow.value = it.into(avatarViewManager, localParticipantIdentifier)
}
}
}
}
private fun canShowCaptionsStartInProgressUI(

Просмотреть файл

@ -33,6 +33,7 @@ import com.azure.android.communication.ui.calling.presentation.fragment.calling.
import com.azure.android.communication.ui.calling.presentation.fragment.calling.participantlist.ParticipantListViewModel
import com.azure.android.communication.ui.calling.presentation.fragment.common.audiodevicelist.AudioDeviceListViewModel
import com.azure.android.communication.ui.calling.presentation.manager.CapabilitiesManager
import com.azure.android.communication.ui.calling.presentation.manager.CaptionsDataManager
import com.azure.android.communication.ui.calling.presentation.manager.DebugInfoManager
import com.azure.android.communication.ui.calling.presentation.manager.UpdatableOptionsManager
import com.azure.android.communication.ui.calling.redux.Store
@ -45,6 +46,7 @@ internal class CallingViewModelFactory(
private val debugInfoManager: DebugInfoManager,
private val capabilitiesManager: CapabilitiesManager,
private val updatableOptionsManager: UpdatableOptionsManager,
private val captionsDataManager: CaptionsDataManager,
private val showSupportFormOption: Boolean = false,
private val enableMultitasking: Boolean,
private val isTelecomManagerEnabled: Boolean = false,
@ -164,5 +166,10 @@ internal class CallingViewModelFactory(
)
}
val captionsLanguageSelectionListViewModel by lazy { CaptionsLanguageSelectionListViewModel(store::dispatch) }
val captionsViewModel by lazy { CaptionsViewModel(store::dispatch) }
val captionsViewModel by lazy {
CaptionsViewModel(
store::dispatch,
captionsDataManager,
)
}
}

Просмотреть файл

@ -3,16 +3,18 @@
package com.azure.android.communication.ui.calling.presentation.manager
import android.graphics.Bitmap
import com.azure.android.communication.ui.calling.models.CallCompositeCaptionsData
import com.azure.android.communication.ui.calling.models.CaptionsResultType
import com.azure.android.communication.ui.calling.presentation.fragment.calling.CallingFragment
import com.azure.android.communication.ui.calling.presentation.fragment.calling.captions.CaptionsRecord
import com.azure.android.communication.ui.calling.presentation.fragment.calling.captions.CaptionsRttRecord
import com.azure.android.communication.ui.calling.presentation.fragment.calling.captions.CaptionsRttType
import com.azure.android.communication.ui.calling.redux.AppStore
import com.azure.android.communication.ui.calling.redux.state.ReduxState
import com.azure.android.communication.ui.calling.service.CallingService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@ -22,23 +24,19 @@ import java.time.Instant
internal class CaptionsDataManager(
private val callingService: CallingService,
private val appStore: AppStore<ReduxState>
private val appStore: AppStore<ReduxState>,
private val avatarViewManager: AvatarViewManager,
) {
private val mutex = Mutex()
private val captionsNewDataStateFlow = MutableStateFlow<CaptionsRecord?>(null)
private val captionsLastDataUpdatedStateFlow = MutableStateFlow<CaptionsRecord?>(null)
private val captionsAndRttMutableList = mutableListOf<CaptionsRttRecord>()
private val recordUpdatedAtPositionMutableSharedFlow = MutableSharedFlow<Int>()
private val recordInsertedAtPositionMutableSharedFlow = MutableSharedFlow<Int>()
private val recordRemovedAtPositionMutableSharedFlow = MutableSharedFlow<Int>()
fun getOnNewCaptionsDataAddedStateFlow() = captionsNewDataStateFlow
fun getOnLastCaptionsDataUpdatedStateFlow() = captionsLastDataUpdatedStateFlow
// cache to get last captions on screen rotation
val captionsDataCache = mutableListOf<CaptionsRecord>()
fun resetFlows() {
captionsNewDataStateFlow.value = null
captionsLastDataUpdatedStateFlow.value = null
}
val captionsAndRttData: List<CaptionsRttRecord> = captionsAndRttMutableList
val recordUpdatedAtPositionSharedFlow: SharedFlow<Int> = recordUpdatedAtPositionMutableSharedFlow
val recordInsertedAtPositionSharedFlow: SharedFlow<Int> = recordInsertedAtPositionMutableSharedFlow
val recordRemovedAtPositionSharedFlow: SharedFlow<Int> = recordRemovedAtPositionMutableSharedFlow
fun start(coroutineScope: CoroutineScope) {
coroutineScope.launch {
@ -46,21 +44,21 @@ internal class CaptionsDataManager(
mutex.withLock {
if (shouldSkipCaption(captionData)) return@collect
removeOverflownCaptionsFromCache()
val (captionText, languageCode) = getCaptionTextAndLanguage(captionData)
val captionsRecord = CaptionsRecord(
captionData.speakerName,
captionText,
captionData.speakerRawId,
languageCode,
captionData.resultType == CaptionsResultType.FINAL,
captionData.timestamp,
CaptionsRttType.CAPTIONS
val (customizedDisplayName, avatar) = getParticipantCustomizationsBitmap(captionData.speakerRawId)
val record = CaptionsRttRecord(
avatarBitmap = avatar,
displayName = customizedDisplayName ?: captionData.speakerName,
displayText = captionText,
speakerRawId = captionData.speakerRawId,
languageCode = languageCode,
isFinal = captionData.resultType == CaptionsResultType.FINAL,
timestamp = captionData.timestamp,
type = CaptionsRttType.CAPTIONS
)
handleCaptionData(captionsRecord)
removeOverflownCaptionsFromCache()
handleCaptionData(record)
}
}
}
@ -68,17 +66,19 @@ internal class CaptionsDataManager(
coroutineScope.launch {
callingService.getRttStateFlow().collect { rttRecord ->
mutex.withLock {
removeOverflownCaptionsFromCache()
val captionsRecord = CaptionsRecord(
rttRecord.senderName,
rttRecord.message,
rttRecord.senderUserRawId,
null,
rttRecord.isFinalized,
rttRecord.localCreatedTime,
CaptionsRttType.RTT
val (customizedDisplayName, avatar) = getParticipantCustomizationsBitmap(rttRecord.senderUserRawId)
val captionsRecord = CaptionsRttRecord(
avatarBitmap = avatar,
displayName = customizedDisplayName ?: rttRecord.senderName,
displayText = rttRecord.message,
speakerRawId = rttRecord.senderUserRawId,
languageCode = null,
isFinal = rttRecord.isFinalized,
timestamp = rttRecord.localCreatedTime,
type = CaptionsRttType.RTT
)
removeOverflownCaptionsFromCache()
handleRttData(captionsRecord)
}
}
@ -91,9 +91,10 @@ internal class CaptionsDataManager(
return !activeCaptionLanguage.isNullOrEmpty() && captionData.captionLanguage.isNullOrEmpty()
}
private fun removeOverflownCaptionsFromCache() {
if (captionsDataCache.size >= CallingFragment.MAX_CAPTIONS_DATA_SIZE) {
captionsDataCache.removeAt(0)
private suspend fun removeOverflownCaptionsFromCache() {
if (captionsAndRttMutableList.size >= CallingFragment.MAX_CAPTIONS_DATA_SIZE) {
captionsAndRttMutableList.removeAt(0)
recordRemovedAtPositionMutableSharedFlow.emit(0)
}
}
@ -105,8 +106,8 @@ internal class CaptionsDataManager(
}
}
private fun handleRttData(newCaptionsRecord: CaptionsRecord) {
val lastCaptionFromSameUser: CaptionsRecord? = getLastCaptionFromUser(newCaptionsRecord.speakerRawId, CaptionsRttType.RTT)
private suspend fun handleRttData(newCaptionsRecord: CaptionsRttRecord) {
val lastCaptionFromSameUser = getLastCaptionFromUser(newCaptionsRecord.speakerRawId, CaptionsRttType.RTT)
if (lastCaptionFromSameUser?.isFinal == false) {
updateLastCaption(lastCaptionFromSameUser, newCaptionsRecord)
@ -115,8 +116,8 @@ internal class CaptionsDataManager(
}
}
private fun handleCaptionData(newCaptionsRecord: CaptionsRecord) {
var lastCaptionFromSameUser: CaptionsRecord? = getLastCaptionFromUser(newCaptionsRecord.speakerRawId, CaptionsRttType.CAPTIONS)
private suspend fun handleCaptionData(newCaptionsRecord: CaptionsRttRecord) {
var lastCaptionFromSameUser = getLastCaptionFromUser(newCaptionsRecord.speakerRawId, CaptionsRttType.CAPTIONS)
if (lastCaptionFromSameUser != null && shouldFinalizeLastCaption(lastCaptionFromSameUser, newCaptionsRecord)) {
lastCaptionFromSameUser = finalizeLastCaption(lastCaptionFromSameUser)
@ -129,31 +130,39 @@ internal class CaptionsDataManager(
}
}
private fun getLastCaptionFromUser(speakerRawId: String, type: CaptionsRttType): CaptionsRecord? {
return captionsDataCache.lastOrNull { it.type == type && it.speakerRawId == speakerRawId }
private fun getLastCaptionFromUser(speakerRawId: String, type: CaptionsRttType): CaptionsRttRecord? {
return captionsAndRttMutableList.lastOrNull { it.type == type && it.speakerRawId == speakerRawId }
}
private fun shouldFinalizeLastCaption(lastCaption: CaptionsRecord, newCaptionsRecord: CaptionsRecord): Boolean {
val duration = Duration.between(Instant.ofEpochMilli(lastCaption.timestamp.time), Instant.ofEpochMilli(newCaptionsRecord.timestamp.time))
private fun shouldFinalizeLastCaption(lastCaption: CaptionsRttRecord, newCaptionsRecord: CaptionsRttRecord): Boolean {
val duration = Duration.between(
Instant.ofEpochMilli(lastCaption.timestamp.time),
Instant.ofEpochMilli(newCaptionsRecord.timestamp.time)
)
return duration.toMillis() > CallingFragment.MAX_CAPTIONS_PARTIAL_DATA_TIME_LIMIT
}
private fun addNewCaption(data: CaptionsRecord) {
captionsNewDataStateFlow.value = data
captionsDataCache.add(data)
private suspend fun addNewCaption(data: CaptionsRttRecord) {
captionsAndRttMutableList.add(data)
recordInsertedAtPositionMutableSharedFlow.emit(captionsAndRttMutableList.size - 1)
}
private fun updateLastCaption(lastCaptionFromSameUser: CaptionsRecord, captionsRecord: CaptionsRecord) {
val lastCaptionIndex = captionsDataCache.indexOf(lastCaptionFromSameUser)
captionsDataCache[lastCaptionIndex] = captionsRecord
captionsLastDataUpdatedStateFlow.value = captionsRecord
private suspend fun updateLastCaption(lastCaptionFromSameUser: CaptionsRttRecord, captionsRecord: CaptionsRttRecord) {
val lastCaptionIndex = captionsAndRttMutableList.indexOf(lastCaptionFromSameUser)
captionsAndRttMutableList[lastCaptionIndex] = captionsRecord
recordUpdatedAtPositionMutableSharedFlow.emit(lastCaptionIndex)
}
private fun finalizeLastCaption(captionsRecord: CaptionsRecord): CaptionsRecord {
val captionIndex = captionsDataCache.indexOf(captionsRecord)
private suspend fun finalizeLastCaption(captionsRecord: CaptionsRttRecord): CaptionsRttRecord {
val captionIndex = captionsAndRttMutableList.indexOf(captionsRecord)
val finalizedCaptionsRecord = captionsRecord.copy(isFinal = true)
captionsDataCache[captionIndex] = finalizedCaptionsRecord
captionsLastDataUpdatedStateFlow.value = captionsRecord
captionsAndRttMutableList[captionIndex] = finalizedCaptionsRecord
recordUpdatedAtPositionMutableSharedFlow.emit(captionIndex)
return finalizedCaptionsRecord
}
private fun getParticipantCustomizationsBitmap(speakerRawId: String): Pair<String?, Bitmap?> {
val remoteParticipantViewData = avatarViewManager.getRemoteParticipantViewData(speakerRawId)
return Pair(remoteParticipantViewData?.displayName, remoteParticipantViewData?.avatarBitmap)
}
}