mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-04-30 07:56:22 +00:00
Display our own video (if enabled) while waiting for other participants to join the conference
This commit is contained in:
parent
065cdfa8c1
commit
fea42aba3b
3 changed files with 113 additions and 18 deletions
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 && conferenceViewModel.participants.size() == 1 && !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 && !conferenceViewModel.isPaused ? View.VISIBLE : View.GONE}"
|
android:visibility="@{conferenceViewModel.participants.size() > 1 && !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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue