From d4305708f42a8da1ab7a773ed99b9c9e7d5778ed Mon Sep 17 00:00:00 2001 From: zlzw <583819556@qq.com> Date: Tue, 17 Oct 2023 18:29:16 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OneToOne/src/main/AndroidManifest.xml | 5 + .../java/com/shayu/onetoone/AppContext.java | 7 + .../shayu/onetoone/activity/MainActivity.java | 2 + .../activity/message/CallVideoActivity.java | 317 ++++++++++++++++-- .../com/shayu/onetoone/dialog/GiftDialog.java | 99 +++++- .../listener/OnCallStatusListener.java | 2 + .../onetoone/manager/CallClientManager.java | 253 ++++++++++++-- .../OTOCallEndMessageItemProvider.java | 255 ++++++++++++++ .../main/res/layout/activity_call_video.xml | 163 ++++----- .../main/res/layout/view_call_video_item.xml | 125 +++++++ .../res/layout/view_message_input_gift.xml | 1 + .../main/res/mipmap-xxhdpi/ic_call_msg.png | Bin 10145 -> 9975 bytes 12 files changed, 1072 insertions(+), 157 deletions(-) create mode 100644 OneToOne/src/main/java/com/shayu/onetoone/provider/OTOCallEndMessageItemProvider.java create mode 100644 OneToOne/src/main/res/layout/view_call_video_item.xml diff --git a/OneToOne/src/main/AndroidManifest.xml b/OneToOne/src/main/AndroidManifest.xml index 5de36921a..6f97d55ba 100644 --- a/OneToOne/src/main/AndroidManifest.xml +++ b/OneToOne/src/main/AndroidManifest.xml @@ -147,6 +147,11 @@ + + + { + SurfaceView surfaceView = CallClientManager.getManager().getRemoteVideo(targetId); + surfaceView.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + myView.addView(surfaceView); + myView.addView(buttonView); + targetView.addView(CallClientManager.getManager().getLocalVideo()); + }, 300); + + } } private void initView() { + rootView = findViewById(R.id.rootView); myView = findViewById(R.id.my_view); targetView = findViewById(R.id.target_view); - callStop = findViewById(R.id.call_stop); - gift = findViewById(R.id.gift); - message = findViewById(R.id.message); - cameraCloseSwitch = findViewById(R.id.camera_close_switch); - cameraSwitch = findViewById(R.id.camera_switch); - micSwitch = findViewById(R.id.mic_switch); - money = findViewById(R.id.money); - avatar = findViewById(R.id.avatar); - close = findViewById(R.id.close); - follow = findViewById(R.id.follow); - followText = findViewById(R.id.follow_text); + callLayout = findViewById(R.id.rc_voip_two_btn); + buttonView = LayoutInflater.from(mContext).inflate(R.layout.view_call_video_item, rootView, false); + initButton(buttonView); + // 为所有View设置点击事件监听器 + setClickListeners(); + } + + private void initButton(View itemView) { + callStop = itemView.findViewById(R.id.call_stop); + gift = itemView.findViewById(R.id.gift); + message = itemView.findViewById(R.id.message); + cameraCloseSwitch = itemView.findViewById(R.id.camera_close_switch); + cameraSwitch = itemView.findViewById(R.id.camera_switch); + micSwitch = itemView.findViewById(R.id.mic_switch); + money = itemView.findViewById(R.id.money); + avatar = itemView.findViewById(R.id.avatar); + close = itemView.findViewById(R.id.close); + follow = itemView.findViewById(R.id.follow); + followText = itemView.findViewById(R.id.follow_text); // 为所有View设置点击事件监听器 setClickListeners(); } @@ -78,14 +180,191 @@ public class CallVideoActivity extends AbsOTOActivity { avatar.setOnClickListener(onClickListener); close.setOnClickListener(onClickListener); follow.setOnClickListener(onClickListener); + findViewById(R.id.rc_voip_call_answer_btn).setOnClickListener(onClickListener); + findViewById(R.id.rc_voip_call_hang_up).setOnClickListener(onClickListener); } - private View.OnClickListener onClickListener = new View.OnClickListener() { + private void accept() { + callLayout.setVisibility(View.GONE); + CallClientManager.getManager().acceptCall(targetId); + } + + private void closeCamera() { + RongCallClient.getInstance().setEnableLocalVideo(!RongCallClient.getInstance().isLocalVideoEnabled()); + cameraCloseSwitch.setImageResource(RongCallClient.getInstance().isLocalAudioEnabled() ? R.mipmap.ic_call_video_select : R.mipmap.ic_call_video); + if (RongCallClient.getInstance().isLocalVideoEnabled()) { + targetView.setVisibility(View.VISIBLE); + } else { + targetView.setVisibility(View.INVISIBLE); + } + } + + private void switchCamera() { + RongCallClient.getInstance().switchCamera(); + } + + private void switchAudio() { + RongCallClient.getInstance().setEnableLocalAudio(!RongCallClient.getInstance().isLocalAudioEnabled()); + ToastUtil.show("麦克风状态:" + RongCallClient.getInstance().isLocalAudioEnabled()); + micSwitch.setImageResource(RongCallClient.getInstance().isLocalAudioEnabled() ? R.mipmap.ic_call_audio_select : R.mipmap.ic_call_audio); + } + + private void showWindow(boolean toChatView) { + CallClientManager.getManager().getRemoteVideo(targetId).setTag(getIntent().getBundleExtra("bundle")); + myView.removeAllViews(); + targetView.removeAllViews(); + finish(); + CallClientManager.getManager().getRemoteVideo(targetId).setLayoutParams(new ViewGroup.LayoutParams(DpUtil.dp2px(114), DpUtil.dp2px(164))); + EasyFloat.with(this) + .setLayout(CallClientManager.getManager().getRemoteVideo(targetId)) + .setShowPattern(ShowPattern.FOREGROUND) + .setTag("call") + .setDragEnable(true) + .setBorder() + .registerCallbacks(new OnFloatCallbacks() { + OnCallStatusListener windowListener; + + @Override + public void createdResult(boolean b, @Nullable String s, @Nullable View view) { + + } + + @Override + public void show(@NonNull View view) { + view.setOnClickListener(v -> { + RouteManager.forwardActivity(RouteManager.ACTIVITY_CALL_VIDEO, (Bundle) v.getTag()); + }); + windowListener = new WindowCallStatusListener(); + CallClientManager.getManager().addOnVoIPCallListener(windowListener); + } + + @Override + public void hide(@NonNull View view) { + + } + + @Override + public void dismiss() { + CallClientManager.getManager().removeOnVoIPCallListener(windowListener); + } + + @Override + public void touchEvent(@NonNull View view, @NonNull MotionEvent motionEvent) { + + } + + @Override + public void drag(@NonNull View view, @NonNull MotionEvent motionEvent) { + + } + + @Override + public void dragEnd(@NonNull View view) { + + } + }).show(); + if (toChatView) { + ConversationUtils.startConversation(mContext, targetId); + } + + + } + + private final View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { // 在这里编写点击事件的处理逻辑 int id = v.getId(); - + if (id == R.id.rc_voip_call_answer_btn) { + accept(); + } else if (id == R.id.call_stop || id == R.id.rc_voip_call_hang_up) { + CallClientManager.getManager().endCall(); + } else if (id == R.id.gift) { + new GiftDialog(mContext) + .setTargetId(targetId) + .showDialog(); + } else if (id == R.id.camera_close_switch) { + closeCamera(); + } else if (id == R.id.camera_switch) { + switchCamera(); + } else if (id == R.id.mic_switch) { + switchAudio(); + } else if (id == R.id.close) { + showWindow(false); + } else if (id == R.id.message) { + showWindow(true); + } } }; + + private class CallStatusListener extends OnCallStatusListener { + @Override + public void onCallWait(SurfaceView surfaceView) { + myView.removeAllViews(); + myView.addView(surfaceView); + } + + @Override + public void onCallStart(String userId, SurfaceView surfaceView) { + surfaceView.setZOrderOnTop(false); + surfaceView.setZOrderMediaOverlay(false); + surfaceView.invalidate(); + surfaceView.setOnClickListener(v -> { + surfaceView.setZOrderOnTop(false); + surfaceView.setZOrderMediaOverlay(false); + surfaceView.invalidate(); + }); + if (model.equals(CallClientManager.VIDEO_CALL)) { + targetView.removeAllViews(); + targetView.addView(surfaceView); + myView.removeAllViews(); + myView.addView(CallClientManager.getManager().getLocalVideo()); + } else { + myView.removeAllViews(); + myView.addView(surfaceView); + targetView.removeAllViews(); + targetView.addView(CallClientManager.getManager().getLocalVideo()); + myView.addView(buttonView); + } + } + + @Override + public void onCallEnd() { + finish(); + } + + @Override + public void onStartFirstFrame() { + /* CallClientManager.getManager().getRemoteVideo(targetId).setZOrderOnTop(false); + CallClientManager.getManager().getRemoteVideo(targetId).setZOrderMediaOverlay(false); + CallClientManager.getManager().getRemoteVideo(targetId).invalidate();*/ + } + } + + private class WindowCallStatusListener extends OnCallStatusListener { + @Override + public void onCallWait(SurfaceView surfaceView) { + + } + + @Override + public void onCallStart(String userId, SurfaceView surfaceView) { + + } + + @Override + public void onCallEnd() { + EasyFloat.dismiss("call"); + } + + @Override + public void onStartFirstFrame() { + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + CallClientManager.getManager().removeOnVoIPCallListener(onCallStatusListener); + } } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java b/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java index 715787dda..7dea7e46b 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/dialog/GiftDialog.java @@ -7,26 +7,44 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.alibaba.fastjson.JSONObject; import com.lxj.xpopup.XPopup; import com.shayu.onetoone.R; import com.shayu.onetoone.adapter.GiftListAdapter; import com.shayu.onetoone.bean.GiftBean; +import com.shayu.onetoone.bean.MessageChatGiftContent; +import com.shayu.onetoone.bean.PurseBean; +import com.shayu.onetoone.listener.OnSendMessageListener; import com.shayu.onetoone.manager.OTONetManager; +import com.shayu.onetoone.manager.SendMessageManager; +import com.shayu.onetoone.view.MsgInputPanelForGift; import com.shayu.onetoone.widget.PagerConfig; import com.shayu.onetoone.widget.PagerGridLayoutManager; import com.shayu.onetoone.widget.PagerGridSnapHelper; import com.yunbao.common.dialog.AbsDialogPopupWindow; import com.yunbao.common.http.base.HttpCallback; +import com.yunbao.common.interfaces.OnItemClickListener; +import com.yunbao.common.manager.IMLoginManager; +import com.yunbao.common.utils.ToastUtil; import java.util.ArrayList; import java.util.List; +import io.rong.imkit.IMCenter; +import io.rong.imlib.IRongCallback; +import io.rong.imlib.RongIMClient; +import io.rong.imlib.model.Conversation; +import io.rong.imlib.model.Message; + public class GiftDialog extends AbsDialogPopupWindow { RecyclerView gifList; GiftListAdapter mAdapter; TextView money; Button topUpBtn; Button sendBtn; + String token; + String targetId; + OnItemClickListener onItemClickListener; public GiftDialog(@NonNull Context context) { @@ -38,6 +56,16 @@ public class GiftDialog extends AbsDialogPopupWindow { } + public GiftDialog setTargetId(String targetId) { + this.targetId = targetId; + return this; + } + + public GiftDialog setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + return this; + } + @Override public int bindLayoutId() { return R.layout.view_message_input_gift; @@ -59,18 +87,79 @@ public class GiftDialog extends AbsDialogPopupWindow { PagerConfig.setMillisecondsPreInch(150); gifList.setAdapter(mAdapter); initData(); + sendBtn.setOnClickListener(v -> { + SendMessageManager.sendMessageForGift(targetId, mAdapter.getItem().getId() + "", new OnSendMessageListener() { + @Override + public void onSuccess(String token) { + super.onSuccess(token); + GiftDialog.this.token = token; + sendGift(mAdapter.getItem()); + + } + + @Override + public void onError(int status, String msg) { + super.onError(status, msg); + } + }); + }); + } + + private void sendGift(GiftBean item) { + MessageChatGiftContent bean = MessageChatGiftContent.obtain(JSONObject.toJSONString(item), "1", IMLoginManager.get(mContext).getUserInfo().getId() + ""); + bean.setExtra(JSONObject.toJSONString(item)); + IMCenter.getInstance().sendMessage(Message.obtain(targetId, Conversation.ConversationType.PRIVATE, bean), + null, + null, + new IRongCallback.ISendMessageCallback() { + @Override + public void onAttached(Message message) { + + } + + @Override + public void onSuccess(Message message) { + iniPurse(); + SendMessageManager.onCallSuccess(token, new OnSendMessageListener() { + @Override + public void onError(int status, String msg) { + super.onError(status, msg); + ToastUtil.show(msg); + } + }); + } + + @Override + public void onError(Message message, RongIMClient.ErrorCode errorCode) { + System.out.println("失败:" + errorCode.getMessage()); + System.out.println("失败:" + errorCode.getValue()); + } + } + ); } private void initData() { + iniPurse(); OTONetManager.getInstance(mContext) .getGiftList(new HttpCallback>() { @Override public void onSuccess(List data) { - List list=new ArrayList<>(); - for (int i = 0; i < 10; i++) { - list.addAll(data); - } - mAdapter.setList(list); + mAdapter.setList(data); + } + + @Override + public void onError(String error) { + + } + }); + } + + private void iniPurse() { + OTONetManager.getInstance(mContext) + .getPurseInfo(new HttpCallback() { + @Override + public void onSuccess(PurseBean data) { + money.setText(data.getStart() + ""); } @Override diff --git a/OneToOne/src/main/java/com/shayu/onetoone/listener/OnCallStatusListener.java b/OneToOne/src/main/java/com/shayu/onetoone/listener/OnCallStatusListener.java index 7960e6614..837d60301 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/listener/OnCallStatusListener.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/listener/OnCallStatusListener.java @@ -8,4 +8,6 @@ public abstract class OnCallStatusListener { public abstract void onCallStart(String userId, SurfaceView remoteVideo); public abstract void onCallEnd(); + + public abstract void onStartFirstFrame(); } diff --git a/OneToOne/src/main/java/com/shayu/onetoone/manager/CallClientManager.java b/OneToOne/src/main/java/com/shayu/onetoone/manager/CallClientManager.java index 664e0a1ba..73182e797 100644 --- a/OneToOne/src/main/java/com/shayu/onetoone/manager/CallClientManager.java +++ b/OneToOne/src/main/java/com/shayu/onetoone/manager/CallClientManager.java @@ -1,26 +1,41 @@ package com.shayu.onetoone.manager; import android.Manifest; +import android.os.Bundle; +import android.text.TextUtils; import android.view.SurfaceView; import com.blankj.utilcode.util.PermissionUtils; import com.shayu.onetoone.listener.OnCallStatusListener; +import com.yunbao.common.CommonAppContext; import com.yunbao.common.manager.IMLoginManager; +import com.yunbao.common.utils.ToastUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; +import io.rong.callkit.util.CallKitUtils; +import io.rong.calllib.CallUserProfile; import io.rong.calllib.IRongCallListener; import io.rong.calllib.IRongReceivedCallListener; import io.rong.calllib.RongCallClient; import io.rong.calllib.RongCallCommon; import io.rong.calllib.RongCallSession; import io.rong.calllib.StartCameraCallback; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.common.RLog; +import io.rong.imkit.IMCenter; +import io.rong.imlib.RongIMClient; import io.rong.imlib.model.Conversation; public class CallClientManager { - public static CallClientManager manager; + public static final String VIDEO_RECEIVED_CALL = "receivedCall"; + public static final String VIDEO_CALL = "call"; + private static CallClientManager manager; + // private SurfaceView localVideo, remoteVideo; + private List listeners; public static CallClientManager getManager() { if (manager == null) { @@ -30,31 +45,139 @@ public class CallClientManager { } private CallClientManager() { + listeners = new ArrayList<>(); init(); } + public SurfaceView getLocalVideo() { + RongCallSession session = RongCallClient.getInstance().getCallSession(); + String userId= IMLoginManager.get(CommonAppContext.getTopActivity()).getUserInfo().getId()+""; + for (CallUserProfile profile : session.getParticipantProfileList()) { + if(profile.getUserId().equals(userId)){ + return profile.getVideoView(); + } + + } + return null; + } + + public SurfaceView getRemoteVideo(String id) { + RongCallSession session = RongCallClient.getInstance().getCallSession(); + for (CallUserProfile profile : session.getParticipantProfileList()) { + if(profile.getUserId().equals(id)){ + return profile.getVideoView(); + } + + } + return null; + } + private void init() { RongCallClient.setReceivedCallListener(new CallMeListener()); } - public void callVideo(String targetId, OnCallStatusListener statusListener) { - List userIds = new ArrayList<>(); - userIds.add(targetId); - RongCallClient.getInstance().setVoIPCallListener(new CallStatusListener(statusListener)); - RongCallClient.getInstance().startCall(Conversation.ConversationType.PRIVATE,targetId,userIds,null, RongCallCommon.CallMediaType.VIDEO,null); + + public void addOnVoIPCallListener(OnCallStatusListener statusListener) { + listeners.add(statusListener); } + public void removeOnVoIPCallListener(OnCallStatusListener statusListener) { + listeners.remove(statusListener); + } + public void callVideo(String targetId) { + RongCallClient.getInstance().setVoIPCallListener(new CallStatusListener(new OnCallStatusListener() { + @Override + public void onCallWait(SurfaceView localVideo) { + for (OnCallStatusListener listener : listeners) { + listener.onCallWait(localVideo); + } + } + + @Override + public void onCallStart(String userId, SurfaceView remoteVideo) { + for (OnCallStatusListener listener : listeners) { + listener.onCallStart(userId, remoteVideo); + } + } + + @Override + public void onCallEnd() { + for (OnCallStatusListener listener : listeners) { + listener.onCallEnd(); + } + } + + @Override + public void onStartFirstFrame() { + for (OnCallStatusListener listener : listeners) { + listener.onStartFirstFrame(); + } + } + })); + List userIds = new ArrayList<>(); + userIds.add(targetId); + RongCallClient.getInstance().startCall(Conversation.ConversationType.PRIVATE, targetId, userIds, null, RongCallCommon.CallMediaType.VIDEO, null); + } + + public void acceptCall(String callId) { + RongCallClient.getInstance().setVoIPCallListener(new CallStatusListener(new OnCallStatusListener() { + @Override + public void onCallWait(SurfaceView localVideo) { + for (OnCallStatusListener listener : listeners) { + listener.onCallWait(localVideo); + } + } + + @Override + public void onCallStart(String userId, SurfaceView remoteVideo) { + for (OnCallStatusListener listener : listeners) { + listener.onCallStart(userId, remoteVideo); + } + } + + @Override + public void onCallEnd() { + for (OnCallStatusListener listener : listeners) { + listener.onCallEnd(); + } + } + + @Override + public void onStartFirstFrame() { + for (OnCallStatusListener listener : listeners) { + listener.onStartFirstFrame(); + } + } + })); + RongCallClient.getInstance().acceptCall(callId); + } + + public void endCall() { + if (RongCallClient.getInstance() != null && RongCallClient.getInstance().getCallSession() != null) { + RongCallClient.getInstance().hangUpCall(RongCallClient.getInstance().getCallSession().getCallId()); + } + } + + public boolean isCalling() { + return RongCallClient.getInstance() != null && RongCallClient.getInstance().getCallSession() != null; + } private static class CallMeListener implements IRongReceivedCallListener { @Override public void onReceivedCall(RongCallSession callSession) { - + Bundle bundle = new Bundle(); + bundle.putString("model", VIDEO_RECEIVED_CALL); + bundle.putString("targetId", callSession.getTargetId()); + bundle.putString("callId", callSession.getCallId()); + bundle.putString("sessionId", callSession.getSessionId()); + RouteManager.forwardActivity(RouteManager.ACTIVITY_CALL_VIDEO, bundle); } @Override public void onCheckPermission(RongCallSession callSession) { - PermissionUtils.permission(Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO).callback(new PermissionUtils.SimpleCallback() { + ToastUtil.show("权限申请"); + PermissionUtils.permission(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).callback(new PermissionUtils.SimpleCallback() { @Override public void onGranted() { RongCallClient.getInstance().onPermissionGranted(); @@ -64,21 +187,28 @@ public class CallClientManager { public void onDenied() { RongCallClient.getInstance().onPermissionDenied(); } - }); + }).request(); } } - private static class CallStatusListener implements IRongCallListener { + private class CallStatusListener implements IRongCallListener { OnCallStatusListener statusListener; + private long time = 0; public CallStatusListener(OnCallStatusListener statusListener) { this.statusListener = statusListener; } + public long getTime(long activeTime) { + long tmpTime = activeTime == 0 ? 0 : (System.currentTimeMillis() - activeTime) / 1000; + time = tmpTime == 0 ? time : tmpTime; + return time; + } + @Override public void onCallIncoming(RongCallSession callSession, SurfaceView localVideo) { - + System.out.println("CallStatusListener.onCallIncoming"); } /** @@ -90,7 +220,10 @@ public class CallClientManager { */ @Override public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) { + localVideo.setZOrderOnTop(true); + localVideo.setZOrderMediaOverlay(true); statusListener.onCallWait(localVideo); + System.out.println("CallStatusListener.onCallOutgoing"); } /** @@ -102,7 +235,9 @@ public class CallClientManager { */ @Override public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) { - + localVideo.setZOrderOnTop(true); + localVideo.setZOrderMediaOverlay(true); + statusListener.onCallWait(localVideo); } /** @@ -114,17 +249,72 @@ public class CallClientManager { */ @Override public void onCallDisconnected(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) { + System.out.println("CallStatusListener.onCallDisconnected"); + + String senderId; + String extra = ""; + + if (callSession == null) { + RLog.e("CallStatusListener", "onCallDisconnected. callSession is null!"); + statusListener.onCallEnd(); + return; + } + senderId = callSession.getInviterUserId(); + long time = getTime(callSession.getActiveTime()); + if (time > 0) { + if (time >= 3600) { + extra = + String.format( + Locale.ROOT, + "%d:%02d:%02d", + time / 3600, + (time % 3600) / 60, + (time % 60)); + } else { + extra = String.format(Locale.ROOT, "%02d:%02d", (time % 3600) / 60, (time % 60)); + } + } + if (!TextUtils.isEmpty(senderId)) { + CallSTerminateMessage message = new CallSTerminateMessage(); + message.setReason(reason); + message.setMediaType(callSession.getMediaType()); + message.setExtra(extra); + long serverTime = + System.currentTimeMillis() - RongIMClient.getInstance().getDeltaTime(); + if (senderId.equals(callSession.getSelfUserId())) { + message.setDirection("MO"); + IMCenter.getInstance() + .insertOutgoingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + io.rong.imlib.model.Message.SentStatus.SENT, + message, + serverTime, + null); + } else { + message.setDirection("MT"); + IMCenter.getInstance() + .insertIncomingMessage( + Conversation.ConversationType.PRIVATE, + callSession.getTargetId(), + senderId, + CallKitUtils.getReceivedStatus(reason), + message, + serverTime, + null); + } + } statusListener.onCallEnd(); } @Override public void onRemoteUserRinging(String userId) { - + System.out.println("CallStatusListener.onRemoteUserRinging"); } @Override public void onRemoteUserAccept(String userId, RongCallCommon.CallMediaType mediaType) { - + System.out.println("CallStatusListener.onRemoteUserAccept"); } /** @@ -138,21 +328,24 @@ public class CallClientManager { * 如果对端调用{@link RongCallClient#startCall(int, boolean, Conversation.ConversationType, String, List, List, RongCallCommon.CallMediaType, String, StartCameraCallback)} 或 * {@link RongCallClient#acceptCall(String, int, boolean, StartCameraCallback)}开始的音视频通话,则可以使用如下设置改变对端视频流的镜像显示:
*
-         *                                            public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
-         *                                                 if (null != remoteVideo) {
-         *                                                     ((RongRTCVideoView) remoteVideo).setMirror( boolean);//观看对方视频流是否镜像处理
-         *                                                 }
-         *                                            }
-         *                                            
+ * public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) { + * if (null != remoteVideo) { + * ((RongRTCVideoView) remoteVideo).setMirror( boolean);//观看对方视频流是否镜像处理 + * } + * } + * */ @Override public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) { - statusListener.onCallStart(userId,remoteVideo); + remoteVideo.setZOrderOnTop(false); + remoteVideo.setZOrderMediaOverlay(false); + statusListener.onCallStart(userId, remoteVideo); + System.out.println("CallStatusListener.onRemoteUserJoined"); } @Override public void onRemoteUserInvited(String userId, RongCallCommon.CallMediaType mediaType) { - + System.out.println("CallStatusListener.onRemoteUserInvited"); } /** @@ -164,17 +357,23 @@ public class CallClientManager { */ @Override public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) { - + System.out.println("CallStatusListener.onRemoteUserLeft"); + if (statusListener != null) { + statusListener.onCallEnd(); + } } @Override public void onMediaTypeChanged(String userId, RongCallCommon.CallMediaType mediaType, SurfaceView video) { - + System.out.println("CallStatusListener.onMediaTypeChanged"); } @Override public void onError(RongCallCommon.CallErrorCode errorCode) { - + System.out.println("CallStatusListener.onError"); + if (statusListener != null) { + statusListener.onCallEnd(); + } } @Override @@ -199,7 +398,9 @@ public class CallClientManager { @Override public void onFirstRemoteVideoFrame(String userId, int height, int width) { - + if (statusListener != null) { + statusListener.onStartFirstFrame(); + } } @Override diff --git a/OneToOne/src/main/java/com/shayu/onetoone/provider/OTOCallEndMessageItemProvider.java b/OneToOne/src/main/java/com/shayu/onetoone/provider/OTOCallEndMessageItemProvider.java new file mode 100644 index 000000000..733a97bdb --- /dev/null +++ b/OneToOne/src/main/java/com/shayu/onetoone/provider/OTOCallEndMessageItemProvider.java @@ -0,0 +1,255 @@ +package com.shayu.onetoone.provider; + + +import static io.rong.calllib.RongCallCommon.CallDisconnectedReason.HANGUP; +import static io.rong.calllib.RongCallCommon.CallDisconnectedReason.OTHER_DEVICE_HAD_ACCEPTED; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import com.shayu.onetoone.R; +import com.yunbao.common.utils.ToastUtil; + +import io.rong.callkit.RongCallAction; +import io.rong.callkit.util.CallKitUtils; +import io.rong.calllib.RongCallClient; +import io.rong.calllib.RongCallCommon; +import io.rong.calllib.RongCallSession; +import io.rong.calllib.message.CallSTerminateMessage; +import io.rong.imkit.conversation.messgelist.provider.BaseMessageItemProvider; +import io.rong.imkit.model.UiMessage; +import io.rong.imkit.widget.adapter.IViewProviderListener; +import io.rong.imkit.widget.adapter.ViewHolder; +import io.rong.imlib.model.Message; +import io.rong.imlib.model.MessageContent; +import java.util.List; +import java.util.Locale; + +public class OTOCallEndMessageItemProvider extends BaseMessageItemProvider { + @Override + protected io.rong.imkit.widget.adapter.ViewHolder onCreateMessageContentViewHolder( + ViewGroup parent, int viewType) { + View textView = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.rc_text_message_item, parent, false); + return new ViewHolder(parent.getContext(), textView); + } + + @Override + protected void bindMessageContentViewHolder( + ViewHolder holder, + ViewHolder parentHolder, + CallSTerminateMessage callSTerminateMessage, + UiMessage uiMessage, + int position, + List list, + IViewProviderListener listener) { + Message message = uiMessage.getMessage(); + final TextView view = holder.getView(io.rong.imkit.R.id.rc_text); + if (message.getMessageDirection() == Message.MessageDirection.SEND) { + view.setBackgroundResource(R.drawable.rc_ic_bubble_right); + } else { + view.setBackgroundResource(R.drawable.rc_ic_bubble_left); + } + + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + String direction = callSTerminateMessage.getDirection(); + Drawable drawable = null; + + String msgContent = ""; + switch (callSTerminateMessage.getReason()) { + case CANCEL: + msgContent = view.getResources().getString(R.string.rc_voip_mo_cancel); + break; + case REJECT: + msgContent = view.getResources().getString(R.string.rc_voip_mo_reject); + break; + case NO_RESPONSE: + case BUSY_LINE: + msgContent = view.getResources().getString(R.string.rc_voip_mo_no_response); + break; + case REMOTE_BUSY_LINE: + msgContent = view.getResources().getString(R.string.rc_voip_mt_busy); + break; + case REMOTE_CANCEL: + msgContent = view.getResources().getString(R.string.rc_voip_mt_cancel); + break; + case REMOTE_REJECT: + msgContent = view.getResources().getString(R.string.rc_voip_mt_reject); + break; + case REMOTE_NO_RESPONSE: + msgContent = view.getResources().getString(R.string.rc_voip_mt_no_response); + break; + case NETWORK_ERROR: + case REMOTE_NETWORK_ERROR: + case INIT_VIDEO_ERROR: + msgContent = view.getResources().getString(R.string.rc_voip_call_interrupt); + break; + case OTHER_DEVICE_HAD_ACCEPTED: + msgContent = view.getResources().getString(R.string.rc_voip_call_other); + break; + case SERVICE_NOT_OPENED: + case REMOTE_ENGINE_UNSUPPORTED: + msgContent = view.getResources().getString(R.string.rc_voip_engine_notfound); + break; + case REJECTED_BY_BLACKLIST: + msgContent = + view.getResources().getString(R.string.rc_voip_mo_rejected_by_blocklist); + break; + default: + String mo_reject = view.getResources().getString(R.string.rc_voip_mo_reject); + String mt_reject = view.getResources().getString(R.string.rc_voip_mt_reject); + String extra = callSTerminateMessage.getExtra(); + String timeRegex = "([0-9]?[0-9]:)?([0-5][0-9]:)?([0-5][0-9])$"; + if (!TextUtils.isEmpty(extra)) { + boolean val = extra.matches(timeRegex); + if (val) { + msgContent = + view.getResources().getString(R.string.rc_voip_call_time_length); + msgContent += extra; + } else { + msgContent = + view.getResources().getString(R.string.rc_voip_call_time_length); + } + } else { + msgContent = + callSTerminateMessage.getReason() == HANGUP ? mo_reject : mt_reject; + } + break; + } + + view.setText(msgContent); + view.setCompoundDrawablePadding(15); + + if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + if (direction != null && direction.equals("MO")) { + drawable = view.getResources().getDrawable(R.drawable.rc_voip_video_right); + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(null, null, drawable, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_right)); + } else { + drawable = view.getResources().getDrawable(R.drawable.rc_voip_video_left); + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(drawable, null, null, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_left)); + } + } else { + if (direction != null && direction.equals("MO")) { + if (callSTerminateMessage.getReason().equals(HANGUP) + || callSTerminateMessage + .getReason() + .equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) { + drawable = + view.getResources() + .getDrawable(R.drawable.rc_voip_audio_right_connected); + } else { + drawable = + view.getResources().getDrawable(R.drawable.rc_voip_audio_right_cancel); + } + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(null, null, drawable, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_right)); + } else { + if (callSTerminateMessage.getReason().equals(HANGUP) + || callSTerminateMessage + .getReason() + .equals(RongCallCommon.CallDisconnectedReason.REMOTE_HANGUP)) { + drawable = + view.getResources() + .getDrawable(R.drawable.rc_voip_audio_left_connected); + } else { + drawable = + view.getResources().getDrawable(R.drawable.rc_voip_audio_left_cancel); + } + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + view.setCompoundDrawablesRelative(drawable, null, null, null); + view.setTextColor(view.getResources().getColor(R.color.rc_voip_color_left)); + } + } + } + + @Override + protected boolean onItemClick( + ViewHolder holder, + CallSTerminateMessage callSTerminateMessage, + UiMessage uiMessage, + int position, + List list, + IViewProviderListener listener) { + if (callSTerminateMessage.getReason() == OTHER_DEVICE_HAD_ACCEPTED) { + return true; + } + ToastUtil.show("点了"); + Context context = holder.getContext(); + RongCallSession profile = RongCallClient.getInstance().getCallSession(); + if (profile != null && profile.getActiveTime() > 0) { + if (profile.getMediaType() == RongCallCommon.CallMediaType.AUDIO) { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_audio_start_fail), + Toast.LENGTH_SHORT) + .show(); + } else { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_video_start_fail), + Toast.LENGTH_SHORT) + .show(); + } + return true; + } + if (!CallKitUtils.isNetworkAvailable(context)) { + Toast.makeText( + context, + context.getString(R.string.rc_voip_call_network_error), + Toast.LENGTH_SHORT) + .show(); + return true; + } + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + String action = null; + if (mediaType.equals(RongCallCommon.CallMediaType.VIDEO)) { + action = "io.rong.intent.action.voip.SINGLEVIDEO"; + } else { + action = "io.rong.intent.action.voip.SINGLEAUDIO"; + } + Intent intent = new Intent(action); + intent.setPackage(context.getPackageName()); + intent.putExtra( + "conversationType", + uiMessage.getMessage().getConversationType().getName().toLowerCase(Locale.US)); + intent.putExtra("targetId", uiMessage.getMessage().getTargetId()); + intent.putExtra("callAction", RongCallAction.ACTION_OUTGOING_CALL.getName()); + context.startActivity(intent); + return true; + } + + @Override + protected boolean isMessageViewType(MessageContent messageContent) { + return messageContent instanceof CallSTerminateMessage; + } + + @Override + public Spannable getSummarySpannable( + Context context, CallSTerminateMessage callSTerminateMessage) { + RongCallCommon.CallMediaType mediaType = callSTerminateMessage.getMediaType(); + if (mediaType.equals(RongCallCommon.CallMediaType.AUDIO)) { + return new SpannableString(context.getString(R.string.rc_voip_message_audio)); + } else { + return new SpannableString(context.getString(R.string.rc_voip_message_video)); + } + } +} diff --git a/OneToOne/src/main/res/layout/activity_call_video.xml b/OneToOne/src/main/res/layout/activity_call_video.xml index 92b34aba1..3d0714f08 100644 --- a/OneToOne/src/main/res/layout/activity_call_video.xml +++ b/OneToOne/src/main/res/layout/activity_call_video.xml @@ -2,13 +2,15 @@ - - - + app:layout_constraintEnd_toEndOf="parent"> + - + - + - + + - + - + - + + - + - - - - - \ No newline at end of file diff --git a/OneToOne/src/main/res/layout/view_call_video_item.xml b/OneToOne/src/main/res/layout/view_call_video_item.xml new file mode 100644 index 000000000..d36c6a597 --- /dev/null +++ b/OneToOne/src/main/res/layout/view_call_video_item.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OneToOne/src/main/res/layout/view_message_input_gift.xml b/OneToOne/src/main/res/layout/view_message_input_gift.xml index 26d8cacfb..768884700 100644 --- a/OneToOne/src/main/res/layout/view_message_input_gift.xml +++ b/OneToOne/src/main/res/layout/view_message_input_gift.xml @@ -11,6 +11,7 @@ android:layout_width="0dp" android:layout_height="165dp" android:layout_marginStart="16dp" + android:layout_marginTop="15dp" android:layout_marginEnd="16dp" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layout_constraintEnd_toEndOf="parent" diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_call_msg.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_call_msg.png index e0490a4794bdc901ce6007f856928eee7e037c76..50436a7a415b309f7683b207e902b33acbc753fb 100644 GIT binary patch literal 9975 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 P00000NkvXXu0mjfl4(`6