@@ -23,53 +23,42 @@ import im.vector.app.core.di.ActiveSessionHolder
23
23
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
24
24
import im.vector.app.features.voice.VoiceFailure
25
25
import im.vector.app.features.voicebroadcast.getVoiceBroadcastChunk
26
- import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId
27
- import im.vector.app.features.voicebroadcast.isVoiceBroadcast
28
26
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener
29
27
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State
28
+ import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
30
29
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
31
- import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
32
30
import im.vector.app.features.voicebroadcast.sequence
33
31
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
34
32
import kotlinx.coroutines.CoroutineScope
35
33
import kotlinx.coroutines.Dispatchers
36
34
import kotlinx.coroutines.Job
37
35
import kotlinx.coroutines.SupervisorJob
36
+ import kotlinx.coroutines.flow.launchIn
37
+ import kotlinx.coroutines.flow.onEach
38
38
import kotlinx.coroutines.launch
39
39
import kotlinx.coroutines.withContext
40
- import org.matrix.android.sdk.api.session.events.model.RelationType
41
- import org.matrix.android.sdk.api.session.getRoom
42
- import org.matrix.android.sdk.api.session.room.Room
43
40
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
44
41
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
45
- import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
46
- import org.matrix.android.sdk.api.session.room.timeline.Timeline
47
- import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
48
- import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
49
42
import timber.log.Timber
50
43
import java.util.concurrent.CopyOnWriteArrayList
51
44
import javax.inject.Inject
45
+ import javax.inject.Singleton
52
46
53
47
@Singleton
54
48
class VoiceBroadcastPlayerImpl @Inject constructor(
55
49
private val sessionHolder : ActiveSessionHolder ,
56
50
private val playbackTracker : AudioMessagePlaybackTracker ,
57
51
private val getVoiceBroadcastUseCase : GetVoiceBroadcastUseCase ,
52
+ private val getLiveVoiceBroadcastChunksUseCase : GetLiveVoiceBroadcastChunksUseCase
58
53
) : VoiceBroadcastPlayer {
54
+
59
55
private val session
60
56
get() = sessionHolder.getActiveSession()
61
57
62
58
private val coroutineScope = CoroutineScope (SupervisorJob () + Dispatchers .Default )
63
59
private var voiceBroadcastStateJob: Job ? = null
64
- private var currentTimeline: Timeline ? = null
65
- set(value) {
66
- field?.removeAllListeners()
67
- field?.dispose()
68
- field = value
69
- }
70
60
71
61
private val mediaPlayerListener = MediaPlayerListener ()
72
- private var timelineListener: TimelineListener ? = null
73
62
74
63
private var currentMediaPlayer: MediaPlayer ? = null
75
64
private var nextMediaPlayer: MediaPlayer ? = null
@@ -79,7 +68,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
79
68
}
80
69
private var currentSequence: Int? = null
81
70
71
+ private var fetchPlaylistJob: Job ? = null
82
72
private var playlist = emptyList<MessageAudioEvent >()
73
+ private var isLive: Boolean = false
74
+
83
75
override var currentVoiceBroadcastId: String? = null
84
76
85
77
override var playingState = State .IDLE
@@ -118,6 +110,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
118
110
// Stop playback
119
111
currentMediaPlayer?.stop()
120
112
currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) }
113
+ isLive = false
121
114
122
115
// Release current player
123
116
release(currentMediaPlayer)
@@ -131,39 +124,33 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
131
124
voiceBroadcastStateJob?.cancel()
132
125
voiceBroadcastStateJob = null
133
126
134
- // In case of live broadcast, stop observing new chunks
135
- currentTimeline = null
136
- timelineListener = null
127
+ // Do not fetch the playlist anymore
128
+ fetchPlaylistJob?.cancel()
129
+ fetchPlaylistJob = null
137
130
138
131
// Update state
139
132
playingState = State .IDLE
140
133
141
134
// Clear playlist
142
135
playlist = emptyList()
143
136
currentSequence = null
137
+
144
138
currentRoomId = null
145
139
currentVoiceBroadcastId = null
146
140
}
147
141
148
- /* *
149
- * Add a [Listener] to the given voice broadcast id.
150
- */
151
142
override fun addListener (voiceBroadcastId : String , listener : Listener ) {
152
143
listeners[voiceBroadcastId]?.add(listener) ? : run {
153
144
listeners[voiceBroadcastId] = CopyOnWriteArrayList <Listener >().apply { add(listener) }
154
145
}
155
146
if (voiceBroadcastId == currentVoiceBroadcastId) listener.onStateChanged(playingState) else listener.onStateChanged(State .IDLE )
156
147
}
157
148
158
- /* *
159
- * Remove a [Listener] from the given voice broadcast id.
160
- */
161
149
override fun removeListener (voiceBroadcastId : String , listener : Listener ) {
162
150
listeners[voiceBroadcastId]?.remove(listener)
163
151
}
164
152
165
153
private fun startPlayback (roomId : String , eventId : String ) {
166
- val room = session.getRoom(roomId) ? : error(" Unknown roomId: $roomId " )
167
154
// Stop listening previous voice broadcast if any
168
155
if (playingState != State .IDLE ) stop()
169
156
@@ -173,16 +160,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
173
160
playingState = State .BUFFERING
174
161
175
162
val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState
176
- if (voiceBroadcastState == VoiceBroadcastState .STOPPED ) {
177
- // Get static playlist
178
- updatePlaylist(getExistingChunks(room, eventId))
179
- startPlayback(false )
180
- } else {
181
- playLiveVoiceBroadcast(room, eventId)
182
- }
163
+ isLive = voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState .STOPPED
164
+ observeIncomingEvents(roomId, eventId)
183
165
}
184
166
185
- private fun startPlayback (isLive : Boolean ) {
167
+ private fun startPlayback () {
186
168
val event = if (isLive) playlist.lastOrNull() else playlist.firstOrNull()
187
169
val content = event?.content ? : run { Timber .w(" ## VoiceBroadcastPlayer: No content to play" ); return }
188
170
val sequence = event.getVoiceBroadcastChunk()?.sequence
@@ -201,24 +183,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
201
183
}
202
184
}
203
185
204
- private fun playLiveVoiceBroadcast (room : Room , eventId : String ) {
205
- room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() ? : error(" Cannot retrieve voice broadcast $eventId " )
206
- updatePlaylist(getExistingChunks(room, eventId))
207
- startPlayback(true )
208
- observeIncomingEvents(room, eventId)
209
- }
210
-
211
- private fun getExistingChunks (room : Room , eventId : String ): List <MessageAudioEvent > {
212
- return room.timelineService().getTimelineEventsRelatedTo(RelationType .REFERENCE , eventId)
213
- .mapNotNull { it.root.asMessageAudioEvent() }
214
- .filter { it.isVoiceBroadcast() }
215
- }
216
-
217
- private fun observeIncomingEvents (room : Room , eventId : String ) {
218
- currentTimeline = room.timelineService().createTimeline(null , TimelineSettings (5 )).also { timeline ->
219
- timelineListener = TimelineListener (eventId).also { timeline.addListener(it) }
220
- timeline.start()
221
- }
186
+ private fun observeIncomingEvents (roomId : String , voiceBroadcastId : String ) {
187
+ fetchPlaylistJob = getLiveVoiceBroadcastChunksUseCase.execute(roomId, voiceBroadcastId)
188
+ .onEach(this ::updatePlaylist)
189
+ .launchIn(coroutineScope)
222
190
}
223
191
224
192
private fun resumePlayback () {
@@ -229,11 +197,32 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
229
197
230
198
private fun updatePlaylist (playlist : List <MessageAudioEvent >) {
231
199
this .playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ? : it.root.originServerTs }
200
+ onPlaylistUpdated()
201
+ }
202
+
203
+ private fun onPlaylistUpdated () {
204
+ when (playingState) {
205
+ State .PLAYING -> {
206
+ if (nextMediaPlayer == null ) {
207
+ coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
208
+ }
209
+ }
210
+ State .PAUSED -> {
211
+ if (nextMediaPlayer == null ) {
212
+ coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
213
+ }
214
+ }
215
+ State .BUFFERING -> {
216
+ val newMediaContent = getNextAudioContent()
217
+ if (newMediaContent != null ) startPlayback()
218
+ }
219
+ State .IDLE -> startPlayback()
220
+ }
232
221
}
233
222
234
223
private fun getNextAudioContent (): MessageAudioContent ? {
235
224
val nextSequence = currentSequence?.plus(1 )
236
- ? : timelineListener?. let { playlist.lastOrNull()?.sequence }
225
+ ? : playlist.lastOrNull()?.sequence
237
226
? : 1
238
227
return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content
239
228
}
@@ -279,37 +268,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
279
268
}
280
269
}
281
270
282
- private inner class TimelineListener (private val voiceBroadcastId : String ) : Timeline.Listener {
283
- override fun onTimelineUpdated (snapshot : List <TimelineEvent >) {
284
- val currentSequences = playlist.map { it.sequence }
285
- val newChunks = snapshot
286
- .mapNotNull { timelineEvent ->
287
- timelineEvent.root.asMessageAudioEvent()
288
- ?.takeIf { it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.sequence !in currentSequences }
289
- }
290
- if (newChunks.isEmpty()) return
291
- updatePlaylist(playlist + newChunks)
292
-
293
- when (playingState) {
294
- State .PLAYING -> {
295
- if (nextMediaPlayer == null ) {
296
- coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
297
- }
298
- }
299
- State .PAUSED -> {
300
- if (nextMediaPlayer == null ) {
301
- coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() }
302
- }
303
- }
304
- State .BUFFERING -> {
305
- val newMediaContent = getNextAudioContent()
306
- if (newMediaContent != null ) startPlayback(true )
307
- }
308
- State .IDLE -> startPlayback(true )
309
- }
310
- }
311
- }
312
-
313
271
private inner class MediaPlayerListener : MediaPlayer .OnInfoListener , MediaPlayer .OnCompletionListener , MediaPlayer .OnErrorListener {
314
272
315
273
override fun onInfo (mp : MediaPlayer , what : Int , extra : Int ): Boolean {
@@ -329,7 +287,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
329
287
val roomId = currentRoomId ? : return
330
288
val voiceBroadcastId = currentVoiceBroadcastId ? : return
331
289
val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ? : return
332
- val isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState .STOPPED
290
+ isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState .STOPPED
333
291
334
292
if (! isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) {
335
293
// We'll not receive new chunks anymore so we can stop the live listening
0 commit comments