Skip to content

Commit b675005

Browse files
authored
Merge pull request #7363 from vector-im/feature/fre/voice_broadcast_start_record
Voice Broadcast - Start record
2 parents f330969 + b9335c6 commit b675005

34 files changed

+581
-226
lines changed

changelog.d/7363.wip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Voice Broadcast] Record and send not aggregated voice messages to the room

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
2121
import org.matrix.android.sdk.api.session.events.model.Event
2222
import org.matrix.android.sdk.api.session.room.model.message.MessageType
2323
import org.matrix.android.sdk.api.session.room.model.message.PollType
24+
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
2425
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
2526
import org.matrix.android.sdk.api.util.Cancelable
2627

@@ -71,7 +72,7 @@ interface SendService {
7172
text: String,
7273
formattedText: String? = null,
7374
autoMarkdown: Boolean,
74-
rootThreadEventId: String? = null
75+
rootThreadEventId: String? = null,
7576
): Cancelable
7677

7778
/**
@@ -81,13 +82,15 @@ interface SendService {
8182
* @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present.
8283
* It can be useful to send media to multiple room. It's safe to include the current roomId in this set
8384
* @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread
85+
* @param relatesTo add a relation content to the media event
8486
* @return a [Cancelable]
8587
*/
8688
fun sendMedia(
8789
attachment: ContentAttachmentData,
8890
compressBeforeSending: Boolean,
8991
roomIds: Set<String>,
90-
rootThreadEventId: String? = null
92+
rootThreadEventId: String? = null,
93+
relatesTo: RelationDefaultContent? = null,
9194
): Cancelable
9295

