Display video in recordings
This commit is contained in:
parent
09ebb7e63b
commit
2f4b8ae760
5 changed files with 138 additions and 34 deletions
|
@ -21,9 +21,11 @@ package org.linphone.activities.main.recordings.adapters
|
|||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.TextureView
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.linphone.R
|
||||
|
@ -31,14 +33,22 @@ import org.linphone.activities.main.recordings.viewmodels.RecordingViewModel
|
|||
import org.linphone.activities.main.viewmodels.ListTopBarViewModel
|
||||
import org.linphone.databinding.GenericListHeaderBinding
|
||||
import org.linphone.databinding.RecordingListCellBinding
|
||||
import org.linphone.utils.HeaderAdapter
|
||||
import org.linphone.utils.LifecycleListAdapter
|
||||
import org.linphone.utils.LifecycleViewHolder
|
||||
import org.linphone.utils.TimestampUtils
|
||||
import org.linphone.utils.*
|
||||
|
||||
class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : LifecycleListAdapter<RecordingViewModel, RecordingsListAdapter.ViewHolder>(
|
||||
RecordingDiffCallback()
|
||||
), HeaderAdapter {
|
||||
|
||||
val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
private lateinit var videoSurface: TextureView
|
||||
|
||||
fun setVideoTextureView(textureView: TextureView) {
|
||||
videoSurface = textureView
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding: RecordingListCellBinding = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
|
@ -72,6 +82,15 @@ class RecordingsListAdapter(val selectionViewModel: ListTopBarViewModel) : Lifec
|
|||
}
|
||||
}
|
||||
|
||||
recording.isVideoRecordingPlayingEvent.observe(this@ViewHolder, Observer {
|
||||
it.consume { value ->
|
||||
if (value) {
|
||||
recording.setTextureView(videoSurface)
|
||||
}
|
||||
isVideoRecordingPlayingEvent.value = Event(value)
|
||||
}
|
||||
})
|
||||
|
||||
executePendingBindings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.linphone.activities.main.recordings.fragments
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Observer
|
||||
|
@ -41,6 +42,9 @@ class RecordingsFragment : MasterFragment() {
|
|||
private lateinit var viewModel: RecordingsViewModel
|
||||
private lateinit var adapter: RecordingsListAdapter
|
||||
|
||||
private var videoX: Float = 0f
|
||||
private var videoY: Float = 0f
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -83,6 +87,31 @@ class RecordingsFragment : MasterFragment() {
|
|||
binding.setBackClickListener { findNavController().popBackStack() }
|
||||
|
||||
binding.setEditClickListener { listSelectionViewModel.isEditionEnabled.value = true }
|
||||
|
||||
binding.setVideoTouchListener { v, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
videoX = v.x - event.rawX
|
||||
videoY = v.y - event.rawY
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
v.animate().x(event.rawX + videoX).y(event.rawY + videoY).setDuration(0).start()
|
||||
}
|
||||
else -> {
|
||||
v.performClick()
|
||||
false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
adapter.isVideoRecordingPlayingEvent.observe(viewLifecycleOwner, Observer {
|
||||
it.consume { value ->
|
||||
viewModel.isVideoVisible.value = value
|
||||
}
|
||||
})
|
||||
|
||||
adapter.setVideoTextureView(binding.recordingVideoSurface)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
package org.linphone.activities.main.recordings.viewmodels
|
||||
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.view.TextureView
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -35,6 +37,7 @@ 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> {
|
||||
|
@ -46,23 +49,17 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
lateinit var name: String
|
||||
lateinit var date: Date
|
||||
|
||||
val duration: Int
|
||||
get() {
|
||||
if (isClosed()) player.open(path)
|
||||
return player.duration
|
||||
}
|
||||
|
||||
val formattedDuration: String
|
||||
get() = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) // is already in milliseconds
|
||||
|
||||
val formattedDate: String
|
||||
get() = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
||||
|
||||
val duration = MutableLiveData<Int>()
|
||||
val formattedDuration = MutableLiveData<String>()
|
||||
val formattedDate = MutableLiveData<String>()
|
||||
val position = MutableLiveData<Int>()
|
||||
val formattedPosition = MutableLiveData<String>()
|
||||
|
||||
val isPlaying = MutableLiveData<Boolean>()
|
||||
|
||||
val isVideoRecordingPlayingEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
||||
private val tickerChannel = ticker(1000, 1000)
|
||||
|
||||
private lateinit var player: Player
|
||||
|
@ -82,23 +79,7 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
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
|
||||
var earpieceCard: String? = null
|
||||
for (device in coreContext.core.audioDevices) {
|
||||
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
||||
if (device.type == AudioDevice.Type.Speaker) {
|
||||
speakerCard = device.id
|
||||
} else if (device.type == AudioDevice.Type.Earpiece) {
|
||||
earpieceCard = device.id
|
||||
}
|
||||
}
|
||||
}
|
||||
val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null)
|
||||
if (localPlayer != null) player = localPlayer
|
||||
else Log.e("[Recording VM] Couldn't create local player!")
|
||||
player.addListener(listener)
|
||||
initPlayer()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
@ -130,6 +111,8 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
isVideoRecordingPlayingEvent.value = Event(player.isVideoAvailable)
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
|
@ -147,6 +130,62 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
}
|
||||
}
|
||||
|
||||
fun setTextureView(textureView: TextureView) {
|
||||
Log.i("[Recording VM] Is TextureView available? ${textureView.isAvailable}")
|
||||
if (textureView.isAvailable) {
|
||||
player.setWindowId(textureView.surfaceTexture)
|
||||
} else {
|
||||
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
||||
override fun onSurfaceTextureSizeChanged(
|
||||
surface: SurfaceTexture?,
|
||||
width: Int,
|
||||
height: Int
|
||||
) { }
|
||||
|
||||
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) { }
|
||||
|
||||
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureAvailable(
|
||||
surface: SurfaceTexture?,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
Log.i("[Recording VM] Surface texture should be available now")
|
||||
player.setWindowId(textureView.surfaceTexture)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPlayer() {
|
||||
// Use speaker sound card to play recordings, otherwise use earpiece
|
||||
// If none are available, default one will be used
|
||||
var speakerCard: String? = null
|
||||
var earpieceCard: String? = null
|
||||
for (device in coreContext.core.audioDevices) {
|
||||
if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) {
|
||||
if (device.type == AudioDevice.Type.Speaker) {
|
||||
speakerCard = device.id
|
||||
} else if (device.type == AudioDevice.Type.Earpiece) {
|
||||
earpieceCard = device.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val localPlayer = coreContext.core.createLocalPlayer(speakerCard ?: earpieceCard, null, null)
|
||||
if (localPlayer != null) player = localPlayer
|
||||
else Log.e("[Recording VM] Couldn't create local player!")
|
||||
player.addListener(listener)
|
||||
|
||||
player.open(path)
|
||||
duration.value = player.duration
|
||||
formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(player.duration) // is already in milliseconds
|
||||
formattedDate.value = DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
|
||||
}
|
||||
|
||||
private fun updatePosition() {
|
||||
val progress = if (isClosed()) 0 else player.currentPosition
|
||||
position.postValue(progress)
|
||||
|
@ -157,6 +196,8 @@ class RecordingViewModel(val path: String) : ViewModel(), Comparable<RecordingVi
|
|||
pause()
|
||||
player.seek(0)
|
||||
updatePosition()
|
||||
player.close()
|
||||
isVideoRecordingPlayingEvent.value = Event(false)
|
||||
}
|
||||
|
||||
private fun isClosed(): Boolean {
|
||||
|
|
|
@ -28,8 +28,11 @@ import org.linphone.utils.FileUtils
|
|||
class RecordingsViewModel : ViewModel() {
|
||||
val recordingsList = MutableLiveData<ArrayList<RecordingViewModel>>()
|
||||
|
||||
val isVideoVisible = MutableLiveData<Boolean>()
|
||||
|
||||
init {
|
||||
getRecordings()
|
||||
isVideoVisible.value = false
|
||||
}
|
||||
|
||||
fun deleteRecordings(list: ArrayList<RecordingViewModel>) {
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
<variable
|
||||
name="editClickListener"
|
||||
type="android.view.View.OnClickListener"/>
|
||||
<variable
|
||||
name="videoTouchListener"
|
||||
type="android.view.View.OnTouchListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.activities.main.recordings.viewmodels.RecordingsViewModel" />
|
||||
|
@ -85,6 +88,15 @@
|
|||
android:gravity="center"
|
||||
android:text="@string/recordings_empty_list" />
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/recording_video_surface"
|
||||
android:onTouch="@{videoTouchListener}"
|
||||
android:visibility="@{viewModel.isVideoVisible ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</layout>
|
Loading…
Reference in a new issue