VoiceBroadcastListener - Handle end of live listening

This commit is contained in:
Florian Renaud 2022-10-20 14:36:47 +02:00
parent bafa2f8bde
commit 05eeef9dfe
2 changed files with 25 additions and 51 deletions

View file

@ -23,16 +23,12 @@ import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlayb
import im.vector.app.features.voice.VoiceFailure import im.vector.app.features.voice.VoiceFailure
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.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastUseCase
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
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.getRelationContent import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
@ -43,8 +39,6 @@ import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.Timeline
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.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -53,7 +47,7 @@ import javax.inject.Singleton
class VoiceBroadcastPlayer @Inject constructor( class VoiceBroadcastPlayer @Inject constructor(
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
private val playbackTracker: AudioMessagePlaybackTracker, private val playbackTracker: AudioMessagePlaybackTracker,
private val getVoiceBroadcastStateUseCase: GetVoiceBroadcastStateUseCase, private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase,
) { ) {
private val session private val session
get() = sessionHolder.getActiveSession() get() = sessionHolder.getActiveSession()
@ -87,6 +81,7 @@ class VoiceBroadcastPlayer @Inject constructor(
Timber.w("## VoiceBroadcastPlayer state: $field -> $value") Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
field = value field = value
} }
private var currentRoomId: String? = null
fun playOrResume(roomId: String, eventId: String) { fun playOrResume(roomId: String, eventId: String) {
val hasChanged = currentVoiceBroadcastId != eventId val hasChanged = currentVoiceBroadcastId != eventId
@ -132,17 +127,19 @@ class VoiceBroadcastPlayer @Inject constructor(
// Clear playlist // Clear playlist
playlist = emptyList() playlist = emptyList()
currentSequence = null currentSequence = null
currentRoomId = null
} }
private fun startPlayback(roomId: String, eventId: String) { private fun startPlayback(roomId: String, eventId: String) {
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
currentRoomId = roomId
// Stop listening previous voice broadcast if any // Stop listening previous voice broadcast if any
if (state != State.IDLE) stop() if (state != State.IDLE) stop()
state = State.BUFFERING state = State.BUFFERING
val voiceBroadcastState = getVoiceBroadcastStateUseCase.execute(roomId, eventId) val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState
if (voiceBroadcastState == VoiceBroadcastState.STOPPED) { if (voiceBroadcastState == VoiceBroadcastState.STOPPED) {
// Get static playlist // Get static playlist
updatePlaylist(getExistingChunks(room, eventId)) updatePlaylist(getExistingChunks(room, eventId))
@ -172,33 +169,10 @@ class VoiceBroadcastPlayer @Inject constructor(
} }
private fun playLiveVoiceBroadcast(room: Room, eventId: String) { private fun playLiveVoiceBroadcast(room: Room, eventId: String) {
val voiceBroadcastEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() ?: error("Cannot retrieve voice broadcast $eventId")
?: error("Cannot retrieve voice broadcast $eventId")
updatePlaylist(getExistingChunks(room, eventId)) updatePlaylist(getExistingChunks(room, eventId))
startPlayback(true) startPlayback(true)
room.flow() observeIncomingEvents(room, eventId)
.liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!))
.unwrap()
.mapNotNull { event ->
event.asVoiceBroadcastEvent()
?.takeIf { it.reference?.eventId == eventId }
?.content?.voiceBroadcastState
}
.onEach { state ->
when (state) {
VoiceBroadcastState.STARTED,
VoiceBroadcastState.PAUSED,
VoiceBroadcastState.RESUMED -> {
observeIncomingChunks(room, eventId)
}
VoiceBroadcastState.STOPPED -> {
currentTimeline?.dispose()
currentTimeline?.removeAllListeners()
currentTimeline = null
}
}
}
.launchIn(coroutineScope)
} }
private fun getExistingChunks(room: Room, eventId: String): List<MessageAudioEvent> { private fun getExistingChunks(room: Room, eventId: String): List<MessageAudioEvent> {
@ -207,10 +181,7 @@ class VoiceBroadcastPlayer @Inject constructor(
.filter { it.isVoiceBroadcast() } .filter { it.isVoiceBroadcast() }
} }
private fun observeIncomingChunks(room: Room, eventId: String) { private fun observeIncomingEvents(room: Room, eventId: String) {
// Fixme this is probably not necessary here
currentTimeline?.dispose()
currentTimeline?.removeAllListeners()
currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline -> currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline ->
timelineListener = TimelineListener(eventId).also { timeline.addListener(it) } timelineListener = TimelineListener(eventId).also { timeline.addListener(it) }
timeline.start() timeline.start()
@ -321,13 +292,17 @@ class VoiceBroadcastPlayer @Inject constructor(
} }
override fun onCompletion(mp: MediaPlayer) { override fun onCompletion(mp: MediaPlayer) {
when { if (nextMediaPlayer != null) return
timelineListener == null && nextMediaPlayer == null -> { val roomId = currentRoomId ?: return
stop() val voiceBroadcastId = currentVoiceBroadcastId ?: return
} val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return
nextMediaPlayer == null -> { val isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED
state = State.BUFFERING
} if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) {
// We'll not receive new chunks anymore so we can stop the live listening
stop()
} else {
state = State.BUFFERING
} }
} }

View file

@ -16,7 +16,7 @@
package im.vector.app.features.voicebroadcast.usecase package im.vector.app.features.voicebroadcast.usecase
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
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.RelationType
@ -24,18 +24,17 @@ import org.matrix.android.sdk.api.session.getRoom
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class GetVoiceBroadcastStateUseCase @Inject constructor( class GetVoiceBroadcastUseCase @Inject constructor(
private val session: Session, private val session: Session,
) { ) {
fun execute(roomId: String, eventId: String): VoiceBroadcastState? { fun execute(roomId: String, eventId: String): VoiceBroadcastEvent? {
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
Timber.d("## GetVoiceBroadcastStateUseCase: get voice broadcast state requested for $eventId") Timber.d("## GetVoiceBroadcastUseCase: get voice broadcast $eventId")
val initialEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() // Fallback to initial event val initialEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() // Fallback to initial event
val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs } val relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs }
val lastVoiceBroadcastEvent = relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent return relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent
return lastVoiceBroadcastEvent?.content?.voiceBroadcastState
} }
} }