9396
/**
@@ -103,7 +106,7 @@ interface SendService {
103106
attachments: List<ContentAttachmentData>,
104107
compressBeforeSending: Boolean,
105108
roomIds: Set<String>,
106-
rootThreadEventId: String? = null
109+
rootThreadEventId: String? = null,
107110
): Cancelable
108111

109112
/**

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
3939
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
4040
import org.matrix.android.sdk.api.session.room.model.message.PollType
4141
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
42+
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
4243
import org.matrix.android.sdk.api.session.room.send.SendService
4344
import org.matrix.android.sdk.api.session.room.send.SendState
4445
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -280,7 +281,8 @@ internal class DefaultSendService @AssistedInject constructor(
280281
attachment: ContentAttachmentData,
281282
compressBeforeSending: Boolean,
282283
roomIds: Set<String>,
283-
rootThreadEventId: String?
284+
rootThreadEventId: String?,
285+
relatesTo: RelationDefaultContent?,
284286
): Cancelable {
285287
// Ensure that the event will not be send in a thread if we are a different flow.
286288
// Like sending files to multiple rooms
@@ -295,7 +297,8 @@ internal class DefaultSendService @AssistedInject constructor(
295297
localEchoEventFactory.createMediaEvent(
296298
roomId = it,
297299
attachment = attachment,
298-
rootThreadEventId = rootThreadId
300+
rootThreadEventId = rootThreadId,
301+
relatesTo,
299302
).also { event ->
300303
createLocalEcho(event)
301304
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt

Lines changed: 57 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ internal class LocalEchoEventFactory @Inject constructor(
127127
newBodyFormattedText: CharSequence?,
128128
newBodyAutoMarkdown: Boolean,
129129
msgType: String,
130-
compatibilityText: String
130+
compatibilityText: String,
131131
): Event {
132132
val content = if (newBodyFormattedText != null) {
133133
TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType)
@@ -148,7 +148,7 @@ internal class LocalEchoEventFactory @Inject constructor(
148148
private fun createPollContent(
149149
question: String,
150150
options: List<String>,
151-
pollType: PollType
151+
pollType: PollType,
152152
): MessagePollContent {
153153
return MessagePollContent(
154154
unstablePollCreationInfo = PollCreationInfo(
@@ -166,7 +166,7 @@ internal class LocalEchoEventFactory @Inject constructor(
166166
pollType: PollType,
167167
targetEventId: String,
168168
question: String,
169-
options: List<String>
169+
options: List<String>,
170170
): Event {
171171
val newContent = MessagePollContent(
172172
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
@@ -186,7 +186,7 @@ internal class LocalEchoEventFactory @Inject constructor(
186186
fun createPollReplyEvent(
187187
roomId: String,
188188
pollEventId: String,
189-
answerId: String
189+
answerId: String,
190190
): Event {
191191
val content = MessagePollResponseContent(
192192
body = answerId,
@@ -212,7 +212,7 @@ internal class LocalEchoEventFactory @Inject constructor(
212212
roomId: String,
213213
pollType: PollType,
214214
question: String,
215-
options: List<String>
215+
options: List<String>,
216216
): Event {
217217
val content = createPollContent(question, options, pollType)
218218
val localId = LocalEcho.createLocalEchoId()
@@ -229,7 +229,7 @@ internal class LocalEchoEventFactory @Inject constructor(
229229

230230
fun createEndPollEvent(
231231
roomId: String,
232-
eventId: String
232+
eventId: String,
233233
): Event {
234234
val content = MessageEndPollContent(
235235
relatesTo = RelationDefaultContent(
@@ -254,7 +254,7 @@ internal class LocalEchoEventFactory @Inject constructor(
254254
latitude: Double,
255255
longitude: Double,
256256
uncertainty: Double?,
257-
isUserLocation: Boolean
257+
isUserLocation: Boolean,
258258
): Event {
259259
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
260260
val assetType = if (isUserLocation) LocationAssetType.SELF else LocationAssetType.PIN
@@ -274,7 +274,7 @@ internal class LocalEchoEventFactory @Inject constructor(
274274
roomId: String,
275275
latitude: Double,
276276
longitude: Double,
277-
uncertainty: Double?
277+
uncertainty: Double?,
278278
): Event {
279279
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
280280
val content = MessageBeaconLocationDataContent(
@@ -305,7 +305,7 @@ internal class LocalEchoEventFactory @Inject constructor(
305305
newBodyText: String,
306306
autoMarkdown: Boolean,
307307
msgType: String,
308-
compatibilityText: String
308+
compatibilityText: String,
309309
): Event {
310310
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false)
311311
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
@@ -347,14 +347,21 @@ internal class LocalEchoEventFactory @Inject constructor(
347347
fun createMediaEvent(
348348
roomId: String,
349349
attachment: ContentAttachmentData,
350-
rootThreadEventId: String?
350+
rootThreadEventId: String?,
351+
relatesTo: RelationDefaultContent?,
351352
): Event {
352353
return when (attachment.type) {
353-
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId)
354-
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId)
355-
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId)
356-
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(roomId, attachment, isVoiceMessage = true, rootThreadEventId = rootThreadEventId)
357-
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId)
354+
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId, relatesTo)
355+
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId, relatesTo)
356+
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, isVoiceMessage = false, rootThreadEventId = rootThreadEventId, relatesTo)
357+
ContentAttachmentData.Type.VOICE_MESSAGE -> createAudioEvent(
358+
roomId,
359+
attachment,
360+
isVoiceMessage = true,
361+
rootThreadEventId = rootThreadEventId,
362+
relatesTo,
363+
)
364+
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId, relatesTo)
358365
}
359366
}
360367

@@ -378,7 +385,12 @@ internal class LocalEchoEventFactory @Inject constructor(
378385
)
379386
}
380387

381-
private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
388+
private fun createImageEvent(
389+
roomId: String,
390+
attachment: ContentAttachmentData,
391+
rootThreadEventId: String?,
392+
relatesTo: RelationDefaultContent?,
393+
): Event {
382394
var width = attachment.width
383395
var height = attachment.height
384396

@@ -403,19 +415,17 @@ internal class LocalEchoEventFactory @Inject constructor(
403415
size = attachment.size
404416
),
405417
url = attachment.queryUri.toString(),
406-
relatesTo = rootThreadEventId?.let {
407-
RelationDefaultContent(
408-
type = RelationType.THREAD,
409-
eventId = it,
410-
isFallingBack = true,
411-
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
412-
)
413-
}
418+
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
414419
)
415420
return createMessageEvent(roomId, content)
416421
}
417422

418-
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
423+
private fun createVideoEvent(
424+
roomId: String,
425+
attachment: ContentAttachmentData,
426+
rootThreadEventId: String?,
427+
relatesTo: RelationDefaultContent?,
428+
): Event {
419429
val mediaDataRetriever = MediaMetadataRetriever()
420430
mediaDataRetriever.setDataSource(context, attachment.queryUri)
421431

@@ -447,14 +457,7 @@ internal class LocalEchoEventFactory @Inject constructor(
447457
thumbnailInfo = thumbnailInfo
448458
),
449459
url = attachment.queryUri.toString(),
450-
relatesTo = rootThreadEventId?.let {
451-
RelationDefaultContent(
452-
type = RelationType.THREAD,
453-
eventId = it,
454-
isFallingBack = true,
455-
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
456-
)
457-
}
460+
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
458461
)
459462
return createMessageEvent(roomId, content)
460463
}
@@ -463,7 +466,8 @@ internal class LocalEchoEventFactory @Inject constructor(
463466
roomId: String,
464467
attachment: ContentAttachmentData,
465468
isVoiceMessage: Boolean,
466-
rootThreadEventId: String?
469+
rootThreadEventId: String?,
470+
relatesTo: RelationDefaultContent?,
467471
): Event {
468472
val content = MessageAudioContent(
469473
msgType = MessageType.MSGTYPE_AUDIO,
@@ -479,19 +483,17 @@ internal class LocalEchoEventFactory @Inject constructor(
479483
waveform = waveformSanitizer.sanitize(attachment.waveform)
480484
),
481485
voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
482-
relatesTo = rootThreadEventId?.let {
483-
RelationDefaultContent(
484-
type = RelationType.THREAD,
485-
eventId = it,
486-
isFallingBack = true,
487-
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
488-
)
489-
}
486+
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
490487
)
491488
return createMessageEvent(roomId, content)
492489
}
493490

494-
private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
491+
private fun createFileEvent(
492+
roomId: String,
493+
attachment: ContentAttachmentData,
494+
rootThreadEventId: String?,
495+
relatesTo: RelationDefaultContent?,
496+
): Event {
495497
val content = MessageFileContent(
496498
msgType = MessageType.MSGTYPE_FILE,
497499
body = attachment.name ?: "file",
@@ -500,14 +502,7 @@ internal class LocalEchoEventFactory @Inject constructor(
500502
size = attachment.size
501503
),
502504
url = attachment.queryUri.toString(),
503-
relatesTo = rootThreadEventId?.let {
504-
RelationDefaultContent(
505-
type = RelationType.THREAD,
506-
eventId = it,
507-
isFallingBack = true,
508-
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(it))
509-
)
510-
}
505+
relatesTo = relatesTo ?: rootThreadEventId?.let { generateThreadRelationContent(it) }
511506
)
512507
return createMessageEvent(roomId, content)
513508
}
@@ -559,7 +554,7 @@ internal class LocalEchoEventFactory @Inject constructor(
559554
text: CharSequence,
560555
msgType: String,
561556
autoMarkdown: Boolean,
562-
formattedText: String?
557+
formattedText: String?,
563558
): Event {
564559
val content = formattedText?.let { TextContent(text.toString(), it) } ?: createTextContent(text, autoMarkdown)
565560
return createEvent(
@@ -588,7 +583,7 @@ internal class LocalEchoEventFactory @Inject constructor(
588583
replyTextFormatted: CharSequence?,
589584
autoMarkdown: Boolean,
590585
rootThreadEventId: String? = null,
591-
showInThread: Boolean
586+
showInThread: Boolean,
592587
): Event? {
593588
// Fallbacks and event representation
594589
// TODO Add error/warning logs when any of this is null
@@ -629,6 +624,14 @@ internal class LocalEchoEventFactory @Inject constructor(
629624
return createMessageEvent(roomId, content)
630625
}
631626

627+
private fun generateThreadRelationContent(rootThreadEventId: String) =
628+
RelationDefaultContent(
629+
type = RelationType.THREAD,
630+
eventId = rootThreadEventId,
631+
isFallingBack = true,
632+
inReplyTo = ReplyToContent(eventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId)),
633+
)
634+
632635
/**
633636
* Generates the appropriate relatesTo object for a reply event.
634637
* It can either be a regular reply or a reply within a thread
@@ -772,7 +775,7 @@ internal class LocalEchoEventFactory @Inject constructor(
772775
text: String,
773776
formattedText: String?,
774777
autoMarkdown: Boolean,
775-
rootThreadEventId: String?
778+
rootThreadEventId: String?,
776779
): Event {
777780
val messageContent = quotedEvent.getLastMessageContent()
778781
val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.core.di
18+
19+
import android.content.Context
20+
import android.os.Build
21+
import dagger.Module
22+
import dagger.Provides
23+
import dagger.hilt.InstallIn
24+
import dagger.hilt.components.SingletonComponent
25+
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorder
26+
import im.vector.app.features.voicebroadcast.VoiceBroadcastRecorderQ
27+
import javax.inject.Singleton
28+
29+
@Module
30+
@InstallIn(SingletonComponent::class)
31+
object VoiceModule {
32+
@Provides
33+
@Singleton
34+
fun providesVoiceBroadcastRecorder(context: Context): VoiceBroadcastRecorder? {
35+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
36+
VoiceBroadcastRecorderQ(context)
37+
} else {
38+
null
39+
}
40+
}
41+
}

vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package im.vector.app.core.extensions
1818

19-
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
19+
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
2020
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
2121
import org.matrix.android.sdk.api.session.events.model.EventType
2222
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -39,7 +39,9 @@ fun TimelineEvent.canReact(): Boolean {
3939
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
4040
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
4141
return when (root.getClearType()) {
42-
STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
42+
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
43+
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
44+
}
4345
else -> getLastMessageContent()
4446
}
4547
}

0 commit comments

Comments
 (0)