Show video preview in in-call conversation screen

This commit is contained in:
Sylvain Berfini 2026-03-02 11:14:34 +01:00
parent 7fe554be3c
commit 1c4f73a9b8
6 changed files with 116 additions and 0 deletions

View file

@ -34,6 +34,7 @@ Group changes to describe their impact on the project, as follows:
- one to let edit native contacts Linphone copy in-app instead of opening native addressbook third party app - one to let edit native contacts Linphone copy in-app instead of opening native addressbook third party app
- Added a vu meter for recording & playback volumes (must be enabled in developer settings) - Added a vu meter for recording & playback volumes (must be enabled in developer settings)
- Added support for HDMI audio devices - Added support for HDMI audio devices
- Added video preview during in-call conversation
### Changed ### Changed
- No longer follow TelecomManager audio endpoint during calls, using our own routing policy - No longer follow TelecomManager audio endpoint during calls, using our own routing policy

View file

@ -19,12 +19,20 @@
*/ */
package org.linphone.ui.call.fragment package org.linphone.ui.call.fragment
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnLayout
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.R import org.linphone.R
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
import org.linphone.ui.call.view.RoundCornersTextureView
import org.linphone.ui.call.viewmodel.CurrentCallViewModel
import org.linphone.ui.fileviewer.FileViewerActivity import org.linphone.ui.fileviewer.FileViewerActivity
import org.linphone.ui.fileviewer.MediaViewerActivity import org.linphone.ui.fileviewer.MediaViewerActivity
import org.linphone.ui.main.chat.fragment.ConversationFragment import org.linphone.ui.main.chat.fragment.ConversationFragment
@ -34,9 +42,48 @@ class ConversationFragment : ConversationFragment() {
private const val TAG = "[In-call Conversation Fragment]" private const val TAG = "[In-call Conversation Fragment]"
} }
private lateinit var callViewModel: CurrentCallViewModel
private lateinit var localPreviewVideoSurface: RoundCornersTextureView
private var videoPreviewX: Float = 0f
private var videoPreviewY: Float = 0f
// For moving video preview purposes
private val videoPreviewTouchListener = View.OnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
videoPreviewX = view.x - event.rawX
videoPreviewY = view.y - event.rawY
true
}
MotionEvent.ACTION_UP -> {
videoPreviewX = view.x
videoPreviewY = view.y
true
}
MotionEvent.ACTION_MOVE -> {
view.animate()
.x(event.rawX + videoPreviewX)
.y(event.rawY + videoPreviewY)
.setDuration(0)
.start()
true
}
else -> {
view.performClick()
false
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
callViewModel = requireActivity().run {
ViewModelProvider(this)[CurrentCallViewModel::class.java]
}
Log.i("$TAG Creating an in-call ConversationFragment") Log.i("$TAG Creating an in-call ConversationFragment")
sendMessageViewModel.isCallConversation.value = true sendMessageViewModel.isCallConversation.value = true
viewModel.isCallConversation.value = true viewModel.isCallConversation.value = true
@ -70,5 +117,50 @@ class ConversationFragment : ConversationFragment() {
} }
} }
} }
val layout = layoutInflater.inflate(R.layout.call_video_local_preview_surface, binding.constraintLayout, false)
binding.constraintLayout.addView(layout)
localPreviewVideoSurface = layout.findViewById<RoundCornersTextureView>(R.id.local_preview_video_surface)
callViewModel.isSendingVideo.observe(viewLifecycleOwner) { sending ->
coreContext.postOnCoreThread { core ->
core.nativePreviewWindowId = if (sending) {
Log.i("$TAG We are sending video, setting capture preview surface")
localPreviewVideoSurface
} else {
Log.i("$TAG We are not sending video, clearing capture preview surface")
null
}
}
}
}
override fun onResume() {
super.onResume()
(binding.root as? ViewGroup)?.doOnLayout {
setupVideoPreview(localPreviewVideoSurface)
}
}
override fun onPause() {
super.onPause()
cleanVideoPreview(localPreviewVideoSurface)
}
@SuppressLint("ClickableViewAccessibility")
private fun setupVideoPreview(localPreviewVideoSurface: RoundCornersTextureView) {
if (requireActivity().isInPictureInPictureMode) {
Log.i("$TAG Activity is in PiP mode, do not move video preview")
return
}
localPreviewVideoSurface.setOnTouchListener(videoPreviewTouchListener)
}
@SuppressLint("ClickableViewAccessibility")
private fun cleanVideoPreview(localPreviewVideoSurface: RoundCornersTextureView) {
localPreviewVideoSurface.setOnTouchListener(null)
} }
} }

View file

@ -100,6 +100,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
app:alignTopRight="true" app:alignTopRight="true"
app:displayMode="black_bars" app:displayMode="black_bars"
roundCornersRadius="@dimen/call_round_corners_texture_view_radius" roundCornersRadius="@dimen/call_round_corners_texture_view_radius"

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.linphone.ui.call.view.RoundCornersTextureView
android:id="@+id/local_preview_video_surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:alignTopRight="true"
app:displayMode="black_bars"
roundCornersRadius="@dimen/call_round_corners_texture_view_radius"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="@dimen/call_video_preview_max_size"
app:layout_constraintWidth_max="@dimen/call_video_preview_max_size" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -98,6 +98,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
app:alignTopRight="true" app:alignTopRight="true"
app:displayMode="black_bars" app:displayMode="black_bars"
roundCornersRadius="@dimen/call_round_corners_texture_view_radius" roundCornersRadius="@dimen/call_round_corners_texture_view_radius"

View file

@ -53,6 +53,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/color_background_contrast_in_dark_mode"> android:background="?attr/color_background_contrast_in_dark_mode">