This document defines a set of ECMAScript APIs in WebIDL to allow creation of an {{RTCIceController}} object to manage the ICE connection used by an {{RTCPeerConnection}}.

Introduction

This specification extends the WebRTC specification [[WEBRTC]] to allow construction of an {{RTCIceController}} object to manage the ICE connection used by an {{RTCPeerConnection}}.

This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.

Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)

Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [[WEBIDL]], as this specification uses that specification and terminology.

Terminology

The {{EventHandler}} interface, representing a callback used for event handlers, is defined in [[!HTML]].

The concepts [= queue a task =] and [= networking task source =] are defined in [[!HTML]].

The concept [= fire an event =] is defined in [[!DOM]].

The terms [= event =], [= event handlers =] and [= event handler event types =] are defined in [[!HTML]].

The term generation is defined in [[RFC8838]] Section 2.

When referring to exceptions, the terms [= exception/throw =] and [= exception/created =] are defined in [[!WEBIDL]].

The term "throw" is used as specified in [[!INFRA]]: it terminates the current processing steps.

The terms rejected and resolved used in the context of Promises are defined in [[!ECMASCRIPT-6.0]].

{{DOMHighResTimeStamp}}, {{Performance.timeOrigin}}, and {{Performance.now()}} are defined in [[!HIGHRES-TIME]].

A timestamp is expressed with {{DOMHighResTimeStamp}}, and is defined as {{Performance}}.{{Performance/timeOrigin}} + {{Performance}}.{{Performance/now()}} at any instant.

RTCIceController Interface

The {{RTCIceController}} interface allows an application to observe and affect certain actions that an ICE agent [[RFC5245]] undertakes, such as performing connectivity checks, nominating a candidate pair, and pruning candidate pairs when unviable or no longer necessary. It also allows the application to request the [= ICE agent =] to perform these actions for candidate pairs selected by the application.

Interface Definition

[Exposed=Window]
interface RTCIceController : EventTarget {
  constructor();

  readonly attribute RTCIceRole role;
  RTCIceCandidatePair? getSelectedCandidatePair();

  attribute EventHandler oncandidatepairadded;
  attribute EventHandler oncandidatepairupdated;
  attribute EventHandler oncandidatepairswitched;
  attribute EventHandler oncandidatepairdestroyed;

  attribute EventHandler onicepingproposed;
  attribute EventHandler oniceswitchproposed;
  attribute EventHandler onicepruneproposed;

  Promise<undefined> sendIcePing(RTCIceCandidatePair candidatePair);
  Promise<undefined> switchToCandidatePair(RTCIceCandidatePair candidatePair);
  Promise<undefined> pruneCandidatePairs(sequence<RTCIceCandidatePair> candidatePairs);
};

Constructor

constructor()

When the constructor is invoked, the [= user agent =] MUST run the following steps:

  1. Let |controller:RTCIceController| be a newly created {{RTCIceController}} object.
  2. Let |controller| have an [[\IceRole]] internal slot, initialized to {{RTCIceRole/"unknown"}}.
  3. Let |controller| have a [[\SelectedCandidatePair]] internal slot, initialized to null.
  4. Let |controller| have a [[\PeerConnection]] internal slot, initialized to null.
  5. Let |controller| have a [[\ProposalPending]] internal slot, initialized to false.
  6. Return |controller|.
No parameters.

Attributes

role of type {{RTCIceRole}}, readonly

The {{role}} attribute MUST, on getting, return the value of the {{RTCIceController/[[IceRole]]}} internal slot.

oncandidatepairadded of type {{EventHandler}}

The event type of this event handler is {{candidatepairadded}}.

When the [= ICE agent =] has found a candidate pair, the [= user agent =] MUST queue a task to [= fire an event =] named {{candidatepairadded}} using the {{RTCIceCandidatePairEvent}} interface with the {{RTCIceCandidatePairEvent/candidatePair}} attribute set to the found candidate pair.

oncandidatepairupdated of type {{EventHandler}}

The event type of this event handler is {{candidatepairupdated}}.

When the [= ICE agent =] completes a connectivity check on a candidate pair, or when a candidate pair undergoes a state change, the [= user agent =] MUST queue a task to [= fire an event =] named {{candidatepairupdated}} using the {{RTCIceCandidatePairUpdateEvent}} interface. This may have been a consequence of the application invoking {{RTCIceController/sendIcePing()}}.

The [= user agent =] MAY choose to batch together these events to reduce the volume and increase the efficiency of the updates.

oncandidatepairswitched of type {{EventHandler}}

The event type of this event handler is {{candidatepairswitched}}.

This event is fired when the [= ICE agent =] indicates that the selected candidate pair for the transport has changed, causing {{RTCIceTransport}} to [= update the selected candidate pair =]. The change may have been a consequence of the application invoking {{RTCIceController/switchToCandidatePair()}}.

oncandidatepairdestroyed of type {{EventHandler}}

The event type of this event handler is {{candidatepairdestroyed}}.

When the [= ICE agent =] destroys a candidate pair, the [= user agent =] MUST queue a task to [= fire an event =] named {{candidatepairdestroyed}} using the {{RTCIceCandidatePairEvent}} interface with the {{RTCIceCandidatePairEvent/candidatePair}} attribute set to the destroyed candidate pair. This may have been a consequence of the application invoking {{RTCIceController/pruneCandidatePairs()}}.

onicepingproposed of type {{EventHandler}}

The event type of this event handler is {{icepingproposed}}.

When the [= ICE agent =] has selected a candidate pair to ping for a connectivity check, but before the ping has actually been sent, the [= user agent =] MUST [= propose an ICE ping =].

This event handler MUST not be fired when the application has requested a connectivity check by invoking {{RTCIceController/sendIcePing()}}, which does not cause the [= ICE agent =] to select a candidate pair for a connectivity check.

