Re: Proposed resolution to Issue 93: ICE restart behavior still needs to be resolved

Peter Thatcher has suggested a simplification of the ICE restart proposal.  Rather than having a gather() method within the RTCIceListener, the ICE Listener can be constructed with a gather policy.  Also, rather than having ICE gathering state in either the RTCIceListener or the RTCIceTransport, gathering begins when an RTCIceListener is constructed, and fires an "icegatheringcomplete" event when ICE gathering is completed.

Assuming that the onlocalcandidate Event Handler and the getLocalCandidates() method remain on the RTCIceTransport (it may be possible to only have these on the RTCIceListener), then the RTCIceTransport and RTCIceListener interfaces would look like this:

[Constructor(RTCIceListener iceListener),
     Constructor(RTCIceOptions options)]
interface RTCIceTransport : RTCStatsProvider {
    readonly    attribute RTCIceListener       iceListener;
    readonly    attribute RTCIceRole           role;
    readonly    attribute RTCIceComponent      component;
    readonly    attribute RTCIceTransportState state;
    sequence<RTCIceCandidate> getLocalCandidates ();
    sequence<RTCIceCandidate> getRemoteCandidates ();
    void                      start (RTCIceParameters remoteParameters, optional RTCIceRole role, optional RTCIceListener listener);
    void                      stop ();
    RTCIceParameters?         getRemoteParameters ();
    RTCIceTransport           createAssociatedTransport ();
    void                      addRemoteCandidate (RTCIceCandidate remoteCandidate);
    void                      setRemoteCandidates (sequence<RTCIceCandidate> remoteCandidates);
                attribute EventHandler?        onlocalcandidate;
                attribute EventHandler?        onicestatechange;
};

[Constructor(RTCIceGatherOptions options)]
interface RTCIceListener {
    readonly    attribute boolean       complete;
    RTCIceParameters          getLocalParameters ();
    sequence<RTCIceCandidate> getLocalCandidates ();
                attribute EventHandler? onerror;
                attribute EventHandler? onlocalcandidate;
                attribute EventHandler? oncomplete;
};

dictionary RTCIceOptions {
    sequence<RTCIceServer> iceServers;
};

dictionary RTCIceGatherOptions {
    RTCIceGatherPolicy     gatherPolicy;
    sequence<RTCIceServer> iceservers;
};

[Constructor(DOMString type)]
interface RTCIceGatheringCompleteEvent : Event {
};

NOTE:  If the onlocalcandidate Event Handler and the getLocalCandidates() method are moved to the RTCIceListener, the RTCIceListener would then have all the ICE candidate gathering functionality, so that it may make sense to rename it the "RTCIceGatherer".

The above changes don't appear to have much impact on the sample code.  Some rewritten examples appear below.

EXAMPLE 3


// Assume we already have a way to signal. This is an example
// of  how to offer ICE and DTLS parameters and ICE candidates and
// get back ICE and DTLS parameters and ICE candidates, and start
// both ICE and DTLS, assuming that RTP and RTCP are multiplexed.

function initiate(mySignaller) {
var iceOptions = ...;
var ice = new RTCIceTransport(iceOptions);
var dtls = new RTCDtlsTransport(ice);
// ... get tracks and RTP objects from other example

mySignaller.mySendInitiate({
   "ice": ice.iceListener.getLocalParameters(),
   "dtls": dtls.getLocalParameters(),
   // ... include RTP info from other example
}, function(remote) {
   // Start the ICE transport with an implicit gather policy of "all"
   ice.start(remote.ice, RTCIceRole.controlling);
   dtls.start(remote.dtls);
   // ... start RTP senders and receivers from other example
});

ice.onlocalcandidate = function(candidate) {
   mySignaller.mySendLocalCandidate(candidate);
}

mySignaller.onRemoteCandidate = function(candidate) {
   ice.addRemoteCandidate(candidate);
}
}


EXAMPLE 4


