2

I have searched many web pages and even have bouhgt "getting started with webrtc" book, whose sample gives errors. However I could not work find any working example of a working 1to1 video chat.

Better is using nodeJS transmission.

I think samples I have found do not respect these rules:

  1. Peers must be present with local streaming video before sending SIP (offer/answer SDP)
  2. For ‘Answerer’; Do not add ICE candidate until the peer generates the ‘Answer SDP’
  3. Once remote media starts streaming stop adding ICE candidates
  4. Never create peer connect for answerer until you get the ‘Offer SDP’

I have found many personal APIs or personal aproaches, but could not find the SIMPLEST way to create a single 1to1 video chat.

Best regards

Sample from webrtc book (that does work) gives error:

Mon Apr 14 2014 23:19:26 GMT+0200 (Paris, Madrid invalid signal:
{"type":"new_ice_candidate","candidate":{"candidate":"candidate:1 2 UDP 16924671
98 90.7.245.247 63704 typ srflx raddr 192.168.1.15 rport 63704","sdpMid":"","sdp
MLineIndex":0}}

Client side:

<!DOCTYPE html>
<html>
<head>
<script>

var webrtc_capable = true;
var rtc_peer_connection = null;
var rtc_session_description = null;
var get_user_media = null;
var connect_stream_to_src = null;
var stun_server = "stun01.sipphone.com";

if (navigator.getUserMedia) { // WebRTC 1.0 standard compliant browser
  rtc_peer_connection = RTCPeerConnection;
  rtc_session_description = RTCSessionDescription;
  get_user_media = navigator.getUserMedia.bind(navigator);
  connect_stream_to_src = function(media_stream, media_element) {
    // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21606
    media_element.srcObject = media_stream;
    media_element.play();
  };
} else if (navigator.mozGetUserMedia) { // early firefox webrtc implementation
  rtc_peer_connection = mozRTCPeerConnection;
  rtc_session_description = mozRTCSessionDescription;
  get_user_media = navigator.mozGetUserMedia.bind(navigator);
  connect_stream_to_src = function(media_stream, media_element) {
    media_element.mozSrcObject = media_stream;
    media_element.play();
  };
  stun_server = "74.125.31.127:19302";
} else if (navigator.webkitGetUserMedia) { // early webkit webrtc implementation
  rtc_peer_connection = webkitRTCPeerConnection;
  rtc_session_description = RTCSessionDescription;
  get_user_media = navigator.webkitGetUserMedia.bind(navigator);
  connect_stream_to_src = function(media_stream, media_element) {
    media_element.src = webkitURL.createObjectURL(media_stream);
  };
} else {
  alert("This browser does not support WebRTC - visit WebRTC.org for more info");
  webrtc_capable = false;
}
</script>
<script>

var call_token; // unique token for this call
var signaling_server; // signaling server for this call
var peer_connection; // peer connection object

function start() {
  // create the WebRTC peer connection object
  peer_connection = new rtc_peer_connection({ // RTCPeerConnection configuration 
    "iceServers": [ // information about ice servers
      { "url": "stun:"+stun_server }, // stun server info
    ]
  });

  // generic handler that sends any ice candidates to the other peer
  peer_connection.onicecandidate = function (ice_event) {
    if (ice_event.candidate) {
      signaling_server.send(
        JSON.stringify({
          type: "new_ice_candidate",
          candidate: ice_event.candidate ,
        })
      );
    }
  };

  // display remote video streams when they arrive using local <video> MediaElement
  peer_connection.onaddstream = function (event) {
    connect_stream_to_src(event.stream, document.getElementById("remote_video"));
    // hide placeholder and show remote video
    document.getElementById("loading_state").style.display = "none";
    document.getElementById("open_call_state").style.display = "block";
  };

  // setup stream from the local camera 
  setup_video();

  // setup generic connection to the signaling server using the WebSocket API
  signaling_server = new WebSocket("ws://192.168.1.15:1234");

  if (document.location.hash === "" || document.location.hash === undefined) { // you are the Caller

    // create the unique token for this call 
    var token = Date.now()+"-"+Math.round(Math.random()*10000);
    var token = Math.round(Math.random()*10000);
    call_token = "#"+token;
    // set location.hash to the unique token for this call
    document.location.hash = token;
    signaling_server.onopen = function() {
      // setup caller signal handler
      signaling_server.onmessage = caller_signal_handler;

      // tell the signaling server you have joined the call 
      signaling_server.send(
        JSON.stringify({ 
          token:call_token,
          type:"join",
        })
      );
    }

    document.title = "You are the Caller";
    document.getElementById("loading_state").innerHTML = "Ready for a call...ask your friend to visit:<br/><br/>"+document.location;

  } else { // you have a hash fragment so you must be the Callee 

    // get the unique token for this call from location.hash
    call_token = document.location.hash;

    signaling_server.onopen = function() {
      // setup caller signal handler
      signaling_server.onmessage = callee_signal_handler;

      // tell the signaling server you have joined the call 
      signaling_server.send(
        JSON.stringify({ 
          token:call_token,
          type:"join",
        })
      );

      // let the caller know you have arrived so they can start the call
      signaling_server.send(
        JSON.stringify({ 
          token:call_token,
          type:"callee_arrived",
        })
      );
    }

    document.title = "You are the Callee";
    document.getElementById("loading_state").innerHTML = "One moment please...connecting your call...";
  }
}

/* functions used above are defined below */

// handler to process new descriptions
function new_description_created(description) {
  peer_connection.setLocalDescription(
    description, 
    function () {
      signaling_server.send(
        JSON.stringify({
          token:call_token,
          type:"new_description",
          sdp:description 
        })
      );
    }, 
    log_error
  );
}

// handle signals as a caller
function caller_signal_handler(event) {
  var signal = JSON.parse(event.data);
  if (signal.type === "callee_arrived") {
    peer_connection.createOffer(
      new_description_created, 
      log_error
    );
  } else if (signal.type === "new_ice_candidate") {
    peer_connection.addIceCandidate(
      new RTCIceCandidate(signal.candidate)
    );
  } else if (signal.type === "new_description") {
    peer_connection.setRemoteDescription(
      new rtc_session_description(signal.sdp), 
      function () {
        if (peer_connection.remoteDescription.type == "answer") {
          // extend with your own custom answer handling here
        }
      },
      log_error
    );
  } else {
    // extend with your own signal types here
  }
}

// handle signals as a callee
function callee_signal_handler(event) {
  var signal = JSON.parse(event.data);
  if (signal.type === "new_ice_candidate") {
    peer_connection.addIceCandidate(
      new RTCIceCandidate(signal.candidate)
    );
  } else if (signal.type === "new_description") {
    peer_connection.setRemoteDescription(
      new rtc_session_description(signal.sdp), 
      function () {
        if (peer_connection.remoteDescription.type == "offer") {
          peer_connection.createAnswer(new_description_created, log_error);
        }
      },
      log_error
    );
  } else {
    // extend with your own signal types here
  }
}

// setup stream from the local camera 
function setup_video() {
  get_user_media(
    { 
      "audio": true, // request access to local microphone
      "video": true  // request access to local camera
    }, 
    function (local_stream) { // success callback
      // display preview from the local camera & microphone using local <video> MediaElement
      connect_stream_to_src(local_stream, document.getElementById("local_video"));
      // add local camera stream to peer_connection ready to be sent to the remote peer
      peer_connection.addStream(local_stream);
    },
    log_error
  );
}

// generic error handler
function log_error(error) {
  console.log(error);
}

</script>
<style>
html, body {
  padding: 0px;
  margin: 0px;
  font-family: "Arial","Helvetica",sans-serif;
}
#loading_state {
  position: absolute;
  top: 45%;
  left: 0px;
  width: 100%;
  font-size: 20px;
  text-align: center;
}
#open_call_state {
  display: none;
}
#local_video {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 160px;
  height: 120px;
  background: #333333;
}
#remote_video {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 1024px;
  height: 768px;
  background: #999999;
}
</style>
</head>
<body onload="start()">
  <div id="loading_state">
    loading...
  </div>
  <div id="open_call_state">
    <video id="remote_video"></video>
    <video id="local_video"></video>
  </div>