oniceswitchproposed of type {{EventHandler}}

The event type of this event handler is {{iceswitchproposed}}.

When the [=ICE agent =] has selected a candidate pair to switch the transport to, but before the switch has actually occurred, the [= user agent =] MUST [= propose an ICE switch =].

This event handler MUST not be fired when the application has requested a candidate pair switch by invoking {{RTCIceController/switchToCandidatePair()}}, which does not cause the [= ICE agent =] to select a candidate pair to switch to.

onicepruneproposed of type {{EventHandler}}

The event type of this event handler is {{icepruneproposed}}.

When the [=ICE agent =] has selected one or more candidate pairs to prune, but before the pruning has actually occurred, the [= user agent =] MUST [= propose an ICE pruning =].

This event handler MUST not be fired when the application has requested to prune candidate pairs by invoking {{RTCIceController/pruneCandidatePairs()}}, which does not cause the [= ICE agent =] to select candidate pairs to prune.

Methods

getSelectedCandidatePair

The {{getSelectedCandidatePair}} method returns the candidate pair currently selected for the ICE transport. This method MUST return the value of the {{RTCIceController/[[SelectedCandidatePair]]}} slot. {{getSelectedCandidatePair}} returns null before at least one {{candidatepairswitched}} event is fired.

sendIcePing

The {{sendIcePing}} method attempts to send an ICE connectivity check on the provided candidate pair. When the {{sendIcePing}} method is invoked, the [= user agent =] MUST run the following steps:

  1. Let |candidatePair:RTCIceCandidatePair| be the method's first argument.

  2. Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{sendIcePing}} is invoked.

  3. If |controller|.{{RTCIceController/[[ProposalPending]]}} is true, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.

  4. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.

  5. Validate |candidatePair| by running the [= validate() algorithm =] to which |connection| and a new sequence containing |candidatePair| are supplied as arguments.

  6. Let |p:Promise| be a new promise.

  7. Run the following steps in parallel:

    1. If either the local or remote candidate pair identified by |candidatePair| is [= administratively prohibited =], [= resolve =] |p| with undefined.

      A candidate is administratively prohibited if the [= user agent =] has decided not to allow connection attempts to this address, as described further in {{RTCPeerConnection/addIceCandidate()}}.

    2. If the current {{RTCIceCandidatePairReport/state}} of the candidate pair {{RTCStatsIceCandidatePairState}} is {{RTCStatsIceCandidatePairState/"failed"}}, [= reject =] |p| with a newly [= exception/created =] {{InvalidStateError}}.

    3. If X connectivity checks have been sent in the previous Y duration of time, reject |p| with a newly [= exception/created =] {{QuotaExceededError}}.

      The exact nature of this rate-limiting check is TBD.

    4. Instruct the [= ICE agent=] to construct and send a STUN check from |candidatePair|.{{RTCIceCandidatePair/local}} to |candidatePair|.{{RTCIceCandidatePair/remote}}.

    5. [= Resolve =] |p| with undefined.

  8. Return |p|.

switchToCandidatePair

The {{switchToCandidatePair}} method attempts to switch the transport to the provided candidate pair. When the {{switchToCandidatePair}} method is invoked, the [= user agent =] MUST run the following steps:

  1. Let |candidatePair:RTCIceCandidatePair| be the method's first argument.

  2. Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{switchToCandidatePair}} is invoked.

  3. If |controller|.{{RTCIceController/[[ProposalPending]]}} is true, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.

  4. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.

  5. Validate |candidatePair| by running the [= validate() algorithm =] to which |connection| and a new sequence containing |candidatePair| are supplied as arguments.

  6. Let |p:Promise| be a new promise.

  7. Run the following steps in parallel:

    1. If the current {{RTCIceCandidatePairReport/state}} of the candidate pair {{RTCStatsIceCandidatePairState}} is not {{RTCStatsIceCandidatePairState/"succeeded"}}, reject |p| with a newly [= exception/created =] {{InvalidStateError}}.

      Attempting to switch to a candidate pair with either a local or remote candidate that is [= administratively prohibited =] will also lead to an abort at this stage, because connectivity checks are prohibited on such a candidate pair, so it will never transition to the {{RTCStatsIceCandidatePairState/"succeeded"}} state.

    2. Instruct the [= ICE agent =] to change the selected candidate pair to |candidatePair|.

    3. [= Resolve =] |p| with undefined.

  8. Return |p|.

pruneCandidatePairs

The {{pruneCandidatePairs}} method attempts to prune the provided candidate pairs. When the {{pruneCandidatePairs}} method is invoked, the [= user agent =] MUST run the following steps:

  1. Let |candidatePairs:sequence<RTCIceCandidatePair>| be the method's first argument.

  2. Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{pruneCandidatePairs}} is invoked.

  3. If |controller|.{{RTCIceController/[[ProposalPending]]}} is true, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.

  4. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.

  5. Validate |candidatePairs| by running the [= validate() algorithm =] to which |connection| and |candidatePairs| are supplied as arguments.

  6. Let |p:Promise| be a new promise.

  7. Run the following steps in parallel:

    1. Instruct the [= ICE agent =] to prune the candidate pairs in |candidatePairs|.

    2. [= Resolve =] |p| with undefined.

  8. Return |p|.

