Skip to content

Commit

Permalink
fix: Listen few audios at the same time [WPB-11180] (#3639)
Browse files Browse the repository at this point in the history
  • Loading branch information
borichellow authored Nov 26, 2024
1 parent 8d01a7d commit 286ad3f
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import android.content.Context
import android.media.MediaPlayer
import android.media.MediaPlayer.SEEK_CLOSEST_SYNC
import android.net.Uri
import com.wire.android.di.KaliumCoreLogic
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.MessageAssetResult
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
Expand All @@ -34,14 +36,44 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

class ConversationAudioMessagePlayer
@Singleton
class ConversationAudioMessagePlayerProvider
@Inject constructor(
private val context: Context,
private val audioMediaPlayer: MediaPlayer,
private val getMessageAsset: GetMessageAssetUseCase
@KaliumCoreLogic private val coreLogic: CoreLogic,
) {
private var player: ConversationAudioMessagePlayer? = null
private var usageCount: Int = 0

@Synchronized
fun provide(): ConversationAudioMessagePlayer {
val player = player ?: ConversationAudioMessagePlayer(context, audioMediaPlayer, coreLogic).also { player = it }
usageCount++

return player
}

@Synchronized
fun onCleared() {
usageCount--
if (usageCount <= 0) {
player?.close()
player = null
}
}
}

class ConversationAudioMessagePlayer
internal constructor(
private val context: Context,
private val audioMediaPlayer: MediaPlayer,
@KaliumCoreLogic private val coreLogic: CoreLogic,
) {
private companion object {
const val UPDATE_POSITION_INTERVAL_IN_MS = 1000L
Expand Down Expand Up @@ -137,7 +169,7 @@ class ConversationAudioMessagePlayer
}

audioMessageStateHistory
}
}.onStart { emit(audioMessageStateHistory) }

private var currentAudioMessageId: String? = null

Expand Down Expand Up @@ -169,10 +201,10 @@ class ConversationAudioMessagePlayer
}

