Merge pull request #7646 from vector-im/bugfix/fre/fix_playback_stuck_in_buffering

Voice Broadcast - Fix playback stuck in buffering
This commit is contained in:
Florian Renaud 2022-11-29 09:59:08 +01:00 committed by GitHub
commit 559af32ab6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 39 deletions

1
changelog.d/7646.bugfix Normal file
View file

@ -0,0 +1 @@
Voice Broadcast - Fix playback stuck in buffering mode

View file

@ -36,7 +36,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import timber.log.Timber
@ -73,7 +72,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
@MainThread
set(value) {
if (field != value) {
Timber.w("isLiveListening: $field -> $value")
Timber.d("## Voice Broadcast | isLiveListening: $field -> $value")
field = value
onLiveListeningChanged(value)
}
@ -83,7 +82,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
@MainThread
set(value) {
if (field != value) {
Timber.w("playingState: $field -> $value")
Timber.d("## Voice Broadcast | playingState: $field -> $value")
field = value
onPlayingStateChanged(value)
}
@ -175,41 +174,35 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private fun onPlaylistUpdated() {
when (playingState) {
State.PLAYING -> {
if (nextMediaPlayer == null && !isPreparingNextPlayer) {
prepareNextMediaPlayer()
}
}
State.PLAYING,
State.PAUSED -> {
if (nextMediaPlayer == null && !isPreparingNextPlayer) {
prepareNextMediaPlayer()
}
}
State.BUFFERING -> {
val nextItem = playlist.getNextItem()
val nextItem = if (isLiveListening && playlist.currentSequence == null) {
// live listening, jump to the last item if playback has not started
playlist.lastOrNull()
} else {
// not live or playback already started, request next item
playlist.getNextItem()
}
if (nextItem != null) {
val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) }
startPlayback(savedPosition?.takeIf { it > 0 })
startPlayback(nextItem.startTime)
}
}
State.IDLE -> {
val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) }
startPlayback(savedPosition?.takeIf { it > 0 })
}
State.IDLE -> Unit // Should not happen
}
}
private fun startPlayback(position: Int? = null) {
private fun startPlayback(position: Int) {
stopPlayer()
val playlistItem = when {
position != null -> playlist.findByPosition(position)
mostRecentVoiceBroadcastEvent?.isLive.orFalse() -> playlist.lastOrNull()
else -> playlist.firstOrNull()
}
val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
val sequence = playlistItem.sequence ?: run { Timber.w("## VoiceBroadcastPlayer: playlist item has no sequence"); return }
val sequencePosition = position?.let { it - playlistItem.startTime } ?: 0
val playlistItem = playlist.findByPosition(position)
val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## Voice Broadcast | No content to play at position $position"); return }
val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return }
val sequencePosition = position - playlistItem.startTime
sessionScope.launch {
try {
prepareMediaPlayer(content) { mp ->
@ -223,7 +216,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
prepareNextMediaPlayer()
}
} catch (failure: Throwable) {
Timber.e(failure, "Unable to start playback")
Timber.e(failure, "## Voice Broadcast | Unable to start playback: $failure")
throw VoiceFailure.UnableToPlay(failure)
}
}
@ -248,8 +241,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
currentMediaPlayer?.start()
playingState = State.PLAYING
} else {
val position = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) }
startPlayback(position)
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0
startPlayback(savedPosition)
}
}
@ -274,9 +267,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
isPreparingNextPlayer = true
sessionScope.launch {
prepareMediaPlayer(nextItem.audioEvent.content) { mp ->
nextMediaPlayer = mp
currentMediaPlayer?.setNextMediaPlayer(mp)
isPreparingNextPlayer = false
nextMediaPlayer = mp
when (playingState) {
State.PLAYING,
State.PAUSED -> {
currentMediaPlayer?.setNextMediaPlayer(mp)
}
State.BUFFERING -> {
mp.start()
onNextMediaPlayerStarted(mp)
}
State.IDLE -> stopPlayer()
}
}
}
}
@ -287,7 +290,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
val audioFile = try {
session.fileService().downloadFile(messageAudioContent)
} catch (failure: Throwable) {
Timber.e(failure, "Unable to start playback")
Timber.e(failure, "Voice Broadcast | Download has failed: $failure")
throw VoiceFailure.UnableToPlay(failure)
}
@ -373,6 +376,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
// Notify live mode change to all the listeners attached to the current voice broadcast id
listeners[voiceBroadcastId]?.forEach { listener -> listener.onLiveModeChanged(isLiveListening) }
}
// Live has ended and last chunk has been reached, we can stop the playback
if (!isLiveListening && playingState == State.BUFFERING && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) {
stop()
}
}
private fun onNextMediaPlayerStarted(mp: MediaPlayer) {
playingState = State.PLAYING
playlist.currentSequence = playlist.currentSequence?.inc()
currentMediaPlayer = mp
nextMediaPlayer = null
prepareNextMediaPlayer()
}
private fun getCurrentPlaybackPosition(): Int? {
@ -398,23 +414,24 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean {
when (what) {
MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> {
playlist.currentSequence = playlist.currentSequence?.inc()
currentMediaPlayer = mp
nextMediaPlayer = null
playingState = State.PLAYING
prepareNextMediaPlayer()
}
MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT -> onNextMediaPlayerStarted(mp)
}
return false
}
override fun onCompletion(mp: MediaPlayer) {
// Next media player is already attached to this player and will start playing automatically
if (nextMediaPlayer != null) return
if (isLiveListening || mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence) {
// Next media player is preparing but not attached yet, reset the currentMediaPlayer and let the new player take over
if (isPreparingNextPlayer) {
currentMediaPlayer?.release()
currentMediaPlayer = null
playingState = State.BUFFERING
} else {
return
}
if (!isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence) {
// We'll not receive new chunks anymore so we can stop the live listening
stop()
}