Merge pull request #8012 from vector-im/bugfix/fre/fix_vb_scrubbing
[Voice Broadcast] Use internal playback timer to compute the playback position
This commit is contained in:
commit
ca37cc5cd3
1
changelog.d/8012.bugfix
Normal file
1
changelog.d/8012.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Voice Broadcast] Use internal playback timer to compute the current playback position
|
@ -102,7 +102,7 @@ class VideoViewHolder constructor(itemView: View) :
|
|||||||
|
|
||||||
views.videoView.setOnPreparedListener {
|
views.videoView.setOnPreparedListener {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
countUpTimer = CountUpTimer(100).also {
|
countUpTimer = CountUpTimer(intervalInMs = 100).also {
|
||||||
it.tickListener = CountUpTimer.TickListener {
|
it.tickListener = CountUpTimer.TickListener {
|
||||||
val duration = views.videoView.duration
|
val duration = views.videoView.duration
|
||||||
val progress = views.videoView.currentPosition
|
val progress = views.videoView.currentPosition
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 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.lib.core.utils.timer
|
||||||
|
|
||||||
|
interface Clock {
|
||||||
|
fun epochMillis(): Long
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultClock : Clock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a UTC epoch in milliseconds
|
||||||
|
*
|
||||||
|
* This value is not guaranteed to be correct with reality
|
||||||
|
* as a User can override the system time and date to any values.
|
||||||
|
*/
|
||||||
|
override fun epochMillis(): Long {
|
||||||
|
return System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
@ -28,41 +28,50 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||||
class CountUpTimer(private val intervalInMs: Long = 1_000) {
|
class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_000) {
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||||
private val elapsedTime: AtomicLong = AtomicLong()
|
|
||||||
private val resumed: AtomicBoolean = AtomicBoolean(false)
|
private val resumed: AtomicBoolean = AtomicBoolean(false)
|
||||||
|
|
||||||
|
private val clock: Clock = DefaultClock()
|
||||||
|
private val lastTime: AtomicLong = AtomicLong()
|
||||||
|
private val elapsedTime: AtomicLong = AtomicLong(initialTime)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
startCounter()
|
startCounter()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startCounter() {
|
private fun startCounter() {
|
||||||
tickerFlow(coroutineScope, intervalInMs / 10)
|
tickerFlow(coroutineScope, intervalInMs)
|
||||||
.filter { resumed.get() }
|
.filter { resumed.get() }
|
||||||
.map { elapsedTime.addAndGet(intervalInMs / 10) }
|
.map { elapsedTime() }
|
||||||
.filter { it % intervalInMs == 0L }
|
.onEach { tickListener?.onTick(it) }
|
||||||
.onEach {
|
.launchIn(coroutineScope)
|
||||||
tickListener?.onTick(it)
|
|
||||||
}.launchIn(coroutineScope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var tickListener: TickListener? = null
|
var tickListener: TickListener? = null
|
||||||
|
|
||||||
fun elapsedTime(): Long {
|
fun elapsedTime(): Long {
|
||||||
return elapsedTime.get()
|
return if (resumed.get()) {
|
||||||
|
val now = clock.epochMillis()
|
||||||
|
elapsedTime.addAndGet(now - lastTime.getAndSet(now))
|
||||||
|
} else {
|
||||||
|
elapsedTime.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
|
tickListener?.onTick(elapsedTime())
|
||||||
resumed.set(false)
|
resumed.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
|
lastTime.set(clock.epochMillis())
|
||||||
resumed.set(true)
|
resumed.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
tickListener?.onTick(elapsedTime())
|
||||||
coroutineScope.cancel()
|
coroutineScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ PreferenceManager\.getDefaultSharedPreferences==2
|
|||||||
R\.string\.template_
|
R\.string\.template_
|
||||||
|
|
||||||
### Use the Clock interface, or use `measureTimeMillis`
|
### Use the Clock interface, or use `measureTimeMillis`
|
||||||
System\.currentTimeMillis\(\)===2
|
System\.currentTimeMillis\(\)===3
|
||||||
|
|
||||||
### Remove extra space between the name and the description
|
### Remove extra space between the name and the description
|
||||||
\* @\w+ \w+ +
|
\* @\w+ \w+ +
|
||||||
|
@ -166,7 +166,7 @@ class WebRtcCall(
|
|||||||
private var videoSender: RtpSender? = null
|
private var videoSender: RtpSender? = null
|
||||||
private var screenSender: RtpSender? = null
|
private var screenSender: RtpSender? = null
|
||||||
|
|
||||||
private val timer = CountUpTimer(1000L).apply {
|
private val timer = CountUpTimer(intervalInMs = 1000L).apply {
|
||||||
tickListener = CountUpTimer.TickListener { milliseconds ->
|
tickListener = CountUpTimer.TickListener { milliseconds ->
|
||||||
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
|
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
|
@ -198,7 +198,7 @@ class AudioMessageHelper @Inject constructor(
|
|||||||
|
|
||||||
private fun startRecordingAmplitudes() {
|
private fun startRecordingAmplitudes() {
|
||||||
amplitudeTicker?.stop()
|
amplitudeTicker?.stop()
|
||||||
amplitudeTicker = CountUpTimer(50).apply {
|
amplitudeTicker = CountUpTimer(intervalInMs = 50).apply {
|
||||||
tickListener = CountUpTimer.TickListener { onAmplitudeTick() }
|
tickListener = CountUpTimer.TickListener { onAmplitudeTick() }
|
||||||
resume()
|
resume()
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ abstract class LiveLocationUserItem : VectorEpoxyModel<LiveLocationUserItem.Hold
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val timer: CountUpTimer = CountUpTimer(1000)
|
val timer: CountUpTimer = CountUpTimer(intervalInMs = 1000)
|
||||||
val itemUserAvatarImageView by bind<ImageView>(R.id.itemUserAvatarImageView)
|
val itemUserAvatarImageView by bind<ImageView>(R.id.itemUserAvatarImageView)
|
||||||
val itemUserDisplayNameTextView by bind<TextView>(R.id.itemUserDisplayNameTextView)
|
val itemUserDisplayNameTextView by bind<TextView>(R.id.itemUserDisplayNameTextView)
|
||||||
val itemRemainingTimeTextView by bind<TextView>(R.id.itemRemainingTimeTextView)
|
val itemRemainingTimeTextView by bind<TextView>(R.id.itemRemainingTimeTextView)
|
||||||
|
@ -206,7 +206,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
State.Buffering -> {
|
State.Buffering -> {
|
||||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) }
|
val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) }
|
||||||
when {
|
when {
|
||||||
// resume playback from the next sequence item
|
// resume playback from the next sequence item
|
||||||
playlist.currentSequence != null -> playlist.getNextItem()?.let { startPlayback(it.startTime) }
|
playlist.currentSequence != null -> playlist.getNextItem()?.let { startPlayback(it.startTime) }
|
||||||
@ -223,24 +223,42 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlayback(position: Int) {
|
private fun startPlayback(playbackPosition: Int) {
|
||||||
stopPlayer()
|
stopPlayer()
|
||||||
|
playingState = State.Buffering
|
||||||
|
|
||||||
|
val playlistItem = playlist.findByPosition(playbackPosition) ?: run {
|
||||||
|
Timber.w("## Voice Broadcast | No content to play at position $playbackPosition"); stop(); return
|
||||||
|
}
|
||||||
|
val sequence = playlistItem.sequence ?: run {
|
||||||
|
Timber.w("## Voice Broadcast | Playlist item has no sequence"); stop(); return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVoiceBroadcast?.let {
|
||||||
|
val percentage = tryOrNull { playbackPosition.toFloat() / playlist.duration } ?: 0f
|
||||||
|
playbackTracker.updatePausedAtPlaybackTime(it.voiceBroadcastId, playbackPosition, percentage)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
|
||||||
prepareCurrentPlayerJob = sessionScope.launch {
|
prepareCurrentPlayerJob = sessionScope.launch {
|
||||||
try {
|
try {
|
||||||
val mp = prepareMediaPlayer(content)
|
val mp = prepareMediaPlayer(playlistItem.audioEvent.content)
|
||||||
|
|
||||||
|
// Take the difference between the duration given from the media player and the duration given from the chunk event
|
||||||
|
// If the offset is smaller than 500ms, we consider there is no offset to keep the normal behaviour
|
||||||
|
val offset = (mp.duration - playlistItem.duration).takeUnless { it < 500 }?.coerceAtLeast(0) ?: 0
|
||||||
|
val sequencePosition = offset + (playbackPosition - playlistItem.startTime)
|
||||||
|
|
||||||
playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted
|
playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted
|
||||||
mp.start()
|
mp.start()
|
||||||
if (sequencePosition > 0) {
|
if (sequencePosition > 0) {
|
||||||
mp.seekTo(sequencePosition)
|
mp.seekTo(sequencePosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
onNextMediaPlayerStarted(mp)
|
onNextMediaPlayerStarted(mp)
|
||||||
} catch (failure: VoiceBroadcastFailure.ListeningError) {
|
} catch (failure: VoiceBroadcastFailure.ListeningError) {
|
||||||
playingState = State.Error(failure)
|
if (failure.cause !is CancellationException) {
|
||||||
|
playingState = State.Error(failure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +277,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
playingState = State.Playing
|
playingState = State.Playing
|
||||||
currentMediaPlayer?.start()
|
currentMediaPlayer?.start()
|
||||||
} else {
|
} else {
|
||||||
val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0
|
val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } ?: 0
|
||||||
startPlayback(savedPosition)
|
startPlayback(savedPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +319,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
} catch (failure: VoiceBroadcastFailure.ListeningError) {
|
} catch (failure: VoiceBroadcastFailure.ListeningError) {
|
||||||
// Do not change the playingState if the current player is still valid,
|
// Do not change the playingState if the current player is still valid,
|
||||||
// the error will be thrown again when switching to the next player
|
// the error will be thrown again when switching to the next player
|
||||||
if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) {
|
if (failure.cause !is CancellationException && (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true)) {
|
||||||
playingState = State.Error(failure)
|
playingState = State.Error(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,6 +373,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
|
|
||||||
private fun stopPlayer() {
|
private fun stopPlayer() {
|
||||||
tryOrNull { currentMediaPlayer?.stop() }
|
tryOrNull { currentMediaPlayer?.stop() }
|
||||||
|
playbackTicker.stopPlaybackTicker()
|
||||||
|
|
||||||
currentMediaPlayer?.release()
|
currentMediaPlayer?.release()
|
||||||
currentMediaPlayer = null
|
currentMediaPlayer = null
|
||||||
|
|
||||||
@ -376,7 +396,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
State.Paused,
|
State.Paused,
|
||||||
State.Buffering,
|
State.Buffering,
|
||||||
is State.Error,
|
is State.Error,
|
||||||
State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId)
|
State.Idle -> playbackTicker.stopPlaybackTicker()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify playback tracker about error
|
// Notify playback tracker about error
|
||||||
@ -416,22 +436,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
prepareNextMediaPlayer()
|
prepareNextMediaPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCurrentPlaybackPosition(): Int? {
|
|
||||||
val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null
|
|
||||||
val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlist.currentItem?.startTime?.plus(it) }
|
|
||||||
val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId)
|
|
||||||
return computedPosition ?: savedPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCurrentPlaybackPercentage(): Float? {
|
|
||||||
val playlistPosition = playlist.currentItem?.startTime
|
|
||||||
val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlistPosition?.plus(it) } ?: playlistPosition
|
|
||||||
val duration = playlist.duration
|
|
||||||
val computedPercentage = if (computedPosition != null && duration > 0) computedPosition.toFloat() / duration else null
|
|
||||||
val savedPercentage = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPercentage(it) }
|
|
||||||
return computedPercentage ?: savedPercentage
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class MediaPlayerListener :
|
private inner class MediaPlayerListener :
|
||||||
MediaPlayer.OnInfoListener,
|
MediaPlayer.OnInfoListener,
|
||||||
MediaPlayer.OnCompletionListener,
|
MediaPlayer.OnCompletionListener,
|
||||||
@ -488,40 +492,41 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||||||
|
|
||||||
fun startPlaybackTicker(id: String) {
|
fun startPlaybackTicker(id: String) {
|
||||||
playbackTicker?.stop()
|
playbackTicker?.stop()
|
||||||
playbackTicker = CountUpTimer(50L).apply {
|
playbackTicker = CountUpTimer(
|
||||||
tickListener = CountUpTimer.TickListener { onPlaybackTick(id) }
|
initialTime = playbackTracker.getPlaybackTime(id)?.toLong() ?: 0L,
|
||||||
|
intervalInMs = 50L
|
||||||
|
).apply {
|
||||||
|
tickListener = CountUpTimer.TickListener { onPlaybackTick(id, it.toInt()) }
|
||||||
resume()
|
resume()
|
||||||
}
|
}
|
||||||
onPlaybackTick(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopPlaybackTicker(id: String) {
|
fun stopPlaybackTicker() {
|
||||||
playbackTicker?.stop()
|
playbackTicker?.stop()
|
||||||
|
playbackTicker?.tickListener = null
|
||||||
playbackTicker = null
|
playbackTicker = null
|
||||||
onPlaybackTick(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPlaybackTick(id: String) {
|
private fun onPlaybackTick(id: String, position: Int) {
|
||||||
val playbackTime = getCurrentPlaybackPosition()
|
val percentage = tryOrNull { position.toFloat() / playlist.duration }
|
||||||
val percentage = getCurrentPlaybackPercentage()
|
|
||||||
when (playingState) {
|
when (playingState) {
|
||||||
State.Playing -> {
|
State.Playing -> {
|
||||||
if (playbackTime != null && percentage != null) {
|
if (percentage != null) {
|
||||||
playbackTracker.updatePlayingAtPlaybackTime(id, playbackTime, percentage)
|
playbackTracker.updatePlayingAtPlaybackTime(id, position, percentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State.Paused,
|
State.Paused,
|
||||||
State.Buffering -> {
|
State.Buffering -> {
|
||||||
if (playbackTime != null && percentage != null) {
|
if (percentage != null) {
|
||||||
playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage)
|
playbackTracker.updatePausedAtPlaybackTime(id, position, percentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State.Idle -> {
|
State.Idle -> {
|
||||||
// restart the playback time if player completed with less than 250 ms remaining time
|
// restart the playback time if player completed with less than 1s remaining time
|
||||||
if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 250) {
|
if (percentage == null || (playlist.duration - position) < 1000) {
|
||||||
playbackTracker.stopPlayback(id)
|
playbackTracker.stopPlayback(id)
|
||||||
} else {
|
} else {
|
||||||
playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage)
|
playbackTracker.updatePausedAtPlaybackTime(id, position, percentage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is State.Error -> Unit
|
is State.Error -> Unit
|
||||||
|
@ -65,6 +65,6 @@ class VoiceBroadcastPlaylist(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) {
|
data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) {
|
||||||
val sequence: Int?
|
val sequence: Int? = audioEvent.sequence
|
||||||
get() = audioEvent.sequence
|
val duration: Int = audioEvent.duration
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content of the state event of type [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO].
|
* Content of the state event of type [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO].
|
||||||
@ -50,8 +49,4 @@ data class MessageVoiceBroadcastInfoContent(
|
|||||||
|
|
||||||
val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values()
|
val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values()
|
||||||
.find { it.value == voiceBroadcastStateStr }
|
.find { it.value == voiceBroadcastStateStr }
|
||||||
?: run {
|
|
||||||
Timber.w("Invalid value for state: `$voiceBroadcastStateStr`")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -248,30 +248,20 @@ class VoiceBroadcastRecorderQ(
|
|||||||
recordingTicker = CountUpTimer().apply {
|
recordingTicker = CountUpTimer().apply {
|
||||||
tickListener = CountUpTimer.TickListener { onTick(elapsedTime()) }
|
tickListener = CountUpTimer.TickListener { onTick(elapsedTime()) }
|
||||||
resume()
|
resume()
|
||||||
onTick(elapsedTime())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
recordingTicker?.apply {
|
recordingTicker?.pause()
|
||||||
pause()
|
|
||||||
onTick(elapsedTime())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resume() {
|
fun resume() {
|
||||||
recordingTicker?.apply {
|
recordingTicker?.resume()
|
||||||
resume()
|
|
||||||
onTick(elapsedTime())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
recordingTicker?.apply {
|
recordingTicker?.stop()
|
||||||
stop()
|
recordingTicker = null
|
||||||
onTick(elapsedTime())
|
|
||||||
recordingTicker = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTick(elapsedTimeMillis: Long) {
|
private fun onTick(elapsedTimeMillis: Long) {
|
||||||
|
@ -147,9 +147,11 @@
|
|||||||
android:id="@+id/seekBar"
|
android:id="@+id/seekBar"
|
||||||
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="20dp"
|
||||||
android:paddingStart="0dp"
|
android:paddingStart="0dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
android:paddingEnd="0dp"
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
android:progressDrawable="@drawable/bg_seek_bar"
|
android:progressDrawable="@drawable/bg_seek_bar"
|
||||||
android:thumbOffset="3dp"
|
android:thumbOffset="3dp"
|
||||||
android:thumbTint="?vctr_content_secondary"
|
android:thumbTint="?vctr_content_secondary"
|
||||||
@ -164,7 +166,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginTop="-3dp"
|
android:layout_marginTop="-7dp"
|
||||||
android:textColor="?vctr_content_tertiary"
|
android:textColor="?vctr_content_tertiary"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/seekBar"
|
app:layout_constraintTop_toBottomOf="@id/seekBar"
|
||||||
@ -176,7 +178,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:layout_marginTop="-3dp"
|
android:layout_marginTop="-7dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:textColor="?vctr_content_tertiary"
|
android:textColor="?vctr_content_tertiary"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user