Skip to main content

@pexip/peer-connection

Wrapper for RTCPeerConnection with @pexip/signal

Install

npm install @pexip/peer-connection

APIs

Cores

  • PeerConnection: The general purpose RTCPeerConnection wrapper, which can be used to create different kind of peer connection with different behavior based on needs. See MainPeerConnection or PresentationPeerConnection.
  • MainPeerConnection: Logical layer on top of PeerConnection to handle media connection with the common interface BasePeerConnection.
  • PresentationPeerConnection: Logical layer on top of PeerConnection to handle presentation media connection with the common interface BasePeerConnection.

Utilities

  • createPCSignals: Create the signals used for MainPeerConnection and PresentationPeerConnection 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 counterpart onconnectionstatechange
  • onDataChannel: Signal<RTCDataChannel>;: RTCPeerConnection counterpart ondatachannel
  • onIceCandidate: Signal<RTCIceCandidate | null>;: RTCPeerConnection counterpart onicecandidate, and use this signal for trickleIce. If this signal is NOT provided, trickleIce feature will be disabled.
  • onIceCandidateError: Signal<RTCPeerConnectionIceErrorEvent>;: RTCPeerConnection counterpart onicecandidateerror.
  • onIceConnectionStateChange: Signal<RTCIceConnectionState>;: RTCPeerConnection counterpart oniceconnectionstatechange
  • onIceGatheringStateChange: Signal<RTCIceGatheringState>;: RTCPeerConnection counterpart onicegatheringstatechange
  • onSignalingStateChange: Signal<RTCSignalingState>;: RTCPeerConnection counterpart onsignalingstatechange
  • onTrack: Signal<RTCTrackEvent>;: RTCPeerConnection counterpart ontrack
  • onNegotiationNeeded: Signal<RTCOfferOptions | undefined>;: RTCPeerConnection counterpart onnegotiationneeded
  • onRemoteStreams: Signal<MediaStream[]>;: Based on ontrack to emit MediaStream[] when there is one from RTCPeerConnection['ontrack'].
  • onRemoteContentStreams: Signal<MediaStream[]>;: Based on ontrack to emit MediaStream[] when there is one from RTCPeerConnection['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 connection
  • onOffer: Signal<RTCSessionDescriptionInit>;: Emit offer SDP after createOffer
  • onAnswer: Signal<RTCSessionDescriptionInit>;: Emit answer SDP after createAnswer

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 signal
  • onReceiveOffer: Signal<RTCSessionDescriptionInit>;: Signaling counterpart should emit an offer when the remote peer sends an offer via this signal
  • onOfferRequired: Signal<MediaStream | undefined>;: This is an alternative way to setLocalStream to trigger RTCPeerConnection['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: {in: 1024, out: 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}