Skip to content

Commit f34758c

Browse files
authored
Merge pull request #7494 from vector-im/feature/fre/voice_broadcast_seek_to
Voice Broadcast - Add seek bar with basic implementation
2 parents 98e0397 + 481388e commit f34758c

16 files changed

+219
-46
lines changed

library/ui-strings/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3094,6 +3094,8 @@
30943094
<string name="a11y_play_voice_broadcast">Play or resume voice broadcast</string>
30953095
<string name="a11y_pause_voice_broadcast">Pause voice broadcast</string>
30963096
<string name="a11y_voice_broadcast_buffering">Buffering</string>
3097+
<string name="a11y_voice_broadcast_fast_backward">Fast backward 30 seconds</string>
3098+
<string name="a11y_voice_broadcast_fast_forward">Fast forward 30 seconds</string>
30973099
<string name="error_voice_broadcast_unauthorized_title">Can’t start a new voice broadcast</string>
30983100
<string name="error_voice_broadcast_permission_denied_message">You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.</string>
30993101
<string name="error_voice_broadcast_blocked_by_someone_else_message">Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.</string>

library/ui-styles/src/main/res/values/dimens.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@
7474
<dimen name="location_sharing_live_duration_choice_margin_vertical">22dp</dimen>
7575

7676
<!-- Voice Broadcast -->
77-
<dimen name="voice_broadcast_controller_button_size">48dp</dimen>
77+
<dimen name="voice_broadcast_recorder_button_size">48dp</dimen>
78+
<dimen name="voice_broadcast_player_button_size">36dp</dimen>
7879

7980
<!-- Material 3 -->
8081
<dimen name="collapsing_toolbar_layout_medium_size">112dp</dimen>

vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ sealed class RoomDetailAction : VectorViewModelAction {
129129
}
130130

131131
sealed class Listening : VoiceBroadcastAction() {
132-
data class PlayOrResume(val eventId: String) : Listening()
132+
data class PlayOrResume(val voiceBroadcastId: String) : Listening()
133133
object Pause : Listening()
134134
object Stop : Listening()
135+
data class SeekTo(val voiceBroadcastId: String, val positionMillis: Int) : Listening()
135136
}
136137
}
137138
}

vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
5050
import im.vector.app.features.createdirect.DirectRoomHelper
5151
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
5252
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
53+
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
5354
import im.vector.app.features.home.room.detail.error.RoomNotFound
5455
import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase
5556
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
@@ -478,7 +479,7 @@ class TimelineViewModel @AssistedInject constructor(
478479
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
479480
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
480481
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
481-
is RoomDetailAction.VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
482+
is VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
482483
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
483484
is RoomDetailAction.StartCall -> handleStartCall(action)
484485
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
@@ -620,22 +621,23 @@ class TimelineViewModel @AssistedInject constructor(
620621
}
621622
}
622623

623-
private fun handleVoiceBroadcastAction(action: RoomDetailAction.VoiceBroadcastAction) {
624+
private fun handleVoiceBroadcastAction(action: VoiceBroadcastAction) {
624625
if (room == null) return
625626
viewModelScope.launch {
626627
when (action) {
627-
RoomDetailAction.VoiceBroadcastAction.Recording.Start -> {
628+
VoiceBroadcastAction.Recording.Start -> {
628629
voiceBroadcastHelper.startVoiceBroadcast(room.roomId).fold(
629630
{ _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) },
630631
{ _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, it)) },
631632
)
632633
}
633-
RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
634-
RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
635-
RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
636-
is RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.eventId)
637-
RoomDetailAction.VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
638-
RoomDetailAction.VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
634+
VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
635+
VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
636+
VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
637+
is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.voiceBroadcastId)
638+
VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
639+
VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
640+
is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcastId, action.positionMillis)
639641
}
640642
}
641643
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
6767
val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes(
6868
voiceBroadcastId = voiceBroadcastId,
6969
voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState,
70+
duration = voiceBroadcastEventsGroup.getDuration(),
7071
recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(),
7172
recorder = voiceBroadcastRecorder,
7273
player = voiceBroadcastPlayer,

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper
1818

1919
import im.vector.app.core.utils.TextUtils
2020
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
21+
import im.vector.app.features.voicebroadcast.duration
2122
import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId
2223
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
2324
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
@@ -148,4 +149,8 @@ class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) {
148149
return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED }
149150
?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L }
150151
}
152+
153+
fun getDuration(): Int {
154+
return group.events.mapNotNull { it.root.asMessageAudioEvent()?.duration }.sum()
155+
}
151156
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ abstract class AbsMessageVoiceBroadcastItem<H : AbsMessageVoiceBroadcastItem.Hol
9494
data class Attributes(
9595
val voiceBroadcastId: String,
9696
val voiceBroadcastState: VoiceBroadcastState?,
97+
val duration: Int,
9798
val recorderName: String,
9899
val recorder: VoiceBroadcastRecorder?,
99100
val player: VoiceBroadcastPlayer,

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616

1717
package im.vector.app.features.home.room.detail.timeline.item
1818

19+
import android.text.format.DateUtils
1920
import android.view.View
2021
import android.widget.ImageButton
22+
import android.widget.SeekBar
23+
import android.widget.TextView
24+
import androidx.core.view.isInvisible
2125
import androidx.core.view.isVisible
2226
import com.airbnb.epoxy.EpoxyModelClass
2327
import im.vector.app.R
2428
import im.vector.app.core.epoxy.onClick
25-
import im.vector.app.features.home.room.detail.RoomDetailAction
29+
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
2630
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
2731
import im.vector.app.features.voicebroadcast.views.VoiceBroadcastMetadataView
2832

@@ -41,6 +45,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
4145
renderPlayingState(holder, state)
4246
}
4347
player.addListener(voiceBroadcastId, playerListener)
48+
bindSeekBar(holder)
4449
}
4550