</body>
</html>

server side (nodejs)

// useful libs
var http = require("http");
var fs = require("fs");
var websocket = require("websocket").server;

// general variables
var port = 1234;
var webrtc_clients = [];
var webrtc_discussions = {};

// web server functions
var http_server = http.createServer(function(request, response) {
  var matches = undefined;
  if (matches = request.url.match("^/images/(.*)")) {
    var path = process.cwd()+"/images/"+matches[1];
    fs.readFile(path, function(error, data) {
      if (error) {
        log_error(error);
      } else {
        response.end(data);
      }
    });
  } else {
    response.end(page);
  }
});
http_server.listen(port, function() {
  log_comment("server listening (port "+port+")");
});
var page = undefined;
fs.readFile("basic_video_call.html", function(error, data) {
  if (error) {
    log_error(error);
  } else {
    page = data;
  }
});

// web socket functions
var websocket_server = new websocket({
  httpServer: http_server
});
websocket_server.on("request", function(request) {
  log_comment("new request ("+request.origin+")");

  var connection = request.accept(null, request.origin);
  log_comment("new connection ("+connection.remoteAddress+")");

  webrtc_clients.push(connection);
  connection.id = webrtc_clients.length-1;

  connection.on("message", function(message) {
    if (message.type === "utf8") {
      log_comment("got message "+message.utf8Data);

      var signal = undefined;
      try { signal = JSON.parse(message.utf8Data); } catch(e) { };
      if (signal) {
        if (signal.type === "join" && signal.token !== undefined) {
          try {
            if (webrtc_discussions[signal.token] === undefined) {
              webrtc_discussions[signal.token] = {};
            }
          } catch(e) { };
          try {
            webrtc_discussions[signal.token][connection.id] = true;
          } catch(e) { };
        } else if (signal.token !== undefined) {
          try {
            Object.keys(webrtc_discussions[signal.token]).forEach(function(id) {
              if (id != connection.id) {
                webrtc_clients[id].send(message.utf8Data, log_error);
              }
            });
          } catch(e) { };
        } else {
          log_comment("signal.type="+signal.type+" *invalid signal: "+message.utf8Data);
        }
      } else {
        log_comment("**invalid signal: "+message.utf8Data);
      }
    }
  });

  connection.on("close", function(connection) {
    log_comment("connection closed ("+connection.remoteAddress+")");    
    Object.keys(webrtc_discussions).forEach(function(token) {
      Object.keys(webrtc_discussions[token]).forEach(function(id) {
        if (id === connection.id) {
          delete webrtc_discussions[token][id];
        }
      });
    });
  });
});

