Added export feature for call recordings
This commit is contained in:
parent
d630f0ceec
commit
61454f5923
8 changed files with 65 additions and 13 deletions
|
@ -19,6 +19,7 @@ Group changes to describe their impact on the project, as follows:
|
||||||
- Image & Video in-app viewers allow for full-screen display
|
- Image & Video in-app viewers allow for full-screen display
|
||||||
- Display name can be set during assistant when creating / logging in a sip.linphone.org account
|
- Display name can be set during assistant when creating / logging in a sip.linphone.org account
|
||||||
- Android 13 support, using new post notifications & media permissions
|
- Android 13 support, using new post notifications & media permissions
|
||||||
|
- Call recordings can be exported
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- In-call views have been re-designed
|
- In-call views have been re-designed
|
||||||
|
|
|
@ -163,6 +163,10 @@ class RecordingData(val path: String, private val recordingListener: RecordingLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun export() {
|
||||||
|
recordingListener.onExportClicked(path)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initPlayer() {
|
private fun initPlayer() {
|
||||||
// In case no headphones/headset is connected, use speaker sound card to play recordings, otherwise use earpiece
|
// In case no headphones/headset is connected, 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
|
||||||
|
@ -218,5 +222,6 @@ class RecordingData(val path: String, private val recordingListener: RecordingLi
|
||||||
interface RecordingListener {
|
interface RecordingListener {
|
||||||
fun onPlayingStarted(videoAvailable: Boolean)
|
fun onPlayingStarted(videoAvailable: Boolean)
|
||||||
fun onPlayingEnded()
|
fun onPlayingEnded()
|
||||||
|
fun onExportClicked(path: String)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
*/
|
*/
|
||||||
package org.linphone.activities.main.recordings.fragments
|
package org.linphone.activities.main.recordings.fragments
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -32,6 +34,7 @@ import org.linphone.activities.main.recordings.viewmodels.RecordingsViewModel
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.databinding.RecordingsFragmentBinding
|
import org.linphone.databinding.RecordingsFragmentBinding
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
|
import org.linphone.utils.FileUtils
|
||||||
import org.linphone.utils.RecyclerViewHeaderDecoration
|
import org.linphone.utils.RecyclerViewHeaderDecoration
|
||||||
|
|
||||||
class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsListAdapter>() {
|
class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsListAdapter>() {
|
||||||
|
@ -75,6 +78,25 @@ class RecordingsFragment : MasterFragment<RecordingsFragmentBinding, RecordingsL
|
||||||
adapter.submitList(recordings)
|
adapter.submitList(recordings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.exportRecordingEvent.observe(
|
||||||
|
viewLifecycleOwner
|
||||||
|
) {
|
||||||
|
it.consume { path ->
|
||||||
|
val publicFilePath = FileUtils.getPublicFilePath(requireContext(), "file://$path")
|
||||||
|
Log.i("[Recordings] Exporting file [$path] with public URI [$publicFilePath]")
|
||||||
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
|
intent.type = " video/x-matroska"
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, publicFilePath)
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.recordings_export))
|
||||||
|
|
||||||
|
try {
|
||||||
|
requireActivity().startActivity(Intent.createChooser(intent, getString(R.string.recordings_export)))
|
||||||
|
} catch (anfe: ActivityNotFoundException) {
|
||||||
|
Log.e(anfe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.setBackClickListener { goBack() }
|
binding.setBackClickListener { goBack() }
|
||||||
|
|
||||||
binding.setEditClickListener { listSelectionViewModel.isEditionEnabled.value = true }
|
binding.setEditClickListener { listSelectionViewModel.isEditionEnabled.value = true }
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.linphone.LinphoneApplication.Companion.coreContext
|
||||||
import org.linphone.activities.main.recordings.data.RecordingData
|
import org.linphone.activities.main.recordings.data.RecordingData
|
||||||
import org.linphone.core.tools.Log
|
import org.linphone.core.tools.Log
|
||||||
import org.linphone.utils.AppUtils
|
import org.linphone.utils.AppUtils
|
||||||
|
import org.linphone.utils.Event
|
||||||
import org.linphone.utils.FileUtils
|
import org.linphone.utils.FileUtils
|
||||||
|
|
||||||
class RecordingsViewModel : ViewModel() {
|
class RecordingsViewModel : ViewModel() {
|
||||||
|
@ -34,6 +35,10 @@ class RecordingsViewModel : ViewModel() {
|
||||||
|
|
||||||
val isVideoVisible = MutableLiveData<Boolean>()
|
val isVideoVisible = MutableLiveData<Boolean>()
|
||||||
|
|
||||||
|
val exportRecordingEvent: MutableLiveData<Event<String>> by lazy {
|
||||||
|
MutableLiveData<Event<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
private var recordingPlayingAudioFocusRequest: AudioFocusRequestCompat? = null
|
private var recordingPlayingAudioFocusRequest: AudioFocusRequestCompat? = null
|
||||||
|
|
||||||
private val recordingListener = object : RecordingData.RecordingListener {
|
private val recordingListener = object : RecordingData.RecordingListener {
|
||||||
|
@ -56,6 +61,10 @@ class RecordingsViewModel : ViewModel() {
|
||||||
|
|
||||||
isVideoVisible.value = false
|
isVideoVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onExportClicked(path: String) {
|
||||||
|
exportRecordingEvent.value = Event(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -93,7 +102,7 @@ class RecordingsViewModel : ViewModel() {
|
||||||
val list = arrayListOf<RecordingData>()
|
val list = arrayListOf<RecordingData>()
|
||||||
|
|
||||||
for (f in FileUtils.getFileStorageDir().listFiles().orEmpty()) {
|
for (f in FileUtils.getFileStorageDir().listFiles().orEmpty()) {
|
||||||
Log.i("[Recordings] Found file ${f.path}")
|
Log.d("[Recordings] Found file ${f.path}")
|
||||||
if (RecordingData.RECORD_PATTERN.matcher(f.path).matches()) {
|
if (RecordingData.RECORD_PATTERN.matcher(f.path).matches()) {
|
||||||
list.add(
|
list.add(
|
||||||
RecordingData(
|
RecordingData(
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginLeft="10dp"
|
||||||
android:layout_marginRight="10dp"
|
android:layout_marginRight="10dp"
|
||||||
android:layout_toLeftOf="@id/delete"
|
android:layout_toLeftOf="@id/export"
|
||||||
android:layout_toRightOf="@id/record_play_pause">
|
android:layout_toRightOf="@id/record_play_pause">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
android:layout_alignParentTop="true">
|
android:layout_alignParentTop="true">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@{data.name}"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@{data.name}" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -80,9 +80,9 @@
|
||||||
android:text=" - " />
|
android:text=" - " />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@{data.formattedDate}"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@{data.formattedDate}" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -96,10 +96,10 @@
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@{data.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"
|
||||||
|
android:text="@{data.formattedPosition}" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -107,27 +107,37 @@
|
||||||
android:text="/" />
|
android:text="/" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@{data.formattedDuration}"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@{data.formattedDuration}" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<SeekBar
|
<SeekBar
|
||||||
android:id="@+id/record_progression_bar"
|
android:id="@+id/record_progression_bar"
|
||||||
onProgressChanged="@{(progress) -> data.onProgressChanged(progress)}"
|
onProgressChanged="@{(progress) -> data.onProgressChanged(progress)}"
|
||||||
android:max="@{data.duration}"
|
|
||||||
android:progress="@{data.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"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:max="@{data.duration}"
|
||||||
|
android:progress="@{data.position}"
|
||||||
android:progressTint="?attr/accentColor"
|
android:progressTint="?attr/accentColor"
|
||||||
android:thumbTint="?attr/accentColor" />
|
android:thumbTint="?attr/accentColor" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/export"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:onClick="@{() -> data.export()}"
|
||||||
|
android:src="@drawable/icon_share" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -731,4 +731,6 @@
|
||||||
<string name="chat_message_context_menu_turn_on_notifications">Activer les notifications</string>
|
<string name="chat_message_context_menu_turn_on_notifications">Activer les notifications</string>
|
||||||
<string name="content_description_muted_chat_room">Les notifications sont désactivées pour cette conversation</string>
|
<string name="content_description_muted_chat_room">Les notifications sont désactivées pour cette conversation</string>
|
||||||
<string name="chat_room_context_menu_go_to_contact">Voir le contact</string>
|
<string name="chat_room_context_menu_go_to_contact">Voir le contact</string>
|
||||||
|
<string name="recordings_export">Exporter l\'enregistrement avec…</string>
|
||||||
|
<string name="content_description_recording_export">Exporter l\'enregistrement</string>
|
||||||
</resources>
|
</resources>
|
|
@ -234,6 +234,7 @@
|
||||||
|
|
||||||
<!-- Recordings -->
|
<!-- Recordings -->
|
||||||
<string name="recordings_empty_list">No recordings</string>
|
<string name="recordings_empty_list">No recordings</string>
|
||||||
|
<string name="recordings_export">Export recording using…</string>
|
||||||
|
|
||||||
<!-- Conferencing -->
|
<!-- Conferencing -->
|
||||||
<string name="conference">Meeting</string>
|
<string name="conference">Meeting</string>
|
||||||
|
@ -861,4 +862,5 @@
|
||||||
<string name="content_description_participant_is_paused">Participant has momentarily left the group call</string>
|
<string name="content_description_participant_is_paused">Participant has momentarily left the group call</string>
|
||||||
<string name="content_description_toggle_conference_info_details">Toggle meeting information details visibility</string>
|
<string name="content_description_toggle_conference_info_details">Toggle meeting information details visibility</string>
|
||||||
<string name="content_description_conference_participants">Group call participants</string>
|
<string name="content_description_conference_participants">Group call participants</string>
|
||||||
|
<string name="content_description_recording_export">Export recording</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<paths>
|
<paths>
|
||||||
<files-path name="internal_files" path="." />
|
<files-path name="internal_files" path="." />
|
||||||
|
<files-path name="internal_recordings" path="Download/" />
|
||||||
<external-files-path name="pictures" path="Pictures/" />
|
<external-files-path name="pictures" path="Pictures/" />
|
||||||
<external-files-path name="downloads" path="Download/" />
|
<external-files-path name="downloads" path="Download/" />
|
||||||
<external-files-path name="files" path="." />
|
<external-files-path name="files" path="." />
|
||||||
|
|
Loading…
Reference in a new issue