mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Fix pause/resume playback not working correctly
This commit is contained in:
parent
6bdf237cc9
commit
42b3ecc0b6
35
vector/src/main/java/im/vector/app/core/extensions/Flow.kt
Normal file
35
vector/src/main/java/im/vector/app/core/extensions/Flow.kt
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 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.core.extensions
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* Returns a flow that invokes the given action after the first value of the upstream flow is emitted downstream.
|
||||
*/
|
||||
fun <T> Flow<T>.onFirst(action: (T) -> Unit): Flow<T> = flow {
|
||||
var emitted = false
|
||||
collect { value ->
|
||||
emit(value) // always emit value
|
||||
|
||||
if (!emitted) {
|
||||
action(value) // execute the action after the first emission
|
||||
emitted = true
|
||||
}
|
||||
}
|
||||
}
|
@ -149,7 +149,7 @@ class AudioMessageHelper @Inject constructor(
|
||||
}
|
||||
|
||||
private fun startPlayback(id: String, file: File) {
|
||||
val currentPlaybackTime = playbackTracker.getPlaybackTime(id)
|
||||
val currentPlaybackTime = playbackTracker.getPlaybackTime(id) ?: 0
|
||||
|
||||
try {
|
||||
FileInputStream(file).use { fis ->
|
||||
|
@ -67,8 +67,8 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
||||
}
|
||||
|
||||
fun startPlayback(id: String) {
|
||||
val currentPlaybackTime = getPlaybackTime(id)
|
||||
val currentPercentage = getPercentage(id)
|
||||
val currentPlaybackTime = getPlaybackTime(id) ?: 0
|
||||
val currentPercentage = getPercentage(id) ?: 0f
|
||||
val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage)
|
||||
setState(id, currentState)
|
||||
// Pause any active playback
|
||||
@ -85,9 +85,10 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
||||
}
|
||||
|
||||
fun pausePlayback(id: String) {
|
||||
if (getPlaybackState(id) is Listener.State.Playing) {
|
||||
val currentPlaybackTime = getPlaybackTime(id)
|
||||
val currentPercentage = getPercentage(id)
|
||||
val state = getPlaybackState(id)
|
||||
if (state is Listener.State.Playing) {
|
||||
val currentPlaybackTime = state.playbackTime
|
||||
val currentPercentage = state.percentage
|
||||
setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage))
|
||||
}
|
||||
}
|
||||
@ -110,21 +111,23 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
||||
|
||||
fun getPlaybackState(id: String) = states[id]
|
||||
|
||||
fun getPlaybackTime(id: String): Int {
|
||||
fun getPlaybackTime(id: String): Int? {
|
||||
return when (val state = states[id]) {
|
||||
is Listener.State.Playing -> state.playbackTime
|
||||
is Listener.State.Paused -> state.playbackTime
|
||||
/* Listener.State.Idle, */
|
||||
else -> 0
|
||||
is Listener.State.Recording,
|
||||
Listener.State.Idle,
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getPercentage(id: String): Float {
|
||||
fun getPercentage(id: String): Float? {
|
||||
return when (val state = states[id]) {
|
||||
is Listener.State.Playing -> state.percentage
|
||||
is Listener.State.Paused -> state.percentage
|
||||
/* Listener.State.Idle, */
|
||||
else -> 0f
|
||||
is Listener.State.Recording,
|
||||
Listener.State.Idle,
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,14 +141,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
||||
renderBackwardForwardButtons(holder, playbackState)
|
||||
renderLiveIndicator(holder)
|
||||
if (!isUserSeeking) {
|
||||
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId)
|
||||
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) {
|
||||
val isPlayingOrPaused = playbackState is State.Playing || playbackState is State.Paused
|
||||
val playbackTime = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId)
|
||||
val playbackTime = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0
|
||||
val canBackward = isPlayingOrPaused && playbackTime > 0
|
||||
val canForward = isPlayingOrPaused && playbackTime < duration
|
||||
holder.fastBackwardButton.isInvisible = !canBackward
|
||||
|
@ -21,6 +21,7 @@ import android.media.MediaPlayer
|
||||
import android.media.MediaPlayer.OnPreparedListener
|
||||
import androidx.annotation.MainThread
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.onFirst
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.voice.VoiceFailure
|
||||
@ -145,11 +146,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
playingState = State.BUFFERING
|
||||
|
||||
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
||||
fetchPlaylistAndStartPlayback(voiceBroadcast)
|
||||
}
|
||||
|
||||
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
|
||||
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
|
||||
.onFirst { fetchPlaylistAndStartPlayback(voiceBroadcast) }
|
||||
.onEach { onVoiceBroadcastStateEventUpdated(it.getOrNull()) }
|
||||
.launchIn(sessionScope)
|
||||
}
|
||||
@ -222,24 +223,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun pausePlayback(positionMillis: Int? = null) {
|
||||
if (positionMillis == null) {
|
||||
private fun pausePlayback() {
|
||||
playingState = State.PAUSED // This will trigger a playing state update and save the current position
|
||||
if (currentMediaPlayer != null) {
|
||||
currentMediaPlayer?.pause()
|
||||
} else {
|
||||
stopPlayer()
|
||||
val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId
|
||||
val duration = playlist.duration.takeIf { it > 0 }
|
||||
if (voiceBroadcastId != null && duration != null) {
|
||||
playbackTracker.updatePausedAtPlaybackTime(voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration)
|
||||
}
|
||||
}
|
||||
playingState = State.PAUSED
|
||||
}
|
||||
|
||||
private fun resumePlayback() {
|
||||
if (currentMediaPlayer != null) {
|
||||
currentMediaPlayer?.start()
|
||||
playingState = State.PLAYING
|
||||
currentMediaPlayer?.start()
|
||||
} else {
|
||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0
|
||||
startPlayback(savedPosition)
|
||||
@ -256,7 +252,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
startPlayback(positionMillis)
|
||||
}
|
||||
playingState == State.IDLE || playingState == State.PAUSED -> {
|
||||
pausePlayback(positionMillis)
|
||||
stopPlayer()
|
||||
playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -366,8 +363,12 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
isLiveListening && newSequence == playlist.currentSequence
|
||||
}
|
||||
}
|
||||
// otherwise, stay in live or go in live if we reached the latest sequence
|
||||
else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence
|
||||
// if there is no saved position, go in live
|
||||
getCurrentPlaybackPosition() == null -> true
|
||||
// if we reached the latest sequence, go in live
|
||||
playlist.currentSequence == playlist.lastOrNull()?.sequence -> true
|
||||
// otherwise, do not change
|
||||
else -> isLiveListening
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,9 +393,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
}
|
||||
|
||||
private fun getCurrentPlaybackPosition(): Int? {
|
||||
val playlistPosition = playlist.currentItem?.startTime
|
||||
val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition
|
||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) }
|
||||
val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null
|
||||
val computedPosition = currentMediaPlayer?.currentPosition?.let { playlist.currentItem?.startTime?.plus(it) }
|
||||
val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId)
|
||||
return computedPosition ?: savedPosition
|
||||
}
|
||||
|
||||
@ -423,19 +424,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
// Next media player is already attached to this player and will start playing automatically
|
||||
if (nextMediaPlayer != null) return
|
||||
|
||||
// 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
|
||||
return
|
||||
}
|
||||
|
||||
if (!isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence) {
|
||||
val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence
|
||||
if (hasEnded) {
|
||||
// We'll not receive new chunks anymore so we can stop the live listening
|
||||
stop()
|
||||
} else {
|
||||
// Enter in buffering mode and release current media player
|
||||
playingState = State.BUFFERING
|
||||
currentMediaPlayer?.release()
|
||||
currentMediaPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user