mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-22 14:18:15 +00:00
Added simple call recording player
This commit is contained in:
parent
80da408930
commit
1ea32e7544
3 changed files with 182 additions and 4 deletions
|
|
@ -21,9 +21,23 @@ package org.linphone.ui.main.recordings.model
|
|||
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.media.AudioFocusRequestCompat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.ticker
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.core.Factory
|
||||
import org.linphone.core.Player
|
||||
import org.linphone.core.PlayerListener
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AudioUtils
|
||||
import org.linphone.utils.FileUtils
|
||||
import org.linphone.utils.LinphoneUtils
|
||||
import org.linphone.utils.TimestampUtils
|
||||
|
|
@ -39,7 +53,31 @@ class RecordingModel @WorkerThread constructor(val filePath: String, val fileNam
|
|||
|
||||
val dateTime: String
|
||||
|
||||
val formattedDuration: String
|
||||
|
||||
val duration: Int
|
||||
|
||||
val isPlaying = MutableLiveData<Boolean>()
|
||||
|
||||
val position = MutableLiveData<Int>()
|
||||
|
||||
private var audioFocusRequest: AudioFocusRequestCompat? = null
|
||||
|
||||
private lateinit var player: Player
|
||||
private val playerListener = PlayerListener {
|
||||
Log.i("$TAG End of file reached")
|
||||
pause()
|
||||
player.seek(0)
|
||||
position.postValue(0)
|
||||
player.close()
|
||||
}
|
||||
|
||||
private val tickerChannel = ticker(1000, 1000)
|
||||
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
|
||||
init {
|
||||
isPlaying.postValue(false)
|
||||
|
||||
val withoutHeader = fileName.substring(LinphoneUtils.RECORDING_FILE_NAME_HEADER.length)
|
||||
val indexOfSeparator = withoutHeader.indexOf(
|
||||
LinphoneUtils.RECORDING_FILE_NAME_URI_TIMESTAMP_SEPARATOR
|
||||
|
|
@ -69,6 +107,52 @@ class RecordingModel @WorkerThread constructor(val filePath: String, val fileNam
|
|||
} else {
|
||||
sipUri
|
||||
}
|
||||
|
||||
val playbackSoundCard = AudioUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage()
|
||||
val audioPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null)
|
||||
if (audioPlayer != null) {
|
||||
player = audioPlayer
|
||||
player.open(filePath)
|
||||
player.addListener(playerListener)
|
||||
duration = player.duration
|
||||
formattedDuration = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration)
|
||||
} else {
|
||||
duration = 0
|
||||
formattedDuration = "??:??"
|
||||
}
|
||||
position.postValue(0)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun destroy() {
|
||||
scope.cancel()
|
||||
tickerChannel.cancel()
|
||||
|
||||
if (::player.isInitialized) {
|
||||
if (player.state != Player.State.Closed) {
|
||||
player.close()
|
||||
}
|
||||
|
||||
if (audioFocusRequest != null) {
|
||||
AudioUtils.releaseAudioFocusForVoiceRecordingOrPlayback(
|
||||
coreContext.context,
|
||||
audioFocusRequest!!
|
||||
)
|
||||
}
|
||||
|
||||
player.removeListener(playerListener)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun togglePlayPause() {
|
||||
coreContext.postOnCoreThread {
|
||||
if (isPlaying.value == true) {
|
||||
pause()
|
||||
} else {
|
||||
play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
@ -76,4 +160,58 @@ class RecordingModel @WorkerThread constructor(val filePath: String, val fileNam
|
|||
Log.i("$TAG Deleting call recording [$filePath]")
|
||||
FileUtils.deleteFile(filePath)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun play() {
|
||||
if (!::player.isInitialized) return
|
||||
|
||||
Log.i("$TAG Starting player, acquiring audio focus")
|
||||
audioFocusRequest = AudioUtils.acquireAudioFocusForVoiceRecordingOrPlayback(
|
||||
coreContext.context
|
||||
)
|
||||
|
||||
if (player.state == Player.State.Closed) {
|
||||
player.open(filePath)
|
||||
player.seek(0)
|
||||
}
|
||||
|
||||
player.start()
|
||||
isPlaying.postValue(true)
|
||||
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
for (tick in tickerChannel) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (player.state == Player.State.Playing) {
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun updatePosition() {
|
||||
val progress = if (player.state == Player.State.Closed) 0 else player.currentPosition
|
||||
position.value = progress
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun pause() {
|
||||
if (!::player.isInitialized) return
|
||||
|
||||
Log.i("$TAG Stopping player, releasing audio focus")
|
||||
if (audioFocusRequest != null) {
|
||||
AudioUtils.releaseAudioFocusForVoiceRecordingOrPlayback(
|
||||
coreContext.context,
|
||||
audioFocusRequest!!
|
||||
)
|
||||
}
|
||||
|
||||
coreContext.postOnCoreThread {
|
||||
player.pause()
|
||||
}
|
||||
isPlaying.postValue(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ class RecordingsListViewModel @UiThread constructor() : GenericViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
recordings.value.orEmpty().forEach(RecordingModel::destroy)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun openSearchBar() {
|
||||
searchBarVisible.value = true
|
||||
|
|
@ -82,7 +87,7 @@ class RecordingsListViewModel @UiThread constructor() : GenericViewModel() {
|
|||
|
||||
@WorkerThread
|
||||
private fun computeList(filter: String) {
|
||||
// TODO FIXME: use filter
|
||||
recordings.value.orEmpty().forEach(RecordingModel::destroy)
|
||||
val list = arrayListOf<RecordingModel>()
|
||||
|
||||
// TODO FIXME: also load recordings from previous Linphone versions
|
||||
|
|
|
|||
|
|
@ -57,10 +57,11 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/play_pause"
|
||||
android:onClick="@{() -> model.togglePlayPause()}"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/play_fill"
|
||||
android:src="@{model.isPlaying ? @drawable/pause_fill : @drawable/play_fill, default=@drawable/play_fill}"
|
||||
app:layout_constraintTop_toTopOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -71,7 +72,6 @@
|
|||
android:id="@+id/time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
|
|
@ -82,9 +82,44 @@
|
|||
android:ellipsize="end"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_300"
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@{model.formattedDuration, default=`00:42`}"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?attr/color_main2_500"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintTop_toBottomOf="@id/time"
|
||||
app:layout_constraintBottom_toTopOf="@id/progress"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:max="@{model.duration, default=100}"
|
||||
android:progress="@{model.position, default=75}"
|
||||
app:trackCornerRadius="5dp"
|
||||
app:trackThickness="10dp"
|
||||
app:trackColor="?attr/color_main1_100"
|
||||
app:indicatorColor="?attr/color_main1_500"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/duration"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue