@airtame/webrtc
TypeScript icon, indicating that this package has built-in type declarations

4.0.0 • Public • Published

Airtame WebRTC abstraction library

Introduction

This library is intended to be used for Airtame use-cases. Namely standalone streaming and streaming during a video call.

It supports, 1-1, 1-N, N-1, N-N types calls. The library is very opinionated and should only be used as intended.

In the library 2 things have to be understood:

  • The streamer connection is always the one to create the data channel and to initiate the media connection. Additionally, it will always be the "impolite" peer (see Perfect Negotiation Pattern).
  • The library is not tested to support bi-directional media meaning that we haven't tested if a receiver can easily send data and a streamer receive some.

Usage

Build

To build the library:

# Install the dependencies
npm install

# Run the build script
npm run build

After that you have the artifacts in the dist/ folder.

Run the examples

# Install the dependencies
npm install

# Start the dev server
npm run start

You can now go to http://localhost:8080/public/example/streamer for the streamer and http://localhost:8080/public/example/receiver for the receiver.

To implement

There are only two raw object in this library: WebRTCApplication and WebRTCConnection. For conveniance we also provide a React wrapper: WebRTCApplicationProvider.

Here are the outline of both main object:

WebRTCApplication:

class WebRTCApplication {
  /*
   * Create a connection that will be used by a receiver.
   *
   * @returns A tuple containing the newly created connection and the answer to the offer.
   */
  public acceptConnection(id: string, offer: string): Promise<[Connection, string]>;

  /*
   * Create a connection that will be used by a streamer.
   *
   * @returns A tuple containing the newly created connection and the offer.
   */
  public connect(id: string): Promise<[Connection, string]>;
}

WebRTCConnection:

class WebRTCConnection extends EventEmitter {
  /*
   * Returns the main RTCPeerConnection used for data communication.
   */
  get mainPeerConnection(): RTCPeerConnection;

  /*
   * Returns the RTCPeerConnection used to transport media. Can be undefined is no streaming
   * session is started.
   */
  get mediaPeerConnection(): RTCPeerConnection | undefined;
  /*
   * Returns the current state of the main connection - data channel (see below for the state).
   */
  get connectionState(): ConnectionState;

  /*
   * Returns the current state of the media connection (see below for the state).
   */
  get mediaConnectionState(): ConnectionState;

  /*
   * Initialize the connection to be used as a receiving connection. That is, to be
   * used in a receiver.
   *
   * @returns The answer to the offer provided in argument.
   */
  public initReceiver(offer: string): Promise<string>;

  /*
   * Initialize the connection to be used as a sending connection. That is, to be
   * used in a streamer.
   *
   * @returns The offer.
   */
  public initSender(): Promise<string>;

  /*
   * Disconnect the full application, media stream included. Will emit the `CLOSED` event on
   * the CONNECTION_STATE and the MEDIA_STATE.
   * The connection is not usable from that point, and you need to call `connect` once again to
   * create a new connection.
   */
  public disconnect(): void;

  /*
   * Start the streaming of media data to the receiver.
   *
   * *This is only used in a streamer otherwise it will throw an error*.
   *
   * NOTE:
   * -----
   *   This is the responsibility of the caller to stop the mediaStream tracks when
   *   needed. The WebRTC API is not responsible for it when calling stopStream.
   */
  public startStream(ms: MediaStream, codecFilter?: CodecFilterPredicate): void;

  /*
   * Stop the stream connection. Stop all the tracks on the media stream and close the connection.
   * The state closed will be emitted.
   */
  public stopStream(): void;

  /*
   * Set the initial answer provided by a receiver to a streamer.
   *
   * *This is only used in a streamer otherwise it will throw an error*.
   */
  public setAnswer(answer: string): Promise<void>;

  /*
   * Send a message to the peer connection. This is used in both the receiver and streamer.
   *
   * Notes: The kind is used to identify the message being sent and the message is the content.
   * Notes: The kind shouldn't start with `__`. This is reserved to the internal usage of the
   *        library.
   */
  public sendMessage(kind: string, message: string): void;
}

Additionally, we have a connection state enum:

enum ConnectionState {
  // When nothing started, for example:
  //   * No receiver nor streamer are event initialized
  //   * For media state, it would mean that no streaming session is started
  NOT_INITIALIZED = 'not-initialized',
  // When somethig is initialized like the streamer or receiver, or stream is called
  INITIALIZED = 'initialized',
  // When the connections are starting to their initialization and connection
  LOADING = 'loading',
  // When a session is started (main or media)
  OK = 'ok',
  // When a connection looses contact with the peer, but still trying to connect
  DISCONNECTED = 'disconnected',
  // When the disconnection passes a timeout, it turns out to this state
  FAILED = 'failed',
  // When a connection closed properly
  CLOSED = 'closed',
  // Other more generic errors
  ERROR = 'error',
}

For a complete usage example (using the React wrapper), please refer to the examples.

Filtering codecs

In some specific use cases, you might want to limit the codec used by the streamer. For example this is what happens with our Zoom SIP application.

Here is an example:

connection.startStream(mediaStream, (codecs: RTCRtpCodecCapability[]) =>
  codecs.filter(
    (codec) =>
      (codec.mimeType.toLowerCase() === 'video/h264' &&
        codec.sdpFmtpLine?.match(/42e01f/) &&
        codec.sdpFmtpLine.match(/packetization-mode=0/)) ||
      codec.mimeType.toLowerCase() === 'video/rtx'
  )
);

Events

An enum Events is available to be used to subscribe to events:

// See below for specific explanation of how to use those events and when.
export enum Events {
  MESSAGE = 'message',
  STREAM = 'stream',
  CONNECTION_STATE_CHANGE = 'connectionStateChange',
  MEDIA_STATE_CHANGE = 'mediaStateChange',
}

The Connection object described above implements EventEmitter and has some built in events to be aware of:

  • message: Used for the text communication between 2 peers. This is generated by both kind of connections.

    connection.on(Events.MESSAGE, (data) => {
      const message = JSON.parse(data);
      if (message.kind === 'my_great_message') {
        doSomething(message.message);
      }
    });
  • stream: Used by the receiver when it receives media data from the streamer.

    connection.on(Events.STREAM, (stream) => {
      if (video && video.current) {
        video.current.srcObject = stream;
      }
    });
  • connectionStateChange: Emitted when the main RTC session state changed. The emitter callback will have the signature (state: ConnectionState, message?: string): void.

    connection.on(Events.CONNECTION_STATE_CHANGE, (state, message) => {
      if (state === ConnectionState.OK) {
        console.log('Connected successfully');
      } else if (state === ConnectionState.ERROR) {
        console.log(`Error: ${message}`);
      }
    });
  • mediaStateChange: Emitted when the media streaming session state changed. The emitter callback will have the signature (state: ConnectionState, message?: string): void.

    connection.on(Events.MEDIA_STATE_CHANGE, (state, message) => {
      if (state === ConnectionState.OK) {
        console.log('Connected successfully');
      } else if (state === ConnectionState.ERROR) {
        console.log(`Error: ${message}`);
      }
    });

Readme

Keywords

none

Package Sidebar

Install

npm i @airtame/webrtc

Weekly Downloads

64

Version

4.0.0

License

none

Unpacked Size

112 kB

Total Files

8

Last publish

Collaborators

  • rene-airtame
  • airtame-ops