Web

I. Installation

II. Extension login

Declare global variables and import the following classes

import {
  Invitation,
  InvitationAcceptOptions,
  Inviter,
  InviterInviteOptions,
  Registerer,
  Session,
  SessionState,
  UserAgent,
  UserAgentOptions,
  Web
} from "sip.js";
import {IncomingResponse} from "sip.js/lib/core";

userAgent: UserAgent;

// register to manage extension login and logout operations.
registerer: Registerer;

// incomingInvitation to manage events related to incoming calls
incomingInvitation: Invitation;

// outgoingInviter to manage events related to outgoing calls
outgoingInviter: Inviter;

// When any call takes place, a session is created, this variable is used to track the status of the call until the call ends.
session: Session;

Declare UserAgent and Registerer to log in to the extension:

// Explain:
// username: extension_number (Ex: 1234, 5678, ...)
// password: extension_password (Ex: abcDefg123, xzyqerTT11, ...)
// domain: tenant_domain (Ex: voip.example.com, ...)
async login(username: string, password: string, domain: string) {

  const _uri = UserAgent.makeURI(`sip:${username}@${tenant_domain`);
  if (!_uri) { return; }

  const _userAgentOptions: UserAgentOptions = {
    uri: _uri,
    authorizationUsername: username,
    authorizationPassword: password,
    transportOptions: {
      server: "wss://sip.etelecom.vn:10443",
    },
    logLevel: "debug",
    displayName: username,
    delegate: {
     // contains events such as incoming calls, forwarded calls, ... 
     // for example, section V will talk about receiving incoming calls, that part will be put in here
    }
  };
  this.userAgent = new UserAgent(_userAgentOptions);

  this.registerer = new Registerer(this.userAgent, {});

  await this.userAgent?.start();

  await this.registerer?.register();

}

III. Log out of extension

async logout() {
  await this.registerer?.unregister();
  await this.userAgent?.stop();
}

IV. Making an outgoing call

To make a call:

async call(phoneNumber: string) {
  const target = UserAgent.makeURI(`sip:${phone}@${tenant_domain}`);
  if (!target) { return; }

  const inviter = new Inviter(this.userAgent, target, {});

  this.outgoingInviter = inviter;
  this.session = inviter;

  **// listen to the call's States: start, end, ... Will be discussed in section VII.**
  this._sessionStateListener();

  const _inviterInviteOptions: InviterInviteOptions = {
    requestDelegate: {
   // When the call starts, this onProgress event occurs.
      onProgress: (response: IncomingResponse) => {
        if (response.message.headers['X-Session-Id']?.length) {
        // TODO: get X-Session-Id to serve for getting Call Logs (if needed).
        }
      },
     // When the call is accepted by the receiver, this onAccept event occurs.
      onAccept: (response: IncomingResponse) => {
        this.session = inviter;

        **// Handle calls, get remoteMediaStream (audio/video) of customers
          // to be able to listen and talk to customers.
          // Will be discussed in section VI.**
        this._processCalls();
      }
    }
  };

  await this.outgoingInviter.invite(_inviterInviteOptions);
}

Video Call, continues the call()above function:

// you can customize these parameters if needed.
const videoConstraints: MediaTrackConstraints = {
  advanced: [
    {
      width: 480,
      height: 640,
      echoCancellation: true,
      frameRate: 20,
    }
  ]
};

async call(phoneNumber: string) {
  
  [...]
  **// listen to the call's States: start, end, ... Will be discussed in section VII.** 
  this._sessionStateListener();

  const constraints: MediaStreamConstraints = {
    video: videoConstraints,
    audio: true
  };

 **// prepare webcam for video call.**
  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    // find Local Video Element (Local is the user side, different from Remote which is the side of the person talking to the User)
    const localVideo = document.getElementById("localVideo") as HTMLVideoElement;

    // then assign MediaStream as the source of that Video Element
    localVideo.srcObject = stream;

    // call play() function to play video.
    localVideo.play().catch(err => console.error("ERROR in Streaming Local Video", err));
  });

  const _inviterInviteOptions: InviterInviteOptions = {
    requestDelegate: {
      [...]
      sessionDescriptionHandlerOptions: {constraints}
    }
  };
}

V. Receive call (incoming call)

