DataChannel.state () всегда возвращает CONNECTING webRTC Android

У меня есть приложение для Android, работающее как клиент сервера WebRTC, работающего на сервере Node.js. Текущее состояние приложения – я могу совершать видеозвонки, но не могу отправить сообщение в DataChannel.

Вот мой полный код для приложения для Android.

Home.java

public class Home extends Activity { public List<PeerConnection.IceServer> iceServers; private GLSurfaceView videoView; public static SocketIO socket; ArrayList<String> userIDs = new ArrayList<>(); private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/"; String RoomId = ""; String sreverURL = "http://xx.xx.xx.xx:xxxx/"; private EditText roomid; private VideoRenderer.Callbacks remote_view; private VideoRenderer.Callbacks local_view; protected PeerConnectionFactory factory; PeerConnectionFactory.Options options = null; Events pc_events; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); videoView = (GLSurfaceView) findViewById(R.id.glview_call_remote); VideoRendererGui.setView(videoView, new Runnable() { @Override public void run() { createPeerConnectionFactory(); } }); remote_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FIT, false); local_view = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true); iceServers = new ArrayList<>(); IceServer icc = new IceServer("stun:stun.l.google.com:19302", "", ""); iceServers.add(icc); roomid = (EditText) findViewById(R.id.roomId); Random rand = new Random(); roomid.setText("" + rand.nextInt(9999)); pc_events = new peerEventHandler(); } private void createPeerConnectionFactory() { runOnUiThread(new Runnable() { @Override public void run() { PeerConnectionFactory.initializeFieldTrials(FIELD_TRIAL_VP9); PeerConnectionFactory.initializeAndroidGlobals(Home.this, true, true, true, VideoRendererGui.getEGLContext()); try { factory = new PeerConnectionFactory(); } catch (Exception e) { e.printStackTrace(); } } }); } public void ondail(View view) { try { try { SocketIO.setDefaultSSLSocketFactory(SSLContext.getDefault()); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } socket = new SocketIO(); socket.connect(sreverURL, new IOCallback() { @Override public void onMessage(JSONObject json, IOAcknowledge ack) { } @Override public void onMessage(String data, IOAcknowledge ack) { } @Override public void onError(SocketIOException socketIOException) { socketIOException.printStackTrace(); } @Override public void onDisconnect() { } @Override public void onConnect() { showToast("Connected to " + sreverURL); } @Override public void on(final String event, IOAcknowledge ack, final Object... args) { Log.e("Socked.on", event + ", " + args); switch (getEvent(event)) { case LOG : break; case MESSAGE : if (args instanceof Object[]) { pc_events.setMessage(args[0].toString()); } else { pc_events.setMessage(args.toString()); } break; case CREATED : runOnUiThread(new Runnable() { public void run() { showToast("Room Created " + args[0]); } }); break; case BROADCAST : break; case JOIN : break; case EMIT : Log.e("Socked.onEMIT", args.toString()); startCall(); pc_events.createOffer(); break; case ERROR : Log.e("Socked.onERROR", args.toString()); break; default : break; } } }); try { RoomId = roomid.getText().toString(); } catch (Exception e) { } socket.emit("create or join", RoomId); } catch (MalformedURLException e) { e.printStackTrace(); } } public void oncancel(View view) { } public SocketEvent getEvent(String eventString) { SocketEvent eventType; try { if (eventString.contains("log")) { eventType = SocketEvent.LOG; } else if (eventString.contains("created")) { eventType = SocketEvent.CREATED; } else if (eventString.contains("emit():")) { eventType = SocketEvent.EMIT; } else if (eventString.contains("broadcast():")) { eventType = SocketEvent.BROADCAST; } else if (eventString.contains("message")) { eventType = SocketEvent.MESSAGE; } else if (eventString.toLowerCase().substring(0, 20).contains("join")) { eventType = SocketEvent.JOIN; } else { eventType = SocketEvent.ERROR; } } catch (Exception e) { eventType = SocketEvent.ERROR; } return eventType; } public static interface Events { public void peerConnectionEvent(VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender); public void setFactory(PeerConnectionFactory factory); public void setMessage(String message); public void createOffer(); public void sendMessage(String msg); } private void startCall() { pc_events.setFactory(factory); pc_events.peerConnectionEvent(remote_view, local_view); } public void showToast(final String message) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(Home.this, message, Toast.LENGTH_SHORT).show(); } }); } public void makeOffer(View v) { pc_events.sendMessage("Hello"); } } 

peerEventHandler.java

