VoiceBroadcastListener - Handle end of live listening
This commit is contained in:
parent
bafa2f8bde
commit
05eeef9dfe
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue