package com.netease.nim.demo.avchat.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Toast; import com.netease.nim.demo.R; import com.netease.nim.demo.avchat.AVChatNotification; import com.netease.nim.demo.avchat.AVChatProfile; import com.netease.nim.demo.avchat.AVChatUI; import com.netease.nim.demo.avchat.AVChatSoundPlayer; import com.netease.nim.demo.avchat.constant.CallStateEnum; import com.netease.nim.demo.avchat.receiver.PhoneCallStateObserver; import com.netease.nim.uikit.common.activity.UI; import com.netease.nim.uikit.common.util.log.LogUtil; import com.netease.nim.uikit.common.util.sys.NetworkUtil; import com.netease.nimlib.sdk.NIMClient; import com.netease.nimlib.sdk.Observer; import com.netease.nimlib.sdk.StatusCode; import com.netease.nimlib.sdk.auth.AuthServiceObserver; import com.netease.nimlib.sdk.auth.ClientType; import com.netease.nimlib.sdk.avchat.AVChatManager; import com.netease.nimlib.sdk.avchat.AVChatStateObserver; import com.netease.nimlib.sdk.avchat.constant.AVChatControlCommand; import com.netease.nimlib.sdk.avchat.constant.AVChatEventType; import com.netease.nimlib.sdk.avchat.constant.AVChatType; import com.netease.nimlib.sdk.avchat.model.AVChatAudioFrame; import com.netease.nimlib.sdk.avchat.model.AVChatCalleeAckEvent; import com.netease.nimlib.sdk.avchat.model.AVChatCommonEvent; import com.netease.nimlib.sdk.avchat.model.AVChatControlEvent; import com.netease.nimlib.sdk.avchat.model.AVChatData; import com.netease.nimlib.sdk.avchat.model.AVChatOnlineAckEvent; import com.netease.nimlib.sdk.avchat.model.AVChatVideoFrame; import java.util.Map; /** * 音视频界面 * Created by hzxuwen on 2015/4/21. */ public class AVChatActivity extends UI implements AVChatUI.AVChatListener, AVChatStateObserver { // constant private static final String TAG = "AVChatActivity"; private static final String KEY_IN_CALLING = "KEY_IN_CALLING"; private static final String KEY_ACCOUNT = "KEY_ACCOUNT"; private static final String KEY_CALL_TYPE = "KEY_CALL_TYPE"; private static final String KEY_SOURCE = "source"; private static final String KEY_CALL_CONFIG = "KEY_CALL_CONFIG"; public static final String INTENT_ACTION_AVCHAT = "INTENT_ACTION_AVCHAT"; /** * 来自广播 */ public static final int FROM_BROADCASTRECEIVER = 0; /** * 来自发起方 */ public static final int FROM_INTERNAL = 1; /** * 来自通知栏 */ public static final int FROM_NOTIFICATION = 2; /** * 未知的入口 */ public static final int FROM_UNKNOWN = -1; // data private AVChatUI avChatUI; // 音视频总管理器 private AVChatData avChatData; // config for connect video server private int state; // calltype 音频或视频 private String receiverId; // 对方的account // state private boolean isUserFinish = false; private boolean mIsInComingCall = false;// is incoming call or outgoing call private boolean isCallEstablished = false; // 电话是否接通 private static boolean needFinish = true; // 若来电或去电未接通时,点击home。另外一方挂断通话。从最近任务列表恢复,则finish private boolean hasOnPause = false; // 是否暂停音视频 // notification private AVChatNotification notifier; public static void launch(Context context, String account, int callType, int source) { needFinish = false; Intent intent = new Intent(); intent.setClass(context, AVChatActivity.class); intent.putExtra(KEY_ACCOUNT, account); intent.putExtra(KEY_IN_CALLING, false); intent.putExtra(KEY_CALL_TYPE, callType); intent.putExtra(KEY_SOURCE, source); context.startActivity(intent); } /** * incoming call * * @param context */ public static void launch(Context context, AVChatData config, int source) { needFinish = false; Intent intent = new Intent(); intent.setClass(context, AVChatActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(KEY_CALL_CONFIG, config); intent.putExtra(KEY_IN_CALLING, true); intent.putExtra(KEY_SOURCE, source); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (needFinish || !checkSource()) { finish(); return; } View root = LayoutInflater.from(this).inflate(R.layout.avchat_activity, null); setContentView(root); mIsInComingCall = getIntent().getBooleanExtra(KEY_IN_CALLING, false); avChatUI = new AVChatUI(this, root, this); if (!avChatUI.initiation()) { this.finish(); return; } registerNetCallObserver(true); if (mIsInComingCall) { inComingCalling(); } else { outgoingCalling(); } notifier = new AVChatNotification(this); notifier.init(receiverId != null ? receiverId : avChatData.getAccount()); isCallEstablished = false; //放到所有UI的基类里面注册,所有的UI实现onKickOut接口 NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(userStatusObserver, true); } @Override protected void onPause() { super.onPause(); avChatUI.pauseVideo(); // 暂停视频聊天(用于在视频聊天过程中,APP退到后台时必须调用) hasOnPause = true; } @Override protected void onStop() { super.onStop(); activeCallingNotifier(); } @Override protected void onResume() { super.onResume(); cancelCallingNotifier(); if (hasOnPause) { avChatUI.resumeVideo(); hasOnPause = false; } } @Override protected void onDestroy() { super.onDestroy(); NIMClient.getService(AuthServiceObserver.class).observeOnlineStatus(userStatusObserver, false); AVChatProfile.getInstance().setAVChatting(false); registerNetCallObserver(false); cancelCallingNotifier(); needFinish = true; } @Override public void onBackPressed() { } /** * 判断来电还是去电 * * @return */ private boolean checkSource() { switch (getIntent().getIntExtra(KEY_SOURCE, FROM_UNKNOWN)) { case FROM_BROADCASTRECEIVER: // incoming call parseIncomingIntent(); return true; case FROM_INTERNAL: // outgoing call parseOutgoingIntent(); if (state == AVChatType.VIDEO.getValue() || state == AVChatType.AUDIO.getValue()) { return true; } return false; default: return false; } } /** * 来电参数解析 */ private void parseIncomingIntent() { avChatData = (AVChatData) getIntent().getSerializableExtra(KEY_CALL_CONFIG); state = avChatData.getChatType().getValue(); } /** * 去电参数解析 */ private void parseOutgoingIntent() { receiverId = getIntent().getStringExtra(KEY_ACCOUNT); state = getIntent().getIntExtra(KEY_CALL_TYPE, -1); } /** * 注册监听 * * @param register */ private void registerNetCallObserver(boolean register) { AVChatManager.getInstance().observeAVChatState(this, register); AVChatManager.getInstance().observeCalleeAckNotification(callAckObserver, register); AVChatManager.getInstance().observeControlNotification(callControlObserver, register); AVChatManager.getInstance().observeHangUpNotification(callHangupObserver, register); AVChatManager.getInstance().observeOnlineAckNotification(onlineAckObserver, register); AVChatManager.getInstance().observeTimeoutNotification(timeoutObserver, register); PhoneCallStateObserver.getInstance().observeAutoHangUpForLocalPhone(autoHangUpForLocalPhoneObserver, register); } /** * 注册/注销网络通话被叫方的响应(接听、拒绝、忙) */ Observer<AVChatCalleeAckEvent> callAckObserver = new Observer<AVChatCalleeAckEvent>() { @Override public void onEvent(AVChatCalleeAckEvent ackInfo) { AVChatSoundPlayer.instance().stop(); if (ackInfo.getEvent() == AVChatEventType.CALLEE_ACK_BUSY) { AVChatSoundPlayer.instance().play(AVChatSoundPlayer.RingerTypeEnum.PEER_BUSY); avChatUI.closeSessions(AVChatExitCode.PEER_BUSY); } else if (ackInfo.getEvent() == AVChatEventType.CALLEE_ACK_REJECT) { avChatUI.closeSessions(AVChatExitCode.REJECT); } else if (ackInfo.getEvent() == AVChatEventType.CALLEE_ACK_AGREE) { avChatUI.isCallEstablish.set(true); avChatUI.canSwitchCamera = true; } } }; Observer<Long> timeoutObserver = new Observer<Long>() { @Override public void onEvent(Long chatId) { AVChatData info = avChatUI.getAvChatData(); if (info != null && info.getChatId() == chatId) { avChatUI.closeSessions(AVChatExitCode.PEER_NO_RESPONSE); // 来电超时,自己未接听 if (mIsInComingCall) { activeMissCallNotifier(); } AVChatSoundPlayer.instance().stop(); } } }; Observer<Integer> autoHangUpForLocalPhoneObserver = new Observer<Integer>() { @Override public void onEvent(Integer integer) { AVChatSoundPlayer.instance().stop(); avChatUI.closeSessions(AVChatExitCode.PEER_BUSY); } }; /** * 注册/注销网络通话控制消息(音视频模式切换通知) */ Observer<AVChatControlEvent> callControlObserver = new Observer<AVChatControlEvent>() { @Override public void onEvent(AVChatControlEvent netCallControlNotification) { handleCallControl(netCallControlNotification); } }; /** * 注册/注销网络通话对方挂断的通知 */ Observer<AVChatCommonEvent> callHangupObserver = new Observer<AVChatCommonEvent>() { @Override public void onEvent(AVChatCommonEvent avChatHangUpInfo) { AVChatSoundPlayer.instance().stop(); avChatUI.closeSessions(AVChatExitCode.HANGUP); cancelCallingNotifier(); // 如果是incoming call主叫方挂断,那么通知栏有通知 if (mIsInComingCall && !isCallEstablished) { activeMissCallNotifier(); } } }; /** * 注册/注销同时在线的其他端对主叫方的响应 */ Observer<AVChatOnlineAckEvent> onlineAckObserver = new Observer<AVChatOnlineAckEvent>() { @Override public void onEvent(AVChatOnlineAckEvent ackInfo) { AVChatSoundPlayer.instance().stop(); String client = null; switch (ackInfo.getClientType()) { case ClientType.Web: client = "Web"; break; case ClientType.Windows: client = "Windows"; break; case ClientType.Android: client = "Android"; break; case ClientType.iOS: client = "iOS"; break; default: break; } if (client != null) { String option = ackInfo.getEvent() == AVChatEventType.CALLEE_ONLINE_CLIENT_ACK_AGREE ? "接听!" : "拒绝!"; Toast.makeText(AVChatActivity.this, "通话已在" + client + "端被" + option, Toast.LENGTH_SHORT).show(); } avChatUI.closeSessions(-1); } }; /** * 接听 */ private void inComingCalling() { avChatUI.inComingCalling(avChatData); } /** * 拨打 */ private void outgoingCalling() { if (!NetworkUtil.isNetAvailable(AVChatActivity.this)) { // 网络不可用 Toast.makeText(this, R.string.network_is_not_available, Toast.LENGTH_SHORT).show(); finish(); return; } avChatUI.outGoingCalling(receiverId, AVChatType.typeOfValue(state)); } /** * *************************** AVChatListener ********************************* */ @Override public void uiExit() { finish(); } /****************************** 连接建立处理 ********************/ /** * 处理连接服务器的返回值 * * @param auth_result */ protected void handleWithConnectServerResult(int auth_result) { LogUtil.i(TAG, "result code->" + auth_result); if (auth_result == 200) { Log.d(TAG, "onConnectServer success"); } else if (auth_result == 101) { // 连接超时 avChatUI.closeSessions(AVChatExitCode.PEER_NO_RESPONSE); } else if (auth_result == 401) { // 验证失败 avChatUI.closeSessions(AVChatExitCode.CONFIG_ERROR); } else if (auth_result == 417) { // 无效的channelId avChatUI.closeSessions(AVChatExitCode.INVALIDE_CHANNELID); } else { // 连接服务器错误,直接退出 avChatUI.closeSessions(AVChatExitCode.CONFIG_ERROR); } } /**************************** 处理音视频切换 *********************************/ /** * 处理音视频切换请求 * * @param notification */ private void handleCallControl(AVChatControlEvent notification) { switch (notification.getControlCommand()) { case AVChatControlCommand.SWITCH_AUDIO_TO_VIDEO: avChatUI.incomingAudioToVideo(); break; case AVChatControlCommand.SWITCH_AUDIO_TO_VIDEO_AGREE: onAudioToVideo(); break; case AVChatControlCommand.SWITCH_AUDIO_TO_VIDEO_REJECT: avChatUI.onCallStateChange(CallStateEnum.AUDIO); Toast.makeText(AVChatActivity.this, R.string.avchat_switch_video_reject, Toast.LENGTH_SHORT).show(); break; case AVChatControlCommand.SWITCH_VIDEO_TO_AUDIO: onVideoToAudio(); break; case AVChatControlCommand.NOTIFY_VIDEO_OFF: avChatUI.peerVideoOff(); break; case AVChatControlCommand.NOTIFY_VIDEO_ON: avChatUI.peerVideoOn(); break; default: Toast.makeText(this, "对方发来指令值:" + notification.getControlCommand(), Toast.LENGTH_SHORT).show(); break; } } /** * 音频切换为视频 */ private void onAudioToVideo() { avChatUI.onAudioToVideo(); avChatUI.initAllSurfaceView(avChatUI.getVideoAccount()); } /** * 视频切换为音频 */ private void onVideoToAudio() { avChatUI.onCallStateChange(CallStateEnum.AUDIO); avChatUI.onVideoToAudio(); } /** * 通知栏 */ private void activeCallingNotifier() { if (notifier != null && !isUserFinish) { notifier.activeCallingNotification(true); } } private void cancelCallingNotifier() { if (notifier != null) { notifier.activeCallingNotification(false); } } private void activeMissCallNotifier() { if (notifier != null) { notifier.activeMissCallNotification(true); } } @Override public void finish() { isUserFinish = true; super.finish(); } /** * ************************ AVChatStateObserver **************************** */ @Override public void onTakeSnapshotResult(String account, boolean success, String file) { } @Override public void onConnectionTypeChanged(int netType) { } @Override public void onAVRecordingCompletion(String account, String filePath) { if (account != null && filePath != null && filePath.length() > 0) { String msg = "音视频录制已结束, " + "账号:" + account + " 录制文件已保存至:" + filePath; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录制已结束.", Toast.LENGTH_SHORT).show(); } if (avChatUI != null) { avChatUI.resetRecordTip(); } } @Override public void onAudioRecordingCompletion(String filePath) { if (filePath != null && filePath.length() > 0) { String msg = "音频录制已结束, 录制文件已保存至:" + filePath; Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录制已结束.", Toast.LENGTH_SHORT).show(); } if (avChatUI != null) { avChatUI.resetRecordTip(); } } @Override public void onLowStorageSpaceWarning(long availableSize) { if (avChatUI != null) { avChatUI.showRecordWarning(); } } @Override public void onFirstVideoFrameAvailable(String account) { } @Override public void onVideoFpsReported(String account, int fps) { } @Override public void onJoinedChannel(int code, String audioFile, String videoFile) { handleWithConnectServerResult(code); } @Override public void onLeaveChannel() { } @Override public void onUserJoined(String account) { Log.d(TAG, "onUserJoin -> " + account); avChatUI.setVideoAccount(account); avChatUI.initLargeSurfaceView(avChatUI.getVideoAccount()); } @Override public void onUserLeave(String account, int event) { Log.d(TAG, "onUserLeave -> " + account); } @Override public void onProtocolIncompatible(int status) { } @Override public void onDisconnectServer() { } @Override public void onNetworkQuality(String user, int value) { } @Override public void onCallEstablished() { Log.d(TAG, "onCallEstablished"); if (avChatUI.getTimeBase() == 0) avChatUI.setTimeBase(SystemClock.elapsedRealtime()); if (state == AVChatType.AUDIO.getValue()) { avChatUI.onCallStateChange(CallStateEnum.AUDIO); } else { avChatUI.initSmallSurfaceView(); avChatUI.onCallStateChange(CallStateEnum.VIDEO); } isCallEstablished = true; } @Override public void onDeviceEvent(int code, String desc) { } @Override public void onFirstVideoFrameRendered(String user) { } @Override public void onVideoFrameResolutionChanged(String user, int width, int height, int rotate) { } @Override public boolean onVideoFrameFilter(AVChatVideoFrame frame) { return true; } @Override public boolean onAudioFrameFilter(AVChatAudioFrame frame) { return true; } @Override public void onAudioDeviceChanged(int device) { } @Override public void onReportSpeaker(Map<String, Integer> speakers, int mixedEnergy) { } @Override public void onAudioMixingEvent(int event) { } Observer<StatusCode> userStatusObserver = new Observer<StatusCode>() { @Override public void onEvent(StatusCode code) { if (code.wontAutoLogin()) { AVChatSoundPlayer.instance().stop(); finish(); } } }; }