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.voicebroadcast.model.VoiceBroadcastState
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.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
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.getRelationContent
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.TimelineEvent
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 javax.inject.Inject
import javax.inject.Singleton
@ -53,7 +47,7 @@ import javax.inject.Singleton
class VoiceBroadcastPlayer @Inject constructor(
private val sessionHolder: ActiveSessionHolder,
private val playbackTracker: AudioMessagePlaybackTracker,
private val getVoiceBroadcastStateUseCase: GetVoiceBroadcastStateUseCase,
private val getVoiceBroadcastUseCase: GetVoiceBroadcastUseCase,
) {
private val session
get() = sessionHolder.getActiveSession()
@ -87,6 +81,7 @@ class VoiceBroadcastPlayer @Inject constructor(
Timber.w("## VoiceBroadcastPlayer state: $field -> $value")
field = value
}
private var currentRoomId: String? = null
fun playOrResume(roomId: String, eventId: String) {
val hasChanged = currentVoiceBroadcastId != eventId
@ -132,17 +127,19 @@ class VoiceBroadcastPlayer @Inject constructor(
// Clear playlist
playlist = emptyList()
currentSequence = null
currentRoomId = null
}
private fun startPlayback(roomId: String, eventId: String) {
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
currentRoomId = roomId
// Stop listening previous voice broadcast if any
if (state != State.IDLE) stop()
state = State.BUFFERING
val voiceBroadcastState = getVoiceBroadcastStateUseCase.execute(roomId, eventId)
val voiceBroadcastState = getVoiceBroadcastUseCase.execute(roomId, eventId)?.content?.voiceBroadcastState
if (voiceBroadcastState == VoiceBroadcastState.STOPPED) {
// Get static playlist
updatePlaylist(getExistingChunks(room, eventId))
@ -172,33 +169,10 @@ class VoiceBroadcastPlayer @Inject constructor(
}
private fun playLiveVoiceBroadcast(room: Room, eventId: String) {
val voiceBroadcastEvent = room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent()
?: error("Cannot retrieve voice broadcast $eventId")
room.timelineService().getTimelineEvent(eventId)?.root?.asVoiceBroadcastEvent() ?: error("Cannot retrieve voice broadcast $eventId")
updatePlaylist(getExistingChunks(room, eventId))
startPlayback(true)
room.flow()
.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)
observeIncomingEvents(room, eventId)
}
private fun getExistingChunks(room: Room, eventId: String): List<MessageAudioEvent> {
@ -207,10 +181,7 @@ class VoiceBroadcastPlayer @Inject constructor(
.filter { it.isVoiceBroadcast() }
}
private fun observeIncomingChunks(room: Room, eventId: String) {
// Fixme this is probably not necessary here
currentTimeline?.dispose()
currentTimeline?.removeAllListeners()
private fun observeIncomingEvents(room: Room, eventId: String) {
currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline ->
timelineListener = TimelineListener(eventId).also { timeline.addListener(it) }
timeline.start()
@ -321,15 +292,19 @@ class VoiceBroadcastPlayer @Inject constructor(
}
override fun onCompletion(mp: MediaPlayer) {
when {
timelineListener == null && nextMediaPlayer == null -> {
if (nextMediaPlayer != null) return
val roomId = currentRoomId ?: return
val voiceBroadcastId = currentVoiceBroadcastId ?: return
val voiceBroadcastEventContent = getVoiceBroadcastUseCase.execute(roomId, voiceBroadcastId)?.content ?: return
val isLive = voiceBroadcastEventContent.voiceBroadcastState != null && voiceBroadcastEventContent.voiceBroadcastState != VoiceBroadcastState.STOPPED
if (!isLive && voiceBroadcastEventContent.lastChunkSequence == currentSequence) {
// We'll not receive new chunks anymore so we can stop the live listening
stop()
}
nextMediaPlayer == null -> {
} else {
state = State.BUFFERING
}
}
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
stop()

View file

@ -16,7 +16,7 @@
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 org.matrix.android.sdk.api.session.Session
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 javax.inject.Inject
class GetVoiceBroadcastStateUseCase @Inject constructor(
class GetVoiceBroadcastUseCase @Inject constructor(
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")
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 relatedEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId).sortedBy { it.root.originServerTs }
val lastVoiceBroadcastEvent = relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent
return lastVoiceBroadcastEvent?.content?.voiceBroadcastState
return relatedEvents.mapNotNull { it.root.asVoiceBroadcastEvent() }.lastOrNull() ?: initialEvent
}
}