The [= user agent =] MUST validate that the application is allowed to perform a requested operation by running the following steps:

  1. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object and |candidatePairs:sequence<RTCIceCandidatePair>| be the sequence of candidate pairs supplied to the algorithm.

  2. If |connection| is null, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}} and abort these steps.

  3. If |connection|.{{RTCPeerConnection/[[IsClosed]]}} is true, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.

  4. Let |transport:RTCIceTransport| be the sole {{RTCIceTransport}} object in use by |connection|.

    The presence of one and only one {{RTCIceTransport}} object is ensured by limiting the use of {{RTCIceController}} to cases where {{RTCBundlePolicy}} is set to {{RTCBundlePolicy/"max-bundle"}}.

  5. If |transport|.{{RTCIceTransport/[[IceTransportState]]}} is either of {{RTCIceTransportState/"new"}}, {{RTCIceTransportState/"failed"}} or {{RTCIceTransportState/"closed"}}, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.

  6. If |candidatePair| does not identify a valid candidate pair sent in {{RTCIceController/oncandidatepairadded}}, return a [= rejected =] promise with a newly [= exception/created =] {{NotFoundError}}.

    A valid candidate pair will be composed of a local candidate gathered by |transport|, and a remote candidate received by |transport| via {{RTCPeerConnection/addIceCandidate()}} or a peer reflexive candidate.

Informational events

These events convey information about some action that has already taken place, and allow the application to have an up-to-date view of the relevant internal state of the [= ICE agent =].

RTCIceCandidatePairEvent

The {{RTCIceController/candidatepairadded}}, {{RTCIceController/candidatepairswitched}}, and {{RTCIceController/candidatepairdestroyed}} events use the {{RTCIceCandidatePairEvent}} interface.

[Exposed=Window]
interface RTCIceCandidatePairEvent : Event {
  constructor(DOMString type, RTCIceCandidatePairEventInit eventInitDict);
  readonly attribute RTCIceCandidatePair candidatePair;
};

Constructors

RTCIceCandidatePairEvent.constructor()

Attributes

candidatePair of type {{RTCIceCandidatePair}}, readonly

The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object with the new ICE candidate pair that was gathered by the [= ICE agent =].

dictionary RTCIceCandidatePairEventInit : EventInit {
  required RTCIceCandidatePair candidatePair;
};

Dictionary RTCIceCandidatePairEventInit Members

candidatePair of type {{RTCIceCandidatePair}}, required

See the {{RTCIceCandidatePairEvent/candidatePair}} attribute of the {{RTCIceCandidatePairEvent}} interface.

RTCIceCandidatePairUpdateEvent

The {{RTCIceController/candidatepairupdated}} event uses the {{RTCIceCandidatePairUpdateEvent}} interface.

[Exposed=Window]
interface RTCIceCandidatePairUpdateEvent : Event {
  constructor(DOMString type, RTCIceCandidatePairUpdateEventInit eventInitDict);
  readonly attribute RTCIceCandidatePair candidatePair;
  readonly attribute RTCIceCandidatePairReport report;
};

Constructors

RTCIceCandidatePairUpdateEvent.constructor()

Attributes

candidatePair of type {{RTCIceCandidatePair}}, readonly

The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object that represents the candidate pair for which updated information is available.

report of type {{RTCIceCandidatePairReport}}, readonly

The {{report}} attribute is the {{RTCIceCandidatePairReport}} object containing the information that the [= ICE agent =] has gathered about a candidate pair through ICE connectivity checks.

dictionary RTCIceCandidatePairUpdateEventInit : EventInit {
  required RTCIceCandidatePair candidatePair;
  required RTCIceCandidatePairReport report;
};

Dictionary RTCIceCandidatePairUpdateEventInit Members

candidatePair of type {{RTCIceCandidatePair}}, required

See the {{RTCIceCandidatePairUpdateEvent/candidatePair}} attribute of the {{RTCIceCandidatePairUpdateEvent}} interface.

report of type {{RTCIceCandidatePairReport}}, required

See the {{RTCIceCandidatePairUpdateEvent/report}} attribute of the {{RTCIceCandidatePairUpdateEvent}} interface.

RTCIceCandidatePairReport Interface

[Exposed=Window]
interface RTCIceCandidatePairReport {
  constructor(RTCIceCandidatePairReportInit reportInitDict);
  readonly attribute boolean connected;
  readonly attribute RTCStatsIceCandidatePairState state;
  readonly attribute RTCIceCandidatePairWriteState writeState;
  readonly attribute DOMHighResTimeStamp? lastPingSentTimestamp;
  readonly attribute DOMHighResTimeStamp? lastPingResponseReceivedTimestamp;
  readonly attribute DOMHighResTimeStamp? lastPingReceivedTimestamp;
  readonly attribute DOMHighResTimeStamp? lastPacketReceivedTimestamp;
  readonly attribute unsigned long long pingsSent;
  sequence<RTCRoundTripTimeSample> getRoundTripTimeSamples();
};
Constructors
RTCIceCandidatePairReport.constructor()
Attributes
connected of type boolean, readonly

The {{connected}} attribute represents whether the ICE candidate pair is in connected state.

state of type RTCStatsIceCandidatePairState, readonly

The {{state}} attribute represents the state of a candidate pair in an ICE check list defined in Section 6.1.2.6 of [[RFC8445]].

writeState of type RTCIceCandidatePairWriteState, readonly

The {{writeState}} attribute indicates the write state of the candidate pair.

lastPingSentTimestamp of type DOMHighResTimeStamp, readonly

The {{lastPingSentTimestamp}} attribute, if present, represents the [= timestamp =] at which the last ping was sent for an ICE connectivity check on this candidate pair.

lastPingResponseReceivedTimestamp of type DOMHighResTimeStamp, readonly

The {{lastPingResponseReceivedTimestamp}} attribute, if present, represents the [= timestamp =] at which the last response was received to a ping for an ICE connectivity check on this candidate pair.

lastPingReceivedTimestamp of type DOMHighResTimeStamp, readonly

The {{lastPingReceivedTimestamp}} attribute, if present, represents the [= timestamp =] at which the last ping was received from the peer for an ICE connectivity check on this candidate pair.

lastPacketReceivedTimestamp of type DOMHighResTimeStamp, readonly

