Improved calls' recording player
This commit is contained in:
parent
785ae1ad4e
commit
0a33b33888
4 changed files with 66 additions and 30 deletions
|
@ -20,7 +20,6 @@
|
|||
package org.linphone.activities.main.recordings.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -73,23 +72,6 @@ class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifec
|
|||
}
|
||||
}
|
||||
|
||||
recording.playStartedEvent.observe(this@ViewHolder, Observer {
|
||||
it.consume { playing ->
|
||||
recordCurrentTime.base = SystemClock.elapsedRealtime()
|
||||
|
||||
if (playing) {
|
||||
recordCurrentTime.start()
|
||||
} else {
|
||||
recordCurrentTime.stop()
|
||||
recordProgressionBar.progress = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
recordCurrentTime.setOnChronometerTickListener {
|
||||
recordProgressionBar.progress = (SystemClock.elapsedRealtime() - it.base).toInt()
|
||||
}
|
||||
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,16 +21,20 @@ package org.linphone.activities.main.recordings.viewmodels
|
|||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.ticker
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.AudioDevice
|
||||
import org.linphone.core.Player
|
||||
import org.linphone.core.PlayerListener
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
|
||||
class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingViewModel> {
|
||||
|
@ -54,14 +58,17 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
val formattedDate: String
|
||||
get() = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
||||
|
||||
val playStartedEvent = MutableLiveData<Event<Boolean>>()
|
||||
val position = MutableLiveData<Int>()
|
||||
val formattedPosition = MutableLiveData<String>()
|
||||
|
||||
val isPlaying = MutableLiveData<Boolean>()
|
||||
|
||||
private val tickerChannel = ticker(1000, 1000)
|
||||
|
||||
private var player: Player
|
||||
private val listener = PlayerListener {
|
||||
Log.i("[Recording] End of file reached")
|
||||
pause()
|
||||
stop()
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -72,6 +79,9 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
}
|
||||
isPlaying.value = false
|
||||
|
||||
position.value = 0
|
||||
formattedPosition.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(0)
|
||||
|
||||
// Use speaker sound card to play recordings, otherwise use earpiece
|
||||
// If none are available, default one will be used
|
||||
var speakerCard: String? = null
|
||||
|
@ -90,6 +100,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
}
|
||||
|
||||
override fun onCleared() {
|
||||
tickerChannel.cancel()
|
||||
if (!isClosed()) player.close()
|
||||
player.removeListener(listener)
|
||||
|
||||
|
@ -101,21 +112,49 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
}
|
||||
|
||||
fun play() {
|
||||
if (isClosed()) player.open(path)
|
||||
seek(0)
|
||||
if (isClosed()) {
|
||||
player.open(path)
|
||||
player.seek(0)
|
||||
}
|
||||
player.start()
|
||||
playStartedEvent.value = Event(true)
|
||||
isPlaying.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
for (tick in tickerChannel) {
|
||||
if (player.state == Player.State.Playing) {
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
player.pause()
|
||||
isPlaying.value = false
|
||||
playStartedEvent.value = Event(false)
|
||||
}
|
||||
|
||||
private fun seek(position: Int) {
|
||||
if (!isClosed()) player.seek(position)
|
||||
fun onProgressChanged(progress: Any) {
|
||||
if (progress is Int) {
|
||||
if (player.state == Player.State.Playing) {
|
||||
pause()
|
||||
}
|
||||
player.seek(progress)
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePosition() {
|
||||
val progress = if (isClosed()) 0 else player.currentPosition
|
||||
position.postValue(progress)
|
||||
formattedPosition.postValue(SimpleDateFormat("mm:ss", Locale.getDefault()).format(progress)) // is already in milliseconds
|
||||
}
|
||||
|
||||
private fun stop() {
|
||||
pause()
|
||||
player.seek(0)
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
private fun isClosed(): Boolean {
|
||||
|
|
|
@ -31,6 +31,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.*
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.databinding.*
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
|
@ -181,6 +182,19 @@ fun spinnerSetting(spinner: Spinner, selectedIndex: Int, listener: SettingListen
|
|||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("onProgressChanged")
|
||||
fun setListener(view: SeekBar, lambda: (Any) -> Unit) {
|
||||
view.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
if (fromUser) lambda(progress)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) { }
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) { }
|
||||
})
|
||||
}
|
||||
|
||||
@BindingAdapter("entries")
|
||||
fun setEntries(
|
||||
viewGroup: ViewGroup,
|
||||
|
|
|
@ -88,8 +88,8 @@
|
|||
android:layout_alignParentRight="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Chronometer
|
||||
android:id="@+id/record_current_time"
|
||||
<TextView
|
||||
android:text="@{viewModel.formattedPosition}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
@ -108,8 +108,9 @@
|
|||
|
||||
<SeekBar
|
||||
android:id="@+id/record_progression_bar"
|
||||
android:enabled="false"
|
||||
onProgressChanged="@{(progress) -> viewModel.onProgressChanged(progress)}"
|
||||
android:max="@{viewModel.duration}"
|
||||
android:progress="@{viewModel.position}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
|
|
Loading…
Reference in a new issue