一篇文章教會(huì)你---怎樣實(shí)現(xiàn)Android-WebRtc視頻通話

先看一下核心代碼

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ù)器傳來(lái)的攝像頭預(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)限未打開(kāi)", 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);//控制錄音來(lái)源
//        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.開(kāi)始捕獲
        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, "請(qǐng)輸入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("是否同意接聽(tīng) " + sendMsgRequestBean.fromUserId + " 的來(lái)電?",
                                                    "同意", 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, "同意接聽(tīng)");
                                                            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, "拒絕接聽(tīng)");
                                                            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, "請(qǐng)先輸入登錄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, "請(qǐng)先填寫(xiě)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());
    }
}

了解過(guò)WebRtc技術(shù)的大家都知道,他是一個(gè)點(diǎn)對(duì)點(diǎn)(p2p),解決端到端之間,直接進(jìn)行一個(gè)數(shù)據(jù)傳輸?shù)募夹g(shù),這樣可以節(jié)省很大的服務(wù)器帶寬成本??墒俏覀兌贾郎钪谐S玫氖謾C(jī)或電腦,他們一般情況下并沒(méi)有一個(gè)固定的公網(wǎng)ip,然而這項(xiàng)技術(shù)剛好可以一定程度上解決這個(gè)問(wèn)題。他的關(guān)鍵是利用了udp打洞技術(shù)(這個(gè)其實(shí)我以前也研究過(guò),大概原理就是利用一個(gè)公網(wǎng)服務(wù)器,兩個(gè)客戶端都給這個(gè)公網(wǎng)服務(wù)器發(fā)數(shù)據(jù)包,然后公網(wǎng)服務(wù)器,獲取經(jīng)過(guò)層層轉(zhuǎn)換的公網(wǎng)ip和端口號(hào),并告訴兩個(gè)客戶端對(duì)方的公網(wǎng)ip端口信息,然后兩個(gè)客戶端同時(shí)不斷給另一個(gè)客戶端發(fā)數(shù)據(jù),理論上這樣就可以成功打洞了,但是我測(cè)試時(shí)不知道是不是忽略了別的什么因素(防火墻關(guān)了,兩個(gè)不同網(wǎng)絡(luò)的客戶端也試了,或者是當(dāng)時(shí)寬帶nat網(wǎng)絡(luò)轉(zhuǎn)換未達(dá)到要求),最后也沒(méi)成功打洞,無(wú)耐,暫時(shí)放棄)

首先我們需要一個(gè)信令服務(wù)器,說(shuō)白了就是個(gè)中間人,用來(lái)交換打洞通信需要的一些會(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用的),如果遇到打洞失敗的情況怎么辦,那我們?cè)儆靡粋€(gè)turn服務(wù)器(這就是個(gè)數(shù)據(jù)中轉(zhuǎn)服務(wù)器,不過(guò)這個(gè)一般都得自己搭建,網(wǎng)上基本沒(méi)免費(fèi)讓大家測(cè)試使用的(畢竟如果打洞不成功用到這個(gè)服務(wù)器會(huì)很占帶寬),我代碼中有一個(gè)我已經(jīng)搭好的turn服務(wù)器地址,有需要的可以給大家測(cè)試試試,這里最好要用火狐瀏覽器,谷歌的根本不行,測(cè)試sturn/turn服務(wù)器有沒(méi)有配置成功的網(wǎng)站地址https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/),如果打洞失敗,只能用這個(gè)服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)了。

最后貼上一張網(wǎng)上搜到的webrtc流程圖

流程圖.png

最后再上一張視頻通話效果圖

演示.gif

完整代碼地址(Android和web服務(wù)器代碼)

https://github.com/dxh104/WebRtcDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容