// utility functions
function log_error(error) {
  if (error !== "Connection closed" && error !== undefined) {
    log_comment("ERROR: "+error);
  }
}
function log_comment(comment) {
  console.log((new Date())+" "+comment);
}
yarek
  • 11,278
  • 30
  • 120
  • 219
  • Simply checking `peer_connection.signalingState` in console returns `stable`, but checking ICE (`peer_connection.iceConnectionState`) returns `new`. Also, when logging the received messages in `callee_signal_handler` and `caller_signal_handler`, I don't see any ICE connection thing (callee_signal_handler doesn't show up __any__ message). It would probably be best to write it all yourself (and I believe that is your idea, but you just need an example on how to do it). In [this](http://stackoverflow.com/a/21103641/2844473) answer I explain in detail how to set up a connection. Have a look at it. – MarijnS95 Apr 27 '14 at 10:29

1 Answers1

1

You can refer to the samples on codelab. Try to run the Step 7: Putting it all together: RTCPeerConnection + RTCDataChannel + signaling. Node.js is needed. You can also read the article How to Implement a Real-time Commnication Application with WebRTC, which shares some learning experience based on codelab.

yushulx
  • 11,695
  • 8
  • 37
  • 64
  • there is absolutely NOTHING simple about the information provided on that link! and most probably that's why it wasn't accepted as the correct answer, – James Juanjie Apr 03 '19 at 13:50