mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-09 08:38:43 +01:00
Merge pull request #7655 from vector-im/feature/fre/voice_broadcast_buffering
[Voice Broadcast] Update buffering display and improve playback
This commit is contained in:
commit
5560694f54
1
changelog.d/7655.wip
Normal file
1
changelog.d/7655.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
Voice Broadcast - Update the buffering display in the timeline
|
@ -3094,12 +3094,13 @@
|
|||||||
<string name="audio_message_file_size">(%1$s)</string>
|
<string name="audio_message_file_size">(%1$s)</string>
|
||||||
|
|
||||||
<string name="voice_broadcast_live">Live</string>
|
<string name="voice_broadcast_live">Live</string>
|
||||||
|
<!-- TODO Rename id to voice_broadcast_buffering -->
|
||||||
|
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
|
||||||
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
||||||
<string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string>
|
<string name="a11y_pause_voice_broadcast_record">Pause voice broadcast record</string>
|
||||||
<string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string>
|
<string name="a11y_stop_voice_broadcast_record">Stop voice broadcast record</string>
|
||||||
<string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string>
|
<string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string>
|
||||||
<string name="a11y_pause_voice_broadcast">Pause voice broadcast</string>
|
<string name="a11y_pause_voice_broadcast">Pause voice broadcast</string>
|
||||||
<string name="a11y_voice_broadcast_buffering">Buffering</string>
|
|
||||||
<string name="a11y_voice_broadcast_fast_backward">Fast backward 30 seconds</string>
|
<string name="a11y_voice_broadcast_fast_backward">Fast backward 30 seconds</string>
|
||||||
<string name="a11y_voice_broadcast_fast_forward">Fast forward 30 seconds</string>
|
<string name="a11y_voice_broadcast_fast_forward">Fast forward 30 seconds</string>
|
||||||
<string name="error_voice_broadcast_unauthorized_title">Can’t start a new voice broadcast</string>
|
<string name="error_voice_broadcast_unauthorized_title">Can’t start a new voice broadcast</string>
|
||||||
|
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) {
|
private fun startPlayback(id: String, file: File) {
|
||||||
val currentPlaybackTime = playbackTracker.getPlaybackTime(id)
|
val currentPlaybackTime = playbackTracker.getPlaybackTime(id) ?: 0
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileInputStream(file).use { fis ->
|
FileInputStream(file).use { fis ->
|
||||||
|
@ -67,8 +67,8 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startPlayback(id: String) {
|
fun startPlayback(id: String) {
|
||||||
val currentPlaybackTime = getPlaybackTime(id)
|
val currentPlaybackTime = getPlaybackTime(id) ?: 0
|
||||||
val currentPercentage = getPercentage(id)
|
val currentPercentage = getPercentage(id) ?: 0f
|
||||||
val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage)
|
val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage)
|
||||||
setState(id, currentState)
|
setState(id, currentState)
|
||||||
// Pause any active playback
|
// Pause any active playback
|
||||||
@ -85,9 +85,10 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pausePlayback(id: String) {
|
fun pausePlayback(id: String) {
|
||||||
if (getPlaybackState(id) is Listener.State.Playing) {
|
val state = getPlaybackState(id)
|
||||||
val currentPlaybackTime = getPlaybackTime(id)
|
if (state is Listener.State.Playing) {
|
||||||
val currentPercentage = getPercentage(id)
|
val currentPlaybackTime = state.playbackTime
|
||||||
|
val currentPercentage = state.percentage
|
||||||
setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage))
|
setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,21 +111,23 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
|||||||
|
|
||||||
fun getPlaybackState(id: String) = states[id]
|
fun getPlaybackState(id: String) = states[id]
|
||||||
|
|
||||||
fun getPlaybackTime(id: String): Int {
|
fun getPlaybackTime(id: String): Int? {
|
||||||
return when (val state = states[id]) {
|
return when (val state = states[id]) {
|
||||||
is Listener.State.Playing -> state.playbackTime
|
is Listener.State.Playing -> state.playbackTime
|
||||||
is Listener.State.Paused -> state.playbackTime
|
is Listener.State.Paused -> state.playbackTime
|
||||||
/* Listener.State.Idle, */
|
is Listener.State.Recording,
|
||||||
else -> 0
|
Listener.State.Idle,
|
||||||
|
null -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPercentage(id: String): Float {
|
fun getPercentage(id: String): Float? {
|
||||||
return when (val state = states[id]) {
|
return when (val state = states[id]) {
|
||||||
is Listener.State.Playing -> state.percentage
|
is Listener.State.Playing -> state.percentage
|
||||||
is Listener.State.Paused -> state.percentage
|
is Listener.State.Paused -> state.percentage
|
||||||
/* Listener.State.Idle, */
|
is Listener.State.Recording,
|
||||||
else -> 0f
|
Listener.State.Idle,
|
||||||
|
null -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@ -30,6 +29,7 @@ import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAc
|
|||||||
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State
|
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker.Listener.State
|
||||||
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastBufferingView
|
||||||
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
@ -63,10 +63,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
playPauseButton.setOnClickListener {
|
playPauseButton.setOnClickListener {
|
||||||
if (player.currentVoiceBroadcast == voiceBroadcast) {
|
if (player.currentVoiceBroadcast == voiceBroadcast) {
|
||||||
when (player.playingState) {
|
when (player.playingState) {
|
||||||
VoiceBroadcastPlayer.State.PLAYING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause)
|
VoiceBroadcastPlayer.State.PLAYING,
|
||||||
|
VoiceBroadcastPlayer.State.BUFFERING -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause)
|
||||||
VoiceBroadcastPlayer.State.PAUSED,
|
VoiceBroadcastPlayer.State.PAUSED,
|
||||||
VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
|
VoiceBroadcastPlayer.State.IDLE -> callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
|
||||||
VoiceBroadcastPlayer.State.BUFFERING -> Unit
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
|
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcast))
|
||||||
@ -86,7 +86,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
override fun renderMetadata(holder: Holder) {
|
override fun renderMetadata(holder: Holder) {
|
||||||
with(holder) {
|
with(holder) {
|
||||||
broadcasterNameMetadata.value = recorderName
|
broadcasterNameMetadata.value = recorderName
|
||||||
voiceBroadcastMetadata.isVisible = true
|
|
||||||
listenersCountMetadata.isVisible = false
|
listenersCountMetadata.isVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,10 +101,11 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) {
|
private fun renderPlayingState(holder: Holder, state: VoiceBroadcastPlayer.State) {
|
||||||
with(holder) {
|
with(holder) {
|
||||||
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
|
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
|
||||||
playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
|
voiceBroadcastMetadata.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
VoiceBroadcastPlayer.State.PLAYING -> {
|
VoiceBroadcastPlayer.State.PLAYING,
|
||||||
|
VoiceBroadcastPlayer.State.BUFFERING -> {
|
||||||
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
|
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
|
||||||
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
|
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
|
||||||
}
|
}
|
||||||
@ -114,7 +114,6 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
|
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
|
||||||
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
|
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
|
||||||
}
|
}
|
||||||
VoiceBroadcastPlayer.State.BUFFERING -> Unit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLiveIndicator(holder)
|
renderLiveIndicator(holder)
|
||||||
@ -142,14 +141,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
renderBackwardForwardButtons(holder, playbackState)
|
renderBackwardForwardButtons(holder, playbackState)
|
||||||
renderLiveIndicator(holder)
|
renderLiveIndicator(holder)
|
||||||
if (!isUserSeeking) {
|
if (!isUserSeeking) {
|
||||||
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId)
|
holder.seekBar.progress = playbackTracker.getPlaybackTime(voiceBroadcast.voiceBroadcastId) ?: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) {
|
private fun renderBackwardForwardButtons(holder: Holder, playbackState: State) {
|
||||||
val isPlayingOrPaused = playbackState is State.Playing || playbackState is State.Paused
|
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 canBackward = isPlayingOrPaused && playbackTime > 0
|
||||||
val canForward = isPlayingOrPaused && playbackTime < duration
|
val canForward = isPlayingOrPaused && playbackTime < duration
|
||||||
holder.fastBackwardButton.isInvisible = !canBackward
|
holder.fastBackwardButton.isInvisible = !canBackward
|
||||||
@ -174,7 +173,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
|
|||||||
|
|
||||||
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
|
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
|
||||||
val playPauseButton by bind<ImageButton>(R.id.playPauseButton)
|
val playPauseButton by bind<ImageButton>(R.id.playPauseButton)
|
||||||
val bufferingView by bind<View>(R.id.bufferingView)
|
val bufferingView by bind<VoiceBroadcastBufferingView>(R.id.bufferingMetadata)
|
||||||
val fastBackwardButton by bind<ImageButton>(R.id.fastBackwardButton)
|
val fastBackwardButton by bind<ImageButton>(R.id.fastBackwardButton)
|
||||||
val fastForwardButton by bind<ImageButton>(R.id.fastForwardButton)
|
val fastForwardButton by bind<ImageButton>(R.id.fastForwardButton)
|
||||||
val seekBar by bind<SeekBar>(R.id.seekBar)
|
val seekBar by bind<SeekBar>(R.id.seekBar)
|
||||||
|
@ -21,6 +21,7 @@ import android.media.MediaPlayer
|
|||||||
import android.media.MediaPlayer.OnPreparedListener
|
import android.media.MediaPlayer.OnPreparedListener
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
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.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.voice.VoiceFailure
|
import im.vector.app.features.voice.VoiceFailure
|
||||||
@ -145,11 +146,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
playingState = State.BUFFERING
|
playingState = State.BUFFERING
|
||||||
|
|
||||||
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
observeVoiceBroadcastStateEvent(voiceBroadcast)
|
||||||
fetchPlaylistAndStartPlayback(voiceBroadcast)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
|
private fun observeVoiceBroadcastStateEvent(voiceBroadcast: VoiceBroadcast) {
|
||||||
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
|
voiceBroadcastStateObserver = getVoiceBroadcastEventUseCase.execute(voiceBroadcast)
|
||||||
|
.onFirst { fetchPlaylistAndStartPlayback(voiceBroadcast) }
|
||||||
.onEach { onVoiceBroadcastStateEventUpdated(it.getOrNull()) }
|
.onEach { onVoiceBroadcastStateEventUpdated(it.getOrNull()) }
|
||||||
.launchIn(sessionScope)
|
.launchIn(sessionScope)
|
||||||
}
|
}
|
||||||
@ -222,24 +223,19 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pausePlayback(positionMillis: Int? = null) {
|
private fun pausePlayback() {
|
||||||
if (positionMillis == null) {
|
playingState = State.PAUSED // This will trigger a playing state update and save the current position
|
||||||
|
if (currentMediaPlayer != null) {
|
||||||
currentMediaPlayer?.pause()
|
currentMediaPlayer?.pause()
|
||||||
} else {
|
} else {
|
||||||
stopPlayer()
|
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() {
|
private fun resumePlayback() {
|
||||||
if (currentMediaPlayer != null) {
|
if (currentMediaPlayer != null) {
|
||||||
currentMediaPlayer?.start()
|
|
||||||
playingState = State.PLAYING
|
playingState = State.PLAYING
|
||||||
|
currentMediaPlayer?.start()
|
||||||
} else {
|
} else {
|
||||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0
|
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0
|
||||||
startPlayback(savedPosition)
|
startPlayback(savedPosition)
|
||||||
@ -256,7 +252,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
startPlayback(positionMillis)
|
startPlayback(positionMillis)
|
||||||
}
|
}
|
||||||
playingState == State.IDLE || playingState == State.PAUSED -> {
|
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
|
isLiveListening && newSequence == playlist.currentSequence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// otherwise, stay in live or go in live if we reached the latest sequence
|
// if there is no saved position, go in live
|
||||||
else -> isLiveListening || playlist.currentSequence == playlist.lastOrNull()?.sequence
|
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? {
|
private fun getCurrentPlaybackPosition(): Int? {
|
||||||
val playlistPosition = playlist.currentItem?.startTime
|
val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null
|
||||||
val computedPosition = currentMediaPlayer?.currentPosition?.let { playlistPosition?.plus(it) } ?: playlistPosition
|
val computedPosition = currentMediaPlayer?.currentPosition?.let { playlist.currentItem?.startTime?.plus(it) }
|
||||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) }
|
val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId)
|
||||||
return computedPosition ?: savedPosition
|
return computedPosition ?: savedPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,17 +424,15 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
// Next media player is already attached to this player and will start playing automatically
|
// Next media player is already attached to this player and will start playing automatically
|
||||||
if (nextMediaPlayer != null) return
|
if (nextMediaPlayer != null) return
|
||||||
|
|
||||||
// Next media player is preparing but not attached yet, reset the currentMediaPlayer and let the new player take over
|
val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence
|
||||||
if (isPreparingNextPlayer) {
|
if (hasEnded) {
|
||||||
currentMediaPlayer?.release()
|
|
||||||
currentMediaPlayer = null
|
|
||||||
playingState = State.BUFFERING
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence) {
|
|
||||||
// We'll not receive new chunks anymore so we can stop the live listening
|
// We'll not receive new chunks anymore so we can stop the live listening
|
||||||
stop()
|
stop()
|
||||||
|
} else {
|
||||||
|
// Enter in buffering mode and release current media player
|
||||||
|
playingState = State.BUFFERING
|
||||||
|
currentMediaPlayer?.release()
|
||||||
|
currentMediaPlayer = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import im.vector.app.databinding.ViewVoiceBroadcastBufferingBinding
|
||||||
|
|
||||||
|
class VoiceBroadcastBufferingView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
ViewVoiceBroadcastBufferingBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -37,9 +37,9 @@ class VoiceBroadcastMetadataView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
var value: String
|
var value: String
|
||||||
get() = views.metadataValue.text.toString()
|
get() = views.metadataText.text.toString()
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
views.metadataValue.text = newValue
|
views.metadataText.text = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -61,6 +61,6 @@ class VoiceBroadcastMetadataView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private fun setValue(typedArray: TypedArray) {
|
private fun setValue(typedArray: TypedArray) {
|
||||||
val value = typedArray.getString(R.styleable.VoiceBroadcastMetadataView_metadataValue)
|
val value = typedArray.getString(R.styleable.VoiceBroadcastMetadataView_metadataValue)
|
||||||
views.metadataValue.text = value
|
views.metadataText.text = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
<shape
|
<shape
|
||||||
android:shape="line">
|
android:shape="line">
|
||||||
<stroke
|
<stroke
|
||||||
android:color="?vctr_content_tertiary"
|
android:color="?vctr_content_secondary"
|
||||||
android:width="2dp"/>
|
android:width="2dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</clip>
|
</clip>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:progressDrawable="@drawable/bg_seek_bar"
|
android:progressDrawable="@drawable/bg_seek_bar"
|
||||||
android:thumbTint="?vctr_content_tertiary"
|
android:thumbTint="?vctr_content_secondary"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/audioPlaybackTime"
|
app:layout_constraintEnd_toStartOf="@id/audioPlaybackTime"
|
||||||
app:layout_constraintTop_toBottomOf="@id/audioPlaybackControlButton"
|
app:layout_constraintTop_toBottomOf="@id/audioPlaybackControlButton"
|
||||||
@ -85,7 +85,7 @@
|
|||||||
style="@style/Widget.Vector.TextView.Caption"
|
style="@style/Widget.Vector.TextView.Caption"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="?vctr_content_tertiary"
|
android:textColor="?vctr_content_secondary"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/audioSeekBar"
|
app:layout_constraintTop_toTopOf="@id/audioSeekBar"
|
||||||
@ -104,4 +104,4 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/rounded_rect_shape_8"
|
android:background="@drawable/rounded_rect_shape_8"
|
||||||
android:backgroundTint="?vctr_content_quinary"
|
android:backgroundTint="?vctr_content_quinary"
|
||||||
android:padding="@dimen/layout_vertical_margin">
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/liveIndicator"
|
android:id="@+id/liveIndicator"
|
||||||
@ -51,7 +53,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:constraint_referenced_ids="broadcasterNameMetadata,voiceBroadcastMetadata,listenersCountMetadata"
|
app:constraint_referenced_ids="broadcasterNameMetadata,bufferingMetadata,voiceBroadcastMetadata,listenersCountMetadata"
|
||||||
app:flow_horizontalAlign="start"
|
app:flow_horizontalAlign="start"
|
||||||
app:flow_verticalGap="4dp"
|
app:flow_verticalGap="4dp"
|
||||||
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
app:layout_constraintStart_toEndOf="@id/avatarRightBarrier"
|
||||||
@ -64,6 +66,11 @@
|
|||||||
app:metadataIcon="@drawable/ic_voice_broadcast_mic"
|
app:metadataIcon="@drawable/ic_voice_broadcast_mic"
|
||||||
tools:metadataValue="@sample/users.json/data/displayName" />
|
tools:metadataValue="@sample/users.json/data/displayName" />
|
||||||
|
|
||||||
|
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastBufferingView
|
||||||
|
android:id="@+id/bufferingMetadata"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
<im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
|
||||||
android:id="@+id/voiceBroadcastMetadata"
|
android:id="@+id/voiceBroadcastMetadata"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -92,7 +99,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
app:constraint_referenced_ids="fastBackwardButton,playPauseButton,bufferingView,fastForwardButton"
|
app:constraint_referenced_ids="fastBackwardButton,playPauseButton,fastForwardButton"
|
||||||
app:layout_constraintBottom_toTopOf="@id/seekBar"
|
app:layout_constraintBottom_toTopOf="@id/seekBar"
|
||||||
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" />
|
app:layout_constraintTop_toBottomOf="@id/headerBottomBarrier" />
|
||||||
|
|
||||||
@ -117,16 +124,6 @@
|
|||||||
android:src="@drawable/ic_play_pause_play"
|
android:src="@drawable/ic_play_pause_play"
|
||||||
app:tint="?vctr_content_secondary" />
|
app:tint="?vctr_content_secondary" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/bufferingView"
|
|
||||||
android:layout_width="@dimen/voice_broadcast_player_button_size"
|
|
||||||
android:layout_height="@dimen/voice_broadcast_player_button_size"
|
|
||||||
android:contentDescription="@string/a11y_voice_broadcast_buffering"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:indeterminateTint="?vctr_content_secondary"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/fastForwardButton"
|
android:id="@+id/fastForwardButton"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
@ -143,13 +140,17 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
android:progressDrawable="@drawable/bg_seek_bar"
|
android:progressDrawable="@drawable/bg_seek_bar"
|
||||||
android:thumbTint="?vctr_content_tertiary"
|
android:thumbTint="?vctr_content_secondary"
|
||||||
|
android:thumbOffset="3dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/playbackDuration"
|
app:layout_constraintEnd_toStartOf="@id/playbackDuration"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/controllerButtonsFlow"
|
app:layout_constraintTop_toBottomOf="@id/controllerButtonsFlow"
|
||||||
tools:progress="40" />
|
tools:progress="0" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/playbackDuration"
|
android:id="@+id/playbackDuration"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/rounded_rect_shape_8"
|
android:background="@drawable/rounded_rect_shape_8"
|
||||||
android:backgroundTint="?vctr_content_quinary"
|
android:backgroundTint="?vctr_content_quinary"
|
||||||
android:padding="@dimen/layout_vertical_margin">
|
android:padding="12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/liveIndicator"
|
android:id="@+id/liveIndicator"
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/bufferingIcon"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:indeterminateTint="?vctr_content_secondary"
|
||||||
|
tools:src="@drawable/ic_voice_broadcast" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bufferingText"
|
||||||
|
style="@style/Widget.Vector.TextView.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/a11y_voice_broadcast_buffering" />
|
||||||
|
</merge>
|
@ -18,7 +18,7 @@
|
|||||||
tools:src="@drawable/ic_voice_broadcast" />
|
tools:src="@drawable/ic_voice_broadcast" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/metadata_value"
|
android:id="@+id/metadataText"
|
||||||
style="@style/Widget.Vector.TextView.Caption"
|
style="@style/Widget.Vector.TextView.Caption"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user