private suspend fun stopCurrentlyPlayingAudioMessage() {
if (currentAudioMessageId != null) {
val currentAudioState = audioMessageStateHistory[currentAudioMessageId]
currentAudioMessageId?.let {
val currentAudioState = audioMessageStateHistory[it]
if (currentAudioState?.audioMediaPlayingState != AudioMediaPlayingState.Fetching) {
stop(currentAudioMessageId!!)
stop(it)
}
}
}
Expand All @@ -194,14 +226,22 @@ class ConversationAudioMessagePlayer

coroutineScope {
launch {
val currentAccountResult = coreLogic.getGlobalScope().session.currentSession()
if (currentAccountResult is CurrentSessionResult.Failure) return@launch

audioMessageStateUpdate.emit(
AudioMediaPlayerStateUpdate.AudioMediaPlayingStateUpdate(
messageId,
AudioMediaPlayingState.Fetching
)
)

when (val result = getMessageAsset(conversationId, messageId).await()) {
val assetMessage = coreLogic
.getSessionScope((currentAccountResult as CurrentSessionResult.Success).accountInfo.userId)
.messages
.getAssetMessage(conversationId, messageId)

when (val result = assetMessage.await()) {
is MessageAssetResult.Success -> {
audioMessageStateUpdate.emit(
AudioMediaPlayerStateUpdate.AudioMediaPlayingStateUpdate(
Expand All @@ -219,9 +259,7 @@ class ConversationAudioMessagePlayer
)
audioMediaPlayer.prepare()

if (position != null) {
audioMediaPlayer.seekTo(position)
}
if (position != null) audioMediaPlayer.seekTo(position)

audioMediaPlayer.start()

Expand Down Expand Up @@ -292,7 +330,7 @@ class ConversationAudioMessagePlayer
)
}

fun close() {
audioMediaPlayer.release()
internal fun close() {
audioMediaPlayer.reset()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayerProvider
import com.wire.android.model.SnackBarMessage
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.ui.home.conversations.ConversationNavArgs
Expand Down Expand Up @@ -98,7 +98,7 @@ class ConversationMessagesViewModel @Inject constructor(
private val getMessageForConversation: GetMessagesForConversationUseCase,
private val toggleReaction: ToggleReactionUseCase,
private val resetSession: ResetSessionUseCase,
private val conversationAudioMessagePlayer: ConversationAudioMessagePlayer,
private val conversationAudioMessagePlayerProvider: ConversationAudioMessagePlayerProvider,
private val getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase,
private val clearUsersTypingEvents: ClearUsersTypingEventsUseCase,
private val getSearchedConversationMessagePosition: GetSearchedConversationMessagePositionUseCase,
Expand All @@ -108,6 +108,7 @@ class ConversationMessagesViewModel @Inject constructor(
private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs()
val conversationId: QualifiedID = conversationNavArgs.conversationId
private val searchedMessageIdNavArgs: String? = conversationNavArgs.searchedMessageId
private val conversationAudioMessagePlayer = conversationAudioMessagePlayerProvider.provide()

var conversationViewState by mutableStateOf(
ConversationMessagesViewState(
Expand Down Expand Up @@ -436,7 +437,7 @@ class ConversationMessagesViewModel @Inject constructor(

override fun onCleared() {
super.onCleared()
conversationAudioMessagePlayer.close()
conversationAudioMessagePlayerProvider.onCleared()
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ import com.wire.android.framework.FakeKaliumFileSystem
import com.wire.android.media.audiomessage.AudioMediaPlayingState
import com.wire.android.media.audiomessage.AudioState
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.auth.AccountInfo
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.asset.MessageAssetResult
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
Expand All @@ -45,11 +48,14 @@ class ConversationAudioMessagePlayerTest {
val (arrangement, conversationAudioMessagePlayer) = Arrangement()
.withAudioMediaPlayerReturningTotalTime(1000)
.withSuccessFullAssetFetch()
.withCurrentSession()
.arrange()

val testAudioMessageId = "some-dummy-message-id"

conversationAudioMessagePlayer.observableAudioMessagesState.test {
// skip first emit from onStart
awaitItem()
conversationAudioMessagePlayer.playAudio(
ConversationId("some-dummy-value", "some.dummy.domain"),
testAudioMessageId
Expand Down Expand Up @@ -95,13 +101,16 @@ class ConversationAudioMessagePlayerTest {
fun givenTheSuccessFullAssetFetch_whenPlayingTheSameMessageIdTwiceSequentially_thenEmitStatesAsExpected() = runTest {
val (arrangement, conversationAudioMessagePlayer) = Arrangement()
.withSuccessFullAssetFetch()
.withCurrentSession()
.withAudioMediaPlayerReturningTotalTime(1000)
.withMediaPlayerPlaying()
.arrange()

val testAudioMessageId = "some-dummy-message-id"

conversationAudioMessagePlayer.observableAudioMessagesState.test {
// skip first emit from onStart
awaitItem()
// playing first time
conversationAudioMessagePlayer.playAudio(
ConversationId("some-dummy-value", "some.dummy.domain"),
Expand Down Expand Up @@ -161,13 +170,16 @@ class ConversationAudioMessagePlayerTest {
runTest {
val (arrangement, conversationAudioMessagePlayer) = Arrangement()
.withSuccessFullAssetFetch()
.withCurrentSession()
.withAudioMediaPlayerReturningTotalTime(1000)
.arrange()

val firstAudioMessageId = "some-dummy-message-id1"
val secondAudioMessageId = "some-dummy-message-id2"

conversationAudioMessagePlayer.observableAudioMessagesState.test {
// skip first emit from onStart
awaitItem()
// playing first audio message
conversationAudioMessagePlayer.playAudio(
ConversationId("some-dummy-value", "some.dummy.domain"),
Expand Down Expand Up @@ -242,13 +254,16 @@ class ConversationAudioMessagePlayerTest {
runTest {
val (arrangement, conversationAudioMessagePlayer) = Arrangement()
.withSuccessFullAssetFetch()
.withCurrentSession()
.withAudioMediaPlayerReturningTotalTime(1000)
.arrange()

val firstAudioMessageId = "some-dummy-message-id1"
val secondAudioMessageId = "some-dummy-message-id2"

conversationAudioMessagePlayer.observableAudioMessagesState.test {
// skip first emit from onStart
awaitItem()
// playing first audio message
conversationAudioMessagePlayer.playAudio(
ConversationId("some-dummy-value", "some.dummy.domain"),
Expand Down Expand Up @@ -366,13 +381,16 @@ class ConversationAudioMessagePlayerTest {
runTest {
val (arrangement, conversationAudioMessagePlayer) = Arrangement()
.withSuccessFullAssetFetch()
.withCurrentSession()
.withAudioMediaPlayerReturningTotalTime(1000)
.withMediaPlayerPlaying()
.arrange()

val testAudioMessageId = "some-dummy-message-id"

conversationAudioMessagePlayer.observableAudioMessagesState.test {
// skip first emit from onStart
awaitItem()
// playing first time
conversationAudioMessagePlayer.playAudio(
ConversationId("some-dummy-value", "some.dummy.domain"),
Expand Down Expand Up @@ -454,7 +472,7 @@ class Arrangement {
lateinit var context: Context

@MockK
lateinit var getMessageAssetUseCase: GetMessageAssetUseCase
lateinit var coreLogic: CoreLogic

@MockK
lateinit var mediaPlayer: MediaPlayer
Expand All @@ -463,16 +481,24 @@ class Arrangement {
ConversationAudioMessagePlayer(
context,
mediaPlayer,
getMessageAssetUseCase,
coreLogic,
)
}

init {
MockKAnnotations.init(this, relaxed = true)
}

fun withCurrentSession() = apply {
coEvery { coreLogic.getGlobalScope().session.currentSession.invoke() } returns CurrentSessionResult.Success(
AccountInfo.Valid(UserId("some-user-value", "some.user.domain"))
)
}

fun withSuccessFullAssetFetch() = apply {
coEvery { getMessageAssetUseCase.invoke(any(), any()) } returns CompletableDeferred(
coEvery {
coreLogic.getSessionScope(any()).messages.getAssetMessage.invoke(any<ConversationId>(), any<String>())
} returns CompletableDeferred(
MessageAssetResult.Success(
decodedAssetPath = FakeKaliumFileSystem().selfUserAvatarPath(),
assetSize = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.wire.android.config.TestDispatcherProvider
import com.wire.android.config.mockUri
import com.wire.android.media.audiomessage.AudioState
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayerProvider
import com.wire.android.ui.home.conversations.ConversationNavArgs
import com.wire.android.ui.home.conversations.model.AssetBundle
import com.wire.android.ui.home.conversations.model.UIMessage
Expand Down Expand Up @@ -96,6 +97,9 @@ class ConversationMessagesViewModelArrangement {
@MockK
lateinit var conversationAudioMessagePlayer: ConversationAudioMessagePlayer

@MockK
lateinit var conversationAudioMessagePlayerProvider: ConversationAudioMessagePlayerProvider

@MockK
lateinit var getConversationUnreadEventsCount: GetConversationUnreadEventsCountUseCase

Expand Down Expand Up @@ -124,7 +128,7 @@ class ConversationMessagesViewModelArrangement {
getMessagesForConversationUseCase,
toggleReaction,
resetSession,
conversationAudioMessagePlayer,
conversationAudioMessagePlayerProvider,
getConversationUnreadEventsCount,
clearUsersTypingEvents,
getSearchedConversationMessagePosition,
Expand All @@ -143,6 +147,8 @@ class ConversationMessagesViewModelArrangement {
coEvery { getConversationUnreadEventsCount(any()) } returns GetConversationUnreadEventsCountUseCase.Result.Success(0L)
coEvery { updateAssetMessageDownloadStatus(any(), any(), any()) } returns UpdateTransferStatusResult.Success
coEvery { clearUsersTypingEvents() } returns Unit
every { conversationAudioMessagePlayerProvider.provide() } returns conversationAudioMessagePlayer
every { conversationAudioMessagePlayerProvider.onCleared() } returns Unit
coEvery {
getSearchedConversationMessagePosition(any(), any())
} returns GetSearchedConversationMessagePositionUseCase.Result.Success(position = 0)
Expand Down

0 comments on commit 286ad3f

Please sign in to comment.