Focus on Android development, share experience summary, welcome to join

QQ group: 686809487

More content, please read patiently

1. Introduction to Google Webrtc

WebRTC (Web Real-Time Communication) implements web-based video conferencing. The standard is WHATWG protocol. The goal is to achieve real-time Communications (RTC) capability by providing simple javascript through the browser. Provides the core technology of video conference, including audio and video collection, coding and decoding, network transmission, display and other functions, and also supports cross-platform: Windows, Linux, MAC, Android, audio and video communication technology is based on P2P implementation

2. Google Webrtc server configuration

Signaling server (Signaling server), Forward server (TURN), Penetration server (STUN) (self hundred, configuration), see the book WebRtc authority guide for more details

  • Signaling Server (Signaling)
    • Data is exchanged between WebrTCs using PeerConnection, but a mechanism is also needed to coordinate communication and send control messages, a process called signaling transmission
  • Forwarding Server (TURN) & Penetration Server (STUN)
    • WebRTC is designed to work in point-to-point mode, so users are connected by the shortest possible route, whereas in the real world: Client applications need to penetrate NAT gateways and firewalls, and peer-to-peer networks need handshakes to prevent direct connection failures. During this process, WebRTC uses STUN servers and TURN servers to obtain computer IP addresses to ensure a successful peer-to-peer connection

3. ICE framework

Signaling protocol-based multimedia transmission is a two-stage transmission. A connection is established through signaling protocol (such as WebSocket), through which the two parties exchange information necessary for media transmission. Considering transmission efficiency, after completing the interaction in the first stage, communication parties will establish a channel to realize media transmission to reduce transmission delay, packet loss rate and cost. When one of the transport parties is behind the NAT, the new transport link needs to consider NAT traversal. Generally, there are four types of NAT, each of which has its own solution. However, in a complex network environment, each NAT traversal solution is limited to the corresponding NAT mode. These schemes bring some complexity to the whole system. In this context, Interactive Connectivity Establishment, or ICE solution, has emerged, enabling the traversal of various forms of NAT without increasing the complexity and vulnerability of the entire system.

The official implementation principle is based on the following figure

4. Android Webrtc configuration

The client communicates with the server using WebSocket

Camera, recording, SD card reading and other permissions added, some need to be called at run time

4.1 Introduce WebSocket and WebrTC in build.gradle
Dependencies {implementation 'org.webrtc: Google - webrTC :1.0.28513' implementation 'org. Java - websocket: Java - websocket: 1.4.0' implementation 'com. Google. Code. Gson: gson: 2.8.5'}Copy the code

5. Use of Android Webrtc

