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
|
package org.linphone.activities.main.recordings.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.SystemClock
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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()
|
executePendingBindings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,20 @@ package org.linphone.activities.main.recordings.viewmodels
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
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.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.core.AudioDevice
|
import org.linphone.core.AudioDevice
|
||||||
import org.linphone.core.Player
|
import org.linphone.core.Player
|
||||||
import org.linphone.core.PlayerListener
|
import org.linphone.core.PlayerListener
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.utils.Event
|
|
||||||
import org.linphone.utils.LinphoneUtils
|
import org.linphone.utils.LinphoneUtils
|
||||||
|
|
||||||
class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingViewModel> {
|
class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingViewModel> {
|
||||||
|
@ -54,14 +58,17 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
||||||
val formattedDate: String
|
val formattedDate: String
|
||||||
get() = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
get() = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
||||||
|
|
||||||
val playStartedEvent = MutableLiveData<Event<Boolean>>()
|
val position = MutableLiveData<Int>()
|
||||||
|
val formattedPosition = MutableLiveData<String>()
|
||||||
|
|
||||||
val isPlaying = MutableLiveData<Boolean>()
|
val isPlaying = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
private val tickerChannel = ticker(1000, 1000)
|
||||||
|
|
||||||
private var player: Player
|
private var player: Player
|
||||||
private val listener = PlayerListener {
|
private val listener = PlayerListener {
|
||||||
Log.i("[Recording] End of file reached")
|
Log.i("[Recording] End of file reached")
|
||||||
pause()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -72,6 +79,9 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
||||||
}
|
}
|
||||||
isPlaying.value = false
|
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
|
// Use speaker sound card to play recordings, otherwise use earpiece
|
||||||
// If none are available, default one will be used
|
// If none are available, default one will be used
|
||||||
var speakerCard: String? = null
|
var speakerCard: String? = null
|
||||||
|
@ -90,6 +100,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
|
tickerChannel.cancel()
|
||||||
if (!isClosed()) player.close()
|
if (!isClosed()) player.close()
|
||||||
player.removeListener(listener)
|
player.removeListener(listener)
|
||||||
|
|
||||||
|
@ -101,21 +112,49 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
||||||
}
|
}
|
||||||
|
|
||||||
fun play() {
|
fun play() {
|
||||||
if (isClosed()) player.open(path)
|
if (isClosed()) {
|
||||||
seek(0)
|
player.open(path)
|
||||||
|
player.seek(0)
|
||||||
|
}
|
||||||
player.start()
|
player.start()
|
||||||
playStartedEvent.value = Event(true)
|
|
||||||
isPlaying.value = true
|
isPlaying.value = true
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
for (tick in tickerChannel) {
|
||||||
|
if (player.state == Player.State.Playing) {
|
||||||
|
updatePosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
player.pause()
|
player.pause()
|
||||||
isPlaying.value = false
|
isPlaying.value = false
|
||||||
playStartedEvent.value = Event(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun seek(position: Int) {
|
fun onProgressChanged(progress: Any) {
|
||||||
if (!isClosed()) player.seek(position)
|
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 {
|
private fun isClosed(): Boolean {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||||
import androidx.databinding.*
|
import androidx.databinding.*
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DataSource
|
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")
|
@BindingAdapter("entries")
|
||||||
fun setEntries(
|
fun setEntries(
|
||||||
viewGroup: ViewGroup,
|
viewGroup: ViewGroup,
|
||||||
|
|
|
@ -88,8 +88,8 @@
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Chronometer
|
<TextView
|
||||||
android:id="@+id/record_current_time"
|
android:text="@{viewModel.formattedPosition}"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center" />
|
||||||
|
@ -108,8 +108,9 @@
|
||||||
|
|
||||||
<SeekBar
|
<SeekBar
|
||||||
android:id="@+id/record_progression_bar"
|
android:id="@+id/record_progression_bar"
|
||||||
android:enabled="false"
|
onProgressChanged="@{(progress) -> viewModel.onProgressChanged(progress)}"
|
||||||
android:max="@{viewModel.duration}"
|
android:max="@{viewModel.duration}"
|
||||||
|
android:progress="@{viewModel.position}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
|
|
Loading…
Reference in a new issue