// Assume we already have a way to signal and remote info is
// signalled to us.  This is an example of how to answer with ICE and DTLS
// and DTLS parameters and ICE candidates and start both ICE and DTLS,
// assuming that RTP and RTCP are multiplexed.
//
function accept(mySignaller, remote) {
var iceOptions = ...;
var ice = new RTCIceTransport(iceOptions);
var dtls = new RTCDtlsTransport(ice);
// ... get tracks and RTP objects from other example
ice.onlocalcandidate = function(candidate) {
   mySignaller.mySendLocalCandidate(candidate);
}

mySignaller.onRemoteCandidate = function(candidate) {
   ice.addRemoteCandidate(candidate);
}

mySignaller.mySendAccept({
   "ice": ice.iceListener.getLocalParameters(),
   "dtls": dtls.getLocalParameters()
   // ... include RTP info from other example
});
// Start the ICE transport with an implicit gather policy of "all"
ice.start(remote.ice, RTCIceRole.controlled);
dtls.start(remote.dtls);

// ... start RTP senders and receivers from other example
}

EXAMPLE 5

// This is an example of how to utilize distinct ICE transports for Audio and Video
// As well as for RTP and RTCP.  If both sides can multiplex audio/video
// and/or RTP/RTCP then the multiplexing will occur.
//
// Assume we have an audioTrack and a videoTrack to send.
//
//create the RTP and RTCP ICE transports for audio and video
var audioRtpIceTransport = new RTCIceTransport(...);
var audioRtcpIceTransport = audioRtpIceTransport.createAssociatedTransport();
var videoRtpIceTransport = new RTCIceTransport(...);
var videoRtcpIceTransport = audioRtpIceTransport.createAssociatedTransport();
//
// Prepare the audio and video ICE transports
audioRtpIceTransport.onlocalcandidate = function (event)  {sendLocalCandidate(audioRtpIceTransport, event.candidate,"audio")};
audioRtcpIceTransport.onlocalcandidate = function (event) {sendLocalCandidate(audioRtcpIceTransport, event.candidate,"audio")};
videoRtpIceTransport.onlocalcandidate = function (event)  {sendLocalCandidate(videoRtpIceTransport, event.candidate,"video")};
videoRtcpIceTransport.onlocalcandidate = function (event) {sendLocalCandidate(videoRtcpIceTransport, event.candidate,"video")};
audioRtpIceTransport.onicestatechange = ... ;
audioRtpIceTransport.iceListener.oncomplete = ... ;
audioRtpIceTransport.iceListener.onerror = errorHandler;
audioRtcpIceTransport.onicestatechange = ... ;
audioRtcpIceTransport.iceListener.oncomplete = ... ;
audioRtcpIceTransport.iceListener.onerror = errorHandler;
videoRtpIceTransport.onicestatechange = ... ;
videoRtpIceTransport.iceListener.oncomplete = ... ;
videoRtpIceTransport.iceListener.onerror = errorHandler;
videoRctpIceTransport.onicestatechange = ... ;
videoRctpIceTransport.iceListener.oncomplete = ... ;
videoRctpIceTransport.iceListener.onerror = errorHandler;
//Prepare the remote candidate handler
mySignaller.onRemoteCandidate = function(remote) {
   switch (remote.kind) {
      case "audio":
        if (remote.component === RTCIceComponent.RTP){
           audioRtpIceTransport.addRemoteCandidate(remote.candidate);
        } else {
           audioRtcpIceTransport.addRemoteCandidate(remote.candidate);
        };
        break;
      case "video":
        if (remote.component === RTCIceComponent.RTP){
           videoRtpIceTransport.addRemoteCandidate(remote.candidate);
        } else {
           videoRtcpIceTransport.addRemoteCandidate(remote.candidate);
        };
        break;
      default:
         log('Invalid media type received');
   };
}
// Create the DTLS transports
var audioRtpDtlsTransport = new RTCDtlsTransport(audioRtpIceTransport);
var audioRtcpDtlsTransport = new RTCDtlsTransport(audioRtcpIceTransport);
var videoRtpDtlsTransport = new RTCDtlsTransport(videoRtpIceTransport);
var videoRtcpDtlsTransport = new RTCDtlsTransport(videoRtcpIceTransport);
//
// Create the sender and receiver objects
var audioSender = new RtpSender(audioTrack, audioRtpDtlsTransport, audioRtcpDtlsTransport);
var videoSender = new RtpSender(videoTrack, videoRtpDtlsTransport, videoRtcpDtlsTransport);
var audioReceiver = new RtpReceiver(audioRtpDtlsTransport, audioRtcpDtlsTransport);
var videoReceiver = new RtpReceiver(videoRtpDtlsTransport, videoRtcpDtlsTransport);
//
// Retrieve the receiver and sender capabilities
var recvAudioCaps = RTCRtpReceiver.getCapabilities("audio");
var recvVideoCaps = RTCRtpReceiver.getCapabilities("video");
var sendAudioCaps = RTCRtpSender.getCapabilities("audio");
var recvVideoCaps = RTCRtpSender.getCapabilities("video");
//
// At this point, ICE/DTLS parameters and Send/Receive capabilities can be exchanged.
mySignaller.myOfferTracks({
   // Indicate that the initiator would prefer to multiplex both A/V and RTP/RTCP
   "bundle": true,
   // Indicate that the initiator is willing to multiplex RTP/RTCP without A/V mux
   "rtcpMux": true,
   // Offer the ICE parameters
   "audioRtpIce": audioRtpIceTransport.iceListener.getLocalParameters(),
   "audioRtcpIce": audioRtcpIceTransport.iceListener.getLocalParameters(),
   "videoRtpIce": videoRtpIceTransport.iceListener.getLocalParameters(),
   "videoRtcpIce": videoRtcpIceTransport.iceListener.getLocalParameters(),
   // Offer the DTLS parameters
   "audioRtpDtls": audioRtpDtlsTransport.getLocalParameters(),
   "audioRtcpDtls": audioRtcpDtlsTransport.getLocalParameters(),
   "videoRtpDtls": videoRtpDtlsTransport.getLocalParameters(),
   "videoRtcpDtls": videoRtcpDtlsTransport.getLocalParameters(),
   // Offer the receiver and sender audio and video capabilities.
   "recvAudioCaps": recvAudioCaps,
   "recvVideoCaps": recvVideoCaps,
   "sendAudioCaps": sendAudioCaps,
   "sendVideoCaps": sendVideoCaps
  }, function(answer) {
   // The responder answers with its preferences, parameters and capabilities
   //
   // Derive the send and receive parameters, assuming that RTP/RTCP mux will be enabled.
   var audioSendParams = myCapsToSendParams(sendAudioCaps, answer.recvAudioCaps);
   var videoSendParams = myCapsToSendParams(sendVideoCaps, answer.recvVideoCaps);
   var audioRecvParams = myCapsToRecvParams(recvAudioCaps, answer.sendAudioCaps);
   var videoRecvParams = myCapsToRecvParams(recvVideoCaps, answer.sendVideoCaps);
   //
   // If the responder wishes to enable bundle, we will enable it
     if (answer.bundle) {
        // Only start the single ICE and DTLS transport that is needed.
        // No need for the ICE Transport Controller.
        audioRtpIceTransport.start(answer.audioRtpIce, RTCIceRole.controlling);
        audioRtpDtlsTransport.start(remote.audioRtpDtls);
        //
        // Replace the transport on the Sender and Receiver objects
        //
        audioSender.setTransport(audioRtpDtlsTransport);
        videoSender.setTransport(audioRtpDtlsTransport);
        audioReceiver.setTransport(audioRtpDtlsTransport);
        videoReceiver.setTransport(audioRtpDtlsTransport);
        // If BUNDLE was requested, then also assume RTP/RTCP mux
        answer.rtcpMux = true;
      } else {
        if (answer.rtcpMux){
         // The peer doesn't want BUNDLE, but it does want to multiplex RTP/RTCP
         // Create the ICE Transport Controller object
         var controller = new RTCIceTransportController();
         controller.addTransport(audioRtpIceTransport);
         controller.addTransport(videoRtpIceTransport);
         // Start the audio and video ICE transports, with an implicit gather policy of "all"
         audioRtpIceTransport.start(answer.audioRtpIce, RTCIceRole.controlling);
         videoRtpIceTransport.start(answer.videoRtpIce, RTCIceRole.controlling);
         // Start the audio and video DTLS transports
         audioRtpDtlsTransport.onerror = errorHandler;
         audioRtpDtlsTransport.start(answer.audioRtpDtls);
         videoRtpDtlsTransport.onerror = errorHandler;
         videoRtpDtlsTransport.start(answer.videoRtpDtls);
         // Replace the transport on the Sender and Receiver objects
         //
         audioSender.setTransport(audioRtpDtlsTransport);
         videoSender.setTransport(videoRtpDtlsTransport);
         audioReceiver.setTransport(audioRtpDtlsTransport);
         videoReceiver.setTransport(videoRtpDtlsTransport);
      };
      // Check if the responder does not want BUNDLE
      // and does not want RTP/RTCP multiplexing
      if (!answer.rtcpMux) {
         // Create the ICE Transport Controller object
         var controller = new RTCIceTransportController();
         controller.addTransport(audioRtpIceTransport);
         controller.addTransport(videoRtpIceTransport);
         // Prepare the ICE transports

         // Start the ICE transports, with an implicit gather policy of "all"
         audioRtpIceTransport.start(answer.audioRtpIce, RTCIceRole.controlling);
         audioRtcpIceTransport.start(answer.audioRtcpIce, RTCIceRole.controlling);
         videoRtpIceTransport.start(answer.videoRtpIce, RTCIceRole.controlling);
         videoRtcpIceTransport.start(answer.videoRtcpIce, RTCIceRole.controlling);
         // Start the DTLS transports that are needed
         audioRtpDtlsTransport.start(answer.audioRtpDtls);
         audioRtcpDtlsTransport.start(answer.audioRtcpDtls);
         videoRtpDtlsTransport.start(answer.videoRtpDtls);
         videoRtcpDtlsTransport.start(answer.videoRtcpDtls);
         // Disable RTP/RTCP multiplexing
         audioSendParams.rtcp.mux = false;
         videoSendParams.rtcp.mux = false;
         audioRecvParams.rtcp.mux = false;
         videoRecvParams.rtcp.mux = false;
       };
     //
     // Set the audio and video send and receive parameters.
     audioSender.send(audioSendParams);
     videoSender.send(videoSendParams);
     audioReceiver.receive(audioRecvParams);
     videoReceiver.receive(videoRecvParams);
  });
// Now we can render/play
// audioReceiver.track and videoReceiver.track.

// Helper functions
function log(text) {
  console.log('Time: ' + (performance.now() / 1000).toFixed(2) + ' ' + text);
}

function errorHandler (error) {
              log('Error encountered: ' + error.name);
}

function sendLocalCandidate(transport, candidate, kind){
   mySignaller.mySendLocalCandidate({
   "candidate": candidate,
   "kind": kind,
   "component": transport.component
   });
}

EXAMPLE 6


// Example to demonstrate forking when RTP and RTCP are not multiplexed.

var iceGatherOptions = new RTCIceGatherOptions();
iceGatherOptions.gatherPolicy = RTCIceGatherPolicy.relay;
iceGatherOptions.iceservers = ... ;
var iceRtpListener = new RTCIceListener(iceGatherOptions);
var iceBaseRtpTransport = new RTCIceTransport(iceRtpListener);
//create the RTCP ICE transport, which inherits the iceGatherOptions
var iceBaseRtcpTransport = iceBaseRtpTransport.createAssociatedTransport();

mySendInitiate(
{
   "icertp": iceBaseRtpTransport.iceListener.getLocalParameters(),
   "icertcp": iceBaseRtcpTransport.iceListener.getLocalParameters()
},
  function(response) {
  // We may get N responses
  var iceRtpTransport = new RTCIceTransport(iceRtpListener);
  // Create new ice RTCP transport based on the (implicitly created) iceListener
  var iceRtcpTransport = iceRtpTransport.createAssociatedTransport();

  // check to make sure the RTCRtpIceListener objects are set up as expected.
  assert(iceRtpTransport.iceListener == iceBaseRtpTransport.iceListener);
  assert(iceRtcpTransport.iceListener == iceBaseRtcpTransport.iceListener);
  // Create the ICE Transport Controller object
  var controller = new RTCIceTransportController();
  controller.addTransport(iceRtpTransport);
  controller.addTransport(iceRtcpTransport);
  // Start the ICE transports (using the "relay" gather policy inherited from the iceListener)
  iceRtpTransport.start(response.icertp, RTCIceRole.controlling);
  iceRtcpTransport.start(response.icertcp, RTCIceRole.controlling);
  // ... setup DTLS, RTP, SCTP, etc.
});

iceBaseRtpTransport.onlocalcandidate = mySendLocalRtpCandidate;
iceBaseRtcpTransport.onlocalcandidate = mySendLocalRtcpCandidate;

Received on Wednesday, 13 August 2014 23:00:43 UTC