4651
override fun renderMetadata(holder: Holder) {
@@ -56,35 +61,61 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
5661
bufferingView.isVisible = state == VoiceBroadcastPlayer.State.BUFFERING
5762
playPauseButton.isVisible = state != VoiceBroadcastPlayer.State.BUFFERING
5863

64+
fastBackwardButton.isInvisible = true
65+
fastForwardButton.isInvisible = true
66+
5967
when (state) {
6068
VoiceBroadcastPlayer.State.PLAYING -> {
6169
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
6270
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
63-
playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) }
71+
playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) }
72+
seekBar.isEnabled = true
6473
}
6574
VoiceBroadcastPlayer.State.IDLE,
6675
VoiceBroadcastPlayer.State.PAUSED -> {
6776
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
6877
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
69-
playPauseButton.onClick {
70-
callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
71-
}
78+
playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) }
79+
seekBar.isEnabled = false
80+
}
81+
VoiceBroadcastPlayer.State.BUFFERING -> {
82+
seekBar.isEnabled = true
7283
}
73-
VoiceBroadcastPlayer.State.BUFFERING -> Unit
7484
}
7585
}
7686
}
7787

88+
private fun bindSeekBar(holder: Holder) {
89+
holder.durationView.text = formatPlaybackTime(voiceBroadcastAttributes.duration)
90+
holder.seekBar.max = voiceBroadcastAttributes.duration
91+
holder.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
92+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
93+
94+
override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
95+
96+
override fun onStopTrackingTouch(seekBar: SeekBar) {
97+
callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcastId, seekBar.progress))
98+
}
99+
})
100+
}
101+
102+
private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
103+
78104
override fun unbind(holder: Holder) {
79105
super.unbind(holder)
80106
player.removeListener(voiceBroadcastId, playerListener)
107+
holder.seekBar.setOnSeekBarChangeListener(null)
81108
}
82109

83110
override fun getViewStubId() = STUB_ID
84111

85112
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
86113
val playPauseButton by bind<ImageButton>(R.id.playPauseButton)
87114
val bufferingView by bind<View>(R.id.bufferingView)
115+
val fastBackwardButton by bind<ImageButton>(R.id.fastBackwardButton)
116+
val fastForwardButton by bind<ImageButton>(R.id.fastForwardButton)
117+
val seekBar by bind<SeekBar>(R.id.seekBar)
118+
val durationView by bind<TextView>(R.id.playbackDuration)
88119
val broadcasterNameMetadata by bind<VoiceBroadcastMetadataView>(R.id.broadcasterNameMetadata)
89120
val voiceBroadcastMetadata by bind<VoiceBroadcastMetadataView>(R.id.voiceBroadcastMetadata)
90121
val listenersCountMetadata by bind<VoiceBroadcastMetadataView>(R.id.listenersCountMetadata)

vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? {
3232
}
3333

3434
val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence
35+
36+
val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0

vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ class VoiceBroadcastHelper @Inject constructor(
4141

4242
suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId)
4343

44-
fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.playOrResume(roomId, eventId)
44+
fun playOrResumePlayback(roomId: String, voiceBroadcastId: String) = voiceBroadcastPlayer.playOrResume(roomId, voiceBroadcastId)
4545

4646
fun pausePlayback() = voiceBroadcastPlayer.pause()
4747

4848
fun stopPlayback() = voiceBroadcastPlayer.stop()
49+
50+
fun seekTo(voiceBroadcastId: String, positionMillis: Int) {
51+
if (voiceBroadcastPlayer.currentVoiceBroadcastId == voiceBroadcastId) {
52+
voiceBroadcastPlayer.seekTo(positionMillis)
53+
}
54+
}
4955
}

0 commit comments

Comments
 (0)