mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-29 14:38:45 +03:00
Merge pull request #7363 from vector-im/feature/fre/voice_broadcast_start_record
Voice Broadcast - Start record
This commit is contained in:
commit
b67500515c
34 changed files with 581 additions and 226 deletions
1
changelog.d/7363.wip
Normal file
1
changelog.d/7363.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[Voice Broadcast] Record and send not aggregated voice messages to the room
|
|
@ -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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
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.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.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ interface SendService {
|
||||||
text: String,
|
text: String,
|
||||||
formattedText: String? = null,
|
formattedText: String? = null,
|
||||||
autoMarkdown: Boolean,
|
autoMarkdown: Boolean,
|
||||||
rootThreadEventId: String? = null
|
rootThreadEventId: String? = null,
|
||||||
): Cancelable
|
): 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.
|
* @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
|
* 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 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]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendMedia(
|
fun sendMedia(
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
compressBeforeSending: Boolean,
|
compressBeforeSending: Boolean,
|
||||||
roomIds: Set<String>,
|
roomIds: Set<String>,
|
||||||
rootThreadEventId: String? = null
|
rootThreadEventId: String? = null,
|
||||||
|
relatesTo: RelationDefaultContent? = null,
|
||||||
): Cancelable
|
): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +106,7 @@ interface SendService {
|
||||||
attachments: List<ContentAttachmentData>,
|
attachments: List<ContentAttachmentData>,
|
||||||
compressBeforeSending: Boolean,
|
compressBeforeSending: Boolean,
|
||||||
roomIds: Set<String>,
|
roomIds: Set<String>,
|
||||||
rootThreadEventId: String? = null
|
rootThreadEventId: String? = null,
|
||||||
): Cancelable
|
): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.MessageWithAttachmentContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
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.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.SendService
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
@ -280,7 +281,8 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
compressBeforeSending: Boolean,
|
compressBeforeSending: Boolean,
|
||||||
roomIds: Set<String>,
|
roomIds: Set<String>,
|
||||||
rootThreadEventId: String?
|
rootThreadEventId: String?,
|
||||||
|
relatesTo: RelationDefaultContent?,
|
||||||
): Cancelable {
|
): Cancelable {
|
||||||
// Ensure that the event will not be send in a thread if we are a different flow.
|
// Ensure that the event will not be send in a thread if we are a different flow.
|
||||||
// Like sending files to multiple rooms
|
// Like sending files to multiple rooms
|
||||||
|
@ -295,7 +297,8 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
localEchoEventFactory.createMediaEvent(
|
localEchoEventFactory.createMediaEvent(
|
||||||
roomId = it,
|
roomId = it,
|
||||||
attachment = attachment,
|
attachment = attachment,
|
||||||
rootThreadEventId = rootThreadId
|
rootThreadEventId = rootThreadId,
|
||||||
|
relatesTo,
|
||||||
).also { event ->
|
).also { event ->
|
||||||
createLocalEcho(event)
|
createLocalEcho(event)
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
newBodyFormattedText: CharSequence?,
|
newBodyFormattedText: CharSequence?,
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
compatibilityText: String
|
compatibilityText: String,
|
||||||
): Event {
|
): Event {
|
||||||
val content = if (newBodyFormattedText != null) {
|
val content = if (newBodyFormattedText != null) {
|
||||||
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
|
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
|
||||||
|
@ -148,7 +148,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
private fun createPollContent(
|
private fun createPollContent(
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>,
|
options: List<String>,
|
||||||
pollType: PollType
|
pollType: PollType,
|
||||||
): MessagePollContent {
|
): MessagePollContent {
|
||||||
return MessagePollContent(
|
return MessagePollContent(
|
||||||
unstablePollCreationInfo = PollCreationInfo(
|
unstablePollCreationInfo = PollCreationInfo(
|
||||||
|
@ -166,7 +166,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
pollType: PollType,
|
pollType: PollType,
|
||||||
targetEventId: String,
|
targetEventId: String,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>
|
options: List<String>,
|
||||||
): Event {
|
): Event {
|
||||||
val newContent = MessagePollContent(
|
val newContent = MessagePollContent(
|
||||||
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
||||||
|
@ -186,7 +186,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
fun createPollReplyEvent(
|
fun createPollReplyEvent(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
pollEventId: String,
|
pollEventId: String,
|
||||||
answerId: String
|
answerId: String,
|
||||||
): Event {
|
): Event {
|
||||||
val content = MessagePollResponseContent(
|
val content = MessagePollResponseContent(
|
||||||
body = answerId,
|
body = answerId,
|
||||||
|
@ -212,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
pollType: PollType,
|
pollType: PollType,
|
||||||
question: String,
|
question: String,
|
||||||
options: List<String>
|
options: List<String>,
|
||||||
): Event {
|
): Event {
|
||||||
val content = createPollContent(question, options, pollType)
|
val content = createPollContent(question, options, pollType)
|
||||||
val localId = LocalEcho.createLocalEchoId()
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
|
@ -229,7 +229,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
|
|
||||||
fun createEndPollEvent(
|
fun createEndPollEvent(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
eventId: String
|
eventId: String,
|
||||||
): Event {
|
): Event {
|
||||||
val content = MessageEndPollContent(
|
val content = MessageEndPollContent(
|
||||||
relatesTo = RelationDefaultContent(
|
relatesTo = RelationDefaultContent(
|
||||||
|
@ -254,7 +254,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
latitude: Double,
|
latitude: Double,
|
||||||
longitude: Double,
|
longitude: Double,
|
||||||
uncertainty: Double?,
|
uncertainty: Double?,
|
||||||
isUserLocation: Boolean
|
isUserLocation: Boolean,
|
||||||
): Event {
|
): Event {
|
||||||
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
|
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
|
||||||
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
|
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
|
||||||
|
@ -274,7 +274,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
latitude: Double,
|
latitude: Double,
|
||||||
longitude: Double,
|
longitude: Double,
|
||||||
uncertainty: Double?
|
uncertainty: Double?,
|
||||||
): Event {
|
): Event {
|
||||||
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
|
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
|
||||||
val content = MessageBeaconLocationDataContent(
|
val content = MessageBeaconLocationDataContent(
|
||||||
|
@ -305,7 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
newBodyText: String,
|
newBodyText: String,
|
||||||
autoMarkdown: Boolean,
|
autoMarkdown: Boolean,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
compatibilityText: String
|
compatibilityText: String,
|
||||||
): Event {
|
): Event {
|
||||||
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
|
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
|
||||||
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
|
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
|
||||||
|
@ -347,14 +347,21 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
fun createMediaEvent(
|
fun createMediaEvent(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
rootThreadEventId: String?
|
rootThreadEventId: String?,
|
||||||
|
relatesTo: RelationDefaultContent?,
|
||||||
): Event {
|
): Event {
|
||||||
return when (attachment.type) {
|
return when (attachment.type) {
|
||||||
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId)
|
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo)
|
||||||
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId)
|
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo)
|
||||||
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId)
|
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo)
|
||||||
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId)
|
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(
|
||||||
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId)
|
roomId,
|
||||||
|
attachment,
|
||||||
|
isVoiceMessage = true,
|
||||||
|
rootThreadEventId = rootThreadEventId,
|
||||||
|
relatesTo,
|
||||||
|
)
|
||||||
|
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +385,12 @@ 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 width = attachment.width
|
||||||
var height = attachment.height
|
var height = attachment.height
|
||||||
|
|
||||||
|
@ -403,19 +415,17 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString(),
|
url = attachment.queryUri.toString(),
|
||||||
relatesTo = rootThreadEventId?.let {
|
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.THREAD,
|
|
||||||
eventId = it,
|
|
||||||
isFallingBack = true,
|
|
||||||
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return createMessageEvent(roomId, content)
|
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()
|
val mediaDataRetriever = MediaMetadataRetriever()
|
||||||
mediaDataRetriever.setDataSource(context, attachment.queryUri)
|
mediaDataRetriever.setDataSource(context, attachment.queryUri)
|
||||||
|
|
||||||
|
@ -447,14 +457,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
thumbnailInfo = thumbnailInfo
|
thumbnailInfo = thumbnailInfo
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString(),
|
url = attachment.queryUri.toString(),
|
||||||
relatesTo = rootThreadEventId?.let {
|
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.THREAD,
|
|
||||||
eventId = it,
|
|
||||||
isFallingBack = true,
|
|
||||||
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return createMessageEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
@ -463,7 +466,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
isVoiceMessage: Boolean,
|
isVoiceMessage: Boolean,
|
||||||
rootThreadEventId: String?
|
rootThreadEventId: String?,
|
||||||
|
relatesTo: RelationDefaultContent?,
|
||||||
): Event {
|
): Event {
|
||||||
val content = MessageAudioContent(
|
val content = MessageAudioContent(
|
||||||
msgType = MessageType.MSGTYPE_AUDIO,
|
msgType = MessageType.MSGTYPE_AUDIO,
|
||||||
|
@ -479,19 +483,17 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
waveform = waveformSanitizer.sanitize(attachment.waveform)
|
waveform = waveformSanitizer.sanitize(attachment.waveform)
|
||||||
),
|
),
|
||||||
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
|
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
|
||||||
relatesTo = rootThreadEventId?.let {
|
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.THREAD,
|
|
||||||
eventId = it,
|
|
||||||
isFallingBack = true,
|
|
||||||
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return createMessageEvent(roomId, content)
|
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(
|
val content = MessageFileContent(
|
||||||
msgType = MessageType.MSGTYPE_FILE,
|
msgType = MessageType.MSGTYPE_FILE,
|
||||||
body = attachment.name ?: "file",
|
body = attachment.name ?: "file",
|
||||||
|
@ -500,14 +502,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
size = attachment.size
|
size = attachment.size
|
||||||
),
|
),
|
||||||
url = attachment.queryUri.toString(),
|
url = attachment.queryUri.toString(),
|
||||||
relatesTo = rootThreadEventId?.let {
|
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
|
||||||
RelationDefaultContent(
|
|
||||||
type = RelationType.THREAD,
|
|
||||||
eventId = it,
|
|
||||||
isFallingBack = true,
|
|
||||||
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return createMessageEvent(roomId, content)
|
return createMessageEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
@ -559,7 +554,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
text: CharSequence,
|
text: CharSequence,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
autoMarkdown: Boolean,
|
autoMarkdown: Boolean,
|
||||||
formattedText: String?
|
formattedText: String?,
|
||||||
): Event {
|
): Event {
|
||||||
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
|
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
|
||||||
return createEvent(
|
return createEvent(
|
||||||
|
@ -588,7 +583,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
replyTextFormatted: CharSequence?,
|
replyTextFormatted: CharSequence?,
|
||||||
autoMarkdown: Boolean,
|
autoMarkdown: Boolean,
|
||||||
rootThreadEventId: String? = null,
|
rootThreadEventId: String? = null,
|
||||||
showInThread: Boolean
|
showInThread: Boolean,
|
||||||
): Event? {
|
): Event? {
|
||||||
// Fallbacks and event representation
|
// Fallbacks and event representation
|
||||||
// TODO Add error/warning logs when any of this is null
|
// TODO Add error/warning logs when any of this is null
|
||||||
|
@ -629,6 +624,14 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
return createMessageEvent(roomId, content)
|
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.
|
* Generates the appropriate relatesTo object for a reply event.
|
||||||
* It can either be a regular reply or a reply within a thread
|
* It can either be a regular reply or a reply within a thread
|
||||||
|
@ -772,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
text: String,
|
text: String,
|
||||||
formattedText: String?,
|
formattedText: String?,
|
||||||
autoMarkdown: Boolean,
|
autoMarkdown: Boolean,
|
||||||
rootThreadEventId: String?
|
rootThreadEventId: String?,
|
||||||
): Event {
|
): Event {
|
||||||
val messageContent = quotedEvent.getLastMessageContent()
|
val messageContent = quotedEvent.getLastMessageContent()
|
||||||
val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
|
val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
|
||||||
|
|
41
vector/src/main/java/im/vector/app/core/di/VoiceModule.kt
Normal file
41
vector/src/main/java/im/vector/app/core/di/VoiceModule.kt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ
|
||||||
|
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) {
|
||||||
|
VoiceBroadcastRecorderQ(context)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.core.extensions
|
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 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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean {
|
||||||
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
|
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
|
||||||
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
|
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
|
||||||
return when (root.getClearType()) {
|
return when (root.getClearType()) {
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
|
||||||
|
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
}
|
||||||
else -> getLastMessageContent()
|
else -> getLastMessageContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,8 @@ class AudioMessageHelper @Inject constructor(
|
||||||
private var amplitudeTicker: CountUpTimer? = null
|
private var amplitudeTicker: CountUpTimer? = null
|
||||||
private var playbackTicker: CountUpTimer? = null
|
private var playbackTicker: CountUpTimer? = null
|
||||||
|
|
||||||
fun initializeRecorder(attachmentData: ContentAttachmentData) {
|
fun initializeRecorder(roomId: String, attachmentData: ContentAttachmentData) {
|
||||||
voiceRecorder.initializeRecord(attachmentData)
|
voiceRecorder.initializeRecord(roomId, attachmentData)
|
||||||
amplitudeList.clear()
|
amplitudeList.clear()
|
||||||
attachmentData.waveform?.let {
|
attachmentData.waveform?.let {
|
||||||
amplitudeList.addAll(it)
|
amplitudeList.addAll(it)
|
||||||
|
@ -114,6 +114,7 @@ class AudioMessageHelper @Inject constructor(
|
||||||
* When entering in playback mode actually.
|
* When entering in playback mode actually.
|
||||||
*/
|
*/
|
||||||
fun pauseRecording() {
|
fun pauseRecording() {
|
||||||
|
// TODO should we pause instead of stop?
|
||||||
voiceRecorder.stopRecord()
|
voiceRecorder.stopRecord()
|
||||||
stopRecordingAmplitudes()
|
stopRecordingAmplitudes()
|
||||||
}
|
}
|
||||||
|
@ -221,6 +222,10 @@ class AudioMessageHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resumeRecordingAmplitudes() {
|
||||||
|
amplitudeTicker?.resume()
|
||||||
|
}
|
||||||
|
|
||||||
private fun stopRecordingAmplitudes() {
|
private fun stopRecordingAmplitudes() {
|
||||||
amplitudeTicker?.stop()
|
amplitudeTicker?.stop()
|
||||||
amplitudeTicker = null
|
amplitudeTicker = null
|
||||||
|
|
|
@ -227,12 +227,20 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) {
|
withState(messageComposerViewModel) {
|
||||||
|
when {
|
||||||
|
it.isVoiceRecording && requireActivity().isChangingConfigurations -> {
|
||||||
// we're rotating, maintain any active recordings
|
// we're rotating, maintain any active recordings
|
||||||
} else {
|
}
|
||||||
|
// 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()))
|
messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.composer
|
package im.vector.app.features.home.room.detail.composer
|
||||||
|
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
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.powerlevel.PowerLevelsFlowFactory
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -90,6 +94,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
init {
|
init {
|
||||||
loadDraftIfAny()
|
loadDraftIfAny()
|
||||||
observePowerLevelAndEncryption()
|
observePowerLevelAndEncryption()
|
||||||
|
observeVoiceBroadcast()
|
||||||
subscribeToStateInternal()
|
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) {
|
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
||||||
|
@ -943,7 +958,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
|
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
|
||||||
audioMessageHelper.initializeRecorder(attachmentData)
|
audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
||||||
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
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.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.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
@ -67,6 +68,7 @@ data class MessageComposerViewState(
|
||||||
val startsThread: Boolean = false,
|
val startsThread: Boolean = false,
|
||||||
val sendMode: SendMode = SendMode.Regular("", false),
|
val sendMode: SendMode = SendMode.Regular("", false),
|
||||||
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle,
|
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle,
|
||||||
|
val voiceBroadcastState: VoiceBroadcastState? = null,
|
||||||
val text: CharSequence? = null,
|
val text: CharSequence? = null,
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
|
@ -77,6 +79,13 @@ data class MessageComposerViewState(
|
||||||
is VoiceMessageRecorderView.RecordingUiState.Recording -> true
|
is VoiceMessageRecorderView.RecordingUiState.Recording -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isVoiceBroadcasting = when (voiceBroadcastState) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
VoiceBroadcastState.PAUSED,
|
||||||
|
VoiceBroadcastState.RESUMED -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
val isVoiceMessageIdle = !isVoiceRecording
|
val isVoiceMessageIdle = !isVoiceRecording
|
||||||
|
|
||||||
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording
|
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.action
|
package im.vector.app.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
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.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -31,7 +31,7 @@ class CheckIfCanRedactEventUseCase @Inject constructor(
|
||||||
val canRedactEventTypes: List<String> = listOf(
|
val canRedactEventTypes: List<String> = listOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO,
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
) +
|
) +
|
||||||
EventType.POLL_START +
|
EventType.POLL_START +
|
||||||
EventType.STATE_ROOM_BEACON_INFO
|
EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.features.analytics.DecryptionFailureTracker
|
import im.vector.app.features.analytics.DecryptionFailureTracker
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
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.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -89,7 +89,7 @@ class TimelineItemFactory @Inject constructor(
|
||||||
// State room create
|
// State room create
|
||||||
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
|
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
|
||||||
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.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
|
// Unhandled state event types
|
||||||
else -> {
|
else -> {
|
||||||
// Should only happen when shouldShowHiddenEvents() settings is ON
|
// Should only happen when shouldShowHiddenEvents() settings is ON
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
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.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ object TimelineDisplayableEvents {
|
||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO,
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
) +
|
) +
|
||||||
EventType.POLL_START +
|
EventType.POLL_START +
|
||||||
EventType.STATE_ROOM_BEACON_INFO +
|
EventType.STATE_ROOM_BEACON_INFO +
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
import im.vector.app.core.utils.TextUtils
|
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.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -59,7 +59,7 @@ class TimelineEventsGroups {
|
||||||
val content = root.getClearContent()
|
val content = root.getClearContent()
|
||||||
return when {
|
return when {
|
||||||
EventType.isCallEvent(type) -> (content?.get("call_id") as? String)
|
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
|
type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey
|
||||||
else -> {
|
else -> {
|
||||||
null
|
null
|
||||||
|
|
|
@ -17,75 +17,22 @@
|
||||||
package im.vector.app.features.voice
|
package im.vector.app.features.voice
|
||||||
|
|
||||||
import android.content.Context
|
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.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.util.md5
|
import org.matrix.android.sdk.api.util.md5
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
abstract class AbstractVoiceRecorder(
|
abstract class AbstractVoiceRecorder(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val filenameExt: String,
|
|
||||||
) : VoiceRecorder {
|
) : VoiceRecorder {
|
||||||
|
|
||||||
private val outputDirectory: File by lazy { ensureAudioDirectory(context) }
|
private val outputDirectory: File by lazy { ensureAudioDirectory(context) }
|
||||||
|
protected var outputFile: File? = null
|
||||||
|
|
||||||
private var mediaRecorder: MediaRecorder? = null
|
override fun initializeRecord(roomId: String, attachmentData: ContentAttachmentData?) {
|
||||||
private var outputFile: File? = null
|
if (attachmentData != 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
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() {
|
|
||||||
// Can throw when the record is less than 1 second.
|
|
||||||
mediaRecorder?.let {
|
|
||||||
tryOrNull { it.stop() }
|
|
||||||
it.reset()
|
|
||||||
it.release()
|
|
||||||
}
|
|
||||||
mediaRecorder = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelRecord() {
|
override fun cancelRecord() {
|
||||||
|
@ -95,11 +42,15 @@ abstract class AbstractVoiceRecorder(
|
||||||
outputFile = null
|
outputFile = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMaxAmplitude(): Int {
|
|
||||||
return mediaRecorder?.maxAmplitude ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getVoiceMessageFile(): File? {
|
override fun getVoiceMessageFile(): File? {
|
||||||
return outputFile
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* 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 audioSamplingRate: Int = 48_000
|
||||||
|
protected val audioEncodingBitRate: Int = 24_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)
|
||||||
|
mediaRecorder.setNextOutputFile(nextOutputFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
mediaRecorder.setOutputFile(outputFile)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,11 +22,19 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface VoiceRecorder {
|
interface VoiceRecorder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize recording with a pre-recorded file.
|
* Audio file extension (eg. `mp4`).
|
||||||
* @param attachmentData data of the recorded file
|
|
||||||
*/
|
*/
|
||||||
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.
|
* Start the recording.
|
||||||
|
@ -34,6 +42,16 @@ interface VoiceRecorder {
|
||||||
*/
|
*/
|
||||||
fun startRecord(roomId: String)
|
fun startRecord(roomId: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the recording.
|
||||||
|
*/
|
||||||
|
fun pauseRecord()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the recording.
|
||||||
|
*/
|
||||||
|
fun resumeRecord()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the recording.
|
* Stop the recording.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.media.MediaRecorder
|
||||||
import android.media.audiofx.AutomaticGainControl
|
import android.media.audiofx.AutomaticGainControl
|
||||||
import android.media.audiofx.NoiseSuppressor
|
import android.media.audiofx.NoiseSuppressor
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import io.element.android.opusencoder.OggOpusEncoder
|
import io.element.android.opusencoder.OggOpusEncoder
|
||||||
import io.element.android.opusencoder.configuration.SampleRate
|
import io.element.android.opusencoder.configuration.SampleRate
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -30,29 +31,22 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
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
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VoiceRecorder to be used on Android versions < [Build.VERSION_CODES.Q]. It uses libopus to record ogg files.
|
* VoiceRecorder to be used on Android versions < [Build.VERSION_CODES.Q]. It uses libopus to record ogg files.
|
||||||
*/
|
*/
|
||||||
class VoiceRecorderL(
|
class VoiceRecorderL(
|
||||||
context: Context,
|
private val context: Context,
|
||||||
coroutineContext: CoroutineContext,
|
coroutineContext: CoroutineContext,
|
||||||
private val codec: OggOpusEncoder,
|
private val codec: OggOpusEncoder,
|
||||||
) : VoiceRecorder {
|
) : AbstractVoiceRecorder(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val SAMPLE_RATE = SampleRate.Rate48kHz
|
private val SAMPLE_RATE = SampleRate.Rate48kHz
|
||||||
private const val BITRATE = 24 * 1024
|
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 val recorderScope = CoroutineScope(coroutineContext)
|
||||||
private var recordingJob: Job? = null
|
private var recordingJob: Job? = null
|
||||||
|
|
||||||
|
@ -64,6 +58,8 @@ class VoiceRecorderL(
|
||||||
private var bufferSizeInShorts = 0
|
private var bufferSizeInShorts = 0
|
||||||
private var maxAmplitude = 0
|
private var maxAmplitude = 0
|
||||||
|
|
||||||
|
override val fileNameExt: String = "ogg"
|
||||||
|
|
||||||
private fun initializeCodec(filePath: String) {
|
private fun initializeCodec(filePath: String) {
|
||||||
codec.init(filePath, SAMPLE_RATE)
|
codec.init(filePath, SAMPLE_RATE)
|
||||||
codec.setBitrate(BITRATE)
|
codec.setBitrate(BITRATE)
|
||||||
|
@ -85,19 +81,10 @@ class VoiceRecorderL(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initializeRecord(attachmentData: ContentAttachmentData) {
|
|
||||||
outputFile = attachmentData.findVoiceFile(outputDirectory)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startRecord(roomId: String) {
|
override fun startRecord(roomId: String) {
|
||||||
val fileName = "${UUID.randomUUID()}.ogg"
|
outputFile = createOutputFile(roomId).also {
|
||||||
val outputDirectoryForRoom = File(outputDirectory, roomId.md5()).apply {
|
initializeCodec(it.absolutePath)
|
||||||
mkdirs()
|
|
||||||
}
|
}
|
||||||
val outputFile = File(outputDirectoryForRoom, fileName)
|
|
||||||
this.outputFile = outputFile
|
|
||||||
|
|
||||||
initializeCodec(outputFile.absolutePath)
|
|
||||||
|
|
||||||
recordingJob = recorderScope.launch {
|
recordingJob = recorderScope.launch {
|
||||||
audioRecorder?.startRecording()
|
audioRecorder?.startRecording()
|
||||||
|
@ -112,6 +99,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() {
|
override fun stopRecord() {
|
||||||
val recorder = this.audioRecorder ?: return
|
val recorder = this.audioRecorder ?: return
|
||||||
recordingJob?.cancel()
|
recordingJob?.cancel()
|
||||||
|
@ -131,19 +126,10 @@ class VoiceRecorderL(
|
||||||
codec.release()
|
codec.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelRecord() {
|
|
||||||
outputFile?.delete()
|
|
||||||
outputFile = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMaxAmplitude(): Int {
|
override fun getMaxAmplitude(): Int {
|
||||||
return maxAmplitude
|
return maxAmplitude
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getVoiceMessageFile(): File? {
|
|
||||||
return outputFile
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAudioRecord() {
|
private fun createAudioRecord() {
|
||||||
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||||
val format = AudioFormat.ENCODING_PCM_16BIT
|
val format = AudioFormat.ENCODING_PCM_16BIT
|
||||||
|
|
|
@ -22,13 +22,15 @@ import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
class VoiceRecorderQ(context: Context) : AbstractVoiceRecorder(context, "ogg") {
|
class VoiceRecorderQ(context: Context) : AbstractVoiceRecorderQ(context) {
|
||||||
override fun setOutputFormat(mediaRecorder: MediaRecorder) {
|
|
||||||
// We can directly use OGG here
|
// We can directly use OGG here
|
||||||
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.OGG)
|
override val outputFormat = MediaRecorder.OutputFormat.OGG
|
||||||
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
|
override val audioEncoder = MediaRecorder.AudioEncoder.OPUS
|
||||||
}
|
|
||||||
|
override val fileNameExt: String = "ogg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,11 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast
|
package im.vector.app.features.voicebroadcast
|
||||||
|
|
||||||
/** Voice Broadcast State Event. */
|
object VoiceBroadcastConstants {
|
||||||
const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info"
|
|
||||||
|
/** 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_IN_SECONDS = 30
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.voice.VoiceRecorder
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface VoiceBroadcastRecorder : VoiceRecorder {
|
||||||
|
|
||||||
|
var listener: Listener?
|
||||||
|
|
||||||
|
fun startRecord(roomId: String, chunkLength: Int)
|
||||||
|
|
||||||
|
fun interface Listener {
|
||||||
|
fun onVoiceMessageCreated(file: File)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
class VoiceBroadcastRecorderQ(
|
||||||
|
context: Context,
|
||||||
|
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
||||||
|
|
||||||
|
private var maxFileSize = 0L // zero or negative for no limit
|
||||||
|
|
||||||
|
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 startRecord(roomId: String, chunkLength: Int) {
|
||||||
|
maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong()
|
||||||
|
startRecord(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.voicebroadcast.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
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
|
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
|
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.
|
* It contains general info related to a voice broadcast.
|
||||||
*/
|
*/
|
||||||
|
@ -41,7 +41,7 @@ data class MessageVoiceBroadcastInfoContent(
|
||||||
/** The [VoiceBroadcastState] value. **/
|
/** The [VoiceBroadcastState] value. **/
|
||||||
@Json(name = "state") val voiceBroadcastStateStr: String = "",
|
@Json(name = "state") val voiceBroadcastStateStr: String = "",
|
||||||
/** The length of the voice chunks in seconds. **/
|
/** 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 {
|
) : MessageContent {
|
||||||
|
|
||||||
val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values()
|
val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values()
|
||||||
|
|
|
@ -16,14 +16,14 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.model
|
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.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
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.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
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.
|
* Provides additional fields and functions related to voice broadcast.
|
||||||
*/
|
*/
|
||||||
@JvmInline
|
@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
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
@ -31,6 +32,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class PauseVoiceBroadcastUseCase @Inject constructor(
|
class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
@ -39,7 +41,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested")
|
Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested")
|
||||||
|
|
||||||
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO,
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
QueryStringValue.Equals(session.myUserId)
|
QueryStringValue.Equals(session.myUserId)
|
||||||
)?.asVoiceBroadcastEvent()
|
)?.asVoiceBroadcastEvent()
|
||||||
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
||||||
|
@ -52,7 +54,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||||
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||||
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
room.stateService().sendStateEvent(
|
room.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = session.myUserId,
|
stateKey = session.myUserId,
|
||||||
body = MessageVoiceBroadcastInfoContent(
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
relatesTo = reference,
|
relatesTo = reference,
|
||||||
|
@ -60,6 +62,10 @@ class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||||
).toContent(),
|
).toContent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO pause recording audio files
|
pauseRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseRecording() {
|
||||||
|
voiceBroadcastRecorder?.pauseRecord()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
@ -31,6 +32,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class ResumeVoiceBroadcastUseCase @Inject constructor(
|
class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
@ -39,7 +41,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested")
|
Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested")
|
||||||
|
|
||||||
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO,
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
QueryStringValue.Equals(session.myUserId)
|
QueryStringValue.Equals(session.myUserId)
|
||||||
)?.asVoiceBroadcastEvent()
|
)?.asVoiceBroadcastEvent()
|
||||||
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
||||||
|
@ -57,7 +59,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||||
private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||||
Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
room.stateService().sendStateEvent(
|
room.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = session.myUserId,
|
stateKey = session.myUserId,
|
||||||
body = MessageVoiceBroadcastInfoContent(
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
relatesTo = reference,
|
relatesTo = reference,
|
||||||
|
@ -65,6 +67,10 @@ class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||||
).toContent(),
|
).toContent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO resume recording audio files
|
resumeRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resumeRecording() {
|
||||||
|
voiceBroadcastRecorder?.resumeRecord()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,32 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
import android.content.Context
|
||||||
|
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.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
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.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
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.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StartVoiceBroadcastUseCase @Inject constructor(
|
class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
|
private val context: Context,
|
||||||
|
private val buildMeta: BuildMeta,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
@ -38,7 +50,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested")
|
Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested")
|
||||||
|
|
||||||
val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents(
|
val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents(
|
||||||
setOf(STATE_ROOM_VOICE_BROADCAST_INFO),
|
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||||
QueryStringValue.IsNotEmpty
|
QueryStringValue.IsNotEmpty
|
||||||
)
|
)
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
|
@ -53,15 +65,39 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
|
||||||
private suspend fun startVoiceBroadcast(room: Room) {
|
private suspend fun startVoiceBroadcast(room: Room) {
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
room.stateService().sendStateEvent(
|
val chunkLength = VoiceBroadcastConstants.DEFAULT_CHUNK_LENGTH_IN_SECONDS // Todo Get the length from the room settings
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
val eventId = room.stateService().sendStateEvent(
|
||||||
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = session.myUserId,
|
stateKey = session.myUserId,
|
||||||
body = MessageVoiceBroadcastInfoContent(
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value,
|
voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value,
|
||||||
chunkLength = 5L, // TODO Get length from voice broadcast settings
|
chunkLength = chunkLength,
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO start recording audio files
|
startRecording(room, eventId, chunkLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
val outputFileUri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
buildMeta.applicationId + ".fileProvider",
|
||||||
|
voiceMessageFile,
|
||||||
|
"Voice message.${voiceMessageFile.extension}"
|
||||||
|
)
|
||||||
|
val audioType = outputFileUri.toMultiPickerAudioType(context) ?: return
|
||||||
|
room.sendService().sendMedia(
|
||||||
|
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
|
||||||
|
compressBeforeSending = false,
|
||||||
|
roomIds = emptySet(),
|
||||||
|
relatesTo = RelationDefaultContent(RelationType.REFERENCE, referenceEventId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
@ -31,6 +32,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class StopVoiceBroadcastUseCase @Inject constructor(
|
class StopVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
@ -39,7 +41,7 @@ class StopVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested")
|
Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested")
|
||||||
|
|
||||||
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
val lastVoiceBroadcastEvent = room.stateService().getStateEvent(
|
||||||
STATE_ROOM_VOICE_BROADCAST_INFO,
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
QueryStringValue.Equals(session.myUserId)
|
QueryStringValue.Equals(session.myUserId)
|
||||||
)?.asVoiceBroadcastEvent()
|
)?.asVoiceBroadcastEvent()
|
||||||
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) {
|
||||||
|
@ -53,7 +55,7 @@ class StopVoiceBroadcastUseCase @Inject constructor(
|
||||||
private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||||
Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
room.stateService().sendStateEvent(
|
room.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = session.myUserId,
|
stateKey = session.myUserId,
|
||||||
body = MessageVoiceBroadcastInfoContent(
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
relatesTo = reference,
|
relatesTo = reference,
|
||||||
|
@ -61,6 +63,10 @@ class StopVoiceBroadcastUseCase @Inject constructor(
|
||||||
).toContent(),
|
).toContent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO stop recording audio files
|
stopRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRecording() {
|
||||||
|
voiceBroadcastRecorder?.stopRecord()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.action
|
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 im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
@ -35,7 +35,7 @@ class CheckIfCanRedactEventUseCaseTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an event which can be redacted and owned by user when use case executes then the result is true`() {
|
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
|
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
||||||
canRedactEventTypes.forEach { eventType ->
|
canRedactEventTypes.forEach { eventType ->
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.model
|
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.shouldBeEqualTo
|
||||||
import org.amshove.kluent.shouldBeNull
|
import org.amshove.kluent.shouldBeNull
|
||||||
import org.amshove.kluent.shouldNotBeNull
|
import org.amshove.kluent.shouldNotBeNull
|
||||||
|
@ -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 AN_EVENT_ID = "event_id"
|
||||||
private const val A_REFERENCED_EVENT_ID = "event_id_ref"
|
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 {
|
class VoiceBroadcastEventTest {
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class VoiceBroadcastEventTest {
|
||||||
)
|
)
|
||||||
val event = Event(
|
val event = Event(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
content = content.toContent(),
|
content = content.toContent(),
|
||||||
)
|
)
|
||||||
val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId)
|
val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId)
|
||||||
|
@ -71,7 +71,7 @@ class VoiceBroadcastEventTest {
|
||||||
relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID),
|
relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID),
|
||||||
)
|
)
|
||||||
val event = Event(
|
val event = Event(
|
||||||
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
content = content.toContent(),
|
content = content.toContent(),
|
||||||
)
|
)
|
||||||
val expectedReference = content.relatesTo
|
val expectedReference = content.relatesTo
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
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.clearAllMocks
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
@ -44,7 +46,8 @@ class PauseVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
|
private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder)
|
||||||
|
|
||||||
@Test
|
@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 {
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is paused or not`() = runTest {
|
||||||
|
@ -80,7 +83,7 @@ class PauseVoiceBroadcastUseCaseTest {
|
||||||
// Then
|
// Then
|
||||||
coVerify {
|
coVerify {
|
||||||
fakeRoom.stateService().sendStateEvent(
|
fakeRoom.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = fakeSession.myUserId,
|
stateKey = fakeSession.myUserId,
|
||||||
body = any(),
|
body = any(),
|
||||||
)
|
)
|
||||||
|
@ -114,7 +117,7 @@ class PauseVoiceBroadcastUseCaseTest {
|
||||||
val event = state?.let {
|
val event = state?.let {
|
||||||
Event(
|
Event(
|
||||||
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
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,
|
stateKey = fakeSession.myUserId,
|
||||||
content = MessageVoiceBroadcastInfoContent(
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
voiceBroadcastStateStr = state.value,
|
voiceBroadcastStateStr = state.value,
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
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.clearAllMocks
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
@ -44,7 +46,8 @@ class ResumeVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
|
private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder)
|
||||||
|
|
||||||
@Test
|
@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 {
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest {
|
||||||
|
@ -80,7 +83,7 @@ class ResumeVoiceBroadcastUseCaseTest {
|
||||||
// Then
|
// Then
|
||||||
coVerify {
|
coVerify {
|
||||||
fakeRoom.stateService().sendStateEvent(
|
fakeRoom.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = fakeSession.myUserId,
|
stateKey = fakeSession.myUserId,
|
||||||
body = any(),
|
body = any(),
|
||||||
)
|
)
|
||||||
|
@ -114,7 +117,7 @@ class ResumeVoiceBroadcastUseCaseTest {
|
||||||
val event = state?.let {
|
val event = state?.let {
|
||||||
Event(
|
Event(
|
||||||
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
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,
|
stateKey = fakeSession.myUserId,
|
||||||
content = MessageVoiceBroadcastInfoContent(
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
voiceBroadcastStateStr = state.value,
|
voiceBroadcastStateStr = state.value,
|
||||||
|
|
|
@ -16,15 +16,18 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
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.FakeRoom
|
||||||
import im.vector.app.test.fakes.FakeRoomService
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
import im.vector.app.test.fakes.FakeSession
|
import im.vector.app.test.fakes.FakeSession
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
@ -44,7 +47,13 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
|
private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(
|
||||||
|
fakeSession,
|
||||||
|
fakeVoiceBroadcastRecorder,
|
||||||
|
FakeContext().instance,
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
|
|
||||||
@Test
|
@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 {
|
fun `given a room id with potential several existing voice broadcast states when calling execute then the voice broadcast is started or not`() = runTest {
|
||||||
|
@ -81,7 +90,7 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
// Then
|
// Then
|
||||||
coVerify {
|
coVerify {
|
||||||
fakeRoom.stateService().sendStateEvent(
|
fakeRoom.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = fakeSession.myUserId,
|
stateKey = fakeSession.myUserId,
|
||||||
body = any(),
|
body = any(),
|
||||||
)
|
)
|
||||||
|
@ -106,7 +115,7 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
private fun givenAVoiceBroadcasts(voiceBroadcasts: List<VoiceBroadcast>) {
|
private fun givenAVoiceBroadcasts(voiceBroadcasts: List<VoiceBroadcast>) {
|
||||||
val events = voiceBroadcasts.map {
|
val events = voiceBroadcasts.map {
|
||||||
Event(
|
Event(
|
||||||
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
type = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = it.userId,
|
stateKey = it.userId,
|
||||||
content = MessageVoiceBroadcastInfoContent(
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
voiceBroadcastStateStr = it.state.value
|
voiceBroadcastStateStr = it.state.value
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.voicebroadcast.usecase
|
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.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.test.fakes.FakeRoom
|
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.clearAllMocks
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
@ -44,7 +46,8 @@ class StopVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
|
private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession, fakeVoiceBroadcastRecorder)
|
||||||
|
|
||||||
@Test
|
@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 {
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is stopped or not`() = runTest {
|
||||||
|
@ -80,7 +83,7 @@ class StopVoiceBroadcastUseCaseTest {
|
||||||
// Then
|
// Then
|
||||||
coVerify {
|
coVerify {
|
||||||
fakeRoom.stateService().sendStateEvent(
|
fakeRoom.stateService().sendStateEvent(
|
||||||
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
eventType = VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
stateKey = fakeSession.myUserId,
|
stateKey = fakeSession.myUserId,
|
||||||
body = any(),
|
body = any(),
|
||||||
)
|
)
|
||||||
|
@ -114,7 +117,7 @@ class StopVoiceBroadcastUseCaseTest {
|
||||||
val event = state?.let {
|
val event = state?.let {
|
||||||
Event(
|
Event(
|
||||||
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
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,
|
stateKey = fakeSession.myUserId,
|
||||||
content = MessageVoiceBroadcastInfoContent(
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
voiceBroadcastStateStr = state.value,
|
voiceBroadcastStateStr = state.value,
|
||||||
|
|
Loading…
Reference in a new issue