The {{lastPacketReceivedTimestamp}} attribute, if present, represents the [= timestamp =] at which the last packet was received on this candidate pair, excluding STUN packets.

pingsSent of type unsigned long long, readonly

The {{pingsSent}} attribute represents the total number of pings sent for ICE connectivity check on this candidate pair.

Methods
getRoundTripTimeSamples

The {{getRoundTripTimeSamples}} method returns all the round trip time samples gathered for this candidate pair.

dictionary RTCIceCandidatePairReportInit {
  required boolean connected;
  required RTCStatsIceCandidatePairState state;
  required RTCIceCandidatePairWriteState writeState;
  DOMHighResTimeStamp lastPingSentTimestamp;
  DOMHighResTimeStamp lastPingResponseReceivedTimestamp;
  DOMHighResTimeStamp lastPingReceivedTimestamp;
  DOMHighResTimeStamp lastPacketReceivedTimestamp;
  required unsigned long long pingsSent;
  sequence<RTCRoundTripTimeSample> roundTripTimeSamples;
};
Dictionary RTCIceCandidatePairReportInit Members
connected of type boolean, required

The {{connected}} member represents whether the ICE candidate pair is in connected state.

state of type RTCStatsIceCandidatePairState, required

The {{state}} member represents the state of a candidate pair in an ICE check list defined in Section 6.1.2.6 of [[RFC8445]].

writeState of type RTCIceCandidatePairWriteState, required

The {{writeState}} member indicates the write state of the candidate pair.

lastPingSentTimestamp of type DOMHighResTimeStamp

The {{lastPingSentTimestamp}} member, if present, represents the [= timestamp =] at which the last ping was sent for an ICE connectivity check on this candidate pair.

lastPingResponseReceivedTimestamp of type DOMHighResTimeStamp

The {{lastPingResponseReceivedTimestamp}} member, if present, represents the [= timestamp =] at which the last response was received to a ping for an ICE connectivity check on this candidate pair.

lastPingReceivedTimestamp of type DOMHighResTimeStamp

The {{lastPingReceivedTimestamp}} member, if present, represents the [= timestamp =] at which the last ping was received from the peer for an ICE connectivity check on this candidate pair.

lastPacketReceivedTimestamp of type DOMHighResTimeStamp

The {{lastPacketReceivedTimestamp}} member, if present, represents the [= timestamp =] at which the last packet was received on this candidate pair, excluding STUN packets.

pingsSent of type unsigned long long, required

The {{pingsSent}} member represents the total number of pings sent for ICE connectivity check on this candidate pair.

roundTripTimeSamples of type sequence<{{RTCRoundTripTimeSample}}>

The {{roundTripTimeSamples}} member, if present, represents all the round trip time samples gathered for this candidate pair.

RTCRoundTripTimeSample Dictionary

dictionary RTCRoundTripTimeSample {
  required DOMHighResTimeStamp timestamp;
  required double value;
};
Dictionary {{RTCRoundTripTimeSample}} Members
timestamp of type DOMHighResTimeStamp, required

The {{RTCRoundTripTimeSample/timestamp}} member represents the time at which this round trip time sample was measured.

value of type double, required

The {{value}} member indicates the measured round trip time in this sample.

RTCIceCandidatePairWriteState Enum

The {{RTCIceCandidatePairWriteState}} represents the write state of the ICE candidate pair as determined by the [= ICE agent =]. The exact criteria for determining the write state, i.e. the number of failures or the timeout period, is left up to the browser implementation.

enum RTCIceCandidatePairWriteState {
  "init",
  "writeable",
  "unreliable",
  "timeout"
};
{{RTCIceCandidatePairWriteState}} Enumeration description
Enum value Description
init Responses have not yet been received to connectivity check on this candidate pair, but the timeout has not elapsed.
writeable Responses have been received recently to connectivity checks, and the candidate pair is considered writeable.
unreliable Some connectivity check have failed recently for this candidate pair, but it may still be writeable.
timeout A large number of connectivity checks have failed and the candidate pair is not considered writeable any longer.

Proposal events

These events represent some action that the [= ICE agent =] is proposing to take with regards to ICE connectivity checks, changing the candidate pair used for the transport, or maintainence of the candidate pairs. The application can affect the operation of the [= ICE agent =] by rejecting any of the proposed actions, and initiating its own actions instead through {{RTCIceController}} methods.

RTCIcePingProposalEvent

The {{RTCIceController/icepingproposed}} event uses the {{RTCIcePingProposalEvent}} interface.

When the [= ICE agent =] has selected a candidate pair to ping for connectivity check, the [= user agent =] MUST [= queue a task =] to propose an ICE ping:

  1. Let |candidatePair:RTCIceCandidatePair| be the candidate pair for which a ping is being proposed.

  2. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].

  3. If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.

  4. Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.

  5. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true.

  6. Let |accepted:boolean| be the result of [= fire an event | firing an event =] named {{RTCIceController/icepingproposed}} at |controller|, using {{RTCIcePingProposalEvent}}, with the {{Event/cancelable}} attribute initialized to true, the {{RTCIcePingProposalEventInit/candidatePair}} attribute initialized to |candidatePair|, and the {{RTCIcePingProposalEventInit/lastPingSentTimestamp}} and {{RTCIcePingProposalEventInit/earliestNextPingTimestamp}} attributes initialized to the relevant values for the [= ICE agent =].

  7. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false.

  8. If |accepted| is false, abort these steps.

  9. Otherwise, instruct the [= ICE agent =] to construct and send a STUN check from |candidatePair|.{{RTCIceCandidatePair/local}} to |candidatePair|.{{RTCIceCandidatePair/remote}}.

The [= user agent =] MAY continue to propose further connectivity checks in the future for the same or other candidate pairs.

