Skip to content

Commit 0dad78a

Browse files
authored
Merge pull request #7387 from vector-im/feature/fre/voice_broadcast_start_listening
Voice Broadcast - Listening
2 parents b675005 + d53ad43 commit 0dad78a

File tree

17 files changed

+428
-44
lines changed

17 files changed

+428
-44
lines changed

changelog.d/7387.wip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Voice Broadcast] Start listening to a voice broadcast

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
3131
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
3232
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
3333
import org.matrix.android.sdk.api.session.room.model.message.MessageType
34+
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
3435
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
3536
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
3637
import org.matrix.android.sdk.api.session.room.send.SendState
@@ -357,6 +358,10 @@ fun Event.isAudioMessage(): Boolean {
357358
}
358359
}
359360

361+
fun Event.isVoiceMessage(): Boolean {
362+
return this.asMessageAudioEvent()?.content?.voiceMessageIndicator != null
363+
}
364+
360365
fun Event.isFileMessage(): Boolean {
361366
return when (getMsgType()) {
362367
MessageType.MSGTYPE_FILE -> true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
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 org.matrix.android.sdk.api.session.room.model.message
18+
19+
import org.matrix.android.sdk.api.extensions.tryOrNull
20+
import org.matrix.android.sdk.api.session.events.model.Event
21+
import org.matrix.android.sdk.api.session.events.model.EventType
22+
import org.matrix.android.sdk.api.session.events.model.toModel
23+
24+
/**
25+
* [Event] wrapper for [EventType.MESSAGE] event type.
26+
* Provides additional fields and functions related to this event type.
27+
*/
28+
@JvmInline
29+
value class MessageAudioEvent(val root: Event) {
30+
31+
/**
32+
* The mapped [MessageAudioContent] model of the event content.
33+
*/
34+
val content: MessageAudioContent
35+
get() = root.getClearContent().toModel<MessageContent>() as MessageAudioContent
36+
37+
init {
38+
require(tryOrNull { content } != null)
39+
}
40+
}
41+
42+
/**
43+
* Map a [EventType.MESSAGE] event to a [MessageAudioEvent].
44+
*/
45+
fun Event.asMessageAudioEvent() = if (getClearType() == EventType.MESSAGE) {
46+
tryOrNull { MessageAudioEvent(this) }
47+
} else null

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ interface TimelineService {
5555
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
5656
*/
5757
fun getAttachmentMessages(): List<TimelineEvent>
58+
59+
/**
60+
* Returns a snapshot list of TimelineEvent with a content relation of the given type to the given eventId.
61+
*/
62+
fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List<TimelineEvent>
5863
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
9696
override fun getAttachmentMessages(): List<TimelineEvent> {
9797
return timelineEventDataSource.getAttachmentMessages(roomId)
9898
}
99+
100+
override fun getTimelineEventsRelatedTo(relationType: String, eventId: String): List<TimelineEvent> {
101+
return timelineEventDataSource.getTimelineEventsRelatedTo(roomId, relationType, eventId)
102+
}
99103
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
1919
import androidx.lifecycle.LiveData
2020
import com.zhuinden.monarchy.Monarchy
2121
import io.realm.Sort
22+
import org.matrix.android.sdk.api.session.events.model.getRelationContent
2223
import org.matrix.android.sdk.api.session.events.model.isImageMessage
2324
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
2425
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -63,4 +64,18 @@ internal class TimelineEventDataSource @Inject constructor(
6364
.orEmpty()
6465
}
6566
}
67+
68+
fun getTimelineEventsRelatedTo(roomId: String, eventType: String, eventId: String): List<TimelineEvent> {
69+
// TODO Remove this trick and call relations API
70+
// see https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1roomsroomidrelationseventidreltypeeventtype
71+
return realmSessionProvider.withRealm { realm ->
72+
TimelineEventEntity.whereRoomId(realm, roomId)
73+
.sort(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, Sort.ASCENDING)
74+
.distinct(TimelineEventEntityFields.EVENT_ID)
75+
.findAll()
76+
.mapNotNull {
77+
timelineEventMapper.map(it).takeIf { it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null }
78+
}
79+
}
80+
}
6681
}

vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,17 @@ sealed class RoomDetailAction : VectorViewModelAction {
121121
object OpenElementCallWidget : RoomDetailAction()
122122

123123
sealed class VoiceBroadcastAction : RoomDetailAction() {
124-
object Start : VoiceBroadcastAction()
125-
object Pause : VoiceBroadcastAction()
126-
object Resume : VoiceBroadcastAction()
127-
object Stop : VoiceBroadcastAction()
124+
sealed class Recording : VoiceBroadcastAction() {
125+
object Start : Recording()
126+
object Pause : Recording()
127+
object Resume : Recording()
128+
object Stop : Recording()
129+
}
130+
131+
sealed class Listening : VoiceBroadcastAction() {
132+
data class PlayOrResume(val eventId: String) : Listening()
133+
object Pause : Listening()
134+
object Stop : Listening()
135+
}
128136
}
129137
}

vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -604,10 +604,13 @@ class TimelineViewModel @AssistedInject constructor(
604604
if (room == null) return
605605
viewModelScope.launch {
606606
when (action) {
607-
RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
608-
RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
609-
RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
610-
RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
607+
RoomDetailAction.VoiceBroadcastAction.Recording.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
608+
RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
609+
RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
610+
RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
611+
is RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.eventId)
612+
RoomDetailAction.VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
613+
RoomDetailAction.VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
611614
}
612615
}
613616
}

vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,9 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
234234
}
235235
// TODO remove this when there will be a recording indicator outside of the timeline
236236
// Pause voice broadcast if the timeline is not shown anymore
237-
it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Pause)
237+
it.isVoiceBroadcasting && !requireActivity().isChangingConfigurations -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Pause)
238238
else -> {
239+
timelineViewModel.handle(VoiceBroadcastAction.Listening.Pause)
239240
messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString()))
240241
}
241242
}
@@ -684,7 +685,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
684685
locationOwnerId = session.myUserId
685686
)
686687
}
687-
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start)
688+
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Recording.Start)
688689
}
689690
}
690691

vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
4242
import im.vector.app.features.session.coroutineScope
4343
import im.vector.app.features.settings.VectorPreferences
4444
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
45+
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
4546
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
4647
import kotlinx.coroutines.Dispatchers
4748
import kotlinx.coroutines.flow.combine
@@ -84,6 +85,7 @@ class MessageComposerViewModel @AssistedInject constructor(
8485
private val rainbowGenerator: RainbowGenerator,
8586
private val audioMessageHelper: AudioMessageHelper,
8687
private val analyticsTracker: AnalyticsTracker,
88+
private val voiceBroadcastHelper: VoiceBroadcastHelper,
8789
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
8890

8991
private val room = session.getRoom(initialState.roomId)!!
@@ -981,6 +983,8 @@ class MessageComposerViewModel @AssistedInject constructor(
981983
private fun handleEntersBackground(composerText: String) {
982984
// Always stop all voice actions. It may be playing in timeline or active recording
983985
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
986+
// TODO remove this when there will be a listening indicator outside of the timeline
987+
voiceBroadcastHelper.pausePlayback()
984988

985989
val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
986990
if (isVoiceRecording) {

0 commit comments

Comments
 (0)