Display our own video (if enabled) while waiting for other participants to join the conference

This commit is contained in:
Sylvain Berfini 2024-04-09 11:09:22 +02:00
parent 065cdfa8c1
commit fea42aba3b
3 changed files with 113 additions and 18 deletions

View file

@ -19,12 +19,14 @@
*/ */
package org.linphone.ui.call.conference.fragment package org.linphone.ui.call.conference.fragment
import android.annotation.SuppressLint
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -83,6 +85,33 @@ class ActiveConferenceCallFragment : GenericCallFragment() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { } override fun onSlide(bottomSheet: View, slideOffset: Float) { }
} }
// For moving video preview purposes
private var previewX: Float = 0f
private var previewY: Float = 0f
private val previewTouchListener = View.OnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
previewX = view.x - event.rawX
previewY = view.y - event.rawY
true
}
MotionEvent.ACTION_MOVE -> {
view.animate()
.x(event.rawX + previewX)
.y(event.rawY + previewY)
.setDuration(0)
.start()
true
}
else -> {
view.performClick()
false
}
}
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -163,6 +192,15 @@ class ActiveConferenceCallFragment : GenericCallFragment() {
actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED actionsBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
} }
callViewModel.conferenceModel.participants.observe(viewLifecycleOwner) { participants ->
coreContext.postOnCoreThread { core ->
if (participants.size == 1) {
Log.i("$TAG We are alone in that conference, using nativePreviewWindowId")
core.nativePreviewWindowId = binding.localPreviewVideoSurface
}
}
}
binding.setBackClickListener { binding.setBackClickListener {
requireActivity().finish() requireActivity().finish()
} }
@ -201,12 +239,22 @@ class ActiveConferenceCallFragment : GenericCallFragment() {
} }
} }
@SuppressLint("ClickableViewAccessibility")
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
coreContext.postOnCoreThread { coreContext.postOnCoreThread {
binding.localPreviewVideoSurface.setOnTouchListener(previewTouchListener)
// Need to be done manually // Need to be done manually
callViewModel.updateCallDuration() callViewModel.updateCallDuration()
} }
} }
@SuppressLint("ClickableViewAccessibility")
override fun onPause() {
super.onPause()
binding.localPreviewVideoSurface.setOnTouchListener(null)
}
} }

View file