[Exposed=Window]
interface RTCIcePingProposalEvent : Event {
  constructor(DOMString type, RTCIcePingProposalEventInit eventInitDict);
  readonly attribute RTCIceCandidatePair candidatePair;
  readonly attribute DOMHighResTimeStamp? lastPingSentTimestamp;
  readonly attribute DOMHighResTimeStamp? earliestNextPingTimestamp;
};

Constructors

RTCIcePingProposalEvent.constructor()

Attributes

candidatePair of type {{RTCIceCandidatePair}}, readonly

The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object that represents the candidate pair that the [= ICE agent =] has selected to ping for a connectivity check.

lastPingSentTimestamp of type DOMHighResTimeStamp, readonly, nullable

The {{lastPingSentTimestamp}} attribute represents the [= timestamp =] at which the last connectivity check ping was sent by the [= ICE agent =].

earliestNextPingTimestamp of type DOMHighResTimeStamp, readonly, nullable

The {{earliestNextPingTimestamp}} attribute represents the earliest [= timestamp =] at which the [= ICE agent =] will select another candidate pair to ping for connectivity check.

dictionary RTCIcePingProposalEventInit : EventInit {
  required RTCIceCandidatePair candidatePair;
  DOMHighResTimeStamp lastPingSentTimestamp;
  DOMHighResTimeStamp earliestNextPingTimestamp;
};

Dictionary RTCIcePingProposalEventInit Members

candidatePair of type {{RTCIceCandidatePair}}, required

See the {{RTCIcePingProposalEvent/candidatePair}} attribute of the {{RTCIcePingProposalEvent}} interface.

lastPingSentTimestamp of type DOMHighResTimeStamp

See the {{RTCIcePingProposalEvent/lastPingSentTimestamp}} attribute of the {{RTCIcePingProposalEvent}} interface.

earliestNextPingTimestamp of type DOMHighResTimeStamp

See the {{RTCIcePingProposalEvent/earliestNextPingTimestamp}} attribute of the {{RTCIcePingProposalEvent}} interface.

RTCIceSwitchProposalEvent

The {{RTCIceController/iceswitchproposed}} event uses the {{RTCIceSwitchProposalEvent}} interface.

When the [= ICE agent =] has selected a candidate pair to switch the transport to, the [= user agent =] MUST [= queue a task =] to propose an ICE switch:

  1. Let |candidatePair:RTCIceCandidatePair| be the candidate pair to which a switch is being proposed.

  2. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].

  3. If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.

  4. Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.

  5. Let |canCancel:boolean| be false if the [= ICE agent =]'s reason for the switch is one of {{RTCIceSwitchReason/"data-received"}}, {{RTCIceSwitchReason/"nomination-on-controlled-side"}}, or {{RTCIceSwitchReason/"selected-connection-destroyed"}}, indicating that such a switch may not be prevented by the application, and true otherwise.

  6. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true.

  7. Let |accepted:boolean| be the result of [= fire an event | firing an event =] named {{RTCIceController/iceswitchproposed}} at |controller|, using {{RTCIceSwitchProposalEvent}}, with the {{Event/cancelable}} attribute initialized to |canCancel|, the {{RTCIceSwitchProposalEventInit/candidatePair}} attribute initialized to |candidatePair|, and the {{RTCIceSwitchProposalEventInit/reason}} and {{RTCIceSwitchProposalEventInit/recheck}} attributes initialized to the relevant values from the [= ICE agent =].

  8. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false.

  9. If |accepted| is false, abort these steps.

  10. Otherwise, instruct the [= ICE agent =] to change the selected candidate pair to |candidatePair|.

The [= user agent =] MAY continue to propose further switches in the future to the same or other candidate pairs.

[Exposed=Window]
interface RTCIceSwitchProposalEvent : Event {
  constructor(DOMString type, RTCIceSwitchProposalEventInit eventInitDict);
  readonly attribute RTCIceCandidatePair candidatePair;
  readonly attribute RTCIceSwitchReason reason;
  readonly attribute RTCIceRecheck? recheck;
};

Constructors

RTCIceSwitchProposalEvent.constructor()

Attributes

candidatePair of type {{RTCIceCandidatePair}}, readonly

The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object that represents the candidate pair that the [= ICE agent =] has selected to switch to using for the transport.

reason of type {{RTCIceSwitchReason}}, readonly

The {{reason}} attribute represents the reason that triggered the [= ICE agent =] to search for a different candidate pair to switch the transport to.

recheck of type {{RTCIceRecheck}}, readonly, nullable

The {{recheck}} attribute represent a future trigger for the [= ICE agent =] to search for a different candidate pair to switch the transport to.

dictionary RTCIceSwitchProposalEventInit : EventInit {
  required RTCIceCandidatePair candidatePair;
  required RTCIceSwitchReason reason;
  RTCIceRecheck recheck;
};

Dictionary RTCIceSwitchProposalEventInit Members

candidatePair of type {{RTCIceCandidatePair}}, required

See the {{RTCIceSwitchProposalEvent/candidatePair}} attribute of the {{RTCIceSwitchProposalEvent}} interface.

reason of type {{RTCIceSwitchReason}}, required

See the {{RTCIceSwitchProposalEvent/reason}} attribute of the {{RTCIceSwitchProposalEvent}} interface.

recheck of type {{RTCIceRecheck}}

See the {{RTCIceSwitchProposalEvent/recheck}} attribute of the {{RTCIceSwitchProposalEvent}} interface.

RTCIceSwitchReason Enum

