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}}.
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.
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.
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.
[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); };
When the constructor is invoked, the [= user agent =] MUST run the following steps:
null
.
null
.
false
.
The {{role}} attribute MUST, on getting, return the value of the {{RTCIceController/[[IceRole]]}} internal slot.
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.
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.
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()}}.
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()}}.
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.
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.
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.
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.
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:
Let |candidatePair:RTCIceCandidatePair| be the method's first argument.
Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{sendIcePing}} is invoked.
If |controller|.{{RTCIceController/[[ProposalPending]]}} is true
, return a [= rejected =]
promise with a newly [= exception/created =] {{InvalidStateError}}.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.
Validate |candidatePair| by running the [= validate() algorithm =] to which |connection| and a new sequence containing |candidatePair| are supplied as arguments.
Let |p:Promise| be a new promise.
Run the following steps in parallel:
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()}}.
If the current {{RTCIceCandidatePairReport/state}} of the candidate pair {{RTCStatsIceCandidatePairState}} is {{RTCStatsIceCandidatePairState/"failed"}}, [= reject =] |p| with a newly [= exception/created =] {{InvalidStateError}}.
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.
Instruct the [= ICE agent=] to construct and send a STUN check from |candidatePair|.{{RTCIceCandidatePair/local}} to |candidatePair|.{{RTCIceCandidatePair/remote}}.
[= Resolve =] |p| with undefined
.
Return |p|.
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:
Let |candidatePair:RTCIceCandidatePair| be the method's first argument.
Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{switchToCandidatePair}} is invoked.
If |controller|.{{RTCIceController/[[ProposalPending]]}} is true
, return a [= rejected =]
promise with a newly [= exception/created =] {{InvalidStateError}}.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.
Validate |candidatePair| by running the [= validate() algorithm =] to which |connection| and a new sequence containing |candidatePair| are supplied as arguments.
Let |p:Promise| be a new promise.
Run the following steps in parallel:
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.
Instruct the [= ICE agent =] to change the selected candidate pair to |candidatePair|.
[= Resolve =] |p| with undefined.
Return |p|.
The {{pruneCandidatePairs}} method attempts to prune the provided candidate pairs. When the {{pruneCandidatePairs}} method is invoked, the [= user agent =] MUST run the following steps:
Let |candidatePairs:sequence<RTCIceCandidatePair>| be the method's first argument.
Let |controller:RTCIceController| be the {{RTCIceController}} object on which {{pruneCandidatePairs}} is invoked.
If |controller|.{{RTCIceController/[[ProposalPending]]}} is true
, return a [= rejected =]
promise with a newly [= exception/created =] {{InvalidStateError}}.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object stored in |controller|.{{RTCIceController/[[PeerConnection]]}}.
Validate |candidatePairs| by running the [= validate() algorithm =] to which |connection| and |candidatePairs| are supplied as arguments.
Let |p:Promise| be a new promise.
Run the following steps in parallel:
Instruct the [= ICE agent =] to prune the candidate pairs in |candidatePairs|.
[= Resolve =] |p| with undefined.
Return |p|.
The [= user agent =] MUST validate that the application is allowed to perform a requested operation by running the following steps:
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object and |candidatePairs:sequence<RTCIceCandidatePair>| be the sequence of candidate pairs supplied to the algorithm.
If |connection| is null
, return a [= rejected =] promise with a newly [= exception/created
=] {{InvalidStateError}} and abort these steps.
If |connection|.{{RTCPeerConnection/[[IsClosed]]}} is true
, return a [= rejected =] promise
with a newly [= exception/created =] {{InvalidStateError}}.
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"}}.
If |transport|.{{RTCIceTransport/[[IceTransportState]]}} is either of {{RTCIceTransportState/"new"}}, {{RTCIceTransportState/"failed"}} or {{RTCIceTransportState/"closed"}}, return a [= rejected =] promise with a newly [= exception/created =] {{InvalidStateError}}.
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.
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 =].
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; };
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; };
See the {{RTCIceCandidatePairEvent/candidatePair}} attribute of the {{RTCIceCandidatePairEvent}} interface.
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; };
The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object that represents the candidate pair for which updated information is available.
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; };
See the {{RTCIceCandidatePairUpdateEvent/candidatePair}} attribute of the {{RTCIceCandidatePairUpdateEvent}} interface.
See the {{RTCIceCandidatePairUpdateEvent/report}} attribute of the {{RTCIceCandidatePairUpdateEvent}} 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(); };
The {{connected}} attribute represents whether the ICE candidate pair is in connected state.
The {{state}} attribute represents the state of a candidate pair in an ICE check list defined in Section 6.1.2.6 of [[RFC8445]].
The {{writeState}} attribute indicates the write state of the candidate pair.
The {{lastPingSentTimestamp}} attribute, if present, represents the [= timestamp =] at which the last ping was sent for an ICE connectivity check on this candidate pair.
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.
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.
The {{lastPacketReceivedTimestamp}} attribute, if present, represents the [= timestamp =] at which the last packet was received on this candidate pair, excluding STUN packets.
The {{pingsSent}} attribute represents the total number of pings sent for ICE connectivity check on this candidate pair.
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; };
The {{connected}} member represents whether the ICE candidate pair is in connected state.
The {{state}} member represents the state of a candidate pair in an ICE check list defined in Section 6.1.2.6 of [[RFC8445]].
The {{writeState}} member indicates the write state of the candidate pair.
The {{lastPingSentTimestamp}} member, if present, represents the [= timestamp =] at which the last ping was sent for an ICE connectivity check on this candidate pair.
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.
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.
The {{lastPacketReceivedTimestamp}} member, if present, represents the [= timestamp =] at which the last packet was received on this candidate pair, excluding STUN packets.
The {{pingsSent}} member represents the total number of pings sent for ICE connectivity check on this candidate pair.
The {{roundTripTimeSamples}} member, if present, represents all the round trip time samples gathered for this candidate pair.
dictionary RTCRoundTripTimeSample { required DOMHighResTimeStamp timestamp; required double value; };
The {{RTCRoundTripTimeSample/timestamp}} member represents the time at which this round trip time sample was measured.
The {{value}} member indicates the measured round trip time in this sample.
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" };
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. |
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.
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:
Let |candidatePair:RTCIceCandidatePair| be the candidate pair for which a ping is being proposed.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].
If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.
Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true
.
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 =].
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false
.
If |accepted| is false
, abort these steps.
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; };
The {{candidatePair}} attribute is the {{RTCIceCandidatePair}} object that represents the candidate pair that the [= ICE agent =] has selected to ping for a connectivity check.
The {{lastPingSentTimestamp}} attribute represents the [= timestamp =] at which the last connectivity check ping was sent by the [= ICE agent =].
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; };
See the {{RTCIcePingProposalEvent/candidatePair}} attribute of the {{RTCIcePingProposalEvent}} interface.
See the {{RTCIcePingProposalEvent/lastPingSentTimestamp}} attribute of the {{RTCIcePingProposalEvent}} interface.
See the {{RTCIcePingProposalEvent/earliestNextPingTimestamp}} attribute of the {{RTCIcePingProposalEvent}} interface.
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:
Let |candidatePair:RTCIceCandidatePair| be the candidate pair to which a switch is being proposed.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].
If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.
Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.
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.
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true
.
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 =].
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false
.
If |accepted| is false
, abort these steps.
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; };
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.
The {{reason}} attribute represents the reason that triggered the [= ICE agent =] to search for a different candidate pair to switch the transport to.
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; };
See the {{RTCIceSwitchProposalEvent/candidatePair}} attribute of the {{RTCIceSwitchProposalEvent}} interface.
See the {{RTCIceSwitchProposalEvent/reason}} attribute of the {{RTCIceSwitchProposalEvent}} interface.
See the {{RTCIceSwitchProposalEvent/recheck}} attribute of the {{RTCIceSwitchProposalEvent}} interface.
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" };
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. |
dictionary RTCIceRecheck { required RTCIceSwitchReason reason; required DOMHighResTimeStamp earliestRecheckTimestamp; };
The {{reason}} member represents the reason for the future trigger to search for a different candidate pair for the transport.
The {{earliestRecheckTimestamp}} member indicates the earliest [= timestamp =] at which the future check will be performed.
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:
Let |candidatePairs:sequence<RTCIceCandidatePair>| be the candidate pairs which are being proposed for pruning.
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].
If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.
Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to true
.
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|.
Set |controller|.{{RTCIceController/[[ProposalPending]]}} to false
.
If |accepted| is false
, abort these steps.
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(); };
The {{getCandidatePairs}} method returns the candidate pairs selected by the [= ICE agent =] for pruning at this time.
dictionary RTCIcePruneProposalEventInit : EventInit { required sequence<RTCIceCandidatePair> candidatePairs; };
The {{candidatePairs}} member represents the candidate pairs selected by the [= ICE agent =] for pruning at this time.
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.
The {{RTCConfiguration}} dictionary is extended with an {{RTCConfiguration/iceController}} member.
partial dictionary RTCConfiguration { RTCIceController iceController; };
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}}.
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.
When the RTCPeerConnection.constructor()
is invoked, run the following additional steps:
Let |connection:RTCPeerConnection| be the newly created {{RTCPeerConnection}} object.
Let |connection| have an [[\IceController]] internal
slot, initialized to null
.
To set a configuration with |configuration:RTCConfiguration|, run the following additional steps:
Let |connection:RTCPeerConnection| be the target {{RTCPeerConnection}} object.
Let |oldConfig:RTCConfiguration| be |connection|.[[\Configuration]]
.
If |oldConfig| is not null
, and if the value of
|configuration|.{{RTCConfiguration/iceController}} differs from
|oldConfig|.{{RTCConfiguration/iceController}}, [= exception/throw =] an {{InvalidModificationError}}.
Otherwise, if |configuration|.{{RTCConfiguration/iceController}} is not null
, run
the following steps:
If |configuration|.{{RTCConfiguration/iceController}}.{{RTCIceController/[[PeerConnection]]}} is not null, [= exception/throw =] an {{InvalidModificationError}}.
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}}.
If the value of |configuration|.{{RTCConfiguration/iceCandidatePoolSize}} is not
0
, [= exception/throw =] a {{NotSupportedError}}.
Store |configuration|.{{RTCConfiguration/iceController}} in the {{RTCPeerConnection/[[IceController]]}} internal slot.
Store |connection| in {{RTCIceController/[[PeerConnection]]}} of |configuration|.{{RTCConfiguration/iceController}}.
When either {{RTCPeerConnection/setLocalDescription()}} or {{RTCPeerConnection/setRemoteDescription()}} is invoked, and a session description is set as a result, run the following additional steps:
Let |connection:RTCPeerConnection| be the target {{RTCPeerConnection}} object.
If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.
Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.
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.
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:
Let |connection:RTCPeerConnection| be the {{RTCPeerConnection}} object associated with this [= ICE agent =].
If |connection|.{{RTCPeerConnection/[[IceController]]}} is null, abort these steps.
Let |controller:RTCIceController| be the {{RTCIceController}} object stored in |connection|.{{RTCPeerConnection/[[IceController]]}}.
Let |newCandidatePair:RTCIceCandidatePair| be a newly created {{RTCIceCandidatePair}} representing the
indicated pair if one is selected, and null
otherwise.
Set |controller|.{{RTCIceController/[[SelectedCandidatePair]]}} to |newCandidatePair|.
[= Fire an event =] named {{RTCIceController/candidatepairswitched}} using the {{RTCIceCandidatePairEvent}} interface with the {{RTCIceCandidatePairEvent/candidatePair}} attribute set to |newCandidatePair| at |controller|.
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 });
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 });
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. |
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.
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.