Web
I. Cài đặt
Tải SIP.js tại đây: https://github.com/onsip/SIP.js/releases hoặc nếu project có sử dụng npm thì dùng
npm install sip.js
Phiên bản mới nhất hiện tại: 0.20.0
Document SIP.js: https://github.com/onsip/SIP.js/blob/master/docs/README.md
II. Đăng nhập máy nhánh (extension)
Khai báo các biến global và import các class sau:
import {
Invitation,
InvitationAcceptOptions,
Inviter,
InviterInviteOptions,
Registerer,
Session,
SessionState,
UserAgent,
UserAgentOptions,
Web
} from "sip.js";
import {IncomingResponse} from "sip.js/lib/core";
userAgent: UserAgent;
// registerer để quản lý các thao tác đăng nhập, đăng xuất máy nhánh.
registerer: Registerer;
// incomingInvitation để quản lý các sự kiện liên quan đến cuộc gọi đến (incoming call)
incomingInvitation: Invitation;
// outgoingInviter để quản lý các sự kiện liên quan đến cuộc gọi đi (outgoing call)
outgoingInviter: Inviter;
// khi một cuộc gọi bất kì diễn ra, 1 session được tạo ra, biến này dùng để theo dõi các trạng thái của cuộc gọi cho đến khi cuộc gọi kết thúc.
session: Session;
Khai báo UserAgent và Registerer để đăng nhập máy nhánh:
// Giải thích:
// username: extension_number (ví dụ: 1234, 5678, ...)
// password: extension_password (ví dụ: abcDefg123, xzyqerTT11, ...)
// domain: tenant_domain (ví dụ: 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: {
// chứa các sự kiện như có cuộc gọi đến, có cuộc gọi chuyển tiếp, ...
// ví dụ ở mục V sẽ nói tới nhận cuộc gọi đến, phần đó sẽ được để trong này
}
};
this.userAgent = new UserAgent(_userAgentOptions);
this.registerer = new Registerer(this.userAgent, {});
await this.userAgent?.start();
await this.registerer?.register();
}
III. Đăng xuất máy nhánh (extension)
async logout() {
await this.registerer?.unregister();
await this.userAgent?.stop();
}
IV. Thực hiện cuộc gọi (outgoing call)
Để thực hiện cuộc gọi:
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;
**// lắng nghe các State của cuộc gọi: bắt đầu, kết thúc, ... Sẽ nói ở mục VII.**
this._sessionStateListener();
const _inviterInviteOptions: InviterInviteOptions = {
requestDelegate: {
// Khi cuộc gọi bắt đầu thì sự kiện onProgress này xảy ra.
onProgress: (response: IncomingResponse) => {
if (response.message.headers['X-Session-Id']?.length) {
// TODO: lấy X-Session-Id ra để phục vụ cho việc lấy Call Logs (nếu cần).
}
},
// Khi cuộc gọi được người nhận chấp nhận thì sự kiện onAccept này xảy ra.
onAccept: (response: IncomingResponse) => {
this.session = inviter;
**// Xử lý cuộc gọi, lấy remoteMediaStream (audio/video) của khách hàng
// để có thể nghe và nói chuyện được với khách hàng.
// Sẽ nói ở mục VI.**
this._processCalls();
}
}
};
await this.outgoingInviter.invite(_inviterInviteOptions);
}
Cuộc gọi video (Video Call), tiếp tục là hàm call()
bên trên:
// bạn có thể tuỳ chỉnh những thông số này theo nếu cần thiết.
const videoConstraints: MediaTrackConstraints = {
advanced: [
{
width: 480,
height: 640,
echoCancellation: true,
frameRate: 20,
}
]
};
async call(phoneNumber: string) {
[...]
**// lắng nghe các State của cuộc gọi: bắt đầu, kết thúc, ... Sẽ nói ở mục VII.**
this._sessionStateListener();
const constraints: MediaStreamConstraints = {
video: videoConstraints,
audio: true
};
**// chuẩn bị webcam để tiến hành video call.**
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
// tìm Local Video Element (Local là phía user, phân biệt với Remote là phía người đàm thoại với User)
const localVideo = document.getElementById("localVideo") as HTMLVideoElement;
// sau đó gán MediaStream làm source của Video Element đó
localVideo.srcObject = stream;
// gọi hàm play() để phát video.
localVideo.play().catch(err => console.error("ERROR in Streaming Local Video", err));
});
const _inviterInviteOptions: InviterInviteOptions = {
requestDelegate: {
[...]
sessionDescriptionHandlerOptions: {constraints}
}
};
}
V. Nhận cuộc gọi (incoming call)
Thêm giá trị này vào field delegate của UserAgentOptions ở mục II, bước 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;
**// lắng nghe các State của cuộc gọi: bắt đầu, kết thúc, ... Sẽ nói ở mục VII.**
this._sessionStateListener();
if (this.incomingInvitation.request.headers['X-Session-Id']?.length) {
// TODO: lấy X-Session-Id ra để phục vụ cho việc lấy Call Logs (nếu cần).
}
}
Nếu là cuộc gọi video thì check biến
incomingInvitation
this.incomingInvitation.body.includes("label:video_label")
Chấp nhận cuộc gọi
async answer() {
await this.incomingInvitation.accept();
**// Xử lý cuộc gọi, lấy remoteMediaStream (audio/video) của khách hàng
// để có thể nghe và nói chuyện được với khách hàng.
// Sẽ nói ở mục VI.**
this._processCalls();
}
Nếu là cuộc gọi video thì cũng với hàm
answer()
bên trên, ta có thể hiện thực như sau:
async answer() {
const constraints: MediaStreamConstraints = {
video: videoConstraints,
audio: true
};
**// chuẩn bị webcam để tiến hành video call.**
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
// tìm Local Video Element (Local là phía user, phân biệt với Remote là phía người đàm thoại với User)
const localVideo = document.getElementById("localVideo") as HTMLVideoElement;
// sau đó gán MediaStream làm source của Video Element đó
localVideo.srcObject = stream;
// gọi hàm play() để phát video.
localVideo.play().catch(err => console.error("ERROR in Streaming Local Video", err));
});
await this.incomingInvitation.accept({
sessionDescriptionHandlerOptions: {constraints}
});
this._processCalls();
}
Từ chối cuộc gọi
async reject() {
await this.incomingInvitation.reject();
}
VI. Xử lý cuộc gọi, lấy remoteMediaStream
_processCalls() {
const sessionDescriptionHandler = this.session.sessionDescriptionHandler;
if (sessionDescriptionHandler && sessionDescriptionHandler instanceof Web.SessionDescriptionHandler) {
if (**cuộc gọi là video**) {
// tìm Remote Video Element trên HTML DOM
const remoteVideo = document.getElementById("remoteVideo") as HTMLVideoElement;
// sau đó gán MediaStream làm source của Video Element đó
remoteVideo.srcObject = sessionDescriptionHandler.remoteMediaStream;
// gọi hàm play() để phát video.
remoteVideo.play().catch(err => console.error("ERROR in Streaming Remote Video", err));
} else {
// tìm Audio Element trên HTML DOM
const remoteAudio = document.getElementById("remote") as HTMLAudioElement;
// sau đó gán MediaStream làm source của Audio Element đó
remoteAudio.srcObject = sessionDescriptionHandler.remoteMediaStream;
// gọi hàm play() để phát âm thanh.
remoteAudio.play().catch(err => console.error("ERROR in Streaming Remote Audio", err));
}
}
}
Giao diện của bạn phải có các tag
<audio>
và<video>
để hiển thị video và phát audio khi nghe gọi. Với local là phía user và remote là phía người đàm thoại với user.Ví dụ:
<video id="remoteVideo" autoplay>
</video>
<video id="localVideo" autoplay>
</video>
<audio style="display: none" id="remoteAudio"></audio>
<!--localAudio có thể được dùng để chứa audio về ring back tone, nhạc chuông báo cuộc gọi đến...-->
<audio style="display: none" id="localAudio" loop></audio>
VII. Lắng nghe các State của cuộc gọi
_sessionStateListener() {
this.session.stateChange.addListener((state: SessionState) => {
switch (state) {
case SessionState.Initial:
// Cuộc gọi vừa được khởi tạo.
case SessionState.Establishing:
// Cuộc gọi vừa được chấp nhận.
case SessionState.Established:
// Cuộc gọi bắt đầu diễn ra.
case SessionState.Terminating:
// Cuộc gọi vừa được kết thúc.
case SessionState.Terminated:
// Cuộc gọi hoàn toàn kết thúc
default:
}
});
}
VIII. Chủ động kết thúc cuộc gọi
async hangup() {
if (**Cuộc gọi là cuộc gọi đi và mới được khởi tạo, chưa được chấp nhận**) {
await this.outgoingInviter.cancel();
} else {
await this.session?.bye();
}
}
IX. Giữ máy
async hold(isHold: boolean) {
const _holdOptions: Web.SessionDescriptionHandlerOptions = {
hold: isHold
};
this.session.sessionDescriptionHandlerOptionsReInvite = _holdOptions;
this.session.invite().then();
}
Last updated