5.1 create a websocket
private void connectionWebsocket() { try { webSocketClient = new WebSocketClient(URI.create(Constant.URL)) { @Override Public void onOpen(ServerHandshake HandshakeData) {setText(" Connect "); Log.e(TAG, "onOpen == Status == " + handshakedata.getHttpStatus() + " StatusMessage == " + handshakedata.getHttpStatusMessage()); Model model = new Model(Constant.REGISTER, getFromName(), getFrom(), getToName(), getTo()); webSocketClient.send(new Gson().toJson(model)); } @Override public void onMessage(String message) { Log.e(TAG, "onMessage == " + message); if (! TextUtils.isEmpty(message)) { Model model = new Gson().fromJson(message, Model.class); if (model ! = null) { String id = model.getId(); if (! TextUtils.isEmpty(id)) { int isSucceed = model.getIsSucceed(); switch (id) { case Constant.REGISTER_RESPONSE: if (isSucceed == Constant.RESPONSE_SUCCEED) { Message msg = new Message(); msg.obj = Constant.OPEN; handler.sendMessage(msg); Log.e(TAG, "connection successful "); } else if (isSucceed == constant.response_failure) {log. e(TAG, "register failed, register "); } break; Case constant.call_response: if (isSucceed == constant.response_succeed) {log. e(TAG, "isSucceed, create SDP offer"); createOffer(); } else if (isSucceed == constant.response_failure) {log. e(TAG, "failed "); } break; case Constant.INCALL: isIncall(); break; case Constant.INCALL_RESPONSE: if (isSucceed == Constant.RESPONSE_SUCCEED) { createOffer(); Log.e(TAG, "receiver agrees to answer "); } else if (isSucceed == constant.response_failure) {log. e(TAG, "RESPONSE_FAILURE "); } break; Case Constant. OFFER: / / receives OFFER SDP SessionDescription sessionDescription1 = model. GetSessionDescription (); peerConnection.setRemoteDescription(observer, sessionDescription1); createAnswer(); break; Case constant. CANDIDATE: // Server send receiver sdpAnswer IceCandidate IceCandidate = model.geticecandiDate (); if (iceCandidate ! = null) { peerConnection.addIceCandidate(iceCandidate); } break; }}}}} @override public void onClose(int code, String reason, Boolean remote) {setText(" closed "); Log.e(TAG, "onClose == code " + code + " reason == " + reason + " remote == " + remote); } @Override public void onError(Exception ex) { setText("onError == " + ex.getMessage()); Log.e(TAG, "onError== " + ex.getMessage()); }}; webSocketClient.connect(); } catch (Exception e) { Log.d(TAG, "socket Exception : " + e.getMessage()); }}Copy the code
5.2 create websocket after a successful start creating PeerConnection, penetrates the server using Google’s (” stun:stun.l.google.com: 19302)”
private void createPeerConnection() { //Initialising PeerConnectionFactory InitializationOptions initializationOptions =  InitializationOptions.builder(this) .setEnableInternalTracer(true) .setFieldTrials("WebRTC-H264HighProfile/Enabled/") .createInitializationOptions(); PeerConnectionFactory.initialize(initializationOptions); EglBase = eglBase.create (); PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); options.disableEncryption = true; options.disableNetworkMonitor = true; peerConnectionFactory = PeerConnectionFactory.builder() .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBase.getEglBaseContext())) .setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true)) .setOptions(options) .createPeerConnectionFactory(); IceServers = new ArrayList<>(); PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder(Constant.STUN).createIceServer(); iceServers.add(iceServer); streamList = new ArrayList<>(); PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers); PeerConnectionObserver connectionObserver = getObserver(); peerConnection = peerConnectionFactory.createPeerConnection(configuration, connectionObserver); /* DataChannel.Init Ordered: Whether to ensure the sequential transmission; MaxRetransmitTimeMs: Maximum time allowed for retransmissions; MaxRetransmits: indicates the maximum number of retransmits allowed. */ DataChannel.Init init = new DataChannel.Init(); if (peerConnection ! = null) { channel = peerConnection.createDataChannel(Constant.CHANNEL, init); } DateChannelObserver channelObserver = new DateChannelObserver(); connectionObserver.setObserver(channelObserver); initView(); initObserver(); }Copy the code
  • 5.2.1 Initializing a View

private void initSurfaceview(SurfaceViewRenderer localSurfaceView) {
        localSurfaceView.init(eglBase.getEglBaseContext(), null);
        localSurfaceView.setMirror(true);
        localSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        localSurfaceView.setKeepScreenOn(true);
        localSurfaceView.setZOrderMediaOverlay(true);
        localSurfaceView.setEnableHardwareScaler(false);
}
Copy the code
  • 5.2.2 Initializing Audio and Video

/** @param localSurfaceView */ private void startLocalVideoCapture(SurfaceViewRenderer localSurfaceView) { VideoSource videoSource = peerConnectionFactory.createVideoSource(true); SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(Thread.currentThread().getName(), eglBase.getEglBaseContext()); VideoCapturer videoCapturer = createVideoCapturer(); videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver()); videoCapturer.startCapture(Constant.VIDEO_RESOLUTION_WIDTH, Constant.VIDEO_RESOLUTION_HEIGHT, Constant.VIDEO_FPS); // width, height, frame per second videoTrack = peerConnectionFactory.createVideoTrack(Constant.VIDEO_TRACK_ID, videoSource); videoTrack.addSink(localSurfaceView); MediaStream localMediaStream = peerConnectionFactory.createLocalMediaStream(Constant.LOCAL_VIDEO_STREAM); localMediaStream.addTrack(videoTrack); peerConnection.addTrack(videoTrack, streamList); peerConnection.addStream(localMediaStream); } // private void startLocalAudioCapture() {// Voice MediaConstraints = new MediaConstraints(); / / echo cancellation audioConstraints. Mandatory. The add (new MediaConstraints. KeyValuePair (" googEchoCancellation ", "true")); / / automatic gain audioConstraints. Mandatory. The add (new MediaConstraints. KeyValuePair (" googAutoGainControl ", "true")); / / the high filtration audioConstraints. Mandatory. The add (new MediaConstraints. KeyValuePair (" googHighpassFilter ", "true")); / / noise processing audioConstraints. Mandatory. The add (new MediaConstraints. KeyValuePair (" googNoiseSuppression ", "true")); AudioSource audioSource = peerConnectionFactory.createAudioSource(audioConstraints); audioTrack = peerConnectionFactory.createAudioTrack(Constant.AUDIO_TRACK_ID, audioSource); MediaStream localMediaStream = peerConnectionFactory.createLocalMediaStream(Constant.LOCAL_AUDIO_STREAM); localMediaStream.addTrack(audioTrack); audioTrack.setVolume(Constant.VOLUME); peerConnection.addTrack(audioTrack, streamList); peerConnection.addStream(localMediaStream); }Copy the code
5.3 After a PeerConnection is created, information is exchanged through signaling transmission to implement video calls. The following figure shows the adjusted process

signaling

public static final String REGISTER = "register"; Public static final String REGISTER_RESPONSE = "REGISTER_RESPONSE "; Public static final int RESPONSE_SUCCEED = 1; Public static final int RESPONSE_FAILURE = 2; Public static final String CALL = "CALL "; Public static final String CALL_RESPONSE = "CALL_RESPONSE "; Public static final String INCALL = "INCALL "; Public static final String INCALL_RESPONSE = "INCALL_RESPONSE "; Public static final String OFFER = "OFFER "; Public static final String CANDIDATE = "CANDIDATE "; / / ice to each otherCopy the code

Flow content: Client A, client B

  • 5.3.1 –>> After A and B are registered successfully, A initiates A call. The server receives A’s command and forwards it to B. B receives the command and forwards it to the server
  • 5.3.2 –>> A createOffer sends its SessionDescription to the server, and the server forwards it to B
  • 5.3.3 –>> B will set A’s SessionDescription to its own setRemoteDescription, and createAnswer will be sent to the server, and the server will forward A
  • 5.3.4 –>> A sets THE SessionDescription of B to its own setRemoteDescription after receiving it
  • 5.3.5 –>> A sends the received IceCandidate to the server in the PeerConnectionObserver onIceCandidate method, and the server forwards the IceCandidate to B
  • 5.3.6 –>> B will set IceCandidate to his addIceCandidate after receiving the addIceCandidate
  • 5.3.7 –>> B sends the received IceCandidate to the server in the PeerConnectionObserver onIceCandidate method, and the server forwards the IceCandidate to A
  • 5.3.8 –>> A will set the IceCandidate to his addIceCandidate after receiving the message
  • 5.3.9 –>> A and B add the remote streaming media to the local View in the onAddStream method of PeerConnectionObserver
  • 5.3.10 –>> The interaction is complete and video calls can be made normally

Detailed flow in JSON format

isSucceed 1成功 2失败

"from": "11",  "fromName": "王安", "to": "12", "toName": "往事"
	
1.注册 
from 发送服务端:register 
{
"from": "11",
"fromName": "王安",
"id": "register",
"isSucceed": 0
}

from接收:register_response 
{
"id": "register_response",
"isSucceed": 1
}

2.呼叫
from 发送服务端:call 
{
"from": "11",
"fromName": "王安",
"id": "call",
"isSucceed": 0,
"to": "12",
"toName": "往事"
}

2.1服务端做判断

如果不在线 直接返回 "isSucceed": 1
{
"id": "call_response",
"isSucceed": 1
}
 
在线 返回给to做判断

to 接收:incall 
改变本地状态
{
"id": "incall",
"isSucceed": 0
}
	to 发送服务端 拒绝:"isSucceed": 2
	{
		"from": "12",
		"fromName": "往事",
		"id": "incall",
		"isSucceed": 2,
		"to": "11",
		"toName": "王安"
	}
		from接收:incall_response
		做拒绝的操作
		{
			"id": "incall_response",
			"from": "12",
			"fromName": "往事",
			"to": "11",
			"toName": "王安",
			"isSucceed": 2
		}
		
-------------------------------------------------
	
	to 发送服务端 同意:"isSucceed": 1
	{
		"from": "12",
		"fromName": "往事",
		"id": "incall",
		"isSucceed": 1,
		"to": "11",
		"toName": "王安"
	}
		from接收 同意:incall_response
		  
		{
			"id": "incall_response",
			"from": "12",
			"fromName": "往事",
			"to": "11",
			"toName": "王安",
			"isSucceed": 1
		}

3.to同意后 from发送流
from 发送服务端:offer
{
"from": "11",
"fromName": "王安",
"id": "offer",
"isSucceed": 0,
"sessionDescription": {
"description": "v=0\r\no=- 3285195603599485281 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE video\r\na=msid-semantic: WMS localstream\r\nm=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:9Jqg\r\na=ice-pwd:lM34EvVxjaPGBislEH9VccVu\r\na=ice-options:trickle renomination\r\na=mid:video\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=sendrecv\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 H264/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:127 red/90000\r\na=rtpmap:124 rtx/90000\r\na=fmtp:124 apt=127\r\na=rtpmap:125 ulpfec/90000\r\na=ssrc-group:FID 1791802911 305163890\r\na=ssrc:1791802911 cname:8uTWIc2YbUNSY02u\r\na=ssrc:1791802911 msid:localstream videtrack\r\na=ssrc:1791802911 mslabel:localstream\r\na=ssrc:1791802911 label:videtrack\r\na=ssrc:305163890 cname:8uTWIc2YbUNSY02u\r\na=ssrc:305163890 msid:localstream videtrack\r\na=ssrc:305163890 mslabel:localstream\r\na=ssrc:305163890 label:videtrack\r\n",
"type": "OFFER"
},
"to": "12",
"toName": "往事"
}

to接收:offer
{
"id": "offer",
"from": "11",
"fromName": "王安",
"to": "12",
"toName": "往事",
"sessionDescription": {
"type": "OFFER",
"description": "v=0\r\no=- 3285195603599485281 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE video\r\na=msid-semantic: WMS localstream\r\nm=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:9Jqg\r\na=ice-pwd:lM34EvVxjaPGBislEH9VccVu\r\na=ice-options:trickle renomination\r\na=mid:video\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=sendrecv\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 H264/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:127 red/90000\r\na=rtpmap:124 rtx/90000\r\na=fmtp:124 apt=127\r\na=rtpmap:125 ulpfec/90000\r\na=ssrc-group:FID 1791802911 305163890\r\na=ssrc:1791802911 cname:8uTWIc2YbUNSY02u\r\na=ssrc:1791802911 msid:localstream videtrack\r\na=ssrc:1791802911 mslabel:localstream\r\na=ssrc:1791802911 label:videtrack\r\na=ssrc:305163890 cname:8uTWIc2YbUNSY02u\r\na=ssrc:305163890 msid:localstream videtrack\r\na=ssrc:305163890 mslabel:localstream\r\na=ssrc:305163890 label:videtrack\r\n"
},
"isSucceed": 0
}

to接收到 offer 创建answer 创建完 to 发送服务端:offer
{
"from": "12",
"fromName": "往事",
"id": "offer",
"isSucceed": 0,
"sessionDescription": {
"description": "v=0\r\no=- 8665372075017263288 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE video\r\na=msid-semantic: WMS localstream\r\nm=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:zoQn\r\na=ice-pwd:oBePBH1X7SehiT6Tb83SZV2U\r\na=ice-options:trickle renomination\r\na=mid:video\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=sendrecv\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 H264/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:127 red/90000\r\na=rtpmap:124 rtx/90000\r\na=fmtp:124 apt=127\r\na=rtpmap:125 ulpfec/90000\r\na=ssrc-group:FID 330040035 1917181902\r\na=ssrc:330040035 cname:uxoKaX6C0J4LHdZ2\r\na=ssrc:330040035 msid:localstream videtrack\r\na=ssrc:330040035 mslabel:localstream\r\na=ssrc:330040035 label:videtrack\r\na=ssrc:1917181902 cname:uxoKaX6C0J4LHdZ2\r\na=ssrc:1917181902 msid:localstream videtrack\r\na=ssrc:1917181902 mslabel:localstream\r\na=ssrc:1917181902 label:videtrack\r\n",
"type": "ANSWER"
},
"to": "11",
"toName": "王安"
}

4.ice交换
from 发送服务端:candidate
{
"from": "11",
"fromName": "王安",
"iceCandidate": {
"sdp": "candidate:3575964346 1 udp 2122260223 192.168.0.166 47369 typ host generation 0 ufrag 9Jqg network-id 3 network-cost 10",
"sdpMLineIndex": 0,
"sdpMid": "video",
"serverUrl": ""
},
"id": "candidate",
"isSucceed": 0,
"to": "12",
"toName": "往事"
}

to 接收:candidate

{
"from": "11",
"fromName": "王安",
"iceCandidate": {
"sdp": "candidate:3575964346 1 udp 2122260223 192.168.0.166 47369 typ host generation 0 ufrag 9Jqg network-id 3 network-cost 10",
"sdpMLineIndex": 0,
"sdpMid": "video",
"serverUrl": ""
},
"id": "candidate",
"isSucceed": 0,
"to": "12",
"toName": "往事"
}

to 发送服务端:candidate
{
"from": "12",
"fromName": "往事",
"iceCandidate": {
"sdp": "candidate:559267639 1 udp 2122202367 ::1 55843 typ host generation 0 ufrag zoQn network-id 2",
"sdpMLineIndex": 0,
"sdpMid": "video",
"serverUrl": ""
},
"id": "candidate",
"isSucceed": 0,
"to": "11",
"toName": "王安"
}

from接收:candidate
{
"from": "12",
"fromName": "往事",
"iceCandidate": {
"sdp": "candidate:559267639 1 udp 2122202367 ::1 55843 typ host generation 0 ufrag zoQn network-id 2",
"sdpMLineIndex": 0,
"sdpMid": "video",
"serverUrl": ""
},
"id": "candidate",
"isSucceed": 0,
"to": "11",
"toName": "王安"
}
Copy the code
5.4 Video Call (Some screenshots


Follow the wechat official account below (Taxiao) and reply to WebrTC for relevant demo