@ -102,9 +102,9 @@ class ConferenceViewModel {
) { ) {
if (conference.isMe(device.address)) { if (conference.isMe(device.address)) {
val direction = device.getStreamCapability(StreamType.Video) val direction = device.getStreamCapability(StreamType.Video)
isMeParticipantSendingVideo.postValue( val sendingVideo = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly isMeParticipantSendingVideo.postValue(sendingVideo)
) Log.i("$TAG We ${if (sendingVideo) "are" else "aren't"} sending video")
} }
} }
@ -147,7 +147,24 @@ class ConferenceViewModel {
Log.i( Log.i(
"$TAG Participant device added: ${participantDevice.address.asStringUriOnly()}" "$TAG Participant device added: ${participantDevice.address.asStringUriOnly()}"
) )
addParticipantDevice(participantDevice)
// Since we do not compute our own devices until another participant joins,
// We have to do it when someone else joins
if (participantDevices.value.orEmpty().isEmpty()) {
val list = arrayListOf<ConferenceParticipantDeviceModel>()
val ourDevices = conference.me.devices
Log.i("$TAG We have [${ourDevices.size}] devices, now it's time to add them")
for (device in ourDevices) {
val model = ConferenceParticipantDeviceModel(device, true)
list.add(model)
}
val newModel = ConferenceParticipantDeviceModel(participantDevice)
list.add(newModel)
participantDevices.postValue(sortParticipantDevicesList(list))
} else {
addParticipantDevice(participantDevice)
}
} }
@WorkerThread @WorkerThread
@ -202,6 +219,10 @@ class ConferenceViewModel {
override fun onStateChanged(conference: Conference, state: Conference.State) { override fun onStateChanged(conference: Conference, state: Conference.State) {
Log.i("$TAG State changed [$state]") Log.i("$TAG State changed [$state]")
if (conference.state == Conference.State.Created) { if (conference.state == Conference.State.Created) {
val isIn = conference.isIn
isPaused.postValue(!isIn)
Log.i("$TAG We ${if (isIn) "are" else "aren't"} in the conference")
computeParticipants() computeParticipants()
} }
} }
@ -226,15 +247,20 @@ class ConferenceViewModel {
isCurrentCallInConference.postValue(true) isCurrentCallInConference.postValue(true)
conference = conf conference = conf
conference.addListener(conferenceListener) conference.addListener(conferenceListener)
isPaused.postValue(conference.isIn)
val isIn = conference.isIn
isPaused.postValue(!isIn)
Log.i("$TAG We ${if (isIn) "are" else "aren't"} in the conference right now")
val screenSharing = conference.screenSharingParticipant != null val screenSharing = conference.screenSharingParticipant != null
isScreenSharing.postValue(screenSharing) isScreenSharing.postValue(screenSharing)
val confSubject = conference.subject.orEmpty()
Log.i( Log.i(
"$TAG Configuring conference with subject [${conference.subject}] from call [${call.callLog.callId}]" "$TAG Configuring conference with subject [$confSubject] from call [${call.callLog.callId}]"
) )
sipUri.postValue(conference.conferenceAddress.asStringUriOnly()) sipUri.postValue(conference.conferenceAddress.asStringUriOnly())
subject.postValue(conference.subject) subject.postValue(confSubject)
if (conference.state == Conference.State.Created) { if (conference.state == Conference.State.Created) {
computeParticipants() computeParticipants()
@ -420,10 +446,10 @@ class ConferenceViewModel {
val meParticipantModel = ConferenceParticipantModel(meParticipant, admin, true, null, null) val meParticipantModel = ConferenceParticipantModel(meParticipant, admin, true, null, null)
participantsList.add(meParticipantModel) participantsList.add(meParticipantModel)
if (!skipDevices) { val ourDevices = conference.me.devices
val ourDevices = conference.me.devices Log.i("$TAG We have [${ourDevices.size}] devices")
Log.i("$TAG We have [${ourDevices.size}] devices") for (device in ourDevices) {
for (device in ourDevices) { if (!skipDevices) {
val model = ConferenceParticipantDeviceModel(device, true) val model = ConferenceParticipantDeviceModel(device, true)
devicesList.add(model) devicesList.add(model)
@ -433,12 +459,12 @@ class ConferenceViewModel {
activeSpeaker.postValue(model) activeSpeaker.postValue(model)
activeSpeakerParticipantDeviceFound = true activeSpeakerParticipantDeviceFound = true
} }
val direction = device.getStreamCapability(StreamType.Video)
isMeParticipantSendingVideo.postValue(
direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
)
} }
val direction = device.getStreamCapability(StreamType.Video)
val sendingVideo = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
isMeParticipantSendingVideo.postValue(sendingVideo)
Log.i("$TAG We ${if (sendingVideo) "are" else "aren't"} sending video right now")
} }
if (!activeSpeakerParticipantDeviceFound && devicesList.isNotEmpty()) { if (!activeSpeakerParticipantDeviceFound && devicesList.isNotEmpty()) {
@ -453,7 +479,14 @@ class ConferenceViewModel {
participants.postValue(sortParticipantList(participantsList)) participants.postValue(sortParticipantList(participantsList))
if (!skipDevices) { if (!skipDevices) {
checkIfTooManyParticipantDevicesForGridLayout(devicesList) checkIfTooManyParticipantDevicesForGridLayout(devicesList)
participantDevices.postValue(sortParticipantDevicesList(devicesList))
if (participantsList.size == 1) {
Log.i("$TAG We are alone in that conference, not posting devices list for now")
participantDevices.postValue(arrayListOf())
} else {
participantDevices.postValue(sortParticipantDevicesList(devicesList))
}
participantsLabel.postValue( participantsLabel.postValue(
AppUtils.getStringWithPlural( AppUtils.getStringWithPlural(
R.plurals.conference_participants_list_title, R.plurals.conference_participants_list_title,

View file

@ -235,6 +235,20 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="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_marginEnd="10dp"
android:layout_marginBottom="@{viewModel.fullScreenMode || viewModel.pipMode ? @dimen/zero : @dimen/call_main_actions_menu_margin, default=@dimen/call_main_actions_menu_margin}"
android:visibility="@{conferenceViewModel.isMeParticipantSendingVideo &amp;&amp; conferenceViewModel.participants.size() == 1 &amp;&amp; !conferenceViewModel.isPaused ? View.VISIBLE : View.GONE}"
app:alignTopRight="true"
app:displayMode="black_bars"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="200dp"
app:layout_constraintWidth_max="200dp" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/conference_layout_nav_host_fragment" android:id="@+id/conference_layout_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
@ -242,7 +256,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginBottom="@{viewModel.fullScreenMode || viewModel.pipMode ? @dimen/zero : @dimen/call_main_actions_menu_margin, default=@dimen/call_main_actions_menu_margin}" android:layout_marginBottom="@{viewModel.fullScreenMode || viewModel.pipMode ? @dimen/zero : @dimen/call_main_actions_menu_margin, default=@dimen/call_main_actions_menu_margin}"
android:layout_marginTop="@{viewModel.fullScreenMode || viewModel.pipMode ? @dimen/zero : @dimen/call_remote_video_top_margin, default=@dimen/call_remote_video_top_margin}" android:layout_marginTop="@{viewModel.fullScreenMode || viewModel.pipMode ? @dimen/zero : @dimen/call_remote_video_top_margin, default=@dimen/call_remote_video_top_margin}"
android:visibility="@{conferenceViewModel.participantDevices.size() > 1 &amp;&amp; !conferenceViewModel.isPaused ? View.VISIBLE : View.GONE}" android:visibility="@{conferenceViewModel.participants.size() > 1 &amp;&amp; !conferenceViewModel.isPaused ? View.VISIBLE : View.GONE, default=gone}"
app:navGraph="@navigation/conference_nav_graph" app:navGraph="@navigation/conference_nav_graph"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"