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}

Enumerations

EnumerationDescription
RecoveryTimeout-

Interfaces

InterfaceDescription
Bandwidth-
BasePeerConnection-
CorePeerConnectionSignals-
DataChannelConfigData Channel Config for managed media
DataChannelInit-
ExtendedRTCPeerConnection-
MediaEncodingParameters-
PeerConnection-
PeerConnectionCommandSignals-
PeerConnectionOptions-
PeerConnectionSignals-
PexipMediaLine-
RTCPeerConnectionEventMap-
TransceiverConfigTransceiver Config for managed media
TransceiverInit-

Type Aliases

Type AliasDescription
DetachFn-
ErrorMessage-
EventHandler-
GetSignalTypeFromInterface-
LogFn-
MainPeerConnection-
MainPeerConnectionOptions-
MediaConfig-
MediaDirection-
MediaInit-
MediaTypeMedia Types ("media")
OnDataChannelEventHandler-
OnIceCandidateHandler-
OnNegotiationNeededHandler-
OnRemoteStreamsEventHandler-
OnSecureCheckCodeHandler-
OnTrackEventHandler-
OnTransceiverChangeHandler-
PCOptionalsSignals-
PCRequiredSignals-
PCSignals-
References-
ReferenceValue-
RTCPeerConnectionEventListeners-
TransceiverConfigDirectionTuple-

Variables

VariableDescription
REQUIRED_SIGNAL_KEYS-

Functions

FunctionDescription
asMediaDirection-
assertTransceiverMediaType-
changeTransceiverDirectionTry to derive the direction from currentDirection and the intendedDirection
compareArrayCompare 2 Array and using the provided predicate to compare
compareRecordCompare 2 flat records (an object with primitive type) using Object.is comparator
createEventQueueCreate a queue to handle buffering and trigger provided callback being called in order.
createGetRefs-
createMainPeerConnectionLogical layer of the Peer Connection for Call/Main, which connecting Signals and RTCPeerConnection events,
createMediaConfigs-
createPCSignalCreate a general signal with consistent scoped name
createPCSignalsCreate and return all required and optional (if specified with more), signals for peer connection to work
createPeerConnectionWrap RTCPeerConnection with polyfill the old APIs and simplifies the common logics
createRefsLogcreate a logger with reference attached
createRTCPeerConnectionA wrapper to create a RTCPeerConnection instance and subscribe the events declared from the provided list of listeners.
deriveSendDirectionFromTrack-
getCreateLoopbackConnectionFnworkaround to allow echo cancellation in Chromium browsers, due to https://bugs.chromium.org/p/chromium/issues/detail?id=687574.
getPeerConnectionStatesGet the states from RTCPeerConnection
getRelativeDirectionGet the relative direction so you can get the correct direction, e.g. "sendonly" <--> "recvonly"
getStatesAndPropsGet common states and props from PeerConnection
isDataChannelConfig-
isDataChannelInit-
isMediaConfig-
isMediaDirection-
isMediaInit-
isMediaType-
isSameStreamCompare the provided 2 streams to check if they are the same
isTransceiverConfig-
isTransceiverInit-
isTransceiverMediaType-
isTransceiverObsolete-
logReferences-
mergeMerge two array and overwriting the old on with the new one in order
resolveKindOrTrack-
setLogger-
subscribePCEvents-
wirePeerConnectionEventHandlerWire the peer connection event with the pre-defined handler and signal accordingly
wirePeerConnectionEventsWire the peer connection events with provided signals
withSignalsHandle some core signals for the peer connection.