 public class peerEventHandler implements Events { private PeerConnection peerConnection; private PeerConnectionFactory factory; PCObserver pcObserver = new PCObserver(); public LooperExecutor executor; private MediaStream mediaStream; private VideoSource videoSource; private DcObserver dc_observer; public static final String VIDEO_TRACK_ID = "ARDAMSv0"; public static final String AUDIO_TRACK_ID = "ARDAMSa0"; private VideoCapturerAndroid videoCapturer; private VideoTrack localVideoTrack; private VideoTrack remoteVideoTrack; public boolean preferIsac = false; public boolean videoCallEnabled = true; public boolean preferH264 = false; private SessionDescription localSdp; private final SDPObserver sdpObserver = new SDPObserver(); public boolean isInitiator = false; private MediaConstraints sdpMediaConstraints; private VideoRenderer.Callbacks remote_view; private VideoRenderer.Callbacks local_view; private DataChannel dataChannel; @Override public void peerConnectionEvent(Callbacks remoteRender, Callbacks localRender) { this.remote_view = remoteRender; this.local_view = localRender; creatPeerConnection(); } public void creatPeerConnection() { executor = new LooperExecutor(); executor.requestStart(); MediaConstraints pcConstraints = new MediaConstraints(); MediaConstraints videoConstraints = new MediaConstraints(); MediaConstraints audioConstraints = new MediaConstraints(); sdpMediaConstraints = new MediaConstraints(); creatPcConstrains(pcConstraints); creatvideoConstraints(videoConstraints); creatsdpMediaConstraints(sdpMediaConstraints); List<PeerConnection.IceServer> iceServers = new ArrayList<PeerConnection.IceServer>(); IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", ""); iceServers.add(iceServer); PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers); rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED; rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver); Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT), Logging.Severity.LS_WARNING); mediaStream = factory.createLocalMediaStream("ARDAMS"); String cameraDeviceName = CameraEnumerationAndroid.getDeviceName(0); String frontCameraDeviceName = CameraEnumerationAndroid.getNameOfFrontFacingDevice(); cameraDeviceName = frontCameraDeviceName; videoCapturer = VideoCapturerAndroid.create(cameraDeviceName, null); videoSource = factory.createVideoSource(videoCapturer, videoConstraints); localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource); localVideoTrack.setEnabled(true); localVideoTrack.addRenderer(new VideoRenderer(local_view)); mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints))); mediaStream.addTrack(localVideoTrack); peerConnection.addStream(mediaStream); dataChannel = peerConnection.createDataChannel("sendDataChannel", new DataChannel.Init()); dc_observer = new DcObserver(); dataChannel.registerObserver(dc_observer); } @Override public void createOffer() { executor.execute(new Runnable() { @Override public void run() { if (peerConnection != null) { isInitiator = true; peerConnection.createOffer(sdpObserver, sdpMediaConstraints); } } }); } public void createAnswer() { executor.execute(new Runnable() { @Override public void run() { if (peerConnection != null) { isInitiator = false; peerConnection.createAnswer(sdpObserver, sdpMediaConstraints); } } }); } private class PCObserver implements PeerConnection.Observer { @Override public void onAddStream(final MediaStream stream) { Log.e("onAddStream", "onAddStream"); executor.execute(new Runnable() { @Override public void run() { if (peerConnection == null) { return; } if (stream.audioTracks.size() > 1 || stream.videoTracks.size() > 1) { // /reportError("Weird-looking stream: " + stream); return; } if (stream.videoTracks.size() == 1) { remoteVideoTrack = stream.videoTracks.get(0); remoteVideoTrack.setEnabled(true); remoteVideoTrack.addRenderer(new VideoRenderer(remote_view)); VideoRendererGui.update(local_view, 75, 70, 60, 60, ScalingType.SCALE_ASPECT_FIT, true); VideoRendererGui.update(remote_view, 0, 0, 200, 200, ScalingType.SCALE_ASPECT_FILL, false); } } }); } @Override public void onDataChannel(final DataChannel dc) { executor.execute(new Runnable() { @Override public void run() { dataChannel = dc; String channelName = dataChannel.label(); dataChannel.registerObserver(new DcObserver()); } }); } @Override public void onIceCandidate(IceCandidate candidate) { SocketIO socket = Home.socket; JSONObject json = new JSONObject(); try { json.putOpt("type", "candidate"); json.putOpt("label", candidate.sdpMLineIndex); json.putOpt("id", candidate.sdpMid); json.putOpt("candidate", candidate.sdp); } catch (JSONException e) { e.printStackTrace(); } socket.emit("message", json); } @Override public void onIceConnectionChange(IceConnectionState arg0) { } @Override public void onIceConnectionReceivingChange(boolean arg0) { } @Override public void onIceGatheringChange(IceGatheringState arg0) { } @Override public void onRemoveStream(MediaStream arg0) { } @Override public void onRenegotiationNeeded() { } @Override public void onSignalingChange(SignalingState arg0) { } } public void creatPcConstrains(MediaConstraints pcConstraints) { pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true")); pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true")); pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true")); } public void creatvideoConstraints(MediaConstraints videoConstraints) { String MAX_VIDEO_WIDTH_CONSTRAINT = "maxWidth"; String MIN_VIDEO_WIDTH_CONSTRAINT = "minWidth"; String MAX_VIDEO_HEIGHT_CONSTRAINT = "maxHeight"; String MIN_VIDEO_HEIGHT_CONSTRAINT = "minHeight"; String MAX_VIDEO_FPS_CONSTRAINT = "maxFrameRate"; String MIN_VIDEO_FPS_CONSTRAINT = "minFrameRate"; int videoWidth = 0; int videoHeight = 0; if ((videoWidth == 0 || videoHeight == 0) && true && MediaCodecVideoEncoder.isVp8HwSupported()) { videoWidth = 1280; videoHeight = 1280; } if (videoWidth > 0 && videoHeight > 0) { videoWidth = Math.min(videoWidth, 1280); videoHeight = Math.min(videoHeight, 1280); videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth))); videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_WIDTH_CONSTRAINT, Integer.toString(videoWidth))); videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight))); videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_HEIGHT_CONSTRAINT, Integer.toString(videoHeight))); } int videoFps = 30; videoConstraints.mandatory.add(new KeyValuePair(MIN_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps))); videoConstraints.mandatory.add(new KeyValuePair(MAX_VIDEO_FPS_CONSTRAINT, Integer.toString(videoFps))); } public void creataudioConstraints(MediaConstraints pcConstraints) { pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true")); pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "true")); pcConstraints.optional.add(new KeyValuePair("internalSctpDataChannels", "true")); } public void creatsdpMediaConstraints(MediaConstraints sdpMediaConstraints) { sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveAudio", "true")); sdpMediaConstraints.mandatory.add(new KeyValuePair("OfferToReceiveVideo", "true")); } private class SDPObserver implements SdpObserver { @Override public void onCreateFailure(String arg0) { System.out.print(arg0); } @Override public void onCreateSuccess(SessionDescription origSdp) { if (localSdp != null) { return; } localSdp = origSdp; setLocalDescription(origSdp); } @Override public void onSetFailure(String arg0) { } @Override public void onSetSuccess() { executor.execute(new Runnable() { @Override public void run() { if (peerConnection == null) { return; } if (isInitiator) { if (peerConnection != null) { JSONObject json = new JSONObject(); try { json.putOpt("type", localSdp.type.toString().toLowerCase()); json.putOpt("sdp", localSdp.description); } catch (JSONException e) { e.printStackTrace(); } Home.socket.emit("message", json); } } else { // createAnswer(); } } }); } } public void addRemoteIceCandidate(final IceCandidate candidate) { executor.execute(new Runnable() { @Override public void run() { peerConnection.addIceCandidate(candidate); } }); } public void setLocalDescription(final SessionDescription sdp) { executor.execute(new Runnable() { @Override public void run() { if (peerConnection == null) { return; } peerConnection.setLocalDescription(sdpObserver, sdp); } }); } public void setRemoteDescription(final SessionDescription sdp) { executor.execute(new Runnable() { @Override public void run() { if (peerConnection == null) { return; } peerConnection.setRemoteDescription(sdpObserver, sdp); } }); } @Override public void setFactory(PeerConnectionFactory factory) { this.factory = factory; } public void onWebSocketMessage(final String msg) { try { Log.e("onWebSocketMessage", msg); JSONObject json = new JSONObject(msg); json = new JSONObject(msg); String type = json.optString("type"); if (type.equals("candidate")) { IceCandidate candidate = new IceCandidate(json.getString("id"), json.getInt("label"), json.getString("candidate")); addRemoteIceCandidate(candidate); } else if (type.equals("answer")) { isInitiator = false; SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp")); setRemoteDescription(sdp); } else if (type.equals("offer")) { SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp")); setRemoteDescription(sdp); } else if (type.equals("bye")) { } else { } } catch (JSONException e) { } } @Override public void setMessage(String message) { if (message.toString().contains("got user media") || message.toString().contains("bye")) { } else onWebSocketMessage(message); } private class DcObserver implements DataChannel.Observer { @Override public void onMessage(DataChannel.Buffer buffer) { ByteBuffer data = buffer.data; byte[] bytes = new byte[data.remaining()]; data.get(bytes); String command = new String(bytes); Log.e("onMessage ", command); } @Override public void onStateChange() { Log.e("onStateChange ", "onStateChange"); } @Override public void onBufferedAmountChange(long arg0) { Log.e("onMessage ", "" + arg0); } } @Override public void sendMessage(String msg) { ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); boolean sent = dataChannel.send(new DataChannel.Buffer(buffer, false)); if (sent) { Log.e("Message sent", "" + sent); } } } 

Любые замечания и предложения приветствуются;)

Канал данных WebRTC теперь работает через SCTP, поэтому вы можете удалить ограничение RtpDataChannels.