先看一下核心代碼
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity------";
private EditText etWsUrl;//ws地址
private Button btnConnectSever;//連接ws
private EditText etLoginUserId;//登錄userId
private Button btnLogin;//登錄
private EditText etUserInfo;//在線用戶信息
private EditText etCallUserId;//呼叫userId
private Button btnCall;//呼叫
private SurfaceViewRenderer localView;//本地?cái)z像頭預(yù)覽(本地視頻)
private SurfaceViewRenderer remoteView;//服務(wù)器傳來的攝像頭預(yù)覽(對(duì)端視頻)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
checkPermission();
}
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//適配6.0權(quán)限
if (ContextCompat.checkSelfPermission(getApplication(),
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(getApplication(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(getApplication(),
Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.RECORD_AUDIO
}, 1);
} else {
//已經(jīng)有權(quán)限
havePermission();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.e(TAG, "onRequestPermissionsResult: ");
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, permissions[i] + "權(quán)限未打開", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
//已經(jīng)有權(quán)限
havePermission();
}
private EglBase.Context eglBaseContext;
private PeerConnectionFactory peerConnectionFactory;
private PeerConnection callPeerConnection;
private PeerConnection receivePeerConnection;
private MediaStream mMediaStream;
private void havePermission() {
Log.e(TAG, "havePermission: ");
//創(chuàng)建EglBase對(duì)象 并獲取上下文環(huán)境
eglBaseContext = EglBase.create().getEglBaseContext();
//1.初始化p2p連接工廠
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions
.builder(getApplicationContext())
.createInitializationOptions()
);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);//視頻編碼工廠
DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);//視頻解碼工廠
// JavaAudioDeviceModule.Builder admbuilder = JavaAudioDeviceModule.builder(this);
// admbuilder.setAudioSource(MediaRecorder.AudioSource.MIC);//控制錄音來源
// JavaAudioDeviceModule audioDeviceModule = admbuilder.createAudioDeviceModule();
//2.創(chuàng)建p2p連接工廠
peerConnectionFactory = PeerConnectionFactory.builder()
.setOptions(options)
.setVideoEncoderFactory(defaultVideoEncoderFactory)
.setVideoDecoderFactory(defaultVideoDecoderFactory)
// .setAudioDeviceModule(audioDeviceModule)
.createPeerConnectionFactory();
//3.創(chuàng)建SurfaceTextureHelper
SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
//4.創(chuàng)建視頻捕獲器
VideoCapturer videoCapturer = createCameraCapturer(false);//是否正面攝像頭
//5.創(chuàng)建視頻源
VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
//6.初始化視頻捕獲器
videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
//7.開始捕獲
videoCapturer.startCapture(480, 640, 30);
localView.setMirror(false);//是否鏡像
localView.init(eglBaseContext, null);//初始化SurfaceView
remoteView.setMirror(false);//是否鏡像
remoteView.init(eglBaseContext, null);//初始化SurfaceView
//8.創(chuàng)建視頻軌道
VideoTrack videoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);
videoTrack.addSink(localView);//展示本地視頻
//9.創(chuàng)建本地媒體流
mMediaStream = peerConnectionFactory.createLocalMediaStream("mMediaStream");
mMediaStream.addTrack(videoTrack);//媒體流添加視頻軌道
mMediaStream.addTrack(createAudioTrack());//媒體流添加音頻軌道
//連接websocket服務(wù)
btnConnectSever.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (TextUtils.isEmpty(etWsUrl.getText().toString())) {
Toast.makeText(MainActivity.this, "請輸入wsUrl", Toast.LENGTH_SHORT).show();
return;
}
RtcWebSokcetHelper.getInstance().connectServer(etWsUrl.getText().toString().trim(), new RtcWebSokcetHelper.OnReciveServerMgsListener() {
@Override
public void onConnect() {
Log.e(TAG, "onConnect: ");
Toast.makeText(MainActivity.this, "服務(wù)連接成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onRecive(String json) {
RtcWebSokcetHelper.WebsocketDealEvent websocketDealEvent;
Log.e(TAG, "onRecive: json=" + json);
try {
int code = new JSONObject(json).getInt("code");
switch (code) {
case Constant.SendMsg_Code://收到轉(zhuǎn)發(fā)消息
NetBean.SendMsgRequestBean sendMsgRequestBean = GsonUtil.GsonToBean(json, NetBean.SendMsgRequestBean.class);
if (sendMsgRequestBean.message.contains("不在線")) {
Toast.makeText(MainActivity.this, "目標(biāo)用戶不在線", Toast.LENGTH_SHORT).show();
return;
}
int turnCode = new JSONObject(sendMsgRequestBean.message).getInt("code");
switch (turnCode) {
case Constant.Message_Call_Code:
Log.e(TAG, "onRecive: Message_Call_Code");
showCustomeDialog("是否同意接聽 " + sendMsgRequestBean.fromUserId + " 的來電?",
"同意", new View.OnClickListener() {
@Override
public void onClick(View v) {
NetBean.SendMsgResponseBean sendMsgResponseBean = new NetBean.SendMsgResponseBean(Constant.Message_Call_Result_Code, sendMsgRequestBean.sequenceId, sendMsgRequestBean.toUserId, sendMsgRequestBean.fromUserId, true, "同意接聽");
NetBean.SendMsgRequestBean sendMsgRequestBean1 = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, sendMsgRequestBean.sequenceId, sendMsgRequestBean.toUserId, sendMsgRequestBean.fromUserId, GsonUtil.BeanToJson(sendMsgResponseBean));
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean1));
call(sendMsgRequestBean.toUserId, sendMsgRequestBean.fromUserId);//建立通話
}
}, "拒絕", new View.OnClickListener() {
@Override
public void onClick(View v) {
NetBean.SendMsgResponseBean sendMsgResponseBean = new NetBean.SendMsgResponseBean(Constant.Message_Call_Result_Code, sendMsgRequestBean.sequenceId, sendMsgRequestBean.toUserId, sendMsgRequestBean.fromUserId, false, "拒絕接聽");
NetBean.SendMsgRequestBean sendMsgRequestBean1 = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, sendMsgRequestBean.sequenceId, sendMsgRequestBean.toUserId, sendMsgRequestBean.fromUserId, GsonUtil.BeanToJson(sendMsgResponseBean));
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean1));
}
});
break;
case Constant.Message_SDP_Offer_Code:
Log.e(TAG, "--------onRecive: Message_SDP_Offer_Code 1");
NetBean.SdpMessage sdpMessage = GsonUtil.GsonToBean(sendMsgRequestBean.message, NetBean.SdpMessage.class);
receive(sdpMessage.description, sendMsgRequestBean.fromUserId, sendMsgRequestBean.toUserId);
break;
case Constant.Message_SDP_Answer_Code:
Log.e(TAG, "--------onRecive: Message_SDP_Answer_Code 2");
sdpMessage = GsonUtil.GsonToBean(sendMsgRequestBean.message, NetBean.SdpMessage.class);
SessionDescription sessionDescription = new SessionDescription(SessionDescription.Type.ANSWER, sdpMessage.description);
//11. receive sdp setRemoteDescription
callPeerConnection.setRemoteDescription(new SdpAdapter("setRemoteDescription"), sessionDescription);
break;
case Constant.Message_IceCandidate_Request_Code:
Log.e(TAG, "--------onRecive: Message_IceCandidate_Request_Code 3");
NetBean.IceCandidateMessage iceCandidateMessage = GsonUtil.GsonToBean(sendMsgRequestBean.message, NetBean.IceCandidateMessage.class);
IceCandidate iceCandidate = new IceCandidate(iceCandidateMessage.sdpMid, iceCandidateMessage.sdpMLineIndex, iceCandidateMessage.sdp);
//14.receive iceCandidate
receivePeerConnection.addIceCandidate(iceCandidate);
break;
case Constant.Message_IceCandidate_Response_Code:
Log.e(TAG, "--------onRecive: Message_IceCandidate_Response_Code 4");
iceCandidateMessage = GsonUtil.GsonToBean(sendMsgRequestBean.message, NetBean.IceCandidateMessage.class);
iceCandidate = new IceCandidate(iceCandidateMessage.sdpMid, iceCandidateMessage.sdpMLineIndex, iceCandidateMessage.sdp);
//16.receive iceCandidate addIceCandidate
callPeerConnection.addIceCandidate(iceCandidate);
break;
}
callBackEvent(json);
break;
case Constant.Login_Code://登錄通知
callBackEvent(json);
break;
case Constant.OFFLine_Code://斷線通知
NetBean.OffLineResponseBean offLineResponseBean = GsonUtil.GsonToBean(json, NetBean.OffLineResponseBean.class);
Toast.makeText(MainActivity.this, offLineResponseBean.message, Toast.LENGTH_SHORT).show();
break;
case Constant.OnLineUserInfo_Code://登錄在線人源信息通知
NetBean.OnLineUserInfoResponseBean onLineUserInfoResponseBean = GsonUtil.GsonToBean(json, NetBean.OnLineUserInfoResponseBean.class);
List<String> userIdList = onLineUserInfoResponseBean.userIdList;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < userIdList.size(); i++) {
String userId = userIdList.get(i);
if (userId.equals(etLoginUserId.getText().toString()))
sb.append(userId + "(自己)\n");
else
sb.append(userId + "\n");
}
etUserInfo.setText(sb.toString());
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
Log.e(TAG, "onClose: reason=" + reason);
}
});
}
});
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (TextUtils.isEmpty(etLoginUserId.getText().toString())) {
Toast.makeText(MainActivity.this, "請先輸入登錄UserId", Toast.LENGTH_SHORT).show();
return;
}
NetBean.LoginRequsetBean loginRequsetBean = new NetBean.LoginRequsetBean(Constant.Login_Code, System.currentTimeMillis() + "", etLoginUserId.getText().toString());
RtcWebSokcetHelper.getInstance().putWebsocketDealEvent(GsonUtil.BeanToJson(loginRequsetBean), new RtcWebSokcetHelper.Runnable() {
@Override
public void run() {
String responseJson = websocketDealEvent.responseJson;
NetBean.LoginResponseBean loginResponseBean = GsonUtil.GsonToBean(responseJson, NetBean.LoginResponseBean.class);
if (loginResponseBean.isSucceed) {
Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "登錄失敗祟牲," + loginResponseBean.message, Toast.LENGTH_SHORT).show();
}
}
});
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(loginRequsetBean));
}
});
btnCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String fromUserId = etLoginUserId.getText().toString();
String toUserId = etCallUserId.getText().toString();
if (TextUtils.isEmpty(fromUserId) || TextUtils.isEmpty(toUserId)) {
Toast.makeText(MainActivity.this, "請先填寫fromUserId,toUserId", Toast.LENGTH_SHORT).show();
return;
}
String message = GsonUtil.GsonToString(new NetBean.NormalMessage(Constant.Message_Call_Code));
NetBean.SendMsgRequestBean sendMsgRequestBean = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, System.currentTimeMillis() + "", fromUserId, toUserId, message);
RtcWebSokcetHelper.getInstance().putWebsocketDealEvent(GsonUtil.BeanToJson(sendMsgRequestBean), new RtcWebSokcetHelper.Runnable() {
@Override
public void run() {
String responseJson = websocketDealEvent.responseJson;
try {
responseJson = new JSONObject(websocketDealEvent.responseJson).getString("message");
} catch (JSONException e) {
e.printStackTrace();
}
NetBean.SendMsgResponseBean sendMsgResponseBean = GsonUtil.GsonToBean(responseJson, NetBean.SendMsgResponseBean.class);
if (sendMsgResponseBean.isSucceed) {
Log.e(TAG, "run: 呼叫成功");
Toast.makeText(MainActivity.this, "呼叫成功", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "run: 呼叫失敗");
Toast.makeText(MainActivity.this, "呼叫失敗," + sendMsgResponseBean.message, Toast.LENGTH_SHORT).show();
}
}
});
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean));
}
});
}
private void callBackEvent(String json) {
RtcWebSokcetHelper.WebsocketDealEvent websocketDealEvent;
websocketDealEvent = RtcWebSokcetHelper.getInstance().getWebsocketDealEvent(json);
if (websocketDealEvent != null && websocketDealEvent.dealRunnable != null) {
websocketDealEvent.responseJson = json;
websocketDealEvent.dealRunnable.run();
}
RtcWebSokcetHelper.getInstance().removeWebsocketDealEvent(json);
}
/**
* Create local audio track
*
* @return AudioTrack
*/
public AudioTrack createAudioTrack() {
AudioSource audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
AudioTrack audioTrack = peerConnectionFactory.createAudioTrack("200", audioSource);
audioTrack.setEnabled(true);
mMediaStream.addTrack(audioTrack);
return audioTrack;
}
private VideoCapturer createCameraCapturer(boolean isFront) {
Camera1Enumerator enumerator = new Camera1Enumerator(false);
final String[] deviceNames = enumerator.getDeviceNames();
// First, try to find front facing camera
for (String deviceName : deviceNames) {
if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
//建立WebRtc通話
private void call(String fromUserId, String toUserId) {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();//turn/sTurn服務(wù)器集合
PeerConnection.IceServer iceServer1 = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302")
.createIceServer();
PeerConnection.IceServer iceServer2 = PeerConnection.IceServer.builder("stun:stun.ekiga.net")
.createIceServer();
PeerConnection.IceServer iceServer3 = PeerConnection.IceServer.builder("stun:stun.schlund.de")
.createIceServer();
PeerConnection.IceServer iceServer4 = PeerConnection.IceServer.builder("stun:stun.voxgratia.org")
.createIceServer();
PeerConnection.IceServer iceServer5 = PeerConnection.IceServer.builder("turn:1.117.194.160:3478?transport=tcp")
.setUsername("test")
.setPassword("123456")
.createIceServer();
PeerConnection.IceServer iceServer6 = PeerConnection.IceServer.builder("turn:1.117.194.160:3478?transport=udp")
.setUsername("test")
.setPassword("123456")
.createIceServer();
iceServers.add(iceServer1);
iceServers.add(iceServer2);
iceServers.add(iceServer3);
iceServers.add(iceServer4);
iceServers.add(iceServer5);
iceServers.add(iceServer6);
//1.createPeerConnection
callPeerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") {
//12.onIceCandidate
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
// String sdpMid;
// int sdpMLineIndex;
// String sdp; //todo---WS轉(zhuǎn)發(fā)
//13.send iceCandidate
String message = GsonUtil.GsonToString(new NetBean.IceCandidateMessage(Constant.Message_IceCandidate_Request_Code, iceCandidate.sdpMid, iceCandidate.sdpMLineIndex, iceCandidate.sdp));
NetBean.SendMsgRequestBean sendMsgRequestBean = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, System.currentTimeMillis() + "", fromUserId, toUserId, message);
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean));
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
remoteVideoTrack.addSink(remoteView);//展示對(duì)端的視頻
});
}
});
//2.addStream
callPeerConnection.addStream(mMediaStream);//添加流
//3.createOffer
callPeerConnection.createOffer(new SdpAdapter("local offer sdp") {//發(fā)送offer
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
super.onCreateSuccess(sessionDescription);
//4.setLocalDescription
callPeerConnection.setLocalDescription(new SdpAdapter("local set local"), sessionDescription);//服務(wù)器中轉(zhuǎn)設(shè)置會(huì)話描述
//5.send sdp
// String description;todo---WS發(fā)送
String message = GsonUtil.GsonToString(new NetBean.SdpMessage(Constant.Message_SDP_Offer_Code, sessionDescription.description));
NetBean.SendMsgRequestBean sendMsgRequestBean = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, System.currentTimeMillis() + "", fromUserId, toUserId, message);
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean));
}
}, new MediaConstraints());
// //11. receive sdp setRemoteDescription
// callPeerConnection.setRemoteDescription(new SdpAdapter("setRemoteDescription"), null);
//16.receive iceCandidate addIceCandidate
// callPeerConnection.addIceCandidate(null);
}
private void receive(String description, String fromUserId, String toUserId) {
List<PeerConnection.IceServer> iceServers = new ArrayList<>();//turn/sTurn服務(wù)器集合
PeerConnection.IceServer iceServer1 = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302")
.createIceServer();
PeerConnection.IceServer iceServer2 = PeerConnection.IceServer.builder("stun:stun.ekiga.net")
.createIceServer();
PeerConnection.IceServer iceServer3 = PeerConnection.IceServer.builder("stun:stun.schlund.de")
.createIceServer();
PeerConnection.IceServer iceServer4 = PeerConnection.IceServer.builder("stun:stun.voxgratia.org")
.createIceServer();
PeerConnection.IceServer iceServer5 = PeerConnection.IceServer.builder("turn:1.117.194.160:3478?transport=tcp")
.setUsername("test")
.setPassword("123456")
.createIceServer();
PeerConnection.IceServer iceServer6 = PeerConnection.IceServer.builder("turn:1.117.194.160:3478?transport=udp")
.setUsername("test")
.setPassword("123456")
.createIceServer();
iceServers.add(iceServer1);
iceServers.add(iceServer2);
iceServers.add(iceServer3);
iceServers.add(iceServer4);
iceServers.add(iceServer5);
iceServers.add(iceServer6);
//6.createPeerConnection
receivePeerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("localconnection") {
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
super.onIceCandidate(iceCandidate);
// String sdpMid;
// int sdpMLineIndex;
// String sdp; //todo---WS轉(zhuǎn)發(fā)
//15.send iceCandidate
String message = GsonUtil.GsonToString(new NetBean.IceCandidateMessage(Constant.Message_IceCandidate_Response_Code, iceCandidate.sdpMid, iceCandidate.sdpMLineIndex, iceCandidate.sdp));
NetBean.SendMsgRequestBean sendMsgRequestBean = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, System.currentTimeMillis() + "", toUserId, fromUserId, message);
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean));
}
@Override
public void onAddStream(MediaStream mediaStream) {
super.onAddStream(mediaStream);
VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
runOnUiThread(() -> {
remoteVideoTrack.addSink(remoteView);//展示對(duì)端的視頻
});
}
});
receivePeerConnection.addStream(mMediaStream);//添加流
SessionDescription fromSdp = new SessionDescription(SessionDescription.Type.OFFER, description);
//7.setRemoteDescription
receivePeerConnection.setRemoteDescription(new SdpAdapter("Remote"), fromSdp);//被叫方設(shè)置會(huì)話描述
//8.createAnswer
receivePeerConnection.createAnswer(new SdpAdapter("remote answer sdp") {//發(fā)送sdp
@Override
public void onCreateSuccess(SessionDescription sdp) {
super.onCreateSuccess(sdp);
//9.setLocalDescription
receivePeerConnection.setLocalDescription(new SdpAdapter("Local"), sdp);//服務(wù)器中轉(zhuǎn)設(shè)置會(huì)話描述
//10.發(fā)送sdp給對(duì)方 對(duì)方設(shè)置遠(yuǎn)端sdp
String message = GsonUtil.GsonToString(new NetBean.SdpMessage(Constant.Message_SDP_Answer_Code, sdp.description));
NetBean.SendMsgRequestBean sendMsgRequestBean = new NetBean.SendMsgRequestBean(Constant.SendMsg_Code, System.currentTimeMillis() + "", toUserId, fromUserId, message);
RtcWebSokcetHelper.getInstance().sendRequest(GsonUtil.BeanToJson(sendMsgRequestBean));
}
}, new MediaConstraints());
// //14.receive iceCandidate
// receivePeerConnection.addIceCandidate(null);
}
private void initView() {
etWsUrl = (EditText) findViewById(R.id.et_wsUrl);
btnConnectSever = (Button) findViewById(R.id.btn_connectSever);
etLoginUserId = (EditText) findViewById(R.id.et_loginUserId);
btnLogin = (Button) findViewById(R.id.btn_login);
etUserInfo = (EditText) findViewById(R.id.et_userInfo);
etCallUserId = (EditText) findViewById(R.id.et_callUserId);
btnCall = (Button) findViewById(R.id.btn_call);
localView = (SurfaceViewRenderer) findViewById(R.id.localView);
remoteView = (SurfaceViewRenderer) findViewById(R.id.remoteView);
}
private void showCustomeDialog(String title, String leftButtonText, View.OnClickListener leftOnClickListener, String rightButtonText, View.OnClickListener rightOnClickListener) {
Dialog dialog = new Dialog(this);
ConstraintLayout constraintLayout = new ConstraintLayout(this);
constraintLayout.setLayoutParams(new ConstraintLayout.LayoutParams(1000, 600));
TextView textView = new TextView(this);
textView.setText(title);
textView.setTextSize(20);
constraintLayout.addView(textView);
ConstraintLayout.LayoutParams textViewLayoutParams = new ConstraintLayout.LayoutParams(-2, -2);
textViewLayoutParams.startToStart = 0;
textViewLayoutParams.endToEnd = 0;
textViewLayoutParams.topToTop = 0;
textViewLayoutParams.topMargin = 80;
textView.setLayoutParams(textViewLayoutParams);
Button leftButton = new Button(this);
Button rightButton = new Button(this);
leftButton.setText(leftButtonText);
rightButton.setText(rightButtonText);
leftButton.setTextSize(20);
rightButton.setTextSize(20);
constraintLayout.addView(leftButton);
constraintLayout.addView(rightButton);
ConstraintLayout.LayoutParams leftButtonLayoutParams = new ConstraintLayout.LayoutParams(350, 150);
ConstraintLayout.LayoutParams rightButtonLayoutParams = new ConstraintLayout.LayoutParams(350, 150);
leftButtonLayoutParams.startToStart = 0;
leftButtonLayoutParams.bottomToBottom = 0;
rightButtonLayoutParams.endToEnd = 0;
rightButtonLayoutParams.bottomToBottom = 0;
leftButton.setLayoutParams(leftButtonLayoutParams);
rightButton.setLayoutParams(rightButtonLayoutParams);
leftButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (leftOnClickListener != null) {
leftOnClickListener.onClick(v);
}
dialog.dismiss();
}
});
rightButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (rightOnClickListener != null) {
rightOnClickListener.onClick(v);
}
dialog.dismiss();
}
});
dialog.setContentView(constraintLayout);
dialog.getWindow().setLayout(1000, 600);
dialog.show();
}
}
@ServerEndpoint("/ws")
@Component
public class WebSocketServer extends BaseWebSocketServer {
@OnOpen
public void onOpen(Session session) {
Logger.e("onOpen ");
}
@OnMessage
public void onMessage(String message, Session session) {
Logger.e("onMessage:message=" + message);
int code = (int) getJsonValue(message, "code");
switch (code) {
case Constant.Login_Code://登錄
NetBean.LoginRequsetBean wsRequestBean = GsonUtil.GsonToBean(message, NetBean.LoginRequsetBean.class);
if (getSession(wsRequestBean.userId) != null) {
//發(fā)送斷線通知
NetBean.LoginResponseBean wsResponseBean = new NetBean.LoginResponseBean(Constant.OFFLine_Code, wsRequestBean.sequenceId, false, "該賬戶被其他用戶登錄");
sendMessage(getSession(wsRequestBean.userId), GsonUtil.BeanToJson(wsResponseBean));
removeSession(wsRequestBean.userId);
}
if (session.isOpen())
putSession(wsRequestBean.userId, session);
//登錄響應(yīng)
sendMessage(session, GsonUtil.BeanToJson(new NetBean.LoginResponseBean(wsRequestBean.code, wsRequestBean.sequenceId, true, "登錄成功,當(dāng)前在線人數(shù):" + getSessionSize())));
//每次有人登錄起意,廣播所有人當(dāng)前在線用戶信息
broadcastAllSession(GsonUtil.BeanToJson(new NetBean.OnLineUserInfoResponseBean(Constant.OnLineUserInfo_Code, getUserIdList())));
Logger.e(wsRequestBean.userId + "登錄成功");
break;
case Constant.SendMsg_Code://轉(zhuǎn)發(fā)消息
Logger.e("SendMsg_Code------"+message);
NetBean.SendMsgRequestBean sendMsgRequestBean = GsonUtil.GsonToBean(message, NetBean.SendMsgRequestBean.class);
if (getSession(sendMsgRequestBean.toUserId) == null) {
Logger.e("SendMsg_Code------該用戶不在線");
sendMessage(session, GsonUtil.BeanToJson(new NetBean.SendMsgResponseBean(Constant.SendMsg_Code, sendMsgRequestBean.sequenceId, sendMsgRequestBean.fromUserId, sendMsgRequestBean.toUserId, false, "該用戶不在線")));
return;
}
Logger.e("SendMsg_Code------getSession成功");
Session session1 = getSession(sendMsgRequestBean.toUserId);
Logger.e("SendMsg_Code------sendMessage成功");
sendMessage(session1, message);
break;
default://直接斷線
if (session.isOpen()) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
break;
}
}
@OnClose
public void onClose(Session session) {
Logger.e("onClose");
removeSession(session);
//有人斷線,廣播所有人當(dāng)前在線用戶信息
broadcastAllSession(GsonUtil.BeanToJson(new NetBean.OnLineUserInfoResponseBean(Constant.OnLineUserInfo_Code, getUserIdList())));
}
@OnError
public void onError(Session session, Throwable error) {
Logger.e("onError:" + error.getMessage());
}
}
了解過WebRtc技術(shù)的大家都知道病瞳,他是一個(gè)點(diǎn)對(duì)點(diǎn)(p2p)揽咕,解決端到端之間悲酷,直接進(jìn)行一個(gè)數(shù)據(jù)傳輸?shù)募夹g(shù),這樣可以節(jié)省很大的服務(wù)器帶寬成本心褐√蛳眩可是我們都知道生活中常用的手機(jī)或電腦,他們一般情況下并沒有一個(gè)固定的公網(wǎng)ip逗爹,然而這項(xiàng)技術(shù)剛好可以一定程度上解決這個(gè)問題。他的關(guān)鍵是利用了udp打洞技術(shù)(這個(gè)其實(shí)我以前也研究過嚎于,大概原理就是利用一個(gè)公網(wǎng)服務(wù)器掘而,兩個(gè)客戶端都給這個(gè)公網(wǎng)服務(wù)器發(fā)數(shù)據(jù)包,然后公網(wǎng)服務(wù)器于购,獲取經(jīng)過層層轉(zhuǎn)換的公網(wǎng)ip和端口號(hào)袍睡,并告訴兩個(gè)客戶端對(duì)方的公網(wǎng)ip端口信息,然后兩個(gè)客戶端同時(shí)不斷給另一個(gè)客戶端發(fā)數(shù)據(jù)肋僧,理論上這樣就可以成功打洞了斑胜,但是我測試時(shí)不知道是不是忽略了別的什么因素(防火墻關(guān)了,兩個(gè)不同網(wǎng)絡(luò)的客戶端也試了嫌吠,或者是當(dāng)時(shí)寬帶nat網(wǎng)絡(luò)轉(zhuǎn)換未達(dá)到要求)止潘,最后也沒成功打洞,無耐辫诅,暫時(shí)放棄)
首先我們需要一個(gè)信令服務(wù)器凭戴,說白了就是個(gè)中間人,用來交換打洞通信需要的一些會(huì)話數(shù)據(jù)炕矮,理論上你用websocket么夫,socket,http實(shí)現(xiàn)都可以肤视。這里我是用websocket實(shí)現(xiàn)的档痪,里面主要的作用就是讓指定兩個(gè)客戶端可以互相通信,互發(fā)SessionDescription(SDP信息)和IceCandidate邢滑,這兩個(gè)是webrtc中通信的關(guān)鍵信息腐螟。然后他還需要有多個(gè)公網(wǎng)ip的sturn服務(wù)器或者多添加幾個(gè)sturn服務(wù)器地址也可以(俗話中繼服務(wù)器:就是獲取外網(wǎng)ip用的),如果遇到打洞失敗的情況怎么辦殊鞭,那我們再用一個(gè)turn服務(wù)器(這就是個(gè)數(shù)據(jù)中轉(zhuǎn)服務(wù)器遭垛,不過這個(gè)一般都得自己搭建,網(wǎng)上基本沒免費(fèi)讓大家測試使用的(畢竟如果打洞不成功用到這個(gè)服務(wù)器會(huì)很占帶寬)操灿,我代碼中有一個(gè)我已經(jīng)搭好的turn服務(wù)器地址锯仪,有需要的可以給大家測試試試,這里最好要用火狐瀏覽器趾盐,谷歌的根本不行庶喜,測試sturn/turn服務(wù)器有沒有配置成功的網(wǎng)站地址https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/)小腊,如果打洞失敗,只能用這個(gè)服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)了久窟。
最后貼上一張網(wǎng)上搜到的webrtc流程圖
流程圖.png
最后再上一張視頻通話效果圖
演示.gif