mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Using new APIs to be able to make asymetrical video calls
This commit is contained in:
parent
d6ea531cea
commit
9c1b9b2939
19 changed files with 104 additions and 49 deletions
|
|
@ -19,6 +19,9 @@ upload_bw=0
|
|||
|
||||
[video]
|
||||
size=vga
|
||||
automatically_accept=1
|
||||
automatically_initiate=0
|
||||
automatically_accept_direction=2 #receive only
|
||||
|
||||
[app]
|
||||
tunnel=disabled
|
||||
|
|
|
|||
|
|
@ -36,8 +36,6 @@ android_disable_audio_focus_requests=1
|
|||
displaytype=MSAndroidTextureDisplay
|
||||
auto_resize_preview_to_keep_ratio=1
|
||||
max_conference_size=vga
|
||||
automatically_accept=1
|
||||
automatically_initiate=0
|
||||
|
||||
[misc]
|
||||
enable_basic_to_client_group_chat_room_migration=0
|
||||
|
|
|
|||
|
|
@ -249,6 +249,16 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
|||
Log.i("$TAG Configuring Core")
|
||||
core.videoCodecPriorityPolicy = CodecPriorityPolicy.Auto
|
||||
|
||||
val oldVersion = corePreferences.linphoneConfigurationVersion
|
||||
val newVersion = "6.0.0"
|
||||
if (oldVersion == "5.2") {
|
||||
Log.i("$TAG Migrating configuration from [$oldVersion] to [$newVersion]")
|
||||
val policy = core.videoActivationPolicy.clone()
|
||||
policy.automaticallyAccept = true
|
||||
policy.automaticallyAcceptDirection = MediaDirection.RecvOnly
|
||||
core.videoActivationPolicy = policy
|
||||
}
|
||||
|
||||
updateFriendListsSubscriptionDependingOnDefaultAccount()
|
||||
|
||||
computeUserAgent()
|
||||
|
|
@ -262,7 +272,7 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
|||
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
audioManager.registerAudioDeviceCallback(audioDeviceCallback, coreThread)
|
||||
|
||||
corePreferences.linphoneConfigurationVersion = "6.0"
|
||||
corePreferences.linphoneConfigurationVersion = newVersion
|
||||
|
||||
Log.i("$TAG Report Core created and started")
|
||||
}
|
||||
|
|
@ -354,6 +364,30 @@ class CoreContext @UiThread constructor(val context: Context) : HandlerThread("C
|
|||
return found != null
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun startAudioCall(
|
||||
address: Address,
|
||||
forceZRTP: Boolean = false,
|
||||
localAddress: Address? = null
|
||||
) {
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
params?.videoDirection = MediaDirection.Inactive
|
||||
startCall(address, params, forceZRTP, localAddress)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun startVideoCall(
|
||||
address: Address,
|
||||
forceZRTP: Boolean = false,
|
||||
localAddress: Address? = null
|
||||
) {
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
params?.videoDirection = MediaDirection.SendRecv
|
||||
startCall(address, params, forceZRTP, localAddress)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun startCall(
|
||||
address: Address,
|
||||
|
|
|
|||
|
|
@ -335,6 +335,22 @@ class ActiveCallFragment : GenericCallFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
callViewModel.isSendingVideo.observe(viewLifecycleOwner) { sending ->
|
||||
coreContext.core.nativePreviewWindowId = if (sending) {
|
||||
binding.localPreviewVideoSurface
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.isReceivingVideo.observe(viewLifecycleOwner) { receiving ->
|
||||
coreContext.core.nativeVideoWindowId = if (receiving) {
|
||||
binding.remoteVideoSurface
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
callViewModel.chatRoomCreationErrorEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { error ->
|
||||
(requireActivity() as CallActivity).showRedToast(
|
||||
|
|
@ -368,8 +384,6 @@ class ActiveCallFragment : GenericCallFragment() {
|
|||
super.onResume()
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
core.nativeVideoWindowId = binding.remoteVideoSurface
|
||||
coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface
|
||||
binding.localPreviewVideoSurface.setOnTouchListener(previewTouchListener)
|
||||
|
||||
// Need to be done manually
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class NewCallFragment : AbstractNewTransferCallFragment() {
|
|||
@WorkerThread
|
||||
override fun action(address: Address) {
|
||||
Log.i("$TAG Calling [${address.asStringUriOnly()}]")
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
|
||||
coreContext.postOnMainThread {
|
||||
findNavController().popBackStack()
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val isVideoEnabled = MutableLiveData<Boolean>()
|
||||
|
||||
val isSendingVideo = MutableLiveData<Boolean>()
|
||||
|
||||
val isReceivingVideo = MutableLiveData<Boolean>()
|
||||
|
||||
val showSwitchCamera = MutableLiveData<Boolean>()
|
||||
|
||||
val isOutgoing = MutableLiveData<Boolean>()
|
||||
|
|
@ -230,6 +234,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
Log.i("$TAG Call [${call.remoteAddress.asStringUriOnly()}] state changed [$state]")
|
||||
if (LinphoneUtils.isCallOutgoing(call.state)) {
|
||||
isVideoEnabled.postValue(call.params.isVideoEnabled)
|
||||
updateVideoDirection(call.currentParams.videoDirection)
|
||||
} else if (LinphoneUtils.isCallEnding(call.state)) {
|
||||
// If current call is being terminated but there is at least one other call, switch
|
||||
val core = call.core
|
||||
|
|
@ -270,6 +275,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
isVideoEnabled.postValue(videoEnabled)
|
||||
updateVideoDirection(call.currentParams.videoDirection)
|
||||
|
||||
// Toggle full screen OFF when remote disables video
|
||||
if (!videoEnabled && fullScreenMode.value == true) {
|
||||
|
|
@ -606,10 +612,15 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
params?.videoDirection = MediaDirection.SendRecv
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params?.isVideoEnabled = params?.isVideoEnabled == false
|
||||
} else if (params != null) {
|
||||
params.isVideoEnabled = true
|
||||
params.videoDirection = when (currentCall.currentParams.videoDirection) {
|
||||
MediaDirection.RecvOnly -> MediaDirection.SendRecv
|
||||
MediaDirection.SendRecv, MediaDirection.SendOnly -> MediaDirection.RecvOnly
|
||||
else -> MediaDirection.SendOnly
|
||||
}
|
||||
Log.i(
|
||||
"$TAG Updating call with video enabled set to ${params?.isVideoEnabled}"
|
||||
"$TAG Updating call with video enabled and media direction set to ${params.videoDirection}"
|
||||
)
|
||||
}
|
||||
currentCall.update(params)
|
||||
|
|
@ -967,6 +978,7 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
} else {
|
||||
isVideoEnabled.postValue(call.currentParams.isVideoEnabled)
|
||||
}
|
||||
updateVideoDirection(call.currentParams.videoDirection)
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
coreContext.context,
|
||||
|
|
@ -1056,6 +1068,16 @@ class CurrentCallViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun updateVideoDirection(direction: MediaDirection) {
|
||||
isSendingVideo.postValue(
|
||||
direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly
|
||||
)
|
||||
isReceivingVideo.postValue(
|
||||
direction == MediaDirection.SendRecv || direction == MediaDirection.RecvOnly
|
||||
)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun updateCallQualityIcon() {
|
||||
viewModelScope.launch {
|
||||
|
|
|
|||
|
|
@ -622,7 +622,7 @@ class MainActivity : GenericActivity() {
|
|||
)
|
||||
Log.i("$TAG Interpreted SIP URI is [${address?.asStringUriOnly()}]")
|
||||
if (address != null) {
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ class ConversationModel @WorkerThread constructor(
|
|||
coreContext.postOnCoreThread {
|
||||
val address = chatRoom.participants.firstOrNull()?.address ?: chatRoom.peerAddress
|
||||
Log.i("$TAG Calling [${address.asStringUriOnly()}]")
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -557,7 +557,7 @@ class MessageModel @WorkerThread constructor(
|
|||
Log.i("$TAG Clicked on SIP URI: $text")
|
||||
val address = coreContext.core.interpretUrl(text, false)
|
||||
if (address != null) {
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
} else {
|
||||
Log.w("$TAG Failed to parse [$text] as SIP URI")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class ConversationInfoViewModel @UiThread constructor() : AbstractConversationVi
|
|||
Log.i(
|
||||
"$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}"
|
||||
)
|
||||
coreContext.startCall(conferenceAddress)
|
||||
coreContext.startVideoCall(conferenceAddress)
|
||||
} else {
|
||||
Log.e("$TAG Conference info URI is null!")
|
||||
// TODO: notify error to user
|
||||
|
|
@ -284,9 +284,7 @@ class ConversationInfoViewModel @UiThread constructor() : AbstractConversationVi
|
|||
val address = firstParticipant?.address
|
||||
if (address != null) {
|
||||
Log.i("$TAG Audio calling SIP address [${address.asStringUriOnly()}]")
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = false
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startAudioCall(address)
|
||||
} else {
|
||||
Log.e("$TAG Failed to find participant to call!")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
Log.i(
|
||||
"$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}"
|
||||
)
|
||||
coreContext.startCall(conferenceAddress)
|
||||
coreContext.startVideoCall(conferenceAddress)
|
||||
} else {
|
||||
Log.e("$TAG Conference info URI is null!")
|
||||
// TODO: notify error to user
|
||||
|
|
@ -394,9 +394,7 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo
|
|||
val address = firstParticipant?.address
|
||||
if (address != null) {
|
||||
Log.i("$TAG Audio calling SIP address [${address.asStringUriOnly()}]")
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = false
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,15 +140,11 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
when (expectedAction) {
|
||||
START_AUDIO_CALL -> {
|
||||
Log.i("$TAG Audio calling SIP address [${address.asStringUriOnly()}]")
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = false
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
START_VIDEO_CALL -> {
|
||||
Log.i("$TAG Video calling SIP address [${address.asStringUriOnly()}]")
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startVideoCall(address)
|
||||
}
|
||||
START_CONVERSATION -> {
|
||||
Log.i(
|
||||
|
|
@ -405,7 +401,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
"$TAG Only 1 SIP address found for contact [${friend.name}], starting audio call directly"
|
||||
)
|
||||
val address = friend.addresses.first()
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
|
||||
val number = friend.phoneNumbers.first()
|
||||
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
|
||||
|
|
@ -413,7 +409,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
Log.i(
|
||||
"$TAG Only 1 phone number found for contact [${friend.name}], starting audio call directly"
|
||||
)
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
} else {
|
||||
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
|
||||
}
|
||||
|
|
@ -442,9 +438,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
"$TAG Only 1 SIP address found for contact [${friend.name}], starting video call directly"
|
||||
)
|
||||
val address = friend.addresses.first()
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startVideoCall(address)
|
||||
} else if (addressesCount == 0 && numbersCount == 1 && enablePhoneNumbers) {
|
||||
val number = friend.phoneNumbers.first()
|
||||
val address = core.interpretUrl(number, LinphoneUtils.applyInternationalPrefix())
|
||||
|
|
@ -452,9 +446,7 @@ class ContactViewModel @UiThread constructor() : ViewModel() {
|
|||
Log.i(
|
||||
"$TAG Only 1 phone number found for contact [${friend.name}], starting video call directly"
|
||||
)
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startVideoCall(address)
|
||||
} else {
|
||||
Log.e("$TAG Failed to interpret phone number [$number] as SIP address")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ class HistoryListFragment : AbstractTopBarFragment() {
|
|||
)
|
||||
} else {
|
||||
Log.i("$TAG Starting call to [${model.address.asStringUriOnly()}]")
|
||||
coreContext.startCall(model.address)
|
||||
coreContext.startAudioCall(model.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ class StartCallFragment : GenericAddressPickerFragment() {
|
|||
|
||||
@WorkerThread
|
||||
override fun onSingleAddressSelected(address: Address, friend: Friend) {
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
|||
|
|
@ -140,18 +140,14 @@ class ContactHistoryViewModel @UiThread constructor() : ViewModel() {
|
|||
@UiThread
|
||||
fun startAudioCall() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = false
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun startVideoCall() {
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val params = core.createCallParams(null)
|
||||
params?.isVideoEnabled = true
|
||||
coreContext.startCall(address, params)
|
||||
coreContext.startVideoCall(address)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() {
|
|||
Log.i(
|
||||
"$TAG Conference info created, address is ${conferenceAddress.asStringUriOnly()}"
|
||||
)
|
||||
coreContext.startCall(conferenceAddress)
|
||||
coreContext.startVideoCall(conferenceAddress)
|
||||
} else {
|
||||
Log.e("$TAG Conference info URI is null!")
|
||||
// TODO: notify error to user
|
||||
|
|
@ -122,7 +122,7 @@ class StartCallViewModel @UiThread constructor() : AddressSelectionViewModel() {
|
|||
)
|
||||
if (address != null) {
|
||||
Log.i("$TAG Calling [${address.asStringUriOnly()}]")
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
} else {
|
||||
Log.e("$TAG Failed to parse [$suggestion] as SIP address")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ abstract class AddressSelectionViewModel @UiThread constructor() : DefaultAccoun
|
|||
}
|
||||
|
||||
val model = ContactOrSuggestionModel(address) {
|
||||
coreContext.startCall(address)
|
||||
coreContext.startAudioCall(address)
|
||||
}
|
||||
|
||||
suggestionsList.add(model)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
android:padding="@dimen/call_button_icon_padding"
|
||||
android:enabled="@{!viewModel.isPaused && !viewModel.isPausedByRemote}"
|
||||
android:visibility="@{viewModel.hideVideo ? View.GONE : View.VISIBLE}"
|
||||
android:src="@{viewModel.isVideoEnabled ? @drawable/video_camera : @drawable/video_camera_slash, default=@drawable/video_camera}"
|
||||
android:src="@{viewModel.isSendingVideo ? @drawable/video_camera : @drawable/video_camera_slash, default=@drawable/video_camera}"
|
||||
android:background="@drawable/in_call_button_background_red"
|
||||
app:tint="@color/in_call_button_tint_color"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@
|
|||
android:layout_marginBottom="@{viewModel.fullScreenMode || viewModel.pipMode || viewModel.halfOpenedFolded ? @dimen/zero : @dimen/call_main_actions_menu_margin, default=@dimen/call_main_actions_menu_margin}"
|
||||
android:layout_marginTop="@{viewModel.fullScreenMode || viewModel.pipMode || viewModel.halfOpenedFolded ? @dimen/zero : @dimen/call_remote_video_top_margin, default=@dimen/call_remote_video_top_margin}"
|
||||
android:onClick="@{() -> viewModel.toggleFullScreen()}"
|
||||
android:visibility="@{viewModel.isVideoEnabled && !(viewModel.isPaused || viewModel.isPausedByRemote) ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{viewModel.isReceivingVideo && !(viewModel.isPaused || viewModel.isPausedByRemote) ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/hinge_bottom"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:src="@drawable/camera_rotate"
|
||||
android:visibility="@{!viewModel.fullScreenMode && !viewModel.pipMode && viewModel.isVideoEnabled && viewModel.showSwitchCamera ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{!viewModel.fullScreenMode && !viewModel.pipMode && viewModel.isSendingVideo && viewModel.showSwitchCamera ? View.VISIBLE : View.GONE}"
|
||||
app:tint="@color/white"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back"
|
||||
|
|
@ -262,7 +262,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:visibility="@{viewModel.isVideoEnabled && !(viewModel.isPaused || viewModel.isPausedByRemote) ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{viewModel.isSendingVideo && !(viewModel.isPaused || viewModel.isPausedByRemote) ? View.VISIBLE : View.GONE}"
|
||||
app:alignTopRight="true"
|
||||
app:displayMode="black_bars"
|
||||
app:layout_constraintBottom_toBottomOf="@id/remote_video_surface"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue