diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 2ea209a8f0..450eb64849 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3094,6 +3094,8 @@
Play or resume voice broadcast
Pause voice broadcast
Buffering
+ Fast backward 30 seconds
+ Fast forward 30 seconds
Can’t start a new voice broadcast
You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.
Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 50d5aaf014..22c2a3e62c 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -74,7 +74,8 @@
22dp
- 48dp
+ 48dp
+ 36dp
112dp
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index f773671694..8c49213a42 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -129,9 +129,10 @@ sealed class RoomDetailAction : VectorViewModelAction {
}
sealed class Listening : VoiceBroadcastAction() {
- data class PlayOrResume(val eventId: String) : Listening()
+ data class PlayOrResume(val voiceBroadcastId: String) : Listening()
object Pause : Listening()
object Stop : Listening()
+ data class SeekTo(val voiceBroadcastId: String, val positionMillis: Int) : Listening()
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 50bebc81e4..3f4fae1ce9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -50,6 +50,7 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
+import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
import im.vector.app.features.home.room.detail.error.RoomNotFound
import im.vector.app.features.home.room.detail.location.RedactLiveLocationShareEventUseCase
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
@@ -478,7 +479,7 @@ class TimelineViewModel @AssistedInject constructor(
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
- is RoomDetailAction.VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
+ is VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
is RoomDetailAction.StartCall -> handleStartCall(action)
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
@@ -620,22 +621,23 @@ class TimelineViewModel @AssistedInject constructor(
}
}
- private fun handleVoiceBroadcastAction(action: RoomDetailAction.VoiceBroadcastAction) {
+ private fun handleVoiceBroadcastAction(action: VoiceBroadcastAction) {
if (room == null) return
viewModelScope.launch {
when (action) {
- RoomDetailAction.VoiceBroadcastAction.Recording.Start -> {
+ VoiceBroadcastAction.Recording.Start -> {
voiceBroadcastHelper.startVoiceBroadcast(room.roomId).fold(
{ _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) },
{ _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, it)) },
)
}
- RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
- RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
- RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
- is RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.eventId)
- RoomDetailAction.VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
- RoomDetailAction.VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
+ VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
+ VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
+ VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
+ is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(room.roomId, action.voiceBroadcastId)
+ VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
+ VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
+ is VoiceBroadcastAction.Listening.SeekTo -> voiceBroadcastHelper.seekTo(action.voiceBroadcastId, action.positionMillis)
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt
index 56498fa8d3..5d9c663210 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt
@@ -67,6 +67,7 @@ class VoiceBroadcastItemFactory @Inject constructor(
val voiceBroadcastAttributes = AbsMessageVoiceBroadcastItem.Attributes(
voiceBroadcastId = voiceBroadcastId,
voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState,
+ duration = voiceBroadcastEventsGroup.getDuration(),
recorderName = params.event.root.stateKey?.let { session.getUserOrDefault(it) }?.toMatrixItem()?.getBestName().orEmpty(),
recorder = voiceBroadcastRecorder,
player = voiceBroadcastPlayer,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
index 8a3be7d5f2..a4bfa9e155 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper
import im.vector.app.core.utils.TextUtils
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
+import im.vector.app.features.voicebroadcast.duration
import im.vector.app.features.voicebroadcast.getVoiceBroadcastEventId
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
@@ -148,4 +149,8 @@ class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) {
return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED }
?: group.events.filter { it.root.type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO }.maxBy { it.root.originServerTs ?: 0L }
}
+
+ fun getDuration(): Int {
+ return group.events.mapNotNull { it.root.asMessageAudioEvent()?.duration }.sum()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt
index ba9d582ea4..7ada0c71f2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt
@@ -94,6 +94,7 @@ abstract class AbsMessageVoiceBroadcastItem {
playPauseButton.setImageResource(R.drawable.ic_play_pause_pause)
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_pause_voice_broadcast)
- playPauseButton.onClick { callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.Pause) }
+ playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.Pause) }
+ seekBar.isEnabled = true
}
VoiceBroadcastPlayer.State.IDLE,
VoiceBroadcastPlayer.State.PAUSED -> {
playPauseButton.setImageResource(R.drawable.ic_play_pause_play)
playPauseButton.contentDescription = view.resources.getString(R.string.a11y_play_voice_broadcast)
- playPauseButton.onClick {
- callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId))
- }
+ playPauseButton.onClick { callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.PlayOrResume(voiceBroadcastId)) }
+ seekBar.isEnabled = false
+ }
+ VoiceBroadcastPlayer.State.BUFFERING -> {
+ seekBar.isEnabled = true
}
- VoiceBroadcastPlayer.State.BUFFERING -> Unit
}
}
}
+ private fun bindSeekBar(holder: Holder) {
+ holder.durationView.text = formatPlaybackTime(voiceBroadcastAttributes.duration)
+ holder.seekBar.max = voiceBroadcastAttributes.duration
+ holder.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ callback?.onTimelineItemAction(VoiceBroadcastAction.Listening.SeekTo(voiceBroadcastId, seekBar.progress))
+ }
+ })
+ }
+
+ private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
+
override fun unbind(holder: Holder) {
super.unbind(holder)
player.removeListener(voiceBroadcastId, playerListener)
+ holder.seekBar.setOnSeekBarChangeListener(null)
}
override fun getViewStubId() = STUB_ID
@@ -85,6 +112,10 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
class Holder : AbsMessageVoiceBroadcastItem.Holder(STUB_ID) {
val playPauseButton by bind(R.id.playPauseButton)
val bufferingView by bind(R.id.bufferingView)
+ val fastBackwardButton by bind(R.id.fastBackwardButton)
+ val fastForwardButton by bind(R.id.fastForwardButton)
+ val seekBar by bind(R.id.seekBar)
+ val durationView by bind(R.id.playbackDuration)
val broadcasterNameMetadata by bind(R.id.broadcasterNameMetadata)
val voiceBroadcastMetadata by bind(R.id.voiceBroadcastMetadata)
val listenersCountMetadata by bind(R.id.listenersCountMetadata)
diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt
index f9da2e76b1..48554f51d0 100644
--- a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt
+++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastExtensions.kt
@@ -32,3 +32,5 @@ fun MessageAudioEvent.getVoiceBroadcastChunk(): VoiceBroadcastChunk? {
}
val MessageAudioEvent.sequence: Int? get() = getVoiceBroadcastChunk()?.sequence
+
+val MessageAudioEvent.duration get() = content.audioInfo?.duration ?: content.audioWaveformInfo?.duration ?: 0
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 dfc8e35422..7864d3b4e3 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
@@ -41,9 +41,15 @@ class VoiceBroadcastHelper @Inject constructor(
suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId)
- fun playOrResumePlayback(roomId: String, eventId: String) = voiceBroadcastPlayer.playOrResume(roomId, eventId)
+ fun playOrResumePlayback(roomId: String, voiceBroadcastId: String) = voiceBroadcastPlayer.playOrResume(roomId, voiceBroadcastId)
fun pausePlayback() = voiceBroadcastPlayer.pause()
fun stopPlayback() = voiceBroadcastPlayer.stop()
+
+ fun seekTo(voiceBroadcastId: String, positionMillis: Int) {
+ if (voiceBroadcastPlayer.currentVoiceBroadcastId == voiceBroadcastId) {
+ voiceBroadcastPlayer.seekTo(positionMillis)
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt
index e2870c4011..2a2a549af0 100644
--- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt
+++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayer.kt
@@ -43,6 +43,11 @@ interface VoiceBroadcastPlayer {
*/
fun stop()
+ /**
+ * Seek to the given playback position, is milliseconds.
+ */
+ fun seekTo(positionMillis: Int)
+
/**
* Add a [Listener] to the given voice broadcast id.
*/
diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
index 3999a0e0af..166e5a12e5 100644
--- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
+++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
@@ -22,7 +22,7 @@ import androidx.annotation.MainThread
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
import im.vector.app.features.voice.VoiceFailure
-import im.vector.app.features.voicebroadcast.getVoiceBroadcastChunk
+import im.vector.app.features.voicebroadcast.duration
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Listener
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.State
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent
import timber.log.Timber
@@ -62,14 +63,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
private var currentMediaPlayer: MediaPlayer? = null
private var nextMediaPlayer: MediaPlayer? = null
- set(value) {
- field = value
- currentMediaPlayer?.setNextMediaPlayer(value)
- }
private var currentSequence: Int? = null
private var fetchPlaylistJob: Job? = null
- private var playlist = emptyList()
+ private var playlist = emptyList()
+
private var isLive: Boolean = false
override var currentVoiceBroadcastId: String? = null
@@ -170,8 +168,18 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
.launchIn(coroutineScope)
}
- private fun updatePlaylist(playlist: List) {
- this.playlist = playlist.sortedBy { it.getVoiceBroadcastChunk()?.sequence?.toLong() ?: it.root.originServerTs }
+ private fun updatePlaylist(audioEvents: List) {
+ val sorted = audioEvents.sortedBy { it.sequence?.toLong() ?: it.root.originServerTs }
+ val chunkPositions = sorted
+ .map { it.duration }
+ .runningFold(0) { acc, i -> acc + i }
+ .dropLast(1)
+ playlist = sorted.mapIndexed { index, messageAudioEvent ->
+ PlaylistItem(
+ audioEvent = messageAudioEvent,
+ startTime = chunkPositions.getOrNull(index) ?: 0
+ )
+ }
onPlaylistUpdated()
}
@@ -195,16 +203,23 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
}
}
- private fun startPlayback() {
- 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
+ private fun startPlayback(sequence: Int? = null, position: Int = 0) {
+ val playlistItem = when {
+ sequence != null -> playlist.find { it.audioEvent.sequence == sequence }
+ isLive -> playlist.lastOrNull()
+ else -> playlist.firstOrNull()
+ }
+ val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## VoiceBroadcastPlayer: No content to play"); return }
+ val computedSequence = playlistItem.audioEvent.sequence
coroutineScope.launch {
try {
currentMediaPlayer = prepareMediaPlayer(content)
currentMediaPlayer?.start()
+ if (position > 0) {
+ currentMediaPlayer?.seekTo(position)
+ }
currentVoiceBroadcastId?.let { playbackTracker.startPlayback(it) }
- currentSequence = sequence
+ currentSequence = computedSequence
withContext(Dispatchers.Main) { playingState = State.PLAYING }
nextMediaPlayer = prepareNextMediaPlayer()
} catch (failure: Throwable) {
@@ -220,11 +235,27 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
playingState = State.PLAYING
}
+ override fun seekTo(positionMillis: Int) {
+ val duration = getVoiceBroadcastDuration()
+ val playlistItem = playlist.lastOrNull { it.startTime <= positionMillis } ?: return
+ val audioEvent = playlistItem.audioEvent
+ val eventPosition = positionMillis - playlistItem.startTime
+
+ Timber.d("## Voice Broadcast | seekTo - duration=$duration, position=$positionMillis, sequence=${audioEvent.sequence}, sequencePosition=$eventPosition")
+
+ tryOrNull { currentMediaPlayer?.stop() }
+ release(currentMediaPlayer)
+ tryOrNull { nextMediaPlayer?.stop() }
+ release(nextMediaPlayer)
+
+ startPlayback(audioEvent.sequence, eventPosition)
+ }
+
private fun getNextAudioContent(): MessageAudioContent? {
val nextSequence = currentSequence?.plus(1)
- ?: playlist.lastOrNull()?.sequence
+ ?: playlist.lastOrNull()?.audioEvent?.sequence
?: 1
- return playlist.find { it.getVoiceBroadcastChunk()?.sequence == nextSequence }?.content
+ return playlist.find { it.audioEvent.sequence == nextSequence }?.audioEvent?.content
}
private suspend fun prepareNextMediaPlayer(): MediaPlayer? {
@@ -268,7 +299,11 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
}
}
- private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
+ private inner class MediaPlayerListener :
+ MediaPlayer.OnInfoListener,
+ MediaPlayer.OnPreparedListener,
+ MediaPlayer.OnCompletionListener,
+ MediaPlayer.OnErrorListener {
override fun onInfo(mp: MediaPlayer, what: Int, extra: Int): Boolean {
when (what) {
@@ -282,6 +317,17 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
return false
}
+ override fun onPrepared(mp: MediaPlayer) {
+ when (mp) {
+ currentMediaPlayer -> {
+ nextMediaPlayer?.let { mp.setNextMediaPlayer(it) }
+ }
+ nextMediaPlayer -> {
+ tryOrNull { currentMediaPlayer?.setNextMediaPlayer(mp) }
+ }
+ }
+ }
+
override fun onCompletion(mp: MediaPlayer) {
if (nextMediaPlayer != null) return
val roomId = currentRoomId ?: return
@@ -302,4 +348,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
return true
}
}
+
+ private fun getVoiceBroadcastDuration() = playlist.lastOrNull()?.let { it.startTime + it.audioEvent.duration } ?: 0
+
+ private data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int)
}
diff --git a/vector/src/main/res/drawable/ic_player_backward_30.xml b/vector/src/main/res/drawable/ic_player_backward_30.xml
new file mode 100644
index 0000000000..cb244806b3
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_player_backward_30.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_player_forward_30.xml b/vector/src/main/res/drawable/ic_player_forward_30.xml
new file mode 100644
index 0000000000..be61fda8ff
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_player_forward_30.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
index d508569cb0..bed9407dfa 100644
--- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml
@@ -84,22 +84,31 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
- app:barrierMargin="12dp"
+ app:barrierMargin="10dp"
app:constraint_referenced_ids="roomAvatarImageView,titleText,metadataFlow" />
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml
index 3296134919..7da0701cc7 100644
--- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_recording_stub.xml
@@ -91,8 +91,8 @@