WebRTC - RTCPeerConnection APIs
WebRTC - RTCPeerConnection APIs
Section titled “WebRTC - RTCPeerConnection APIs”The RTCPeerConnection API is the heart of WebRTC, enabling direct peer-to-peer connections between browsers (or other WebRTC-compatible endpoints) for exchanging audio, video, and arbitrary data.
To create an RTCPeerConnection object, you instantiate it with an optional configuration object:
const configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, // { urls: 'stun:stun1.l.google.com:19302' }, // Example of another STUN server // { // urls: 'turn:your.turn.server.com:3478', // username: 'yourUsername', // credential: 'yourPassword' // } // Example of a TURN server configuration ], // iceCandidatePoolSize: 10, // Optional: control number of pre-fetched ICE candidates // sdpSemantics: 'unified-plan' // Default in modern browsers, 'plan-b' is deprecated};
const peerConnection = new RTCPeerConnection(configuration);The configuration object typically includes an iceServers array. This array lists STUN (Session Traversal Utilities for NAT) and/or TURN (Traversal Using Relays around NAT) servers. STUN servers help discover a peer’s public IP address and port, while TURN servers act as relays when direct P2P connection fails due to complex NATs or firewalls. Many public STUN servers are available (e.g., Google’s stun:stun.l.google.com:19302). For robust applications, deploying your own TURN server(s) is often necessary.
The usage of RTCPeerConnection differs slightly depending on whether the peer is initiating the call (caller) or receiving it (callee), primarily in the offer/answer exchange.
A typical flow for establishing a connection involves:
- Setup Local Media: Use
navigator.mediaDevices.getUserMedia()to get local audio/video streams. - Create
RTCPeerConnection: Instantiatenew RTCPeerConnection(configuration). - Add Local Tracks: Add tracks from the local media stream to the
RTCPeerConnectionusingpeerConnection.addTrack(track, stream). This makes them available to be sent to the remote peer. - Register
onicecandidateHandler: This event fires when the local ICE agent finds an ICE candidate. These candidates must be sent to the remote peer via the signaling channel. - Register
ontrackHandler: This event fires when a remote media track is received. The handler should typically add this track to a remote<video>or<audio>element. - Register
ondatachannelHandler (if using data channels): For the receiving peer, this handles incoming data channel requests. - Signaling Logic: Implement handlers for messages received from the signaling server. These messages will contain offers, answers, or ICE candidates from the remote peer.
-
* If an SDP offer is received, call `peerConnection.setRemoteDescription(offer)`, then `peerConnection.createAnswer()`, then `peerConnection.setLocalDescription(answer)`, and send the answer back.
-
* If an SDP answer is received, call `peerConnection.setRemoteDescription(answer)`.
-
* If an ICE candidate is received, call `peerConnection.addIceCandidate(candidate)`.
- Initiate Offer (Caller side): The caller starts the negotiation by calling
peerConnection.createOffer(), thenpeerConnection.setLocalDescription(offer), and sends this offer to the callee via the signaling channel. - Negotiation Needed: The
negotiationneededevent onRTCPeerConnectioncan signal when an offer should be created, e.g., after adding tracks or creating data channels.
RTCPeerConnection API Details
Section titled “RTCPeerConnection API Details”Properties (Read-Only unless specified)
Section titled “Properties (Read-Only unless specified)”iceConnectionState: An enum (RTCIceConnectionState) indicating the current state of the ICE connection. Values:"new","checking","connected","completed","failed","disconnected","closed". Theiceconnectionstatechangeevent fires on changes.iceGatheringState: An enum (RTCIceGatheringState) indicating the ICE candidate gathering state:"new","gathering","complete". Theicegatheringstatechangeevent fires on changes.localDescription: AnRTCSessionDescriptionobject describing the local end of the connection (its SDP). Null if not yet set.remoteDescription: AnRTCSessionDescriptionobject describing the remote end of the connection. Null if not yet set.signalingState: An enum (RTCSignalingState) describing the state of the signaling process (offer/answer exchange):"stable","have-local-offer","have-remote-offer","have-local-pranswer"(provisional answer, less common in basic WebRTC),"have-remote-pranswer","closed". Thesignalingstatechangeevent fires on changes.canTrickleIceCandidates: A boolean indicating if the remote peer can accept trickle ICE candidates (candidates sent one by one as they are discovered). Modern WebRTC implementations generally support this.currentLocalDescription: Similar tolocalDescriptionbut represents the description that has been successfully applied most recently.currentRemoteDescription: Similar toremoteDescription.pendingLocalDescription: AnRTCSessionDescriptionthat is waiting to be applied locally (e.g., an offer or answer just created).pendingRemoteDescription: AnRTCSessionDescriptionthat is waiting to be applied from the remote side.
Event Handlers (assignable properties like peerConnection.onevent = handlerFunc;)
Section titled “Event Handlers (assignable properties like peerConnection.onevent = handlerFunc;)”onicecandidate: Fired when anRTCIceCandidateis generated by the local ICE agent. The event object’scandidateproperty should be sent to the remote peer.ontrack: Fired when a remoteMediaStreamTrackis added to the connection. The event object has atrackproperty and often astreamsarray (typically containing one stream to which the track belongs). This is the modern replacement foronaddstream.ondatachannel: Fired when the remote peer creates anRTCDataChanneland it’s signaled to this peer. The event object’schannelproperty is the newRTCDataChannelinstance.onnegotiationneeded: Fired when a new offer/answer negotiation is required, e.g., after adding a track or creating a data channel after the initial setup. The application should typically respond by creating an offer.oniceconnectionstatechange: Fired when theiceConnectionStateproperty changes.onicegatheringstatechange: Fired when theiceGatheringStateproperty changes.onsignalingstatechange: Fired when thesignalingStateproperty changes.onconnectionstatechange(Newer consolidated state): Fired when the overall connection state changes, derived from ICE and DTLS transport states. Values:"new","connecting","connected","disconnected","failed","closed". This is often a more convenient state to monitor thaniceConnectionStatealone.
Methods (many return Promises)
Section titled “Methods (many return Promises)”constructor (RTCPeerConnection(configuration?)): Creates a newRTCPeerConnectionobject.createOffer(options?): Asynchronously creates an SDP offer. Returns a Promise that resolves with anRTCSessionDescriptionInitobject (the offer). Options can specify if audio/video should be offered, or if ICE should be restarted.createAnswer(options?): Asynchronously creates an SDP answer to a received offer. Returns a Promise resolving with anRTCSessionDescriptionInitobject (the answer).setLocalDescription(description): Sets the local SDP description (offer or answer). Returns a Promise. Must be called aftercreateOfferorcreateAnswer.setRemoteDescription(description): Sets the remote SDP description received from the peer. Returns a Promise.addIceCandidate(candidate): Adds an ICE candidate received from the remote peer. Returns a Promise.getConfiguration(): Returns theRTCConfigurationobject currently in use by the connection.setConfiguration(configuration): Updates the configuration of the connection, e.g., to add new ICE servers. (Note: Not all parameters can be changed dynamically).addTrack(track, stream...?): Adds aMediaStreamTrackto be sent to the remote peer. Optionally associates it with one or moreMediaStreamobjects (useful forontrackevent on the remote side). Returns anRTCRtpSenderobject.removeTrack(sender): Stops sending a track previously added withaddTrack(). Takes anRTCRtpSender(returned byaddTrack) as an argument.getSenders(): Returns an array ofRTCRtpSenderobjects, each representing an RTP sender responsible for encoding and transmitting a track.getReceivers(): Returns an array ofRTCRtpReceiverobjects, each representing an RTP receiver responsible for decoding an incoming track.getTransceivers(): Returns an array ofRTCRtpTransceiverobjects. A transceiver groups a sender and a receiver, and manages negotiation for a media track. This is part of Unified Plan SDP.addTransceiver(trackOrKind, init?): Creates a newRTCRtpTransceiverand adds it to the set of transceivers. More advanced method for controlling media negotiation.trackOrKindcan be aMediaStreamTrackor a string ('audio'or'video').close(): Closes the peer connection, terminates media, and releases resources.createDataChannel(label, options?): Creates a newRTCDataChannelfor sending arbitrary data.labelis a string name,optionscan configure reliability, ordering, etc.getStats(selector?): Asynchronously collects statistics for the connection or a specific track/sender/receiver. Returns a Promise resolving with anRTCStatsReport.
Example: Establishing a Connection (Conceptual Client Code)
Section titled “Example: Establishing a Connection (Conceptual Client Code)”Below is a conceptual outline of client-side JavaScript for establishing a connection. Assume a signaling server (signalingChannel.send(message)) is available and handles message relay.
// --- Shared Variables ---let peerConnection;let localStream;let remoteStream;const signalingChannel = new WebSocket('ws://your-signaling-server.com'); // Example
const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
// --- Signaling Message Handler ---signalingChannel.onmessage = async (event) => { const message = JSON.parse(event.data); console.log('Received signaling message:', message);
if (message.offer) { if (!peerConnection) await createPeerConnection(); // Callee creates PC on offer await peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer)); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); signalingChannel.send(JSON.stringify({ answer: answer })); } else if (message.answer) { await peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer)); } else if (message.candidate) { try { if (message.candidate) { // Ensure candidate is not null/empty await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate)); } } catch (e) { console.error('Error adding received ICE candidate', e); } } else if (message.type === 'user-left') { // Handle peer disconnection closePeerConnection(); }};
// --- PeerConnection Setup ---async function createPeerConnection() { peerConnection = new RTCPeerConnection(configuration);
peerConnection.onicecandidate = event => { if (event.candidate) { signalingChannel.send(JSON.stringify({ candidate: event.candidate })); } };
peerConnection.ontrack = event => { console.log('Remote track received'); // const remoteVideo = document.getElementById('remoteVideo'); if (!remoteStream) remoteStream = new MediaStream(); remoteStream.addTrack(event.track); // remoteVideo.srcObject = remoteStream; };
// Add local tracks if localStream is available if (localStream) { localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream)); }}
// --- Caller Actions ---async function startCall() { if (!peerConnection) await createPeerConnection(); const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); signalingChannel.send(JSON.stringify({ offer: offer }));}
// --- Utility Functions ---async function setupLocalMedia() { try { localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); // const localVideo = document.getElementById('localVideo'); // localVideo.srcObject = localStream; } catch (e) { console.error('Error getting user media', e); }}
function closePeerConnection() { if (peerConnection) { peerConnection.close(); peerConnection = null; console.log('PeerConnection closed.'); } // Also clean up UI, local/remote streams as needed}
// --- Initialization Example ---// (async () => {// await setupLocalMedia();// // If this client is the caller:// // document.getElementById('callButton').onclick = startCall;// // If this client is a callee, it waits for an offer via signaling.// // In a real app, you'd have UI to decide to call or just be ready to receive.// })();The original tutorial included a console screenshot showing RTCPeerConnection object was created and the object’s properties after successful login in its example. Another screenshot showed SDP offers and ICE candidates being exchanged in the console. This updated example focuses on the API structure and conceptual flow; specific console outputs would depend on the full demo implementation.
This overview covers the fundamental aspects of RTCPeerConnection. Advanced topics include Simulcast, Scalable Video Coding (SVC), renegotiation for changing media, and detailed statistics analysis. Subsequent tutorials will provide practical demonstrations of these APIs in building voice, video, and data chat applications.