title: ApprtcDemo with local server works between browsers but not Android native to browser
body: <p>I am developing a chat application and done with it. Now I want to implement video chat also.
  After research a lot I decided to go with "WebRTC" library.</p>
  
  <p><strong>What I have done?</strong></p>
  
  <p>1) Able to run AppRtcDemo at local server and Its working fine between browsers.</p>
  
  <p>Reference : <a href="http://www.webrtc.org/reference/getting-started">http://www.webrtc.org/reference/getting-started</a></p>
  
  <p>2) Able to build Android AppRtcDemo.But when I run it say "Cross origin does not support".</p>
  
  <p>After research I found in webrtc discussion that to resolve this issue I need to set-up  own turn server.</p>
  
  <p>3) So I install latest rfc5766TurnServer recommended by webrtc. I got success to run turn server.</p>
  
  <p>Reference : <a href="http://code.google.com/p/rfc5766-turn-server/">http://code.google.com/p/rfc5766-turn-server/</a></p>
  
  <p><strong>I do following changes to ApprtcDemo (web) and (Android) to work with my Turn server</strong></p>
  
  <p><strong>1) apprtc.py</strong></p>
  
  <p>Replace:</p>
  
  <pre><code>turn_url = 'https://computeengineondemand.appspot.com/'
  turn_url = turn_url + 'turn?' + 'username=' + user + '&amp;key=4080218913'
  </code></pre>
  
  <p>With point to my turn server:</p>
  
  <pre><code>turn_url = 'http://192.168.5.85:3478/?service=turn&amp;username=biraj'
  </code></pre>
  
  <p><strong>2) index.html</strong></p>
  
  <p>Replace:</p>
  
  <pre><code>var pcConfig = {{ pc_config|safe }};
  </code></pre>
  
  <p>With:</p>
  
  <pre><code>var pcConfig = {"iceServers": [{"url": "stun:stun.l.google.com:19302"},            {"url":"turn:biraj@192.168.5.85:3479", "credential":"0x5b04123c3eec4cf0be64ab909bb2ff5b"}]};
  </code></pre>
  
  <p><strong>Android</strong></p>
  
  <p><strong>1)AppRTCDemoActivity.java</strong></p>
  
  <p>Replace:</p>
  
  <pre><code>roomInput.setText("https://apprtc.appspot.com/?r=");
  </code></pre>
  
  <p>With my local apprtc server:</p>
  
  <pre><code>roomInput.setText("http://192.168.5.86:8080/?r=");
  </code></pre>
  
  <p><strong>2) AppRTCClient.java</strong></p>
  
  <p>In <code>private PeerConnection.IceServer requestTurnServer(String url){}</code> function</p>
  
  <p>Replace:</p>
  
  <pre><code>connection.addRequestProperty("origin", "https://apprtc.appspot.com");
  </code></pre>
  
  <p>With:</p>
  
  <pre><code>connection.addRequestProperty("origin", "http://192.168.5.86:8080");
  </code></pre>
  
  <p><strong>3) /assets/channel.html</strong></p>
  
  <p>Replace:</p>
  
  <pre><code>&lt;script src="https://apprtc.appspot.com/_ah/channel/jsapi"&gt;&lt;/script&gt;
  </code></pre>
  
  <p>With:</p>
  
  <pre><code>&lt;script src="http://192.168.5.86:8080/_ah/channel/jsapi"&gt;&lt;/script&gt;
  </code></pre>
  
  <p>Now my question is why this is working between browsers but not between android AppRtcDemo and browser.</p>
  
  <p>When I run AppRtcDemo on android after doing above changes local camera preview is started at right-top corner and message prompt "waiting for ICEcandidates" then nothing happens.</p>
  
  <p>Thanks in advance.</p>
  
  <p>Thanks to All for supporting my question.After long rocky ride with ApprtcDemo I got success  and it works fine.I am posting the solution.</p>
  
  <p>Find the "<strong>GAEChannelClient.java</strong>" java file.</p>
  
  <p>and do change as below.</p>
  
  <pre><code>/*
   * libjingle
   * Copyright 2013, Google Inc.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *  1. Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *  2. Redistributions in binary form must reproduce the above copyright notice,
   *     this list of conditions and the following disclaimer in the documentation
   *     and/or other materials provided with the distribution.
   *  3. The name of the author may not be used to endorse or promote products
   *     derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
   * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
   * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package org.appspot.apprtc;
  
  import java.io.InputStream;
  
  import android.annotation.SuppressLint;
  import android.app.Activity;
  import android.util.Log;
  import android.webkit.ConsoleMessage;
  import android.webkit.JavascriptInterface;
  import android.webkit.WebChromeClient;
  import android.webkit.WebView;
  import android.webkit.WebViewClient;
  
  /**
   * Java-land version of Google AppEngine's JavaScript Channel API:
   * https://developers.google.com/appengine/docs/python/channel/javascript
   * 
   * Requires a hosted HTML page that opens the desired channel and dispatches JS
   * on{Open,Message,Close,Error}() events to a global object named
   * "androidMessageHandler".
   */
  public class GAEChannelClient {
      private static final String TAG = "GAEChannelClient";
      private WebView webView;
      private final ProxyingMessageHandler proxyingMessageHandler;
  
      /**
       * Callback interface for messages delivered on the Google AppEngine
       * channel.
       * 
       * Methods are guaranteed to be invoked on the UI thread of |activity|
       * passed to GAEChannelClient's constructor.
       */
      public interface MessageHandler {
          public void onOpen();
  
          public void onMessage(String data);
  
          public void onClose();
  
          public void onError(int code, String description);
      }
  
      /** Asynchronously open an AppEngine channel. */
      @SuppressLint("SetJavaScriptEnabled")
      public GAEChannelClient(Activity activity, String token, MessageHandler handler) {
          webView = new WebView(activity);
  
          webView.getSettings().setJavaScriptEnabled(true);
          webView.getSettings().setAllowFileAccessFromFileURLs(true); // Maybe you
                                                                      // don't
                                                                      // need this
                                                                      // rule
          webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
  
          webView.setWebChromeClient(new WebChromeClient() { // Purely for
                                                              // debugging.
              public boolean onConsoleMessage(ConsoleMessage msg) {
                  Log.d(TAG, "console: " + msg.message() + " at " + msg.sourceId() + ":" + msg.lineNumber());
                  return false;
              }
          });
          webView.setWebViewClient(new WebViewClient() { // Purely for debugging.
              public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                  Log.e(TAG, "JS error: " + errorCode + " in " + failingUrl + ", desc: " + description);
              }
  
              @Override
              public boolean shouldOverrideUrlLoading(WebView view, String url) {
                  System.out.println("HI");
                  return super.shouldOverrideUrlLoading(view, url);
              }
          });
  
          proxyingMessageHandler = new ProxyingMessageHandler(activity, handler, token);
          webView.addJavascriptInterface(proxyingMessageHandler, "androidMessageHandler");
  //       webView.loadUrl("file:///android_asset/channel.html");
          try {
              InputStream is = activity.getAssets().open("channel.html");
              StringBuilder builder = new StringBuilder();
              byte[] buffer = new byte[1024];
              while (is.read(buffer) != -1) {
                  builder.append(new String(buffer));
              }
              is.close();
              String str = builder.toString();
              webView.loadDataWithBaseURL("http://192.168.5.86:8080", str, "text/html", "utf-8", null);
          } catch (Exception e) {
              e.printStackTrace();
          }
  
      }
  
      /** Close the connection to the AppEngine channel. */
      public void close() {
          if (webView == null) {
              return;
          }
          proxyingMessageHandler.disconnect();
          webView.removeJavascriptInterface("androidMessageHandler");
          webView.loadUrl("about:blank");
          webView = null;
      }
  
      // Helper class for proxying callbacks from the Java&lt;-&gt;JS interaction
      // (private, background) thread to the Activity's UI thread.
      private static class ProxyingMessageHandler {
          private final Activity activity;
          private final MessageHandler handler;
          private final boolean[] disconnected = { false };
          private final String token;
  
          public ProxyingMessageHandler(Activity activity, MessageHandler handler, String token) {
              this.activity = activity;
              this.handler = handler;
              this.token = token;
          }
  
          public void disconnect() {
              disconnected[0] = true;
          }
  
          private boolean disconnected() {
              return disconnected[0];
          }
  
          @JavascriptInterface
          public String getToken() {
              return token;
          }
  
          @JavascriptInterface
          public void onOpen() {
  
              System.out.println("GAEClient : Open" );
              activity.runOnUiThread(new Runnable() {
                  public void run() {
                      if (!disconnected()) {
                          handler.onOpen();
                      }
                  }
              });
          }
  
          @JavascriptInterface
          public void onMessage(final String data) {
              System.out.println("GAEClient : Message : " +data );
              activity.runOnUiThread(new Runnable() {
                  public void run() {
                      if (!disconnected()) {
                          handler.onMessage(data);
                      }
                  }
              });
          }
  
          @JavascriptInterface
          public void onClose() {
              System.out.println("GAEClient : Close" );
              activity.runOnUiThread(new Runnable() {
                  public void run() {
                      if (!disconnected()) {
                          handler.onClose();
                      }
                  }
              });
          }
  
          @JavascriptInterface
          public void onError(final int code, final String description) {
              System.out.println("GAEClient : Erroe : " + description);
              activity.runOnUiThread(new Runnable() {
                  public void run() {
                      if (!disconnected()) {
                          handler.onError(code, description);
                      }
                  }
              });
          }
      }
  }
  </code></pre>
  
  <p><strong>Channel.html</strong> in assets folder</p>
  
  <pre><code>&lt;html&gt;
    &lt;head&gt;
      &lt;script src="http://192.168.5.86:8080/_ah/channel/jsapi"&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;!--
    Helper HTML that redirects Google AppEngine's Channel API to a JS object named
    |androidMessageHandler|, which is expected to be injected into the WebView
    rendering this page by an Android app's class such as AppRTCClient.
    --&gt;
    &lt;body onbeforeunload="closeSocket()" onload="openSocket()"&gt;
      &lt;script type="text/javascript"&gt;
        var token = androidMessageHandler.getToken();
        if (!token)
          throw "Missing/malformed token parameter: [" + token + "]";
  
        var channel = null;
        var socket = null;
  
        function openSocket() {
          channel = new goog.appengine.Channel(token);
          socket = channel.open({
            'onopen': function() { androidMessageHandler.onOpen(); },
            'onmessage': function(msg) { androidMessageHandler.onMessage(msg.data); },
            'onclose': function() { androidMessageHandler.onClose(); },
            'onerror': function(err) { androidMessageHandler.onError(err.code, err.description); }
          });
        }
  
        function closeSocket() {
          socket.close();
        }
      &lt;/script&gt;
    &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>