enum RTCIceSwitchReason {
  "remote-candidate-generation-change",
  "network-preference-change",
  "new-connection-from-local-candidate",
  "new-connection-from-remote-candidate",
  "new-connection-from-unknown-remote-address",
  "nomination-on-controlled-side",
  "data-received",
  "connect-state-change",
  "selected-connection-destroyed",
  "scheduled-recheck"
};
{{RTCIceSwitchReason}} Enumeration description
Enum value Description
remote-candidate-generation-change The {{generation}} of the remote ICE candidate possibly changed.
network-preference-change The preferred network type changed for the [= user agent =] .
new-connection-from-local-candidate A candidate pair was created from a new local ICE candidate.
new-connection-from-remote-candidate A candidate pair was created from a new remote ICE candidate.
new-connection-from-unknown-remote-address A new candidate pair was created from an unknown remote address.
nomination-on-controlled-side A nomination for a candidate pair was received on the ICE {{RTCIceRole/"controlled"}} peer.
data-received The [= user agent =] received data on a candidate pair.
connect-state-change A candidate pair underwent a state change.
selected-connection-destroyed The selected candidate pair was destroyed.
scheduled-recheck A periodic check for a different candidate pair was triggered.

RTCIceRecheck Dictionary

dictionary RTCIceRecheck {
  required RTCIceSwitchReason reason;
  required DOMHighResTimeStamp earliestRecheckTimestamp;
};
Dictionary {{RTCIceRecheck}} Members
reason of type RTCIceSwitchReason, required

The {{reason}} member represents the reason for the future trigger to search for a different candidate pair for the transport.

earliestRecheckTimestamp of type DOMHighResTimeStamp, required

The {{earliestRecheckTimestamp}} member indicates the earliest [= timestamp =] at which the future check will be performed.

RTCIcePruneProposalEvent

The {{RTCIceController/icepruneproposed}} event uses the {{RTCIcePruneProposalEvent}} interface.

When the [= ICE agent =] has selected some candidate pairs to prune, the [= user agent =] MUST [= queue a task =] to propose an ICE pruning:

  1. Let |candidatePairs:sequence<RTCIceCandidatePair>| be the candidate pairs which are being proposed for pruning.

  2. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].

  3. If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.

  4. Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.

  5. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true.

  6. Let |accepted:boolean| be the result of [= fire an event | firing an event =] named {{RTCIceController/icepruneproposed}} at |controller|, using {{RTCIcePruneProposalEvent}}, with the {{Event/cancelable}} attribute initialized to true, the {{RTCIcePruneProposalEventInit/candidatePairs}} attribute initialized to |candidatePairs|.

  7. Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false.

  8. If |accepted| is false, abort these steps.

  9. Otherwise, instruct the [= ICE agent =] to prune the candidate pairs in |candidatePairs|.

The [= user agent =] MAY continue to propose pruning of the same or other candidate pairs in the future.

While the application cannot respond to pruning of individual candidate pairs in a prune proposal, the application can subsequently invoke {{RTCIceController/pruneCandidatePairs()}} with any subset of candidate pairs in the proposal.

[Exposed=Window]
interface RTCIcePruneProposalEvent : Event {
  constructor(DOMString type, RTCIcePruneProposalEventInit eventInitDict);
  sequence<RTCIceCandidatePair> getCandidatePairs();
};

Constructors

RTCIcePruneProposalEvent.constructor()

Methods

getCandidatePairs

The {{getCandidatePairs}} method returns the candidate pairs selected by the [= ICE agent =] for pruning at this time.

dictionary RTCIcePruneProposalEventInit : EventInit {
  required sequence<RTCIceCandidatePair> candidatePairs;
};
Dictionary RTCIcePruneProposalEventInit Members
candidatePairs of type sequence<{{RTCIceCandidatePair}}>, required

The {{candidatePairs}} member represents the candidate pairs selected by the [= ICE agent =] for pruning at this time.

{{RTCPeerConnection}} Extensions

The {{RTCPeerConnection}} extension allows associating an {{RTCIceController}} with an {{RTCPeerConnection}} object at the time of its construction or through {{RTCPeerConnection/setConfiguration()}}. An application can test for support of this specification by checking for the presence of an {{RTCIceController}} prototype object.

{{RTCConfiguration}} Extensions

The {{RTCConfiguration}} dictionary is extended with an {{RTCConfiguration/iceController}} member.

partial dictionary RTCConfiguration {
  RTCIceController iceController;
};

Dictionary {{RTCConfiguration}} Members

iceController of type {{RTCIceController}}

The {{iceController}} member represents the {{RTCIceController}} object to associate with the {{RTCPeerConnection}}. Proposals from its [= ICE agent =] will result in events firing on {{iceController}} while it is associated with the {{RTCPeerConnection}}.

Operation

Certain additional steps need to be performed during the construction of an {{RTCPeerConnection}} and when {{RTCPeerConnection/setConfiguration()}} is invoked to establish its association with an {{RTCIceController}}, if supplied. Both operations achieve this through an extension to [= Set the configuration =] operation.

Constructor

When the RTCPeerConnection.constructor() is invoked, run the following additional steps:

  1. Let |connection:RTCPeerConnection| be the newly created {{RTCPeerConnection}} object.

  2. Let |connection| have an [[\IceController]] internal slot, initialized to null.

Set the configuration

