Extract voice message player to its own module

This commit is contained in:
Benoit Marty
2024-12-13 15:30:24 +01:00
committed by Benoit Marty
parent c40d468e4f
commit 5e0d6a6e8a
23 changed files with 321 additions and 141 deletions

View File

@@ -47,6 +47,7 @@ dependencies {
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.preferences.api)
implementation(projects.libraries.roomselect.api)
implementation(projects.libraries.voiceplayer.api)
implementation(projects.libraries.voicerecorder.api)
implementation(projects.libraries.mediaplayer.api)
implementation(projects.libraries.uiUtils)

View File

@@ -29,8 +29,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
@Composable
fun TimelineItemEventContentView(

View File

@@ -40,9 +40,6 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageEvents
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageStateProvider
import io.element.android.libraries.androidutils.accessibility.isScreenReaderEnabled
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
import io.element.android.libraries.designsystem.preview.ElementPreview
@@ -52,6 +49,9 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider
import kotlinx.coroutines.delay
@Composable

View File

@@ -8,9 +8,9 @@
package io.element.android.features.messages.impl.timeline.di
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState
import io.element.android.features.messages.impl.voicemessages.timeline.aVoiceMessageState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.aVoiceMessageState
/**
* A fake [TimelineItemPresenterFactories] for screenshot tests.

View File

@@ -21,7 +21,6 @@ import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.messages.api.MessageComposerContext
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.permissions.api.PermissionsEvents
@@ -29,6 +28,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageException
import io.element.android.libraries.voicerecorder.api.VoiceRecorder
import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
import io.element.android.services.analytics.api.AnalyticsService

View File

@@ -8,11 +8,6 @@
package io.element.android.features.messages.impl.voicemessages.timeline
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
@@ -23,17 +18,10 @@ import dagger.multibindings.IntoMap
import io.element.android.features.messages.impl.timeline.di.TimelineItemEventContentKey
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactory
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.ui.utils.time.formatShort
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
@Module
@ContributesTo(RoomScope::class)
@@ -45,9 +33,7 @@ interface VoiceMessagePresenterModule {
}
class VoiceMessagePresenter @AssistedInject constructor(
voiceMessagePlayerFactory: VoiceMessagePlayer.Factory,
private val analyticsService: AnalyticsService,
private val scope: CoroutineScope,
voiceMessagePresenterFactory: VoiceMessagePresenterFactory,
@Assisted private val content: TimelineItemVoiceContent,
) : Presenter<VoiceMessageState> {
@AssistedFactory
@@ -55,97 +41,16 @@ class VoiceMessagePresenter @AssistedInject constructor(
override fun create(content: TimelineItemVoiceContent): VoiceMessagePresenter
}
private val player = voiceMessagePlayerFactory.create(
private val presenter = voiceMessagePresenterFactory.createVoiceMessagePresenter(
eventId = content.eventId,
mediaSource = content.mediaSource,
mimeType = content.mimeType,
filename = content.filename,
duration = content.duration,
)
private val play = mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized)
@Composable
override fun present(): VoiceMessageState {
val playerState by player.state.collectAsState(
VoiceMessagePlayer.State(
isReady = false,
isPlaying = false,
isEnded = false,
currentPosition = 0L,
duration = null
)
)
val button by remember {
derivedStateOf {
when {
content.eventId == null -> VoiceMessageState.Button.Disabled
playerState.isPlaying -> VoiceMessageState.Button.Pause
play.value is AsyncData.Loading -> VoiceMessageState.Button.Downloading
play.value is AsyncData.Failure -> VoiceMessageState.Button.Retry
else -> VoiceMessageState.Button.Play
}
}
}
val duration by remember {
derivedStateOf { playerState.duration ?: content.duration.inWholeMilliseconds }
}
val progress by remember {
derivedStateOf {
playerState.currentPosition / duration.toFloat()
}
}
val time by remember {
derivedStateOf {
when {
playerState.isReady && !playerState.isEnded -> playerState.currentPosition
playerState.currentPosition > 0 -> playerState.currentPosition
else -> duration
}.milliseconds.formatShort()
}
}
val showCursor by remember {
derivedStateOf {
!play.value.isUninitialized() && !playerState.isEnded
}
}
fun eventSink(event: VoiceMessageEvents) {
when (event) {
is VoiceMessageEvents.PlayPause -> {
if (playerState.isPlaying) {
player.pause()
} else if (playerState.isReady) {
player.play()
} else {
scope.launch {
play.runUpdatingState(
errorTransform = {
analyticsService.trackError(
VoiceMessageException.PlayMessageError("Error while trying to play voice message", it)
)
it
},
) {
player.prepare().flatMap {
runCatching { player.play() }
}
}
}
}
}
is VoiceMessageEvents.Seek -> {
player.seekTo((event.percentage * duration).toLong())
}
}
}
return VoiceMessageState(
button = button,
progress = progress,
time = time,
showCursor = showCursor,
eventSink = { eventSink(it) },
)
return presenter.present()
}
}

View File

@@ -18,7 +18,6 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Composer
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.media.AudioInfo
@@ -36,6 +35,7 @@ import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageException
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule

View File

@@ -0,0 +1,23 @@
import extension.setupAnvil
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.libraries.voiceplayer.api"
}
setupAnvil()
dependencies {
implementation(libs.androidx.annotationjvm)
implementation(libs.coroutines.core)
implementation(projects.libraries.matrix.api)
}

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.api
sealed interface VoiceMessageEvents {
data object PlayPause : VoiceMessageEvents

View File

@@ -5,17 +5,19 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages
package io.element.android.libraries.voiceplayer.api
internal sealed class VoiceMessageException : Exception() {
sealed class VoiceMessageException : Exception() {
data class FileException(
override val message: String?,
override val cause: Throwable? = null
) : VoiceMessageException()
data class PermissionMissing(
override val message: String?,
override val cause: Throwable?
) : VoiceMessageException()
data class PlayMessageError(
override val message: String?,
override val cause: Throwable?

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.voiceplayer.api
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import kotlin.time.Duration
interface VoiceMessagePresenterFactory {
fun createVoiceMessagePresenter(
eventId: EventId?,
mediaSource: MediaSource,
mimeType: String?,
filename: String?,
duration: Duration,
): Presenter<VoiceMessageState>
}

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.api
data class VoiceMessageState(
val button: Button,

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.api
import androidx.compose.ui.tooling.preview.PreviewParameterProvider

View File

@@ -0,0 +1,43 @@
import extension.setupAnvil
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.libraries.voiceplayer.impl"
}
setupAnvil()
dependencies {
api(projects.libraries.voiceplayer.api)
implementation(projects.libraries.core)
implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.mediaplayer.api)
implementation(projects.libraries.uiUtils)
implementation(projects.services.analytics.api)
implementation(libs.androidx.annotationjvm)
implementation(libs.coroutines.core)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testImplementation(libs.test.mockk)
testImplementation(libs.test.turbine)
testImplementation(libs.coroutines.core)
testImplementation(libs.coroutines.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.mediaplayer.test)
testImplementation(projects.services.analytics.test)
testImplementation(projects.tests.testutils)
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.voiceplayer.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.voiceplayer.api.VoiceMessagePresenterFactory
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import javax.inject.Inject
import kotlin.time.Duration
@ContributesBinding(RoomScope::class)
class DefaultVoiceMessagePresenterFactory @Inject constructor(
private val analyticsService: AnalyticsService,
private val scope: CoroutineScope,
private val voiceMessagePlayerFactory: VoiceMessagePlayer.Factory,
) : VoiceMessagePresenterFactory {
override fun createVoiceMessagePresenter(
eventId: EventId?,
mediaSource: MediaSource,
mimeType: String?,
filename: String?,
duration: Duration,
): Presenter<VoiceMessageState> {
val player = voiceMessagePlayerFactory.create(
eventId = eventId,
mediaSource = mediaSource,
mimeType = mimeType,
filename = filename,
)
return VoiceMessagePresenter(
analyticsService = analyticsService,
scope = scope,
player = player,
eventId = eventId,
duration = duration,
)
}
}

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.mimetype.MimeTypes

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.voiceplayer.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.ui.utils.time.formatShort
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents
import io.element.android.libraries.voiceplayer.api.VoiceMessageException
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
class VoiceMessagePresenter(
private val analyticsService: AnalyticsService,
private val scope: CoroutineScope,
private val player: VoiceMessagePlayer,
private val eventId: EventId?,
private val duration: Duration,
) : Presenter<VoiceMessageState> {
private val play = mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized)
@Composable
override fun present(): VoiceMessageState {
val playerState by player.state.collectAsState(
VoiceMessagePlayer.State(
isReady = false,
isPlaying = false,
isEnded = false,
currentPosition = 0L,
duration = null
)
)
val button by remember {
derivedStateOf {
when {
eventId == null -> VoiceMessageState.Button.Disabled
playerState.isPlaying -> VoiceMessageState.Button.Pause
play.value is AsyncData.Loading -> VoiceMessageState.Button.Downloading
play.value is AsyncData.Failure -> VoiceMessageState.Button.Retry
else -> VoiceMessageState.Button.Play
}
}
}
val duration by remember {
derivedStateOf { playerState.duration ?: duration.inWholeMilliseconds }
}
val progress by remember {
derivedStateOf {
playerState.currentPosition / duration.toFloat()
}
}
val time by remember {
derivedStateOf {
when {
playerState.isReady && !playerState.isEnded -> playerState.currentPosition
playerState.currentPosition > 0 -> playerState.currentPosition
else -> duration
}.milliseconds.formatShort()
}
}
val showCursor by remember {
derivedStateOf {
!play.value.isUninitialized() && !playerState.isEnded
}
}
fun eventSink(event: VoiceMessageEvents) {
when (event) {
is VoiceMessageEvents.PlayPause -> {
if (playerState.isPlaying) {
player.pause()
} else if (playerState.isReady) {
player.play()
} else {
scope.launch {
play.runUpdatingState(
errorTransform = {
analyticsService.trackError(
VoiceMessageException.PlayMessageError("Error while trying to play voice message", it)
)
it
},
) {
player.prepare().flatMap {
runCatching { player.play() }
}
}
}
}
}
is VoiceMessageEvents.Seek -> {
player.seekTo((event.percentage * duration).toLong())
}
}
}
return VoiceMessageState(
button = button,
progress = progress,
time = time,
showCursor = showCursor,
eventSink = { eventSink(it) },
)
}
}

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test

View File

@@ -5,7 +5,7 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import io.element.android.tests.testutils.simulateLongTask
import java.io.File

View File

@@ -5,21 +5,25 @@
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.features.messages.impl.voicemessages.timeline
package io.element.android.libraries.voiceplayer.impl
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents
import io.element.android.libraries.voiceplayer.api.VoiceMessageException
import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
class VoiceMessagePresenterTest {
@@ -41,7 +45,7 @@ class VoiceMessagePresenterTest {
fun `pressing play downloads and plays`() = runTest {
val presenter = createVoiceMessagePresenter(
mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000),
content = aTimelineItemVoiceContent(duration = 2_000.milliseconds),
duration = 2_000.milliseconds,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -79,7 +83,7 @@ class VoiceMessagePresenterTest {
mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000),
voiceMessageMediaRepo = FakeVoiceMessageMediaRepo().apply { shouldFail = true },
analyticsService = analyticsService,
content = aTimelineItemVoiceContent(duration = 2_000.milliseconds),
duration = 2_000.milliseconds,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -115,7 +119,7 @@ class VoiceMessagePresenterTest {
fun `pressing pause while playing pauses`() = runTest {
val presenter = createVoiceMessagePresenter(
mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000),
content = aTimelineItemVoiceContent(duration = 2_000.milliseconds),
duration = 2_000.milliseconds,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -147,7 +151,7 @@ class VoiceMessagePresenterTest {
@Test
fun `content with null eventId shows disabled button`() = runTest {
val presenter = createVoiceMessagePresenter(
content = aTimelineItemVoiceContent(eventId = null),
eventId = null,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -164,7 +168,7 @@ class VoiceMessagePresenterTest {
fun `seeking before play`() = runTest {
val presenter = createVoiceMessagePresenter(
mediaPlayer = FakeMediaPlayer(fakeTotalDurationMs = 2_000),
content = aTimelineItemVoiceContent(duration = 10_000.milliseconds),
duration = 10_000.milliseconds,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -188,7 +192,7 @@ class VoiceMessagePresenterTest {
@Test
fun `seeking after play`() = runTest {
val presenter = createVoiceMessagePresenter(
content = aTimelineItemVoiceContent(duration = 10_000.milliseconds),
duration = 10_000.milliseconds,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -224,19 +228,23 @@ fun TestScope.createVoiceMessagePresenter(
mediaPlayer: FakeMediaPlayer = FakeMediaPlayer(),
voiceMessageMediaRepo: VoiceMessageMediaRepo = FakeVoiceMessageMediaRepo(),
analyticsService: AnalyticsService = FakeAnalyticsService(),
content: TimelineItemVoiceContent = aTimelineItemVoiceContent(),
eventId: EventId? = EventId("\$anEventId"),
filename: String = "filename doesn't really matter for a voice message",
duration: Duration = 61_000.milliseconds,
contentUri: String = "mxc://matrix.org/1234567890abcdefg",
mimeType: String = MimeTypes.Ogg,
mediaSource: MediaSource = MediaSource(contentUri),
) = VoiceMessagePresenter(
voiceMessagePlayerFactory = { eventId, mediaSource, mimeType, filename ->
DefaultVoiceMessagePlayer(
mediaPlayer = mediaPlayer,
voiceMessageMediaRepoFactory = { _, _, _ -> voiceMessageMediaRepo },
eventId = eventId,
mediaSource = mediaSource,
mimeType = mimeType,
filename = filename
)
},
analyticsService = analyticsService,
scope = this,
content = content,
player = DefaultVoiceMessagePlayer(
mediaPlayer = mediaPlayer,
voiceMessageMediaRepoFactory = { _, _, _ -> voiceMessageMediaRepo },
eventId = eventId,
mediaSource = mediaSource,
mimeType = mimeType,
filename = filename
),
eventId = eventId,
duration = duration,
)

View File

@@ -83,6 +83,7 @@ fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:textcomposer:impl"))
implementation(project(":libraries:roomselect:impl"))
implementation(project(":libraries:cryptography:impl"))
implementation(project(":libraries:voiceplayer:impl"))
implementation(project(":libraries:voicerecorder:impl"))
implementation(project(":libraries:mediaplayer:impl"))
implementation(project(":libraries:mediaviewer:impl"))