package io.openim.android.ouicalling; import android.content.Context; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.lifecycle.Observer; import com.blankj.utilcode.util.LogUtils; import com.hjq.permissions.Permission; import com.hjq.window.EasyWindow; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import io.livekit.android.events.RoomEvent; import io.livekit.android.room.participant.Participant; import io.livekit.android.room.participant.RemoteParticipant; import io.livekit.android.room.track.RemoteVideoTrack; import io.openim.android.ouicalling.databinding.DialogCallBinding; import io.openim.android.ouicalling.databinding.LayoutFloatViewBinding; import io.openim.android.ouicalling.service.AudioVideoService; import io.openim.android.ouicalling.vm.CallingVM; import io.openim.android.ouicore.base.BaseApp; import io.openim.android.ouicore.base.BaseDialog; import io.openim.android.ouicore.im.IMUtil; import io.openim.android.ouicore.net.bage.GsonHel; import io.openim.android.ouicore.services.CallingService; import io.openim.android.ouicore.utils.Common; import io.openim.android.ouicore.utils.Constants; import io.openim.android.ouicore.utils.HasPermissions; import io.openim.android.ouicore.utils.MediaPlayerUtil; import io.openim.android.ouicore.utils.NotificationUtil; import io.openim.android.ouicore.utils.Obs; import io.openim.android.ouicore.utils.OnDedrepClickListener; import io.openim.android.sdk.OpenIMClient; import io.openim.android.sdk.enums.ConversationType; import io.openim.android.sdk.listener.OnBase; import io.openim.android.sdk.models.Message; import io.openim.android.sdk.models.SignalingInfo; import io.openim.android.sdk.models.UserInfo; import kotlin.Unit; import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; public class CallDialog extends BaseDialog { protected final HasPermissions hasShoot, hasRecord, hasSystemAlert; protected Context context; private DialogCallBinding view; public CallingVM callingVM; protected SignalingInfo signalingInfo; protected EasyWindow easyWindow; protected LayoutFloatViewBinding floatViewBinding; private boolean isSubscribe; public CallDialog(@NonNull Context context, CallingService callingService) { this(context, callingService, false); } /** * 弹出通话界面 * * @param context 上下文 * @param callingService 通话服务 * @param isCallOut 是否呼出 */ public CallDialog(@NonNull Context context, CallingService callingService, boolean isCallOut) { super(context); this.context = context; hasShoot = new HasPermissions(context, Permission.CAMERA, Permission.RECORD_AUDIO); hasRecord = new HasPermissions(context, Permission.RECORD_AUDIO); hasSystemAlert = new HasPermissions(context, Permission.SYSTEM_ALERT_WINDOW); callingVM = new CallingVM(callingService, isCallOut); callingVM.setDismissListener(v -> { dismiss(); }); callingVM.callViewModel.subscribe(callingVM.callViewModel.getRoom().getEvents().getEvents(), (v) -> { if (v instanceof RoomEvent.ParticipantDisconnected && v.getRoom().getRemoteParticipants().size() == 0) { //当只有1个人时关闭会议 dismiss(); } return null; }, callingVM.scope); initView(); initRendererView(); } public void initRendererView() { callingVM.initLocalSpeakerVideoView(view.localSpeakerVideoView); callingVM.initRemoteVideoRenderer(view.remoteSpeakerVideoView, view.remoteSpeakerVideoView2, floatViewBinding.shrinkRemoteSpeakerVideoView); } private void initView() { floatViewBinding = LayoutFloatViewBinding.inflate(getLayoutInflater()); Window window = getWindow(); view = DialogCallBinding.inflate(getLayoutInflater()); window.requestFeature(Window.FEATURE_NO_TITLE); window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(view.getRoot()); //背景状态栏透明 window.setDimAmount(1f); WindowManager.LayoutParams params = getWindow().getAttributes(); params.height = ViewGroup.LayoutParams.MATCH_PARENT; params.width = ViewGroup.LayoutParams.MATCH_PARENT; setCancelable(false); setCanceledOnTouchOutside(false); Common.addTypeSystemAlert(params); window.setAttributes(params); window.setBackgroundDrawableResource(android.R.color.transparent); window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); view.zoomOut.setVisibility(Common.isScreenLocked() ? View.GONE : View.VISIBLE); } // 收起/展开 public void shrink(boolean isShrink) { if (isShrink) { showFloatView(); } else if (null != easyWindow) { easyWindow.cancel(); } view.home.setVisibility(isShrink ? View.GONE : View.VISIBLE); getWindow().setDimAmount(isShrink ? 0f : 1f); if (callingVM.isStartCall) { floatViewBinding.sTips.setText(io.openim.android.ouicore.R.string.calling); } else { floatViewBinding.sTips.setText(callingVM.isCallOut ? context.getString(io.openim.android.ouicore.R.string.waiting_tips2) : context.getString(io.openim.android.ouicore.R.string.waiting_tips3)); } WindowManager.LayoutParams params = getWindow().getAttributes(); params.height = isShrink ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT; params.width = isShrink ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.MATCH_PARENT; params.gravity = isShrink ? (Gravity.TOP | Gravity.END) : Gravity.CENTER; getWindow().setAttributes(params); } protected void showFloatView() { // 传入 Activity 对象表示设置成局部的,不需要有悬浮窗权限 // 传入 Application 对象表示设置成全局的,但需要有悬浮窗权限 if (null == easyWindow) { easyWindow = new EasyWindow<>(BaseApp.inst()).setContentView(floatViewBinding.getRoot()).setGravity(Gravity.END | Gravity.TOP) // 设置成可拖拽的 .setDraggable(); floatViewBinding.shrink.setOnClickListener(v -> shrink(false)); } if (!easyWindow.isShowing()) easyWindow.show(); } public void bindData(SignalingInfo signalingInfo) { this.signalingInfo = signalingInfo; callingVM.isGroup = signalingInfo.getInvitation().getSessionType() != ConversationType.SINGLE_CHAT; callingVM.setVideoCalls(Constants.MediaType.VIDEO.equals(signalingInfo.getInvitation().getMediaType())); view.cameraControl.setVisibility(callingVM.isVideoCalls ? View.VISIBLE : View.GONE); if (!callingVM.isVideoCalls) { callingVM.callViewModel.setCameraEnabled(false); view.localSpeakerVideoView.setVisibility(View.GONE); view.timeTv.setVisibility(View.GONE); view.headTips.setVisibility(View.GONE); view.audioCall.setVisibility(View.VISIBLE); } view.micIsOn.setChecked(true); view.speakerIsOn.setChecked(true); if (callingVM.isCallOut) { view.callingMenu.setVisibility(View.VISIBLE); view.ask.setVisibility(View.GONE); view.callingTips.setText(context.getString(io.openim.android.ouicore.R.string.waiting_tips) + "..."); view.callingTips2.setText(context.getString(io.openim.android.ouicore.R.string.waiting_tips) + "..."); callingVM.signalingInvite(signalingInfo); } else { view.callingMenu.setVisibility(View.GONE); view.ask.setVisibility(View.VISIBLE); } bindUserInfo(signalingInfo); listener(signalingInfo); } /** * 绑定用户信息 */ public void bindUserInfo(SignalingInfo signalingInfo) { try { ArrayList ids = new ArrayList<>(); ids.add(callingVM.isCallOut ? signalingInfo.getInvitation().getInviteeUserIDList().get(0) : signalingInfo.getInvitation().getInviterUserID()); OpenIMClient.getInstance().userInfoManager.getUsersInfo(new OnBase>() { @Override public void onError(int code, String error) { Toast.makeText(context, error + code, Toast.LENGTH_SHORT).show(); } @Override public void onSuccess(List data) { if (data.isEmpty()) return; UserInfo userInfo = data.get(0); view.avatar.load(userInfo.getFaceURL(),userInfo.getNickname()); floatViewBinding.sAvatar.load(userInfo.getFaceURL(), userInfo.getNickname()); view.name.setText(userInfo.getNickname()); //audio call view.avatar2.load(userInfo.getFaceURL(),userInfo.getNickname()); view.name2.setText(userInfo.getNickname()); } }, ids); } catch (Exception e) { e.printStackTrace(); } } public final Observer bindTime = new Observer() { @Override public void onChanged(String s) { if (TextUtils.isEmpty(s)) return; view.timeTv.setText(s); view.callingTips2.setText(s); } }; public void listener(SignalingInfo signalingInfo) { callingVM.callViewModel.subscribe(callingVM.callViewModel.getRemoteParticipants(), (v) -> { if (isSubscribe) return null; Object[] toArray = v.values().toArray(); if (toArray.length == 0) return null; callingVM.callViewModel.subscribe(((RemoteParticipant) toArray[0]).getEvents().getEvents(), (event) -> { isSubscribe = true; view.remoteSpeakerVideoView.setVisibility(event.getParticipant().isCameraEnabled() ? View.VISIBLE : View.GONE); return null; }, callingVM.scope); return null; }, callingVM.scope); callingVM.timeStr.observeForever(bindTime); view.closeCamera.setOnCheckedChangeListener((buttonView, isChecked) -> { hasShoot.safeGo(() -> { boolean isEnabled = !isChecked; callingVM.callViewModel.setCameraEnabled(isEnabled); view.localSpeakerVideoView.setVisibility(isEnabled ? View.VISIBLE : View.GONE); }); }); view.switchCamera.setOnClickListener(new OnDedrepClickListener(1000) { @Override public void click(View v) { callingVM.callViewModel.flipCamera(); } }); view.micIsOn.setOnClickListener(new OnDedrepClickListener(1000) { @Override public void click(View v) { view.micIsOn.setText(view.micIsOn.isChecked() ? context.getString(io.openim.android.ouicore.R.string.microphone_on) : context.getString(io.openim.android.ouicore.R.string.microphone_off)); //关闭麦克风 callingVM.callViewModel.setMicEnabled(view.micIsOn.isChecked()); } }); view.speakerIsOn.setOnCheckedChangeListener((buttonView, isChecked) -> { view.speakerIsOn.setText(isChecked ? context.getString(io.openim.android.ouicore.R.string.speaker_on) : context.getString(io.openim.android.ouicore.R.string.speaker_off)); // 打开扬声器 callingVM.setSpeakerphoneOn(isChecked); }); view.hangUp.setOnClickListener(new OnDedrepClickListener() { @Override public void click(View v) { // callingVM.renewalDB(callingVM.buildPrimaryKey(signalingInfo), (realm, callHistory) -> // callHistory.setDuration((int) (System.currentTimeMillis() - callHistory.getDate()))); callingVM.signalingHungUp(signalingInfo); } }); view.reject.setOnClickListener(new OnDedrepClickListener() { @Override public void click(View v) { callingVM.signalingHungUp(signalingInfo); } }); view.answer.setOnClickListener(new OnDedrepClickListener() { @Override public void click(View v) { answerClick(signalingInfo); } }); view.zoomOut.setOnClickListener(v -> { zoomOutClick(); }); view.shrink.setOnClickListener(v -> { shrink(false); }); view.localSpeakerVideoView.setOnClickListener(new OnDedrepClickListener() { @Override public void click(View v) { Object object = view.remoteSpeakerVideoView.getTag(); if (null != object) { Participant participant = object instanceof RemoteVideoTrack ? (Participant) callingVM.callViewModel.getRoom().getLocalParticipant() : (Participant) callingVM.callViewModel.getSingleRemotePar(); Participant participant2 = object instanceof RemoteVideoTrack ? (Participant) callingVM.callViewModel.getSingleRemotePar() : (Participant) callingVM.callViewModel.getRoom().getLocalParticipant(); if (null == participant2 || null == participant) return; callingVM.callViewModel.bindRemoteViewRenderer(view.localSpeakerVideoView, participant2, callingVM.scope, new Continuation() { @NonNull @Override public CoroutineContext getContext() { return null; } @Override public void resumeWith(@NonNull Object o) { } }); callingVM.callViewModel.bindRemoteViewRenderer(view.remoteSpeakerVideoView, participant, callingVM.scope, new Continuation() { @NonNull @Override public CoroutineContext getContext() { return null; } @Override public void resumeWith(@NonNull Object o) { } }); } } }); } public void zoomOutClick() { hasSystemAlert.safeGo(() -> shrink(true)); } public void answerClick(SignalingInfo signalingInfo) { if (callingVM.isVideoCalls) { hasShoot.safeGo(() -> signalingAccept(signalingInfo)); } else { hasRecord.safeGo(() -> signalingAccept(signalingInfo)); } } public void signalingAccept(SignalingInfo signalingInfo) { callingVM.signalingAccept(signalingInfo, new OnBase() { @Override public void onError(int code, String error) { } @Override public void onSuccess(Object data) { changeView(); callingVM.renewalDB(CallingVM.buildPrimaryKey(signalingInfo), (realm, v1) -> v1.setSuccess(true)); } }); } public void changeView() { view.headTips.setVisibility(View.GONE); view.ask.setVisibility(View.GONE); view.callingMenu.setVisibility(View.VISIBLE); view.cameraControl.setVisibility(callingVM.isVideoCalls ? View.VISIBLE : View.GONE); waitingHandle(); } @Override public void show() { playRingtone(); super.show(); } public void playRingtone() { try { Common.wakeUp(context); NotificationUtil.cancelNotify(AudioVideoService.NOTIFY_ID); // Ringtone铃声 if (!MediaPlayerUtil.INSTANCE.isPlaying()) { MediaPlayerUtil.INSTANCE.initMedia(BaseApp.inst(), R.raw.incoming_call_ring); MediaPlayerUtil.INSTANCE.loopPlay(); } } catch (Exception e) { e.printStackTrace(); } } @Override public void dismiss() { try { Common.UIHandler.removeCallbacksAndMessages(null); if (null != easyWindow) { easyWindow.cancel(); } callingVM.renewalDB(callingVM.buildPrimaryKey(signalingInfo), (realm, callHistory) -> callHistory.setDuration((int) (System.currentTimeMillis() - callHistory.getDate()))); insertChatHistory(); MediaPlayerUtil.INSTANCE.pause(); MediaPlayerUtil.INSTANCE.release(); callingVM.setSpeakerphoneOn(true); callingVM.timeStr.removeObserver(bindTime); videoViewRelease(); callingVM.unBindView(); super.dismiss(); ((CallingServiceImp) callingVM.callingService).callDialog = null; } catch (Exception e) { e.printStackTrace(); } } public void videoViewRelease() { view.localSpeakerVideoView.release(); view.remoteSpeakerVideoView.release(); view.remoteSpeakerVideoView2.release(); floatViewBinding.shrinkRemoteSpeakerVideoView.release(); } private void insertChatHistory() { boolean isGroup = callingVM.isGroup; if (!isShowing() || isGroup || (null != signalingInfo && TextUtils.isEmpty(callingVM.buildPrimaryKey(signalingInfo)))) return; String id = callingVM.buildPrimaryKey(signalingInfo); String senderID = isGroup ? BaseApp.inst().loginCertificate.userID : signalingInfo.getInvitation().getInviterUserID(); String receiver = signalingInfo.getInvitation().getInviteeUserIDList().get(0); callingVM.renewalDB(id, (realm, callHistory) -> { callHistory = realm.copyFromRealm(callHistory); HashMap map = new HashMap<>(); map.put(Constants.K_CUSTOM_TYPE, Constants.MsgType.LOCAL_CALL_HISTORY); map.put(Constants.K_DATA, callHistory); String data = GsonHel.toJson(map); LogUtils.e(data); Message message = OpenIMClient.getInstance().messageManager.createCustomMessage(data, "", ""); message.setRead(true); OpenIMClient.getInstance().messageManager.insertSingleMessageToLocalStorage(new IMUtil.IMCallBack() { @Override public void onSuccess(String data) { Obs.newMessage(Constants.Event.INSERT_MSG); } }, message, receiver, senderID); }); } public void otherSideAccepted() { callingVM.isStartCall = true; callingVM.buildTimer(); view.headTips.setVisibility(View.GONE); MediaPlayerUtil.INSTANCE.pause(); MediaPlayerUtil.INSTANCE.release(); waitingHandle(); } public String buildPrimaryKey() { return CallingVM.buildPrimaryKey(signalingInfo); } private void waitingHandle() { if (callingVM.isVideoCalls) floatViewBinding.waiting.setVisibility(View.GONE); if (callingVM.isStartCall) { floatViewBinding.sTips.setText(io.openim.android.ouicore.R.string.calling); } else { floatViewBinding.sTips.setText(callingVM.isCallOut ? context.getString(io.openim.android.ouicore.R.string.waiting_tips2) : context.getString(io.openim.android.ouicore.R.string.waiting_tips3)); } } }