Add this value to the delegate field of UserAgentOptions in section II, step 2.

**// khi có cuộc gọi đến, sự kiện onInvite này sẽ xảy ra**
onInvite: (invitation: Invitation) => {

  this.incomingInvitation = invitation;
  this.session = invitation;
  **// listen to the call's States: start, end, ... Will be discussed in section VII.** 
  this._sessionStateListener();

  if (this.incomingInvitation.request.headers['X-Session-Id']?.length) {
   // TODO: get X-Session-Id to serve for getting Call Logs (if needed).
  }

}
  • If it is a video call, check the variableincomingInvitation

this.incomingInvitation.body.includes("label:video_label")

Accept call

async answer() {
  await this.incomingInvitation.accept();
 **// Handle calls, get remoteMediaStream (audio/video) of customers
// to be able to listen and talk to customers.
// Will be discussed in section VI.**
  this._processCalls();
}
  • If it is a video call, with the same function answer()above, we can implement it as follows:

async answer() {
  const constraints: MediaStreamConstraints = {
    video: videoConstraints,
    audio: true
  };

**// prepare webcam for video call.**
  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    // find Local Video Element (Local is the user side, different from Remote which is the side of the person talking to the User)
    const localVideo = document.getElementById("localVideo") as HTMLVideoElement;

    // then assign MediaStream as the source of that Video Element
    localVideo.srcObject = stream;

   // call play() function to play video.
    localVideo.play().catch(err => console.error("ERROR in Streaming Local Video", err));
  });

  await this.incomingInvitation.accept({
    sessionDescriptionHandlerOptions: {constraints}
  });

  this._processCalls();
}

Reject call

async reject() {
  await this.incomingInvitation.reject();
}

VI. Call handling, getting remoteMediaStream

_processCalls() {
  const sessionDescriptionHandler = this.session.sessionDescriptionHandler;
  if (sessionDescriptionHandler && sessionDescriptionHandler instanceof Web.SessionDescriptionHandler) {
    if (**cuộc gọi là video**) {
     // find Remote Video Element on HTML DOM
      const remoteVideo = document.getElementById("remoteVideo") as HTMLVideoElement;
  
     // then assign MediaStream as the source of that Video Element
      remoteVideo.srcObject = sessionDescriptionHandler.remoteMediaStream;
  
   // call play() function to play video.
      remoteVideo.play().catch(err => console.error("ERROR in Streaming Remote Video", err));
    } else {
   // find Audio Element on HTML DOM
      const remoteAudio = document.getElementById("remote") as HTMLAudioElement;
  
     // then assign MediaStream as the source of that Audio Element
      remoteAudio.srcObject = sessionDescriptionHandler.remoteMediaStream;
  
      // call play() function to play sound.
      remoteAudio.play().catch(err => console.error("ERROR in Streaming Remote Audio", err));
    }
  }
}
  • Your interface must have tags <audio>and <video>to display video and play audio when making calls. With local being the user side and remote being the side where the user is talking to .

  • For example:

<video id="remoteVideo" autoplay>
</video>
<video id="localVideo" autoplay>
</video>
<audio style="display: none" id="remoteAudio"></audio>
<!--localAudio can be used to contain audio about ring back tone, incoming call ringtone...-->
<audio style="display: none" id="localAudio" loop></audio>

VII. Listening to the Call States

_sessionStateListener() {
  this.session.stateChange.addListener((state: SessionState) => {
    switch (state) {
      case SessionState.Initial:
      // The call has just been initiated.

      case SessionState.Establishing:
      // The call has just been accepted.

      case SessionState.Established:
      // The call starts.

      case SessionState.Terminating:
      // The call has just ended.

      case SessionState.Terminated:
       // The call is completely terminated

      default:
    }
  });
}

VIII. Actively end the call

async hangup() {
  if (**The call is an outgoing call and has just been initiated, not yet accepted.n**) {
    await this.outgoingInviter.cancel();
  } else {
    await this.session?.bye();
  }
}

IX. Hold the line

async hold(isHold: boolean) {
  const _holdOptions: Web.SessionDescriptionHandlerOptions = {
    hold: isHold
  };
  
  this.session.sessionDescriptionHandlerOptionsReInvite = _holdOptions;
  
  this.session.invite().then();
}

Last updated