To set a configuration with |configuration:RTCConfiguration|, run the following additional steps:

  1. Let |connection:RTCPeerConnection| be the target {{RTCPeerConnection}} object.

  2. Let |oldConfig:RTCConfiguration| be |connection|.[[\Configuration]].

  3. If |oldConfig| is not null, and if the value of |configuration|.{{RTCConfiguration/iceController}} differs from |oldConfig|.{{RTCConfiguration/iceController}}, [= exception/throw =] an {{InvalidModificationError}}.

  4. Otherwise, if |configuration|.{{RTCConfiguration/iceController}} is not null, run the following steps:

    1. If |configuration|.{{RTCConfiguration/iceController}}.{{RTCIceController/[[PeerConnection]]}} is not null, [= exception/throw =] an {{InvalidModificationError}}.

    2. If the value of |configuration|.{{RTCConfiguration/bundlePolicy}} is not {{RTCBundlePolicy/"max-bundle"}}, [= exception/throw =] a {{NotSupportedError}}.

      The use of an {{RTCIceController}} is currently only supported when {{RTCBundlePolicy}} is set to {{RTCBundlePolicy/"max-bundle"}}, and thus only one media track is negotiated and a single {{RTCIceTransport}} is used.

      A future extension to the specification is possible either by allowing to associate an {{RTCIceController}} with each individual {{RTCIceTransport}} or by multiplexing of several {{RTCIceTransport}} events and actions to a single {{RTCIceController}}.

    3. If the value of |configuration|.{{RTCConfiguration/iceCandidatePoolSize}} is not 0, [= exception/throw =] a {{NotSupportedError}}.

      For simplicity, the use of an {{RTCIceController}} is currently only supported when the application does not use a prefetched ICE pool as defined in [[RFC8829]] (section 3.5.4 and section 4.1.1).

    4. Store |configuration|.{{RTCConfiguration/iceController}} in the {{RTCPeerConnection/[[IceController]]}} internal slot.

    5. Store |connection| in {{RTCIceController/[[PeerConnection]]}} of |configuration|.{{RTCConfiguration/iceController}}.

Set the session description

When either {{RTCPeerConnection/setLocalDescription()}} or {{RTCPeerConnection/setRemoteDescription()}} is invoked, and a session description is set as a result, run the following additional steps:

  1. Let |connection:RTCPeerConnection| be the target {{RTCPeerConnection}} object.

  2. If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.

  3. Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.

  4. When setting the {{RTCIceTransport/[[IceRole]]}} of the relevant {{RTCIceTransport}} object according to the rules of [[RFC8445]], also set |controller|.{{RTCIceController/[[IceRole]]}} to the same value.

{{RTCIceTransport}} Extensions

The {{RTCIceTransport}} extensions allow accessing a small subset of the attributes of an {{RTCIceTransport}} object through an {{RTCIceController}} object to make it easy for an application to refer to this information.

The {{RTCIceTransport}} extensions do not change the interface of {{RTCIceTransport}} or its associated types. Rather, they specify certain additional steps to be performed during the course of {{RTCIceTransport}} operations.

When the [= ICE agent =] signals that the ICE role for an {{RTCIceTransport}} has changed due to an ICE binding request with a role collision per [[RFC8445]] section 7.3.1.1, the [= user agent =] will [= queue a task =] on the [= networking task source =] to set the value of {{RTCIceTransport/[[IceRole]]}} for the {{RTCIceTransport}} to the new value. The task will also set the value of {{RTCIceController/[[IceRole]]}} for the {{RTCPeerConnection/[[IceController]]}} of the {{RTCPeerConnection}} object associated with this [= ICE agent =] to the new value.

When the [= ICE agent =] indicates that an {{RTCIceTransport}} has changed the selected candidate pair, run these additional steps to update the selected candidate pair:

  1. Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].

  2. If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.

  3. Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.

  4. Let |newCandidatePair:RTCIceCandidatePair| be a newly created {{RTCIceCandidatePair}} representing the indicated pair if one is selected, and null otherwise.

  5. Set |controller|.{{RTCIceController/[[SelectedCandidatePair]]}} to |newCandidatePair|.

  6. [= Fire an event =] named {{RTCIceController/candidatepairswitched}} using the {{RTCIceCandidatePairEvent}} interface with the {{RTCIceCandidatePairEvent/candidatePair}} attribute set to |newCandidatePair| at |controller|.

Examples

Simple example

A simple application can delegate most ICE operations to the default implementation of the user agent, and only interject when needed. In the following example, the application keeps additional candidate pairs around by preventing the user agent from pruning them. One of the alternate candidate pairs can then be used without needing an ICE restart if the active candidate pair is lost.

const iceController = new RTCIceController();

// Observe proposals to prune candidate pairs and prevent pruning of
// relay candidates and those using a different transport protocol than
// the active candidate pair.
iceController.addEventListener('icepruneproposed', event => {
  const activeProtocol = iceController.getSelectedCandidatePair()?.local.protocol;

  // Relay and candidate pairs dissimilar to the active pair should not be pruned.
  const allowedToPrune = event.getCandidatePairs().filter(
    pair => pair.local.type !== 'relay' && pair.local.protocol === activeProtocol);

  // If any candidate pair proposed for pruning is disallowed, reject the proposal.
  if (allowedToPrune.length < event.getCandidatePairs().length) {
    event.preventDefault();

    // Request pruning of the allowed candidate pairs after the handling of this
    // proposal is finished.
    setTimeout(() => iceController.pruneCandidatePairs(allowedToPrune));
  }
});

// Set the ICE controller in RTCConfiguration.
const pc = new RTCPeerConnection({ iceController: iceController });

Advanced example with substantial ICE takeover

An advanced application can decide to take over ICE to any extent necessary by rejecting the user agent's proposals and injecting its own logic for candidate pair checking and selection. The application in the following example adjusts how frequently it sends connectivity checks and selects a candidate pair to use for the transport based on the quality of the currently active pair.

const iceController = new RTCIceController();

// Application implements these methods for candidate pair selection.
function selectBestPairToPing() { /* TODO... */ }
function selectBestPairToUse() { /* TODO... */ }
function selectPairsToPrune() { /* TODO... */ }

// Application implements these methods to determine if a candidate pair
// ping or switch should be allowed to happen.
function shouldPingPair(pair) { /* TODO... */ }
function shouldSwitchToPair(pair) { /* TODO... */ }

// Initial intervals for sending periodic connectivity checks and picking
// the most suitable candidate pair for transport.
let pingRecheckIntervalMs = 2.5 * 1000;
let switchRecheckIntervalMs = 5 * 1000;

