diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 814262321c..ca3051351f 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.voicerecorder.api) + implementation(projects.libraries.mediaplayer.api) implementation(projects.libraries.uiUtils) implementation(projects.features.networkmonitor.api) implementation(projects.services.analytics.api) @@ -83,6 +84,7 @@ dependencies { testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.textcomposer.test) testImplementation(projects.libraries.voicerecorder.test) + testImplementation(projects.libraries.mediaplayer.test) testImplementation(libs.test.mockk) ksp(libs.showkase.processor) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 2263fe51cb..636569a24b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -31,7 +31,7 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.features.messages.impl.mediaplayer.MediaPlayer +import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt index 976351fd62..36dddb7d0d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt @@ -16,7 +16,7 @@ package io.element.android.features.messages.impl.voicemessages.composer -import io.element.android.features.messages.impl.mediaplayer.MediaPlayer +import io.element.android.libraries.mediaplayer.api.MediaPlayer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt index 3934c88b9c..93b336095f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePlayer.kt @@ -17,7 +17,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.messages.impl.mediaplayer.MediaPlayer +import io.element.android.libraries.mediaplayer.api.MediaPlayer 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 diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 6ab2e0d7f3..9709dde50e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -43,7 +43,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter import io.element.android.features.messages.media.FakeLocalMediaFactory -import io.element.android.features.messages.mediaplayer.FakeMediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.features.messages.textcomposer.TestRichTextEditorStateFactory import io.element.android.features.messages.timeline.components.customreaction.FakeEmojibaseProvider import io.element.android.features.messages.utils.messagesummary.FakeMessageSummaryFormatter diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index 27828446e7..3597db9473 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -30,7 +30,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.VoiceMessageException import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer -import io.element.android.features.messages.mediaplayer.FakeMediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt index 397a4a0373..eac9a905bd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/DefaultVoiceMessagePlayerTest.kt @@ -18,10 +18,10 @@ package io.element.android.features.messages.voicemessages.timeline import app.cash.turbine.test import com.google.common.truth.Truth -import io.element.android.features.messages.impl.mediaplayer.MediaPlayer +import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.features.messages.impl.voicemessages.timeline.DefaultVoiceMessagePlayer import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageMediaRepo -import io.element.android.features.messages.mediaplayer.FakeMediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.test.AN_EVENT_ID diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt index 3239c1879e..e735dd4f28 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/timeline/VoiceMessagePresenterTest.kt @@ -27,7 +27,7 @@ import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMes import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageMediaRepo import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessagePresenter import io.element.android.features.messages.impl.voicemessages.timeline.VoiceMessageState -import io.element.android.features.messages.mediaplayer.FakeMediaPlayer +import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/libraries/mediaplayer/api/build.gradle.kts b/libraries/mediaplayer/api/build.gradle.kts new file mode 100644 index 0000000000..383820b4e0 --- /dev/null +++ b/libraries/mediaplayer/api/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.mediaplayer.api" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(libs.coroutines.core) +} diff --git a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt new file mode 100644 index 0000000000..2c72cbf54a --- /dev/null +++ b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.mediaplayer.api + +import io.element.android.libraries.matrix.api.core.EventId +import kotlinx.coroutines.flow.StateFlow + +/** + * A media player for Element X. + */ +interface MediaPlayer : AutoCloseable { + + /** + * The current state of the player. + */ + val state: StateFlow + + /** + * Acquires control of the player and starts playing the given media. + */ + fun acquireControlAndPlay( + uri: String, + mediaId: String, + mimeType: String, + ) + + /** + * Plays the current media. + */ + fun play() + + /** + * Pauses the current media. + */ + fun pause() + + /** + * Seeks the current media to the given position. + */ + fun seekTo(positionMs: Long) + + /** + * Releases any resources associated with this player. + */ + override fun close() + + data class State( + /** + * Whether the player is currently playing. + */ + val isPlaying: Boolean, + /** + * The id of the media which is currently playing. + * + * NB: This is usually the string representation of the [EventId] of the event + * which contains the media. + */ + val mediaId: String?, + /** + * The current position of the player. + */ + val currentPosition: Long, + ) +} diff --git a/libraries/mediaplayer/impl/build.gradle.kts b/libraries/mediaplayer/impl/build.gradle.kts new file mode 100644 index 0000000000..b71f031ba0 --- /dev/null +++ b/libraries/mediaplayer/impl/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.mediaplayer.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + api(projects.libraries.mediaplayer.api) + implementation(libs.androidx.media3.exoplayer) + + implementation(libs.dagger) + implementation(projects.libraries.di) + + implementation(libs.coroutines.core) + + testImplementation(projects.tests.testutils) + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) + testImplementation(libs.test.mockk) + testImplementation(libs.test.turbine) + testImplementation(libs.coroutines.core) + testImplementation(libs.coroutines.test) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/MediaPlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt similarity index 76% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/MediaPlayer.kt rename to libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt index 0055496f43..88e592a9d8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/MediaPlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImpl.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.mediaplayer +package io.element.android.libraries.mediaplayer.impl import androidx.media3.common.MediaItem import androidx.media3.common.Player import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn -import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.mediaplayer.api.MediaPlayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -33,64 +33,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject -/** - * A media player for Element X. - */ -interface MediaPlayer : AutoCloseable { - - /** - * The current state of the player. - */ - val state: StateFlow - - /** - * Acquires control of the player and starts playing the given media. - */ - fun acquireControlAndPlay( - uri: String, - mediaId: String, - mimeType: String, - ) - - /** - * Plays the current media. - */ - fun play() - - /** - * Pauses the current media. - */ - fun pause() - - /** - * Seeks the current media to the given position. - */ - fun seekTo(positionMs: Long) - - /** - * Releases any resources associated with this player. - */ - override fun close() - - data class State( - /** - * Whether the player is currently playing. - */ - val isPlaying: Boolean, - /** - * The id of the media which is currently playing. - * - * NB: This is usually the string representation of the [EventId] of the event - * which contains the media. - */ - val mediaId: String?, - /** - * The current position of the player. - */ - val currentPosition: Long, - ) -} - /** * Default implementation of [MediaPlayer] backed by a [SimplePlayer]. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/SimplePlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt similarity index 97% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/SimplePlayer.kt rename to libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt index aeaa7bd69f..79a51973e6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mediaplayer/SimplePlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.messages.impl.mediaplayer +package io.element.android.libraries.mediaplayer.impl import android.content.Context import androidx.media3.common.MediaItem diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImplTest.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImplTest.kt new file mode 100644 index 0000000000..bf111026e1 --- /dev/null +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/MediaPlayerImplTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.mediaplayer.impl + +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MediaPlayerImplTest { + @Test + fun `default test`() = runTest { + // TODO + } +} diff --git a/libraries/mediaplayer/test/build.gradle.kts b/libraries/mediaplayer/test/build.gradle.kts new file mode 100644 index 0000000000..7f5654ccdf --- /dev/null +++ b/libraries/mediaplayer/test/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.mediaplayer.test" +} + +dependencies { + api(projects.libraries.mediaplayer.api) + implementation(projects.tests.testutils) + + implementation(libs.coroutines.test) + implementation(libs.test.truth) +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/mediaplayer/FakeMediaPlayer.kt b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt similarity index 93% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/mediaplayer/FakeMediaPlayer.kt rename to libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt index a9f0349552..efa5bb1fea 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/mediaplayer/FakeMediaPlayer.kt +++ b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.features.messages.mediaplayer +package io.element.android.libraries.mediaplayer.test -import io.element.android.features.messages.impl.mediaplayer.MediaPlayer +import io.element.android.libraries.mediaplayer.api.MediaPlayer import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index a64b822d62..9986d490fa 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -28,6 +28,6 @@ dependencies { implementation(libs.kotlin.gradle.plugin) implementation(platform(libs.google.firebase.bom)) // FIXME: using the bom ^, it should not be necessary to provide the version v... - implementation("com.google.firebase:firebase-appdistribution-gradle:4.0.0") + implementation("com.google.firebase:firebase-appdistribution-gradle:4.0.1") implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) } diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 33ba672bcd..28069b932c 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -103,6 +103,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:textcomposer:impl")) implementation(project(":libraries:cryptography:impl")) implementation(project(":libraries:voicerecorder:impl")) + implementation(project(":libraries:mediaplayer:impl")) } fun DependencyHandlerScope.allServicesImpl() {