From 6554f571f22a45212ee926c55b2da93d63716826 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 11:18:35 +0200 Subject: [PATCH 01/12] VoiceBroadcastPlayer - Inject ActiveSessionHolder instead of Session --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index dfd50ea5cb..e93e128686 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -18,6 +18,7 @@ package im.vector.app.features.voicebroadcast import android.media.AudioAttributes import android.media.MediaPlayer +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State import im.vector.app.features.voice.VoiceFailure @@ -25,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse -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.getRelationContent import org.matrix.android.sdk.api.session.getRoom @@ -39,10 +39,12 @@ import javax.inject.Singleton @Singleton class VoiceBroadcastPlayer @Inject constructor( - private val session: Session, + private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, ) { + private val session get() = sessionHolder.getActiveSession() + private val mediaPlayerScope = CoroutineScope(Dispatchers.IO) private var currentMediaPlayer: MediaPlayer? = null From b89ab6c2fd54cedfb76228d3189deafbecee705a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:04:03 +0200 Subject: [PATCH 02/12] VoiceBroadcastPlayer - release previous MediaPlayer --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index e93e128686..c7259b12f8 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -76,10 +76,8 @@ class VoiceBroadcastPlayer @Inject constructor( fun stop() { currentMediaPlayer?.stop() - currentMediaPlayer?.release() - currentMediaPlayer?.setOnInfoListener(null) - currentMediaPlayer = null currentVoiceBroadcastEventId?.let { playbackTracker.stopPlayback(it) } + release(currentMediaPlayer) playlist = emptyList() currentPlayingIndex = -1 } @@ -147,11 +145,21 @@ class VoiceBroadcastPlayer @Inject constructor( } } + private fun release(mp: MediaPlayer?) { + mp?.apply { + release() + setOnInfoListener(null) + setOnCompletionListener(null) + setOnErrorListener(null) + } + } + inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { + release(currentMediaPlayer) currentMediaPlayer = mp currentPlayingIndex++ mediaPlayerScope.launch { prepareNextFile() } From 0c847cffc131f446cc94c3757883aee2ea870f21 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:16:54 +0200 Subject: [PATCH 03/12] VoiceBroadcastPlayer - Use more accurate coroutine scope --- .../features/voicebroadcast/VoiceBroadcastPlayer.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index c7259b12f8..db07503927 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlayb import im.vector.app.features.voice.VoiceFailure import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.RelationType @@ -42,13 +43,13 @@ class VoiceBroadcastPlayer @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, ) { - - private val session get() = sessionHolder.getActiveSession() - - private val mediaPlayerScope = CoroutineScope(Dispatchers.IO) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val session + get() = sessionHolder.getActiveSession() private var currentMediaPlayer: MediaPlayer? = null private var currentPlayingIndex: Int = -1 + private var playlist = emptyList() private val currentVoiceBroadcastEventId get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId @@ -90,7 +91,7 @@ class VoiceBroadcastPlayer @Inject constructor( private fun startPlayback() { val content = playlist.firstOrNull()?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } - mediaPlayerScope.launch { + coroutineScope.launch { try { currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() @@ -162,7 +163,7 @@ class VoiceBroadcastPlayer @Inject constructor( release(currentMediaPlayer) currentMediaPlayer = mp currentPlayingIndex++ - mediaPlayerScope.launch { prepareNextFile() } + coroutineScope.launch { prepareNextFile() } } } return false From fe44a829afdc23d35e0fc13a399affe77d19c7b6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 13:17:59 +0200 Subject: [PATCH 04/12] VoiceBroadcastPlayer - Improve currentVoiceBroadcastId --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index db07503927..72ec181966 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -51,8 +51,8 @@ class VoiceBroadcastPlayer @Inject constructor( private var currentPlayingIndex: Int = -1 private var playlist = emptyList() - private val currentVoiceBroadcastEventId - get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId + private val currentVoiceBroadcastId + get() = playlist.getOrNull(currentPlayingIndex)?.root?.getRelationContent()?.eventId private val mediaPlayerListener = MediaPlayerListener() @@ -60,7 +60,7 @@ class VoiceBroadcastPlayer @Inject constructor( val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") when { - currentVoiceBroadcastEventId != eventId -> { + currentVoiceBroadcastId != eventId -> { stop() updatePlaylist(room, eventId) startPlayback() @@ -72,12 +72,12 @@ class VoiceBroadcastPlayer @Inject constructor( fun pause() { currentMediaPlayer?.pause() - currentVoiceBroadcastEventId?.let { playbackTracker.pausePlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) } } fun stop() { currentMediaPlayer?.stop() - currentVoiceBroadcastEventId?.let { playbackTracker.stopPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) } release(currentMediaPlayer) playlist = emptyList() currentPlayingIndex = -1 @@ -96,7 +96,7 @@ class VoiceBroadcastPlayer @Inject constructor( currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() currentPlayingIndex = 0 - currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } prepareNextFile() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") @@ -107,7 +107,7 @@ class VoiceBroadcastPlayer @Inject constructor( private fun resumePlayback() { currentMediaPlayer?.start() - currentVoiceBroadcastEventId?.let { playbackTracker.startPlayback(it) } + currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } } private suspend fun prepareNextFile() { From e9c81ca98fea45ced23afa32185d3a29762ded4a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 19 Oct 2022 18:07:44 +0200 Subject: [PATCH 05/12] VoiceBroadcastPlayer - Live listening --- .../sdk/api/session/events/model/Event.kt | 2 +- .../room/timeline/TimelineEventDataSource.kt | 10 +- .../voicebroadcast/VoiceBroadcastHelper.kt | 2 +- .../voicebroadcast/VoiceBroadcastPlayer.kt | 236 +++++++++++++++--- .../usecase/GetVoiceBroadcastStateUseCase.kt | 41 +++ 5 files changed, 249 insertions(+), 42 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 71daf4cc4f..1f16041b54 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -401,7 +401,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { when (getClearType()) { EventType.STICKER -> getClearContent().toModel()?.relatesTo in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel()?.relatesTo - else -> null + else -> getClearContent()?.get("m.relates_to")?.toContent().toModel() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt index 20094e4be8..2d6082f9b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt @@ -22,6 +22,8 @@ import io.realm.Sort import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider @@ -74,7 +76,13 @@ internal class TimelineEventDataSource @Inject constructor( .distinct(TimelineEventEntityFields.EVENT_ID) .findAll() .mapNotNull { - timelineEventMapper.map(it).takeIf { it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null } + timelineEventMapper.map(it) + .takeIf { + val isEventRelatedTo = it.root.getRelationContent()?.takeIf { it.type == eventType && it.eventId == eventId } != null + val isContentRelatedTo = it.root.getClearContent()?.toModel() + ?.relatesTo?.takeIf { it.type == eventType && it.eventId == eventId } != null + isEventRelatedTo || isContentRelatedTo + } } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt index b967afa9cb..58e7de7f32 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -40,7 +40,7 @@ class VoiceBroadcastHelper @Inject constructor( suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId) - fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.play(roomId, eventId) + fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.playOrResume(roomId, eventId) fun pausePlayback() = voiceBroadcastPlayer.pause() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 72ec181966..7f5e13504e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -20,13 +20,19 @@ import android.media.AudioAttributes import android.media.MediaPlayer import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker -import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State 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 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.extensions.orFalse +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 @@ -34,6 +40,11 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent 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 @@ -42,62 +53,117 @@ import javax.inject.Singleton class VoiceBroadcastPlayer @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val playbackTracker: AudioMessagePlaybackTracker, + private val getVoiceBroadcastStateUseCase: GetVoiceBroadcastStateUseCase, ) { - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val session get() = sessionHolder.getActiveSession() + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private var voiceBroadcastStateJob: Job? = null + private var currentTimeline: Timeline? = null + set(value) { + field?.removeAllListeners() + field?.dispose() + field = value + } + + private val mediaPlayerListener = MediaPlayerListener() + private var timelineListener: TimelineListener? = null + private var currentMediaPlayer: MediaPlayer? = null - private var currentPlayingIndex: Int = -1 + private var nextMediaPlayer: MediaPlayer? = null + set(value) { + field = value + currentMediaPlayer?.setNextMediaPlayer(value) + } + private var currentSequence: Int? = null private var playlist = emptyList() private val currentVoiceBroadcastId - get() = playlist.getOrNull(currentPlayingIndex)?.root?.getRelationContent()?.eventId + get() = playlist.firstOrNull()?.root?.getRelationContent()?.eventId - private val mediaPlayerListener = MediaPlayerListener() - - fun play(roomId: String, eventId: String) { - val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + private var state: State = State.IDLE + set(value) { + Timber.w("## VoiceBroadcastPlayer state: $field -> $value") + field = value + } + fun playOrResume(roomId: String, eventId: String) { + val hasChanged = currentVoiceBroadcastId != eventId when { - currentVoiceBroadcastId != eventId -> { - stop() - updatePlaylist(room, eventId) - startPlayback() - } - playbackTracker.getPlaybackState(eventId) is State.Playing -> pause() - else -> resumePlayback() + hasChanged -> startPlayback(roomId, eventId) + state == State.PAUSED -> resumePlayback() + else -> Unit } } fun pause() { currentMediaPlayer?.pause() currentVoiceBroadcastId?.let { playbackTracker.pausePlayback(it) } + state = State.PAUSED } fun stop() { + // Stop playback currentMediaPlayer?.stop() currentVoiceBroadcastId?.let { playbackTracker.stopPlayback(it) } + + // Release current player release(currentMediaPlayer) + currentMediaPlayer = null + + // Release next player + release(nextMediaPlayer) + nextMediaPlayer = null + + // Do not observe anymore voice broadcast state changes + voiceBroadcastStateJob?.cancel() + voiceBroadcastStateJob = null + + // In case of live broadcast, stop observing new chunks + currentTimeline?.dispose() + currentTimeline?.removeAllListeners() + currentTimeline = null + timelineListener = null + + // Update state + state = State.IDLE + + // Clear playlist playlist = emptyList() - currentPlayingIndex = -1 + currentSequence = null } - private fun updatePlaylist(room: Room, eventId: String) { - val timelineEvents = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) - val audioEvents = timelineEvents.mapNotNull { it.root.asMessageAudioEvent() } - playlist = audioEvents.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs } + private fun startPlayback(roomId: String, eventId: String) { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + // Stop listening previous voice broadcast if any + if (state != State.IDLE) stop() + + state = State.BUFFERING + + val voiceBroadcastState = getVoiceBroadcastStateUseCase.execute(roomId, eventId) + if (voiceBroadcastState == VoiceBroadcastState.STOPPED) { + // Get static playlist + updatePlaylist(getExistingChunks(room, eventId)) + startPlayback(false) + } else { + playLiveVoiceBroadcast(room, eventId) + } } - private fun startPlayback() { - val content = playlist.firstOrNull()?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } + private fun startPlayback(isLive: Boolean) { + val event = if (isLive) playlist.lastOrNull() else playlist.firstOrNull() + val content = event?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return } + val sequence = event.getVoiceBroadcastChunk()?.sequence coroutineScope.launch { try { currentMediaPlayer = prepareMediaPlayer(content) currentMediaPlayer?.start() - currentPlayingIndex = 0 currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } - prepareNextFile() + currentSequence = sequence + state = State.PLAYING + nextMediaPlayer = prepareNextMediaPlayer() } catch (failure: Throwable) { Timber.e(failure, "Unable to start playback") throw VoiceFailure.UnableToPlay(failure) @@ -105,19 +171,68 @@ 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") + updatePlaylist(getExistingChunks(room, eventId)) + startPlayback(true) + room.flow() + .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!)) + .unwrap() + .mapNotNull { it.asVoiceBroadcastEvent()?.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 { + return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, eventId) + .mapNotNull { it.root.asMessageAudioEvent() } + .filter { it.isVoiceBroadcast() } + } + + private fun observeIncomingChunks(room: Room, eventId: String) { + // Fixme this is probably not necessary here + currentTimeline?.dispose() + currentTimeline?.removeAllListeners() + currentTimeline = room.timelineService().createTimeline(null, TimelineSettings(5)).also { timeline -> + timelineListener = TimelineListener(eventId).also { timeline.addListener(it) } + timeline.start() + } + } + private fun resumePlayback() { currentMediaPlayer?.start() currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) } + state = State.PLAYING } - private suspend fun prepareNextFile() { - val nextContent = playlist.getOrNull(currentPlayingIndex + 1)?.content - if (nextContent == null) { - currentMediaPlayer?.setOnCompletionListener(mediaPlayerListener) - } else { - val nextMediaPlayer = prepareMediaPlayer(nextContent) - currentMediaPlayer?.setNextMediaPlayer(nextMediaPlayer) - } + private fun updatePlaylist(playlist: List) { + this.playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs } + } + + private fun getNextAudioContent(): MessageAudioContent? { + val nextSequence = currentSequence?.plus(1) + ?: timelineListener?.let { playlist.lastOrNull()?.sequence } + ?: 1 + return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content + } + + private suspend fun prepareNextMediaPlayer(): MediaPlayer? { + val nextContent = getNextAudioContent() ?: return null + return prepareMediaPlayer(nextContent) } private suspend fun prepareMediaPlayer(messageAudioContent: MessageAudioContent): MediaPlayer { @@ -141,6 +256,7 @@ class VoiceBroadcastPlayer @Inject constructor( setDataSource(fis.fd) setOnInfoListener(mediaPlayerListener) setOnErrorListener(mediaPlayerListener) + setOnCompletionListener(mediaPlayerListener) prepare() } } @@ -155,24 +271,59 @@ class VoiceBroadcastPlayer @Inject constructor( } } - inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { + private inner class TimelineListener(private val voiceBroadcastId: String) : Timeline.Listener { + override fun onTimelineUpdated(snapshot: List) { + val currentSequences = playlist.map { it.sequence } + val newChunks = snapshot + .mapNotNull { timelineEvent -> + timelineEvent.root.asMessageAudioEvent() + ?.takeIf { it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.sequence !in currentSequences } + } + if (newChunks.isEmpty()) return + updatePlaylist(playlist + newChunks) + + when (state) { + State.PLAYING -> { + if (nextMediaPlayer == null) { + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + } + } + State.PAUSED -> { + if (nextMediaPlayer == null) { + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } + } + } + State.BUFFERING -> { + val newMediaContent = getNextAudioContent() + if (newMediaContent != null) startPlayback(true) + } + State.IDLE -> startPlayback(true) + } + } + } + + private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean { when (what) { MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> { release(currentMediaPlayer) currentMediaPlayer = mp - currentPlayingIndex++ - coroutineScope.launch { prepareNextFile() } + currentSequence = currentSequence?.plus(1) + coroutineScope.launch { nextMediaPlayer = prepareNextMediaPlayer() } } } return false } override fun onCompletion(mp: MediaPlayer) { - // Verify that a new media has not been set in the mean time - if (!currentMediaPlayer?.isPlaying.orFalse()) { - stop() + when { + timelineListener == null && nextMediaPlayer == null -> { + stop() + } + nextMediaPlayer == null -> { + state = State.BUFFERING + } } } @@ -181,4 +332,11 @@ class VoiceBroadcastPlayer @Inject constructor( return true } } + + enum class State { + PLAYING, + PAUSED, + BUFFERING, + IDLE + } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt new file mode 100644 index 0000000000..5b3153ea40 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +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 +import org.matrix.android.sdk.api.session.getRoom +import timber.log.Timber +import javax.inject.Inject + +class GetVoiceBroadcastStateUseCase @Inject constructor( + private val session: Session, +) { + + fun execute(roomId: String, eventId: String): VoiceBroadcastState? { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") + + Timber.d("## GetVoiceBroadcastStateUseCase: get voice broadcast state requested for $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 + } +} From f05f0a85b01ade9525b9f8abbc2e4616983e17fa Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 02:22:59 +0200 Subject: [PATCH 06/12] VoiceBroadcastRecorder - Improve recorder by sending chunk when pausing --- .../voicebroadcast/VoiceBroadcastRecorderQ.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 404b112574..21d12ee986 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -21,6 +21,7 @@ import android.media.MediaRecorder import android.os.Build import androidx.annotation.RequiresApi import im.vector.app.features.voice.AbstractVoiceRecorderQ +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentAttachmentData @RequiresApi(Build.VERSION_CODES.Q) @@ -30,6 +31,7 @@ class VoiceBroadcastRecorderQ( private var maxFileSize = 0L // zero or negative for no limit private var currentSequence = 0 + private var currentRoomId: String? = null override var listener: VoiceBroadcastRecorder.Listener? = null @@ -51,11 +53,23 @@ class VoiceBroadcastRecorderQ( } override fun startRecord(roomId: String, chunkLength: Int) { + currentRoomId = roomId maxFileSize = (chunkLength * audioEncodingBitRate / 8).toLong() currentSequence = 1 startRecord(roomId) } + override fun pauseRecord() { + tryOrNull { mediaRecorder?.stop() } + mediaRecorder?.reset() + notifyOutputFileCreated() + } + + override fun resumeRecord() { + currentSequence++ + currentRoomId?.let { startRecord(it) } + } + override fun stopRecord() { super.stopRecord() notifyOutputFileCreated() From 6d6b4e52087c4c28c4beeedba00be437d6e82da6 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 11:24:45 +0200 Subject: [PATCH 07/12] VoiceBroadcast - Ignore voice broadcast info with empty content (eg. redacted) --- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 7cb66cd9e5..d5d58f822e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -55,7 +55,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( QueryStringValue.IsNotEmpty ) .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } if (onGoingVoiceBroadcastEvents.isEmpty()) { startVoiceBroadcast(room) From 94390697ae045e620452f6e99331c9dabf8c3e81 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:18:46 +0200 Subject: [PATCH 08/12] VoiceBroadcastPlayer - Filter live broadcast state listening on the referenced eventId --- .../app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 7f5e13504e..c55cb8a1d0 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -179,7 +179,11 @@ class VoiceBroadcastPlayer @Inject constructor( room.flow() .liveStateEvent(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(voiceBroadcastEvent.root.stateKey!!)) .unwrap() - .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .mapNotNull { event -> + event.asVoiceBroadcastEvent() + ?.takeIf { it.reference?.eventId == eventId } + ?.content?.voiceBroadcastState + } .onEach { state -> when (state) { VoiceBroadcastState.STARTED, From 99a2afa5ee1378050f9b2d334d72166a71a33e54 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:32:16 +0200 Subject: [PATCH 09/12] Add changelog --- changelog.d/7419.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7419.wip diff --git a/changelog.d/7419.wip b/changelog.d/7419.wip new file mode 100644 index 0000000000..06f69dfa7f --- /dev/null +++ b/changelog.d/7419.wip @@ -0,0 +1 @@ +[Voice Broadcast] Live listening support From bafa2f8bde785d85a82df37c4715e7a06f2b261f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 13:43:50 +0200 Subject: [PATCH 10/12] VoiceBroadcastRecorder - Send last sequence number on pause and stop --- .../app/features/voicebroadcast/VoiceBroadcastRecorder.kt | 1 + .../app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt | 2 +- .../voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt | 2 ++ .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 1 + .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt index 37ff920c57..c9bb0c5f54 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorder.kt @@ -23,6 +23,7 @@ import java.io.File interface VoiceBroadcastRecorder : VoiceRecorder { var listener: Listener? + var currentSequence: Int fun startRecord(roomId: String, chunkLength: Int) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt index 21d12ee986..a65aae6f8a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastRecorderQ.kt @@ -30,8 +30,8 @@ class VoiceBroadcastRecorderQ( ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { private var maxFileSize = 0L // zero or negative for no limit - private var currentSequence = 0 private var currentRoomId: String? = null + override var currentSequence = 0 override var listener: VoiceBroadcastRecorder.Listener? = null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index a9db63c538..d882d4049e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -44,6 +44,8 @@ data class MessageVoiceBroadcastInfoContent( @Json(name = "state") val voiceBroadcastStateStr: String = "", /** The length of the voice chunks in seconds. **/ @Json(name = "chunk_length") val chunkLength: Int? = null, + /** The sequence of the last sent chunk. **/ + @Json(name = "last_chunk_sequence") val lastChunkSequence: Int? = null, ) : MessageContent { val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 835a57c102..1430dd8c86 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -59,6 +59,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( body = MessageVoiceBroadcastInfoContent( relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + lastChunkSequence = voiceBroadcastRecorder?.currentSequence, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 6eefa06979..bc6a3e7be6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -60,6 +60,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( body = MessageVoiceBroadcastInfoContent( relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value, + lastChunkSequence = voiceBroadcastRecorder?.currentSequence, ).toContent(), ) From 05eeef9dfec7b89218f4abb430435561b092b607 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 20 Oct 2022 14:36:47 +0200 Subject: [PATCH 11/12] VoiceBroadcastListener - Handle end of live listening --- .../voicebroadcast/VoiceBroadcastPlayer.kt | 65 ++++++------------- ...UseCase.kt => GetVoiceBroadcastUseCase.kt} | 11 ++-- 2 files changed, 25 insertions(+), 51 deletions(-) rename vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/{GetVoiceBroadcastStateUseCase.kt => GetVoiceBroadcastUseCase.kt} (80%) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index c55cb8a1d0..403675a9ee 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -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 { @@ -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,13 +292,17 @@ class VoiceBroadcastPlayer @Inject constructor( } override fun onCompletion(mp: MediaPlayer) { - when { - timelineListener == null && nextMediaPlayer == null -> { - stop() - } - nextMediaPlayer == null -> { - state = State.BUFFERING - } + 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() + } else { + state = State.BUFFERING } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt similarity index 80% rename from vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt rename to vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt index 5b3153ea40..d08fa14a95 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastUseCase.kt @@ -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 } } From 0a9f2bfa0ad050ff99ea546059d868134068dfac Mon Sep 17 00:00:00 2001 From: yostyle Date: Thu, 20 Oct 2022 19:30:17 +0200 Subject: [PATCH 12/12] Fix some PR comments --- .../vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt index 403675a9ee..62252570c6 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastPlayer.kt @@ -116,8 +116,6 @@ class VoiceBroadcastPlayer @Inject constructor( voiceBroadcastStateJob = null // In case of live broadcast, stop observing new chunks - currentTimeline?.dispose() - currentTimeline?.removeAllListeners() currentTimeline = null timelineListener = null