@pexip/peer-connection
Wrapper for RTCPeerConnection
with @pexip/signal
Install
npm install @pexip/peer-connection
APIs
Cores
PeerConnection
: The general purposeRTCPeerConnection
wrapper, which can be used to create different kind of peer connection with different behavior based on needs. SeeMainPeerConnection
orPresentationPeerConnection
.MainPeerConnection
: Logical layer on top ofPeerConnection
to handle media connection with the common interfaceBasePeerConnection
.PresentationPeerConnection
: Logical layer on top ofPeerConnection
to handle presentation media connection with the common interfaceBasePeerConnection
.
Utilities
createPCSignals
: Create the signals used forMainPeerConnection
andPresentationPeerConnection
creation.Example:
// Create all the required signals, and `onConnectionStateChange`
const mainSignals = createPCSignals('main', ['onConnectionStateChange']);
Signals
Optional
Useful when custom implementation of peer connection to handle different behavior
onConnectionStateChange: Signal<RTCPeerConnectionState>;
:RTCPeerConnection
counterpartonconnectionstatechange
onDataChannel: Signal<RTCDataChannel>;
:RTCPeerConnection
counterpartondatachannel
onIceCandidate: Signal<RTCIceCandidate | null>;
:RTCPeerConnection
counterpartonicecandidate
, and use this signal fortrickleIce
. If this signal is NOT provided,trickleIce
feature will be disabled.onIceCandidateError: Signal<RTCPeerConnectionIceErrorEvent>;
:RTCPeerConnection
counterpartonicecandidateerror
.onIceConnectionStateChange: Signal<RTCIceConnectionState>;
:RTCPeerConnection
counterpartoniceconnectionstatechange
onIceGatheringStateChange: Signal<RTCIceGatheringState>;
:RTCPeerConnection
counterpartonicegatheringstatechange
onSignalingStateChange: Signal<RTCSignalingState>;
:RTCPeerConnection
counterpartonsignalingstatechange
onTrack: Signal<RTCTrackEvent>;
:RTCPeerConnection
counterpartontrack
onNegotiationNeeded: Signal<RTCOfferOptions | undefined>;
:RTCPeerConnection
counterpartonnegotiationneeded
onRemoteStreams: Signal<MediaStream[]>;
: Based onontrack
to emitMediaStream[]
when there is one fromRTCPeerConnection['ontrack']
.onRemoteContentStreams: Signal<MediaStream[]>;
: Based onontrack
to emitMediaStream[]
when there is one fromRTCPeerConnection['ontrack']
.
The following signals are listened by the peer connection, they can be used for signal data from remote peer
onReceiveIceCandidate: Signal<RTCIceCandidate | RTCIceCandidateInit>;
: Signaling counterpart should emit an ICE candidate when the remote peer sends an ICE candidate via this signal.
Required
The following signals are required to handle for signaling purpose
onError: Signal<Error>;
: Emit error when there is error from the peer connectiononOffer: Signal<RTCSessionDescriptionInit>;
: Emit offer SDP aftercreateOffer
onAnswer: Signal<RTCSessionDescriptionInit>;
: Emit answer SDP aftercreateAnswer
The following signals are listened by the peer connection, they can be used for signal data from remote peer
onReceiveAnswer: Signal<RTCSessionDescriptionInit>;
: Signaling counterpart should emit the answer when the remote peer sends an answer via this signalonReceiveOffer: Signal<RTCSessionDescriptionInit>;
: Signaling counterpart should emit an offer when the remote peer sends an offer via this signalonOfferRequired: Signal<MediaStream | undefined>;
: This is an alternative way tosetLocalStream
to triggerRTCPeerConnection['createOffer']
Usage
// Create a set of required signals to share with the app
// signals.ts
import {createPCSignals} from '@pexip/peer-connection';
// This utility function creates the following signals
// onOfferRequired: Signal<undefined>;
// onReceiveAnswer: Signal<RTCSessionDescriptionInit>;
// onReceiveOffer: Signal<RTCSessionDescriptionInit>;
// onError: Signal<Error>;
// onOffer: Signal<RTCSessionDescriptionInit>;
// onAnswer: Signal<RTCSessionDescriptionInit>;
// and the following from the `more` parameter
// onRemoteStreams: Signal<MediaStream[]>;
// onReceiveIceCandidate: Signal<RTCIceCandidate | RTCIceCandidateInit>;
// onIceCandidate: Signal<RTCIceCandidate | null>;
export const mainSignals = createPCSignals({
scope: 'main',
more: ['onRemoteStreams', 'onReceiveIceCandidate', 'onIceCandidate'],
});
// Here are the signal keys and signatures to map `RTCPeerConnection` events
// accordingly you can create any of them to pass in the `PeerConnection` when
// creating the object
// onConnectionStateChange: Signal<RTCPeerConnectionState>;
// onDataChannel: Signal<RTCDataChannel>;
// onIceCandidate: Signal<RTCIceCandidate | null>;
// onIceCandidateError: Signal<RTCPeerConnectionIceErrorEvent>;
// onIceConnectionStateChange: Signal<RTCIceConnectionState>;
// onIceGatheringStateChange: Signal<RTCIceGatheringState>;
// onSignalingStateChange: Signal<RTCSignalingState>;
// onTrack: Signal<RTCTrackEvent>;
// onNegotiationNeeded: Signal<RTCOfferOptions | undefined>;
// onRemoteStreams: Signal<MediaStream[]>;
// app.ts
import {createMainPeerConnection} from '@pexip/peer-connection';
import {mainSignals} from './signals';
// NOTE: Use `trickleIce` here since we have provided the `onIceCandidate` signal
const mainPeer = createMainPeerConnection(mainSignals, {bandwidth: 1024});
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
// set local media stream
// Offer will be created based on `negotiationneeded` flag according to the
// spec, and `onOfferSignal` will be emitted afterwards
await mainPeer.setLocalStream(stream);
// Later on to start presentation
const presentationStream = await navigator.mediaDevices.getDisplayMedia();
await peer.setLocalStream(presentationStream, 'slides');
// cleanup
mainPeer.close();
// Change bandwidth, and trigger `RTCPeerConnection['restartIce']` automatically
mainPeer.bandwidth = 2048;
// signaling.ts
// Handle remote peer events
switch (event.type) {
case 'onIceCandidate':
mainSignals.onReceiveIceCandidate.emit(event.candidate);
break;
case 'onOffer':
mainSignals.onReceiveOffer.emit(event.offer);
break;
case 'onAnswer':
mainSignals.onReceiveAnswer.emit(event.answer);
break;
}
// Handle the local peer signals
const subscriptions = [
mainSignals.onOffer.add(offer => {
signaling.send({offer});
}),
mainSignals.onAnswer.add(answer => {
signaling.send({answer});
}),
mainSignals.onError.add(error => {
// Something is wrong with the local peer connection
console.error(error);
}),
mainSignals.onRemoteStreams.add([stream] => {
setStream(stream);
}),
mainSignals.onIceCandidate.add(candidate => {
if (candidate) {
signaling.send({candidate});
}
}),
];
Sequence Diagrams
Initial connection and media setup for Direct Media
sequenceDiagram
participant A as Alice
participant M as MCU (Direct Media)
participant B as Bob
A->>+M: /calls offer
M-->M: Discard offer
M-->>-A: 200 OK
Note over A,B: Starts setting up connection
B-->B: Create DataChannel
B-->B: Negotiation needed
B-->B: Create offer
B->>+M: /calls {"sdp": offer}
M-->>+A: new_offer {"sdp": offer}
A-->A: Marked as a Polite Peer
A-->A: Drop Prev Offer with setRemoteDescription offer
A-->A: Create answer
A->>-M: /ack {"sdp": answer}
M-->>-B: {"sdp": answer}
Note over A,B: Start setting up media
B-->B: Schedule to sync transceivers/media
B-->B: Negotiation needed
B-->B: Create offer
B->>+M: /update {"sdp": offer}
M-->>+A: update_sdp {"sdp": offer}
A-->A: setRemoteDescription offer
A-->A: Sync transceivers/media
A-->A: Create answer
A->>-M: /ack {"sdp": answer}
M-->>-B: {"sdp": answer}
Perfect Negotiation
sequenceDiagram
participant A as Alice
participant M as MCU
participant B as Bob
Note over A,B: Perfect Negotiation after Initial connection & media setup
par A to M
A->>+M: /update {"sdp": offer}
M-->>+B: update_sdp {"sdp": offer}
B-->B: Ignore offer
and B to M
B->>+M: /update {"sdp": offer}
M-->>+A: update_sdp {"sdp": offer}
A-->A: setRemoteDescription offer
A-->A: Sync Transceivers
A-->A: Create Answer
end
A--xM: Cancel /update {"sdp": offer}
A->>-M: /ack {"sdp": answer}
M-->>-B: {"sdp": answer}
Initial connection and media setup for Normal VMR
sequenceDiagram
participant A as Alice
participant M as MCU
Note over A,M: Initial connection & media setup for Nromal VMR
A-->A: Create inactive transceivers for warmup
A-->A: Triggerred Negotiation needed
A-->A: Create offer
A->>+M: /calls {"sdp": offer}
M-->>-A: {"sdp": answer}
A-->A: setRemoteDescription answer
A-->A: Schedule to sync Transceivers
A-->A: Negotiation needed
A->>+M: /update {"sdp": offer}
M-->>-A: {"sdp": answer}