diff --git a/NLiteAVDemo-Android-Java/app/build.gradle b/NLiteAVDemo-Android-Java/app/build.gradle
index b1505cc..5f23573 100644
--- a/NLiteAVDemo-Android-Java/app/build.gradle
+++ b/NLiteAVDemo-Android-Java/app/build.gradle
@@ -62,5 +62,7 @@ dependencies {
implementation(libs.okhttpLoggingInterceptor)
implementation(libs.retrofitCore)
implementation(libs.retrofitConverterGson)
+ implementation(libs.nertc.nenn)
+ implementation(libs.nertc.segment)
implementation project(':call-ui')
}
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/app/src/main/AndroidManifest.xml b/NLiteAVDemo-Android-Java/app/src/main/AndroidManifest.xml
index 71fa168..8e9ed18 100644
--- a/NLiteAVDemo-Android-Java/app/src/main/AndroidManifest.xml
+++ b/NLiteAVDemo-Android-Java/app/src/main/AndroidManifest.xml
@@ -94,6 +94,12 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
+
+
result, Throwable exception) {
}
@Override
- public boolean fetchNicknameByTeam(@NotNull String s, @NotNull String s1, @NotNull Function1 super String, Unit> function1) {
- return false;
- }
-
- @Override
- public boolean loadAvatar(@NotNull Context context, @NotNull String s, @NotNull ImageView imageView) {
+ public boolean fetchAvatar(@NonNull Context context, @NonNull String accId, @NonNull Function2 super String, ? super Integer, Unit> notify) {
return false;
}
}
diff --git a/NLiteAVDemo-Android-Java/app/src/main/java/com/netease/yunxin/app/videocall/TestActivity.java b/NLiteAVDemo-Android-Java/app/src/main/java/com/netease/yunxin/app/videocall/TestActivity.java
new file mode 100644
index 0000000..fd1bb8f
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/app/src/main/java/com/netease/yunxin/app/videocall/TestActivity.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2022 NetEase, Inc. All rights reserved.
+// Use of this source code is governed by a MIT license that can be
+// found in the LICENSE file.
+
+package com.netease.yunxin.app.videocall;
+
+import androidx.annotation.NonNull;
+
+import com.netease.yunxin.nertc.ui.base.CallParam;
+import com.netease.yunxin.nertc.ui.p2p.P2PCallFragmentActivity;
+import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig;
+
+public class TestActivity extends P2PCallFragmentActivity {
+
+ @NonNull
+ @Override
+ protected P2PUIConfig provideUIConfig(CallParam param) {
+ return new P2PUIConfig.Builder()
+ .enableVirtualBlur(true)
+ .enableFloatingWindow(true)
+ .enableVirtualBlur(true)
+ .enableForegroundService(true)
+ .build();
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/build.gradle b/NLiteAVDemo-Android-Java/build.gradle
index 73115c5..d07b93d 100644
--- a/NLiteAVDemo-Android-Java/build.gradle
+++ b/NLiteAVDemo-Android-Java/build.gradle
@@ -44,6 +44,7 @@ ext {
AppKey = ''
BaseUrl = ''
libs = new Libs()
+ libs.nertc = new Libs.Nertc()
}
class Libs{
@@ -60,9 +61,16 @@ class Libs{
static def alog = "com.netease.yunxin.kit:alog:1.1.0"
static def kotlinxCoroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
static def kotlinxCoroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
- static def call = "com.netease.yunxin.kit.call:call:2.1.0"
+ static def call = "com.netease.yunxin.kit.call:call:2.2.0"
static def okhttp = "com.squareup.okhttp3:okhttp:4.9.3"
static def okhttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor:4.9.3"
static def retrofitCore = "com.squareup.retrofit2:retrofit:2.9.0"
static def retrofitConverterGson = "com.squareup.retrofit2:converter-gson:2.9.0"
+
+ static class Nertc {
+ def nenn = "com.netease.yunxin:nertc-nenn:5.5.2"
+ def segment = "com.netease.yunxin:nertc-segment:5.5.2"
+ }
+
+ static def nertc
}
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/AndroidManifest.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/AndroidManifest.xml
index c22091b..1be9d7f 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/AndroidManifest.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/AndroidManifest.xml
@@ -13,6 +13,7 @@
+
@@ -30,14 +31,9 @@
-
-
,
+ observer: ResultObserver?
+ ): Boolean {
+ observer?.onResult(
+ ResultInfo(
+ value = NECallEngine.sharedInstance().callInfo.callStatus == CallState.STATE_IDLE,
+ success = true
+ )
+ )
+ return true
+ }
+ }
+ )
+ )
+
XKitRouter.registerRouter(
Constants.PATH_CALL_SINGLE_PAGE,
XKitRouter.RouterValue(
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/CommonCallActivity.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/CommonCallActivity.kt
index 7bd06b9..d543eab 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/CommonCallActivity.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/CommonCallActivity.kt
@@ -11,6 +11,7 @@ import android.view.View
import android.view.Window
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
+import com.netease.lava.api.IVideoRender
import com.netease.lava.nertc.sdk.NERtcConstants
import com.netease.lava.nertc.sdk.NERtcEx
import com.netease.lava.nertc.sdk.video.NERtcVideoView
@@ -24,11 +25,16 @@ import com.netease.yunxin.kit.call.p2p.model.NECallType
import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
import com.netease.yunxin.kit.call.p2p.model.NEInviteInfo
import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
+import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
+import com.netease.yunxin.nertc.ui.floating.FloatingPermission
+import com.netease.yunxin.nertc.ui.p2p.CallUIFloatingWindowMgr
import com.netease.yunxin.nertc.ui.p2p.CallUIOperationsMgr
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
-import com.netease.yunxin.nertc.ui.service.CallForegroundService
+import com.netease.yunxin.nertc.ui.utils.AppForegroundWatcherHelper
import com.netease.yunxin.nertc.ui.utils.PermissionTipDialog
+import com.netease.yunxin.nertc.ui.utils.SwitchCallTypeConfirmDialog
+import com.netease.yunxin.nertc.ui.view.OverLayPermissionDialog
abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
private val tag = "CommonCallActivity"
@@ -56,14 +62,42 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
protected val cameraDeviceStatus
get() = CallUIOperationsMgr.callInfoWithUIState.cameraDeviceStatus
+ protected val isLocalSmallVideo
+ get() = CallUIOperationsMgr.callInfoWithUIState.isLocalSmallVideo
+
+ protected val isVirtualBlur
+ get() = CallUIOperationsMgr.callInfoWithUIState.isVirtualBlur
+
protected var uiConfig: P2PUIConfig? = null
+ protected val isFromFloatingWindow: Boolean
+ get() {
+ return intent.getBooleanExtra(
+ Constants.PARAM_KEY_FLAG_IS_FROM_FLOATING_WINDOW,
+ false
+ ) || CallUIFloatingWindowMgr.isFloating()
+ }
+
private var occurError = false
- private var serviceId: String? = null
+ private var switchConfirmDialog: SwitchCallTypeConfirmDialog? = null
private var permissionTipDialog: PermissionTipDialog? = null
+ private var overLayPermissionDialog: OverLayPermissionDialog? = null
+
+ private val watcher = object : AppForegroundWatcherHelper.Watcher() {
+ override fun onBackground() {
+ if (uiConfig?.enableFloatingWindow == true &&
+ uiConfig?.enableAutoFloatingWindowWhenHome == true &&
+ CallUIOperationsMgr.currentCallState() != CallState.STATE_INVITED &&
+ CallUIOperationsMgr.currentCallState() != CallState.STATE_IDLE
+ ) {
+ doShowFloatingWindowInner()
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 禁止自动锁屏
@@ -71,36 +105,44 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
// 取消呼叫来电通知
CallUIOperationsMgr.cancelCallNotification(this)
// 初始化呼叫信息
- CallUIOperationsMgr.initCallInfoAndUIState(
- CallUIOperationsMgr.CallInfoWithUIState(callParam = initIntentForCallParam())
- )
channelId = callParam.getChannelId()
uiConfig = provideUIConfig(callParam)?.apply {
ALog.d(tag, "current P2PUIConfig is $this.")
}
+ if (!isFromFloatingWindow) {
+ CallUIOperationsMgr.initCallInfoAndUIState(
+ CallUIOperationsMgr.CallInfoWithUIState(
+ callParam = initIntentForCallParam()
+ ),
+ foregroundServiceConfig = if (uiConfig?.enableForegroundService == true) {
+ CallUIOperationsMgr.CallForegroundServiceConfig(
+ this,
+ intent,
+ uiConfig?.foregroundNotificationConfig
+ )
+ } else {
+ null
+ }
+ )
+ } else {
+ val info = CallUIOperationsMgr.currentSwitchTypeCallInfo()
+ if (switchConfirmDialog?.isShowing != true && info?.state == SwitchCallState.INVITE) {
+ showSwitchCallTypeConfirmDialog(info.callType)
+ }
+ }
doOnCreate(savedInstanceState)
+ // 配置前后台监听
+ AppForegroundWatcherHelper.addWatcher(watcher)
}
open fun doOnCreate(savedInstanceState: Bundle?) {
- provideLayoutId()?.run {
- setContentView(this)
- } ?: provideLayoutView()?.run {
- setContentView(this)
- } ?: throw IllegalArgumentException("provideLayoutId or provideLayoutView must not be null.")
-
+ setContentView(provideLayoutId())
// 由于页面启动耗时,可能出现页面启动中通话被挂断,此时需要销毁页面
if (callParam.isCalled && callEngine.callInfo.callStatus == CallState.STATE_IDLE) {
releaseAndFinish(false)
return
}
callEngine.addCallDelegate(this)
- if (uiConfig?.enableForegroundService == true) {
- serviceId = CallForegroundService.launchForegroundService(
- this,
- intent,
- uiConfig?.foregroundNotificationConfig
- )
- }
}
open fun configWindow() {
@@ -113,6 +155,15 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
override fun onResume() {
super.onResume()
+ if (overLayPermissionDialog?.isShowing == true &&
+ FloatingPermission.isFloatPermissionValid(this)
+ ) {
+ overLayPermissionDialog?.dismiss()
+ overLayPermissionDialog = null
+ }
+ if (CallUIFloatingWindowMgr.isFloating()) {
+ CallUIFloatingWindowMgr.releaseFloat(false)
+ }
when (cameraDeviceStatus) {
NERtcConstants.VideoDeviceState.DISCONNECTED,
NERtcConstants.VideoDeviceState.FREEZED,
@@ -129,17 +180,28 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
}
}
- override fun onDestroy() {
- super.onDestroy()
- callEngine.removeCallDelegate(this)
- if (uiConfig?.enableForegroundService == true) {
- serviceId?.run {
- CallForegroundService.stopService(this@CommonCallActivity, this)
+ override fun onPause() {
+ super.onPause()
+ if (isFinishing) {
+ val floatingEnable =
+ uiConfig?.enableFloatingWindow != true || !CallUIFloatingWindowMgr.isFloating()
+ if (floatingEnable) {
+ stopVideoPreview()
}
+ releaseAndFinish(
+ CallUIOperationsMgr.currentCallState() != CallState.STATE_IDLE && floatingEnable
+ )
}
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // 移除前后台监听
+ AppForegroundWatcherHelper.removeWatcher(watcher)
permissionTipDialog?.dismiss()
permissionTipDialog = null
- CallUIOperationsMgr.releaseCallInfoAndUIState(channelId = channelId)
+ switchConfirmDialog?.dismiss()
+ switchConfirmDialog = null
}
override fun onBackPressed() {
@@ -147,9 +209,7 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
releaseAndFinish(true)
}
- protected open fun provideLayoutId(): Int? = null
-
- protected open fun provideLayoutView():View? = null
+ protected abstract fun provideLayoutId(): Int
protected open fun provideUIConfig(callParam: CallParam?): P2PUIConfig? {
return P2PUIConfig()
@@ -173,6 +233,11 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
CallUIOperationsMgr.doMuteAudio(mute)
}
+ @JvmOverloads
+ protected open fun doVirtualBlur(enable: Boolean = !isVirtualBlur) {
+ CallUIOperationsMgr.doVirtualBlur(enable)
+ }
+
@JvmOverloads
protected open fun doMuteVideo(mute: Boolean = !isLocalMuteVideo) {
when (uiConfig?.closeVideoType ?: Constants.CLOSE_TYPE_MUTE) {
@@ -201,19 +266,24 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
CallUIOperationsMgr.doSwitchCamera()
}
- protected fun doCall(
+ protected open fun doCall(
observer: NEResultObserver>?
) {
- CallUIOperationsMgr.doCall(callParam, observer)
+ CallUIOperationsMgr.doCall(
+ callParam
+ ) { result ->
+ channelId = result?.data?.signalInfo?.channelId
+ observer?.onResult(result)
+ }
}
@JvmOverloads
- protected fun doAccept(observer: NEResultObserver>? = null) {
+ protected open fun doAccept(observer: NEResultObserver>? = null) {
CallUIOperationsMgr.doAccept(observer)
}
@JvmOverloads
- protected fun doHangup(
+ protected open fun doHangup(
observer: NEResultObserver>? = null,
channelId: String? = null,
extraInfo: String? = null
@@ -222,7 +292,7 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
}
@JvmOverloads
- protected fun doSwitchCallType(
+ protected open fun doSwitchCallType(
callType: Int,
switchCallState: Int,
observer: NEResultObserver>? = null
@@ -230,11 +300,26 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
CallUIOperationsMgr.doSwitchCallType(callType, switchCallState, observer)
}
- protected open fun setupLocalView(view: NERtcVideoView?) {
- CallUIOperationsMgr.setupLocalView(view)
+ protected open fun setupLocalView(
+ view: NERtcVideoView?,
+ action: ((NERtcVideoView?) -> Unit)? = {
+ it?.run {
+ setZOrderMediaOverlay(true)
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ }
+ ) {
+ CallUIOperationsMgr.setupLocalView(view, action)
}
- protected open fun setupRemoteView(view: NERtcVideoView?) {
+ protected open fun setupRemoteView(
+ view: NERtcVideoView?,
+ action: ((NERtcVideoView?) -> Unit)? = {
+ it?.run {
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ }
+ ) {
CallUIOperationsMgr.setupRemoteView(view)
}
@@ -245,15 +330,70 @@ abstract class CommonCallActivity : AppCompatActivity(), NECallEngineDelegate {
if (!isFinishing) {
finish()
}
+ if (finishCall) {
+ CallUIOperationsMgr.doHangup(null, channelId = channelId)
+ CallUIOperationsMgr.releaseCallInfoAndUIState(channelId = channelId, force = true)
+ }
}
- protected fun showPermissionDialog(clickListener: View.OnClickListener): PermissionTipDialog {
+ protected open fun showPermissionDialog(clickListener: View.OnClickListener): PermissionTipDialog {
return PermissionTipDialog(this, clickListener).apply {
this@CommonCallActivity.permissionTipDialog = this
show()
}
}
+ protected open fun showSwitchCallTypeConfirmDialog(callType: Int): SwitchCallTypeConfirmDialog {
+ val dialog = this.switchConfirmDialog
+ if (dialog?.isShowing == true) {
+ return dialog
+ }
+ return SwitchCallTypeConfirmDialog(this, {
+ doSwitchCallType(it, SwitchCallState.ACCEPT)
+ }, {
+ doSwitchCallType(it, SwitchCallState.REJECT)
+ }).apply {
+ this@CommonCallActivity.switchConfirmDialog = this
+ show(callType)
+ }
+ }
+
+ protected open fun showOverlayPermissionDialog(clickListener: View.OnClickListener): OverLayPermissionDialog {
+ return OverLayPermissionDialog(this, clickListener).apply {
+ this@CommonCallActivity.overLayPermissionDialog = this
+ show()
+ }
+ }
+
+ protected open fun doShowFloatingWindow() {
+ ALog.dApi(tag, "doShowFloatingWindow")
+ doShowFloatingWindowInner()
+ }
+
+ private fun doShowFloatingWindowInner() {
+ ALog.d(tag, "doShowFloatingWindowInner")
+ if (!FloatingPermission.isFloatPermissionValid(this)) {
+ if (overLayPermissionDialog?.isShowing != true) {
+ showOverlayPermissionDialog {
+ FloatingPermission.requireFloatPermission(this)
+ }
+ }
+ return
+ }
+ if (!CallUIFloatingWindowMgr.isFloating()) {
+ CallUIFloatingWindowMgr.showFloat(this.applicationContext)
+ }
+ finish()
+ }
+
+ protected open fun startVideoPreview() {
+ CallUIOperationsMgr.startVideoPreview()
+ }
+
+ protected open fun stopVideoPreview() {
+ CallUIOperationsMgr.stopVideoPreview()
+ }
+
override fun onReceiveInvited(info: NEInviteInfo) {
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/Constants.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/Constants.kt
index 4f5a070..65beae2 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/Constants.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/Constants.kt
@@ -43,9 +43,17 @@ object Constants {
*/
const val CLOSE_TYPE_COMPAT = 3
+ /**
+ * 是否来自浮窗
+ */
+ const val PARAM_KEY_FLAG_IS_FROM_FLOATING_WINDOW = "is_from_floating_window"
+
// path for single callkit
const val PATH_CALL_SINGLE_PAGE = "imkit://call/single.page"
+ // path for current is idle state.
+ const val PATH_IS_CALL_IDLE = "imkit://call/state/isIdle"
+
const val KEY_CALLER_ACC_ID = "caller_accid"
const val KEY_CALLED_ACC_ID = "called_accid"
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/TransferHelper.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/TransferHelper.kt
index 4c06fb3..efbfa60 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/TransferHelper.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/TransferHelper.kt
@@ -7,17 +7,27 @@
package com.netease.yunxin.nertc.ui.base
+import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import com.netease.yunxin.kit.alog.ALog
+
+private const val TAG = "TransHelper"
+
+const val KEY_PERMISSION_RESULT_GRANTED = "permissions_result_granted"
+const val KEY_PERMISSION_RESULT_DENIED = "permissions_result_denied"
+const val KEY_PERMISSION_RESULT_DENIED_FOREVER = "permissions_result_denied_forever"
private val transferMap = HashMap()
fun launchTask(
context: Context,
requestId: Int,
- action: (Context) -> Unit,
+ action: (Activity, Int) -> Unit,
result: (ResultInfo) -> Unit
) {
transferMap[requestId] = TransferHelperParam(action, result)
@@ -25,7 +35,7 @@ fun launchTask(
}
internal class TransferHelperParam(
- val action: ((Context) -> Unit)? = null,
+ val action: ((Activity, Int) -> Unit)? = null,
val result: ((ResultInfo) -> Unit)? = null
)
@@ -40,6 +50,9 @@ class TransferHelperActivity : AppCompatActivity() {
fun launch(context: Context, requestId: Int) {
Intent(context, TransferHelperActivity::class.java).apply {
putExtra(PARAM_KEY_FOR_REQUEST_ID, requestId)
+ if (context !is Activity) {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
context.startActivity(this)
}
}
@@ -51,14 +64,75 @@ class TransferHelperActivity : AppCompatActivity() {
if (param?.action == null) {
finish()
} else {
- param.action.invoke(this)
+ param.action.invoke(this, requestId)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- val param = transferMap.remove(requestId)
- param?.result?.invoke(ResultInfo(data, resultCode == RESULT_OK))
- finish()
+ try {
+ val param = transferMap.remove(requestId)
+ param?.result?.invoke(ResultInfo(data, resultCode == RESULT_OK))
+ } catch (ignored: Throwable) {
+ ALog.e(TAG, "onActivityResult", ignored)
+ } finally {
+ finish()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ try {
+ val param = transferMap.remove(requestId)
+ val callbackResult = param?.result ?: run {
+ finish()
+ return
+ }
+ permissions.toList()
+ val size = permissions.size
+ val grantedList = ArrayList()
+ val deniedList = ArrayList()
+ val deniedForeverList = ArrayList()
+ for (index in 0 until size) {
+ val permission = permissions[index]
+ when (grantResults[index]) {
+ PackageManager.PERMISSION_GRANTED -> grantedList.add(permission)
+ PackageManager.PERMISSION_DENIED -> {
+ if (!ActivityCompat.shouldShowRequestPermissionRationale(
+ this,
+ permission
+ )
+ ) {
+ deniedForeverList.add(permission)
+ } else {
+ deniedList.add(permission)
+ }
+ }
+ }
+ }
+
+ callbackResult.invoke(
+ ResultInfo(
+ Intent()
+ .apply {
+ putStringArrayListExtra(KEY_PERMISSION_RESULT_GRANTED, grantedList)
+ putStringArrayListExtra(KEY_PERMISSION_RESULT_DENIED, deniedList)
+ putStringArrayListExtra(
+ KEY_PERMISSION_RESULT_DENIED_FOREVER,
+ deniedForeverList
+ )
+ },
+ true
+ )
+ )
+ } catch (ignored: Throwable) {
+ ALog.e(TAG, "onRequestPermissionsResult", ignored)
+ } finally {
+ finish()
+ }
}
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHelper.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHelper.kt
index 7b188ce..c615398 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHelper.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHelper.kt
@@ -7,13 +7,17 @@
package com.netease.yunxin.nertc.ui.base
import android.content.Context
-import android.widget.ImageView
interface UserInfoHelper {
+ /**
+ * 用户根据 accId 内容,利用 notify 接口将用户昵称通知组件
+ */
fun fetchNickname(accId: String, notify: ((String) -> Unit)): Boolean
- fun fetchNicknameByTeam(accId: String, teamId: String, notify: ((String) -> Unit)): Boolean
-
- fun loadAvatar(context: Context, accId: String, avatar: ImageView?): Boolean
+ /**
+ * 用户根据 accId 内容,利用 notify 接口个将用户的头像链接通知组件,
+ * notify 中的两个字段其中一个为头像的url,另一个为加载头像失败后展示占位的本地资源 id
+ */
+ fun fetchAvatar(context: Context, accId: String, notify: (String?, Int?) -> Unit): Boolean
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHepersEx.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHepersEx.kt
index d9fc857..0661381 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHepersEx.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/base/UserInfoHepersEx.kt
@@ -23,7 +23,6 @@ import com.bumptech.glide.request.target.Target
import com.netease.nimlib.sdk.NIMClient
import com.netease.nimlib.sdk.RequestCallbackWrapper
import com.netease.nimlib.sdk.nos.NosService
-import com.netease.nimlib.sdk.team.TeamService
import com.netease.nimlib.sdk.uinfo.UserService
import com.netease.nimlib.sdk.uinfo.model.NimUserInfo
import com.netease.nimlib.sdk.uinfo.model.UserInfo
@@ -51,6 +50,11 @@ private val bgRes = intArrayOf(
*/
private const val AVATAR_NAME_LEN = 2
+/**
+ * 内部缓存用户昵称信息
+ */
+private val nameMap = mutableMapOf()
+
/**
* 直接展示用户头像,如果用户只有短链会自动解析长链进行展示
*/
@@ -61,17 +65,12 @@ fun String.loadAvatarByAccId(
txtView: TextView? = null,
enableTextDefaultAvatar: Boolean
) {
- val extension = UserInfoExtensionHelper.userInfoHelper
- if (extension?.loadAvatar(context, this, imageView) == true) {
- return
- }
val applicationContext = context.applicationContext
val defaultResId = R.drawable.t_avchat_avatar_default
val thumbSize = applicationContext.resources.getDimension(R.dimen.avatar_max_size).toInt()
- val loadAction: (String?, String) -> Unit = { url, name ->
-
+ val loadAction: (String?, Int, String) -> Unit = { url, res, name ->
val requestBuilder = Glide.with(applicationContext).asBitmap().load(url)
.listener(object : RequestListener {
override fun onLoadFailed(
@@ -101,7 +100,7 @@ fun String.loadAvatarByAccId(
isFirstResource: Boolean
): Boolean = false
}).transform(RoundedCornersCenterCrop(4.dip2Px(context))).apply(
- RequestOptions().placeholder(defaultResId).error(defaultResId)
+ RequestOptions().placeholder(res).error(res)
.override(thumbSize, thumbSize)
)
imageView?.run {
@@ -112,12 +111,12 @@ fun String.loadAvatarByAccId(
val userInfo: UserInfo? = NIMClient.getService(UserService::class.java).getUserInfo(this)
val getLongUrlAndLoad: (String?, String) -> Unit = { url, name ->
if (url == null) {
- loadAction(null, name)
+ loadAction(null, defaultResId, name)
} else {
NIMClient.getService(NosService::class.java).getOriginUrlFromShortUrl(url)
.setCallback(object : RequestCallbackWrapper() {
override fun onResult(code: Int, result: String?, exception: Throwable?) {
- loadAction(result, name)
+ loadAction(result, defaultResId, name)
bgView?.run {
Glide.with(applicationContext).asBitmap().load(result)
.transform(BlurCenterCorp(51, 3)).into(this)
@@ -126,6 +125,24 @@ fun String.loadAvatarByAccId(
})
}
}
+ // 用户自定义头像
+ val extension = UserInfoExtensionHelper.userInfoHelper
+ if (extension?.fetchAvatar(context, this) { url, res ->
+ loadAction(
+ url,
+ res ?: defaultResId,
+ nameMap[this] ?: this@loadAvatarByAccId
+ )
+ bgView?.run {
+ Glide.with(applicationContext).asBitmap().load(url)
+ .error(res ?: defaultResId)
+ .placeholder(res ?: defaultResId)
+ .transform(BlurCenterCorp(51, 3)).into(this)
+ }
+ } == true
+ ) {
+ return
+ }
if (userInfo?.avatar != null) {
getLongUrlAndLoad(userInfo.avatar, getNameFromInfo(userInfo.name, this@loadAvatarByAccId))
return
@@ -134,7 +151,7 @@ fun String.loadAvatarByAccId(
.setCallback(object : RequestCallbackWrapper>() {
override fun onResult(code: Int, result: List?, exception: Throwable?) {
if (result.isNullOrEmpty()) {
- loadAction(null, this@loadAvatarByAccId)
+ loadAction(null, defaultResId, this@loadAvatarByAccId)
return
}
getLongUrlAndLoad(
@@ -145,40 +162,13 @@ fun String.loadAvatarByAccId(
})
}
-/**
- * 通过 accId, teamId 获取用户在 teamId 中的昵称,如果昵称为空,则使用使用成员用户昵称,如用户昵称为空则使用 accId
- */
-fun String.fetchNicknameByTeam(teamId: String, notify: ((String) -> Unit)) {
- val extension = UserInfoExtensionHelper.userInfoHelper
- if (extension?.fetchNicknameByTeam(this, teamId) {
- notify(it)
- } == true
- ) {
- return
- }
- val teamMember =
- NIMClient.getService(TeamService::class.java).queryTeamMemberBlock(teamId, this)
-
- val name = if (teamMember?.teamNick?.trim()?.isEmpty() == false) {
- teamMember.teamNick
- } else {
- null
- }
- if (name == null) {
- fetchNickname {
- notify(it)
- }
- } else {
- notify(name)
- }
-}
-
/**
* 获取用户的 昵称信息,如果为空 则返回 accId
*/
fun String.fetchNickname(notify: ((String) -> Unit)) {
val extension = UserInfoExtensionHelper.userInfoHelper
if (extension?.fetchNickname(this) {
+ nameMap[this] = it
notify(it)
} == true
) {
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/DefaultFloatingTouchEventStrategy.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/DefaultFloatingTouchEventStrategy.kt
new file mode 100644
index 0000000..00cd3fc
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/DefaultFloatingTouchEventStrategy.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.floating
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.util.DisplayMetrics
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.core.content.ContextCompat
+import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.nertc.ui.R
+import kotlin.math.max
+
+/**
+ * 实现类似微信浮窗效果,支持拖动以及吸附效果
+ */
+class DefaultFloatingTouchEventStrategy(
+ private val context: Context,
+ private val bgColor: Int = ContextCompat.getColor(
+ context,
+ R.color.transparent
+ ),
+ radius: Float = 0f
+) : FloatingTouchEventStrategy {
+ private val logTag = "DefaultFloatingTouchEventStrategy"
+
+ private var widthPixel = -1
+ private var density = 1f
+
+ private var currentX = 0
+ private var currentY = 0
+ private val radiusPixel = radius.dpToPixel().toFloat()
+ private var animator = BgAndXPosAnimator()
+
+ private var gravity: Int = 0
+
+ private val leftBg = GradientDrawable().apply {
+ setColor(bgColor)
+ cornerRadii =
+ floatArrayOf(0f, 0f, radiusPixel, radiusPixel, radiusPixel, radiusPixel, 0f, 0f)
+ }
+
+ private val movingBg = GradientDrawable().apply {
+ setColor(bgColor)
+ cornerRadius = radiusPixel
+ }
+
+ private val rightBg = GradientDrawable().apply {
+ setColor(bgColor)
+ cornerRadii =
+ floatArrayOf(radiusPixel, radiusPixel, 0f, 0f, 0f, 0f, radiusPixel, radiusPixel)
+ }
+
+ init {
+ val wm = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
+ wm?.run {
+ val outMetrics = DisplayMetrics()
+ wm.defaultDisplay.getMetrics(outMetrics)
+ widthPixel = outMetrics.widthPixels
+ density = outMetrics.density
+ }
+ }
+
+ override fun initForWrapper(wrapper: FloatingWindowWrapper) {
+ wrapper.setBgRes(leftBg)
+ this.gravity = wrapper.config.windowParams.gravity
+ }
+
+ override fun handScrollEvent(event: MotionEvent, windowWrapper: FloatingWindowWrapper) {
+ if (animator.isRunning()) {
+ return
+ }
+ ALog.i(
+ logTag,
+ "x is ${event.x} , y is ${event.y} , rawX is ${event.rawX}, rawY is ${event.rawY}. detail is $event"
+ )
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ currentX = event.rawX.toInt()
+ currentY = event.rawY.toInt()
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ val nowX = event.rawX.toInt()
+ val nowY = event.rawY.toInt()
+ val movedX = nowX - currentX
+ val movedY = nowY - currentY
+ currentX = nowX
+ currentY = nowY
+ windowWrapper.updateWindowParamsPos(
+ max(
+ windowWrapper.fetchWindowParamsX() + if (gravity.and(Gravity.END) == Gravity.END) -movedX else movedX,
+ 0
+ ),
+ max(
+ windowWrapper.fetchWindowParamsY() + if (gravity.and(Gravity.TOP) == Gravity.TOP) movedY else -movedY,
+ 0
+ )
+ )
+ windowWrapper.setBgRes(movingBg)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ if (widthPixel < 0) {
+ return
+ }
+ toAdsorbEdge(event.rawX.toInt(), windowWrapper)
+ }
+ }
+ }
+
+ override fun toUpdateViewUI(wrapper: FloatingWindowWrapper) {
+ if (gravity.and(Gravity.END) == 0) {
+ toAdsorbEdge(wrapper.fetchWindowParamsX(), wrapper)
+ }
+ }
+
+ /**
+ * 吸附至边缘
+ */
+ private fun toAdsorbEdge(x: Int, windowWrapper: FloatingWindowWrapper) {
+ windowWrapper.post {
+ if (x > widthPixel / 2) {
+ animator.start(
+ windowWrapper.fetchWindowParamsX(),
+ if (gravity.and(Gravity.END) == Gravity.END) 0 else widthPixel - windowWrapper.measuredWidth,
+ rightBg,
+ windowWrapper
+ )
+ } else {
+ animator.start(
+ windowWrapper.fetchWindowParamsX(),
+ if (gravity.and(Gravity.END) == Gravity.END) widthPixel - windowWrapper.measuredWidth else 0,
+ leftBg,
+ windowWrapper
+ )
+ }
+ }
+ }
+
+ private var currentBgRes: Drawable? = null
+
+ /**
+ * 设置页面背景
+ */
+ private fun View.setBgRes(bg: Drawable) {
+ if (currentBgRes == bg) {
+ return
+ }
+ background = bg
+ currentBgRes = bg
+ }
+
+ /**
+ * dp 转换至 pixel
+ */
+ private fun Float.dpToPixel(): Int = (this * density + 0.5f).toInt()
+
+ /**
+ * 背景和 x坐标点的动画控制
+ */
+ private inner class BgAndXPosAnimator {
+ private var endXPos: Int? = null
+ private var finishedBg: Drawable? = null
+ private var windowWrapper: FloatingWindowWrapper? = null
+
+ /**
+ * 属性动画更新监听
+ */
+ private val updateListener = object : ValueAnimator.AnimatorUpdateListener {
+ override fun onAnimationUpdate(animation: ValueAnimator?) {
+ animation ?: return
+ (animation.animatedValue as? Int)?.run {
+ windowWrapper?.updateWindowParamsPos(xPos = this)
+ }
+ }
+ }
+
+ /**
+ * 动画状态监听
+ */
+ private val animatorListener = object : Animator.AnimatorListener {
+ override fun onAnimationStart(animation: Animator?) {
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ ALog.d(logTag, "onAnimationEnd")
+ finish()
+ }
+
+ override fun onAnimationCancel(animation: Animator?) {
+ ALog.d(logTag, "onAnimationCancel")
+ finish()
+ }
+
+ override fun onAnimationRepeat(animation: Animator?) {
+ }
+ }
+
+ private val animator = ValueAnimator.ofInt(0, 1).setDuration(100)
+ .apply {
+ interpolator = AccelerateDecelerateInterpolator()
+ }
+
+ /**
+ * 动画开始
+ */
+ fun start(
+ startPos: Int,
+ endPos: Int,
+ finishedBg: Drawable,
+ windowWrapper: FloatingWindowWrapper
+ ) {
+ if (isRunning()) {
+ ALog.d(logTag, "running")
+ return
+ }
+ if (endPos == windowWrapper.fetchWindowParamsX()) {
+ windowWrapper.setBgRes(finishedBg)
+ return
+ }
+ ALog.d(logTag, "start $startPos, $endPos")
+
+ this.endXPos = endPos
+ this.finishedBg = finishedBg
+ this.windowWrapper = windowWrapper
+
+ animator.run {
+ addListener(animatorListener)
+ addUpdateListener(updateListener)
+ setIntValues(startPos, endPos)
+ start()
+ }
+ }
+
+ fun isRunning() = animator.isRunning
+
+ fun cancel() {
+ this.windowWrapper = null
+ this.finishedBg = null
+ this.endXPos = null
+ animator.removeAllUpdateListeners()
+ animator.removeAllListeners()
+
+ if (isRunning()) {
+ animator.cancel()
+ }
+ }
+
+ private fun finish() {
+ animator.removeAllUpdateListeners()
+ animator.removeAllListeners()
+ windowWrapper?.also { wrapper ->
+ finishedBg?.also { bg ->
+ wrapper.setBgRes(bg)
+ }
+ endXPos?.run {
+ wrapper.updateWindowParamsPos(this)
+ }
+ }
+ }
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingPermission.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingPermission.kt
new file mode 100644
index 0000000..8d5b564
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingPermission.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.floating
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import com.netease.yunxin.kit.alog.ALog
+
+/**
+ * 浮窗权限请求,以及检测
+ */
+object FloatingPermission {
+ private const val TAG = "FloatPermission"
+
+ fun isFloatPermissionValid(context: Context): Boolean {
+ // Android 6.0 以下无需申请权限
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // 判断是否拥有悬浮窗权限,无则跳转悬浮窗权限授权页面
+ Settings.canDrawOverlays(context)
+ } else {
+ true
+ }
+ }
+
+ /**
+ * 请求浮窗权限
+ */
+ fun requireFloatPermission(context: Context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return
+ }
+ try {
+ val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
+ intent.data = Uri.parse("package:" + context.packageName)
+ if (context !is Activity) {
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ context.startActivity(intent)
+ } catch (e: Exception) {
+ ALog.e(TAG, "requestFloatWindowPermission Exception", e)
+ }
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingTouchEventStrategy.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingTouchEventStrategy.kt
new file mode 100644
index 0000000..3f56d6b
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingTouchEventStrategy.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.floating
+
+import android.view.MotionEvent
+
+/**
+ * 浮窗手势策略
+ * 通过代理将相关手势行为放在此类中实现
+ */
+interface FloatingTouchEventStrategy {
+ /**
+ * 初始化
+ */
+ fun initForWrapper(wrapper: FloatingWindowWrapper)
+
+ /**
+ * 处理 touch 事件
+ */
+ fun handScrollEvent(event: MotionEvent, windowWrapper: FloatingWindowWrapper)
+
+ /**
+ * 更新浮窗UI
+ */
+ fun toUpdateViewUI(wrapper: FloatingWindowWrapper)
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingWindowWrapper.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingWindowWrapper.kt
new file mode 100644
index 0000000..46819b8
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/floating/FloatingWindowWrapper.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.floating
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.Build
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.annotation.Px
+import androidx.core.view.setPadding
+import com.netease.yunxin.kit.alog.ALog
+import kotlin.math.abs
+
+/**
+ * 提供窗口浮窗能力,用户将子 view 添加到此容器内实现浮动功能
+ * 浮窗的手势也是作用在此类上,具体实现委托[FloatingTouchEventStrategy]
+ */
+@SuppressLint("ViewConstructor")
+class FloatingWindowWrapper(context: Context, val config: Config) : FrameLayout(context) {
+ private val logTag = "FloatingWindowWrapper"
+
+ private val wm: WindowManager =
+ (context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
+ ?: throw IllegalArgumentException()
+
+ /**
+ * 触摸识别距离
+ */
+ private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
+
+ private var currentDownX: Int = 0
+ private var currentDownY: Int = 0
+
+ /**
+ * [FloatingWindowWrapper] 触摸事件分发处理
+ */
+ private val onTouchListener = object : OnTouchListener {
+
+ private var handled = false
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ event ?: return false
+ ALog.i(
+ logTag,
+ "x is ${event.x} , y is ${event.y} , rawX is ${event.rawX}, rawY is ${event.rawY}. detail is $event"
+ )
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ handled = false
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ handled = canHandle(event)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ if (!handled && abs(currentDownX - event.rawX) <= touchSlop && abs(
+ currentDownY - event.rawY
+ ) <= touchSlop
+ ) {
+ config.onClickListener?.onClick(this@FloatingWindowWrapper)
+ }
+ }
+ }
+ config.touchEventStrategy?.handScrollEvent(event, this@FloatingWindowWrapper)
+ return false
+ }
+ }
+
+ init {
+ setOnTouchListener(onTouchListener)
+ }
+
+ /**
+ * 拦截子 view 触摸事件
+ */
+ override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
+ event ?: return false
+ ALog.i(
+ logTag,
+ "onInterceptTouchEvent x is ${event.x} , y is ${event.y} , rawX is ${event.rawX}, rawY is ${event.rawY}. detail is $event"
+ )
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ currentDownX = event.rawX.toInt()
+ currentDownY = event.rawY.toInt()
+ config.touchEventStrategy?.handScrollEvent(event, this@FloatingWindowWrapper)
+ }
+ return canHandle(event)
+ }
+
+ private fun canHandle(event: MotionEvent): Boolean {
+ return abs(currentDownX - event.rawX) > touchSlop || abs(currentDownY - event.rawY) > touchSlop
+ }
+
+ // -------------------------对外接口---------------------------------
+
+ /**
+ * 添加子 view 到浮窗展示
+ */
+ fun showView(view: View) {
+ if (view.parent != null) {
+ throw IllegalArgumentException(
+ "The view had been added other ViewGroup can't be shown."
+ )
+ }
+ addView(view)
+ try {
+ config.touchEventStrategy?.initForWrapper(this)
+ wm.addView(this, config.windowParams)
+ } catch (exception: Exception) {
+ ALog.w(logTag, "showView", exception)
+ }
+ }
+
+ /**
+ * 页面更新内容及UI时让点击事件感知
+ */
+ fun toUpdateViewContent() {
+ config.touchEventStrategy?.toUpdateViewUI(this)
+ }
+
+ /**
+ * 隐藏浮窗
+ */
+ fun dismissView() {
+ removeAllViews()
+ try {
+ background = null
+ wm.removeView(this)
+ } catch (exception: Exception) {
+ ALog.w(logTag, "dismiss", exception)
+ }
+ }
+
+ fun fetchWindowParamsX() = config.windowParams.x
+
+ fun fetchWindowParamsY() = config.windowParams.y
+
+ @JvmOverloads
+ fun updateWindowParamsPos(
+ xPos: Int = config.windowParams.x,
+ yPos: Int = config.windowParams.y
+ ) {
+ if (!isAttachedToWindow) {
+ return
+ }
+ ALog.i(logTag, "xPos is $xPos, yPos is $yPos")
+ config.windowParams.x = xPos
+ config.windowParams.y = yPos
+ wm.updateViewLayout(this, config.windowParams)
+ }
+
+ // ------------------------相关辅助类--------------------------------
+ /**
+ * 浮窗配置
+ */
+
+ class Config(
+ /**
+ * 触摸事件处理策略
+ */
+ val touchEventStrategy: FloatingTouchEventStrategy?,
+ /**
+ * window 参数配置
+ */
+ val windowParams: WindowManager.LayoutParams,
+ /**
+ * view 点击事件监听
+ */
+ val onClickListener: OnClickListener?
+ )
+
+ /**
+ * 构建器,将多种配置参数收集
+ */
+ class Builder {
+ private var windowParams: WindowManager.LayoutParams? = null
+ private var windowType: Int? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+ } else {
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
+ }
+ private var windowFlags: Int? = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ private var windowFormat: Int? = PixelFormat.TRANSLUCENT
+ private var windowGravity: Int? = Gravity.END or Gravity.TOP
+ private var windowWidth: Int? = WindowManager.LayoutParams.WRAP_CONTENT
+ private var windowHeight: Int? = WindowManager.LayoutParams.WRAP_CONTENT
+ private var windowXPos: Int? = null
+ private var windowYPos: Int? = null
+ private var windowAnimations: Int? = null
+
+ private var touchEventStrategy: FloatingTouchEventStrategy? = null
+ private var onClickListener: OnClickListener? = null
+
+ private var padding: Int? = null
+
+ fun windowLayoutParams(params: WindowManager.LayoutParams) = apply {
+ this.windowParams = params
+ }
+
+ fun windowType(type: Int) = apply {
+ this.windowType = type
+ }
+
+ fun windowFlags(flags: Int) = apply {
+ this.windowFlags = flags
+ }
+
+ fun windowFormat(format: Int) = apply {
+ this.windowFormat = format
+ }
+
+ fun windowGravity(gravity: Int) = apply {
+ this.windowGravity = gravity
+ }
+
+ fun windowWidth(width: Int) = apply {
+ this.windowWidth = width
+ }
+
+ fun windowHeight(height: Int) = apply {
+ this.windowHeight = height
+ }
+
+ fun windowXPos(x: Int) = apply {
+ this.windowXPos = x
+ }
+
+ fun windowYPos(y: Int) = apply {
+ this.windowYPos = y
+ }
+
+ fun windowAnimations(animations: Int) = apply {
+ this.windowAnimations = animations
+ }
+
+ fun touchEventStrategy(touchEventStrategy: FloatingTouchEventStrategy) = apply {
+ this.touchEventStrategy = touchEventStrategy
+ }
+
+ fun onClickListener(onClickListener: OnClickListener) = apply {
+ this.onClickListener = onClickListener
+ }
+
+ fun padding(@Px paddingSize: Int) = apply {
+ this.padding = paddingSize
+ }
+
+ fun build(context: Context): FloatingWindowWrapper {
+ return FloatingWindowWrapper(
+ context,
+ Config(
+ touchEventStrategy ?: DefaultFloatingTouchEventStrategy(context),
+ (windowParams ?: WindowManager.LayoutParams()).apply {
+ this@Builder.windowAnimations?.run {
+ windowAnimations = this
+ }
+ windowWidth?.run {
+ width = this
+ }
+ windowHeight?.run {
+ height = this
+ }
+ windowType?.run {
+ type = this
+ }
+ windowFlags?.run {
+ flags = this
+ }
+ windowFormat?.run {
+ format = this
+ }
+ windowGravity?.run {
+ gravity = this
+ }
+ windowXPos?.run {
+ x = this
+ }
+ windowYPos?.run {
+ y = this
+ }
+ },
+ onClickListener
+ )
+ ).apply {
+ padding?.run {
+ setPadding(this)
+ }
+ }
+ }
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIFloatingWindowMgr.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIFloatingWindowMgr.kt
new file mode 100644
index 0000000..331af99
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIFloatingWindowMgr.kt
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.p2p
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.view.View
+import android.view.WindowManager
+import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.kit.alog.ParameterMap
+import com.netease.yunxin.kit.call.p2p.NECallEngine
+import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
+import com.netease.yunxin.kit.call.p2p.model.NECallEngineDelegateAbs
+import com.netease.yunxin.kit.call.p2p.model.NECallType
+import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
+import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
+import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
+import com.netease.yunxin.nertc.ui.CallKitUI
+import com.netease.yunxin.nertc.ui.base.CallParam
+import com.netease.yunxin.nertc.ui.base.Constants
+import com.netease.yunxin.nertc.ui.base.launchTask
+import com.netease.yunxin.nertc.ui.floating.FloatingTouchEventStrategy
+import com.netease.yunxin.nertc.ui.floating.FloatingWindowWrapper
+import com.netease.yunxin.nertc.ui.utils.SwitchCallTypeConfirmDialog
+
+/**
+ * 浮窗整体逻辑:
+ */
+
+object CallUIFloatingWindowMgr {
+
+ private const val TAG = "CallUIFloatingWindowMgr"
+ private const val REQUEST_CODE_FLOAT = 21301
+
+ private var floatContentView: IFloatingView? = null
+ private var floatingWindowWrapper: FloatingWindowWrapper? = null
+ private var dialog: SwitchCallTypeConfirmDialog? = null
+ private var dialogFlag: Any? = null
+
+ /**
+ * 用于用户展示自己的切换弹窗样式
+ */
+ private var switchCallTypeConfirmDialogProvider: ((Activity) -> SwitchCallTypeConfirmDialog?)? = null
+
+ /**
+ * 配置通话结束及通话类型变化监听
+ */
+ private val delegate = object : NECallEngineDelegateAbs() {
+ override fun onCallTypeChange(info: NECallTypeChangeInfo?) {
+ super.onCallTypeChange(info)
+ info ?: return
+
+ if (info.state == SwitchCallState.INVITE && dialog?.isShowing != true) {
+ showSwitchCallTypeConfirmDialog(info)
+ return
+ }
+
+ if (info.state != SwitchCallState.ACCEPT) {
+ return
+ }
+ if (info.callType == NECallType.VIDEO) {
+ // 视频
+ CallUIOperationsMgr.doMuteVideo(false)
+ // 扬声器
+ CallUIOperationsMgr.doConfigSpeaker(true)
+ // 音频
+ CallUIOperationsMgr.doMuteAudio(false)
+ // 背景虚化
+ CallUIOperationsMgr.doVirtualBlur(false)
+ } else {
+ // 视频
+ CallUIOperationsMgr.doMuteVideo(true)
+ // 扬声器
+ CallUIOperationsMgr.doConfigSpeaker(false)
+ // 音频
+ CallUIOperationsMgr.doMuteAudio(false)
+ // 背景虚化
+ CallUIOperationsMgr.doVirtualBlur(false)
+ }
+ // 初始化状态,不同通话类型
+ if (info.callType == NECallType.AUDIO) {
+ floatContentView?.transToAudioUI()
+ } else if (info.callType == NECallType.VIDEO) {
+ floatContentView?.transToVideoUI()
+ }
+ // 用于浮窗在左侧时变化ui时浮窗没有吸附
+ floatingWindowWrapper?.toUpdateViewContent()
+ }
+
+ override fun onCallEnd(info: NECallEndInfo?) {
+ super.onCallEnd(info)
+ innerRelease(true)
+ }
+ }
+
+// -----------------------------------------------------
+
+ /**
+ * 展示悬浮窗
+ *
+ * @param context 上下文
+ */
+ @JvmOverloads
+ fun showFloat(
+ context: Context,
+ uiConfig: P2PUIConfig? = null,
+ floatingView: IFloatingView? = null,
+ windowLayoutParams: WindowManager.LayoutParams? = null,
+ touchEventStrategy: FloatingTouchEventStrategy? = null
+
+ ) {
+ ALog.dApi(
+ TAG,
+ ParameterMap("showFloat").append("context", context).append("uiConfig", uiConfig)
+ )
+ NECallEngine.sharedInstance().addCallDelegate(delegate)
+ if (floatingView != null && floatingView !is View) {
+ throw IllegalArgumentException("floatingView$floatingView must be a view instance.")
+ }
+ floatContentView = floatingView ?: FloatingView(context)
+ val builder = FloatingWindowWrapper.Builder()
+ .windowYPos(400)
+ windowLayoutParams?.run {
+ builder.windowLayoutParams(this)
+ }
+ touchEventStrategy?.run {
+ builder.touchEventStrategy(this)
+ }
+ floatingWindowWrapper = builder.build(context)
+ .apply {
+ showView(floatContentView as View)
+ }
+ floatContentView?.run {
+ toInit()
+ when (CallUIOperationsMgr.callInfoWithUIState.callParam.callType) {
+ NECallType.AUDIO -> {
+ transToAudioUI()
+ }
+
+ NECallType.VIDEO -> {
+ transToVideoUI()
+ }
+
+ else -> {
+ ALog.e(TAG, "unknown call type")
+ }
+ }
+ }
+ // 避免在全屏/小窗切换时丢失音视频切换弹窗
+ val info = CallUIOperationsMgr.currentSwitchTypeCallInfo() ?: return
+ if (dialog?.isShowing == true || info.state != SwitchCallState.INVITE) {
+ return
+ }
+ showSwitchCallTypeConfirmDialog(info)
+ }
+
+ fun registerFullScreenActionForView(context: Context, view: View) {
+ view.setOnClickListener {
+ CallUIOperationsMgr.callInfoWithUIState.run {
+ if (callState != CallState.STATE_IDLE) {
+ val callType = CallUIOperationsMgr.callInfoWithUIState.callParam.callType
+ val clazz =
+ when (CallUIOperationsMgr.callInfoWithUIState.callParam.callType) {
+ NECallType.AUDIO -> CallKitUI.options?.activityConfig?.p2pAudioActivity
+ NECallType.VIDEO -> CallKitUI.options?.activityConfig?.p2pVideoActivity
+ else -> null
+ }
+ if (clazz != null) {
+ innerRelease(false)
+ launch(context, clazz, callParam)
+ } else {
+ innerRelease(true)
+ ALog.e(
+ TAG,
+ "launch activity failed, clazz is null, callType is $callType."
+ )
+ }
+ } else {
+ innerRelease(true)
+ }
+ }
+ }
+ }
+
+ /**
+ * 配置音视频通话切换确认弹窗
+ */
+ fun configSwitchCallTypeConfirmDialogProvider(provider: (Activity) -> SwitchCallTypeConfirmDialog?) {
+ this.switchCallTypeConfirmDialogProvider = provider
+ }
+
+ /**
+ * 销毁悬浮窗
+ */
+ fun releaseFloat(isFinished: Boolean = true) {
+ ALog.dApi(TAG, ParameterMap("releaseFloat"))
+ innerRelease(isFinished)
+ }
+
+ /**
+ * 是否正在展示浮窗
+ */
+ fun isFloating(): Boolean {
+ return floatingWindowWrapper != null
+ }
+
+ /**
+ * 展示音视频切换确认弹窗
+ */
+ private fun showSwitchCallTypeConfirmDialog(info: NECallTypeChangeInfo) {
+ if (dialogFlag != null) {
+ return
+ }
+ dialogFlag = Any()
+ floatingWindowWrapper?.context?.run {
+ launchTask(this, REQUEST_CODE_FLOAT, { activity, _ ->
+ ALog.d(TAG, "showSwitchCallTypeConfirmDialog")
+ dialog =
+ switchCallTypeConfirmDialogProvider?.invoke(activity) ?: SwitchCallTypeConfirmDialog(
+ activity,
+ {
+ CallUIOperationsMgr.doSwitchCallType(
+ info.callType,
+ SwitchCallState.ACCEPT,
+ null
+ )
+ },
+ {
+ CallUIOperationsMgr.doSwitchCallType(
+ info.callType,
+ SwitchCallState.REJECT,
+ null
+ )
+ }
+ ).apply {
+ setOnDismissListener {
+ activity.finish()
+ }
+ show(info.callType)
+ val latestInfo = CallUIOperationsMgr.currentSwitchTypeCallInfo()
+ if (latestInfo?.state != SwitchCallState.INVITE) {
+ dismiss()
+ }
+ }
+ dialogFlag = null
+ }, {})
+ }
+ }
+
+ private fun innerRelease(isFinished: Boolean) {
+ ALog.d(TAG, ParameterMap("innerRelease").toValue())
+ NECallEngine.sharedInstance().removeCallDelegate(delegate)
+ CallUIOperationsMgr.configTimeTick(null)
+ floatContentView?.toDestroy(isFinished)
+ floatingWindowWrapper?.dismissView()
+ floatingWindowWrapper = null
+ dialog?.dismiss()
+ dialog = null
+ dialogFlag = null
+ }
+
+ /**
+ * 启动对应页面
+ */
+ private fun launch(
+ context: Context,
+ clazz: Class,
+ callParam: CallParam
+ ) {
+ ALog.dApi(
+ TAG,
+ ParameterMap("launch")
+ .append("context", context)
+ .append("callParam", callParam)
+ )
+ val intent = Intent(context, clazz).apply {
+ putExtra(Constants.PARAM_KEY_CALL, callParam)
+ if (context !is Activity) {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ putExtra(Constants.PARAM_KEY_FLAG_IS_FROM_FLOATING_WINDOW, true)
+ }
+ context.startActivity(intent)
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIOperationsMgr.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIOperationsMgr.kt
index ac20207..4247280 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIOperationsMgr.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/CallUIOperationsMgr.kt
@@ -9,15 +9,20 @@ package com.netease.yunxin.nertc.ui.p2p
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context
+import android.content.Intent
import android.media.AudioManager
+import android.os.Build
import com.netease.lava.api.IVideoRender
import com.netease.lava.nertc.sdk.NERtcConstants
import com.netease.lava.nertc.sdk.NERtcEx
import com.netease.lava.nertc.sdk.video.NERtcVideoView
+import com.netease.lava.nertc.sdk.video.NERtcVirtualBackgroundSource
import com.netease.yunxin.kit.alog.ALog
import com.netease.yunxin.kit.alog.ParameterMap
import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.NECallEngine
+import com.netease.yunxin.kit.call.p2p.extra.NECallLocalActionMgr
+import com.netease.yunxin.kit.call.p2p.extra.NECallLocalActionObserver
import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
import com.netease.yunxin.kit.call.p2p.model.NECallEngineDelegateAbs
import com.netease.yunxin.kit.call.p2p.model.NECallInfo
@@ -26,14 +31,17 @@ import com.netease.yunxin.kit.call.p2p.param.NECallParam
import com.netease.yunxin.kit.call.p2p.param.NEHangupParam
import com.netease.yunxin.kit.call.p2p.param.NESwitchParam
import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
+import com.netease.yunxin.nertc.nertcvideocall.model.CallLocalAction
import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackExTemp
import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackProxyMgr
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
+import com.netease.yunxin.nertc.ui.CallKitNotificationConfig
import com.netease.yunxin.nertc.ui.CallKitUI
import com.netease.yunxin.nertc.ui.base.CallParam
import com.netease.yunxin.nertc.ui.base.channelId
import com.netease.yunxin.nertc.ui.base.getChannelId
+import com.netease.yunxin.nertc.ui.service.CallForegroundService
import com.netease.yunxin.nertc.ui.service.DefaultIncomingCallEx.Companion.INCOMING_CALL_NOTIFY_ID
import com.netease.yunxin.nertc.ui.utils.SecondsTimer
@@ -47,11 +55,15 @@ object CallUIOperationsMgr {
var callInfoWithUIState: CallInfoWithUIState = CallInfoWithUIState()
private set
+ var timer: SecondsTimer? = null
+ private set
+
private val callEngineDelegate = object : NECallEngineDelegateAbs() {
override fun onCallTypeChange(info: NECallTypeChangeInfo) {
if (info.state == SwitchCallState.ACCEPT) {
callInfoWithUIState.callParam.callType = info.callType
}
+ toSwitchCallTypeInfo = info
}
override fun onVideoAvailable(userId: String?, available: Boolean) {
@@ -91,9 +103,21 @@ object CallUIOperationsMgr {
this@CallUIOperationsMgr.timer?.cancel()
this@CallUIOperationsMgr.timer = null
this@CallUIOperationsMgr.timeTickConfig = null
+ this@CallUIOperationsMgr.foregroundServiceConfig?.stopService(true)
+ this@CallUIOperationsMgr.doVirtualBlurInner(false)
}
}
+ private val localActionObserver =
+ NECallLocalActionObserver { actionId, _, extraInfo ->
+ if (actionId == CallLocalAction.ACTION_SWITCH &&
+ extraInfo is NECallTypeChangeInfo &&
+ extraInfo.state != SwitchCallState.INVITE
+ ) {
+ toSwitchCallTypeInfo = extraInfo
+ }
+ }
+
private val neRtcCallback = object : NERtcCallbackExTemp() {
override fun onVideoDeviceStageChange(deviceState: Int) {
if (currentCallState() == CallState.STATE_IDLE) {
@@ -103,27 +127,35 @@ object CallUIOperationsMgr {
}
}
- private var timeTickConfig: TimeTickConfig? = null
+ private lateinit var context: Context
- private var timer: SecondsTimer? = null
+ private var toSwitchCallTypeInfo: NECallTypeChangeInfo? = null
- private lateinit var context: Context
+ private var foregroundServiceConfig: CallForegroundServiceConfig? = null
+
+ private var timeTickConfig: TimeTickConfig? = null
+ private var startPreviewCode: Int? = null
/**
* 初始化呼叫信息及状态
*/
- fun initCallInfoAndUIState(callInfoWithUIState: CallInfoWithUIState): String? {
- callEngine.addCallDelegate(callEngineDelegate)
- NERtcCallbackProxyMgr.getInstance().addCallback(neRtcCallback)
+ @JvmOverloads
+ fun initCallInfoAndUIState(
+ callInfoWithUIState: CallInfoWithUIState,
+ foregroundServiceConfig: CallForegroundServiceConfig? = null
+ ): String? {
ALog.d(
TAG,
- ParameterMap("initCallInfoAndUIState").append(
- "callInfoWithUIState",
- callInfoWithUIState
- ).toValue()
+ ParameterMap("initCallInfoAndUIState")
+ .append("callInfoWithUIState", callInfoWithUIState)
+ .append("foregroundServiceConfig", foregroundServiceConfig)
+ .toValue()
)
this.callInfoWithUIState = callInfoWithUIState
+ this.foregroundServiceConfig = foregroundServiceConfig
+ this.toSwitchCallTypeInfo = null
+ this.foregroundServiceConfig?.startService()
return this.callInfoWithUIState.callParam.getChannelId()
}
@@ -143,8 +175,9 @@ object CallUIOperationsMgr {
)
return
}
- callEngine.removeCallDelegate(callEngineDelegate)
- NERtcCallbackProxyMgr.getInstance().removeCallback(neRtcCallback)
+ this.foregroundServiceConfig?.stopService(force)
+ this.foregroundServiceConfig = null
+ this.toSwitchCallTypeInfo = null
this.callInfoWithUIState = CallInfoWithUIState()
this.timer?.cancel()
this.timer = null
@@ -168,7 +201,9 @@ object CallUIOperationsMgr {
isLocalMuteVideo: Boolean? = null,
isLocalMuteAudio: Boolean? = null,
isLocalMuteSpeaker: Boolean? = null,
- cameraDeviceStatus: Int? = null
+ cameraDeviceStatus: Int? = null,
+ isLocalSmallVideo: Boolean? = null,
+ isVirtualBlur: Boolean? = null
) {
ALog.dApi(
TAG,
@@ -178,6 +213,8 @@ object CallUIOperationsMgr {
.append("isLocalMuteAudio", isLocalMuteAudio)
.append("isLocalMuteSpeaker", isLocalMuteSpeaker)
.append("cameraDeviceStatus", cameraDeviceStatus)
+ .append("isLocalSmallVideo", isLocalSmallVideo)
+ .append("isVirtualBlur", isVirtualBlur)
)
isRemoteMuteVideo?.run {
callInfoWithUIState.isRemoteMuteVideo = this
@@ -194,6 +231,12 @@ object CallUIOperationsMgr {
cameraDeviceStatus?.run {
callInfoWithUIState.cameraDeviceStatus = this
}
+ isLocalSmallVideo?.run {
+ callInfoWithUIState.isLocalSmallVideo = this
+ }
+ isVirtualBlur?.run {
+ callInfoWithUIState.isVirtualBlur = this
+ }
}
/**
@@ -221,7 +264,31 @@ object CallUIOperationsMgr {
) { result ->
ALog.d(TAG, "call result is $result.")
observer?.onResult(result)
- callParam.channelId(callEngine.callInfo.signalInfo.channelId)
+ callInfoWithUIState.callParam.channelId(callEngine.callInfo.signalInfo.channelId)
+ }
+ }
+ }
+
+ /**
+ * 发起呼叫
+ */
+ fun doCall(
+ callParam: NECallParam,
+ observer: NEResultObserver>?
+ ) {
+ ALog.dApi(
+ TAG,
+ ParameterMap("doCall")
+ .append("callParam", callParam)
+ .append("observer", observer)
+ )
+ with(callParam) {
+ callEngine.call(
+ this
+ ) { result ->
+ ALog.d(TAG, "call result is $result.")
+ observer?.onResult(result)
+ callInfoWithUIState.callParam.channelId(callEngine.callInfo.signalInfo.channelId)
}
}
}
@@ -298,7 +365,9 @@ object CallUIOperationsMgr {
ParameterMap("setupLocalView").append("view", view).append("action", action)
)
action?.invoke(view)
- callEngine.setupLocalView(view)
+ callEngine.setupLocalView(view).apply {
+ ALog.d(TAG, "setupLocalView result is $this.")
+ }
}
/**
@@ -316,7 +385,9 @@ object CallUIOperationsMgr {
ParameterMap("setupRemoteView").append("view", view).append("action", action)
)
action?.invoke(view)
- callEngine.setupRemoteView(view)
+ callEngine.setupRemoteView(view).apply {
+ ALog.d(TAG, "setupRemoteView result is $this.")
+ }
}
/**
@@ -325,8 +396,12 @@ object CallUIOperationsMgr {
@JvmOverloads
fun doConfigSpeaker(enableSpeaker: Boolean = !isSpeakerOn()) {
val action = {
- callInfoWithUIState.isLocalMuteSpeaker = !enableSpeaker
- NERtcEx.getInstance().isSpeakerphoneOn = enableSpeaker
+ NERtcEx.getInstance().setSpeakerphoneOn(enableSpeaker).apply {
+ ALog.d(TAG, "doConfigSpeaker result is $this.")
+ if (this == NERtcConstants.ErrorCode.OK) {
+ callInfoWithUIState.isLocalMuteSpeaker = !enableSpeaker
+ }
+ }
}
if (CallKitUI.options?.joinRtcWhenCall == true && !callInfoWithUIState.callParam.isCalled) {
action.invoke()
@@ -346,7 +421,13 @@ object CallUIOperationsMgr {
mode = AudioManager.MODE_NORMAL
isSpeakerphoneOn = true
} else {
- mode = AudioManager.MODE_IN_CALL
+ // 兼容高版本 sdk
+ @SuppressLint("ObsoleteSdkInt")
+ mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ AudioManager.MODE_IN_COMMUNICATION
+ } else {
+ AudioManager.MODE_IN_CALL
+ }
isSpeakerphoneOn = false
}
}
@@ -380,8 +461,12 @@ object CallUIOperationsMgr {
@JvmOverloads
fun doMuteAudio(mute: Boolean = !callInfoWithUIState.isLocalMuteAudio) {
ALog.dApi(TAG, ParameterMap("doMuteAudio").append("mute", mute))
- callInfoWithUIState.isLocalMuteAudio = mute
- callEngine.muteLocalAudio(callInfoWithUIState.isLocalMuteAudio)
+ callEngine.muteLocalAudio(mute).apply {
+ ALog.d(TAG, "doMuteAudio result is $this.")
+ if (this == NERtcConstants.ErrorCode.OK) {
+ callInfoWithUIState.isLocalMuteAudio = mute
+ }
+ }
}
/**
@@ -390,8 +475,12 @@ object CallUIOperationsMgr {
@JvmOverloads
fun doMuteVideo(mute: Boolean = !callInfoWithUIState.isLocalMuteVideo) {
ALog.dApi(TAG, ParameterMap("doMuteVideo").append("mute", mute))
- callInfoWithUIState.isLocalMuteVideo = mute
- callEngine.muteLocalVideo(callInfoWithUIState.isLocalMuteVideo)
+ callEngine.muteLocalVideo(mute).apply {
+ ALog.d(TAG, "doMuteVideo result is $this.")
+ if (this == NERtcConstants.ErrorCode.OK) {
+ callInfoWithUIState.isLocalMuteVideo = mute
+ }
+ }
}
/**
@@ -399,7 +488,70 @@ object CallUIOperationsMgr {
*/
fun doSwitchCamera() {
ALog.dApi(TAG, ParameterMap("doSwitchCamera"))
- callEngine.switchCamera()
+ callEngine.switchCamera().apply {
+ ALog.d(TAG, "doSwitchCamera result is $this.")
+ }
+ }
+
+ /**
+ * 开启/关闭背景虚化
+ */
+ fun doVirtualBlur(enable: Boolean = !callInfoWithUIState.isVirtualBlur) {
+ ALog.dApi(TAG, ParameterMap("doVirtualBlur").append("enable", enable))
+ doVirtualBlurInner(enable)
+ }
+
+ /**
+ * 内部开启/关闭背景虚化
+ */
+ private fun doVirtualBlurInner(enable: Boolean) {
+ ALog.d(TAG, ParameterMap("doVirtualBlurInner").append("enable", enable).toValue())
+ NERtcEx.getInstance().enableVirtualBackground(
+ enable,
+ NERtcVirtualBackgroundSource().apply {
+ backgroundSourceType = NERtcVirtualBackgroundSource.BACKGROUND_BLUR
+ blur_degree = NERtcVirtualBackgroundSource.BLUR_DEGREE_HIGH
+ }
+ ).apply {
+ ALog.d(TAG, "doVirtualBlurInner result is $this.")
+ if (this == NERtcConstants.ErrorCode.OK) {
+ callInfoWithUIState.isVirtualBlur = enable
+ }
+ }
+ }
+
+ /**
+ * 开启视频预览
+ */
+ fun startVideoPreview() {
+ ALog.dApi(
+ TAG,
+ ParameterMap("startVideoPreview").append("startPreviewCode", startPreviewCode)
+ )
+ if ((CallKitUI.options?.joinRtcWhenCall == false || callInfoWithUIState.callParam.isCalled) &&
+ startPreviewCode != NERtcConstants.ErrorCode.OK &&
+ startPreviewCode != NERtcConstants.ErrorCode.ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED
+ ) {
+ startPreviewCode = NERtcEx.getInstance().startVideoPreview().apply {
+ ALog.d(TAG, "startVideoPreview result is $this.")
+ }
+ }
+ }
+
+ /**
+ * 停止视频预览
+ */
+ fun stopVideoPreview() {
+ ALog.dApi(
+ TAG,
+ ParameterMap("stopVideoPreview").append("startPreviewCode", startPreviewCode)
+ )
+ if (startPreviewCode == NERtcConstants.ErrorCode.OK || startPreviewCode == NERtcConstants.ErrorCode.ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED) {
+ NERtcEx.getInstance().stopVideoPreview().apply {
+ ALog.d(TAG, "stopVideoPreview result is $this.")
+ }
+ startPreviewCode = null
+ }
}
/**
@@ -426,14 +578,25 @@ object CallUIOperationsMgr {
return callInfoWithUIState.callState
}
+ /**
+ * 获取当前音视频切换事件
+ */
+ fun currentSwitchTypeCallInfo(): NECallTypeChangeInfo? = toSwitchCallTypeInfo
+
internal fun load(context: Context) {
ALog.dApi(TAG, ParameterMap("load"))
- this.context = context.applicationContext
releaseCallInfoAndUIState(force = true)
+ this.context = context.applicationContext
+ callEngine.addCallDelegate(callEngineDelegate)
+ NECallLocalActionMgr.getInstance().addCallback(localActionObserver)
+ NERtcCallbackProxyMgr.getInstance().addCallback(neRtcCallback)
}
internal fun unload() {
ALog.dApi(TAG, ParameterMap("unload"))
+ callEngine.removeCallDelegate(callEngineDelegate)
+ NECallLocalActionMgr.getInstance().removeCallback(localActionObserver)
+ NERtcCallbackProxyMgr.getInstance().removeCallback(neRtcCallback)
releaseCallInfoAndUIState(force = true)
}
@@ -450,13 +613,15 @@ object CallUIOperationsMgr {
?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
)?.isSpeakerphoneOn
?: !NERtcEx.getInstance().isSpeakerphoneOn,
- var cameraDeviceStatus: Int = NERtcConstants.VideoDeviceState.OPENED
+ var cameraDeviceStatus: Int = NERtcConstants.VideoDeviceState.OPENED,
+ var isLocalSmallVideo: Boolean = true,
+ var isVirtualBlur: Boolean = false
) {
val callState: Int
get() = callEngine.callInfo.callStatus
override fun toString(): String {
- return "CallInfoWithUIState(callParam=$callParam, isRemoteMuteVideo=$isRemoteMuteVideo, isLocalMuteVideo=$isLocalMuteVideo, isLocalMuteAudio=$isLocalMuteAudio, isLocalMuteSpeaker=$isLocalMuteSpeaker, cameraDeviceStatus=$cameraDeviceStatus)"
+ return "CallInfoWithUIState(callParam=$callParam, isRemoteMuteVideo=$isRemoteMuteVideo, isLocalMuteVideo=$isLocalMuteVideo, isLocalMuteAudio=$isLocalMuteAudio, isLocalMuteSpeaker=$isLocalMuteSpeaker, cameraDeviceStatus=$cameraDeviceStatus, isLocalSmallVideo=$isLocalSmallVideo, isVirtualBlur=$isVirtualBlur)"
}
}
@@ -469,4 +634,37 @@ object CallUIOperationsMgr {
return "TimeTickConfig(onTimeTick=$onTimeTick, period=$period, delay=$delay)"
}
}
+
+ class CallForegroundServiceConfig(
+ context: Context,
+ private val intent: Intent,
+ private val notificationConfig: CallKitNotificationConfig? = null
+ ) {
+
+ private var serviceId: String? = null
+ private var context = context.applicationContext
+
+ fun startService() {
+ serviceId = CallForegroundService.launchForegroundService(
+ context,
+ intent,
+ notificationConfig
+ )
+ }
+
+ fun stopService(force: Boolean = false) {
+ if (force) {
+ CallForegroundService.stopService(context)
+ } else {
+ serviceId?.run {
+ CallForegroundService.stopService(context, this)
+ }
+ }
+ serviceId = null
+ }
+
+ override fun toString(): String {
+ return "CallForegroundServiceConfig(context=$context, intent=$intent, notificationConfig=$notificationConfig)"
+ }
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/FloatingView.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/FloatingView.kt
new file mode 100644
index 0000000..c65a1eb
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/FloatingView.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.p2p
+
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.RECORD_AUDIO
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.Toast
+import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.kit.alog.ParameterMap
+import com.netease.yunxin.kit.call.p2p.NECallEngine
+import com.netease.yunxin.kit.call.p2p.model.NECallEngineDelegateAbs
+import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
+import com.netease.yunxin.nertc.ui.R
+import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
+import com.netease.yunxin.nertc.ui.databinding.ViewFloatingWindowBinding
+import com.netease.yunxin.nertc.ui.utils.dip2Px
+import com.netease.yunxin.nertc.ui.utils.formatSecondTime
+import com.netease.yunxin.nertc.ui.utils.isGranted
+import com.netease.yunxin.nertc.ui.utils.requestPermission
+
+class FloatingView(context: Context) : FrameLayout(context), IFloatingView {
+ private val logTag = "FloatingView"
+ private val binding by lazy {
+ ViewFloatingWindowBinding.inflate(LayoutInflater.from(context), this, true)
+ }
+ private val delegate = object : NECallEngineDelegateAbs() {
+ override fun onVideoAvailable(userId: String?, available: Boolean) {
+ if (available) {
+ binding.videoViewSmall.visibility = View.VISIBLE
+ CallUIOperationsMgr.setupRemoteView(binding.videoViewSmall)
+ } else {
+ binding.videoViewSmall.visibility = View.GONE
+ }
+ }
+
+ override fun onVideoMuted(userId: String?, mute: Boolean) {
+ if (!mute) {
+ binding.videoViewSmall.visibility = View.VISIBLE
+ CallUIOperationsMgr.setupRemoteView(binding.videoViewSmall)
+ } else {
+ binding.videoViewSmall.visibility = View.GONE
+ }
+ }
+ }
+
+ override fun toInit() {
+ NECallEngine.sharedInstance().addCallDelegate(delegate)
+ CallUIOperationsMgr.configTimeTick(
+ CallUIOperationsMgr.TimeTickConfig(onTimeTick = { timestamp: Long ->
+ postCountDownTxt(timestamp.formatSecondTime())
+ })
+ )
+ CallUIFloatingWindowMgr.registerFullScreenActionForView(context, this)
+ }
+
+ override fun transToAudioUI() {
+ ALog.dApi(logTag, ParameterMap("transToAudioUI"))
+ val action = { _: Any? ->
+ binding.floatAudioGroup.visibility = View.VISIBLE
+ binding.videoViewSmall.visibility = View.GONE
+ binding.ivAvatar.visibility = View.GONE
+ binding.videoBg.visibility = View.GONE
+ if (CallUIOperationsMgr.currentCallState() == CallState.STATE_CALL_OUT) {
+ binding.tvAudioTip.setText(R.string.ui_floating_window_audio_call)
+ } else {
+ binding.tvAudioTip.text = "--:--"
+ }
+ binding.root.radius = 6.dip2Px(context).toFloat()
+ }
+ if (context.isGranted(RECORD_AUDIO)) {
+ action.invoke(null)
+ } else {
+ handPermissionRequest(listOf(RECORD_AUDIO), null, action)
+ }
+ }
+
+ override fun transToVideoUI() {
+ ALog.dApi(logTag, ParameterMap("transToVideoUI"))
+ val action = { needToEnableLocalVideo: Boolean ->
+ if (needToEnableLocalVideo) {
+ NECallEngine.sharedInstance().enableLocalVideo(true)
+ }
+ binding.floatAudioGroup.visibility = View.GONE
+ binding.ivAvatar.visibility = View.VISIBLE
+ binding.videoBg.visibility = View.VISIBLE
+ if (CallUIOperationsMgr.currentCallState() == CallState.STATE_DIALOG) {
+ val isVideoing = !CallUIOperationsMgr.callInfoWithUIState.isRemoteMuteVideo
+ if (isVideoing) {
+ binding.videoViewSmall.visibility = View.VISIBLE
+ CallUIOperationsMgr.setupRemoteView(binding.videoViewSmall)
+ } else {
+ binding.videoViewSmall.visibility = View.GONE
+ }
+ } else {
+ binding.videoViewSmall.visibility = View.VISIBLE
+ CallUIOperationsMgr.setupLocalView(binding.videoViewSmall)
+ CallUIOperationsMgr.startVideoPreview()
+ }
+ CallUIOperationsMgr.callInfoWithUIState.callParam.otherAccId
+ ?.loadAvatarByAccId(context, binding.ivAvatar, enableTextDefaultAvatar = false)
+ binding.root.radius = 0f
+ }
+ if (context.isGranted(RECORD_AUDIO, CAMERA)) {
+ action.invoke(false)
+ } else {
+ handPermissionRequest(listOf(RECORD_AUDIO, CAMERA), true, action)
+ }
+ }
+
+ override fun toDestroy(isFinished: Boolean) {
+ CallUIOperationsMgr.configTimeTick(null)
+ if (isFinished) {
+ CallUIOperationsMgr.stopVideoPreview()
+ }
+ NECallEngine.sharedInstance().removeCallDelegate(delegate)
+ }
+
+ /**
+ * 处理权限申请逻辑
+ */
+ private fun handPermissionRequest(
+ permissionList: List,
+ param: T,
+ action: (T) -> Unit
+ ) {
+ context.requestPermission(
+ {
+ action.invoke(param)
+ },
+ { _, _ ->
+ Toast.makeText(
+ context,
+ R.string.ui_dialog_permission_content,
+ Toast.LENGTH_LONG
+ ).show()
+ CallUIOperationsMgr.doHangup(null)
+ },
+ permissions = permissionList.toTypedArray()
+ )
+ }
+
+ private fun postCountDownTxt(countDown: String) {
+ post {
+ if (binding.floatAudioGroup.visibility == View.VISIBLE) {
+ binding.tvAudioTip.text = countDown
+ }
+ }
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/IFloatingView.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/IFloatingView.kt
new file mode 100644
index 0000000..f1404bb
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/IFloatingView.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.p2p
+
+interface IFloatingView {
+
+ /**
+ * 初始化
+ */
+ fun toInit()
+
+ /**
+ * 展示音频UI
+ */
+ fun transToAudioUI()
+
+ /**
+ * 展示视频UI
+ */
+ fun transToVideoUI()
+
+ /**
+ * 销毁
+ */
+ fun toDestroy(isFinished: Boolean)
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallActivity.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallActivity.kt
deleted file mode 100644
index 68c5c43..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallActivity.kt
+++ /dev/null
@@ -1,964 +0,0 @@
-/*
- * Copyright (c) 2022 NetEase, Inc. All rights reserved.
- * Use of this source code is governed by a MIT license that can be
- * found in the LICENSE file.
- */
-
-package com.netease.yunxin.nertc.ui.p2p
-
-import android.Manifest
-import android.content.DialogInterface
-import android.graphics.Color
-import android.os.Bundle
-import android.text.TextUtils
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import androidx.appcompat.app.AlertDialog
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.core.content.ContextCompat
-import com.bumptech.glide.Glide
-import com.netease.lava.nertc.sdk.NERtcConstants
-import com.netease.lava.nertc.sdk.NERtcConstants.ErrorCode.ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED
-import com.netease.lava.nertc.sdk.NERtcEx
-import com.netease.nimlib.sdk.ResponseCode
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
-import com.netease.yunxin.kit.call.p2p.internal.NECallEngineImpl
-import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
-import com.netease.yunxin.kit.call.p2p.model.NECallInfo
-import com.netease.yunxin.kit.call.p2p.model.NECallInitRtcMode
-import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.kit.call.p2p.model.NEHangupReasonCode
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
-import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
-import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
-import com.netease.yunxin.nertc.ui.CallKitUI
-import com.netease.yunxin.nertc.ui.R
-import com.netease.yunxin.nertc.ui.base.CommonCallActivity
-import com.netease.yunxin.nertc.ui.base.currentUserIsCaller
-import com.netease.yunxin.nertc.ui.base.fetchNickname
-import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
-import com.netease.yunxin.nertc.ui.databinding.ActivityP2PcallBinding
-import com.netease.yunxin.nertc.ui.utils.PermissionTipDialog
-import com.netease.yunxin.nertc.ui.utils.SwitchCallTypeConfirmDialog
-import com.netease.yunxin.nertc.ui.utils.dip2Px
-import com.netease.yunxin.nertc.ui.utils.formatSecondTime
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
-import com.netease.yunxin.nertc.ui.utils.toastShort
-
-open class P2PCallActivity : CommonCallActivity() {
- private val tag = "P2PCallActivity"
-
- private val binding: ActivityP2PcallBinding by lazy {
- ActivityP2PcallBinding.inflate(layoutInflater)
- }
- private var startPreviewCode = -1
-
- private var callFinished = true
-
- private var localIsSmallVideo = true
-
- private val switchConfirmDialog by lazy {
- SwitchCallTypeConfirmDialog(this, {
- doSwitchCallType(it, SwitchCallState.ACCEPT)
- }, {
- doSwitchCallType(it, SwitchCallState.REJECT)
- })
- }
-
- private val onClickListener = View.OnClickListener { v ->
- when (v) {
- binding.ivAccept -> {
- v.isEnabled = false
- binding.ivSwitchType.isEnabled = false
- binding.ivCallSwitchType.isEnabled = false
- doAccept()
- }
-
- binding.ivReject -> {
- binding.ivAccept.isEnabled = false
- v.isEnabled = false
- doHangup()
- }
-
- binding.ivCancel -> {
- if (!callFinished) {
- getString(R.string.tip_invite_was_sending).toastShort(this)
- return@OnClickListener
- }
- v.isEnabled = false
- doHangup()
- }
-
- binding.ivHangUp -> {
- v.isEnabled = false
- doHangup()
- }
-
- binding.ivCallMuteAudio,
- binding.ivMuteAudio -> doMuteAudioSwitch(v as ImageView)
-
- binding.ivMuteVideo -> doMuteVideo(binding.ivMuteVideo)
- binding.ivSwitchCamera -> doSwitchCamera()
- binding.ivCallSwitchType,
- binding.ivSwitchType,
- binding.ivCallChannelTypeChange -> doSwitchCallType()
-
- binding.ivCallSpeaker,
- binding.ivMuteSpeaker -> doConfigSpeakerSwitch(v as ImageView)
-
- binding.videoViewSmall -> doSwitchCanvas()
- else -> ALog.d(tag, "can't response this clicked Event for $v")
- }
- }
-
- private val uiRender: UIRender
- get() = if (callParam.callType == NECallType.AUDIO) {
- AudioRender()
- } else {
- VideoRender()
- }
-
- override fun onCallConnected(info: NECallInfo) {
- if (isFinishing) {
- return
- }
- info.otherUserInfo()?.accId.run {
- initForOnTheCall(this)
- }
-
- configTimeTick(
- CallUIOperationsMgr.TimeTickConfig({
- runOnUiThread { binding.tvCountdown.text = it.formatSecondTime() }
- })
- )
- }
-
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (isFinishing) {
- return
- }
- when (info.state) {
- SwitchCallState.ACCEPT -> {
- binding.switchTypeTipGroup.visibility = View.GONE
- if (callEngine.callInfo.callStatus != CallState.STATE_DIALOG) {
- if (callParam.isCalled) {
- uiRender.renderForCalled()
- } else {
- uiRender.renderForCaller()
- }
- return
- }
- initForOnTheCall(callParam.otherAccId)
- }
-
- SwitchCallState.INVITE -> {
- switchConfirmDialog.show(info.callType)
- }
-
- SwitchCallState.REJECT -> {
- binding.switchTypeTipGroup.visibility = View.GONE
- getString(R.string.ui_switch_call_type_reject_tip).toastShort(
- this@P2PCallActivity
- )
- }
- }
- }
-
- override fun onCallEnd(info: NECallEndInfo) {
- super.onCallEnd(info)
- configTimeTick(null)
- when (info.reasonCode) {
- NEHangupReasonCode.CALLER_REJECTED -> if (!isFinishing && !callParam.isCalled) {
- getString(R.string.tip_reject_by_other).toastShort(this@P2PCallActivity)
- }
-
- NEHangupReasonCode.BUSY -> if (!isFinishing && !callParam.isCalled) {
- getString(R.string.tip_busy_by_other).toastShort(this@P2PCallActivity)
- }
-
- NEHangupReasonCode.CALLEE_CANCELED -> if (!isFinishing && callParam.isCalled) {
- getString(R.string.tip_cancel_by_other).toastShort(this@P2PCallActivity)
- }
-
- NEHangupReasonCode.TIME_OUT ->
- if (!callParam.isCalled) {
- getString(R.string.tip_timeout_by_other).toastShort(this@P2PCallActivity)
- }
-
- NEHangupReasonCode.OTHER_REJECTED -> getString(R.string.tip_other_client_other_reject).toastShort(
- this@P2PCallActivity
- )
-
- NEHangupReasonCode.OTHER_ACCEPTED -> getString(R.string.tip_other_client_other_accept).toastShort(
- this@P2PCallActivity
- )
- }
- releaseAndFinish(false)
- }
-
- override fun onVideoAvailable(userId: String?, available: Boolean) {
- if (isFinishing) {
- return
- }
- uiRender.updateOnTheCallState(UserState(userId, muteVideo = !available))
- }
-
- override fun onVideoMuted(userId: String?, mute: Boolean) {
- if (isFinishing) {
- return
- }
- uiRender.updateOnTheCallState(UserState(userId, muteVideo = mute))
- }
-
- override fun doOnCreate(savedInstanceState: Bundle?) {
- super.doOnCreate(savedInstanceState)
- ALog.d(tag, callParam.toString())
- initForLaunchUI()
- val dialog: PermissionTipDialog?
- if (!isGranted(
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO
- )
- ) {
- dialog = showPermissionDialog {
- getString(R.string.tip_permission_request_failed).toastShort(this)
- releaseAndFinish(true)
- }
- } else {
- if (callParam.isCalled && callEngine.callInfo.callStatus == CallState.STATE_IDLE) {
- releaseAndFinish(false)
- return
- }
- initForLaunchAction()
- return
- }
- requestPermission({ granted ->
- if (isFinishing) {
- return@requestPermission
- }
- granted.forEach {
- ALog.i(tag, "granted:$it")
- }
- if (granted.containsAll(
- listOf(
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO
- )
- )
- ) {
- dialog.dismiss()
- if (callParam.isCalled && callEngine.callInfo.callStatus == CallState.STATE_IDLE) {
- releaseAndFinish(false)
- return@requestPermission
- }
- initForLaunchAction()
- }
- ALog.i(tag, "extra info is ${callParam.callExtraInfo}")
- }, { deniedForever, denied ->
- denied.forEach {
- ALog.i(tag, "denied:$it")
- }
- deniedForever.forEach {
- ALog.i(tag, "deniedForever:$it")
- }
- if (deniedForever.isNotEmpty() || denied.isNotEmpty()) {
- getString(R.string.tip_permission_request_failed).toastShort(this@P2PCallActivity)
- dialog.dismiss()
- releaseAndFinish(true)
- }
- }, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
- }
-
- override fun provideLayoutView(): View? = binding.root
-
- override fun releaseAndFinish(finishCall: Boolean) {
- super.releaseAndFinish(finishCall)
-
- if (startPreviewCode == 0 || startPreviewCode == ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED) {
- NERtcEx.getInstance().setupLocalVideoCanvas(null)
- NERtcEx.getInstance().stopVideoPreview()
- }
-
- if (finishCall) {
- doHangup(null)
- }
- }
-
- override fun onBackPressed() {
- showExitDialog()
- }
-
- override fun onPause() {
- super.onPause()
- if (isFinishing) {
- switchConfirmDialog.dismiss()
- releaseAndFinish(true)
- }
- }
-
- private fun showExitDialog() {
- val confirmDialog = AlertDialog.Builder(this)
- confirmDialog.setTitle(R.string.tip_dialog_finish_call_title)
- confirmDialog.setMessage(R.string.tip_dialog_finish_call_content)
- confirmDialog.setPositiveButton(
- R.string.tip_dialog_finish_call_positive
- ) { _: DialogInterface?, _: Int ->
- if (!callFinished) {
- getString(R.string.tip_invite_was_sending).toastShort(this)
- return@setPositiveButton
- }
- finish()
- }
- confirmDialog.setNegativeButton(
- R.string.tip_dialog_finish_call_negative
- ) { _: DialogInterface?, _: Int -> }
- confirmDialog.show()
- }
-
- private fun initForLaunchUI() {
- if (callParam.isCalled) {
- // 主叫页面初始化
- uiRender.renderForCalled()
- } else {
- // 被叫页面初始化
- uiRender.renderForCaller()
- }
- }
-
- private fun initForLaunchAction() {
- if (callParam.isCalled) {
- return
- }
- doCall()
- if (CallKitUI.options?.initRtcMode != NECallInitRtcMode.GLOBAL) {
- setupLocalView(binding.videoViewPreview)
- }
- if (callParam.callType == NECallType.VIDEO &&
- CallKitUI.options?.joinRtcWhenCall == false &&
- startPreviewCode != NERtcConstants.ErrorCode.OK
- ) {
- startPreviewCode = NERtcEx.getInstance().startVideoPreview().apply {
- ALog.d(tag, "initForLaunchAction startPreviewCode is $this.")
- }
- }
- }
-
- /**
- * 通话中页面初始化
- */
- private fun initForOnTheCall(userAccId: String? = null) {
- uiRender.renderForOnTheCall(userAccId)
- }
-
- private fun doCall() {
- callFinished = false
-
- doCall { result ->
- callFinished = true
- if (result?.isSuccessful != true && result.code != ResponseCode.RES_PEER_NIM_OFFLINE.toInt() && result.code != ResponseCode.RES_PEER_PUSH_OFFLINE.toInt()) {
- getString(R.string.tip_start_call_failed).toastShort(this@P2PCallActivity)
- }
- }
- }
-
- private fun doAccept() {
- if (binding.tvConnectingTip.tag != true) {
- binding.tvConnectingTip.tag = true
- binding.tvConnectingTip.visibility = View.VISIBLE
- }
- doAccept { result ->
- if (result?.isSuccessful != true) {
- getString(R.string.tip_accept_failed).toastShort(this@P2PCallActivity)
- finish()
- }
- }
- }
-
- private fun doHangup() {
- releaseAndFinish(true)
- }
-
- private fun doMuteVideo(view: ImageView) {
- doMuteVideo()
- view.setImageResource(if (isLocalMuteVideo) R.drawable.cam_off else R.drawable.cam_on)
- uiRender.updateOnTheCallState(
- UserState(
- callParam.currentAccId!!,
- muteVideo = isLocalMuteVideo
- )
- )
- }
-
- private fun doConfigSpeakerSwitch(
- view: ImageView? = null,
- speakerEnable: Boolean = !isSpeakerOn()
- ) {
- doConfigSpeaker(speakerEnable)
- binding.ivMuteSpeaker.setImageResource(
- if (speakerEnable) R.drawable.speaker_on else R.drawable.speaker_off
- )
- binding.ivCallSpeaker.setImageResource(
- if (speakerEnable) R.drawable.icon_call_audio_speaker_on else R.drawable.icon_call_audio_speaker_off
- )
- }
-
- private fun doMuteAudioSwitch(view: ImageView? = null) {
- super.doMuteAudio(!isLocalMuteAudio)
- binding.ivMuteAudio.setImageResource(
- if (isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
- )
- binding.ivCallMuteAudio.setImageResource(
- if (isLocalMuteAudio) R.drawable.icon_call_audio_off else R.drawable.icon_call_audio_on
- )
- }
-
- private fun doSwitchCallType(switchCallState: Int = SwitchCallState.INVITE) {
- if (!NetworkUtils.isConnected()) {
- getString(R.string.tip_network_error).toastShort(this)
- return
- }
- val toCallType = if (callParam.callType == NECallType.VIDEO) {
- NECallType.AUDIO
- } else {
- NECallType.VIDEO
- }
-
- doSwitchCallType(
- toCallType,
- switchCallState,
- object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- getString(R.string.tip_switch_call_type_failed).toastShort(
- this@P2PCallActivity
- )
- ALog.e(tag, "doSwitchCallType to $toCallType error, result is $result.")
- return
- }
- if (switchCallState == SwitchCallState.INVITE) {
- binding.switchTypeTipGroup.visibility = View.VISIBLE
- }
- }
- }
- )
- }
-
- private fun doSwitchCanvas() {
- if (uiConfig?.enableCanvasSwitch == false) {
- return
- }
-
- val rtcUid = callEngine.callInfo.otherUserInfo().uid
- if (rtcUid == 0L) {
- ALog.e(tag, "doSwitchCanvas rtcUid is 0L with accId ${callParam.otherAccId}.")
- return
- }
- if (isLocalMuteVideo) {
- binding.videoViewBig.clearImage()
- binding.videoViewSmall.clearImage()
- }
-
- if (localIsSmallVideo) {
- NERtcEx.getInstance().setupRemoteVideoCanvas(binding.videoViewSmall, rtcUid)
- NERtcEx.getInstance().setupLocalVideoCanvas(binding.videoViewBig)
- } else {
- NERtcEx.getInstance().setupRemoteVideoCanvas(binding.videoViewBig, rtcUid)
- NERtcEx.getInstance().setupLocalVideoCanvas(binding.videoViewSmall)
- }
- localIsSmallVideo = !localIsSmallVideo
-
- uiRender.updateOnTheCallState(
- UserState(
- callParam.currentAccId,
- muteVideo = isLocalMuteVideo
- )
- )
- uiRender.updateOnTheCallState(
- UserState(
- callParam.otherAccId,
- muteVideo = isRemoteMuteVideo
- )
- )
- }
-
- private open inner class UIRender {
- open fun renderForCaller() {
- binding.tvSwitchTipClose.setOnClickListener {
- binding.switchTypeTipGroup.visibility = View.GONE
- }
- val enableAutoJoinWhenCalled = (callEngine as? NECallEngineImpl)?.recorder?.isEnableAutoJoinWhenCalled == true
- binding.calledSwitchGroup.visibility = View.GONE
- binding.callerSwitchGroup.visibility =
- if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
- }
-
- open fun renderForCalled() {
- binding.tvSwitchTipClose.setOnClickListener {
- binding.switchTypeTipGroup.visibility = View.GONE
- }
- binding.callerSwitchGroup.visibility = View.GONE
- val enableAutoJoinWhenCalled = (callEngine as? NECallEngineImpl)?.recorder?.isEnableAutoJoinWhenCalled == true
- if (enableAutoJoinWhenCalled) {
- binding.calledSwitchGroup.visibility = View.VISIBLE
- } else {
- binding.calledSwitchGroup.visibility = View.GONE
- }
- }
-
- open fun renderForOnTheCall(userAccId: String? = null) {
- binding.tvSwitchTipClose.setOnClickListener {
- binding.switchTypeTipGroup.visibility = View.GONE
- }
- binding.callerSwitchGroup.visibility = View.GONE
- binding.calledSwitchGroup.visibility = View.GONE
- if (this is AudioRender) {
- binding.ivCallChannelTypeChange.visibility =
- if (uiConfig?.showAudio2VideoSwitchOnTheCall == true) View.VISIBLE else View.GONE
- }
- if (this is VideoRender) {
- binding.ivCallChannelTypeChange.visibility =
- if (uiConfig?.showVideo2AudioSwitchOnTheCall == true) View.VISIBLE else View.GONE
- }
- uiConfig?.overlayViewOnTheCall?.run {
- if (parent is ViewGroup) {
- (parent as ViewGroup).removeView(this)
- }
- binding.clRoot.addView(
- this,
- ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
- )
- }
- }
-
- open fun updateOnTheCallState(state: UserState) {}
- }
-
- private inner class AudioRender : UIRender() {
- override fun renderForCaller() {
- super.renderForCaller()
- forUserInfoUI(NECallType.AUDIO, callParam.calledAccId, forVideoCaller = true)
- doConfigSpeakerSwitch(speakerEnable = false)
-
- if (isLocalMuteAudio) {
- doMuteAudioSwitch()
- }
-
- binding.ivBg.visibility = View.VISIBLE
- binding.tvCallSwitchTypeDesc.setText(R.string.tip_switch_to_video)
-
- binding.videoViewBig.visibility = View.GONE
- binding.videoViewPreview.visibility = View.GONE
- binding.videoViewSmall.visibility = View.GONE
-
- binding.llOnTheCallOperation.visibility = View.GONE
- binding.calledOperationGroup.visibility = View.GONE
- binding.callerOperationGroup.visibility = View.VISIBLE
- binding.callerAudioOperationGroup.visibility = View.VISIBLE
-
- binding.ivCancel.setOnClickListener(onClickListener)
- binding.ivCallSwitchType.setOnClickListener(onClickListener)
- binding.ivCallSwitchType.setImageResource(R.drawable.icon_call_tip_audio_to_video)
- binding.ivCallChannelTypeChange.setOnClickListener(onClickListener)
-
- binding.ivCallMuteAudio.setOnClickListener(onClickListener)
- binding.ivCallSpeaker.setOnClickListener(onClickListener)
- binding.ivBg.visibility = View.VISIBLE
- if (startPreviewCode == 0 || startPreviewCode == ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED) {
- NERtcEx.getInstance().setupLocalVideoCanvas(null)
- startPreviewCode = if (NERtcEx.getInstance().stopVideoPreview() == 0) -1 else 0
- }
- }
-
- override fun renderForCalled() {
- super.renderForCalled()
- forUserInfoUI(NECallType.AUDIO, callParam.callerAccId)
-
- binding.ivAccept.setImageResource(R.drawable.icon_call_audio_accept)
- binding.ivSwitchType.setImageResource(R.drawable.icon_call_tip_audio_to_video)
- binding.tvOtherCallTip.setText(R.string.tip_invite_to_audio_call)
- binding.tvSwitchTypeDesc.setText(R.string.tip_switch_to_video)
-
- binding.videoViewPreview.visibility = View.GONE
- binding.videoViewBig.visibility = View.GONE
- binding.videoViewSmall.visibility = View.GONE
-
- binding.llOnTheCallOperation.visibility = View.GONE
- binding.calledOperationGroup.visibility = View.VISIBLE
- binding.callerOperationGroup.visibility = View.GONE
- binding.callerAudioOperationGroup.visibility = View.GONE
-
- binding.ivAccept.setOnClickListener(onClickListener)
- binding.ivReject.setOnClickListener(onClickListener)
- binding.ivSwitchType.setOnClickListener(onClickListener)
- binding.ivBg.visibility = View.VISIBLE
- }
-
- override fun renderForOnTheCall(userAccId: String?) {
- super.renderForOnTheCall(userAccId)
-
- callParam.run {
- forUserInfoUI(NECallType.AUDIO, otherAccId)
- }
-
- binding.tvOtherCallTip.setText(R.string.tip_on_the_call)
- binding.tvConnectingTip.visibility = View.GONE
- binding.videoViewPreview.visibility = View.GONE
- binding.videoViewSmall.visibility = View.GONE
- binding.videoViewBig.visibility = View.GONE
- binding.ivSmallVideoShade.visibility = View.GONE
-
- binding.calledOperationGroup.visibility = View.GONE
- binding.callerOperationGroup.visibility = View.GONE
- binding.callerAudioOperationGroup.visibility = View.GONE
- binding.llOnTheCallOperation.visibility = View.VISIBLE
- binding.tvCountdown.visibility = View.VISIBLE
-
- binding.ivCallChannelTypeChange.setImageResource(R.drawable.audio_to_video)
- binding.ivCallChannelTypeChange.setOnClickListener(onClickListener)
- binding.ivMuteAudio.setOnClickListener(onClickListener)
- binding.ivMuteVideo.visibility = View.GONE
- binding.ivHangUp.setOnClickListener(onClickListener)
- binding.ivMuteSpeaker.setOnClickListener(onClickListener)
-
- binding.ivSwitchCamera.visibility = View.GONE
-
- binding.tvRemoteVideoCloseTip.visibility = View.GONE
- binding.ivSmallVideoShade.visibility = View.GONE
- if (!firstLaunch || callParam.isCalled) {
- resetSwitchState(NECallType.AUDIO)
- } else {
- firstLaunch = false
- }
- // sdk 版本变更后在加入通话前设置扬声器不生效,在对方接听后,自己加入rtc 后进行扬声器设置补充;
- if (!callParam.isCalled) {
- doConfigSpeakerSwitch(speakerEnable = isSpeakerOn())
- }
- binding.ivBg.visibility = View.VISIBLE
- }
- }
-
- private var firstLaunch = true
-
- private inner class VideoRender : UIRender() {
- override fun renderForCaller() {
- super.renderForCaller()
- forUserInfoUI(NECallType.VIDEO, callParam.calledAccId, forVideoCaller = true)
- doConfigSpeakerSwitch(speakerEnable = true)
-
- binding.videoViewBig.visibility = View.GONE
- binding.videoViewPreview.visibility = View.VISIBLE
- binding.videoViewSmall.visibility = View.GONE
-
- binding.llOnTheCallOperation.visibility = View.GONE
- binding.calledOperationGroup.visibility = View.GONE
- binding.callerOperationGroup.visibility = View.VISIBLE
- binding.callerAudioOperationGroup.visibility = View.GONE
- binding.ivCancel.setOnClickListener(onClickListener)
- binding.ivCallSwitchType.setOnClickListener(onClickListener)
- binding.ivCallSwitchType.setImageResource(R.drawable.icon_call_tip_video_to_audio)
- binding.tvCallSwitchTypeDesc.setText(R.string.tip_switch_to_audio)
-
- setupLocalView(binding.videoViewPreview)
- if (startPreviewCode != NERtcConstants.ErrorCode.OK &&
- startPreviewCode != ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED
- ) {
- startPreviewCode = NERtcEx.getInstance().startVideoPreview().apply {
- ALog.d(tag, "renderForCaller startPreviewCode is $this.")
- }
- }
- binding.ivBg.visibility = View.GONE
- }
-
- override fun renderForCalled() {
- super.renderForCalled()
-
- forUserInfoUI(NECallType.VIDEO, callParam.callerAccId)
-
- binding.videoViewPreview.visibility = View.GONE
- binding.videoViewBig.visibility = View.GONE
- binding.videoViewSmall.visibility = View.GONE
-
- binding.llOnTheCallOperation.visibility = View.GONE
- binding.calledOperationGroup.visibility = View.VISIBLE
- binding.callerOperationGroup.visibility = View.GONE
- binding.callerAudioOperationGroup.visibility = View.GONE
- binding.tvOtherCallTip.setText(R.string.tip_invite_to_video_call)
- binding.tvSwitchTypeDesc.setText(R.string.tip_switch_to_audio)
-
- binding.ivAccept.setImageResource(R.drawable.call_accept)
- binding.ivSwitchType.setImageResource(R.drawable.icon_call_tip_video_to_audio)
-
- binding.ivAccept.setOnClickListener(onClickListener)
- binding.ivReject.setOnClickListener(onClickListener)
- binding.ivSwitchType.setOnClickListener(onClickListener)
-
- binding.ivBg.visibility = View.VISIBLE
- }
-
- override fun renderForOnTheCall(userAccId: String?) {
- super.renderForOnTheCall(userAccId)
-
- forUserInfoUI(type = NECallType.VIDEO, visible = false)
-
- binding.tvConnectingTip.visibility = View.GONE
- binding.videoViewPreview.visibility = View.GONE
- binding.videoViewSmall.visibility = View.VISIBLE
- binding.videoViewBig.visibility = View.VISIBLE
- binding.ivSmallVideoShade.visibility = View.GONE
-
- binding.calledOperationGroup.visibility = View.GONE
- binding.callerOperationGroup.visibility = View.GONE
- binding.callerAudioOperationGroup.visibility = View.GONE
- binding.llOnTheCallOperation.visibility = View.VISIBLE
- binding.tvCountdown.visibility = View.VISIBLE
-
- binding.videoViewSmall.setOnClickListener(onClickListener)
- binding.ivCallChannelTypeChange.setOnClickListener(onClickListener)
- binding.ivCallChannelTypeChange.setImageResource(R.drawable.video_to_audio)
- binding.ivMuteAudio.setOnClickListener(onClickListener)
- binding.ivMuteVideo.visibility = View.VISIBLE
- binding.ivMuteVideo.setOnClickListener(onClickListener)
- binding.ivHangUp.setOnClickListener(onClickListener)
- binding.ivMuteSpeaker.setOnClickListener(onClickListener)
- resetSwitchState(NECallType.VIDEO)
- binding.ivBg.visibility = View.GONE
-
- firstLaunch = false
- binding.ivSwitchCamera.run {
- visibility = View.VISIBLE
- setOnClickListener(onClickListener)
- }
- if (callParam.currentUserIsCaller()) {
- NERtcEx.getInstance().setupLocalVideoCanvas(null)
- NERtcEx.getInstance().stopVideoPreview()
- }
- setupRemoteView(binding.videoViewBig)
- binding.videoViewPreview.release()
- setupLocalView(binding.videoViewSmall)
- }
-
- override fun updateOnTheCallState(state: UserState) {
- super.updateOnTheCallState(state)
- if (localIsSmallVideo) {
- if (TextUtils.equals(state.userAccId, callParam.currentAccId)) {
- state.muteVideo?.run {
- loadImg(uiConfig?.closeVideoLocalUrl, binding.ivSmallVideoShade)
- binding.ivSmallVideoShade.visibility = if (this) View.VISIBLE else View.GONE
- }
- } else {
- state.muteVideo?.run {
- loadImg(uiConfig?.closeVideoRemoteUrl, binding.ivBigVideoShade)
- binding.ivBigVideoShade.visibility = if (this) View.VISIBLE else View.GONE
- binding.tvRemoteVideoCloseTip.text =
- if (TextUtils.isEmpty(uiConfig?.closeVideoRemoteTip?.trim())) {
- getString(
- R.string.ui_tip_close_camera_by_other
- )
- } else {
- uiConfig?.closeVideoRemoteTip
- }
- binding.tvRemoteVideoCloseTip.visibility = if (this) View.VISIBLE else View.GONE
- }
- }
- } else {
- if (TextUtils.equals(state.userAccId, callParam.currentAccId)) {
- state.muteVideo?.run {
- loadImg(uiConfig?.closeVideoLocalUrl, binding.ivBigVideoShade)
- binding.ivBigVideoShade.visibility = if (this) View.VISIBLE else View.GONE
- binding.tvRemoteVideoCloseTip.text = if (TextUtils.isEmpty(
- uiConfig?.closeVideoLocalTip?.trim()
- )
- ) {
- getString(
- R.string.ui_tip_close_camera_by_self
- )
- } else {
- uiConfig?.closeVideoLocalTip
- }
- binding.tvRemoteVideoCloseTip.visibility = if (this) View.VISIBLE else View.GONE
- }
- } else {
- state.muteVideo?.run {
- loadImg(uiConfig?.closeVideoRemoteUrl, binding.ivSmallVideoShade)
- binding.ivSmallVideoShade.visibility = if (this) View.VISIBLE else View.GONE
- }
- }
- }
- }
- }
-
- private fun forUserInfoUI(
- type: Int,
- accId: String? = null,
- visible: Boolean = true,
- forVideoCaller: Boolean = false
- ) {
- if (!visible) {
- binding.userInfoGroup.visibility = View.GONE
- return
- }
- binding.userInfoGroup.visibility = View.VISIBLE
-
- accId?.run {
- fetchNickname {
- binding.tvUserName.text = it
- }
- loadAvatarByAccId(
- this@P2PCallActivity,
- binding.ivUserInnerAvatar,
- binding.ivBg,
- binding.tvUserInnerAvatar,
- uiConfig?.enableTextDefaultAvatar ?: true
- )
- }
- val centerSize = 97.dip2Px(this)
- val topSize = 60.dip2Px(this)
-
- val constraintSet = ConstraintSet()
- constraintSet.clone(binding.clRoot)
- constraintSet.clear(R.id.flUserAvatar)
- constraintSet.clear(R.id.tvOtherCallTip)
- constraintSet.clear(R.id.tvUserName)
- constraintSet.constrainHeight(R.id.tvOtherCallTip, ConstraintSet.WRAP_CONTENT)
- constraintSet.constrainWidth(R.id.tvOtherCallTip, ConstraintSet.WRAP_CONTENT)
- constraintSet.constrainWidth(R.id.tvUserName, ConstraintSet.WRAP_CONTENT)
- constraintSet.constrainWidth(R.id.tvUserName, ConstraintSet.WRAP_CONTENT)
-
- if (type == NECallType.VIDEO && forVideoCaller) {
- val marginSize16 = 16.dip2Px(this)
- binding.flUserAvatar.run {
- constraintSet.constrainWidth(id, topSize)
- constraintSet.constrainHeight(id, topSize)
- constraintSet.connect(
- id,
- ConstraintSet.END,
- ConstraintSet.PARENT_ID,
- ConstraintSet.END,
- marginSize16
- )
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- ConstraintSet.PARENT_ID,
- ConstraintSet.TOP,
- marginSize16
- )
- }
- val marginSize10 = 10.dip2Px(this)
- val marginSize5 = 5.dip2Px(this)
- binding.tvUserName.run {
- textSize = 18f
- constraintSet.connect(
- id,
- ConstraintSet.END,
- binding.flUserAvatar.id,
- ConstraintSet.START,
- marginSize10
- )
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- binding.flUserAvatar.id,
- ConstraintSet.TOP,
- marginSize5
- )
- }
- binding.tvOtherCallTip.run {
- setTextColor(ContextCompat.getColor(context, R.color.white))
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- binding.tvUserName.id,
- ConstraintSet.BOTTOM,
- marginSize5
- )
- constraintSet.connect(
- id,
- ConstraintSet.END,
- binding.flUserAvatar.id,
- ConstraintSet.START,
- marginSize10
- )
- }
- } else {
- binding.flUserAvatar.run {
- constraintSet.constrainWidth(id, centerSize)
- constraintSet.constrainHeight(id, centerSize)
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- ConstraintSet.PARENT_ID,
- ConstraintSet.TOP,
- 160.dip2Px(context)
- )
- constraintSet.centerHorizontally(id, ConstraintSet.PARENT_ID)
- }
- binding.tvUserName.run {
- textSize = 20f
- constraintSet.centerHorizontally(id, ConstraintSet.PARENT_ID)
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- binding.flUserAvatar.id,
- ConstraintSet.BOTTOM,
- 15.dip2Px(context)
- )
- }
- binding.tvOtherCallTip.run {
- setTextColor(ContextCompat.getColor(context, R.color.color_cccccc))
- constraintSet.connect(
- id,
- ConstraintSet.TOP,
- binding.tvUserName.id,
- ConstraintSet.BOTTOM,
- 8.dip2Px(context)
- )
- constraintSet.centerHorizontally(id, ConstraintSet.PARENT_ID)
- }
- }
- constraintSet.applyTo(binding.clRoot)
- }
-
- private fun resetSwitchState(callType: Int) {
- if (callType == NECallType.VIDEO) {
- doConfigSpeaker(true)
- binding.ivMuteSpeaker.setImageResource(R.drawable.speaker_on)
- } else {
- doConfigSpeaker(false)
- binding.ivMuteSpeaker.setImageResource(R.drawable.speaker_off)
- }
- // 视频
- localIsSmallVideo = true
-
- doMuteAudio(false)
- binding.ivMuteVideo.setImageResource(R.drawable.cam_on)
- binding.tvRemoteVideoCloseTip.visibility = View.GONE
- binding.videoViewSmall.setBackgroundColor(Color.TRANSPARENT)
- // 音频
- if (isLocalMuteAudio) {
- doMuteAudioSwitch(binding.ivMuteAudio)
- }
- }
-
- private fun loadImg(url: String?, imageView: ImageView) {
- Glide.with(applicationContext).load(url)
- .error(R.color.black)
- .placeholder(R.color.black)
- .centerCrop()
- .into(imageView)
- }
-
- class UserState(
- val userAccId: String?,
- val muteVideo: Boolean? = null
- )
-}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallFragmentActivity.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallFragmentActivity.kt
index c9ad5cf..62f74c5 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallFragmentActivity.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PCallFragmentActivity.kt
@@ -6,10 +6,10 @@
package com.netease.yunxin.nertc.ui.p2p
+import android.app.AlertDialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
-import androidx.appcompat.app.AlertDialog
import com.netease.lava.nertc.sdk.video.NERtcVideoView
import com.netease.yunxin.kit.alog.ALog
import com.netease.yunxin.kit.call.NEResultObserver
@@ -37,13 +37,14 @@ import com.netease.yunxin.nertc.ui.p2p.fragment.P2PCallFragmentType.VIDEO_CALLEE
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PCallFragmentType.VIDEO_CALLER
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PCallFragmentType.VIDEO_ON_THE_CALL
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.CHANGE_CALL_TYPE
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.FROM_FLOATING_WINDOW
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.INIT
import com.netease.yunxin.nertc.ui.p2p.fragment.callee.AudioCalleeFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.callee.VideoCalleeFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.caller.AudioCallerFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.caller.VideoCallerFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.onthecall.AudioOnTheCallFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.onthecall.VideoOnTheCallFragment
-import com.netease.yunxin.nertc.ui.utils.SwitchCallTypeConfirmDialog
import com.netease.yunxin.nertc.ui.utils.toastShort
open class P2PCallFragmentActivity : CommonCallActivity() {
@@ -64,6 +65,10 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
get() = this@P2PCallFragmentActivity.isLocalMuteVideo
override val isLocalMuteSpeaker: Boolean
get() = this@P2PCallFragmentActivity.isLocalMuteSpeaker
+ override val isLocalSmallVideo: Boolean
+ get() = this@P2PCallFragmentActivity.isLocalSmallVideo
+ override val isVirtualBlur: Boolean
+ get() = this@P2PCallFragmentActivity.isVirtualBlur
override fun isSpeakerOn(): Boolean = this@P2PCallFragmentActivity.isSpeakerOn()
@@ -73,6 +78,9 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
override fun doMuteVideo(mute: Boolean) = this@P2PCallFragmentActivity.doMuteVideo(mute)
+ override fun doVirtualBlur(enable: Boolean) =
+ this@P2PCallFragmentActivity.doVirtualBlur(enable)
+
override fun doSwitchCamera() = this@P2PCallFragmentActivity.doSwitchCamera()
override fun configTimeTick(config: CallUIOperationsMgr.TimeTickConfig?) =
@@ -84,7 +92,11 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
override fun doAccept(observer: NEResultObserver>?) =
this@P2PCallFragmentActivity.doAccept(observer)
- override fun doHangup(observer: NEResultObserver>?, channelId: String?, extraInfo: String?) {
+ override fun doHangup(
+ observer: NEResultObserver>?,
+ channelId: String?,
+ extraInfo: String?
+ ) {
this@P2PCallFragmentActivity.doHangup(observer, channelId, extraInfo)
finish()
}
@@ -93,16 +105,24 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
callType: Int, switchCallState: Int, observer: NEResultObserver>?
) = this@P2PCallFragmentActivity.doSwitchCallType(callType, switchCallState, observer)
- override fun setupLocalView(view: NERtcVideoView?) =
- this@P2PCallFragmentActivity.setupLocalView(view)
+ override fun setupLocalView(view: NERtcVideoView?, action: ((NERtcVideoView?) -> Unit)?) {
+ this@P2PCallFragmentActivity.setupLocalView(view, action)
+ }
- override fun setupRemoteView(view: NERtcVideoView?) =
- this@P2PCallFragmentActivity.setupRemoteView(view)
+ override fun setupRemoteView(view: NERtcVideoView?, action: ((NERtcVideoView?) -> Unit)?) {
+ this@P2PCallFragmentActivity.setupRemoteView(view, action)
+ }
override fun currentCallState(): Int = this@P2PCallFragmentActivity.currentCallState()
override fun showPermissionDialog(clickListener: View.OnClickListener) =
this@P2PCallFragmentActivity.showPermissionDialog(clickListener)
+
+ override fun showFloatingWindow() = this@P2PCallFragmentActivity.doShowFloatingWindow()
+
+ override fun startVideoPreview() = this@P2PCallFragmentActivity.startVideoPreview()
+
+ override fun stopVideoPreview() = this@P2PCallFragmentActivity.stopVideoPreview()
}
private var currentFragment: BaseP2pCallFragment? = null
@@ -115,14 +135,6 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
private val audioCalleeFragment = AudioCalleeFragment()
private val audioOnTheCallFragment = AudioOnTheCallFragment()
- private val switchConfirmDialog by lazy {
- SwitchCallTypeConfirmDialog(this, {
- doSwitchCallType(it, SwitchCallState.ACCEPT)
- }, {
- doSwitchCallType(it, SwitchCallState.REJECT)
- })
- }
-
override fun onCallEnd(info: NECallEndInfo) {
when (info.reasonCode) {
NEHangupReasonCode.CALLER_REJECTED -> if (!isFinishing && !callParam.isCalled) {
@@ -170,7 +182,7 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
}
SwitchCallState.INVITE -> {
- switchConfirmDialog.show(info.callType)
+ showSwitchCallTypeConfirmDialog(info.callType)
}
SwitchCallState.REJECT -> {
@@ -229,8 +241,7 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
confirmDialog.setPositiveButton(
R.string.tip_dialog_finish_call_positive
) { _: DialogInterface?, _: Int ->
- doHangup()
- finish()
+ releaseAndFinish(true)
}
confirmDialog.setNegativeButton(
R.string.tip_dialog_finish_call_negative
@@ -244,28 +255,36 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
) {
val fragment = getFragment(callState, callType)
if (fragment == null) {
- ALog.e(tag, "currentFragment is null, currentCallState is ${currentCallState()}.")
+ ALog.e(tag, "currentFragment is null, callState is $callState, callType is $callType.")
finish()
return
}
- fragment.configData(bridge)
+ fragment.configData(
+ bridge,
+ if (isFromFloatingWindow) {
+ FROM_FLOATING_WINDOW
+ } else {
+ INIT
+ }
+ )
if (fragment != currentFragment) {
- supportFragmentManager.beginTransaction().add(R.id.clRoot, fragment)
- .commitAllowingStateLoss()
+ val tag = "${getFragmentKey(callState, callType)}"
+ val transaction = supportFragmentManager.beginTransaction()
+ if (supportFragmentManager.findFragmentByTag(tag) == fragment) {
+ transaction.show(fragment)
+ } else {
+ transaction.add(R.id.clRoot, fragment, tag)
+ }
currentFragment?.run {
- supportFragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
+ transaction.hide(this)
}
+ transaction.commitAllowingStateLoss()
}
currentFragment = fragment
}
- protected fun getFragment(callState: Int, callType: Int): BaseP2pCallFragment? {
- val key = when (callState) {
- STATE_CALL_OUT, STATE_IDLE -> if (callType == NECallType.VIDEO) VIDEO_CALLER else AUDIO_CALLER
- STATE_INVITED -> if (callType == NECallType.VIDEO) VIDEO_CALLEE else AUDIO_CALLEE
- STATE_DIALOG -> if (callType == NECallType.VIDEO) VIDEO_ON_THE_CALL else AUDIO_ON_THE_CALL
- else -> null
- } ?: return null
+ protected open fun getFragment(callState: Int, callType: Int): BaseP2pCallFragment? {
+ val key = getFragmentKey(callState, callType) ?: return null
return uiConfig?.customCallFragmentMap?.get(key) ?: when (key) {
VIDEO_CALLEE -> videoCalleeFragment
VIDEO_CALLER -> videoCallerFragment
@@ -276,4 +295,13 @@ open class P2PCallFragmentActivity : CommonCallActivity() {
else -> null
}
}
+
+ protected open fun getFragmentKey(callState: Int, callType: Int): Int? {
+ return when (callState) {
+ STATE_CALL_OUT, STATE_IDLE -> if (callType == NECallType.VIDEO) VIDEO_CALLER else AUDIO_CALLER
+ STATE_INVITED -> if (callType == NECallType.VIDEO) VIDEO_CALLEE else AUDIO_CALLEE
+ STATE_DIALOG -> if (callType == NECallType.VIDEO) VIDEO_ON_THE_CALL else AUDIO_ON_THE_CALL
+ else -> null
+ }
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PUIConfig.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PUIConfig.kt
index da1c120..e179fc6 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PUIConfig.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/P2PUIConfig.kt
@@ -25,19 +25,19 @@ class P2PUIConfig constructor(
/**
* 本端用户关闭摄像头时的图像展示
*/
- val closeVideoLocalUrl: String? = null, // TODO:
+ val closeVideoLocalUrl: String? = null,
/**
* 本端用户关闭摄像头时的文本提示
*/
- val closeVideoLocalTip: CharSequence? = null, // TODO:
+ val closeVideoLocalTip: CharSequence? = null,
/**
* 对端用户关闭摄像头时的本端的图像展示
*/
- val closeVideoRemoteUrl: String? = null, // TODO:
+ val closeVideoRemoteUrl: String? = null,
/**
* 对端用户关闭摄像头时的本端文本提示
*/
- val closeVideoRemoteTip: CharSequence? = null, // TODO:
+ val closeVideoRemoteTip: CharSequence? = null,
/**
* 关闭视频模式,默认 [CLOSE_TYPE_MUTE],也支持 [CLOSE_TYPE_DISABLE],[CLOSE_TYPE_COMPAT]
*/
@@ -45,11 +45,11 @@ class P2PUIConfig constructor(
/**
* 是否支持通话中大小画面点击切换,默认 true
*/
- val enableCanvasSwitch: Boolean = true, // TODO:
+ val enableCanvasSwitch: Boolean = true,
/**
* 通话中页面支持覆盖的view
*/
- val overlayViewOnTheCall: View? = null, // TODO:
+ val overlayViewOnTheCall: View? = null,
/**
* 当没有头像时是否展示文字头像,默认 true
*/
@@ -65,11 +65,27 @@ class P2PUIConfig constructor(
/**
* 自定义通话中 fragment 页面
*/
- val customCallFragmentMap: Map? = null
+ val customCallFragmentMap: Map? = null,
+ /**
+ * 是否支持小窗,默认false
+ */
+ val enableFloatingWindow: Boolean = false,
+ /**
+ * 是否支持在 home 出应用后自动展示小窗,默认false
+ */
+ val enableAutoFloatingWindowWhenHome: Boolean = false,
+ /**
+ * 是否开启被叫视频预览,默认false
+ */
+ val enableVideoCalleePreview: Boolean = false,
+ /**
+ * 是否支持通话视频虚化,默认false
+ */
+ val enableVirtualBlur: Boolean = false
) {
override fun toString(): String {
- return "P2PUIConfig(showAudio2VideoSwitchOnTheCall=$showAudio2VideoSwitchOnTheCall, showVideo2AudioSwitchOnTheCall=$showVideo2AudioSwitchOnTheCall, closeVideoLocalUrl=$closeVideoLocalUrl, closeVideoLocalTip=$closeVideoLocalTip, closeVideoRemoteUrl=$closeVideoRemoteUrl, closeVideoRemoteTip=$closeVideoRemoteTip, closeVideoType=$closeVideoType, enableCanvasSwitch=$enableCanvasSwitch, overlayViewOnTheCall=$overlayViewOnTheCall, enableTextDefaultAvatar=$enableTextDefaultAvatar, enableForegroundService=$enableForegroundService, foregroundNotificationConfig=$foregroundNotificationConfig, customCallFragmentMap=$customCallFragmentMap)"
+ return "P2PUIConfig(showAudio2VideoSwitchOnTheCall=$showAudio2VideoSwitchOnTheCall, showVideo2AudioSwitchOnTheCall=$showVideo2AudioSwitchOnTheCall, closeVideoLocalUrl=$closeVideoLocalUrl, closeVideoLocalTip=$closeVideoLocalTip, closeVideoRemoteUrl=$closeVideoRemoteUrl, closeVideoRemoteTip=$closeVideoRemoteTip, closeVideoType=$closeVideoType, enableCanvasSwitch=$enableCanvasSwitch, overlayViewOnTheCall=$overlayViewOnTheCall, enableTextDefaultAvatar=$enableTextDefaultAvatar, enableForegroundService=$enableForegroundService, foregroundNotificationConfig=$foregroundNotificationConfig, customCallFragmentMap=$customCallFragmentMap, enableFloatingWindow=$enableFloatingWindow, enableAutoFloatingWindowWhenHome=$enableAutoFloatingWindowWhenHome, enableVideoCalleePreview=$enableVideoCalleePreview, enableVirtualBlur=$enableVirtualBlur)"
}
class Builder {
@@ -86,6 +102,10 @@ class P2PUIConfig constructor(
private var enableForegroundService: Boolean = false
private var foregroundNotificationConfig: CallKitNotificationConfig? = null
private var customCallFragmentMap: MutableMap = mutableMapOf()
+ private var enableFloatingWindow: Boolean = true
+ private var enableAutoFloatingWindowWhenHome: Boolean = false
+ private var enableVideoCalleePreview: Boolean = false
+ private var enableVirtualBlur: Boolean = false
fun showAudio2VideoSwitchOnTheCall(enable: Boolean) =
apply { this.showAudio2VideoSwitchOnTheCall = enable }
@@ -119,21 +139,39 @@ class P2PUIConfig constructor(
fun customCallFragmentByKey(key: Int, fragment: BaseP2pCallFragment) =
apply { customCallFragmentMap[key] = fragment }
+ fun enableFloatingWindow(enable: Boolean) = apply { this.enableFloatingWindow = enable }
+
+ fun enableAutoFloatingWindowWhenHome(enable: Boolean) = apply {
+ this.enableAutoFloatingWindowWhenHome = enable
+ }
+
+ fun enableVideoCalleePreview(enable: Boolean) = apply {
+ this.enableVideoCalleePreview = enable
+ }
+
+ fun enableVirtualBlur(enable: Boolean) = apply {
+ this.enableVirtualBlur = enable
+ }
+
fun build(): P2PUIConfig {
return P2PUIConfig(
- showAudio2VideoSwitchOnTheCall,
- showVideo2AudioSwitchOnTheCall,
- closeVideoLocalUrl,
- closeVideoLocalTip,
- closeVideoRemoteUrl,
- closeVideoRemoteTip,
- closeVideoType,
- enableCanvasSwitch,
- overlayViewOnTheCall,
- enableTextDefaultAvatar,
- enableForegroundService,
- foregroundNotificationConfig,
- customCallFragmentMap
+ showAudio2VideoSwitchOnTheCall = showAudio2VideoSwitchOnTheCall,
+ showVideo2AudioSwitchOnTheCall = showVideo2AudioSwitchOnTheCall,
+ closeVideoLocalUrl = closeVideoLocalUrl,
+ closeVideoLocalTip = closeVideoLocalTip,
+ closeVideoRemoteUrl = closeVideoRemoteUrl,
+ closeVideoRemoteTip = closeVideoRemoteTip,
+ closeVideoType = closeVideoType,
+ enableCanvasSwitch = enableCanvasSwitch,
+ overlayViewOnTheCall = overlayViewOnTheCall,
+ enableTextDefaultAvatar = enableTextDefaultAvatar,
+ enableForegroundService = enableForegroundService,
+ foregroundNotificationConfig = foregroundNotificationConfig,
+ customCallFragmentMap = customCallFragmentMap,
+ enableFloatingWindow = enableFloatingWindow,
+ enableAutoFloatingWindowWhenHome = enableAutoFloatingWindowWhenHome,
+ enableVideoCalleePreview = enableVideoCalleePreview,
+ enableVirtualBlur = enableVirtualBlur
)
}
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/BaseP2pCallFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/BaseP2pCallFragment.kt
index 9abebdc..8b2c368 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/BaseP2pCallFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/BaseP2pCallFragment.kt
@@ -11,16 +11,27 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
import com.netease.yunxin.kit.call.p2p.model.NECallEngineDelegate
import com.netease.yunxin.kit.call.p2p.model.NECallInfo
+import com.netease.yunxin.kit.call.p2p.model.NECallType
import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
import com.netease.yunxin.kit.call.p2p.model.NEInviteInfo
+import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
+import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackExTemp
import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackProxyMgr
+import com.netease.yunxin.nertc.ui.R
import com.netease.yunxin.nertc.ui.base.CallParam
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.INIT
+import com.netease.yunxin.nertc.ui.utils.PermissionRequester
+import com.netease.yunxin.nertc.ui.utils.PermissionTipDialog
+import com.netease.yunxin.nertc.ui.utils.isGranted
+import com.netease.yunxin.nertc.ui.utils.registerPermissionRequesterEx
+import com.netease.yunxin.nertc.ui.utils.toastShort
/**
* 处理 ui 相关内容,主要处理 页面点击相关内容,添加相关监听,页面状态切换
@@ -187,6 +198,19 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
*/
protected val viewKeyTextRemoteVideoCloseTip = "tvRemoteVideoCloseTip"
+ /**
+ * 页面中小窗触发控件
+ */
+ protected val viewKeyImageFloatingWindow = "ivFloatingWindow"
+
+ /**
+ * 视频背景虚化开关图片控件
+ */
+ protected val viewKeyImageVirtualBlur = "ivVirtualBlur"
+
+ /**
+ * fragment和activity交互接口
+ */
protected lateinit var bridge: FragmentActionBridge
private set
@@ -196,40 +220,158 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
protected var rootView: View? = null
private set
+ /**
+ * 申请权限工具
+ */
+ protected var permissionRequester: PermissionRequester? = null
+
+ /**
+ * 权限提示弹窗
+ */
+ protected var permissionTipDialog: PermissionTipDialog? = null
+
+ /**
+ * 日志标签
+ */
+ private val logTag = "BaseP2pCallFragment"
+
+ /**
+ * 页面初始化类型
+ */
+ private var initType: Int = INIT
+
/**
* 页面元素绑定映射关系
*/
private val viewBindMap = mutableMapOf()
+ /**
+ * 页面点击事件前置动作行为映射
+ */
+ private val beforeClickMap = mutableMapOf Boolean>()
+
+ /**
+ * 页面点击事件后置动作行为映射
+ */
+ private val afterClickMap = mutableMapOf Unit>()
+
+ /**
+ * 页面切换通话类型回调
+ */
+ private val switchObserver = object : NEResultObserver> {
+ override fun onResult(result: CommonResult?) {
+ if (result?.isSuccessful != true) {
+ context?.run { getString(R.string.tip_switch_call_type_failed).toastShort(this) }
+ ALog.e(
+ logTag,
+ "doSwitchCallType to ${NECallType.VIDEO} error, result is $result."
+ )
+ return
+ }
+ getView(viewKeySwitchTypeTipGroup)?.run {
+ visibility = View.VISIBLE
+ }
+ }
+ }
+
private val rtcDelegate = object : NERtcCallbackExTemp() {
override fun onJoinChannel(res: Int, cid: Long, time: Long, uid: Long) {
this@BaseP2pCallFragment.onJoinChannel(res, cid, time, uid)
}
}
- fun configData(bridge: FragmentActionBridge) {
+ fun configData(bridge: FragmentActionBridge, initType: Int = INIT) {
this.bridge = bridge
+ this.initType = initType
}
open fun toUpdateUIState(type: Int) {
}
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ registerPermissionRequester()
+ }
+
final override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? = toCreateRootView(inflater, container, savedInstanceState).apply {
+ logApiInvoke("toCreateRootView")
this@BaseP2pCallFragment.rootView = this
NERtcCallbackProxyMgr.getInstance().addCallback(rtcDelegate)
toBindView()
+ logApiInvoke("toBindView")
toRenderView(bridge.callParam, bridge.uiConfig)
+ logApiInvoke("toRenderView")
+ onPermissionRequest()
+ logApiInvoke("onPermissionRequest")
onCreateAction()
- toUpdateUIState(INIT)
+ logApiInvoke("onCreateAction")
+ toUpdateUIState(initType)
+ logApiInvoke("toUpdateUIState")
+ initType = INIT
}
override fun onDestroyView() {
super.onDestroyView()
NERtcCallbackProxyMgr.getInstance().removeCallback(rtcDelegate)
+ permissionTipDialog?.dismiss()
viewBindMap.clear()
- onDestroyAction()
+ logApiInvoke("onDestroyAction")
+ }
+
+ protected open fun registerPermissionRequester() {
+ val permissionList = permissionList()
+ if (permissionList.isNotEmpty()) {
+ permissionRequester = registerPermissionRequesterEx()
+ }
+ }
+
+ protected open fun arePermissionsGranted(permissionList: List = permissionList()): Boolean {
+ return context?.isGranted(*permissionList().toTypedArray()) == true
+ }
+
+ protected open fun onPermissionRequest() {
+ permissionTipDialog = if (arePermissionsGranted()) {
+ actionForPermissionGranted()
+ logApiInvoke("actionForPermissionGranted")
+ return
+ } else {
+ bridge.showPermissionDialog {
+ activity?.finish()
+ }
+ }
+ val permissionList = permissionList()
+ permissionRequester?.request(
+ onGranted = {
+ if (it.containsAll(permissionList)) {
+ permissionTipDialog?.dismiss()
+ actionForPermissionGranted()
+ logApiInvoke("actionForPermissionGranted")
+ }
+ },
+ onDenied = { _, _ ->
+ actionForPermissionDenied()
+ logApiInvoke("actionForPermissionDenied")
+ },
+ permissionList = permissionList
+ )
+ }
+
+ protected open fun requestPermission(
+ permissionList: List,
+ onGranted: ((List) -> Unit)? = null,
+ onDenied: ((List, List) -> Unit)? = null
+ ) {
+ permissionRequester?.request(
+ onGranted = {
+ onGranted?.invoke(it)
+ },
+ onDenied = { deniedForeverList, deniedList ->
+ onDenied?.invoke(deniedForeverList, deniedList)
+ },
+ permissionList = permissionList
+ )
}
protected open fun toCreateRootView(
@@ -238,6 +380,17 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
savedInstanceState: Bundle?
): View? = null
+ protected open fun permissionList(): List = emptyList()
+
+ protected open fun actionForPermissionGranted() {
+ }
+
+ protected open fun actionForPermissionDenied() {
+ context?.run {
+ getString(R.string.tip_permission_request_failed).toastShort(this)
+ }
+ }
+
protected open fun toBindView() {
}
@@ -250,6 +403,14 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
protected open fun onDestroyAction() {
}
+ protected open fun switchCallType(
+ callType: Int,
+ switchCallState: Int = SwitchCallState.INVITE,
+ observer: NEResultObserver>? = switchObserver
+ ) {
+ bridge.doSwitchCallType(callType, SwitchCallState.INVITE, switchObserver)
+ }
+
protected fun bindView(key: String, view: View?) {
viewBindMap[key] = view
}
@@ -259,13 +420,40 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
return viewBindMap[key] as? T
}
+ protected open fun View.bindClick(
+ key: String,
+ onClick: (View) -> Unit
+ ) {
+ setOnClickListener {
+ if (beforeClickMap[key]?.invoke(this) == true) {
+ return@setOnClickListener
+ }
+ onClick.invoke(this)
+ afterClickMap[key]?.invoke(this)
+ }
+ }
+
+ protected fun bindBeforeClick(key: String, onClick: (View) -> Boolean) {
+ beforeClickMap[key] = onClick
+ }
+
+ protected fun bindAfterClick(key: String, onClick: (View) -> Unit) {
+ afterClickMap[key] = onClick
+ }
+
protected fun removeView(key: String) = viewBindMap.remove(key)
override fun onReceiveInvited(info: NEInviteInfo) {}
override fun onCallConnected(info: NECallInfo) {}
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {}
+ override fun onCallTypeChange(info: NECallTypeChangeInfo) {
+ if (info.state == SwitchCallState.REJECT || info.state == SwitchCallState.ACCEPT) {
+ getView(viewKeySwitchTypeTipGroup)?.run {
+ visibility = View.GONE
+ }
+ }
+ }
override fun onCallEnd(info: NECallEndInfo) {}
@@ -276,4 +464,8 @@ open class BaseP2pCallFragment : Fragment(), NECallEngineDelegate {
override fun onAudioMuted(userId: String?, mute: Boolean) {}
open fun onJoinChannel(res: Int, cid: Long, time: Long, uid: Long) {}
+
+ private fun logApiInvoke(name: String?) {
+ ALog.dApi(logTag, "${this@BaseP2pCallFragment}:$name was invoked.")
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/FragmentActionBridge.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/FragmentActionBridge.kt
index f7269be..7fe5b43 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/FragmentActionBridge.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/FragmentActionBridge.kt
@@ -7,6 +7,7 @@
package com.netease.yunxin.nertc.ui.p2p.fragment
import android.view.View.OnClickListener
+import com.netease.lava.api.IVideoRender
import com.netease.lava.nertc.sdk.video.NERtcVideoView
import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.NECallEngine
@@ -33,6 +34,10 @@ interface FragmentActionBridge {
val isLocalMuteSpeaker: Boolean
+ val isLocalSmallVideo: Boolean
+
+ val isVirtualBlur: Boolean
+
fun isSpeakerOn(): Boolean
fun configTimeTick(config: CallUIOperationsMgr.TimeTickConfig?)
@@ -43,6 +48,8 @@ interface FragmentActionBridge {
fun doMuteVideo(mute: Boolean = !isLocalMuteVideo)
+ fun doVirtualBlur(enable: Boolean = !isVirtualBlur)
+
fun doSwitchCamera()
fun doCall(
@@ -61,11 +68,32 @@ interface FragmentActionBridge {
callType: Int, switchCallState: Int, observer: NEResultObserver>? = null
)
- fun setupLocalView(view: NERtcVideoView?)
+ fun setupLocalView(
+ view: NERtcVideoView?,
+ action: ((NERtcVideoView?) -> Unit)? = {
+ it?.run {
+ setZOrderMediaOverlay(true)
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ }
+ )
- fun setupRemoteView(view: NERtcVideoView?)
+ fun setupRemoteView(
+ view: NERtcVideoView?,
+ action: ((NERtcVideoView?) -> Unit)? = {
+ it?.run {
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ }
+ )
fun currentCallState(): Int
fun showPermissionDialog(clickListener: OnClickListener): PermissionTipDialog
+
+ fun showFloatingWindow()
+
+ fun startVideoPreview()
+
+ fun stopVideoPreview()
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/P2PUIUpdateType.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/P2PUIUpdateType.kt
index 6b01a36..fb3997b 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/P2PUIUpdateType.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/P2PUIUpdateType.kt
@@ -16,4 +16,9 @@ object P2PUIUpdateType {
* 变更通话类型
*/
const val CHANGE_CALL_TYPE = 2
+
+ /**
+ * 从浮窗转换至通话界面
+ */
+ const val FROM_FLOATING_WINDOW = 3
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/AudioCalleeFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/AudioCalleeFragment.kt
index 495f1ac..6ff0c6e 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/AudioCalleeFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/AudioCalleeFragment.kt
@@ -6,7 +6,7 @@
package com.netease.yunxin.nertc.ui.p2p.fragment.callee
-import android.Manifest
+import android.Manifest.permission.CAMERA
import android.Manifest.permission.RECORD_AUDIO
import android.os.Bundle
import android.view.LayoutInflater
@@ -14,12 +14,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.CallKitUI
import com.netease.yunxin.nertc.ui.R
@@ -29,9 +24,6 @@ import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
import com.netease.yunxin.nertc.ui.databinding.FragmentP2pAudioCalleeBinding
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
import com.netease.yunxin.nertc.ui.utils.toastShort
/**
@@ -42,23 +34,6 @@ open class AudioCalleeFragment : BaseP2pCallFragment() {
protected lateinit var binding: FragmentP2pAudioCalleeBinding
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run { getString(R.string.tip_switch_call_type_failed).toastShort(this) }
-
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.VIDEO} error, result is $result."
- )
- return
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
- }
- }
- }
-
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = FragmentP2pAudioCalleeBinding.inflate(inflater, container, false).run {
@@ -113,45 +88,34 @@ open class AudioCalleeFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
- bridge.doHangup()
- }
-
val enableAutoJoinWhenCalled = CallKitUI.options?.enableAutoJoinWhenCalled == true
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
getView(viewKeyImageSwitchType)?.run {
visibility = if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
+ bindClick(viewKeyImageSwitchType) {
+ if (!NetworkUtils.isConnected()) {
+ context?.run { getString(R.string.tip_network_error).toastShort(this) }
+ return@bindClick
}
val action = Action@{
- if (!NetworkUtils.isConnected()) {
- context?.run { getString(R.string.tip_network_error).toastShort(this) }
- return@Action
- }
- bridge.doSwitchCallType(
- NECallType.VIDEO,
- SwitchCallState.INVITE,
- switchObserver
- )
+ switchCallType(NECallType.VIDEO)
}
- if (context?.isGranted(Manifest.permission.CAMERA) == true) {
+ if (arePermissionsGranted(listOf(CAMERA))) {
action.invoke()
- return@setOnClickListener
+ return@bindClick
}
requestPermission(
- onGranted = {
+ listOf(CAMERA),
+ {
action.invoke()
},
- onDenied = { _, _ ->
+ { _, _ ->
context?.run {
getString(R.string.tip_permission_request_failed).toastShort(this)
}
- },
- Manifest.permission.CAMERA
+ }
)
}
}
@@ -159,12 +123,12 @@ open class AudioCalleeFragment : BaseP2pCallFragment() {
if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
getView(viewKeyImageReject)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageReject) {
bridge.doHangup()
}
}
getView(viewKeyImageAccept)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageAccept) {
getView(viewKeyTextConnectingTip)?.visibility = View.VISIBLE
bridge.doAccept {
if (!it.isSuccessful) {
@@ -176,7 +140,7 @@ open class AudioCalleeFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
@@ -184,34 +148,11 @@ open class AudioCalleeFragment : BaseP2pCallFragment() {
}
}
- override fun onCreateAction() {
- val dialog = if (context?.isGranted(RECORD_AUDIO) == true) {
- return
- } else {
- bridge.showPermissionDialog {
- bridge.doHangup()
- }
- }
- requestPermission(
- onGranted = {
- dialog.dismiss()
- },
- onDenied = { _, _ ->
- context?.run { getString(R.string.tip_permission_request_failed).toastShort(this) }
- },
- RECORD_AUDIO
- )
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO)
}
override fun toUpdateUIState(type: Int) {
bridge.doConfigSpeaker(false)
}
-
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == SwitchCallState.REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
- }
- }
- }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/VideoCalleeFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/VideoCalleeFragment.kt
index 98f1b49..bcd41aa 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/VideoCalleeFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/callee/VideoCalleeFragment.kt
@@ -14,12 +14,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
+import com.netease.lava.nertc.sdk.video.NERtcVideoView
+import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
+import com.netease.yunxin.kit.call.p2p.model.NECallInitRtcMode
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.CallKitUI
import com.netease.yunxin.nertc.ui.R
@@ -29,9 +27,6 @@ import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
import com.netease.yunxin.nertc.ui.databinding.FragmentP2pVideoCalleeBinding
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
import com.netease.yunxin.nertc.ui.utils.toastShort
/**
@@ -43,22 +38,6 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
protected lateinit var binding: FragmentP2pVideoCalleeBinding
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run { getString(R.string.tip_switch_call_type_failed).toastShort(this) }
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.AUDIO} error, result is $result."
- )
- return
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
- }
- }
- }
-
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = FragmentP2pVideoCalleeBinding.inflate(inflater, container, false).run {
@@ -68,6 +47,7 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
override fun toBindView() {
bindView(viewKeyImageBigBackground, binding.ivBg)
+ bindView(viewKeyVideoViewPreview, binding.videoViewPreview)
bindView(viewKeyTextUserInnerAvatar, binding.tvUserInnerAvatar)
bindView(viewKeyImageUserInnerAvatar, binding.ivUserInnerAvatar)
@@ -113,8 +93,8 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
- bridge.doHangup()
+ getView(viewKeyVideoViewPreview)?.run {
+ visibility = if (isEnableVideoCalleePreview()) View.VISIBLE else View.GONE
}
val enableAutoJoinWhenCalled = CallKitUI.options?.enableAutoJoinWhenCalled == true
@@ -123,27 +103,24 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
}
getView(viewKeyImageSwitchType)?.run {
visibility = if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
- }
+ bindClick(viewKeyImageSwitchType) {
if (!NetworkUtils.isConnected()) {
context?.run { getString(R.string.tip_network_error).toastShort(this) }
- return@setOnClickListener
+ return@bindClick
}
- bridge.doSwitchCallType(NECallType.AUDIO, SwitchCallState.INVITE, switchObserver)
+ switchCallType(NECallType.AUDIO)
}
}
getView(viewKeyTextSwitchTypeDesc)?.visibility =
if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
getView(viewKeyImageReject)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageReject) {
bridge.doHangup()
}
}
getView(viewKeyImageAccept)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageAccept) {
getView(viewKeyTextConnectingTip)?.visibility = View.VISIBLE
bridge.doAccept {
if (!it.isSuccessful) {
@@ -155,7 +132,7 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
@@ -163,41 +140,50 @@ open class VideoCalleeFragment : BaseP2pCallFragment() {
}
}
- override fun onCreateAction() {
- val dialog = if (context?.isGranted(RECORD_AUDIO, CAMERA) == true) {
- return
- } else {
- bridge.showPermissionDialog {
- bridge.doHangup()
+ override fun actionForPermissionGranted() {
+ if (isEnableVideoCalleePreview()) {
+ getView(viewKeyVideoViewPreview)?.run {
+ bridge.setupLocalView(this)
}
+ bridge.startVideoPreview()
}
- requestPermission(
- onGranted = {
- if (it.containsAll(listOf(RECORD_AUDIO, CAMERA))) {
- dialog.dismiss()
- } else {
- context?.run {
- getString(R.string.tip_permission_request_failed).toastShort(this)
- }
- }
- },
- onDenied = { _, _ ->
- context?.run { getString(R.string.tip_permission_request_failed).toastShort(this) }
- },
- RECORD_AUDIO,
- CAMERA
- )
+ }
+
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO, CAMERA)
}
override fun toUpdateUIState(type: Int) {
bridge.doConfigSpeaker(true)
}
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == SwitchCallState.REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
+ override fun onHiddenChanged(hidden: Boolean) {
+ if (isEnableVideoCalleePreview()) {
+ if (hidden) {
+ getView(viewKeyVideoViewPreview)?.run {
+ visibility = View.GONE
+ }
+ bridge.stopVideoPreview()
+ } else {
+ getView(viewKeyVideoViewPreview)?.run {
+ visibility = View.VISIBLE
+ }
+ bridge.startVideoPreview()
}
}
}
+
+ override fun onCallEnd(info: NECallEndInfo) {
+ if (isEnableVideoCalleePreview()) {
+ bridge.stopVideoPreview()
+ }
+ }
+
+ protected open fun isEnableVideoCalleePreview(): Boolean {
+ return (
+ CallKitUI.options?.initRtcMode == NECallInitRtcMode.GLOBAL ||
+ CallKitUI.options?.initRtcMode == NECallInitRtcMode.IN_NEED
+ ) &&
+ bridge.uiConfig?.enableVideoCalleePreview == true
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/AudioCallerFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/AudioCallerFragment.kt
index 7b86d80..6facebd 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/AudioCallerFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/AudioCallerFragment.kt
@@ -15,12 +15,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.netease.nimlib.sdk.ResponseCode
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.CallKitUI
@@ -31,9 +26,7 @@ import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
import com.netease.yunxin.nertc.ui.databinding.FragmentP2pAudioCallerBinding
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.FROM_FLOATING_WINDOW
import com.netease.yunxin.nertc.ui.utils.toastShort
/**
@@ -45,22 +38,6 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
protected lateinit var binding: FragmentP2pAudioCallerBinding
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run { getString(R.string.tip_switch_call_type_failed).toastShort(this) }
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.VIDEO} error, result is $result."
- )
- return
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
- }
- }
- }
-
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? = FragmentP2pAudioCallerBinding.inflate(inflater, container, false).run {
@@ -90,6 +67,7 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
bindView(viewKeyTextMuteAudioDesc, binding.tvCallMuteAudioTip)
bindView(viewKeyImageSpeaker, binding.ivCallSpeaker)
bindView(viewKeyTextSpeakerDesc, binding.tvCallSpeakerTip)
+ bindView(viewKeyImageFloatingWindow, binding.ivFloatingWindow)
}
override fun toRenderView(callParam: CallParam, uiConfig: P2PUIConfig?) {
@@ -115,7 +93,7 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
+ getView(viewKeyImageCancel)?.bindClick(viewKeyImageCancel) {
bridge.doHangup()
activity?.finish()
}
@@ -126,43 +104,32 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
}
getView(viewKeyImageSwitchType)?.run {
visibility = if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
+ bindClick(viewKeyImageSwitchType) {
+ if (!NetworkUtils.isConnected()) {
+ context?.run { getString(R.string.tip_network_error).toastShort(this) }
+ return@bindClick
}
val action = Action@{
- if (!NetworkUtils.isConnected()) {
- context?.run { getString(R.string.tip_network_error).toastShort(this) }
- return@Action
- }
- bridge.doSwitchCallType(
- NECallType.VIDEO,
- SwitchCallState.INVITE,
- switchObserver
- )
+ switchCallType(NECallType.VIDEO)
}
- if (context?.isGranted(CAMERA) == true) {
+ if (arePermissionsGranted(listOf(CAMERA))) {
action.invoke()
- return@setOnClickListener
+ return@bindClick
}
- requestPermission(
- onGranted = {
- action.invoke()
- },
- onDenied = { _, _ ->
- context?.run {
- getString(R.string.tip_permission_request_failed).toastShort(this)
- }
- },
- CAMERA
- )
+ requestPermission(listOf(CAMERA), {
+ action.invoke()
+ }, { _, _ ->
+ context?.run {
+ getString(R.string.tip_permission_request_failed).toastShort(this)
+ }
+ })
}
}
getView(viewKeyTextSwitchTypeDesc)?.visibility =
if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
getView(viewKeyMuteImageAudio)?.run {
- setOnClickListener {
+ bindClick(viewKeyMuteImageAudio) {
bridge.doMuteAudio()
setImageResource(
if (bridge.isLocalMuteAudio) R.drawable.icon_call_audio_off else R.drawable.icon_call_audio_on
@@ -171,7 +138,7 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
}
getView(viewKeyImageSpeaker)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSpeaker) {
val speakerEnable = !bridge.isSpeakerOn()
bridge.doConfigSpeaker(speakerEnable)
setImageResource(
@@ -180,64 +147,60 @@ open class AudioCallerFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
}
}
- }
-
- override fun onCreateAction() {
- val action = {
- if (bridge.currentCallState() == CallState.STATE_IDLE) {
- bridge.doCall { result ->
- if (result?.isSuccessful != true &&
- result.code != ResponseCode.RES_PEER_NIM_OFFLINE.toInt() &&
- result.code != ResponseCode.RES_PEER_PUSH_OFFLINE.toInt()
- ) {
- context?.run { getString(R.string.tip_start_call_failed).toastShort(this) }
- }
- }
+ getView(viewKeyImageFloatingWindow)?.run {
+ visibility = if (uiConfig?.enableFloatingWindow == true) View.VISIBLE else View.GONE
+ bindClick(viewKeyImageFloatingWindow) {
+ bridge.showFloatingWindow()
}
}
+ }
- val dialog = if (context?.isGranted(RECORD_AUDIO) == true) {
- action.invoke()
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO)
+ }
+
+ override fun actionForPermissionGranted() {
+ if (bridge.currentCallState() != CallState.STATE_IDLE) {
return
- } else {
- bridge.showPermissionDialog {
- activity?.finish()
+ }
+ bridge.doCall { result ->
+ if (result?.isSuccessful != true &&
+ result.code != ResponseCode.RES_PEER_NIM_OFFLINE.toInt() &&
+ result.code != ResponseCode.RES_PEER_PUSH_OFFLINE.toInt()
+ ) {
+ context?.run { getString(R.string.tip_start_call_failed).toastShort(this) }
}
}
-
- requestPermission(
- onGranted = {
- dialog.dismiss()
- action.invoke()
- },
- onDenied = { _, _ ->
- context?.run {
- getString(R.string.tip_permission_request_failed).toastShort(this)
- }
- },
- RECORD_AUDIO
- )
}
override fun toUpdateUIState(type: Int) {
- bridge.doConfigSpeaker(false)
- getView(viewKeyImageSpeaker)?.run {
- setImageResource(
- if (bridge.isSpeakerOn()) R.drawable.icon_call_audio_speaker_on else R.drawable.icon_call_audio_speaker_off
- )
- }
- }
+ when (type) {
+ FROM_FLOATING_WINDOW -> {
+ getView(viewKeyImageSpeaker)?.run {
+ setImageResource(
+ if (bridge.isSpeakerOn()) R.drawable.icon_call_audio_speaker_on else R.drawable.icon_call_audio_speaker_off
+ )
+ }
+ getView(viewKeyMuteImageAudio)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteAudio) R.drawable.icon_call_audio_off else R.drawable.icon_call_audio_on
+ )
+ }
+ }
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == SwitchCallState.REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
+ else -> {
+ bridge.doConfigSpeaker(false)
+ getView(viewKeyImageSpeaker)?.run {
+ setImageResource(
+ if (bridge.isSpeakerOn()) R.drawable.icon_call_audio_speaker_on else R.drawable.icon_call_audio_speaker_off
+ )
+ }
}
}
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/VideoCallerFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/VideoCallerFragment.kt
index 1e23398..a1a20ca 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/VideoCallerFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/caller/VideoCallerFragment.kt
@@ -14,18 +14,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-import com.netease.lava.nertc.sdk.NERtcConstants.ErrorCode.ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED
-import com.netease.lava.nertc.sdk.NERtcConstants.ErrorCode.OK
-import com.netease.lava.nertc.sdk.NERtcEx
import com.netease.lava.nertc.sdk.video.NERtcVideoView
import com.netease.nimlib.sdk.ResponseCode
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.CallKitUI
@@ -36,9 +28,6 @@ import com.netease.yunxin.nertc.ui.base.loadAvatarByAccId
import com.netease.yunxin.nertc.ui.databinding.FragmentP2pVideoCallerBinding
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
import com.netease.yunxin.nertc.ui.utils.toastShort
/**
@@ -47,26 +36,8 @@ import com.netease.yunxin.nertc.ui.utils.toastShort
open class VideoCallerFragment : BaseP2pCallFragment() {
protected val logTag = "VideoCallerFragment"
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run { getString(R.string.tip_switch_call_type_failed).toastShort(this) }
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.AUDIO} error, result is $result."
- )
- return
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
- }
- }
- }
-
protected lateinit var binding: FragmentP2pVideoCallerBinding
- protected var startPreviewCode: Int? = null
-
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? = FragmentP2pVideoCallerBinding.inflate(inflater, container, false).run {
@@ -92,6 +63,7 @@ open class VideoCallerFragment : BaseP2pCallFragment() {
bindView(viewKeyTextSwitchTip, binding.tvSwitchTip)
bindView(viewKeyImageSwitchTipClose, binding.ivSwitchTipClose)
bindView(viewKeySwitchTypeTipGroup, binding.switchTypeTipGroup)
+ bindView(viewKeyImageFloatingWindow, binding.ivFloatingWindow)
}
override fun toRenderView(callParam: CallParam, uiConfig: P2PUIConfig?) {
@@ -117,7 +89,7 @@ open class VideoCallerFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
+ getView(viewKeyImageCancel)?.bindClick(viewKeyImageCancel) {
bridge.doHangup()
activity?.finish()
}
@@ -128,103 +100,71 @@ open class VideoCallerFragment : BaseP2pCallFragment() {
}
getView(viewKeyImageSwitchType)?.run {
visibility = if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
- }
+ bindClick(viewKeyImageSwitchType) {
if (!NetworkUtils.isConnected()) {
context?.run { getString(R.string.tip_network_error).toastShort(this) }
- return@setOnClickListener
+ return@bindClick
}
- bridge.doSwitchCallType(NECallType.AUDIO, SwitchCallState.INVITE, switchObserver)
+ switchCallType(NECallType.AUDIO)
}
}
getView(viewKeyTextSwitchTypeDesc)?.visibility =
if (enableAutoJoinWhenCalled) View.VISIBLE else View.GONE
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
}
}
+ getView(viewKeyImageFloatingWindow)?.run {
+ visibility = if (uiConfig?.enableFloatingWindow == true) View.VISIBLE else View.GONE
+ bindClick(viewKeyImageFloatingWindow) {
+ bridge.showFloatingWindow()
+ }
+ }
+ }
+
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO, CAMERA)
}
- override fun onCreateAction() {
- val action = {
- if (bridge.currentCallState() == CallState.STATE_IDLE) {
- bridge.doCall { result ->
- if (result?.isSuccessful != true &&
- result.code != ResponseCode.RES_PEER_NIM_OFFLINE.toInt() &&
- result.code != ResponseCode.RES_PEER_PUSH_OFFLINE.toInt()
- ) {
- context?.run { getString(R.string.tip_start_call_failed).toastShort(this) }
- }
+ override fun actionForPermissionGranted() {
+ if (bridge.currentCallState() == CallState.STATE_IDLE) {
+ bridge.doCall { result ->
+ if (result?.isSuccessful != true &&
+ result.code != ResponseCode.RES_PEER_NIM_OFFLINE.toInt() &&
+ result.code != ResponseCode.RES_PEER_PUSH_OFFLINE.toInt()
+ ) {
+ context?.run { getString(R.string.tip_start_call_failed).toastShort(this) }
}
}
- getView(viewKeyVideoViewPreview)?.run {
- bridge.setupLocalView(this)
- }
- if (CallKitUI.options?.joinRtcWhenCall == false &&
- startPreviewCode != OK &&
- startPreviewCode != ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED
- ) {
- startPreviewCode = startVideoPreview()
- }
}
-
- val dialog = if (context?.isGranted(RECORD_AUDIO, CAMERA) == true) {
- action.invoke()
- return
- } else {
- bridge.showPermissionDialog {
- activity?.finish()
- }
+ getView(viewKeyVideoViewPreview)?.run {
+ bridge.setupLocalView(this)
}
-
- requestPermission(
- onGranted = {
- if (it.containsAll(listOf(RECORD_AUDIO, CAMERA))) {
- dialog.dismiss()
- action.invoke()
- } else {
- context?.run {
- getString(R.string.tip_permission_request_failed).toastShort(this)
- }
- }
- },
- onDenied = { _, _ ->
- context?.run { getString(R.string.tip_permission_request_failed).toastShort(this) }
- },
- RECORD_AUDIO,
- CAMERA
- )
+ bridge.startVideoPreview()
}
override fun toUpdateUIState(type: Int) {
bridge.doConfigSpeaker(true)
}
- override fun onCallEnd(info: NECallEndInfo) {
- if (startPreviewCode == OK || startPreviewCode == ENGINE_ERROR_DEVICE_PREVIEW_ALREADY_STARTED) {
- stopVideoPreview()
- startPreviewCode = null
- }
- }
-
- protected open fun startVideoPreview(): Int = NERtcEx.getInstance().startVideoPreview().apply {
- ALog.d(logTag, "startVideoPreview is $this, this fragment is ${this@VideoCallerFragment}")
- }
-
- protected open fun stopVideoPreview(): Int = NERtcEx.getInstance().stopVideoPreview().apply {
- ALog.d(logTag, "stopVideoPreview is $this, this fragment is ${this@VideoCallerFragment}")
- }
-
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == SwitchCallState.REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
+ override fun onHiddenChanged(hidden: Boolean) {
+ if (hidden) {
+ getView(viewKeyVideoViewPreview)?.run {
visibility = View.GONE
}
+ bridge.stopVideoPreview()
+ } else {
+ getView(viewKeyVideoViewPreview)?.run {
+ visibility = View.VISIBLE
+ }
+ bridge.startVideoPreview()
}
}
+
+ override fun onCallEnd(info: NECallEndInfo) {
+ bridge.stopVideoPreview()
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/AudioOnTheCallFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/AudioOnTheCallFragment.kt
index b92f6ac..6c6e299 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/AudioOnTheCallFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/AudioOnTheCallFragment.kt
@@ -6,21 +6,16 @@
package com.netease.yunxin.nertc.ui.p2p.fragment.onthecall
-import android.Manifest
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.RECORD_AUDIO
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState.REJECT
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.R
@@ -32,11 +27,9 @@ import com.netease.yunxin.nertc.ui.p2p.CallUIOperationsMgr
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.CHANGE_CALL_TYPE
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.FROM_FLOATING_WINDOW
import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.INIT
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
import com.netease.yunxin.nertc.ui.utils.formatSecondTime
-import com.netease.yunxin.nertc.ui.utils.isGranted
-import com.netease.yunxin.nertc.ui.utils.requestPermission
import com.netease.yunxin.nertc.ui.utils.toastShort
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -51,24 +44,6 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
protected lateinit var binding: FragmentP2pAudioOnTheCallBinding
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run {
- getString(R.string.tip_switch_call_type_failed).toastShort(this)
- }
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.VIDEO} error, result is $result."
- )
- return
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
- }
- }
- }
-
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = FragmentP2pAudioOnTheCallBinding.inflate(inflater, container, false).run {
@@ -95,6 +70,7 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
bindView(viewKeyTextSwitchTip, binding.tvSwitchTip)
bindView(viewKeyImageSwitchTipClose, binding.ivSwitchTipClose)
bindView(viewKeySwitchTypeTipGroup, binding.switchTypeTipGroup)
+ bindView(viewKeyImageFloatingWindow, binding.ivFloatingWindow)
}
override fun toRenderView(callParam: CallParam, uiConfig: P2PUIConfig?) {
@@ -120,38 +96,25 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
- bridge.doHangup()
- }
-
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
- }
getView(viewKeyImageSwitchType)?.run {
visibility =
if (uiConfig?.showAudio2VideoSwitchOnTheCall == true) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
+ bindClick(viewKeyImageSwitchType) {
+ if (!NetworkUtils.isConnected()) {
+ context?.run {
+ getString(R.string.tip_network_error).toastShort(this)
+ }
+ return@bindClick
}
val action = Action@{
- if (!NetworkUtils.isConnected()) {
- context?.run {
- getString(R.string.tip_network_error).toastShort(this)
- }
- return@Action
- }
- bridge.doSwitchCallType(
- NECallType.VIDEO,
- SwitchCallState.INVITE,
- switchObserver
- )
+ switchCallType(NECallType.VIDEO)
}
- if (context?.isGranted(Manifest.permission.CAMERA) == true) {
+ if (arePermissionsGranted(listOf(CAMERA))) {
action.invoke()
- return@setOnClickListener
+ return@bindClick
}
requestPermission(
+ listOf(CAMERA),
onGranted = {
action.invoke()
},
@@ -159,13 +122,12 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
context?.run {
getString(R.string.tip_permission_request_failed).toastShort(this)
}
- },
- Manifest.permission.CAMERA
+ }
)
}
}
getView(viewKeyMuteImageAudio)?.run {
- setOnClickListener {
+ bindClick(viewKeyMuteImageAudio) {
bridge.doMuteAudio()
setImageResource(
if (bridge.isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
@@ -173,7 +135,7 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageSpeaker)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSpeaker) {
val speakerEnable = !bridge.isSpeakerOn()
bridge.doConfigSpeaker(speakerEnable)
setImageResource(
@@ -182,27 +144,27 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageHangup)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageHangup) {
bridge.doHangup()
}
}
-
- getView(viewKeyTextTimeCountdown)?.run {
- bridge.configTimeTick(
- CallUIOperationsMgr.TimeTickConfig({
- CoroutineScope(Dispatchers.Main).launch {
- this@run.text = it.formatSecondTime()
- }
- })
- )
- }
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
}
}
+ getView(viewKeyImageFloatingWindow)?.run {
+ visibility = if (uiConfig?.enableFloatingWindow == true) View.VISIBLE else View.GONE
+ bindClick(viewKeyImageFloatingWindow) {
+ bridge.showFloatingWindow()
+ }
+ }
+ }
+
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO)
}
override fun onCreateAction() {
@@ -241,14 +203,34 @@ open class AudioOnTheCallFragment : BaseP2pCallFragment() {
setImageResource(R.drawable.voice_on)
}
}
+ FROM_FLOATING_WINDOW -> {
+ getView(viewKeyImageSpeaker)?.run {
+ setImageResource(
+ if (bridge.isSpeakerOn()) R.drawable.speaker_on else R.drawable.speaker_off
+ )
+ }
+ getView(viewKeyMuteImageAudio)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
+ )
+ }
+ }
}
+ toInitState()
}
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
- }
+ protected open fun toInitState() {
+ getView(viewKeyTextTimeCountdown)?.run {
+ bridge.configTimeTick(
+ CallUIOperationsMgr.TimeTickConfig({
+ CoroutineScope(Dispatchers.Main).launch {
+ this@run.text = it.formatSecondTime()
+ }
+ })
+ )
+ }
+ getView(viewKeySwitchTypeTipGroup)?.run {
+ visibility = View.GONE
}
}
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/VideoOnTheCallFragment.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/VideoOnTheCallFragment.kt
index c71cc5a..662e198 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/VideoOnTheCallFragment.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/p2p/fragment/onthecall/VideoOnTheCallFragment.kt
@@ -6,6 +6,8 @@
package com.netease.yunxin.nertc.ui.p2p.fragment.onthecall
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.RECORD_AUDIO
import android.graphics.Color
import android.os.Bundle
import android.text.TextUtils
@@ -14,15 +16,16 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import android.widget.Toast
import com.bumptech.glide.Glide
+import com.netease.lava.api.IVideoRender
+import com.netease.lava.nertc.sdk.NERtcConstants
import com.netease.lava.nertc.sdk.video.NERtcVideoView
import com.netease.yunxin.kit.alog.ALog
-import com.netease.yunxin.kit.call.NEResultObserver
import com.netease.yunxin.kit.call.p2p.model.NECallEndInfo
import com.netease.yunxin.kit.call.p2p.model.NECallType
-import com.netease.yunxin.kit.call.p2p.model.NECallTypeChangeInfo
-import com.netease.yunxin.nertc.nertcvideocall.bean.CommonResult
-import com.netease.yunxin.nertc.nertcvideocall.model.SwitchCallState
+import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackExTemp
+import com.netease.yunxin.nertc.nertcvideocall.model.impl.NERtcCallbackProxyMgr
import com.netease.yunxin.nertc.nertcvideocall.model.impl.state.CallState
import com.netease.yunxin.nertc.nertcvideocall.utils.NetworkUtils
import com.netease.yunxin.nertc.ui.R
@@ -33,7 +36,9 @@ import com.netease.yunxin.nertc.ui.databinding.FragmentP2pVideoOnTheCallBinding
import com.netease.yunxin.nertc.ui.p2p.CallUIOperationsMgr
import com.netease.yunxin.nertc.ui.p2p.P2PUIConfig
import com.netease.yunxin.nertc.ui.p2p.fragment.BaseP2pCallFragment
-import com.netease.yunxin.nertc.ui.utils.ClickUtils
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.CHANGE_CALL_TYPE
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.FROM_FLOATING_WINDOW
+import com.netease.yunxin.nertc.ui.p2p.fragment.P2PUIUpdateType.INIT
import com.netease.yunxin.nertc.ui.utils.formatSecondTime
import com.netease.yunxin.nertc.ui.utils.toastShort
import kotlinx.coroutines.CoroutineScope
@@ -47,27 +52,33 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
protected val logTag = "VideoOnTheCallFragment"
- protected val switchObserver = object : NEResultObserver> {
- override fun onResult(result: CommonResult?) {
- if (result?.isSuccessful != true) {
- context?.run {
- getString(R.string.tip_switch_call_type_failed).toastShort(this)
+ protected lateinit var binding: FragmentP2pVideoOnTheCallBinding
+
+ protected open val rtcCallback = object : NERtcCallbackExTemp() {
+ override fun onVirtualBackgroundSourceEnabled(enabled: Boolean, reason: Int) {
+ var tipRes: Int? = null
+ when (reason) {
+ NERtcConstants.NERtcVirtualBackgroundSourceStateReason.VBS_STATE_REASON_SUCCESS -> {
+ tipRes = R.string.ui_tip_virtual_blur_success
+ }
+
+ NERtcConstants.NERtcVirtualBackgroundSourceStateReason.VBS_STATE_REASON_DEVICE_NOT_SUPPORTED -> {
+ tipRes = R.string.ui_tip_virtual_blur_device_not_supported
+ getView(viewKeyImageVirtualBlur)?.run {
+ setImageResource(R.drawable.icon_call_virtual_blur_off)
+ }
+ CallUIOperationsMgr.updateUIState(isVirtualBlur = false)
}
- ALog.e(
- logTag,
- "doSwitchCallType to ${NECallType.AUDIO} error, result is $result."
- )
- return
}
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.VISIBLE
+
+ tipRes ?: return
+ context?.run {
+ Toast.makeText(this, tipRes, Toast.LENGTH_SHORT).show()
}
}
}
- protected lateinit var binding: FragmentP2pVideoOnTheCallBinding
-
- protected var localIsSmallVideo = true
+ private var permissionAllowed = false
override fun toCreateRootView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@@ -95,6 +106,8 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
bindView(viewKeyTextSwitchTip, binding.tvSwitchTip)
bindView(viewKeyImageSwitchTipClose, binding.ivSwitchTipClose)
bindView(viewKeySwitchTypeTipGroup, binding.switchTypeTipGroup)
+ bindView(viewKeyImageFloatingWindow, binding.ivFloatingWindow)
+ bindView(viewKeyImageVirtualBlur, binding.ivVirtualBlur)
}
override fun toRenderView(callParam: CallParam, uiConfig: P2PUIConfig?) {
@@ -120,39 +133,27 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
}
protected open fun renderOperations(uiConfig: P2PUIConfig?) {
- getView(viewKeyImageCancel)?.setOnClickListener {
- bridge.doHangup()
- }
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
- }
getView(viewKeyImageSwitchType)?.run {
visibility =
if (uiConfig?.showVideo2AudioSwitchOnTheCall == true) View.VISIBLE else View.GONE
- setOnClickListener {
- if (ClickUtils.isFastClick()) {
- return@setOnClickListener
- }
+ bindClick(viewKeyImageSwitchType) {
if (!NetworkUtils.isConnected()) {
context?.run { getString(R.string.tip_network_error).toastShort(this) }
- return@setOnClickListener
+ return@bindClick
}
- bridge.doSwitchCallType(NECallType.AUDIO, SwitchCallState.INVITE, switchObserver)
+ switchCallType(NECallType.AUDIO)
}
}
getView(viewKeyMuteImageAudio)?.run {
- setOnClickListener {
+ bindClick(viewKeyMuteImageAudio) {
bridge.doMuteAudio()
setImageResource(
if (bridge.isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
)
}
}
- getView(viewKeyImageVideoShadeSmall)?.run {
- setBackgroundColor(Color.BLACK)
- }
getView(viewKeyMuteImageVideo)?.run {
- setOnClickListener {
+ bindClick(viewKeyMuteImageVideo) {
bridge.doMuteVideo()
setImageResource(
if (bridge.isLocalMuteVideo) R.drawable.cam_off else R.drawable.cam_on
@@ -161,7 +162,7 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageSpeaker)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSpeaker) {
val speakerEnable = !bridge.isSpeakerOn()
bridge.doConfigSpeaker(speakerEnable)
setImageResource(
@@ -170,64 +171,82 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
}
}
getView(viewKeyImageHangup)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageHangup) {
bridge.doHangup()
}
}
getView(viewKeyImageSwitchCamera)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchCamera) {
bridge.doSwitchCamera()
}
}
- getView(viewKeyTextTimeCountdown)?.run {
- bridge.configTimeTick(
- CallUIOperationsMgr.TimeTickConfig({
- CoroutineScope(Dispatchers.Main).launch {
- this@run.text = it.formatSecondTime()
- }
- })
- )
- }
- getView(viewKeyVideoViewBig)?.run {
- bridge.setupRemoteView(this)
- }
getView(viewKeyVideoViewSmall)?.run {
- bridge.setupLocalView(this)
if (uiConfig?.enableCanvasSwitch == true) {
- setOnClickListener {
+ bindClick(viewKeyVideoViewSmall) {
doSwitchCanvas()
}
}
}
getView(viewKeyImageSwitchTipClose)?.run {
- setOnClickListener {
+ bindClick(viewKeyImageSwitchTipClose) {
getView(viewKeySwitchTypeTipGroup)?.run {
visibility = View.GONE
}
}
}
+ getView(viewKeyImageFloatingWindow)?.run {
+ visibility = if (uiConfig?.enableFloatingWindow == true) View.VISIBLE else View.GONE
+ bindClick(viewKeyImageFloatingWindow) {
+ bridge.showFloatingWindow()
+ }
+ }
+ getView(viewKeyImageVirtualBlur)?.run {
+ visibility = if (uiConfig?.enableVirtualBlur == true) View.VISIBLE else View.GONE
+ bindClick(viewKeyImageVirtualBlur) {
+ bridge.doVirtualBlur()
+ setImageResource(
+ if (bridge.isVirtualBlur) R.drawable.icon_call_virtual_blur_on else R.drawable.icon_call_virtual_blur_off
+ )
+ }
+ }
+ }
+
+ override fun permissionList(): List {
+ return listOf(RECORD_AUDIO, CAMERA)
+ }
+
+ override fun actionForPermissionGranted() {
+ if (!permissionAllowed) {
+ bridge.callEngine.enableLocalVideo(true)
+ }
+ }
+
+ override fun onPermissionRequest() {
+ permissionAllowed = arePermissionsGranted()
+ super.onPermissionRequest()
}
override fun onCreateAction() {
+ NERtcCallbackProxyMgr.getInstance().addCallback(rtcCallback)
if (bridge.currentCallState() == CallState.STATE_IDLE) {
bridge.doCall()
}
}
+ override fun onDestroyAction() {
+ super.onDestroyAction()
+ NERtcCallbackProxyMgr.getInstance().removeCallback(rtcCallback)
+ }
+
override fun onCallEnd(info: NECallEndInfo) {
bridge.configTimeTick(null)
}
override fun toUpdateUIState(type: Int) {
- bridge.doConfigSpeaker(true)
- bridge.doMuteAudio(false)
- bridge.doMuteVideo(false)
- }
-
- override fun onCallTypeChange(info: NECallTypeChangeInfo) {
- if (info.state == SwitchCallState.REJECT) {
- getView(viewKeySwitchTypeTipGroup)?.run {
- visibility = View.GONE
+ when (type) {
+ INIT, CHANGE_CALL_TYPE -> toInitState()
+ FROM_FLOATING_WINDOW -> {
+ toFromFloatingWindowState()
}
}
}
@@ -237,6 +256,11 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
updateCloseVideoTipUI(userId, mute)
}
+ override fun onVideoAvailable(userId: String?, available: Boolean) {
+ userId ?: return
+ updateCloseVideoTipUI(userId, !available)
+ }
+
protected open fun doSwitchCanvas() {
val videoViewSmall = getView(viewKeyVideoViewSmall)
val videoViewBig = getView(viewKeyVideoViewBig)
@@ -244,14 +268,23 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
videoViewBig?.clearImage()
videoViewSmall?.clearImage()
}
- if (localIsSmallVideo) {
- bridge.setupRemoteView(videoViewSmall)
- bridge.setupLocalView(videoViewBig)
+ if (bridge.isLocalSmallVideo) {
+ bridge.setupRemoteView(videoViewSmall, action = {
+ it?.run {
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ })
+ bridge.setupLocalView(videoViewBig, action = {
+ it?.run {
+ setZOrderMediaOverlay(true)
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ })
} else {
bridge.setupRemoteView(videoViewBig)
bridge.setupLocalView(videoViewSmall)
}
- localIsSmallVideo = !localIsSmallVideo
+ CallUIOperationsMgr.updateUIState(isLocalSmallVideo = !bridge.isLocalSmallVideo)
updateCloseVideoTipUI(bridge.callParam.currentAccId, muteVideo = bridge.isLocalMuteVideo)
updateCloseVideoTipUI(bridge.callParam.otherAccId, muteVideo = bridge.isRemoteMuteVideo)
@@ -262,7 +295,7 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
val ivBigVideoShade = getView(viewKeyImageVideoShadeBig)
val tvRemoteVideoCloseTip = getView(viewKeyTextRemoteVideoCloseTip)
- if (localIsSmallVideo) {
+ if (bridge.isLocalSmallVideo) {
if (TextUtils.equals(userAccId, bridge.callParam.currentAccId)) {
muteVideo?.run {
loadImg(bridge.uiConfig?.closeVideoLocalUrl, ivSmallVideoShade)
@@ -324,4 +357,113 @@ open class VideoOnTheCallFragment : BaseP2pCallFragment() {
.centerCrop()
.into(imageView)
}
+
+ protected open fun toInitState() {
+ getView(viewKeySwitchTypeTipGroup)?.run {
+ visibility = View.GONE
+ }
+ getView(viewKeyImageVideoShadeSmall)?.run {
+ setBackgroundColor(Color.BLACK)
+ }
+ getView(viewKeyTextTimeCountdown)?.run {
+ bridge.configTimeTick(
+ CallUIOperationsMgr.TimeTickConfig({
+ CoroutineScope(Dispatchers.Main).launch {
+ this@run.text = it.formatSecondTime()
+ }
+ })
+ )
+ }
+ bridge.doVirtualBlur(false)
+ getView(viewKeyImageVirtualBlur)?.run {
+ setImageResource(
+ if (bridge.isVirtualBlur) R.drawable.icon_call_virtual_blur_on else R.drawable.icon_call_virtual_blur_off
+ )
+ }
+ bridge.doConfigSpeaker(true)
+ getView(viewKeyImageSpeaker)?.run {
+ setImageResource(
+ if (bridge.isSpeakerOn()) R.drawable.speaker_on else R.drawable.speaker_off
+ )
+ }
+ bridge.doMuteAudio(false)
+ getView(viewKeyMuteImageAudio)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
+ )
+ }
+ bridge.doMuteVideo(false)
+ getView(viewKeyMuteImageVideo)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteVideo) R.drawable.cam_off else R.drawable.cam_on
+ )
+ updateCloseVideoTipUI(bridge.callParam.currentAccId, bridge.isLocalMuteVideo)
+ }
+ updateCloseVideoTipUI(bridge.callParam.otherAccId, muteVideo = false)
+
+ getView(viewKeyVideoViewBig)?.run {
+ bridge.setupRemoteView(this)
+ }
+ getView(viewKeyVideoViewSmall)?.run {
+ bridge.setupLocalView(this)
+ }
+ }
+
+ protected open fun toFromFloatingWindowState() {
+ getView(viewKeyTextTimeCountdown)?.run {
+ bridge.configTimeTick(
+ CallUIOperationsMgr.TimeTickConfig({
+ CoroutineScope(Dispatchers.Main).launch {
+ this@run.text = it.formatSecondTime()
+ }
+ })
+ )
+ }
+ getView(viewKeyImageVirtualBlur)?.run {
+ setImageResource(
+ if (bridge.isVirtualBlur) R.drawable.icon_call_virtual_blur_on else R.drawable.icon_call_virtual_blur_off
+ )
+ }
+ getView(viewKeyImageSpeaker)?.run {
+ setImageResource(
+ if (bridge.isSpeakerOn()) R.drawable.speaker_on else R.drawable.speaker_off
+ )
+ }
+ getView(viewKeyMuteImageAudio)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteAudio) R.drawable.voice_off else R.drawable.voice_on
+ )
+ }
+ getView(viewKeyMuteImageVideo)?.run {
+ setImageResource(
+ if (bridge.isLocalMuteVideo) R.drawable.cam_off else R.drawable.cam_on
+ )
+ }
+ if (bridge.isLocalSmallVideo) {
+ getView(viewKeyVideoViewBig)?.run {
+ bridge.setupRemoteView(this)
+ }
+ getView(viewKeyVideoViewSmall)?.run {
+ bridge.setupLocalView(this)
+ }
+ } else {
+ getView(viewKeyVideoViewBig)?.run {
+ bridge.setupLocalView(this, action = {
+ it?.run {
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ })
+ }
+ getView(viewKeyVideoViewSmall)?.run {
+ bridge.setupRemoteView(this, action = {
+ it?.run {
+ setZOrderMediaOverlay(true)
+ setScalingType(IVideoRender.ScalingType.SCALE_ASPECT_BALANCED)
+ }
+ })
+ }
+ }
+ updateCloseVideoTipUI(bridge.callParam.currentAccId, muteVideo = bridge.isLocalMuteVideo)
+ updateCloseVideoTipUI(bridge.callParam.otherAccId, muteVideo = bridge.isRemoteMuteVideo)
+ }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/service/CallKitUIBridgeService.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/service/CallKitUIBridgeService.kt
index 2adda7c..3042b24 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/service/CallKitUIBridgeService.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/service/CallKitUIBridgeService.kt
@@ -99,7 +99,7 @@ open class CallKitUIBridgeService @JvmOverloads constructor(
/**
* 点对点本地行为监听
*/
- private val localActionObserver = NECallLocalActionObserver { actionId, resultCode ->
+ private val localActionObserver = NECallLocalActionObserver { actionId, resultCode, _ ->
this@CallKitUIBridgeService.onLocalAction(actionId, resultCode)
}
@@ -214,6 +214,7 @@ open class CallKitUIBridgeService @JvmOverloads constructor(
AVChatSoundPlayer.play(context, AVChatSoundPlayer.RingerTypeEnum.CONNECTING)
} else if (canStopAudioPlay && callerAccId != null &&
actionId != CallLocalAction.ACTION_BEFORE_RESET &&
+ actionId != CallLocalAction.ACTION_SWITCH &&
(resultCode == CallErrorCode.SUCCESS || resultCode == ResponseCode.RES_SUCCESS.toInt())
) {
AVChatSoundPlayer.stop(context)
@@ -229,7 +230,7 @@ open class CallKitUIBridgeService @JvmOverloads constructor(
* 点对点通话行为监听
*/
open fun onReceiveInvited(info: NEInviteInfo) {
- ALog.dApi(logTag, ParameterMap("onInvited").append("info", info))
+ ALog.dApi(logTag, ParameterMap("onReceiveInvited").append("info", info))
// 检查参数合理性
if (!isValidParam(info)) {
ALog.d(logTag, "onIncomingCall, param is invalid.")
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/AppForegroundWatcherHelper.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/AppForegroundWatcherHelper.kt
index f44989e..3298a6e 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/AppForegroundWatcherHelper.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/AppForegroundWatcherHelper.kt
@@ -10,8 +10,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
+import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.kit.alog.ParameterMap
object AppForegroundWatcherHelper {
+ private const val TAG = "AppForegroundWatcherHelper"
private val watchers = mutableListOf()
private var background = true
@@ -19,6 +22,7 @@ object AppForegroundWatcherHelper {
private val callServiceObserver = object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onForeground() {
+ ALog.dApi(TAG, ParameterMap("onForeground"))
background = false
watchers.forEach {
it.onForeground()
@@ -27,6 +31,7 @@ object AppForegroundWatcherHelper {
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onBackground() {
+ ALog.dApi(TAG, ParameterMap("onBackground"))
background = true
watchers.forEach {
it.onBackground()
@@ -41,6 +46,7 @@ object AppForegroundWatcherHelper {
fun isBackground() = background
fun addWatcher(watcher: Watcher): Boolean {
+ ALog.dApi(TAG, ParameterMap("addWatcher").append("watcher", watcher))
if (watchers.contains(watcher)) {
return false
}
@@ -48,6 +54,7 @@ object AppForegroundWatcherHelper {
}
fun removeWatcher(watcher: Watcher): Boolean {
+ ALog.dApi(TAG, ParameterMap("removeWatcher").append("watcher", watcher))
return watchers.remove(watcher)
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/FormatSecondTime.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/FormatSecondTime.kt
index 4b0ad2c..02f9e30 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/FormatSecondTime.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/FormatSecondTime.kt
@@ -6,7 +6,7 @@
package com.netease.yunxin.nertc.ui.utils
-import java.util.*
+import java.util.Locale
fun Long.formatSecondTime(): String {
if (this <= 0) {
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionExtends.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionExtends.kt
index ea2620c..97f10e0 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionExtends.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionExtends.kt
@@ -6,22 +6,32 @@
package com.netease.yunxin.nertc.ui.utils
+import android.app.Activity
import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
+import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.netease.yunxin.kit.alog.ALog
+import com.netease.yunxin.nertc.ui.base.KEY_PERMISSION_RESULT_DENIED
+import com.netease.yunxin.nertc.ui.base.KEY_PERMISSION_RESULT_DENIED_FOREVER
+import com.netease.yunxin.nertc.ui.base.KEY_PERMISSION_RESULT_GRANTED
+import com.netease.yunxin.nertc.ui.base.launchTask
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
private const val TAG = "PermissionExtends"
+private const val REQUEST_CODE_PERMISSION = 21302
+
fun Context.isGranted(vararg permissions: String?): Boolean {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || permissions.isEmpty()) {
return true
}
return !permissions
@@ -32,6 +42,48 @@ fun Context.isGranted(vararg permissions: String?): Boolean {
}
}
+fun Context.requestPermission(
+ onGranted: ((List) -> Unit)? = null,
+ onDenied: ((List, List) -> Unit)? = null,
+ vararg permissions: String?
+) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ onGranted?.invoke(permissions.filterNotNull())
+ return
+ }
+ launchTask(
+ this,
+ REQUEST_CODE_PERMISSION,
+ { activity: Activity?, _: Int? ->
+ ActivityCompat.requestPermissions(
+ activity!!,
+ permissions,
+ REQUEST_CODE_PERMISSION
+ )
+ }
+ ) { intentResultInfo ->
+ val intent: Intent? = intentResultInfo.value
+ if (intent == null) {
+ ALog.e(TAG, "requestPermission, intent is null")
+ return@launchTask
+ }
+ val grantedList =
+ intent.getStringArrayListExtra(KEY_PERMISSION_RESULT_GRANTED)
+ if (!grantedList.isNullOrEmpty()) {
+ onGranted?.invoke(grantedList)
+ }
+ val deniedList: List? =
+ intent.getStringArrayListExtra(KEY_PERMISSION_RESULT_DENIED)
+ val deniedForeverList: List? =
+ intent.getStringArrayListExtra(KEY_PERMISSION_RESULT_DENIED_FOREVER)
+ if (!deniedList.isNullOrEmpty() ||
+ !deniedForeverList.isNullOrEmpty()
+ ) {
+ onDenied?.invoke(deniedList ?: emptyList(), deniedForeverList ?: emptyList())
+ }
+ }
+}
+
fun AppCompatActivity.requestPermission(
onGranted: ((List) -> Unit)? = null,
onDenied: ((List, List) -> Unit)? = null,
@@ -107,3 +159,86 @@ fun Fragment.requestPermission(
launcher.launch(permissionList.toTypedArray())
}
}
+
+fun Fragment.registerPermissionRequesterEx(): PermissionRequester {
+ val permissionRequester = PermissionRequester()
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return permissionRequester
+ }
+ permissionRequester.launcher = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissionMap ->
+ ALog.d(TAG, "$permissionMap")
+ permissionRequester.handlePermissionResult(permissionMap)
+ }
+ permissionRequester.shouldShowRequestPermissionRationale = {
+ shouldShowRequestPermissionRationale(it)
+ }
+ return permissionRequester
+}
+
+fun AppCompatActivity.registerPermissionRequesterEx(): PermissionRequester {
+ val permissionRequester = PermissionRequester()
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return permissionRequester
+ }
+ permissionRequester.launcher = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissionMap ->
+ ALog.d(TAG, "$permissionMap")
+ permissionRequester.handlePermissionResult(permissionMap)
+ }
+ permissionRequester.shouldShowRequestPermissionRationale = {
+ shouldShowRequestPermissionRationale(it)
+ }
+ return permissionRequester
+}
+
+class PermissionRequester {
+ internal var launcher: ActivityResultLauncher>? = null
+ internal var shouldShowRequestPermissionRationale: (String) -> Boolean = { true }
+ private var onGranted: ((List) -> Unit)? = null
+ private var onDenied: ((List, List) -> Unit)? = null
+ private var permissionList: List = emptyList()
+
+ internal fun handlePermissionResult(permissionMap: Map) {
+ ALog.d(TAG, "$permissionMap")
+ val grantedList = mutableListOf()
+ val deniedList = mutableListOf()
+ val deniedForeverList = mutableListOf()
+ permissionList.forEach {
+ if (permissionMap[it] == true) {
+ grantedList.add(it)
+ } else if (!shouldShowRequestPermissionRationale(it)) {
+ deniedForeverList.add(it)
+ } else {
+ deniedList.add(it)
+ }
+ }
+ if (grantedList.isNotEmpty()) {
+ onGranted?.invoke(grantedList)
+ }
+ if (deniedForeverList.isNotEmpty() || deniedList.isNotEmpty()) {
+ onDenied?.invoke(deniedForeverList, deniedList)
+ }
+ }
+
+ fun request(
+ permissionList: List,
+ onGranted: ((List) -> Unit)? = null,
+ onDenied: ((List, List) -> Unit)? = null
+ ) {
+ CoroutineScope(Dispatchers.Main).launch {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || permissionList.isEmpty()) {
+ onGranted?.invoke(permissionList)
+ return@launch
+ }
+ this@PermissionRequester.onGranted = onGranted
+ this@PermissionRequester.onDenied = onDenied
+ this@PermissionRequester.permissionList = permissionList
+ launcher?.launch(permissionList.toTypedArray())
+ }
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionTipDialog.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionTipDialog.kt
index 6f5e7f7..d80cddb 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionTipDialog.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/PermissionTipDialog.kt
@@ -19,7 +19,7 @@ import com.netease.yunxin.nertc.ui.R
/**
* 底部弹窗基类,子类需要实现 顶部view,以及底部view 的渲染即可
*/
-class PermissionTipDialog(activity: Activity, private val clickListener: View.OnClickListener) :
+open class PermissionTipDialog(activity: Activity, private val clickListener: View.OnClickListener) :
Dialog(
activity,
R.style.BottomDialogTheme
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/SwitchCallTypeConfirmDialog.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/SwitchCallTypeConfirmDialog.kt
index 3f1ed14..b2a52b0 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/SwitchCallTypeConfirmDialog.kt
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/utils/SwitchCallTypeConfirmDialog.kt
@@ -13,15 +13,14 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
-import android.widget.TextView
-import androidx.annotation.LayoutRes
import com.netease.yunxin.kit.call.p2p.model.NECallType
import com.netease.yunxin.nertc.ui.R
+import com.netease.yunxin.nertc.ui.databinding.ViewSwitchCallTypeTipDialogBinding
/**
* 底部弹窗基类,子类需要实现 顶部view,以及底部view 的渲染即可
*/
-class SwitchCallTypeConfirmDialog(
+open class SwitchCallTypeConfirmDialog(
activity: Activity,
val onPositive: (Int) -> Unit,
val onNegative: (Int) -> Unit
@@ -30,7 +29,9 @@ class SwitchCallTypeConfirmDialog(
activity,
R.style.BottomDialogTheme
) {
- private var rootView: View
+ private val binding: ViewSwitchCallTypeTipDialogBinding by lazy {
+ ViewSwitchCallTypeTipDialogBinding.inflate(LayoutInflater.from(context))
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -43,47 +44,43 @@ class SwitchCallTypeConfirmDialog(
wlp.height = WindowManager.LayoutParams.WRAP_CONTENT
window.attributes = wlp
}
- setContentView(rootView)
+ setContentView(binding.root)
setCanceledOnTouchOutside(false)
setCancelable(false)
}
- @LayoutRes
- private fun contentLayoutId(): Int {
- return R.layout.view_switch_call_type_tip_dialog
- }
-
/**
* 页面渲染
*/
- private fun renderRootView(rootView: View?, type: Int) {
+ protected open fun renderRootView(rootView: View?, type: Int) {
if (rootView == null) {
return
}
- rootView.findViewById(R.id.tv_tip_accept)?.run {
- setOnClickListener {
- onPositive(type)
- dismiss()
- }
+
+ binding.tvTipAccept.setOnClickListener {
+ onPositive(type)
+ dismiss()
}
- rootView.findViewById(R.id.tv_tip_reject)?.run {
- setOnClickListener {
- onNegative(type)
- dismiss()
- }
+ binding.tvTipReject.setOnClickListener {
+ onNegative(type)
+ dismiss()
}
}
- fun show(type: Int) {
+ final override fun show() {
+ super.show()
+ }
+
+ open fun show(type: Int) {
if (isShowing) {
return
}
- rootView.findViewById(R.id.tv_tip_content)
- ?.setText(
- if (type == NECallType.AUDIO) R.string.ui_dialog_switch_call_type_content_audio else R.string.ui_dialog_switch_call_type_content_video
- )
- renderRootView(rootView, type)
+
+ binding.tvTipContent.setText(
+ if (type == NECallType.AUDIO) R.string.ui_dialog_switch_call_type_content_audio else R.string.ui_dialog_switch_call_type_content_video
+ )
+ renderRootView(binding.root, type)
try {
show()
} catch (ignored: Throwable) {
@@ -100,8 +97,4 @@ class SwitchCallTypeConfirmDialog(
} catch (ignored: Throwable) {
}
}
-
- init {
- rootView = LayoutInflater.from(context).inflate(contentLayoutId(), null)
- }
}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/view/OverLayPermissionDialog.kt b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/view/OverLayPermissionDialog.kt
new file mode 100644
index 0000000..beb08cd
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/java/com/netease/yunxin/nertc/ui/view/OverLayPermissionDialog.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2022 NetEase, Inc. All rights reserved.
+ * Use of this source code is governed by a MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package com.netease.yunxin.nertc.ui.view
+
+import android.app.Activity
+import android.app.Dialog
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import androidx.annotation.LayoutRes
+import com.netease.yunxin.nertc.ui.R
+import com.netease.yunxin.nertc.ui.utils.dip2Px
+
+open class OverLayPermissionDialog(activity: Activity, private val clickListener: View.OnClickListener) :
+ Dialog(
+ activity,
+ R.style.BottomDialogTheme
+ ) {
+ private var rootView: View
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val window = window
+ if (window != null) {
+ window.decorView.setPadding(20.dip2Px(context), 0, 20.dip2Px(context), 0)
+ val wlp = window.attributes
+ wlp.gravity = Gravity.CENTER
+ wlp.width = WindowManager.LayoutParams.MATCH_PARENT
+ wlp.height = WindowManager.LayoutParams.WRAP_CONTENT
+ window.attributes = wlp
+ }
+ setContentView(rootView)
+ setCanceledOnTouchOutside(false)
+ setCancelable(true)
+ }
+
+ @LayoutRes
+ private fun contentLayoutId(): Int {
+ return R.layout.view_overlay_permission_tip_dialog
+ }
+
+ /**
+ * 页面渲染
+ */
+ private fun renderRootView(rootView: View?) {
+ if (rootView == null) {
+ return
+ }
+ val button = rootView.findViewById(R.id.tv_tip_ok)
+ button.setOnClickListener {
+ clickListener.onClick(it)
+ }
+ }
+
+ override fun show() {
+ if (isShowing) {
+ return
+ }
+ renderRootView(rootView)
+ try {
+ super.show()
+ } catch (ignored: Throwable) {
+ ignored.printStackTrace()
+ }
+ }
+
+ override fun dismiss() {
+ if (!isShowing) {
+ return
+ }
+ try {
+ super.dismiss()
+ } catch (ignored: Throwable) {
+ }
+ }
+
+ init {
+ rootView = LayoutInflater.from(context).inflate(contentLayoutId(), null)
+ }
+}
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_audio.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_audio.png
deleted file mode 100644
index 6099b9c..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_audio.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_video.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_video.png
deleted file mode 100644
index 501e1fa..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_left_type_video.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_mute_icon_normal.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_mute_icon_normal.png
deleted file mode 100644
index 09985c3..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_mute_icon_normal.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_normal.9.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_normal.9.png
deleted file mode 100644
index 7002b14..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_normal.9.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_pressed.9.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_pressed.9.png
deleted file mode 100644
index 31c3b00..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_receive_bg_pressed.9.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_normal.9.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_normal.9.png
deleted file mode 100644
index 4f0d7d3..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_normal.9.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_pressed.9.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_pressed.9.png
deleted file mode 100644
index a7db35d..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_refuse_bg_pressed.9.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_checked.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_checked.png
deleted file mode 100644
index d74e7af..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_checked.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_pressed.png
deleted file mode 100644
index 56743b1..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_speaker_icon_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_switch_mode_video_icon.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_switch_mode_video_icon.png
deleted file mode 100644
index 5c60e18..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/avchat_switch_mode_video_icon.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_actionbar_dark_back_icon.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_actionbar_dark_back_icon.png
deleted file mode 100644
index 41e226d..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_actionbar_dark_back_icon.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_default.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_default.png
deleted file mode 100644
index 3767ba6..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_default.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_group.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_group.png
deleted file mode 100644
index 73d12b7..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/nim_avatar_group.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera.png
deleted file mode 100644
index b8ce2aa..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute.png
deleted file mode 100644
index 55484f2..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute_pressed.png
deleted file mode 100644
index 8e939a5..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_mute_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_pressed.png
deleted file mode 100644
index ac4de98..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_camera_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup.png
deleted file mode 100644
index ce1d5d7..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup_pressed.png
deleted file mode 100644
index 9904c24..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_hangup_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_loading.gif b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_loading.gif
deleted file mode 100644
index e508c78..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_loading.gif and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone.png
deleted file mode 100644
index 1f05681..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute.png
deleted file mode 100644
index f69ac45..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute_pressed.png
deleted file mode 100644
index 8a8c8d6..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_mute_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_pressed.png
deleted file mode 100644
index 4b66e69..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_microphone_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera.png
deleted file mode 100644
index c8d0484..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera_pressed.png
deleted file mode 100644
index 43d1bfa..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-hdpi/t_avchat_switch_camera_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/nim_actionbar_dark_back_icon.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/nim_actionbar_dark_back_icon.png
deleted file mode 100644
index c244ad4..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/nim_actionbar_dark_back_icon.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera.png
deleted file mode 100644
index 540461c..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute.png
deleted file mode 100644
index 91ca282..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute_pressed.png
deleted file mode 100644
index ed00743..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_mute_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_pressed.png
deleted file mode 100644
index 62761d4..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_camera_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup.png
deleted file mode 100644
index 914a30d..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup_pressed.png
deleted file mode 100644
index edb02e4..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_hangup_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_loading.gif b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_loading.gif
deleted file mode 100644
index e4aa9f9..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_loading.gif and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone.png
deleted file mode 100644
index f74b517..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute.png
deleted file mode 100644
index 51bef69..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute_pressed.png
deleted file mode 100644
index d2349f8..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_mute_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_pressed.png
deleted file mode 100644
index 4239866..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_microphone_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera.png
deleted file mode 100644
index 8b255ec..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera_pressed.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera_pressed.png
deleted file mode 100644
index d3ab851..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xhdpi/t_avchat_switch_camera_pressed.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_off.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_off.png
new file mode 100644
index 0000000..802c93b
Binary files /dev/null and b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_off.png differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_on.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_on.png
new file mode 100644
index 0000000..9d06752
Binary files /dev/null and b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/icon_call_virtual_blur_on.png differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_off.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_off.png
deleted file mode 100644
index a6a1e88..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_off.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_on.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_on.png
deleted file mode 100644
index d54aa1d..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/remote_voice_on.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/team_avchat_icon_add.png b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/team_avchat_icon_add.png
deleted file mode 100644
index 0569884..0000000
Binary files a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable-xxhdpi/team_avchat_icon_add.png and /dev/null differ
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_receive_bg_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_receive_bg_selector.xml
deleted file mode 100644
index 3b0892d..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_receive_bg_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_refuse_bg_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_refuse_bg_selector.xml
deleted file mode 100644
index 960ad8f..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/avchat_refuse_bg_selector.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/floating_window_phone_bg.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/floating_window_phone_bg.xml
new file mode 100644
index 0000000..3b1cff5
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/floating_window_phone_bg.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/g2_avchat_remote_voice_icon_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/g2_avchat_remote_voice_icon_selector.xml
deleted file mode 100644
index e82222f..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/g2_avchat_remote_voice_icon_selector.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_floating_window.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_floating_window.xml
new file mode 100644
index 0000000..362c53c
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_floating_window.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_phone.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_phone.xml
new file mode 100644
index 0000000..b6948cf
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/icon_call_phone.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_actionbar_nest_dark_logo.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_actionbar_nest_dark_logo.xml
deleted file mode 100644
index 442719e..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_actionbar_nest_dark_logo.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_list_item_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_list_item_selector.xml
deleted file mode 100644
index f611f28..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/nim_list_item_selector.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/shape_call_floating_window_video_bg.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/shape_call_floating_window_video_bg.xml
new file mode 100644
index 0000000..9f67fa5
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/shape_call_floating_window_video_bg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_mute_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_mute_selector.xml
deleted file mode 100644
index 0addec1..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_mute_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_selector.xml
deleted file mode 100644
index 3de24cf..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_camera_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_hangup_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_hangup_selector.xml
deleted file mode 100644
index d131960..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_hangup_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_mute_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_mute_selector.xml
deleted file mode 100644
index 83991e0..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_mute_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_selector.xml
deleted file mode 100644
index 052d12e..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_microphone_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_switch_camera_selector.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_switch_camera_selector.xml
deleted file mode 100644
index 574aac1..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/drawable/t_avchat_switch_camera_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/activity_p2p_call_fragment.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/activity_p2p_call_fragment.xml
index 1cd8a07..a849bfe 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/activity_p2p_call_fragment.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/activity_p2p_call_fragment.xml
@@ -5,10 +5,7 @@
-->
+ android:layout_height="match_parent" />
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_caller.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_caller.xml
index ed49747..e1a4b75 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_caller.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_caller.xml
@@ -194,4 +194,16 @@
android:layout_width="wrap_content"
app:constraint_referenced_ids="tvSwitchTip,ivSwitchTipClose"
android:layout_height="wrap_content"/>
+
+
+
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_on_the_call.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_on_the_call.xml
index f14774b..50f2ead 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_on_the_call.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_audio_on_the_call.xml
@@ -161,5 +161,16 @@
android:layout_width="wrap_content"
app:constraint_referenced_ids="tvSwitchTip,ivSwitchTipClose"
android:layout_height="wrap_content"/>
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_base_call.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_base_call.xml
deleted file mode 100644
index 8eb7aaf..0000000
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_base_call.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_callee.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_callee.xml
index b575d3f..063f066 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_callee.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_callee.xml
@@ -18,6 +18,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+
+
+
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_on_the_call.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_on_the_call.xml
index 67bed51..4e9dd35 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_on_the_call.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/fragment_p2p_video_on_the_call.xml
@@ -52,13 +52,25 @@
app:layout_constraintStart_toStartOf="@id/videoViewSmall"
app:layout_constraintTop_toTopOf="@id/videoViewSmall" />
+
+
@@ -70,10 +82,9 @@
android:layout_marginBottom="50dp"
android:background="@drawable/calling_control_bg"
android:orientation="horizontal"
- android:paddingStart="30dp"
- android:paddingTop="14dp"
- android:paddingEnd="30dp"
- android:paddingBottom="14dp"
+ android:paddingHorizontal="15dp"
+ android:paddingVertical="14dp"
+ android:minWidth="310dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
@@ -82,38 +93,54 @@
android:id="@+id/ivMuteAudio"
android:layout_width="24dp"
android:layout_height="24dp"
+ android:layout_weight="1"
+ android:layout_marginHorizontal="12dp"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="30dp"
android:src="@drawable/voice_on" />
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_floating_window.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_floating_window.xml
new file mode 100644
index 0000000..9fc242b
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_floating_window.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_overlay_permission_tip_dialog.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_overlay_permission_tip_dialog.xml
new file mode 100644
index 0000000..e6fd69c
--- /dev/null
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/layout/view_overlay_permission_tip_dialog.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/colors.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/colors.xml
index d8805a3..b5da532 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/colors.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/colors.xml
@@ -44,4 +44,10 @@
#537FF4
#53C3F3
#60CFA7
+ #D9E1E1E1
+ #FF666666
+ #FF1BBF52
+ #232529
+ #5E6471
+ #E6E7EB
\ No newline at end of file
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/strings.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/strings.xml
index 54a1af2..0fadcca 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/strings.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/strings.xml
@@ -45,6 +45,11 @@
同意
拒绝
确定
+ 浮窗权限未获取
+ 你的手机没有授权浮窗权限,音视频通话最小化不能正常使用
+ 开启
+ 呼叫中
+ 待接听
正在等待对方响应…
对方拒绝了您的请求
切换到语音通话
@@ -58,6 +63,8 @@
我方关闭了摄像头
正在等待对方接受邀请…
等待对方接听…
+ 虚化开启
+ 该设备不支持虚化功能
正在接通中…
视频通话
待接听
diff --git a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/styles.xml b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/styles.xml
index 061dd5d..187e6fc 100644
--- a/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/styles.xml
+++ b/NLiteAVDemo-Android-Java/call-ui/src/main/res/values/styles.xml
@@ -12,6 +12,10 @@
- true
- false
- @android:style/Animation.Translucent
+ - true
+ - @color/colorWhite
+ - true
+ - true