// Observe events that indicate when the browser is about to perform an ICE
// action. Cancel the action by calling preventDefault if the application has
// other plans.
iceController.addEventListener('icepingproposed', event => {
  if (!shouldPingPair(event.candidatePair)) {
    event.preventDefault();
  }
});
iceController.addEventListener('iceswitchproposed', event => {
  if (event.cancelable &&
      !shouldSwitchToPair(event.reason, event.candidatePair)) {
    event.preventDefault();
  }
});

// If the listener will not call preventDefault, it can be marked passive to
// allow optimizations.
iceController.addEventListener('icepruneproposed', event => {
  console.log(JSON.stringify(event.getCandidatePairs()));
}, {passive: true});

// Check connectivity of various candidate pairs.
function doPing() {
  iceController.sendIcePing(selectBestPairToPing());
  setTimeout(() => doPing(), pingRecheckIntervalMs);
}
iceController.addEventListener('candidatepairadded', () => {
  doPing();
}, {once: true});

// Select the most suitable candidate pair and use that for transport. Prune
// any unneeded candidate pairs.
function doSelectAndPrune() {
  iceController.switchToCandidatePair(selectBestPairToUse());
  iceController.pruneCandidatePairs(selectPairsToPrune());
  setTimeout(() => doSelectAndPrune(), switchRecheckIntervalMs);
}
iceController.addEventListener('candidatepairupdated', () => {
  doSelectAndPrune();
}, {once: true});

// Clamp a number between lower and upper bounds.
function clamp(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

// Adjust the recheck intervals based on RTT measurements.
function adjustRecheckIntervals(report) {
  const rtts = report.getRoundTripTimeSamples().map(s => s.value);
  const rtt_p75 = quantile(rtts, 0.75);

  const MIN_PING_RECHECK_INTERVAL_MS = 500;
  const MAX_PING_RECHECK_INTERVAL_MS = 10 * 1000;
  const MIN_SWITCH_RECHECK_INTERVAL_MS = 1000;
  const MAX_SWITCH_RECHECK_INTERVAL_MS = 30 * 1000;

  const LOW_RTT_THRESHOLD_MS = 250;
  const HIGH_RTT_THRESHOLD_MS = 500;

  if (rtt_p75 < LOW_RTT_THRESHOLD_MS) {
    // Selected candidate pair has good RTT, recheck less frequently.
    pingRecheckIntervalMs *= 2;
    switchRecheckIntervalMs *= 2;
  }
  else if (rtt_p75 > HIGH_RTT_THRESHOLD_MS) {
    // Selected candidate pair has poor RTT, recheck more frequently.
    pingRecheckIntervalMs = Math.floor(pingRecheckIntervalMs / 2);
    switchRecheckIntervalMs = Math.floor(switchRecheckIntervalMs / 2);
  }

  pingRecheckIntervalMs = clamp(pingRecheckIntervalMs,
    MIN_PING_RECHECK_INTERVAL_MS, MAX_PING_RECHECK_INTERVAL_MS);
  switchRecheckIntervalMs = clamp(switchRecheckIntervalMs,
    MIN_SWITCH_RECHECK_INTERVAL_MS, MAX_SWITCH_RECHECK_INTERVAL_MS);
}

// If the selected candidate pair is worsening, increase ping and select
// intervals more frequently. Otherwise decrease the recheck frequency.
iceController.addEventListener('candidatepairupdated', event => {
  if (event.candidatePair == iceController.getSelectedCandidatePair()) {
    adjustRecheckIntervals(event.report);
  }
});

// Set the ICE controller in RTCConfiguration.
const pc = new RTCPeerConnection({ iceController: iceController });

Event summary

The following events fire on {{RTCIceController}} objects:

Event name Interface Fired when...
candidatepairadded {{RTCIceCandidatePairEvent}} The [= ICE agent =] has gathered a candidate pair and is making it available to the script.
candidatepairupdated {{RTCIceCandidatePairUpdateEvent}} The [= ICE agent =] has updated the information it knows about a candidate pair (eg. the round trip time estimate).
candidatepairswitched {{RTCIceCandidatePairEvent}} The [= ICE agent =] has switched the transport to a different candidate pair.
candidatepairdestroyed {{RTCIceCandidatePairEvent}} The [= ICE agent =] has destroyed a candidate pair.
icepingproposed {{RTCIcePingProposalEvent}} The [= ICE agent =] has selected a candidate pair to ping for a connectivity check, but the ping has not yet been sent.
iceswitchproposed {{RTCIceSwitchProposalEvent}} The [= ICE agent =] has selected a candidate pair to switch the transport to, but the switch has not yet occurred.
icepruneproposed {{RTCIcePruneProposalEvent}} The [= ICE agent =] has selected one or more candidate pairs to prune, but the pruning has not yet occurred.

Privacy and Security Considerations

This section is non-normative; it specifies no new behaviour, but instead summarizes information already present in other parts of the specification. The overall security considerations of the general set of APIs and protocols used in WebRTC are described in [[?RFC8827]].

The data exposed by RTCIceController comprises mostly of data also exposed by [[WEBRTC]] and [[WEBRTC-STATS]] - as such, all the privacy and security considerations of these specifications related to data exposure apply as well to this specifciation.

In addition, {{RTCIceCandidatePairReport/getRoundTripTimeSamples()}} exposes round-trip times over a period of time, which can give some coarse indication of the distance between peers.

The {{RTCIceController/sendIcePing()}} operation allows the application to send STUN connectivity checks to an arbitrary recipient. The risk of using STUN checks to flood the local network is mitigated by the user agent's use of rate limiting.

Acknowledgements

The editors wish to thank the Working Group chairs for their support. The editors would like to thank Harald Alvestrand, Henrik Boström, Jonas Oreland, and Erik Övelius for their contributions to this specification.