title: Stream from Android app to browser client app via socket.io-client and libjingle
body: <p>So I'm trying to connect Android to Browser via webRTC via socket.io and libjingle and server is running on Node.js .
  Issue I'm facing is weired.
  When 1 client is at Android(native app) and other is at Ipad(native app), Everything works fine.
  When 1 client is at iPad(Native app) and other is WebApp, Everyting works fine.
  But When 1 Client is at Android(native app) and other is WebPage, everyting works fine except the audio and video is not streaming to that end.</p>
  
  <p>Following are the two major classes i've used for the purpose:</p>
  
  <p>PS. The method <code>makeOffer(View v)</code> called by the button.</p>
  
  <p><strong>MainActivity.java</strong></p>
  
  <pre><code>public List&lt;PeerConnection.IceServer&gt; iceServers;
      private GLSurfaceView videoView;
      public static SocketIO socket;
      ArrayList&lt;String&gt; userIDs = new ArrayList&lt;&gt;();
      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 localRender;
      private VideoRenderer.Callbacks remoteRender;
      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);
          VideoRendererGui.setView(videoView, new Runnable() {
              @Override
              public void run() {
                  createPeerConnectionFactory();
              }
          });
  
          remoteRender = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, false);
          localRender = VideoRendererGui.create(0, 0, 100, 100, ScalingType.SCALE_ASPECT_FILL, true);
          iceServers = new ArrayList&lt;&gt;();
          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) {
                      Log.e("json Object", json.toString());
                  }
                  @Override
                  public void onMessage(String data, IOAcknowledge ack) {
                      Log.e("json Object", data.toString());
                  }
                  @Override
                  public void onError(SocketIOException socketIOException) {
                      socketIOException.printStackTrace();
                      System.out.println(socketIOException.toString());
                  }
                  @Override
                  public void onDisconnect() {
                  }
                  @Override
                  public void onConnect() {
                      showToast("Connected to " + sreverURL);
                      startCall("");
                  }
                  @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[]) {
                                  if (args[0].toString().contains("offer")) {
  //                                  startCall("");
                                  }
                                  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 :
                              Log.e("Socked.onBROADCAST", args.toString());
                              break;
                          case JOIN :
                              Log.e("Socked.onJOIN", args.toString());
                              break;
  
                          case EMIT :
                              Log.e("Socked.onEMIT", args.toString());
                              Log.e("Socked.onBROADCOST", args.toString());
  
                              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(String clintID, VideoRenderer.Callbacks localRender, VideoRenderer.Callbacks remoteRender);
  
          public void setFactory(PeerConnectionFactory factory);
  
          public void setMessage(String message);
          public void createOffer();
  
      }
  
      private void startCall(String clientID) {
  
          pc_events.setFactory(factory);
  
          pc_events.peerConnectionEvent(clientID, localRender, remoteRender);
  
      }
  
      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.createOffer();
      }
  
  }
  </code></pre>
  
  <p><strong>PeerEventHandler.java</strong></p>
  
  <pre><code>public class peerEventHandler implements Events {
  
      private PeerConnection peerConnection;
      private PeerConnectionFactory factory;
      PCObserver pcObserver = new PCObserver();
      public LooperExecutor executor;
  
      private MediaStream mediaStream;
  
      private VideoSource videoSource;
  
      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 = true;
  
      private static final String VIDEO_CODEC_H264 = "VP8";
      private static final String AUDIO_CODEC_ISAC = "ISAC";
  
      private SessionDescription localSdp;
  
      private final SDPObserver sdpObserver = new SDPObserver();
  
      private LinkedList&lt;IceCandidate&gt; queuedRemoteCandidates;
  
      public boolean isInitiator = false;
      private MediaConstraints sdpMediaConstraints;
  
      private VideoRenderer.Callbacks localRender;
      private VideoRenderer.Callbacks remoteRender;
  
      @Override
      public void peerConnectionEvent(String clintID, Callbacks localRender, Callbacks remoteRender) {
  
          this.localRender = localRender;
          this.remoteRender = remoteRender;
          creatPeerConnection();
  
      }
  
      public void creatPeerConnection() {
  
          queuedRemoteCandidates = new LinkedList&lt;IceCandidate&gt;();
          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&lt;PeerConnection.IceServer&gt; iceServers = new ArrayList&lt;PeerConnection.IceServer&gt;();
  
          IceServer iceServer = new IceServer("stun:stun.l.google.com:19302", "", "");
  
          iceServers.add(iceServer);
  
          PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
  
          // TCP candidates are only useful when connecting to a server that
          // supports
          // ICE-TCP.
          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(localRender));
          mediaStream.addTrack(factory.createAudioTrack(AUDIO_TRACK_ID, factory.createAudioSource(audioConstraints)));
          mediaStream.addTrack(localVideoTrack);
          peerConnection.addStream(mediaStream);
      }
  
      @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() &gt; 1 || stream.videoTracks.size() &gt; 1) {
                          // /reportError("Weird-looking stream: " + stream);
                          return;
                      }
                      if (stream.videoTracks.size() == 1) {
                          remoteVideoTrack = stream.videoTracks.get(0);
                          remoteVideoTrack.setEnabled(true);
                          remoteVideoTrack.addRenderer(new VideoRenderer(remoteRender));
                      }
                  }
              });
  
          }
  
          @Override
          public void onDataChannel(DataChannel arg0) {
              Log.e("On data channel", "Data Channel");
  
          }
  
          @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) {
              }
  
              socket.emit("message", json);
          }
  
          @Override
          public void onIceConnectionChange(IceConnectionState arg0) {
              Log.e("onIceConnectionChange", "onIceConnectionChange");
          }
  
          @Override
          public void onIceConnectionReceivingChange(boolean arg0) {
              Log.e("onIceConnectionReceivingChange", "onIceConnectionReceivingChange");
          }
  
          @Override
          public void onIceGatheringChange(IceGatheringState arg0) {
              Log.e("onIceGatheringChange", "onIceGatheringChange");
          }
  
          @Override
          public void onRemoveStream(MediaStream arg0) {
              Log.e("onRemoveStream", "onRemoveStream");
          }
  
          @Override
          public void onRenegotiationNeeded() {
              Log.e("onRenegotiationNeeded", "onRenegotiationNeeded");
          }
  
          @Override
          public void onSignalingChange(SignalingState arg0) {
              Log.e("onSignalingChange", "onSignalingChange");
          }
  
      }
  
      public void creatPcConstrains(MediaConstraints pcConstraints) {
          pcConstraints.optional.add(new KeyValuePair("DtlsSrtpKeyAgreement", "true"));
          pcConstraints.optional.add(new KeyValuePair("RtpDataChannels", "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 VP8 HW video encoder is supported and video resolution is not
          // specified force it to HD.
          if ((videoWidth == 0 || videoHeight == 0) &amp;&amp; true &amp;&amp; MediaCodecVideoEncoder.isVp8HwSupported()) {
              videoWidth = 1280;
              videoHeight = 1280;
          }
  
          // Add video resolution constraints.
          if (videoWidth &gt; 0 &amp;&amp; videoHeight &gt; 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)));
          }
  
          // Add fps constraints.
          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"));
      }
      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;
              }
              String sdpDescription = origSdp.description;
              if (preferIsac) {
                  sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);
              }
              if (videoCallEnabled &amp;&amp; preferH264) {
                  sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);
              }
              final SessionDescription sdp = new SessionDescription(origSdp.type, sdpDescription);
              localSdp = sdp;
              executor.execute(new Runnable() {
                  @Override
                  public void run() {
                      if (peerConnection != null) {
                          JSONObject json = new JSONObject();
                          try {
  //                          peerConnection.setLocalDescription(null, localSdp);
                              json.putOpt("type", "offer");
                              json.putOpt("sdp", localSdp.description);
                          } catch (JSONException e) {
                              e.printStackTrace();
                          }
  
                          Home.socket.emit("message", json);
                      }
                  }
              });
          }
  
          @Override
          public void onSetFailure(String arg0) {
              Log.e("onSetFailure", arg0);
  
          }
  
          @Override
          public void onSetSuccess() {
  
              executor.execute(new Runnable() {
                  @Override
                  public void run() {
                      if (peerConnection == null) {
                          return;
                      }
                      if (isInitiator) {
                          createOffer();
  
                      } else {
                           createAnswer();
  
                      }
                  }
              });
  
          }
  
      }
  
      private static String preferCodec(String sdpDescription, String codec, boolean isAudio) {
          String[] lines = sdpDescription.split("\r\n");
          int mLineIndex = -1;
          String codecRtpMap = null;
          // a=rtpmap:&lt;payload type&gt; &lt;encoding name&gt;/&lt;clock rate&gt; [/&lt;encoding
          // parameters&gt;]
          String regex = "^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$";
          Pattern codecPattern = Pattern.compile(regex);
          String mediaDescription = "m=video ";
          if (isAudio) {
              mediaDescription = "m=audio ";
          }
          for (int i = 0; (i &lt; lines.length) &amp;&amp; (mLineIndex == -1 || codecRtpMap == null); i++) {
              if (lines[i].startsWith(mediaDescription)) {
                  mLineIndex = i;
                  continue;
              }
              Matcher codecMatcher = codecPattern.matcher(lines[i]);
              if (codecMatcher.matches()) {
                  codecRtpMap = codecMatcher.group(1);
                  continue;
              }
          }
          if (mLineIndex == -1) {
              // Log.w(TAG, "No " + mediaDescription + " line, so can't prefer " +
              // codec);
              return sdpDescription;
          }
          if (codecRtpMap == null) {
              // Log.w(TAG, "No rtpmap for " + codec);
              return sdpDescription;
          }
          // Log.d(TAG, "Found " + codec + " rtpmap " + codecRtpMap +
          // ", prefer at "
          // + lines[mLineIndex]);
          String[] origMLineParts = lines[mLineIndex].split(" ");
          if (origMLineParts.length &gt; 3) {
              StringBuilder newMLine = new StringBuilder();
              int origPartIndex = 0;
              // Format is: m=&lt;media&gt; &lt;port&gt; &lt;proto&gt; &lt;fmt&gt; ...
              newMLine.append(origMLineParts[origPartIndex++]).append(" ");
              newMLine.append(origMLineParts[origPartIndex++]).append(" ");
              newMLine.append(origMLineParts[origPartIndex++]).append(" ");
              newMLine.append(codecRtpMap);
              for (; origPartIndex &lt; origMLineParts.length; origPartIndex++) {
                  if (!origMLineParts[origPartIndex].equals(codecRtpMap)) {
                      newMLine.append(" ").append(origMLineParts[origPartIndex]);
                  }
              }
              lines[mLineIndex] = newMLine.toString();
              // Log.d(TAG, "Change media description: " + lines[mLineIndex]);
          } else {
              // Log.e(TAG, "Wrong SDP media description format: " +
              // lines[mLineIndex]);
          }
          StringBuilder newSdpDescription = new StringBuilder();
          for (String line : lines) {
              newSdpDescription.append(line).append("\r\n");
          }
          return newSdpDescription.toString();
      }
  
      private void drainCandidates() {
          if (queuedRemoteCandidates != null) {
              // Log.d(TAG, "Add " + queuedRemoteCandidates.size() +
              // " remote candidates");
              for (IceCandidate candidate : queuedRemoteCandidates) {
                  // if(!candidate.sdpMid.contains("video"))
                  peerConnection.addIceCandidate(candidate);
              }
              queuedRemoteCandidates = null;
          }
      }
  
      public void addRemoteIceCandidate(final IceCandidate candidate) {
          executor.execute(new Runnable() {
              @Override
              public void run() {
                  if (peerConnection != null) {
                      if (queuedRemoteCandidates != null) {
                          queuedRemoteCandidates.add(candidate);
                      } else {
                          peerConnection.addIceCandidate(candidate);
                      }
                  }
              }
          });
      }
  
      public void setRemoteDescription(final SessionDescription sdp) {
          executor.execute(new Runnable() {
              @Override
              public void run() {
                  if (peerConnection == null) {
                      return;
                  }
                  String sdpDescription = sdp.description;
                  if (preferIsac) {
                      sdpDescription = preferCodec(sdpDescription, AUDIO_CODEC_ISAC, true);
                  }
                  if (videoCallEnabled &amp;&amp; preferH264) {
                      sdpDescription = preferCodec(sdpDescription, VIDEO_CODEC_H264, false);
                  }
  
                  SessionDescription sdpRemote = new SessionDescription(sdp.type, sdpDescription);
                  peerConnection.setRemoteDescription(sdpObserver, sdpRemote);
              }
          });
      }
  
      @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")) {
                  if (isInitiator) {
                      SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
                      setRemoteDescription(sdp);
                  } else {
                  }
              } else if (type.equals("offer")) {
                  if (!isInitiator) {
                      SessionDescription sdp = new SessionDescription(SessionDescription.Type.fromCanonicalForm(type), json.getString("sdp"));
                      setRemoteDescription(sdp);
                  } else {
  
                  }
              } 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);
  
      }
  
  }
  </code></pre>