From a88a172f0fc63d65a7c4a43224335b9d8647e261 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 6 Oct 2022 17:04:27 +0200 Subject: [PATCH 01/20] Trigger play/pause/resume/stop actions on VoiceRecorder --- .../room/detail/composer/AudioMessageHelper.kt | 17 +++++++++++++++-- .../app/features/voice/AbstractVoiceRecorder.kt | 4 ++-- .../vector/app/features/voice/VoiceRecorder.kt | 10 ++++++++++ .../vector/app/features/voice/VoiceRecorderL.kt | 11 ++++++++++- .../vector/app/features/voice/VoiceRecorderQ.kt | 11 +++++++++++ .../usecase/PauseVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/ResumeVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/StartVoiceBroadcastUseCase.kt | 8 +++++++- .../usecase/StopVoiceBroadcastUseCase.kt | 8 +++++++- 9 files changed, 76 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index a5e899c672..f068330cb1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -114,8 +114,13 @@ class AudioMessageHelper @Inject constructor( * When entering in playback mode actually. */ fun pauseRecording() { - voiceRecorder.stopRecord() - stopRecordingAmplitudes() + voiceRecorder.pauseRecord() + pauseRecordingAmplitudes() + } + + fun resumeRecording() { + voiceRecorder.resumeRecord() + resumeRecordingAmplitudes() } fun deleteRecording() { @@ -221,6 +226,14 @@ class AudioMessageHelper @Inject constructor( } } + private fun pauseRecordingAmplitudes() { + amplitudeTicker?.pause() + } + + private fun resumeRecordingAmplitudes() { + amplitudeTicker?.resume() + } + private fun stopRecordingAmplitudes() { amplitudeTicker?.stop() amplitudeTicker = null diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index 5e27aa5bb2..b28d76f176 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -32,7 +32,7 @@ abstract class AbstractVoiceRecorder( ) : VoiceRecorder { private val outputDirectory: File by lazy { ensureAudioDirectory(context) } - private var mediaRecorder: MediaRecorder? = null + protected var mediaRecorder: MediaRecorder? = null private var outputFile: File? = null abstract fun setOutputFormat(mediaRecorder: MediaRecorder) @@ -79,8 +79,8 @@ abstract class AbstractVoiceRecorder( } override fun stopRecord() { - // Can throw when the record is less than 1 second. mediaRecorder?.let { + // Can throw when the record is less than 1 second. tryOrNull { it.stop() } it.reset() it.release() diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 785fb9b4da..761aad2334 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -34,6 +34,16 @@ interface VoiceRecorder { */ fun startRecord(roomId: String) + /** + * Pause the recording. + */ + fun pauseRecord() + + /** + * Resume the recording. + */ + fun resumeRecord() + /** * Stop the recording. */ diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index d5395cc849..1460f1a88f 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -23,6 +23,7 @@ import android.media.MediaRecorder import android.media.audiofx.AutomaticGainControl import android.media.audiofx.NoiseSuppressor import android.os.Build +import android.widget.Toast import io.element.android.opusencoder.OggOpusEncoder import io.element.android.opusencoder.configuration.SampleRate import kotlinx.coroutines.CoroutineScope @@ -40,7 +41,7 @@ import kotlin.coroutines.CoroutineContext * VoiceRecorder to be used on Android versions < [Build.VERSION_CODES.Q]. It uses libopus to record ogg files. */ class VoiceRecorderL( - context: Context, + private val context: Context, coroutineContext: CoroutineContext, private val codec: OggOpusEncoder, ) : VoiceRecorder { @@ -112,6 +113,14 @@ class VoiceRecorderL( } } + override fun pauseRecord() { + Toast.makeText(context, "Not implemented for this Android version", Toast.LENGTH_SHORT).show() + } + + override fun resumeRecord() { + Toast.makeText(context, "Not implemented for this Android version", Toast.LENGTH_SHORT).show() + } + override fun stopRecord() { val recorder = this.audioRecorder ?: return recordingJob?.cancel() diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt index 5091ddfa3b..1eb850b8f5 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt @@ -20,12 +20,23 @@ import android.content.Context import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi +import org.matrix.android.sdk.api.extensions.tryOrNull /** * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. It uses the native OPUS support on Android 10+. */ @RequiresApi(Build.VERSION_CODES.Q) class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") { + + override fun pauseRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.pause() } + } + + override fun resumeRecord() { + mediaRecorder?.resume() + } + override fun setOutputFormat(mediaRecorder: MediaRecorder) { // We can directly use OGG here mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 8f61284423..6c779b7a50 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class PauseVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -60,6 +62,10 @@ class PauseVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO pause recording audio files + pauseRecording() + } + + private fun pauseRecording() { + audioMessageHelper.pauseRecording() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index d0d82b42c3..fbbbc32612 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class ResumeVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -65,6 +67,10 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO resume recording audio files + resumeRecording() + } + + private fun resumeRecording() { + audioMessageHelper.resumeRecording() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 0b8328cd4b..03dbbed5e4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -30,6 +31,7 @@ import javax.inject.Inject class StartVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -62,6 +64,10 @@ class StartVoiceBroadcastUseCase @Inject constructor( ).toContent() ) - // TODO start recording audio files + startRecording(room) + } + + private fun startRecording(room: Room) { + audioMessageHelper.startRecording(room.roomId) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 8b22193770..ed868fced8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,6 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase +import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -31,6 +32,7 @@ import javax.inject.Inject class StopVoiceBroadcastUseCase @Inject constructor( private val session: Session, + private val audioMessageHelper: AudioMessageHelper, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -61,6 +63,10 @@ class StopVoiceBroadcastUseCase @Inject constructor( ).toContent(), ) - // TODO stop recording audio files + stopRecording() + } + + private fun stopRecording() { + audioMessageHelper.stopRecording() } } From dbc61971df6f5bab1e44f5d9af7dbd4a30fe3cbd Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 17:32:36 +0200 Subject: [PATCH 02/20] Reduce duplicated code --- .../room/send/LocalEchoEventFactory.kt | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 4d5e574592..afff51a4a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -403,14 +403,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -447,14 +440,7 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailInfo = thumbnailInfo ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -479,14 +465,7 @@ internal class LocalEchoEventFactory @Inject constructor( waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -500,14 +479,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { - RelationDefaultContent( - type = RelationType.THREAD, - eventId = it, - isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it)) - ) - } + relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -629,6 +601,14 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } + private fun generateThreadRelationContent(rootThreadEventId: String) = + RelationDefaultContent( + type = RelationType.THREAD, + eventId = rootThreadEventId, + isFallingBack = true, + inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)) + ) + /** * Generates the appropriate relatesTo object for a reply event. * It can either be a regular reply or a reply within a thread From 33a021c8edd491de5ff769de762a0983f4f35b1f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 19 Sep 2022 18:11:38 +0200 Subject: [PATCH 03/20] Add sdk entry to attach reference to the outgoing events --- .../sdk/api/session/room/send/SendService.kt | 5 ++- .../session/room/send/DefaultSendService.kt | 7 ++-- .../room/send/LocalEchoEventFactory.kt | 36 +++++++++++-------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index de9bcfbf0d..7bd6337548 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.PollType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -81,13 +82,15 @@ interface SendService { * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread + * @param relatesTo add a relation content to the media event * @return a [Cancelable] */ fun sendMedia( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, + relatesTo: RelationDefaultContent? = null ): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index a3f2825a0c..aa305e6067 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -280,7 +281,8 @@ internal class DefaultSendService @AssistedInject constructor( attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Cancelable { // Ensure that the event will not be send in a thread if we are a different flow. // Like sending files to multiple rooms @@ -295,7 +297,8 @@ internal class DefaultSendService @AssistedInject constructor( localEchoEventFactory.createMediaEvent( roomId = it, attachment = attachment, - rootThreadEventId = rootThreadId + rootThreadEventId = rootThreadId, + relatesTo, ).also { event -> createLocalEcho(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index afff51a4a5..0fb2c9506a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -347,14 +347,21 @@ internal class LocalEchoEventFactory @Inject constructor( fun createMediaEvent( roomId: String, attachment: ContentAttachmentData, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo) + ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo) + ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent( + roomId, + attachment, + isVoiceMessage = true, + rootThreadEventId = rootThreadEventId, + relatesTo + ) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo) } } @@ -378,7 +385,7 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { var width = attachment.width var height = attachment.height @@ -403,12 +410,12 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -440,7 +447,7 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailInfo = thumbnailInfo ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } @@ -449,7 +456,8 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, attachment: ContentAttachmentData, isVoiceMessage: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, ): Event { val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -465,12 +473,12 @@ internal class LocalEchoEventFactory @Inject constructor( waveform = waveformSanitizer.sanitize(attachment.waveform) ), voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { + private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -479,7 +487,7 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size ), url = attachment.queryUri.toString(), - relatesTo = rootThreadEventId?.let { generateThreadRelationContent(it) } + relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) } ) return createMessageEvent(roomId, content) } From e775404e35822ab5e9e9307d589762138ff9dac6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:15:06 +0200 Subject: [PATCH 04/20] Improve VoiceRecorder abstraction --- .../detail/composer/AudioMessageHelper.kt | 4 +- .../composer/MessageComposerViewModel.kt | 2 +- .../features/voice/AbstractVoiceRecorder.kt | 75 ++--------- .../features/voice/AbstractVoiceRecorderQ.kt | 124 ++++++++++++++++++ .../app/features/voice/VoiceRecorder.kt | 14 +- .../app/features/voice/VoiceRecorderL.kt | 33 +---- .../app/features/voice/VoiceRecorderQ.kt | 23 +--- 7 files changed, 163 insertions(+), 112 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index f068330cb1..d98240904c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -55,8 +55,8 @@ class AudioMessageHelper @Inject constructor( private var amplitudeTicker: CountUpTimer? = null private var playbackTicker: CountUpTimer? = null - fun initializeRecorder(attachmentData: ContentAttachmentData) { - voiceRecorder.initializeRecord(attachmentData) + fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) { + voiceRecorder.initializeRecord(roomId, attachmentData) amplitudeList.clear() attachmentData.waveform?.let { amplitudeList.addAll(it) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 5d3465ab2e..6f1210a584 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -943,7 +943,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) { - audioMessageHelper.initializeRecorder(attachmentData) + audioMessageHelper.initializeRecorder(room.roomId, attachmentData) setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) } } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index b28d76f176..9755a0b3fb 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -17,77 +17,24 @@ package im.vector.app.features.voice import android.content.Context -import android.media.MediaRecorder -import android.os.Build -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.util.md5 import java.io.File -import java.io.FileOutputStream import java.util.UUID abstract class AbstractVoiceRecorder( private val context: Context, - private val filenameExt: String, ) : VoiceRecorder { + private val outputDirectory: File by lazy { ensureAudioDirectory(context) } + protected var outputFile: File? = null - protected var mediaRecorder: MediaRecorder? = null - private var outputFile: File? = null - - abstract fun setOutputFormat(mediaRecorder: MediaRecorder) - - private fun init() { - createMediaRecorder().let { - it.setAudioSource(MediaRecorder.AudioSource.DEFAULT) - setOutputFormat(it) - it.setAudioEncodingBitRate(24000) - it.setAudioSamplingRate(48000) - mediaRecorder = it + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + if (attachmentData != null) { + outputFile = attachmentData.findVoiceFile(outputDirectory) } } - private fun createMediaRecorder(): MediaRecorder { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaRecorder(context) - } else { - @Suppress("DEPRECATION") - MediaRecorder() - } - } - - override fun initializeRecord(attachmentData: ContentAttachmentData) { - outputFile = attachmentData.findVoiceFile(outputDirectory) - } - - override fun startRecord(roomId: String) { - init() - val fileName = "${UUID.randomUUID()}.$filenameExt" - val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { - mkdirs() - } - outputFile = File(outputDirectoryForRoom, fileName) - - val mr = mediaRecorder ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mr.setOutputFile(outputFile) - } else { - mr.setOutputFile(FileOutputStream(outputFile).fd) - } - mr.prepare() - mr.start() - } - - override fun stopRecord() { - mediaRecorder?.let { - // Can throw when the record is less than 1 second. - tryOrNull { it.stop() } - it.reset() - it.release() - } - mediaRecorder = null - } - override fun cancelRecord() { stopRecord() @@ -95,11 +42,15 @@ abstract class AbstractVoiceRecorder( outputFile = null } - override fun getMaxAmplitude(): Int { - return mediaRecorder?.maxAmplitude ?: 0 - } - override fun getVoiceMessageFile(): File? { return outputFile } + + protected fun createOutputFile(roomId: String): File { + val fileName = "${UUID.randomUUID()}.$fileNameExt" + val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { + mkdirs() + } + return File(outputDirectoryForRoom, fileName) + } } diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt new file mode 100644 index 0000000000..bd30c38366 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 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 im.vector.app.features.voice + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.io.File + +/** + * VoiceRecorder abstraction to be used on Android versions >= [Build.VERSION_CODES.Q]. + */ +@RequiresApi(Build.VERSION_CODES.Q) +abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVoiceRecorder(context) { + + var mediaRecorder: MediaRecorder? = null + protected var nextOutputFile: File? = null + + private val audioSource: Int = MediaRecorder.AudioSource.DEFAULT + private val audioEncodingBitRate: Int = 24_000 + private val audioSamplingRate: Int = 48_000 + + abstract val outputFormat: Int // see MediaRecorder.OutputFormat + abstract val audioEncoder: Int // see MediaRecorder.AudioEncoder + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder = createMediaRecorder().apply { + setAudioSource(audioSource) + setOutputFormat() + setAudioEncodingBitRate(audioEncodingBitRate) + setAudioSamplingRate(audioSamplingRate) + } + setOutputFile(roomId) + } + + override fun startRecord(roomId: String) { + initializeRecord(roomId = roomId) + mediaRecorder?.prepare() + mediaRecorder?.start() + } + + override fun pauseRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.pause() } + } + + override fun resumeRecord() { + mediaRecorder?.resume() + } + + override fun stopRecord() { + // Can throw when the record is less than 1 second. + tryOrNull { mediaRecorder?.stop() } + mediaRecorder?.reset() + release() + } + + override fun cancelRecord() { + super.cancelRecord() + nextOutputFile?.delete() + nextOutputFile = null + } + + override fun getMaxAmplitude(): Int { + return mediaRecorder?.maxAmplitude ?: 0 + } + + protected open fun release() { + mediaRecorder?.release() + mediaRecorder = null + } + + fun setNextOutputFile(roomId: String) { + val mediaRecorder = mediaRecorder ?: return + nextOutputFile = createOutputFile(roomId) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaRecorder.setNextOutputFile(nextOutputFile) + } else { + mediaRecorder.setNextOutputFile(nextOutputFile?.outputStream()?.fd) + } + } + + private fun createMediaRecorder(): MediaRecorder { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaRecorder(context) + } else { + @Suppress("DEPRECATION") + MediaRecorder() + } + } + + private fun MediaRecorder.setOutputFormat() { + setOutputFormat(outputFormat) + setAudioEncoder(audioEncoder) + } + + private fun setOutputFile(roomId: String) { + val mediaRecorder = mediaRecorder ?: return + outputFile = outputFile ?: createOutputFile(roomId) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaRecorder.setOutputFile(outputFile) + } else { + mediaRecorder.setOutputFile(outputFile?.outputStream()?.fd) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt index 761aad2334..bf38e4adbf 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorder.kt @@ -22,11 +22,19 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData import java.io.File interface VoiceRecorder { + /** - * Initialize recording with a pre-recorded file. - * @param attachmentData data of the recorded file + * Audio file extension (eg. `mp4`). */ - fun initializeRecord(attachmentData: ContentAttachmentData) + val fileNameExt: String + + /** + * Initialize recording with an optional pre-recorded file. + * + * @param roomId id of the room to initialize record + * @param attachmentData data of the pre-recorded file, if any. + */ + fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData? = null) /** * Start the recording. diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index 1460f1a88f..13ddf60620 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -31,10 +31,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.content.ContentAttachmentData -import org.matrix.android.sdk.api.util.md5 -import java.io.File -import java.util.UUID import kotlin.coroutines.CoroutineContext /** @@ -44,16 +40,13 @@ class VoiceRecorderL( private val context: Context, coroutineContext: CoroutineContext, private val codec: OggOpusEncoder, -) : VoiceRecorder { +) : AbstractVoiceRecorder(context) { companion object { private val SAMPLE_RATE = SampleRate.Rate48kHz private const val BITRATE = 24 * 1024 } - private val outputDirectory: File by lazy { ensureAudioDirectory(context) } - private var outputFile: File? = null - private val recorderScope = CoroutineScope(coroutineContext) private var recordingJob: Job? = null @@ -65,6 +58,8 @@ class VoiceRecorderL( private var bufferSizeInShorts = 0 private var maxAmplitude = 0 + override val fileNameExt: String = "ogg" + private fun initializeCodec(filePath: String) { codec.init(filePath, SAMPLE_RATE) codec.setBitrate(BITRATE) @@ -86,19 +81,10 @@ class VoiceRecorderL( } } - override fun initializeRecord(attachmentData: ContentAttachmentData) { - outputFile = attachmentData.findVoiceFile(outputDirectory) - } - override fun startRecord(roomId: String) { - val fileName = "${UUID.randomUUID()}.ogg" - val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply { - mkdirs() + outputFile = createOutputFile(roomId).also { + initializeCodec(it.absolutePath) } - val outputFile = File(outputDirectoryForRoom, fileName) - this.outputFile = outputFile - - initializeCodec(outputFile.absolutePath) recordingJob = recorderScope.launch { audioRecorder?.startRecording() @@ -140,19 +126,10 @@ class VoiceRecorderL( codec.release() } - override fun cancelRecord() { - outputFile?.delete() - outputFile = null - } - override fun getMaxAmplitude(): Int { return maxAmplitude } - override fun getVoiceMessageFile(): File? { - return outputFile - } - private fun createAudioRecord() { val channelConfig = AudioFormat.CHANNEL_IN_MONO val format = AudioFormat.ENCODING_PCM_16BIT diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt index 1eb850b8f5..f128673e27 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderQ.kt @@ -20,26 +20,17 @@ import android.content.Context import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi -import org.matrix.android.sdk.api.extensions.tryOrNull /** - * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. It uses the native OPUS support on Android 10+. + * VoiceRecorder to be used on Android versions >= [Build.VERSION_CODES.Q]. + * It uses the native OPUS support on Android 10+. */ @RequiresApi(Build.VERSION_CODES.Q) -class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") { +class VoiceRecorderQ(context: Context) : AbstractVoiceRecorderQ(context) { - override fun pauseRecord() { - // Can throw when the record is less than 1 second. - tryOrNull { mediaRecorder?.pause() } - } + // We can directly use OGG here + override val outputFormat = MediaRecorder.OutputFormat.OGG + override val audioEncoder = MediaRecorder.AudioEncoder.OPUS - override fun resumeRecord() { - mediaRecorder?.resume() - } - - override fun setOutputFormat(mediaRecorder: MediaRecorder) { - // We can directly use OGG here - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG) - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS) - } + override val fileNameExt: String = "ogg" } From ad2bf8d1cefe9da56afa764509594ef45a8d2bf0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:45:01 +0200 Subject: [PATCH 05/20] Add VoiceBroadcastRecorder --- .../java/im/vector/app/core/di/VoiceModule.kt | 40 +++++++++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 83 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/di/VoiceModule.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt diff --git a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt new file mode 100644 index 0000000000..992ed80677 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 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 im.vector.app.core.di + +import android.content.Context +import android.os.Build +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object VoiceModule { + @Provides + @Singleton + fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + VoiceBroadcastRecorder(context) + } else { + null + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt new file mode 100644 index 0000000000..2ec882a3d6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.voicebroadcast + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import java.io.File + +@RequiresApi(Build.VERSION_CODES.Q) +class VoiceBroadcastRecorder( + context: Context, +) : AbstractVoiceRecorderQ(context) { + + private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + + var listener: Listener? = null + + override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 + override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC + + override val fileNameExt: String = "mp4" + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder?.setMaxFileSize(maxFileSize) + mediaRecorder?.setOnInfoListener { _, what, _ -> + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() + else -> Unit // Nothing to do + } + } + } + + override fun stopRecord() { + super.stopRecord() + notifyOutputFileCreated() + listener = null + } + + override fun release() { + mediaRecorder?.setOnInfoListener(null) + super.release() + } + + private fun onMaxFileSizeApproaching(roomId: String) { + setNextOutputFile(roomId) + } + + private fun onNextOutputFileStarted() { + notifyOutputFileCreated() + } + + private fun notifyOutputFileCreated() { + outputFile?.let { + listener?.onVoiceMessageCreated(it) + outputFile = nextOutputFile + nextOutputFile = null + } + } + + fun interface Listener { + fun onVoiceMessageCreated(file: File) + } +} From 3ad245db8bbc5e9c5254909bc073ac0515e82d4e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 13 Oct 2022 23:45:49 +0200 Subject: [PATCH 06/20] Trigger VoiceBroadcast recording actions --- .../usecase/PauseVoiceBroadcastUseCase.kt | 6 +-- .../usecase/ResumeVoiceBroadcastUseCase.kt | 6 +-- .../usecase/StartVoiceBroadcastUseCase.kt | 46 ++++++++++++++++--- .../usecase/StopVoiceBroadcastUseCase.kt | 9 ++-- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 6c779b7a50..1ba3f7aaa9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +32,7 @@ import javax.inject.Inject class PauseVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -66,6 +66,6 @@ class PauseVoiceBroadcastUseCase @Inject constructor( } private fun pauseRecording() { - audioMessageHelper.pauseRecording() + voiceBroadcastRecorder?.pauseRecord() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index fbbbc32612..69d3fe99a9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +32,7 @@ import javax.inject.Inject class ResumeVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -71,6 +71,6 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( } private fun resumeRecording() { - audioMessageHelper.resumeRecording() + voiceBroadcastRecorder?.resumeRecord() } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 03dbbed5e4..f2d1df0ea4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -16,22 +16,33 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper +import android.content.Context +import android.os.Build +import androidx.core.content.FileProvider +import im.vector.app.core.resources.BuildMeta +import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.lib.multipicker.utils.toMultiPickerAudioType import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber +import java.io.File import javax.inject.Inject class StartVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, + private val context: Context, + private val buildMeta: BuildMeta, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -55,7 +66,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") - room.stateService().sendStateEvent( + val eventId = room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( @@ -64,10 +75,33 @@ class StartVoiceBroadcastUseCase @Inject constructor( ).toContent() ) - startRecording(room) + startRecording(room, eventId) } - private fun startRecording(room: Room) { - audioMessageHelper.startRecording(room.roomId) + private fun startRecording(room: Room, eventId: String) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> + sendVoiceFile(room, file, eventId) + } + voiceBroadcastRecorder?.startRecord(room.roomId) + } + } + + private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String) { + val outputFileUri = FileProvider.getUriForFile( + context, + buildMeta.applicationId + ".fileProvider", + voiceMessageFile, + "Voice message.${voiceMessageFile.extension}" + ) + val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return + if (audioType.duration > 1000) { + room.sendService().sendMedia( + attachment = audioType.toContentAttachmentData(isVoiceMessage = true), + compressBeforeSending = false, + roomIds = emptySet(), + relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index ed868fced8..7f352fc591 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,8 +16,9 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.home.room.detail.composer.AudioMessageHelper +import android.os.Build import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -32,7 +33,7 @@ import javax.inject.Inject class StopVoiceBroadcastUseCase @Inject constructor( private val session: Session, - private val audioMessageHelper: AudioMessageHelper, + private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, ) { suspend fun execute(roomId: String): Result = runCatching { @@ -67,6 +68,8 @@ class StopVoiceBroadcastUseCase @Inject constructor( } private fun stopRecording() { - audioMessageHelper.stopRecording() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + voiceBroadcastRecorder?.stopRecord() + } } } From aecb66015d19728378ac9452f97d9cd2b2e1c97e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 08:45:25 +0200 Subject: [PATCH 07/20] Move Voice Broadcast constants into dedicated object --- .../java/im/vector/app/core/extensions/TimelineEvent.kt | 6 ++++-- .../timeline/action/CheckIfCanRedactEventUseCase.kt | 4 ++-- .../room/detail/timeline/factory/TimelineItemFactory.kt | 4 ++-- .../detail/timeline/helper/TimelineDisplayableEvents.kt | 4 ++-- .../room/detail/timeline/helper/TimelineEventsGroups.kt | 4 ++-- .../features/voicebroadcast/VoiceBroadcastConstants.kt | 7 +++++-- .../model/MessageVoiceBroadcastInfoContent.kt | 4 ++-- .../features/voicebroadcast/model/VoiceBroadcastEvent.kt | 8 ++++---- .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 6 +++--- .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 6 +++--- .../timeline/action/CheckIfCanRedactEventUseCaseTest.kt | 4 ++-- .../voicebroadcast/model/VoiceBroadcastEventTest.kt | 6 +++--- .../usecase/PauseVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/ResumeVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 6 +++--- .../usecase/StopVoiceBroadcastUseCaseTest.kt | 6 +++--- 18 files changed, 52 insertions(+), 47 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index cdb84387ce..5c3393416b 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -16,7 +16,7 @@ package im.vector.app.core.extensions -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean { fun TimelineEvent.getVectorLastMessageContent(): MessageContent? { // Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method return when (root.getClearType()) { - STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> { + (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + } else -> getLastMessageContent() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt index eda1929133..8cb82691d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.action import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -31,7 +31,7 @@ class CheckIfCanRedactEventUseCase @Inject constructor( val canRedactEventTypes: List = listOf( EventType.MESSAGE, EventType.STICKER, - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 0b8f95b4a1..31ff257214 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -21,7 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -89,7 +89,7 @@ class TimelineItemFactory @Inject constructor( // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params) - STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params) + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params) // Unhandled state event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 87844aba8e..2411cb3877 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.helper -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -52,7 +52,7 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index bd211a4513..13de456e84 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.TextUtils -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.extensions.orFalse @@ -59,7 +59,7 @@ class TimelineEventsGroups { val content = root.getClearContent() return when { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) - type == STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId + type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey else -> { null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index d7d74b08e9..8c005deb1f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -16,5 +16,8 @@ package im.vector.app.features.voicebroadcast -/** Voice Broadcast State Event. */ -const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" +object VoiceBroadcastConstants { + + /** Voice Broadcast State Event. */ + const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index b33d6cc4da..7e4a3d04be 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -18,7 +18,7 @@ package im.vector.app.features.voicebroadcast.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon import timber.log.Timber /** - * Content of the state event of type [STATE_ROOM_VOICE_BROADCAST_INFO]. + * Content of the state event of type [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO]. * * It contains general info related to a voice broadcast. */ diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt index c09a5712a8..d464a253d3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -16,14 +16,14 @@ package im.vector.app.features.voicebroadcast.model -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent /** - * [Event] wrapper for [STATE_ROOM_VOICE_BROADCAST_INFO] event type. + * [Event] wrapper for [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] event type. * Provides additional fields and functions related to voice broadcast. */ @JvmInline @@ -50,6 +50,6 @@ value class VoiceBroadcastEvent(val root: Event) { } /** - * Map a [STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. + * Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. */ -fun Event.asVoiceBroadcastEvent() = if (type == STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null +fun Event.asVoiceBroadcastEvent() = if (type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 1ba3f7aaa9..835a57c102 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -41,7 +41,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -54,7 +54,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 69d3fe99a9..2f03d4194c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -41,7 +41,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -59,7 +59,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index f2d1df0ea4..6be8e27345 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -21,7 +21,7 @@ import android.os.Build import androidx.core.content.FileProvider import im.vector.app.core.resources.BuildMeta import im.vector.app.features.attachments.toContentAttachmentData -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -51,7 +51,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( - setOf(STATE_ROOM_VOICE_BROADCAST_INFO), + setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), QueryStringValue.IsNotEmpty ) .mapNotNull { it.asVoiceBroadcastEvent() } @@ -67,7 +67,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") val eventId = room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 7f352fc591..85ffde0d02 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -17,7 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import android.os.Build -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState @@ -42,7 +42,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId) )?.asVoiceBroadcastEvent() when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { @@ -56,7 +56,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event") room.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( relatesTo = reference, diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt index fcb052cb2b..e2157c3af0 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.action -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.test.fakes.FakeActiveSessionHolder import io.mockk.mockk import org.amshove.kluent.shouldBe @@ -35,7 +35,7 @@ class CheckIfCanRedactEventUseCaseTest { @Test fun `given an event which can be redacted and owned by user when use case executes then the result is true`() { - val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, STATE_ROOM_VOICE_BROADCAST_INFO) + + val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO canRedactEventTypes.forEach { eventType -> diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt index 8865e870f0..8c3d24342f 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.model -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldNotBeNull @@ -48,7 +48,7 @@ class VoiceBroadcastEventTest { ) val event = Event( eventId = AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, content = content.toContent(), ) val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId) @@ -71,7 +71,7 @@ class VoiceBroadcastEventTest { relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), ) val event = Event( - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, content = content.toContent(), ) val expectedReference = content.relatesTo diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt index 3139f20cd4..7d950d1c73 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class PauseVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class PauseVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt index 23d506482b..215969a65d 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class ResumeVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class ResumeVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 398d6fedf0..a0336800ca 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -81,7 +81,7 @@ class StartVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -106,7 +106,7 @@ class StartVoiceBroadcastUseCaseTest { private fun givenAVoiceBroadcasts(voiceBroadcasts: List) { val events = voiceBroadcasts.map { Event( - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = it.userId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = it.state.value diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt index aa8dcddf30..dd69f12a4c 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.voicebroadcast.usecase -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -80,7 +80,7 @@ class StopVoiceBroadcastUseCaseTest { // Then coVerify { fakeRoom.stateService().sendStateEvent( - eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, body = any(), ) @@ -114,7 +114,7 @@ class StopVoiceBroadcastUseCaseTest { val event = state?.let { Event( eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, - type = STATE_ROOM_VOICE_BROADCAST_INFO, + type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = fakeSession.myUserId, content = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = state.value, From ad730d55c1ed42c99ad71cbd3a22dcf8362ee09d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 08:56:14 +0200 Subject: [PATCH 08/20] Fix tests --- .../usecase/PauseVoiceBroadcastUseCaseTest.kt | 5 ++++- .../usecase/ResumeVoiceBroadcastUseCaseTest.kt | 5 ++++- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 11 ++++++++++- .../usecase/StopVoiceBroadcastUseCaseTest.kt | 5 ++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt index 7d950d1c73..5c42b26c54 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class PauseVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is paused or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt index 215969a65d..a1bc3a04ec 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class ResumeVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index a0336800ca..9fa6b7a450 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -17,14 +17,17 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeRoom import im.vector.app.test.fakes.FakeRoomService import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +47,13 @@ class StartVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase( + fakeSession, + fakeVoiceBroadcastRecorder, + FakeContext().instance, + mockk() + ) @Test fun `given a room id with potential several existing voice broadcast states when calling execute then the voice broadcast is started or not`() = runTest { diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt index dd69f12a4c..ee6b141bd9 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.test.fakes.FakeRoom @@ -25,6 +26,7 @@ import im.vector.app.test.fakes.FakeSession import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe @@ -44,7 +46,8 @@ class StopVoiceBroadcastUseCaseTest { private val fakeRoom = FakeRoom() private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) - private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession) + private val fakeVoiceBroadcastRecorder = mockk(relaxed = true) + private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder) @Test fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is stopped or not`() = runTest { From c492fda00028a170bc24504d39239b8a64380b5d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 10:24:28 +0200 Subject: [PATCH 09/20] Change VoiceBroadcastRecorder as Interface --- .../java/im/vector/app/core/di/VoiceModule.kt | 3 +- .../voicebroadcast/VoiceBroadcastRecorder.kt | 60 +------------- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 78 +++++++++++++++++++ 3 files changed, 83 insertions(+), 58 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt diff --git a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt index 992ed80677..54d556ea91 100644 --- a/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VoiceModule.kt @@ -23,6 +23,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder +import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ import javax.inject.Singleton @Module @@ -32,7 +33,7 @@ object VoiceModule { @Singleton fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - VoiceBroadcastRecorder(context) + VoiceBroadcastRecorderQ(context) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 2ec882a3d6..916f39106a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -16,66 +16,12 @@ package im.vector.app.features.voicebroadcast -import android.content.Context -import android.media.MediaRecorder -import android.os.Build -import androidx.annotation.RequiresApi -import im.vector.app.features.voice.AbstractVoiceRecorderQ -import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import im.vector.app.features.voice.VoiceRecorder import java.io.File -@RequiresApi(Build.VERSION_CODES.Q) -class VoiceBroadcastRecorder( - context: Context, -) : AbstractVoiceRecorderQ(context) { +interface VoiceBroadcastRecorder : VoiceRecorder { - private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s - - var listener: Listener? = null - - override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 - override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC - - override val fileNameExt: String = "mp4" - - override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { - super.initializeRecord(roomId, attachmentData) - mediaRecorder?.setMaxFileSize(maxFileSize) - mediaRecorder?.setOnInfoListener { _, what, _ -> - when (what) { - MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) - MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() - else -> Unit // Nothing to do - } - } - } - - override fun stopRecord() { - super.stopRecord() - notifyOutputFileCreated() - listener = null - } - - override fun release() { - mediaRecorder?.setOnInfoListener(null) - super.release() - } - - private fun onMaxFileSizeApproaching(roomId: String) { - setNextOutputFile(roomId) - } - - private fun onNextOutputFileStarted() { - notifyOutputFileCreated() - } - - private fun notifyOutputFileCreated() { - outputFile?.let { - listener?.onVoiceMessageCreated(it) - outputFile = nextOutputFile - nextOutputFile = null - } - } + var listener: Listener? fun interface Listener { fun onVoiceMessageCreated(file: File) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt new file mode 100644 index 0000000000..3aaad19a47 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.voicebroadcast + +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import androidx.annotation.RequiresApi +import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.session.content.ContentAttachmentData + +@RequiresApi(Build.VERSION_CODES.Q) +class VoiceBroadcastRecorderQ( + context: Context, +) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { + + private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + + override var listener: VoiceBroadcastRecorder.Listener? = null + + override val outputFormat = MediaRecorder.OutputFormat.MPEG_4 + override val audioEncoder = MediaRecorder.AudioEncoder.HE_AAC + + override val fileNameExt: String = "mp4" + + override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) { + super.initializeRecord(roomId, attachmentData) + mediaRecorder?.setMaxFileSize(maxFileSize) + mediaRecorder?.setOnInfoListener { _, what, _ -> + when (what) { + MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING -> onMaxFileSizeApproaching(roomId) + MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED -> onNextOutputFileStarted() + else -> Unit // Nothing to do + } + } + } + + override fun stopRecord() { + super.stopRecord() + notifyOutputFileCreated() + listener = null + } + + override fun release() { + mediaRecorder?.setOnInfoListener(null) + super.release() + } + + private fun onMaxFileSizeApproaching(roomId: String) { + setNextOutputFile(roomId) + } + + private fun onNextOutputFileStarted() { + notifyOutputFileCreated() + } + + private fun notifyOutputFileCreated() { + outputFile?.let { + listener?.onVoiceMessageCreated(it) + outputFile = nextOutputFile + nextOutputFile = null + } + } +} From 06bceef7c0f528927b1305ed5d046ca8a98fd4f0 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 10:52:28 +0200 Subject: [PATCH 10/20] Add changelog --- changelog.d/7363.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7363.wip diff --git a/changelog.d/7363.wip b/changelog.d/7363.wip new file mode 100644 index 0000000000..b5a5f4c352 --- /dev/null +++ b/changelog.d/7363.wip @@ -0,0 +1 @@ +[Voice Broadcast] Record and send not aggregated voice messages to the room From fb9c747a20e8c1adb42bbe156fd915ffe2c5e0da Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 11:35:53 +0200 Subject: [PATCH 11/20] Reformat and add trailing commas --- .../sdk/api/session/room/send/SendService.kt | 6 +-- .../room/send/LocalEchoEventFactory.kt | 49 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 7bd6337548..53b49129c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -72,7 +72,7 @@ interface SendService { text: String, formattedText: String? = null, autoMarkdown: Boolean, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, ): Cancelable /** @@ -90,7 +90,7 @@ interface SendService { compressBeforeSending: Boolean, roomIds: Set, rootThreadEventId: String? = null, - relatesTo: RelationDefaultContent? = null + relatesTo: RelationDefaultContent? = null, ): Cancelable /** @@ -106,7 +106,7 @@ interface SendService { attachments: List, compressBeforeSending: Boolean, roomIds: Set, - rootThreadEventId: String? = null + rootThreadEventId: String? = null, ): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 0fb2c9506a..1d7f624eba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -127,7 +127,7 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, ): Event { val content = if (newBodyFormattedText != null) { TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType) @@ -148,7 +148,7 @@ internal class LocalEchoEventFactory @Inject constructor( private fun createPollContent( question: String, options: List, - pollType: PollType + pollType: PollType, ): MessagePollContent { return MessagePollContent( unstablePollCreationInfo = PollCreationInfo( @@ -166,7 +166,7 @@ internal class LocalEchoEventFactory @Inject constructor( pollType: PollType, targetEventId: String, question: String, - options: List + options: List, ): Event { val newContent = MessagePollContent( relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), @@ -186,7 +186,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createPollReplyEvent( roomId: String, pollEventId: String, - answerId: String + answerId: String, ): Event { val content = MessagePollResponseContent( body = answerId, @@ -212,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, pollType: PollType, question: String, - options: List + options: List, ): Event { val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() @@ -229,7 +229,7 @@ internal class LocalEchoEventFactory @Inject constructor( fun createEndPollEvent( roomId: String, - eventId: String + eventId: String, ): Event { val content = MessageEndPollContent( relatesTo = RelationDefaultContent( @@ -254,7 +254,7 @@ internal class LocalEchoEventFactory @Inject constructor( latitude: Double, longitude: Double, uncertainty: Double?, - isUserLocation: Boolean + isUserLocation: Boolean, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN @@ -274,7 +274,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, latitude: Double, longitude: Double, - uncertainty: Double? + uncertainty: Double?, ): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) val content = MessageBeaconLocationDataContent( @@ -305,7 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyText: String, autoMarkdown: Boolean, msgType: String, - compatibilityText: String + compatibilityText: String, ): Event { val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" @@ -359,7 +359,7 @@ internal class LocalEchoEventFactory @Inject constructor( attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId, - relatesTo + relatesTo, ) ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo) } @@ -385,7 +385,12 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createImageEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { var width = attachment.width var height = attachment.height @@ -415,7 +420,12 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createVideoEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -478,7 +488,12 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?, relatesTo: RelationDefaultContent?): Event { + private fun createFileEvent( + roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String?, + relatesTo: RelationDefaultContent?, + ): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -539,7 +554,7 @@ internal class LocalEchoEventFactory @Inject constructor( text: CharSequence, msgType: String, autoMarkdown: Boolean, - formattedText: String? + formattedText: String?, ): Event { val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown) return createEvent( @@ -568,7 +583,7 @@ internal class LocalEchoEventFactory @Inject constructor( replyTextFormatted: CharSequence?, autoMarkdown: Boolean, rootThreadEventId: String? = null, - showInThread: Boolean + showInThread: Boolean, ): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null @@ -614,7 +629,7 @@ internal class LocalEchoEventFactory @Inject constructor( type = RelationType.THREAD, eventId = rootThreadEventId, isFallingBack = true, - inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)) + inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)), ) /** @@ -760,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor( text: String, formattedText: String?, autoMarkdown: Boolean, - rootThreadEventId: String? + rootThreadEventId: String?, ): Event { val messageContent = quotedEvent.getLastMessageContent() val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body } From 64e6a2bfaba5aee8448a6e54617fd4b2d60e97c6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 12:23:02 +0200 Subject: [PATCH 12/20] Compute file size from chunk length --- .../voicebroadcast/VoiceBroadcastConstants.kt | 3 +++ .../voicebroadcast/VoiceBroadcastRecorder.kt | 2 ++ .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 7 ++++++- .../model/MessageVoiceBroadcastInfoContent.kt | 2 +- .../usecase/StartVoiceBroadcastUseCase.kt | 16 +++++++--------- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 8c005deb1f..07bd94ca8c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -20,4 +20,7 @@ object VoiceBroadcastConstants { /** Voice Broadcast State Event. */ const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" + + /** Default voice broadcast chunk duration, in seconds */ + const val DEFAULT_CHUNK_LENGTH = 5 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 916f39106a..2668501a8d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -23,6 +23,8 @@ interface VoiceBroadcastRecorder : VoiceRecorder { var listener: Listener? + fun startRecord(roomId: String, chunkLength: Int) + fun interface Listener { fun onVoiceMessageCreated(file: File) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 3aaad19a47..fe8222e840 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -28,7 +28,7 @@ class VoiceBroadcastRecorderQ( context: Context, ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { - private val maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + private var maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s override var listener: VoiceBroadcastRecorder.Listener? = null @@ -49,6 +49,11 @@ class VoiceBroadcastRecorderQ( } } + override fun startRecord(roomId: String, chunkLength: Int) { + maxFileSize = (chunkLength * 0.004166).toLong() // TODO change this approximate conversion + startRecord(roomId) + } + override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index 7e4a3d04be..5044bb5c34 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -41,7 +41,7 @@ data class MessageVoiceBroadcastInfoContent( /** The [VoiceBroadcastState] value. **/ @Json(name = "state") val voiceBroadcastStateStr: String = "", /** The length of the voice chunks in seconds. **/ - @Json(name = "chunk_length") val chunkLength: Long? = null, + @Json(name = "chunk_length") val chunkLength: Int? = null, ) : MessageContent { val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 6be8e27345..64f1dfb317 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -17,7 +17,6 @@ package im.vector.app.features.voicebroadcast.usecase import android.content.Context -import android.os.Build import androidx.core.content.FileProvider import im.vector.app.core.resources.BuildMeta import im.vector.app.features.attachments.toContentAttachmentData @@ -66,25 +65,24 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") + val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH // Todo Get the length from the room settings val eventId = room.stateService().sendStateEvent( eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, - chunkLength = 5L, // TODO Get length from voice broadcast settings + chunkLength = chunkLength, ).toContent() ) - startRecording(room, eventId) + startRecording(room, eventId, chunkLength) } - private fun startRecording(room: Room, eventId: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> - sendVoiceFile(room, file, eventId) - } - voiceBroadcastRecorder?.startRecord(room.roomId) + private fun startRecording(room: Room, eventId: String, chunkLength: Int) { + voiceBroadcastRecorder?.listener = VoiceBroadcastRecorder.Listener { file -> + sendVoiceFile(room, file, eventId) } + voiceBroadcastRecorder?.startRecord(room.roomId, chunkLength) } private fun sendVoiceFile(room: Room, voiceMessageFile: File, referenceEventId: String) { From 9d35e81db7a8e3b7206b2afadc94435863145c99 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:37:59 +0200 Subject: [PATCH 13/20] Compute max file size from chunk length --- .../im/vector/app/features/voice/AbstractVoiceRecorderQ.kt | 2 +- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt index bd30c38366..428b3c578b 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -34,8 +34,8 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo protected var nextOutputFile: File? = null private val audioSource: Int = MediaRecorder.AudioSource.DEFAULT - private val audioEncodingBitRate: Int = 24_000 private val audioSamplingRate: Int = 48_000 + protected val audioEncodingBitRate: Int = 24_000 abstract val outputFormat: Int // see MediaRecorder.OutputFormat abstract val audioEncoder: Int // see MediaRecorder.AudioEncoder diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 07bd94ca8c..83d83b167e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -22,5 +22,5 @@ object VoiceBroadcastConstants { const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" /** Default voice broadcast chunk duration, in seconds */ - const val DEFAULT_CHUNK_LENGTH = 5 + const val DEFAULT_CHUNK_LENGTH = 30 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index fe8222e840..620db721c9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -28,7 +28,7 @@ class VoiceBroadcastRecorderQ( context: Context, ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { - private var maxFileSize = 25_000L // 0,025 Mb = 25 Kb ~= 6s + private var maxFileSize = 0L // zero or negative for no limit override var listener: VoiceBroadcastRecorder.Listener? = null @@ -50,7 +50,7 @@ class VoiceBroadcastRecorderQ( } override fun startRecord(roomId: String, chunkLength: Int) { - maxFileSize = (chunkLength * 0.004166).toLong() // TODO change this approximate conversion + maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() startRecord(roomId) } From 62596b38c78e4bc569777ce2371f43ea1064bb0f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:40:22 +0200 Subject: [PATCH 14/20] Pause recording when the composer is not visible anymore --- .../detail/composer/MessageComposerFragment.kt | 16 ++++++++++++---- .../detail/composer/MessageComposerViewModel.kt | 15 +++++++++++++++ .../detail/composer/MessageComposerViewState.kt | 9 +++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index b3abfa480e..4721b81571 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -227,10 +227,18 @@ class MessageComposerFragment : VectorBaseFragment(), A override fun onPause() { super.onPause() - if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { - // we're rotating, maintain any active recordings - } else { - messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) + withState(messageComposerViewModel) { + when { + it.isVoiceRecording && requireActivity().isChangingConfigurations -> { + // we're rotating, maintain any active recordings + } + // TODO remove this when there will be a recording indicator outside of the timeline + // Pause voice broadcast if the timeline is not shown anymore + it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Pause) + else -> { + messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 6f1210a584..eef06d11b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.composer +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -40,8 +41,11 @@ import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -90,6 +94,7 @@ class MessageComposerViewModel @AssistedInject constructor( init { loadDraftIfAny() observePowerLevelAndEncryption() + observeVoiceBroadcast() subscribeToStateInternal() } @@ -182,6 +187,16 @@ class MessageComposerViewModel @AssistedInject constructor( } } + private fun observeVoiceBroadcast() { + room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + .asFlow() + .unwrap() + .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .setOnEach { + copy(voiceBroadcastState = it) + } + } + private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) { room.getTimelineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 47a7122584..0df1dbebd8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer import com.airbnb.mvrx.MavericksState import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import kotlin.random.Random @@ -67,6 +68,7 @@ data class MessageComposerViewState( val startsThread: Boolean = false, val sendMode: SendMode = SendMode.Regular("", false), val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle, + val voiceBroadcastState: VoiceBroadcastState? = null, val text: CharSequence? = null, ) : MavericksState { @@ -77,6 +79,13 @@ data class MessageComposerViewState( is VoiceMessageRecorderView.RecordingUiState.Recording -> true } + val isVoiceBroadcasting = when (voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED -> true + else -> false + } + val isVoiceMessageIdle = !isVoiceRecording val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording From 3a951f207604d638bb7ab66453e4d4162e84b32b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Oct 2022 16:49:27 +0200 Subject: [PATCH 15/20] Add punctuation to kdoc --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index 83d83b167e..bd37251dd4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -21,6 +21,6 @@ object VoiceBroadcastConstants { /** Voice Broadcast State Event. */ const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" - /** Default voice broadcast chunk duration, in seconds */ + /** Default voice broadcast chunk duration, in seconds. */ const val DEFAULT_CHUNK_LENGTH = 30 } From 1db3d69aebf20d14faf223ee0f0da5648b58e769 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 08:58:21 +0200 Subject: [PATCH 16/20] Change chunk_length type in unit test --- .../features/voicebroadcast/model/VoiceBroadcastEventTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt index 8c3d24342f..ca2e8320c9 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent private const val AN_EVENT_ID = "event_id" private const val A_REFERENCED_EVENT_ID = "event_id_ref" -private const val A_CHUNK_LENGTH = 3_600L +private const val A_CHUNK_LENGTH = 30 class VoiceBroadcastEventTest { From 400118ed3ec8eb19bb4ba6011f5b3f5c6c0816b7 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:03:57 +0200 Subject: [PATCH 17/20] Remove useless Android API checks --- .../app/features/voice/AbstractVoiceRecorderQ.kt | 12 ++---------- .../usecase/StopVoiceBroadcastUseCase.kt | 5 +---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt index 428b3c578b..0d8373870f 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorderQ.kt @@ -91,11 +91,7 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo fun setNextOutputFile(roomId: String) { val mediaRecorder = mediaRecorder ?: return nextOutputFile = createOutputFile(roomId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mediaRecorder.setNextOutputFile(nextOutputFile) - } else { - mediaRecorder.setNextOutputFile(nextOutputFile?.outputStream()?.fd) - } + mediaRecorder.setNextOutputFile(nextOutputFile) } private fun createMediaRecorder(): MediaRecorder { @@ -115,10 +111,6 @@ abstract class AbstractVoiceRecorderQ(private val context: Context) : AbstractVo private fun setOutputFile(roomId: String) { val mediaRecorder = mediaRecorder ?: return outputFile = outputFile ?: createOutputFile(roomId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mediaRecorder.setOutputFile(outputFile) - } else { - mediaRecorder.setOutputFile(outputFile?.outputStream()?.fd) - } + mediaRecorder.setOutputFile(outputFile) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 85ffde0d02..6eefa06979 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -16,7 +16,6 @@ package im.vector.app.features.voicebroadcast.usecase -import android.os.Build import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent @@ -68,8 +67,6 @@ class StopVoiceBroadcastUseCase @Inject constructor( } private fun stopRecording() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - voiceBroadcastRecorder?.stopRecord() - } + voiceBroadcastRecorder?.stopRecord() } } From def9fc07bbd5b8404bd0d70dd03d0bcfaa03c555 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:08:17 +0200 Subject: [PATCH 18/20] Revert AudioMessageHelper.pauseRecording --- .../room/detail/composer/AudioMessageHelper.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index d98240904c..bede02c17f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -114,13 +114,9 @@ class AudioMessageHelper @Inject constructor( * When entering in playback mode actually. */ fun pauseRecording() { - voiceRecorder.pauseRecord() - pauseRecordingAmplitudes() - } - - fun resumeRecording() { - voiceRecorder.resumeRecord() - resumeRecordingAmplitudes() + // TODO should we pause instead of stop? + voiceRecorder.stopRecord() + stopRecordingAmplitudes() } fun deleteRecording() { @@ -226,10 +222,6 @@ class AudioMessageHelper @Inject constructor( } } - private fun pauseRecordingAmplitudes() { - amplitudeTicker?.pause() - } - private fun resumeRecordingAmplitudes() { amplitudeTicker?.resume() } From 92bd8cdcfe71bf01f780f14bf1a41c0765c5b977 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:09:48 +0200 Subject: [PATCH 19/20] Voice Broadcast - Remove check on voice message minimum duration --- .../usecase/StartVoiceBroadcastUseCase.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 64f1dfb317..81a98f6fce 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -93,13 +93,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( "Voice message.${voiceMessageFile.extension}" ) val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return - if (audioType.duration > 1000) { - room.sendService().sendMedia( - attachment = audioType.toContentAttachmentData(isVoiceMessage = true), - compressBeforeSending = false, - roomIds = emptySet(), - relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) - ) - } + room.sendService().sendMedia( + attachment = audioType.toContentAttachmentData(isVoiceMessage = true), + compressBeforeSending = false, + roomIds = emptySet(), + relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId) + ) } } From b9335c60657a114104a62e7a8a59e9ccb0b28c6e Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 18 Oct 2022 13:11:44 +0200 Subject: [PATCH 20/20] Rename const DEFAULT_CHUNK_LENGTH_IN_SECONDS --- .../app/features/voicebroadcast/VoiceBroadcastConstants.kt | 2 +- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt index bd37251dd4..3a9aac12d5 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -22,5 +22,5 @@ object VoiceBroadcastConstants { const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" /** Default voice broadcast chunk duration, in seconds. */ - const val DEFAULT_CHUNK_LENGTH = 30 + const val DEFAULT_CHUNK_LENGTH_IN_SECONDS = 30 } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 81a98f6fce..2a306bcd28 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -65,7 +65,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( private suspend fun startVoiceBroadcast(room: Room) { Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") - val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH // Todo Get the length from the room settings + val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH_IN_SECONDS // Todo Get the length from the room settings val eventId = room.stateService().sendStateEvent( eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId,