当前位置: 首页 > news >正文

《从零开始学 JSSIP:JavaScript 实时通信开发实战》

一、基础知识准备

1.1 什么是 SIP 协议

SIP(Session Initiation Protocol,会话初始协议)是一种用于创建、修改和终止多媒体会话的应用层协议,广泛应用于 IP 电话、视频会议、即时消息等实时通信场景。它是由 IETF(Internet Engineering Task Force)制定的标准协议,在 RFC 3261 中详细定义。

SIP 协议采用 C/S(客户端 / 服务器)架构,基于文本格式进行消息交换,具有简单、灵活、可扩展的特点。与 HTTP 协议类似,SIP 也是一种请求 - 响应式协议,但专为实时通信设计,支持多种媒体类型和复杂的会话控制功能。

SIP 协议的主要功能包括:

  • 用户定位:确定参与会话的用户位置

  • 会话建立:创建多媒体会话,协商媒体参数

  • 会话修改:动态调整会话参数,如添加视频流

  • 会话终止:优雅地结束会话

  • 会话管理:包括用户可用性检查、会话转移等功能

SIP 消息主要分为两类:请求(Request)和响应(Response)。常见的 SIP 请求方法包括:

  • INVITE:用于发起会话

  • ACK:用于确认会话建立

  • BYE:用于终止会话

  • CANCEL:用于取消未完成的请求

  • OPTIONS:用于查询服务器能力

  • MESSAGE:用于发送即时消息

  • INFO:用于发送带外信息

SIP 响应则包含一个状态码,表示请求处理结果。常见的状态码分类与 HTTP 类似,如 1xx(临时响应)、2xx(成功响应)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)等。

1.2 为什么选择 JSSIP

在 Web 浏览器中实现 SIP 功能曾经是一项复杂的任务,需要处理各种底层协议细节和浏览器兼容性问题。JSSIP(JavaScript SIP Library)的出现极大简化了这一过程,它是一个专为浏览器和 Node.js 环境设计的开源 JavaScript 库,提供了简单易用的 API 来实现 SIP 客户端功能。

JSSIP 的主要特点包括:

  1. 纯 JavaScript 实现:100% 用 JavaScript 编写,无需任何外部依赖(除了 WebRTC 支持),可在现代浏览器和 Node.js 环境中直接使用(5)

  2. 轻量级:体积小巧,不会显著增加页面加载时间,适合在各种 Web 应用中集成

  3. 易于使用:提供简洁直观的 API,即使没有 SIP 协议背景的开发者也能快速上手

  4. 全面的 SIP 支持:支持完整的 SIP 协议栈,包括会话建立、修改、终止,即时消息,状态通知等功能

  5. WebRTC 集成:无缝集成 WebRTC 技术,支持音频 / 视频通话和数据通道(5)

  6. WebSocket 传输:支持通过 WebSocket 传输 SIP 消息,适应现代 Web 环境

  7. 多平台兼容:同时支持浏览器和 Node.js 环境,可用于开发全栈实时通信应用

JSSIP 由撰写 RFC 7118(WebSocket 作为 SIP 传输协议)的团队开发,具有很高的权威性和可靠性。它被广泛应用于各种 WebRTC 通信解决方案中,如 Web 电话、视频会议系统、即时通讯工具等(24)。

1.3 开发环境搭建

在开始编写 JSSIP 应用之前,需要搭建合适的开发环境。本节将指导你完成环境设置,并介绍必要的工具和资源。

1.3.1 所需工具

开发 JSSIP 应用需要以下基本工具:

  1. 现代浏览器:JSSIP 支持最新版本的 Chrome、Firefox、Edge 等主流浏览器。由于使用了 WebRTC 技术,不支持 IE 浏览器。

  2. 文本编辑器:选择一款适合 JavaScript 开发的编辑器,如 Visual Studio Code、Sublime Text、Atom 等。

  3. Web 服务器:由于浏览器安全限制,JSSIP 应用需要在 Web 服务器环境中运行,不能直接通过文件协议(file://)打开。可以使用以下任意一种:

  • 内置 Web 服务器的编辑器(如 VS Code 的 Live Server 扩展)

  • Node.js 的 http-server 模块

  • Apache 或 Nginx 等传统 Web 服务器

  • 简单的 Python 服务器:python -m http.server(Python 3.x)

  1. 版本控制系统(可选):如 Git,用于管理项目代码

  2. 调试工具:浏览器自带的开发者工具(F12)是必备的调试工具,用于查看控制台输出、网络请求和 JavaScript 错误。

1.3.2 获取 JSSIP 库

有多种方式获取 JSSIP 库文件:

  1. 从 CDN 引入:可以直接从 cdnjs 等 CDN 服务引入 JSSIP 库,无需下载到本地。
\<script src="https://cdnjs.cloudflare.com/libraries/jssip/3.9.0/jssip.min.js">\</script>
  1. 从官网下载:访问 JSSIP 官方网站(http://www.jssip.net/)下载最新版本的库文件(6)。

  2. 使用 npm 安装(适用于 Node.js 环境或构建工具):

npm install jssip
  1. 从 GitHub 获取:JSSIP 的源代码托管在 GitHub 上,可以克隆整个仓库:
git clone https://github.com/versatica/JsSIP.git
1.3.3 基本 HTML 模板

创建一个基本的 HTML 文件,作为 JSSIP 应用的基础结构:

\<!DOCTYPE html>\<html>\<head>&#x20;   \<title>JSSIP Demo\</title>&#x20;   \<meta charset="UTF-8">&#x20;   \<!-- 引入JSSIP库 -->&#x20;   \<script src="jssip-3.9.0.min.js">\</script>\</head>\<body>&#x20;   \<!-- 在这里添加应用界面元素 -->&#x20;  &#x20;&#x20;   \<script>&#x20;       // 在这里编写JSSIP代码&#x20;   \</script>\</body>\</html>
1.3.4 启用调试日志

JSSIP 提供了强大的调试日志功能,帮助开发者诊断问题。默认情况下,JSSIP 不向浏览器控制台记录任何内容。要启用调试日志,可以在浏览器控制台中运行以下命令并重新加载页面:

JsSIP.debug.enable('JsSIP:\*');

这将启用所有模块的调试输出。你也可以指定特定模块的日志,例如只启用 UA 模块的日志:

JsSIP.debug.enable('JsSIP:UA');

要禁用调试日志,运行:

JsSIP.debug.disable('JsSIP:\*');

日志设置会存储在浏览器的 LocalStorage 中,下次访问时仍然有效(5)。

1.4 WebRTC 基础

JSSIP 与 WebRTC(Web Real-Time Communication)技术紧密结合,实现浏览器端的实时音视频通信。因此,了解 WebRTC 的基础知识对掌握 JSSIP 至关重要。

1.4.1 WebRTC 概述

WebRTC 是一项由 W3C 和 IETF 共同制定的开放标准,允许浏览器之间进行实时数据传输,无需安装第三方插件。它提供了一组 API,使浏览器能够访问本地媒体设备(如摄像头、麦克风),并通过点对点连接直接交换数据。

WebRTC 的核心组件包括:

  1. MediaStream API:用于访问和操作本地媒体设备,如摄像头和麦克风

  2. RTCPeerConnection API:用于建立点对点连接,管理媒体流传输

  3. RTCDataChannel API:用于在浏览器之间传输任意数据

WebRTC 的主要特点包括:

  • 支持多种媒体类型:音频、视频、任意数据

  • 直接点对点连接,减少服务器负载

  • 支持 NAT 穿透和防火墙穿越

  • 内置加密,确保通信安全

1.4.2 获取本地媒体流

使用navigator.mediaDevices.getUserMedia()方法可以获取本地媒体设备的流:

navigator.mediaDevices.getUserMedia({ audio: true, video: true })&#x20;   .then(function(stream) {&#x20;       // 成功获取媒体流&#x20;       videoElement.srcObject = stream;&#x20;   })&#x20;   .catch(function(error) {&#x20;       // 处理错误&#x20;       console.error('获取媒体设备失败:', error);&#x20;   });
1.4.3 RTCPeerConnection

RTCPeerConnection是 WebRTC 的核心 API,用于建立点对点连接。它管理着:

  • 媒体流的传输

  • ICE(Interactive Connectivity Establishment)过程,用于 NAT 穿透

  • DTLS(Datagram Transport Layer Security)安全层

  • SDP(Session Description Protocol)协商

创建RTCPeerConnection实例:

const configuration = {&#x20;   iceServers: \[&#x20;       { urls: 'stun:stun.l.google.com:19302' },&#x20;       { urls: 'turn:turn.example.com', username: 'user', credential: 'password' }&#x20;   ]};const peerConnection = new RTCPeerConnection(configuration);
1.4.4 SDP 协商

SDP(Session Description Protocol)是一种用于描述多媒体会话的格式。在 WebRTC 中,SDP 用于协商媒体格式、编解码器、传输地址等参数。

SDP 协商过程包括:

  1. 本地创建 SDP offer

  2. 将 offer 发送给远程对等方

  3. 远程对等方创建 SDP answer 并返回

  4. 双方交换 ICE 候选地址,建立连接

// 创建offerpeerConnection.createOffer()&#x20;   .then(function(offer) {&#x20;       return peerConnection.setLocalDescription(offer);&#x20;   })&#x20;   .then(function() {&#x20;       // 将offer发送给远程对等方&#x20;       sendSdpToRemotePeer(peerConnection.localDescription);&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('创建offer失败:', error);&#x20;   });// 处理远程offerfunction handleRemoteOffer(offer) {&#x20;   peerConnection.setRemoteDescription(new RTCSessionDescription(offer))&#x20;       .then(function() {&#x20;           return peerConnection.createAnswer();&#x20;       })&#x20;       .then(function(answer) {&#x20;           return peerConnection.setLocalDescription(answer);&#x20;       })&#x20;       .then(function() {&#x20;           // 将answer发送给远程对等方&#x20;           sendSdpToRemotePeer(peerConnection.localDescription);&#x20;       })&#x20;       .catch(function(error) {&#x20;           console.error('处理远程offer失败:', error);&#x20;       });}
1.4.5 ICE 候选收集

ICE(Interactive Connectivity Establishment)是一种用于在 NAT 设备和防火墙之间建立连接的机制。RTCPeerConnection会自动收集本地设备的 ICE 候选地址,并通过信令服务器交换这些候选地址。

// 监听ICE候选收集事件peerConnection.onicecandidate = function(event) {&#x20;   if (event.candidate) {&#x20;       // 将ICE候选发送给远程对等方&#x20;       sendIceCandidateToRemotePeer(event.candidate);&#x20;   }};// 处理远程ICE候选function handleRemoteIceCandidate(candidate) {&#x20;   peerConnection.addIceCandidate(new RTCIceCandidate(candidate))&#x20;       .catch(function(error) {&#x20;           console.error('添加远程ICE候选失败:', error);&#x20;       });}

1.5 本书约定和学习路径

本书旨在帮助 JavaScript 开发者快速掌握 JSSIP 库,从零开始构建实时通信应用。在开始深入学习之前,需要了解一些约定和学习路径。

1.5.1 本书约定
  1. 代码示例:本书中的代码示例均为纯 JavaScript,不使用任何第三方框架(如 React、Vue 等),以确保兼容性和学习的专注性。

  2. 版本约定:本书基于 JSSIP 3.9.x 版本编写,所有示例和说明均以此版本为基础。由于 JSSIP 可能会更新,建议在实际开发中参考最新文档。

  3. 日志输出:为了便于理解代码执行流程,很多示例中会包含console.log()输出。在实际生产环境中,应根据需要调整日志级别或禁用日志。

  4. 注释规范:代码注释采用 JSDoc 风格,详细说明函数参数、返回值和功能。

  5. 安全提示:涉及安全敏感信息(如密码、密钥)的示例,均使用占位符(如'your_password'),实际使用时请替换为真实值。

1.5.2 学习路径建议

本书按照由浅入深的顺序组织内容,建议按照以下路径学习:

  1. 基础篇(第 1-3 章):
  • 第 1 章:了解 SIP 协议基础和 JSSIP 库

  • 第 2 章:学习 JSSIP 核心组件和基本使用方法

  • 第 3 章:掌握 SIP 注册和基本会话管理

  1. 进阶篇(第 4-6 章):
  • 第 4 章:深入学习 JSSIP 的 API 细节

  • 第 5 章:实现完整的音视频通话功能

  • 第 6 章:了解即时消息和高级会话管理

  1. 高级篇(第 7-9 章):
  • 第 7 章:学习安全机制和最佳实践

  • 第 8 章:优化 JSSIP 应用的性能

  • 第 9 章:构建完整的实时通信应用

  1. 附录
  • 附录 A:SIP 消息参考

  • 附录 B:API 快速参考

  • 附录 C:常见问题解答

1.5.3 实践建议

学习 JSSIP 最好的方法是通过实践。建议读者:

  1. 动手编写代码:不要只是阅读,要实际编写和测试每一个示例。

  2. 搭建测试环境:设置一个 SIP 服务器(如 FreeSWITCH、Asterisk)进行实际测试,体验真实的通信场景。

  3. 尝试修改代码:在示例基础上进行修改和扩展,观察结果变化,加深理解。

  4. 参与开源社区:访问 JSSIP 的 GitHub 仓库和社区论坛,学习他人的经验,分享自己的成果。

  5. 阅读官方文档:JSSIP 的官方文档(http://www.jssip.net/documentation/)是最权威的参考资料,应经常查阅。

通过以上方法,你将能够系统地掌握 JSSIP,并能够开发出功能丰富的实时通信应用。

二、JSSIP 核心组件

2.1 JSSIP 体系结构

JSSIP 采用模块化设计,由多个核心组件构成,每个组件负责特定的功能。理解这些组件的结构和关系,对于有效使用 JSSIP 至关重要。

2.1.1 主要组件概述

JSSIP 的核心组件包括:

  1. JsSIP.UA(User Agent):表示 SIP 用户代理,是 JSSIP 的核心类,负责与 SIP 服务器通信,管理注册状态和会话。

  2. JsSIP.Socket:表示传输层接口,负责与 SIP 服务器建立连接,发送和接收 SIP 消息。JSSIP 提供了 WebSocket 和 Node.js 专用的 Socket 实现。

  3. JsSIP.Message:表示 SIP 消息,包含请求或响应的所有信息,如方法、状态码、头部和内容。

  4. JsSIP.RTCSession:表示基于 WebRTC 的多媒体会话,管理音视频流和数据通道(5)。

  5. JsSIP.Registrator:管理 SIP 注册过程,处理注册请求和响应(5)。

  6. JsSIP.Options:用于发送 OPTIONS 请求,查询服务器能力。

  7. JsSIP.DTMF:处理 DTMF(双音多频)信号,用于电话拨号和交互式语音响应系统。

这些组件协同工作,提供了完整的 SIP 客户端功能,从底层网络通信到高层会话管理,一应俱全。

2.1.2 组件关系图
+----------------+\|     JsSIP      |+----------------+\|   - UA         |\|   - Socket     |\|   - Message    |\|   - RTCSession |\|   - Registrator|+----------------+&#x20;     \|   |   |&#x20;     v   v   v+---------+ +---------+ +------------+\|  Socket | |  Message| |  RTCSession|+---------+ +---------+ +------------+&#x20;     \|                |+------v--------+ +-----v------+\|  WebSocket   | |  Node.js  |\|  Interface   | |  Interface|+-------------+ +-----------+
2.1.3 工作流程概述

JSSIP 的典型工作流程如下:

  1. 初始化:创建JsSIP.UA实例,配置 SIP 服务器地址、用户凭证等参数。

  2. 连接:UA 通过 Socket 与 SIP 服务器建立 WebSocket 连接。

  3. 注册:UA 向 SIP 服务器发送 REGISTER 请求,注册用户身份。

  4. 会话建立:通过 INVITE 请求发起会话,协商媒体参数,建立 RTCPeerConnection。

  5. 数据传输:通过 RTCPeerConnection 交换音视频数据或任意应用数据。

  6. 会话管理:处理会话中的各种事件(如来电、挂断、媒体流变化)。

  7. 会话终止:通过 BYE 请求结束会话。

  8. 注销:发送 UNREGISTER 请求,取消注册状态。

  9. 断开连接:关闭 WebSocket 连接,释放资源。

2.2 JsSIP.UA 类详解

JsSIP.UA是 JSSIP 的核心类,代表 SIP 用户代理,负责与 SIP 服务器通信,管理注册状态和会话。本节将详细介绍JsSIP.UA的创建、配置和基本用法。

2.2.1 创建 UA 实例

要创建JsSIP.UA实例,需要提供一个配置对象。配置对象包含了连接到 SIP 服务器所需的基本信息。

// 创建WebSocket接口const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');// 配置对象const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'superpassword'};// 创建UA实例const ua = new JsSIP.UA(configuration);

配置参数说明

  • sockets:一个数组,包含用于连接 SIP 服务器的 Socket 实例。对于 WebSocket 传输,通常只需要一个 Socket。

  • uri:用户的 SIP URI,表示用户的身份,格式为sip:username@domain

  • password:用户的认证密码。

  • register(可选):布尔值,指示 UA 是否在启动时自动注册。默认为true

  • outbound_proxy_set(可选):出站代理地址,用于路由 SIP 消息。

  • session_timers(可选):布尔值,指示是否启用会话计时器。默认为true

  • connection_recovery_max_interval(可选):连接恢复的最大时间间隔(毫秒),用于自动重连机制(10)。

完整的配置参数列表请参考 JSSIP 官方文档。

2.2.2 UA 的生命周期管理

JsSIP.UA提供了以下方法管理其生命周期:

  1. start():连接到信令服务器,并恢复之前的状态(如果之前停止过)。如果配置中的register参数为true,则会向 SIP 域注册。
ua.start();
  1. stop():保存当前注册状态,优雅地注销并终止活动会话(如果有的话),然后断开与信令服务器的连接。
ua.stop();
  1. register():手动触发注册过程。
ua.register();
  1. unregister(options = null):手动触发注销过程。参数options是一个对象,包含all属性(布尔值),指示是否注销同一 SIP 用户的所有绑定(5)。
const options = { all: true };ua.unregister(options);
  1. isRegistered():检查 UA 是否已注册。
if (ua.isRegistered()) {&#x20;   console.log('已注册');} else {&#x20;   console.log('未注册');}
  1. isConnected():检查传输是否已连接。
if (ua.isConnected()) {&#x20;   console.log('已连接');} else {&#x20;   console.log('未连接');}
2.2.3 UA 事件监听

JsSIP.UA定义了一系列事件,用于通知应用程序各种状态变化。可以通过on()方法注册事件处理函数。

常用事件

  1. connecting:在传输连接尝试时触发。
ua.on('connecting', function(data) {&#x20;   console.log('正在连接到:', data.socket.url);});
  1. connected:传输连接建立后触发。
ua.on('connected', function(data) {&#x20;   console.log('已连接到:', data.socket.url);});
  1. disconnected:传输连接断开时触发。
ua.on('disconnected', function(data) {&#x20;   if (data.error) {&#x20;       console.error('连接断开(错误):', data.reason);&#x20;   } else {&#x20;       console.log('连接断开:', data.reason);&#x20;   }});
  1. registered:注册成功后触发。
ua.on('registered', function(data) {&#x20;   console.log('注册成功:', data.response.status\_code);});
  1. unregistered:注销后触发。
ua.on('unregistered', function(data) {&#x20;   console.log('注销成功');});
  1. registrationFailed:注册失败时触发。
ua.on('registrationFailed', function(data) {&#x20;   console.error('注册失败:', data.cause);});
  1. registrationExpiring:注册即将过期时触发(在过期前几秒钟)。
ua.on('registrationExpiring', function() {&#x20;   console.log('注册即将过期,正在自动重新注册...');&#x20;   // 如果未处理此事件,JSSIP将自动重新注册});
  1. newRTCSession:收到新的 RTCSession(如来电)或发起新的 RTCSession 时触发。
ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       console.log('收到来电');&#x20;       // 处理来电&#x20;   } else {&#x20;       console.log('发起新会话');&#x20;   }});
  1. newMessage:收到新的 MESSAGE 请求(即时消息)时触发。
ua.on('newMessage', function(data) {&#x20;   console.log('收到新消息:', data.message.body);});
2.2.4 发送 SIP 请求

JsSIP.UA提供了多种方法发送不同类型的 SIP 请求:

  1. call(target, options = null):发起多媒体呼叫。
const options = {&#x20;   mediaConstraints: { audio: true, video: true },&#x20;   eventHandlers: {&#x20;       confirmed: function() {&#x20;           console.log('呼叫已确认');&#x20;       }&#x20;   }};const session = ua.call('sip:bob@example.com', options);
  1. sendMessage(target, body, options = null):发送即时消息。
const options = {&#x20;   contentType: 'text/plain',&#x20;   eventHandlers: {&#x20;       succeeded: function() {&#x20;           console.log('消息发送成功');&#x20;       }&#x20;   }};ua.sendMessage('sip:bob@example.com', 'Hello, Bob!', options);
  1. sendInfo(contentType, body = null, options = null):发送 INFO 请求,用于传输带外信息。
ua.sendInfo('application/dtmf-relay', '1', {&#x20;   extraHeaders: \['X-Custom-Header: value']});
  1. refer(target, options = null):发送 REFER 请求,用于呼叫转移。
const options = {&#x20;   eventHandlers: {&#x20;       referAccepted: function() {&#x20;           console.log('呼叫转移已接受');&#x20;       }&#x20;   }};ua.refer('sip:carol@example.com', options);
  1. options():发送 OPTIONS 请求,查询服务器能力。
ua.options()&#x20;   .then(function(response) {&#x20;       console.log('服务器支持的方法:', response.headers\['allow']);&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('OPTIONS请求失败:', error);&#x20;   });

2.3 传输层接口(Socket)

传输层接口(Socket)负责与 SIP 服务器建立连接,发送和接收 SIP 消息。JSSIP 提供了多种 Socket 实现,以适应不同的环境。

2.3.1 WebSocket 接口

在浏览器环境中,通常使用 WebSocket 传输 SIP 消息。JSSIP 提供了JsSIP.WebSocketInterface类来实现这一功能。

创建 WebSocket 接口

// 使用WSS(安全WebSocket)const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');// 使用WS(不安全WebSocket)const insecureSocket = new JsSIP.WebSocketInterface('ws://sip.example.com:5066');

WebSocket 接口事件

JsSIP.WebSocketInterface继承自JsSIP.Socket,提供了以下关键事件:

  1. onconnect:连接建立成功时触发。
secureSocket.onconnect = function() {&#x20;   console.log('WebSocket连接成功');};
  1. ondisconnect:连接断开时触发。
secureSocket.ondisconnect = function(error) {&#x20;   if (error) {&#x20;       console.error('WebSocket连接断开(错误):', error);&#x20;   } else {&#x20;       console.log('WebSocket连接断开');&#x20;   }};
  1. onmessage:收到消息时触发。
secureSocket.onmessage = function(message) {&#x20;   console.log('收到SIP消息:', message);&#x20;   // 处理接收到的SIP消息};

发送消息

使用send()方法发送 SIP 消息:

const message = new JsSIP.Message('OPTIONS sip:example.com SIP/2.0');secureSocket.send(message);
2.3.2 Node.js 专用接口

在 Node.js 环境中,JSSIP 提供了专门的 Socket 实现,使用websocket模块代替浏览器内置的 WebSocket API。需要安装额外的包:

npm install jssip-node-websocket

使用 Node.js WebSocket 接口

const NodeWebSocket = require('jssip-node-websocket');const socket = new NodeWebSocket('wss://sip.example.com');// 设置事件处理函数socket.onconnect = function() {&#x20;   console.log('Node.js WebSocket连接成功');};socket.ondisconnect = function(error) {&#x20;   console.log('Node.js WebSocket连接断开:', error);};socket.onmessage = function(message) {&#x20;   console.log('收到消息:', message);};// 连接到服务器socket.connect();

Node.js 专用选项

NodeWebSocket构造函数接受一个可选的选项对象,用于配置底层的 WebSocket 连接:

const options = {&#x20;   rejectUnauthorized: true, // 是否验证服务器证书(默认true)&#x20;   ca: fs.readFileSync('ca.crt'), // 自定义CA证书&#x20;   headers: {&#x20;       'X-Custom-Header': 'value'&#x20;   }};const socket = new NodeWebSocket('wss://sip.example.com', options);
2.3.3 自定义传输接口

JSSIP 允许开发者实现自定义的传输接口,以支持其他传输协议(如 HTTP 长轮询、UDP 等)。要创建自定义传输接口,需要实现JsSIP.Socket接口的以下方法:

  1. connect():建立连接。

  2. disconnect():断开连接。

  3. send(message):发送 SIP 消息。

  4. close():关闭连接。

以下是一个简单的自定义 Socket 示例:

class CustomSocket extends JsSIP.Socket {&#x20;   constructor(url) {&#x20;       super();&#x20;       this.url = url;&#x20;       this.connected = false;&#x20;   }&#x20;  &#x20;&#x20;   connect() {&#x20;       // 实现连接逻辑&#x20;       this.connected = true;&#x20;       this.emit('connect');&#x20;   }&#x20;  &#x20;&#x20;   disconnect() {&#x20;       if (this.connected) {&#x20;           this.connected = false;&#x20;           this.emit('disconnect');&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   send(message) {&#x20;       if (this.connected) {&#x20;           console.log('发送消息:', message.toString());&#x20;           this.emit('message', message);&#x20;       } else {&#x20;           throw new Error('Socket未连接');&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   close() {&#x20;       this.disconnect();&#x20;   }}

2.4 消息处理与解析

JSSIP 提供了JsSIP.Message类来表示 SIP 消息,包括请求和响应。本节将介绍如何创建、解析和处理 SIP 消息。

2.4.1 创建 SIP 消息

创建 SIP 请求

// 使用构造函数const request = new JsSIP.Message('INVITE sip:bob@example.com SIP/2.0');request.headers.set('From', 'sip:alice@example.com');request.headers.set('To', 'sip:bob@example.com');request.headers.set('Call-ID', '12345');request.headers.set('CSeq', '1 INVITE');request.headers.set('Contact', 'sip:alice@example.com');request.setBody('...SDP内容...');// 使用工厂方法const request = JsSIP.Message.createRequest('INVITE', 'sip:bob@example.com', {&#x20;   from: 'sip:alice@example.com',&#x20;   to: 'sip:bob@example.com',&#x20;   callId: '12345',&#x20;   cSeq: '1 INVITE',&#x20;   contact: 'sip:alice@example.com'});

创建 SIP 响应

// 使用构造函数const response = new JsSIP.Message('SIP/2.0 200 OK');response.headers.set('From', 'sip:alice@example.com');response.headers.set('To', 'sip:bob@example.com');response.headers.set('Call-ID', '12345');response.headers.set('CSeq', '1 INVITE');response.setBody('...SDP内容...');// 使用工厂方法const response = JsSIP.Message.createResponse(200, 'OK', request, {&#x20;   to: 'sip:bob@example.com',&#x20;   contact: 'sip:bob@example.com'});
2.4.2 解析 SIP 消息

JSSIP 可以自动解析接收到的 SIP 消息字符串:

const messageString = \`INVITE sip:bob@example.com SIP/2.0From: sip:alice@example.comTo: sip:bob@example.comCall-ID: 12345CSeq: 1 INVITEContent-Type: application/sdpContent-Length: 142v=0o=alice 2890844526 2890844526 IN IP4 192.168.1.100...\`;const message = JsSIP.Message.parse(messageString);console.log('方法:', message.method);console.log('URL:', message.url);console.log('头部:', message.headers);console.log('内容:', message.body);
2.4.3 消息头部操作

JSSIP 提供了方便的方法来操作 SIP 消息头部:

设置头部

message.headers.set('From', 'sip:alice@example.com');message.headers.set('To', 'sip:bob@example.com;tag=123');

获取头部

const fromHeader = message.headers.get('From');console.log('From头部:', fromHeader);const toHeader = message.headers.get('To');console.log('To头部:', toHeader);

删除头部

message.headers.delete('User-Agent');

检查头部是否存在

if (message.headers.has('Content-Length')) {&#x20;   console.log('存在Content-Length头部');}

遍历所有头部

message.headers.forEach(function(value, name) {&#x20;   console.log(name + ': ' + value);});
2.4.4 消息事件处理

JsSIP.Message类定义了一系列事件,用于通知应用程序消息处理的不同阶段:

发送消息事件

const request = new JsSIP.Message('INVITE sip:bob@example.com SIP/2.0');request.on('sent', function() {&#x20;   console.log('消息已发送');});request.on('failed', function(error) {&#x20;   console.error('消息发送失败:', error);});request.on('success', function(response) {&#x20;   console.log('收到成功响应:', response.status\_code);});request.send();

接收消息事件

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   message.on('received', function() {&#x20;       console.log('收到消息:', message.method);&#x20;   });&#x20;  &#x20;&#x20;   message.on('processed', function() {&#x20;       console.log('消息已处理');&#x20;   });&#x20;  &#x20;&#x20;   // 发送响应&#x20;   message.respond(200, 'OK');});
2.4.5 消息体处理

SIP 消息可以包含各种类型的内容,如 SDP、XML、JSON 等。JSSIP 提供了以下方法处理消息体:

设置消息体

const sdp = \`v=0o=alice 2890844526 2890844526 IN IP4 192.168.1.100s=会话c=IN IP4 192.168.1.100t=0 0m=audio 49170 RTP/AVP 0a=rtpmap:0 PCMU/8000\`;message.setBody(sdp);message.headers.set('Content-Type', 'application/sdp');message.headers.set('Content-Length', Buffer.byteLength(sdp));

获取消息体

const body = message.body;console.log('消息体:', body);

解析 SDP 内容

const sdpParser = new JsSIP.SDP(sipMessage.body);const mediaSection = sdpParser.media\[0];console.log('媒体类型:', mediaSection.media);console.log('端口:', mediaSection.port);console.log('协议:', mediaSection.protocol);console.log('格式:', mediaSection.format);

2.5 多媒体会话管理

JSSIP 通过JsSIP.RTCSession类管理基于 WebRTC 的多媒体会话,支持音视频通话和数据通道。本节将介绍如何创建和管理多媒体会话。

2.5.1 创建 RTCSession

RTCSession可以通过JsSIP.UAcall()方法创建,用于发起出站会话;或者通过newRTCSession事件接收,用于处理入站会话。

发起出站会话

const options = {&#x20;   mediaConstraints: { audio: true, video: true },&#x20;   eventHandlers: {&#x20;       confirmed: function() {&#x20;           console.log('会话已确认');&#x20;       }&#x20;   }};const session = ua.call('sip:bob@example.com', options);

处理入站会话

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       const incomingSession = data.session;&#x20;      &#x20;&#x20;       // 自动应答(示例)&#x20;       incomingSession.answer({&#x20;           mediaConstraints: { audio: true, video: true }&#x20;       });&#x20;   }});
2.5.2 RTCSession 事件

JsSIP.RTCSession定义了丰富的事件,用于跟踪会话的各个阶段:

  1. connecting:在初始 INVITE 请求或 “200 OK” 响应传输前触发。
session.on('connecting', function(data) {&#x20;   console.log('会话正在连接...');});
  1. sending:在发送初始 INVITE 之前触发(仅用于出站会话)。
session.on('sending', function(data) {&#x20;   console.log('正在发送INVITE请求...');});
  1. progress:在接收或生成对 INVITE 请求的 1xx 响应时触发。
session.on('progress', function(data) {&#x20;   console.log('收到临时响应:', data.response.status\_code);});
  1. accepted:当会话被接受(收到 2xx 响应)时触发。
session.on('accepted', function(data) {&#x20;   console.log('会话已接受');});
  1. confirmed:当会话确认(ACK 收到 / 发送)时触发。
session.on('confirmed', function(data) {&#x20;   console.log('会话已确认');});
  1. ended:当已建立的会话结束时触发。
session.on('ended', function(data) {&#x20;   console.log('会话结束,原因:', data.cause);&#x20;   // 清理资源});
  1. failed:当会话无法建立时触发。
session.on('failed', function(data) {&#x20;   console.error('会话失败,原因:', data.cause);});
  1. peerconnection:当底层RTCPeerConnection创建后触发。
session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 添加媒体流&#x20;   peerConnection.addStream(localStream);&#x20;  &#x20;&#x20;   // 监听远程流事件&#x20;   peerConnection.onaddstream = function(event) {&#x20;       remoteVideo.srcObject = event.stream;&#x20;   };});
  1. sdp:在处理 SDP 之前触发,允许修改 SDP 内容。
session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 修改SDP&#x20;       data.sdp = data.sdp.replace('UDP/TLS/RTP/SAVPF', 'RTP/SAVPF');&#x20;   }});
2.5.3 会话控制方法

JsSIP.RTCSession提供了丰富的方法来控制会话行为:

  1. answer(options = null):应答入站会话。
session.answer({&#x20;   mediaConstraints: { audio: true, video: false },&#x20;   pcConfig: {&#x20;       iceServers: \[{ urls: 'stun:stun.l.google.com:19302' }]&#x20;   }});
  1. terminate(options = null):终止会话。
session.terminate();
  1. sendDTMF(tone, options = null):发送 DTMF 信号。
session.sendDTMF('1234#', {&#x20;   duration: 160,&#x20;   interToneGap: 1200});
  1. sendInfo(contentType, body = null, options = null):发送 INFO 请求。
session.sendInfo('application/dtmf-relay', '1');
  1. hold(options = null, done = null):保持会话。
session.hold();
  1. unhold(options = null, done = null):恢复被保持的会话。
session.unhold();
  1. renegotiate():重新协商媒体参数。
session.renegotiate();
  1. mute(options = null):静音本地音频或视频。
session.mute({ audio: true, video: false });
  1. unmute(options = null):取消静音。
session.unmute({ audio: true, video: false });
  1. isMuted():检查是否静音。
const muted = session.isMuted();console.log('音频是否静音:', muted.audio);console.log('视频是否静音:', muted.video);
2.5.4 媒体流管理

JsSIP.RTCSession通过底层的RTCPeerConnection管理媒体流。以下是媒体流管理的关键方法:

获取本地媒体流

navigator.mediaDevices.getUserMedia({ audio: true, video: true })&#x20;   .then(function(stream) {&#x20;       localStream = stream;&#x20;       localVideo.srcObject = stream;&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('获取媒体设备失败:', error);&#x20;   });

添加本地媒体流到会话

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;   peerConnection.addStream(localStream);});

处理远程媒体流

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.onaddstream = function(event) {&#x20;       remoteVideo.srcObject = event.stream;&#x20;   };&#x20;  &#x20;&#x20;   peerConnection.ontrack = function(event) {&#x20;       if (event.track.kind === 'video') {&#x20;           remoteVideo.srcObject = event.streams\[0];&#x20;       } else if (event.track.kind === 'audio') {&#x20;           remoteAudio.srcObject = event.streams\[0];&#x20;       }&#x20;   };});

替换媒体流

function switchCamera() {&#x20;   // 获取新的视频流&#x20;   navigator.mediaDevices.getUserMedia({ audio: true, video: { facingMode: 'environment' } })&#x20;       .then(function(newStream) {&#x20;           // 替换旧流&#x20;           const peerConnection = session.connection;&#x20;           const tracks = localStream.getTracks();&#x20;          &#x20;&#x20;           tracks.forEach(function(track) {&#x20;               if (track.kind === 'video') {&#x20;                   peerConnection.removeTrack(track);&#x20;                   track.stop();&#x20;               }&#x20;           });&#x20;          &#x20;&#x20;           localStream = newStream;&#x20;           peerConnection.addStream(localStream);&#x20;           localVideo.srcObject = localStream;&#x20;       })&#x20;       .catch(function(error) {&#x20;           console.error('切换摄像头失败:', error);&#x20;       });}
2.5.5 数据通道

除了音视频流,JsSIP.RTCSession还支持通过RTCDataChannel传输任意应用数据:

创建数据通道

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 创建数据通道&#x20;   const dataChannel = peerConnection.createDataChannel('chat');&#x20;  &#x20;&#x20;   // 监听消息事件&#x20;   dataChannel.onmessage = function(event) {&#x20;       console.log('收到数据:', event.data);&#x20;   };&#x20;  &#x20;&#x20;   // 监听打开事件&#x20;   dataChannel.onopen = function() {&#x20;       dataChannel.send('Hello, peer!');&#x20;   };});

接收数据通道

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.ondatachannel = function(event) {&#x20;       const dataChannel = event.channel;&#x20;      &#x20;&#x20;       dataChannel.onmessage = function(event) {&#x20;           console.log('收到数据:', event.data);&#x20;       };&#x20;      &#x20;&#x20;       dataChannel.onopen = function() {&#x20;           dataChannel.send('Hello, sender!');&#x20;       };&#x20;   };});

三、SIP 注册与基本会话

3.1 SIP 注册流程

SIP 注册是用户代理(UA)向 SIP 服务器表明其可用性的过程,使服务器能够将传入的呼叫和消息路由到正确的终端。本节将详细介绍 SIP 注册的流程和在 JSSIP 中的实现。

3.1.1 注册过程概述

SIP 注册的基本流程如下:

  1. 用户代理发送 REGISTER 请求:UA 向 SIP 服务器发送 REGISTER 请求,包含用户身份(URI)、联系地址(Contact)和过期时间(Expires)。

  2. 服务器验证:SIP 服务器验证用户凭证(如用户名和密码)。

  3. 服务器响应

  • 如果验证成功,服务器返回 200 OK 响应,包含注册有效期。

  • 如果验证失败,服务器返回 401 Unauthorized 或 403 Forbidden 响应。

  1. 刷新注册:注册具有有效期(通常为 3600 秒),UA 需要在过期前发送新的 REGISTER 请求以刷新注册状态。

  2. 注销:用户退出时,UA 发送 Expires 为 0 的 REGISTER 请求,通知服务器注销。

3.1.2 JSSIP 中的注册实现

在 JSSIP 中,注册过程由JsSIP.UA类自动管理,但开发者可以通过配置和事件监听控制注册行为。

基本注册配置

const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'superpassword',&#x20;   register: true // 自动注册(默认值为true)};const ua = new JsSIP.UA(configuration);ua.start();

注册事件监听

ua.on('registered', function(data) {&#x20;   console.log('注册成功,有效期:', data.response.headers.get('Expires'));});ua.on('registrationFailed', function(data) {&#x20;   console.error('注册失败,原因:', data.cause);&#x20;  &#x20;&#x20;   if (data.response && data.response.status\_code === 401) {&#x20;       console.log('需要重新认证');&#x20;       // 处理认证失败&#x20;   }});ua.on('registrationExpiring', function() {&#x20;   console.log('注册即将过期,正在自动刷新...');&#x20;   // 如果未处理此事件,JSSIP将自动重新注册});

手动注册和注销

// 手动注册ua.register();// 手动注销ua.unregister();// 强制注销所有绑定ua.unregister({ all: true });
3.1.3 注册参数配置

JSSIP 提供了丰富的配置参数,用于定制注册行为:

注册有效期

const configuration = {&#x20;   // ...其他配置...&#x20;   expires: 3600 // 注册有效期(秒),默认3600};

自定义 Contact 头部

const configuration = {&#x20;   // ...其他配置...&#x20;   contact\_uri: 'sip:alice@example.com;transport=wss', // 自定义Contact URI&#x20;   contact\_params: { // Contact头部的额外参数&#x20;       'x-extension': '1001',&#x20;       'x-device': 'web'&#x20;   }};

注册重试策略

const configuration = {&#x20;   // ...其他配置...&#x20;   registration\_retries: 3, // 注册失败后的重试次数,默认3&#x20;   registration\_retry\_delay: 5000 // 重试间隔(毫秒),默认5000};

代理注册

如果需要通过代理服务器注册,可以配置outbound_proxy_set参数:

const configuration = {&#x20;   // ...其他配置...&#x20;   outbound\_proxy\_set: 'sip:proxy.example.com'};
3.1.4 处理认证挑战

当 SIP 服务器返回 401 Unauthorized 响应时,表示需要认证。JSSIP 会自动处理基于 Digest 认证的挑战,但开发者可以通过事件监听自定义认证处理。

认证事件监听

ua.on('registrationFailed', function(data) {&#x20;   if (data.response && data.response.status\_code === 401) {&#x20;       const challenge = data.response.headers.get('WWW-Authenticate');&#x20;       console.log('收到认证挑战:', challenge);&#x20;      &#x20;&#x20;       // 可以在此处提供自定义认证处理&#x20;   }});

自定义认证处理

JSSIP 使用JsSIP.Auth类处理认证。要自定义认证行为,可以创建自定义Auth实例:

const auth = new JsSIP.Auth({&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'superpassword',&#x20;   realm: 'example.com'});const configuration = {&#x20;   // ...其他配置...&#x20;   auth: auth};const ua = new JsSIP.UA(configuration);

摘要认证(Digest Authentication)

JSSIP 自动支持 RFC 2617 定义的摘要认证,无需额外配置。当收到 401 响应时,JSSIP 会根据服务器提供的 nonce 和 realm 计算摘要,并在后续请求中包含 Authorization 头部。

3.1.5 注册状态监控

JSSIP 提供了多种方法和事件,用于监控注册状态:

检查注册状态

if (ua.isRegistered()) {&#x20;   console.log('当前已注册');&#x20;   console.log('注册到期时间:', ua.getExpires());} else {&#x20;   console.log('当前未注册');}

注册状态变化事件

ua.on('registered', function() {&#x20;   console.log('注册成功');});ua.on('unregistered', function() {&#x20;   console.log('已注销');});ua.on('registrationFailed', function() {&#x20;   console.log('注册失败');});ua.on('registrationExpiring', function() {&#x20;   console.log('注册即将过期');});

注册重试机制

JSSIP 在注册失败后会自动重试,默认重试 3 次,每次间隔 5 秒。可以通过配置参数调整这些行为:

const configuration = {&#x20;   // ...其他配置...&#x20;   registration\_retries: 5, // 最大重试次数&#x20;   registration\_retry\_delay: 3000 // 重试间隔(毫秒)};

3.2 基本呼叫流程

呼叫建立是 SIP 的核心功能。本节将介绍 SIP 呼叫的基本流程,以及如何使用 JSSIP 实现简单的呼叫功能。

3.2.1 呼叫流程概述

基本的 SIP 呼叫流程包括以下步骤:

  1. INVITE 请求:主叫方发送 INVITE 请求,包含会话描述(SDP)。

  2. 100 Trying 响应:被叫方返回 100 Trying 临时响应,表示请求已收到,正在处理。

  3. 180 Ringing 响应:被叫方返回 180 Ringing 响应,表示正在振铃。

  4. 200 OK 响应:被叫方接受呼叫,返回 200 OK 响应,包含应答的 SDP。

  5. ACK 请求:主叫方发送 ACK 请求,确认收到 200 OK 响应。

  6. 媒体流传输:双方根据协商的 SDP 参数建立媒体连接,开始传输音视频数据。

  7. BYE 请求:任何一方发送 BYE 请求,终止会话。

  8. 200 OK 响应:对方返回 200 OK 响应,确认会话终止。

3.2.2 发起呼叫

在 JSSIP 中,使用JsSIP.UAcall()方法发起呼叫:

基本呼叫示例

// 获取本地媒体流navigator.mediaDevices.getUserMedia({ audio: true, video: true })&#x20;   .then(function(stream) {&#x20;       localStream = stream;&#x20;       localVideo.srcObject = stream;&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('获取媒体设备失败:', error);&#x20;   });// 发起呼叫const options = {&#x20;   mediaConstraints: { audio: true, video: true },&#x20;   eventHandlers: {&#x20;       confirmed: function() {&#x20;           console.log('呼叫已确认');&#x20;       },&#x20;       ended: function() {&#x20;           console.log('呼叫已结束');&#x20;       }&#x20;   }};const session = ua.call('sip:bob@example.com', options);

呼叫选项说明

  • mediaConstraints:媒体约束,指定所需的媒体类型(音频和视频)。

  • eventHandlers:事件处理函数,用于监听呼叫的不同阶段。

  • pcConfig:RTCPeerConnection 配置,如 ICE 服务器地址。

  • extraHeaders:要添加到 INVITE 请求的额外头部。

  • sessionTimersExpires:会话计时器间隔(秒)。

3.2.3 接收呼叫

使用newRTCSession事件监听入站呼叫:

接收呼叫示例

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       const incomingSession = data.session;&#x20;      &#x20;&#x20;       // 自动应答(示例)&#x20;       incomingSession.answer({&#x20;           mediaConstraints: { audio: true, video: true }&#x20;       });&#x20;      &#x20;&#x20;       // 监听会话结束事件&#x20;       incomingSession.on('ended', function() {&#x20;           console.log('来电已结束');&#x20;       });&#x20;   }});

处理不同类型的入站会话

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       const session = data.session;&#x20;      &#x20;&#x20;       // 检查请求类型&#x20;       if (session.direction === 'incoming') {&#x20;           console.log('收到入站呼叫');&#x20;          &#x20;&#x20;           // 可以在此处实现振铃提示、用户确认等逻辑&#x20;          &#x20;&#x20;           // 自动应答&#x20;           session.answer({&#x20;               mediaConstraints: { audio: true, video: true }&#x20;           });&#x20;       }&#x20;   }});
3.2.4 会话控制

JSSIP 提供了丰富的方法控制呼叫会话:

应答呼叫

session.answer({&#x20;   mediaConstraints: { audio: true, video: true },&#x20;   pcConfig: {&#x20;       iceServers: \[{ urls: 'stun:stun.l.google.com:19302' }]&#x20;   }});

终止呼叫

session.terminate();

保持呼叫

session.hold();

恢复呼叫

session.unhold();

静音 / 取消静音

// 静音本地音频session.mute({ audio: true });// 取消静音session.unmute({ audio: true });
3.2.5 媒体协商与处理

JSSIP 自动处理 SDP 协商,但开发者可以通过事件监听和回调函数自定义媒体协商过程。

媒体约束配置

const options = {&#x20;   mediaConstraints: {&#x20;       audio: true,&#x20;       video: {&#x20;           width: { min: 640, ideal: 1280 },&#x20;           height: { min: 480, ideal: 720 }&#x20;       }&#x20;   }};const session = ua.call('sip:bob@example.com', options);

自定义 SDP 处理

session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 修改SDP&#x20;       data.sdp = data.sdp.replace('UDP/TLS/RTP/SAVPF', 'RTP/SAVPF');&#x20;      &#x20;&#x20;       // 添加自定义属性&#x20;       data.sdp += '\na=my-custom-attribute: value';&#x20;   }});

处理媒体流

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 添加本地媒体流&#x20;   peerConnection.addStream(localStream);&#x20;  &#x20;&#x20;   // 监听远程媒体流&#x20;   peerConnection.onaddstream = function(event) {&#x20;       remoteVideo.srcObject = event.stream;&#x20;   };&#x20;  &#x20;&#x20;   // 监听媒体轨道变化&#x20;   peerConnection.ontrack = function(event) {&#x20;       if (event.track.kind === 'video') {&#x20;           remoteVideo.srcObject = event.streams\[0];&#x20;       } else if (event.track.kind === 'audio') {&#x20;           remoteAudio.srcObject = event.streams\[0];&#x20;       }&#x20;   };});
3.2.6 呼叫事件监听

JSSIP 提供了丰富的事件,用于跟踪呼叫的各个阶段:

呼叫建立事件

session.on('connecting', function() {&#x20;   console.log('呼叫正在连接...');});session.on('sending', function() {&#x20;   console.log('正在发送INVITE请求...');});session.on('progress', function(data) {&#x20;   console.log('收到临时响应:', data.response.status\_code);});session.on('accepted', function() {&#x20;   console.log('呼叫已接受');});session.on('confirmed', function() {&#x20;   console.log('呼叫已确认');});

呼叫终止事件

session.on('ended', function(data) {&#x20;   console.log('呼叫结束,原因:', data.cause);&#x20;   // 清理资源});session.on('failed', function(data) {&#x20;   console.error('呼叫失败,原因:', data.cause);});

媒体事件

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.oniceconnectionstatechange = function() {&#x20;       console.log('ICE连接状态:', peerConnection.iceConnectionState);&#x20;   };&#x20;  &#x20;&#x20;   peerConnection.onicegatheringstatechange = function() {&#x20;       console.log('ICE收集状态:', peerConnection.iceGatheringState);&#x20;   };});

3.3 即时消息与状态通知

除了音视频通话,SIP 还支持即时消息和状态通知功能。本节将介绍如何使用 JSSIP 实现这些功能。

3.3.1 发送即时消息

SIP 的 MESSAGE 方法用于发送即时消息,类似于电子邮件或短信。在 JSSIP 中,可以使用JsSIP.UAsendMessage()方法发送即时消息。

基本消息发送示例

const options = {&#x20;   contentType: 'text/plain',&#x20;   eventHandlers: {&#x20;       succeeded: function() {&#x20;           console.log('消息发送成功');&#x20;       },&#x20;       failed: function(error) {&#x20;           console.error('消息发送失败:', error);&#x20;       }&#x20;   }};ua.sendMessage('sip:bob@example.com', 'Hello, Bob!', options);

发送不同类型的消息

// 发送HTML格式的消息ua.sendMessage('sip:bob@example.com', '\<p>Hello, Bob!\</p>', {&#x20;   contentType: 'text/html'});// 发送XML消息const xml = '\<presence xmlns="urn:ietf:params:xml:ns:pidf">\<tuple>\<status>\<basic>open\</basic>\</status>\</tuple>\</presence>';ua.sendMessage('sip:bob@example.com', xml, {&#x20;   contentType: 'application/pidf+xml'});

发送包含自定义头部的消息

ua.sendMessage('sip:bob@example.com', 'Hello', {&#x20;   extraHeaders: \[&#x20;       'X-Custom-Header: value',&#x20;       'X-Another-Header: another value'&#x20;   ]});
3.3.2 接收即时消息

使用newMessage事件监听入站消息:

消息接收示例

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   console.log('收到消息来自:', message.headers.get('From'));&#x20;   console.log('消息内容:', message.body);&#x20;   console.log('内容类型:', message.headers.get('Content-Type'));&#x20;  &#x20;&#x20;   // 发送响应&#x20;   message.respond(200, 'OK');});

处理不同类型的消息

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   switch (message.headers.get('Content-Type')) {&#x20;       case 'text/plain':&#x20;           console.log('纯文本消息:', message.body);&#x20;           break;&#x20;      &#x20;&#x20;       case 'text/html':&#x20;           console.log('HTML消息:', message.body);&#x20;           break;&#x20;      &#x20;&#x20;       case 'application/pidf+xml':&#x20;           console.log('状态信息:', message.body);&#x20;           break;&#x20;      &#x20;&#x20;       default:&#x20;           console.log('未知类型消息:', message.body);&#x20;   }&#x20;  &#x20;&#x20;   // 发送响应&#x20;   message.respond(200, 'OK');});

处理错误消息

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   if (message.method === 'MESSAGE') {&#x20;       // 处理正常消息&#x20;   } else if (message.method === 'NOTIFY') {&#x20;       // 处理状态通知&#x20;   } else {&#x20;       // 未知方法,发送错误响应&#x20;       message.respond(405, 'Method Not Allowed');&#x20;   }});
3.3.3 状态通知

SIP 的 SUBSCRIBE 和 NOTIFY 方法用于实现状态通知功能,允许用户订阅其他用户的状态(如在线、忙碌、离开等)。

订阅状态示例

const options = {&#x20;   event: 'presence',&#x20;   expires: 3600,&#x20;   eventHandlers: {&#x20;       subscribed: function() {&#x20;           console.log('订阅成功');&#x20;       },&#x20;       failed: function(error) {&#x20;           console.error('订阅失败:', error);&#x20;       }&#x20;   }};ua.subscribe('sip:bob@example.com', options);

处理状态通知

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   if (message.method === 'NOTIFY' && message.headers.get('Event') === 'presence') {&#x20;       console.log('收到状态通知');&#x20;       console.log('状态内容:', message.body);&#x20;      &#x20;&#x20;       // 发送响应&#x20;       message.respond(200, 'OK');&#x20;   }});

取消订阅

ua.unsubscribe('sip:bob@example.com', {&#x20;   event: 'presence',&#x20;   expires: 0});
3.3.4 高级消息功能

JSSIP 还支持一些高级消息功能,如消息等待指示(MWI)和文件传输。

消息等待指示

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   if (message.headers.get('Event') === 'message-summary') {&#x20;       console.log('消息等待指示:', message.body);&#x20;      &#x20;&#x20;       // 解析消息摘要&#x20;       const summary = message.body;&#x20;       const parts = summary.split(';');&#x20;       const voicemailCount = parts\[0].split(':')\[1];&#x20;       const newVoicemailCount = parts\[1].split(':')\[1];&#x20;      &#x20;&#x20;       console.log('语音邮件总数:', voicemailCount);&#x20;       console.log('新语音邮件数:', newVoicemailCount);&#x20;      &#x20;&#x20;       // 发送响应&#x20;       message.respond(200, 'OK');&#x20;   }});

文件传输

通过发送包含文件内容的 MESSAGE 请求,可以实现简单的文件传输功能:

// 读取文件内容const fileReader = new FileReader();fileReader.onload = function(event) {&#x20;   const fileContent = event.target.result;&#x20;  &#x20;&#x20;   // 发送文件&#x20;   ua.sendMessage('sip:bob@example.com', fileContent, {&#x20;       contentType: 'application/octet-stream',&#x20;       extraHeaders: \[&#x20;           'Content-Disposition: attachment; filename="document.pdf"'&#x20;       ]&#x20;   });};fileReader.readAsArrayBuffer(fileInput.files\[0]);
3.3.5 消息事件处理

JSSIP 为消息提供了丰富的事件,用于跟踪消息的发送和接收过程:

发送消息事件

const message = ua.sendMessage('sip:bob@example.com', 'Hello');message.on('sent', function() {&#x20;   console.log('消息已发送');});message.on('success', function(response) {&#x20;   console.log('收到成功响应:', response.status\_code);});message.on('failure', function(response) {&#x20;   console.error('收到失败响应:', response.status\_code);});message.on('error', function(error) {&#x20;   console.error('消息处理错误:', error);});

接收消息事件

ua.on('newMessage', function(data) {&#x20;   const message = data.message;&#x20;  &#x20;&#x20;   message.on('received', function() {&#x20;       console.log('消息已收到');&#x20;   });&#x20;  &#x20;&#x20;   message.on('processed', function() {&#x20;       console.log('消息已处理');&#x20;   });&#x20;  &#x20;&#x20;   message.on('responded', function(response) {&#x20;       console.log('已发送响应:', response.status\_code);&#x20;   });});

四、高级 API 与应用场景

4.1 高级会话管理

在复杂的实时通信应用中,需要更精细地控制会话行为。本节将介绍 JSSIP 的高级会话管理功能。

4.1.1 会话转移与合并

SIP 支持会话转移(Call Transfer)和会话合并(Call Merging)功能,允许用户将正在进行的通话转移给第三方或合并多个通话。

盲转移(Blind Transfer)

盲转移是指在不与第三方协商的情况下直接将会话转移。在 JSSIP 中,可以使用refer()方法实现盲转移:

// 转移当前会话到sip:carol@example.comsession.refer('sip:carol@example.com');

咨询转移(Consultative Transfer)

咨询转移允许用户先与第三方建立新会话,然后将当前会话转移给第三方。

// 发起咨询呼叫const consultSession = ua.call('sip:carol@example.com');consultSession.on('confirmed', function() {&#x20;   // 建立咨询会话后,转移原会话&#x20;   originalSession.refer('sip:carol@example.com', {&#x20;       replaces: originalSession&#x20;   });&#x20;  &#x20;&#x20;   // 结束咨询会话&#x20;   consultSession.terminate();});

会话合并

会话合并(也称为会议桥接)允许用户将多个会话合并为一个多方会议。实现会话合并需要服务器支持,JSSIP 本身不提供会议桥接功能,但可以通过与支持会议的 SIP 服务器配合实现。

// 将当前会话加入会议session.sendInfo('application/dtmf-relay', 'Conference');
4.1.2 多设备同步

在多设备环境中,用户可能在多个终端(如手机、电脑、平板)上同时注册。JSSIP 提供了支持多设备同步的功能。

多 UA 实例

在同一应用中创建多个JsSIP.UA实例,代表不同的 SIP 账户或同一账户的不同设备:

// 创建第一个UA实例const ua1 = new JsSIP.UA({&#x20;   sockets: \[new JsSIP.WebSocketInterface('wss://sip.example.com')],&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'password1'});// 创建第二个UA实例const ua2 = new JsSIP.UA({&#x20;   sockets: \[new JsSIP.WebSocketInterface('wss://sip.example.com')],&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'password2'});

设备状态同步

通过订阅其他设备的状态,可以实现设备状态同步:

// 订阅其他设备的状态ua.subscribe('sip:alice@example.com;device=phone');// 处理状态通知ua.on('newMessage', function(data) {&#x20;   if (data.message.method === 'NOTIFY' && data.message.headers.get('Event') === 'presence') {&#x20;       console.log('收到设备状态通知:', data.message.body);&#x20;   }});

呼叫前转

通过修改注册 Contact 头部,可以实现呼叫前转:

const configuration = {&#x20;   // ...其他配置...&#x20;   contact\_uri: 'sip:alice@example.com;user=phone',&#x20;   contact\_params: {&#x20;       '+sip.instance': '\<urn:uuid:12345678-1234-5678-1234-567812345678>'&#x20;   }};
4.1.3 呼叫保持与恢复

JSSIP 提供了hold()unhold()方法来实现呼叫保持和恢复功能。

保持当前会话

// 保持本地会话session.hold();// 保持远程会话(需要对方支持)session.hold({ remote: true });

检查保持状态

const holdStatus = session.isOnHold();console.log('本地是否保持:', holdStatus.local);console.log('远程是否保持:', holdStatus.remote);

恢复被保持的会话

// 恢复本地会话session.unhold();// 恢复远程会话(需要对方支持)session.unhold({ remote: true });

保持事件监听

session.on('hold', function() {&#x20;   console.log('会话已保持');});session.on('unhold', function() {&#x20;   console.log('会话已恢复');});
4.1.4 媒体重协商

在会话过程中,可以动态修改媒体参数,如添加视频流、更换编解码器或调整带宽。这一过程称为媒体重协商。

重新协商媒体参数

// 修改媒体约束session.mediaConstraints = { audio: true, video: true };// 触发重协商session.renegotiate();

添加视频流

// 获取视频流navigator.mediaDevices.getUserMedia({ video: true })&#x20;   .then(function(videoStream) {&#x20;       // 将视频流添加到会话&#x20;       session.connection.addStream(videoStream);&#x20;      &#x20;&#x20;       // 触发重协商&#x20;       session.renegotiate();&#x20;   });

更换编解码器

session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 修改SDP以更换编解码器&#x20;       data.sdp = data.sdp.replace('opus/48000/2', 'pcmu/8000');&#x20;   }});session.renegotiate();

处理重协商事件

session.on('renegotiating', function() {&#x20;   console.log('正在重新协商媒体参数...');});session.on('renegotiated', function() {&#x20;   console.log('媒体参数重新协商成功');});session.on('renegotiationFailed', function(error) {&#x20;   console.error('媒体参数重新协商失败:', error);});
4.1.5 会话计时器管理

SIP 会话计时器(RFC 4028)用于管理会话的生命周期,确保会话在空闲一段时间后自动终止。JSSIP 支持会话计时器功能。

启用 / 禁用会话计时器

const configuration = {&#x20;   // ...其他配置...&#x20;   session\_timers: true // 默认值为true};

设置会话计时器间隔

// 发起呼叫时设置会话计时器const options = {&#x20;   sessionTimersExpires: 1800 // 会话计时器间隔(秒)};ua.call('sip:bob@example.com', options);

会话计时器事件

session.on('sessionTimerRefresh', function(expires) {&#x20;   console.log('会话计时器刷新,新的有效期:', expires);});session.on('sessionTimerUpdate', function(expires) {&#x20;   console.log('会话计时器更新,新的有效期:', expires);});session.on('sessionTimerTerminate', function() {&#x20;   console.log('会话计时器终止会话');});

4.2 高级媒体处理

JSSIP 与 WebRTC 紧密结合,提供了丰富的媒体处理功能。本节将介绍如何利用这些功能实现高级媒体处理。

4.2.1 多流管理

现代 WebRTC 应用通常需要处理多个媒体流,如主视频流、屏幕共享流和数据通道。JSSIP 允许在单个会话中管理多个媒体流。

添加多个视频流

// 获取主视频流navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } })&#x20;   .then(function(mainStream) {&#x20;       // 获取屏幕共享流&#x20;       return navigator.mediaDevices.getDisplayMedia({ video: true })&#x20;           .then(function(screenStream) {&#x20;               return { mainStream, screenStream };&#x20;           });&#x20;   })&#x20;   .then(function(streams) {&#x20;       session.on('peerconnection', function(data) {&#x20;           const peerConnection = data.peerconnection;&#x20;          &#x20;&#x20;           // 添加主视频流&#x20;           peerConnection.addStream(streams.mainStream);&#x20;          &#x20;&#x20;           // 添加屏幕共享流&#x20;           peerConnection.addStream(streams.screenStream);&#x20;       });&#x20;   });

切换视频流

function switchCamera() {&#x20;   // 获取新的视频流&#x20;   navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })&#x20;       .then(function(newStream) {&#x20;           // 替换旧流&#x20;           const peerConnection = session.connection;&#x20;           const tracks = localStream.getTracks();&#x20;          &#x20;&#x20;           tracks.forEach(function(track) {&#x20;               if (track.kind === 'video') {&#x20;                   peerConnection.removeTrack(track);&#x20;                   track.stop();&#x20;               }&#x20;           });&#x20;          &#x20;&#x20;           localStream = newStream;&#x20;           peerConnection.addStream(localStream);&#x20;           localVideo.srcObject = localStream;&#x20;       });}

选择性转发

选择性转发(Selective Forwarding Unit,SFU)是一种媒体处理技术,允许服务器选择性地将媒体流转发给不同的参与者。JSSIP 本身不提供 SFU 功能,但可以与支持 SFU 的服务器配合使用。

// 发送选择性转发指令session.sendInfo('application/sfu-control', 'subscribe:stream1');
4.2.2 音频处理

JSSIP 允许通过 Web Audio API 对音频流进行处理,如降噪、回声消除、音量调节等。

音量调节

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 获取音频轨道&#x20;   const audioTrack = localStream.getAudioTracks()\[0];&#x20;  &#x20;&#x20;   // 创建音频上下文&#x20;   const audioContext = new (window.AudioContext || window.webkitAudioContext)();&#x20;  &#x20;&#x20;   // 创建音量节点&#x20;   const gainNode = audioContext.createGain();&#x20;   gainNode.gain.value = 0.5; // 设置音量为50%&#x20;  &#x20;&#x20;   // 连接音频节点&#x20;   audioTrack.applyConstraints({ volume: 0.5 });});

音频效果处理

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 获取音频轨道&#x20;   const audioTrack = localStream.getAudioTracks()\[0];&#x20;  &#x20;&#x20;   // 创建音频上下文&#x20;   const audioContext = new AudioContext();&#x20;  &#x20;&#x20;   // 创建效果节点(如混响)&#x20;   const convolver = audioContext.createConvolver();&#x20;  &#x20;&#x20;   // 加载混响脉冲响应&#x20;   fetch('reverb.wav')&#x20;       .then(response => response.arrayBuffer())&#x20;       .then(buffer => audioContext.decodeAudioData(buffer))&#x20;       .then(audioBuffer => {&#x20;           convolver.buffer = audioBuffer;&#x20;       });&#x20;  &#x20;&#x20;   // 连接节点&#x20;   audioTrack.applyConstraints({ echoCancellation: false });&#x20;   audioTrack.applyConstraints({ noiseSuppression: false });&#x20;  &#x20;&#x20;   // 将效果应用于音频轨道&#x20;   audioTrack.addSink(audioContext.createMediaStreamDestination());});

音频设备切换

// 获取可用音频设备列表navigator.mediaDevices.enumerateDevices()&#x20;   .then(function(devices) {&#x20;       devices.forEach(function(device) {&#x20;           if (device.kind === 'audiooutput') {&#x20;               console.log('音频输出设备:', device.label);&#x20;           }&#x20;       });&#x20;   });// 切换音频输出设备function switchAudioOutput(deviceId) {&#x20;   const audioTrack = localStream.getAudioTracks()\[0];&#x20;   audioTrack.applyConstraints({ deviceId: { exact: deviceId } });}
4.2.3 数据通道

除了音视频流,JSSIP 还支持通过数据通道传输任意应用数据,如聊天消息、文件传输和控制信号。

创建数据通道

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 创建数据通道&#x20;   const dataChannel = peerConnection.createDataChannel('chat');&#x20;  &#x20;&#x20;   // 监听消息事件&#x20;   dataChannel.onmessage = function(event) {&#x20;       console.log('收到数据:', event.data);&#x20;   };&#x20;  &#x20;&#x20;   // 监听打开事件&#x20;   dataChannel.onopen = function() {&#x20;       dataChannel.send('Hello, peer!');&#x20;   };&#x20;  &#x20;&#x20;   // 监听关闭事件&#x20;   dataChannel.onclose = function() {&#x20;       console.log('数据通道已关闭');&#x20;   };});

接收数据通道

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.ondatachannel = function(event) {&#x20;       const dataChannel = event.channel;&#x20;      &#x20;&#x20;       dataChannel.onmessage = function(event) {&#x20;           console.log('收到数据:', event.data);&#x20;       };&#x20;      &#x20;&#x20;       dataChannel.onopen = function() {&#x20;           dataChannel.send('Hello, sender!');&#x20;       };&#x20;   };});

可靠与不可靠传输

数据通道支持可靠(类似 TCP)和不可靠(类似 UDP)传输模式:

// 创建可靠数据通道const reliableChannel = peerConnection.createDataChannel('reliable', {&#x20;   ordered: true,&#x20;   maxRetransmits: 3});// 创建不可靠数据通道const unreliableChannel = peerConnection.createDataChannel('unreliable', {&#x20;   ordered: false,&#x20;   maxRetransmits: 0});
4.2.4 屏幕共享

屏幕共享是现代视频会议应用的重要功能。JSSIP 支持通过 WebRTC 的getDisplayMedia() API 实现屏幕共享。

实现屏幕共享

// 请求屏幕共享navigator.mediaDevices.getDisplayMedia({ video: true })&#x20;   .then(function(screenStream) {&#x20;       // 将屏幕共享流添加到会话&#x20;       session.on('peerconnection', function(data) {&#x20;           const peerConnection = data.peerconnection;&#x20;           peerConnection.addStream(screenStream);&#x20;       });&#x20;      &#x20;&#x20;       // 显示本地屏幕共享预览&#x20;       screenVideo.srcObject = screenStream;&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('屏幕共享失败:', error);&#x20;   });

停止屏幕共享

function stopScreenSharing() {&#x20;   if (screenStream) {&#x20;       // 从会话中移除屏幕共享流&#x20;       session.connection.removeStream(screenStream);&#x20;      &#x20;&#x20;       // 停止媒体流&#x20;       screenStream.getTracks().forEach(function(track) {&#x20;           track.stop();&#x20;       });&#x20;      &#x20;&#x20;       // 清除预览&#x20;       screenVideo.srcObject = null;&#x20;   }}

选择性屏幕共享

// 选择特定窗口或显示器进行共享const options = {&#x20;   video: {&#x20;       cursor: 'always', // 显示鼠标光标&#x20;       displaySurface: 'window' // 或'display'、'browser'&#x20;   }};navigator.mediaDevices.getDisplayMedia(options)&#x20;   .then(function(screenStream) {&#x20;       // 处理屏幕共享流&#x20;   });
4.2.5 媒体编解码器控制

JSSIP 允许开发者控制使用的媒体编解码器,以满足特定应用需求。

设置首选编解码器

session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 重新排列编解码器顺序,将OPUS设为首选&#x20;       data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2')&#x20;                           .replace('pcmu/8000', 'pcmu/8000');&#x20;   }});

禁用特定编解码器

session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 移除VP8编解码器&#x20;       data.sdp = data.sdp.replace(/a=rtpmap:(\d+) VP8\\/90000\[\s\S]\*?\n/g, '');&#x20;   }});

自定义编解码器参数

session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 设置OPUS编解码器的比特率&#x20;       data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2/128000');&#x20;   }});

获取支持的编解码器

// 获取本地支持的编解码器const codecs = JsSIP.RTCSession.getSupportedCodecs();console.log('支持的编解码器:', codecs);

4.3 与其他系统集成

JSSIP 可以与多种 SIP 服务器和通信系统集成,本节将介绍如何与常见系统集成。

4.3.1 与 FreeSWITCH 集成

FreeSWITCH 是一款开源的软交换平台,广泛用于构建 IP 电话系统。以下是 JSSIP 与 FreeSWITCH 集成的要点。

连接配置

// FreeSWITCH的WebSocket地址const socket = new JsSIP.WebSocketInterface('ws://freeswitch.example.com:5066');// 用户代理配置const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:1000@freeswitch.example.com',&#x20;   password: 'ClueCon', // FreeSWITCH默认密码&#x20;   register: true};const ua = new JsSIP.UA(configuration);

处理 FreeSWITCH 的特殊要求

FreeSWITCH 要求在 Contact 头部包含 transport 参数:

const configuration = {&#x20;   // ...其他配置...&#x20;   contact\_uri: 'sip:1000@freeswitch.example.com;transport=ws'};

配置媒体服务器

在 FreeSWITCH 中,需要启用 WebSocket 监听:

\<!-- FreeSWITCH配置文件:/etc/freeswitch/sip\_profiles/external.xml -->\<param name="ws-binding" value="0.0.0.0:5066"/>\<param name="wss-binding" value="0.0.0.0:7443"/>

处理 DTMF 信号

FreeSWITCH 支持多种 DTMF 传输方式,默认使用 INFO 方法:

session.sendDTMF('1234');
4.3.2 与 Asterisk 集成

Asterisk 是另一款流行的开源软交换平台。以下是 JSSIP 与 Asterisk 集成的要点。

连接配置

const socket = new JsSIP.WebSocketInterface('wss://asterisk.example.com:7443');const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:1000@asterisk.example.com',&#x20;   password: 'secret',&#x20;   register: true};const ua = new JsSIP.UA(configuration);

Asterisk 配置

在 Asterisk 中,需要启用 WebSocket 支持:

\# 在sip.conf中\[general]transport=ws,wss\# 在extensions.conf中\[default]exten => 1000,1,Answer()exten => 1000,2,Playback(hello-world)exten => 1000,3,Hangup()

处理认证

Asterisk 默认使用 MD5 认证,JSSIP 会自动处理:

const configuration = {&#x20;   // ...其他配置...&#x20;   password: 'secret'};

处理呼叫转移

Asterisk 支持多种呼叫转移方式,包括盲转移和咨询转移:

// 盲转移session.refer('sip:2000@asterisk.example.com');// 咨询转移const consultSession = ua.call('sip:2000@asterisk.example.com');consultSession.on('confirmed', function() {&#x20;   originalSession.refer('sip:2000@asterisk.example.com');&#x20;   consultSession.terminate();});
4.3.3 与 Kamailio 集成

Kamailio(前身为 OpenSER)是一款高性能的 SIP 服务器,适合大规模部署。以下是 JSSIP 与 Kamailio 集成的要点。

连接配置

const socket = new JsSIP.WebSocketInterface('wss://kamailio.example.com:7443');const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:alice@kamailio.example.com',&#x20;   password: 'password',&#x20;   register: true};const ua = new JsSIP.UA(configuration);

Kamailio 配置

在 Kamailio 中,需要启用 WebSocket 模块:

\# kamailio.cfgloadmodule "usrloc.so"loadmodule "registrar.so"loadmodule "auth.so"loadmodule "websocket.so"modparam("websocket", "ws\_bind\_addr", "0.0.0.0")modparam("websocket", "ws\_bind\_port", 5066)modparam("websocket", "wss\_bind\_port", 7443)modparam("websocket", "ws\_max\_size", 65535)

处理认证

Kamailio 支持多种认证机制,包括摘要认证:

const configuration = {&#x20;   // ...其他配置...&#x20;   password: 'password'};

高级路由配置

Kamailio 可以根据多种条件进行路由,如用户代理字符串:

// 设置自定义用户代理字符串const configuration = {&#x20;   // ...其他配置...&#x20;   user\_agent: 'MyCustomUA/1.0'};
4.3.4 与 WebRTC 网关集成

JSSIP 可以与 WebRTC 网关(如 Enterprise WebRTC Gateway)集成,实现与传统电话网络的互通。

连接配置

const socket = new JsSIP.WebSocketInterface('wss://webrtc-gateway.example.com:7443');const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:1000@webrtc-gateway.example.com',&#x20;   password: 'password',&#x20;   register: true};const ua = new JsSIP.UA(configuration);

拨打 PSTN 电话

// 拨打普通电话号码(如+1234567890)const options = {&#x20;   mediaConstraints: { audio: true, video: false }};ua.call('sip:+1234567890@webrtc-gateway.example.com', options);

接收 PSTN 来电

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       const incomingSession = data.session;&#x20;      &#x20;&#x20;       // 自动应答&#x20;       incomingSession.answer({&#x20;           mediaConstraints: { audio: true }&#x20;       });&#x20;   }});

处理 DTMF 信号

在与 PSTN 通话时,DTMF 信号通常通过带外 INFO 方法传输:

session.sendDTMF('1234');
4.3.5 与 SIP-Trunk 提供商集成

JSSIP 可以与 SIP-Trunk 服务提供商(如 Twilio、Plivo、SignalWire)集成,实现与公共电话网络的连接。

SignalWire 集成示例

const socket = new JsSIP.WebSocketInterface('wss://sip.signalwire.com:5061');const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:1234567890@example.signalwire.com',&#x20;   password: 'your\_api\_token',&#x20;   register: true};const ua = new JsSIP.UA(configuration);

拨打 PSTN 电话

const options = {&#x20;   mediaConstraints: { audio: true },&#x20;   eventHandlers: {&#x20;       confirmed: function() {&#x20;           console.log('呼叫已接通');&#x20;       }&#x20;   }};ua.call('+1234567890', options);

接收 PSTN 来电

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       const session = data.session;&#x20;      &#x20;&#x20;       // 自动应答&#x20;       session.answer({&#x20;           mediaConstraints: { audio: true }&#x20;       });&#x20;      &#x20;&#x20;       // 播放提示音&#x20;       session.on('peerconnection', function(data) {&#x20;           const peerConnection = data.peerconnection;&#x20;           peerConnection.addStream(beepStream);&#x20;       });&#x20;   }});

使用高级功能

// 呼叫转移session.refer('+0987654321');// 会议桥接session.sendInfo('application/dtmf-relay', 'Conference');

4.4 移动应用开发

JSSIP 不仅可以用于 Web 应用,还可以用于移动应用开发。本节将介绍如何在移动环境中使用 JSSIP。

4.4.1 移动浏览器适配

移动浏览器与桌面浏览器在媒体设备访问和网络连接方面存在差异,需要特别适配。

移动设备媒体约束

// 优化移动设备的视频约束const constraints = {&#x20;   audio: true,&#x20;   video: {&#x20;       width: { ideal: 640 },&#x20;       height: { ideal: 480 },&#x20;       frameRate: { ideal: 30 }&#x20;   }};navigator.mediaDevices.getUserMedia(constraints)&#x20;   .then(function(stream) {&#x20;       // 处理媒体流&#x20;   });

网络连接优化

移动设备通常使用无线网络或蜂窝数据,网络稳定性较差。JSSIP 提供了连接恢复机制:

const configuration = {&#x20;   // ...其他配置...&#x20;   connection\_recovery\_max\_interval: 60000, // 最大重连间隔(毫秒)&#x20;   connection\_recovery\_min\_interval: 10000 // 最小重连间隔(毫秒)};

后台运行处理

移动浏览器在应用进入后台时可能会限制资源使用。可以通过事件监听检测应用状态变化:

document.addEventListener('visibilitychange', function() {&#x20;   if (document.hidden) {&#x20;       // 应用进入后台,暂停媒体流&#x20;       if (localStream) {&#x20;           localStream.getTracks().forEach(function(track) {&#x20;               track.enabled = false;&#x20;           });&#x20;       }&#x20;   } else {&#x20;       // 应用回到前台,恢复媒体流&#x20;       if (localStream) {&#x20;           localStream.getTracks().forEach(function(track) {&#x20;               track.enabled = true;&#x20;           });&#x20;       }&#x20;   }});
4.4.2 React Native 集成

JSSIP 可以与 React Native 框架集成,开发跨平台移动应用。需要使用react-native-webrtcjssip库。

安装依赖

npm install react-native-webrtc jssip

基本使用示例

import { RTCPeerConnection, MediaStream, MediaStreamTrack, getUserMedia } from 'react-native-webrtc';import JsSIP from 'jssip';// 创建WebSocket接口const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');// 配置对象const configuration = {&#x20;   sockets: \[socket],&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'superpassword'};// 创建UA实例const ua = new JsSIP.UA(configuration);// 获取媒体设备getUserMedia({ audio: true, video: true })&#x20;   .then(function(stream) {&#x20;       // 处理媒体流&#x20;   })&#x20;   .catch(function(error) {&#x20;       console.error('获取媒体设备失败:', error);&#x20;   });// 发起呼叫const options = {&#x20;   mediaConstraints: { audio: true, video: true },&#x20;   eventHandlers: {&#x20;       confirmed: function() {&#x20;           console.log('呼叫已确认');&#x20;       }&#x20;   }};const session = ua.call('sip:bob@example.com', options);

处理移动设备特性

// 处理设备旋转import { Dimensions } from 'react-native';const screenOrientation = Dimensions.get('window').width > Dimensions.get('window').height ? 'landscape' : 'portrait';// 切换摄像头function switchCamera() {&#x20;   getUserMedia({ audio: true, video: { facingMode: 'environment' } })&#x20;       .then(function(newStream) {&#x20;           // 替换旧流&#x20;           const tracks = localStream.getTracks();&#x20;           tracks.forEach(function(track) {&#x20;               if (track.kind === 'video') {&#x20;                   track.stop();&#x20;               }&#x20;           });&#x20;          &#x20;&#x20;           localStream = newStream;&#x20;           session.connection.addStream(localStream);&#x20;       });}
4.4.3 原生移动应用集成

JSSIP 也可以与原生移动应用(如 iOS 和 Android)集成,通过 WebView 组件运行 JavaScript 代码。

iOS 集成

在 iOS 中,可以使用 WKWebView 加载包含 JSSIP 代码的 HTML 页面:

import WebKitclass ViewController: UIViewController, WKUIDelegate {&#x20;   var webView: WKWebView!&#x20;  &#x20;&#x20;   override func viewDidLoad() {&#x20;       super.viewDidLoad()&#x20;      &#x20;&#x20;       // 创建WKWebView&#x20;       webView = WKWebView(frame: view.bounds)&#x20;       webView.uiDelegate = self&#x20;       view.addSubview(webView)&#x20;      &#x20;&#x20;       // 加载包含JSSIP代码的HTML页面&#x20;       if let url = Bundle.main.url(forResource: "jssip", withExtension: "html") {&#x20;           webView.loadFileURL(url, allowingReadAccessTo: url)&#x20;       }&#x20;   }}

Android 集成

在 Android 中,可以使用 WebView 组件加载包含 JSSIP 代码的 HTML 页面:

import android.webkit.WebView;public class MainActivity extends AppCompatActivity {&#x20;   private WebView webView;&#x20;  &#x20;&#x20;   @Override&#x20;   protected void onCreate(Bundle savedInstanceState) {&#x20;       super.onCreate(savedInstanceState);&#x20;       setContentView(R.layout.activity\_main);&#x20;      &#x20;&#x20;       // 获取WebView实例&#x20;       webView = findViewById(R.id.webview);&#x20;      &#x20;&#x20;       // 启用JavaScript&#x20;       webView.getSettings().setJavaScriptEnabled(true);&#x20;      &#x20;&#x20;       // 加载包含JSSIP代码的HTML页面&#x20;       webView.loadUrl("file:///android\_asset/jssip.html");&#x20;   }}

与原生功能交互

通过 JavaScript 与原生代码交互,可以实现设备功能调用和 UI 集成:

// JavaScript代码function callNativeFunction(functionName, params) {&#x20;   // 在iOS中&#x20;   if (window.webkit && window.webkit.messageHandlers) {&#x20;       window.webkit.messageHandlers\[functionName].postMessage(params);&#x20;   }&#x20;   // 在Android中&#x20;   else if (window.Android) {&#x20;       window.Android\[functionName]\(params);&#x20;   }}// 示例:调用原生摄像头callNativeFunction('openCamera', {});
4.4.4 移动设备优化

移动设备具有独特的特性和限制,需要进行专门的优化:

电池优化

// 优化电池使用session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 禁用不必要的ICE候选类型&#x20;   peerConnection.iceTransportPolicy = 'relay';&#x20;  &#x20;&#x20;   // 降低视频分辨率&#x20;   localStream.getVideoTracks()\[0].applyConstraints({&#x20;       width: 320,&#x20;       height: 240&#x20;   });});

网络优化

// 使用TURN服务器(如果NAT穿透失败)const configuration = {&#x20;   // ...其他配置...&#x20;   pcConfig: {&#x20;       iceServers: \[&#x20;           { urls: 'stun:stun.l.google.com:19302' },&#x20;           { urls: 'turn:turn.example.com', username: 'user', credential: 'password' }&#x20;       ]&#x20;   }};// 启用带宽估计session.connection.bandwidthEstimation = true;

资源管理

// 释放资源function releaseResources() {&#x20;   if (localStream) {&#x20;       localStream.getTracks().forEach(function(track) {&#x20;           track.stop();&#x20;       });&#x20;       localStream = null;&#x20;   }&#x20;  &#x20;&#x20;   if (session) {&#x20;       session.terminate();&#x20;       session = null;&#x20;   }&#x20;  &#x20;&#x20;   if (ua) {&#x20;       ua.stop();&#x20;       ua = null;&#x20;   }}// 监听应用终止事件window.addEventListener('beforeunload', releaseResources);
4.4.5 推送通知

在移动应用中,推送通知对于接收来电和消息至关重要。以下是实现推送通知的方法:

与 APNS/GCM 集成

// 注册推送通知function registerPushNotification() {&#x20;   // iOS使用APNs&#x20;   if (window.webkit && window.webkit.messageHandlers) {&#x20;       window.webkit.messageHandlers.registerForRemoteNotifications.postMessage({});&#x20;   }&#x20;   // Android使用FCM&#x20;   else if (window.Android) {&#x20;       window.Android.registerForPushNotifications();&#x20;   }}// 处理推送通知function handlePushNotification(payload) {&#x20;   if (payload.type === 'incomingCall') {&#x20;       // 显示来电通知&#x20;       showIncomingCallNotification(payload.number);&#x20;   } else if (payload.type === 'newMessage') {&#x20;       // 显示新消息通知&#x20;       showNewMessageNotification(payload.sender, payload.message);&#x20;   }}

后台唤醒

当应用处于后台时,推送通知可以唤醒应用处理事件:

// 监听应用激活事件document.addEventListener('visibilitychange', function() {&#x20;   if (document.hidden) {&#x20;       // 应用进入后台,释放资源&#x20;       releaseResources();&#x20;   } else {&#x20;       // 应用回到前台,重新连接&#x20;       if (ua) {&#x20;           ua.start();&#x20;       }&#x20;   }});

通知点击处理

用户点击通知时,应用应处理相应的操作:

// 处理通知点击function handleNotificationClick(notificationId) {&#x20;   switch (notificationId) {&#x20;       case 'incomingCall':&#x20;           // 处理来电&#x20;           handleIncomingCall();&#x20;           break;&#x20;       case 'newMessage':&#x20;           // 处理新消息&#x20;           handleNewMessage();&#x20;           break;&#x20;   }}

五、安全机制与最佳实践

5.1 传输层安全

在实时通信中,传输安全至关重要。本节将介绍如何使用 TLS 和 WebSocket 安全机制保护 SIP 通信。

5.1.1 TLS 配置

TLS(Transport Layer Security)是一种加密协议,用于保护网络通信的机密性和完整性。JSSIP 支持通过 TLS 加密 SIP 消息。

使用 WSS 传输

使用wss:// URL 创建 WebSocket 接口,启用 TLS 加密:

const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');

配置 TLS 参数

JSSIP 提供了多个配置参数,用于定制 TLS 行为:

const configuration = {&#x20;   // ...其他配置...&#x20;   ws\_secure: true, // 是否强制使用WSS(默认false)&#x20;   ws\_verify: true, // 是否验证服务器证书(默认true)&#x20;   ws\_ca\_certs: \['ca.crt'], // 自定义CA证书&#x20;   ws\_reject\_unauthorized: true // 是否拒绝无效证书(默认true)};

自定义证书验证

可以通过NodeWebSocket的选项对象自定义证书验证逻辑:

const options = {&#x20;   rejectUnauthorized: true,&#x20;   ca: fs.readFileSync('ca.crt'),&#x20;   requestCert: true,&#x20;   agent: new https.Agent({&#x20;       rejectUnauthorized: true,&#x20;       ca: \[fs.readFileSync('ca.crt')]&#x20;   })};const socket = new NodeWebSocket('wss://sip.example.com', options);
5.1.2 证书管理

证书是 TLS 的核心组件,正确管理证书对于安全通信至关重要。

获取服务器证书

生产环境中应使用由可信 CA 颁发的证书。对于开发和测试环境,可以使用自签名证书:

\# 生成自签名证书openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

配置服务器证书

在 SIP 服务器(如 FreeSWITCH)中配置证书:

\<!-- FreeSWITCH配置 -->\<param name="certificate" value="/path/to/cert.pem"/>\<param name="private-key" value="/path/to/key.pem"/>\<param name="dh-param" value="/path/to/dh.pem"/>

信任自签名证书

在客户端(浏览器或 Node.js)中信任自签名证书:

// Node.js环境const options = {&#x20;   rejectUnauthorized: false, // 不验证证书(不推荐在生产环境中使用)&#x20;   ca: fs.readFileSync('ca.crt') // 添加CA证书到信任列表};const socket = new NodeWebSocket('wss://sip.example.com', options);// 浏览器环境(需要用户手动信任证书)const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');
5.1.3 WebSocket 安全配置

WebSocket 提供了多种安全机制,可以与 TLS 结合使用,提高通信安全性。

安全 WebSocket 连接

始终使用wss://而不是ws://来建立 WebSocket 连接:

const secureSocket = new JsSIP.WebSocketInterface('wss://sip.example.com:7443');

自定义 WebSocket 头

可以添加自定义头部,增强安全性:

const socket = new JsSIP.WebSocketInterface('wss://sip.example.com', {&#x20;   headers: {&#x20;       'X-Secure-Header': 'value',&#x20;       'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'&#x20;   }});

防止 WebSocket 劫持

使用Sec-WebSocket-Protocol头指定 SIP 子协议:

const socket = new JsSIP.WebSocketInterface('wss://sip.example.com', {&#x20;   protocols: \['sip']});
5.1.4 证书验证与错误处理

正确验证服务器证书是防范中间人攻击的关键。

证书验证错误处理

// Node.js环境const options = {&#x20;   rejectUnauthorized: true,&#x20;   ca: fs.readFileSync('ca.crt')};const socket = new NodeWebSocket('wss://sip.example.com', options);socket.on('error', function(error) {&#x20;   if (error.code === 'UNABLE\_TO\_VERIFY\_LEAF\_SIGNATURE') {&#x20;       console.error('证书验证失败:', error);&#x20;       // 处理证书错误&#x20;   }});// 浏览器环境const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');socket.on('error', function(error) {&#x20;   console.error('WebSocket错误:', error);});

自定义证书验证逻辑

在 Node.js 中,可以自定义证书验证逻辑:

const options = {&#x20;   rejectUnauthorized: false,&#x20;   ca: \[fs.readFileSync('ca.crt')],&#x20;   checkServerIdentity: function(host, cert) {&#x20;       // 自定义验证逻辑&#x20;       if (cert.subject.CN !== host) {&#x20;           throw new Error('证书CN不匹配');&#x20;       }&#x20;   }};const socket = new NodeWebSocket('wss://sip.example.com', options);

安全配置建议

  1. 使用由可信 CA 颁发的证书

  2. 定期更新证书

  3. 启用证书吊销检查

  4. 使用强加密算法(如 TLS 1.3、ECDHE-ECDSA-AES256-GCM-SHA384)

  5. 禁用弱加密算法

  6. 实施证书钉扎(在关键应用中)

5.2 认证与授权

认证和授权是保护通信系统安全的基础机制。本节将介绍 SIP 中的认证机制及在 JSSIP 中的实现。

5.2.1 摘要认证

摘要认证(Digest Authentication)是 SIP 中最常用的认证机制,定义于 RFC 2617。JSSIP 自动支持摘要认证。

基本配置

只需提供用户名和密码,JSSIP 会自动处理摘要认证:

const configuration = {&#x20;   // ...其他配置...&#x20;   uri: 'sip:alice@example.com',&#x20;   password: 'superpassword'};

处理认证挑战

当收到 401 Unauthorized 响应时,JSSIP 会自动根据服务器提供的 nonce 和 realm 计算摘要:

ua.on('registrationFailed', function(data) {&#x20;   if (data.response.status\_code === 401) {&#x20;       console.log('收到认证挑战');&#x20;       // JSSIP会自动处理认证,无需额外代码&#x20;   }});

摘要认证流程

  1. 客户端发送 REGISTER 请求(无 Authorization 头部)

  2. 服务器返回 401 响应,包含 WWW-Authenticate 头部(包含 realm、nonce 等参数)

  3. 客户端计算摘要,在后续请求中包含 Authorization 头部

  4. 服务器验证摘要,返回 200 OK 或 403 Forbidden

5.2.2 证书认证

证书认证是一种更安全的认证方式,使用 X.509 证书验证客户端和服务器身份。

客户端证书配置

在 Node.js 环境中,可以配置客户端证书:

const options = {&#x20;   key: fs.readFileSync('client.key'),&#x20;   cert: fs.readFileSync('client.crt'),&#x20;   ca: fs.readFileSync('ca.crt')};const socket = new NodeWebSocket('wss://sip.example.com', options);

浏览器中的证书认证

浏览器通常通过用户交互选择客户端证书:

const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');socket.on('certificate', function(event) {&#x20;   // 浏览器自动处理证书选择});

证书认证流程

  1. 服务器在 TLS 握手过程中验证客户端证书

  2. 如果证书有效,服务器允许连接

  3. 客户端无需提供用户名和密码

5.2.3 访问控制

访问控制确保只有授权用户可以访问系统资源。JSSIP 可以与多种访问控制机制集成。

基于 IP 的访问控制

在 SIP 服务器中配置 IP 白名单:

\# Asterisk配置示例deny=0.0.0.0/0.0.0.0permit=192.168.1.0/24

基于用户的访问控制

在应用层实现授权逻辑:

ua.on('newRTCSession', function(data) {&#x20;   if (data.originator === 'remote') {&#x20;       // 检查用户权限&#x20;       if (!isAuthorized(data.session.remote\_identity)) {&#x20;           data.session.terminate();&#x20;           return;&#x20;       }&#x20;      &#x20;&#x20;       // 处理合法呼叫&#x20;       data.session.answer();&#x20;   }});

基于角色的访问控制

根据用户角色限制功能访问:

function isUserAuthorized(role) {&#x20;   return currentUser.roles.includes(role);}// 限制某些功能仅管理员可用if (isUserAuthorized('admin')) {&#x20;   session.sendInfo('application/admin-control', 'command');}
5.2.4 安全令牌与 OAuth

OAuth 是一种流行的授权框架,可以与 SIP 结合使用,实现更灵活的认证和授权。

使用 OAuth 令牌

// 获取OAuth令牌const accessToken = 'your\_access\_token';// 在SIP消息中包含Bearer令牌const configuration = {&#x20;   // ...其他配置...&#x20;   authorization: 'Bearer ' + accessToken};const ua = new JsSIP.UA(configuration);

OAuth 流程集成

// 实现OAuth授权码流程function getAccessToken() {&#x20;   return fetch('https://auth.example.com/oauth/token', {&#x20;       method: 'POST',&#x20;       headers: {&#x20;           'Content-Type': 'application/x-www-form-urlencoded'&#x20;       },&#x20;       body: 'grant\_type=authorization\_code\&code=AUTH\_CODE\&client\_id=CLIENT\_ID\&client\_secret=CLIENT\_SECRET\&redirect\_uri=REDIRECT\_URI'&#x20;   })&#x20;   .then(response => response.json())&#x20;   .then(data => data.access\_token);}// 使用获取的令牌创建UAgetAccessToken()&#x20;   .then(accessToken => {&#x20;       const configuration = {&#x20;           // ...其他配置...&#x20;           authorization: 'Bearer ' + accessToken&#x20;       };&#x20;      &#x20;&#x20;       const ua = new JsSIP.UA(configuration);&#x20;   });

令牌刷新

function refreshAccessToken(refreshToken) {&#x20;   return fetch('https://auth.example.com/oauth/token', {&#x20;       method: 'POST',&#x20;       headers: {&#x20;           'Content-Type': 'application/x-www-form-urlencoded'&#x20;       },&#x20;       body: 'grant\_type=refresh\_token\&refresh\_token=' + refreshToken + '\&client\_id=CLIENT\_ID\&client\_secret=CLIENT\_SECRET'&#x20;   })&#x20;   .then(response => response.json())&#x20;   .then(data => data.access\_token);}// 监听令牌过期事件setInterval(function() {&#x20;   if (tokenExpired) {&#x20;       refreshAccessToken(refreshToken)&#x20;           .then(newAccessToken => {&#x20;               // 更新UA配置&#x20;               ua.configuration.authorization = 'Bearer ' + newAccessToken;&#x20;           });&#x20;   }}, 30 \* 60 \* 1000); // 每30分钟检查一次

5.3 数据保护与隐私

保护通信内容和用户隐私是实时通信应用的重要责任。本节将介绍如何保护数据隐私。

5.3.1 SRTP 加密

SRTP(Secure Real-Time Transport Protocol)是一种加密协议,用于保护媒体流的机密性、完整性和身份验证。

启用 SRTP

JSSIP 默认启用 SRTP,无需额外配置:

const options = {&#x20;   // ...其他配置...&#x20;   mediaConstraints: { audio: true, video: true }};const session = ua.call('sip:bob@example.com', options);

SRTP 参数配置

可以通过pcConfig参数配置 SRTP:

const configuration = {&#x20;   // ...其他配置...&#x20;   pcConfig: {&#x20;       srtpKeyAgreement: true,&#x20;       srtpIceRestart: true&#x20;   }};

SRTP 与 DTLS

SRTP 通常与 DTLS(Datagram Transport Layer Security)结合使用,提供端到端加密:

session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   // 配置DTLS&#x20;   peerConnection.createDataChannel('dtls');&#x20;   peerConnection.ondatachannel = function(event) {&#x20;       const dataChannel = event.channel;&#x20;       dataChannel.onmessage = function(event) {&#x20;           // 处理DTLS消息&#x20;       };&#x20;   };});
5.3.2 数据匿名化

数据匿名化技术可以保护用户隐私,防止身份泄露。

匿名呼叫

JSSIP 支持匿名呼叫,隐藏呼叫者身份:

const options = {&#x20;   // ...其他配置...&#x20;   anonymous: true};const session = ua.call('sip:bob@example.com', options);

匿名化流程

  1. 客户端发送 INVITE 请求,包含 Privacy 头部(id=telephony)

  2. 服务器处理请求,隐藏呼叫者身份

  3. 服务器可能添加 P-Asserted-Identity 头部(如允许匿名)

伪身份

使用临时身份代替真实身份进行通信:

const configuration = {&#x20;   // ...其他配置...&#x20;   contact\_uri: 'sip:temp-123@example.com',&#x20;   contact\_params: {&#x20;       privacy: 'id=telephony'&#x20;   }};
5.3.3 敏感数据保护

保护敏感数据(如用户凭证、通话内容)是安全通信的基础。

敏感数据存储

// 避免在代码中硬编码敏感信息const configuration = {&#x20;   // ...其他配置...&#x20;   password: getPasswordFromSecureStorage() // 从安全存储获取密码};// 安全存储密码的示例function getPasswordFromSecureStorage() {&#x20;   // 使用安全的存储机制,如Web Crypto API&#x20;   return window.crypto.subtle.importKey(&#x20;       'raw',&#x20;       new TextEncoder().encode('password'),&#x20;       { name: 'AES-GCM' },&#x20;       false,&#x20;       \['encrypt', 'decrypt']&#x20;   );}

防止敏感数据泄露

// 避免在日志中记录敏感信息JsSIP.debug.enable('JsSIP:UA'); // 启用必要的日志JsSIP.debug.disable('JsSIP:MESSAGE'); // 禁用可能包含敏感信息的日志// 安全处理错误function handleError(error) {&#x20;   console.error('发生错误:', error.message); // 避免记录完整的错误对象}

数据销毁

// 销毁敏感数据function destroySensitiveData(data) {&#x20;   // 使用安全的内存清理方法&#x20;   if (typeof data === 'string') {&#x20;       const array = new Uint8Array(data.length);&#x20;       for (let i = 0; i < data.length; i++) {&#x20;           array\[i] = data.charCodeAt(i);&#x20;       }&#x20;       window.crypto.subtle.importKey(&#x20;           'raw',&#x20;           array,&#x20;           { name: 'AES-GCM' },&#x20;           false,&#x20;           \['encrypt']&#x20;       );&#x20;   }}
5.3.4 合规与隐私法规

实时通信应用需要遵守各种隐私法规,如 GDPR、CCPA 等。

数据主体权利

// 实现数据访问请求function handleDataAccessRequest(userId) {&#x20;   // 获取用户数据&#x20;   const userData = getUserData(userId);&#x20;  &#x20;&#x20;   // 提供数据访问&#x20;   return userData;}// 实现数据删除请求function handleDataDeletionRequest(userId) {&#x20;   // 删除用户数据&#x20;   deleteUserData(userId);&#x20;  &#x20;&#x20;   // 确认删除&#x20;   return true;}

日志管理

// 限制日志保留时间const LOG\_RETENTION\_PERIOD = 30 \* 24 \* 60 \* 60 \* 1000; // 30天function rotateLogs() {&#x20;   // 删除超过保留期的日志&#x20;   const now = Date.now();&#x20;   logs.forEach(function(log, index) {&#x20;       if (now - log.timestamp > LOG\_RETENTION\_PERIOD) {&#x20;           logs.splice(index, 1);&#x20;       }&#x20;   });}// 定期轮转日志setInterval(rotateLogs, 24 \* 60 \* 60 \* 1000); // 每天一次

数据跨境传输

// 实施数据本地化策略function storeDataLocally(data) {&#x20;   // 确保数据存储在用户所在地区&#x20;   if (currentRegion === 'EU') {&#x20;       // 存储在欧盟数据中心&#x20;       storeInEUDataCenter(data);&#x20;   } else {&#x20;       // 存储在本地数据中心&#x20;       storeInLocalDataCenter(data);&#x20;   }}// 限制数据跨境传输function transferDataOverseas(data, destination) {&#x20;   // 实施适当的保障措施&#x20;   if (isCrossBorderTransferRequired(data, destination)) {&#x20;       // 应用标准合同条款&#x20;       applyStandardContractualClauses(data);&#x20;      &#x20;&#x20;       // 传输数据&#x20;       transferData(data, destination);&#x20;   }}

5.4 安全最佳实践

本节将总结 JSSIP 应用开发中的安全最佳实践,帮助开发者构建安全可靠的实时通信系统。

5.4.1 通用安全建议

以下是适用于所有 JSSIP 应用的安全建议:

  1. 使用最新版本:定期更新 JSSIP 库,以获取最新的安全补丁和功能改进。

  2. 最小权限原则:为应用分配必要的最小权限,避免过度授权。

  3. 输入验证:验证所有输入数据,防止注入攻击。

  4. 输出编码:对所有输出数据进行适当编码,防止跨站脚本攻击。

  5. 错误处理:避免在错误消息中泄露敏感信息。

  6. 安全配置:使用安全的默认配置,如启用 TLS、禁用不必要的服务。

  7. 监控与日志:实施适当的监控和日志记录,及时发现安全事件。

  8. 安全测试:定期进行安全测试,包括渗透测试和漏洞扫描。

5.4.2 客户端安全措施

在客户端应用中,应采取以下安全措施:

  1. 安全存储:使用安全的方式存储敏感数据,如用户凭证。

  2. 权限管理:限制应用对敏感资源(如摄像头、麦克风)的访问。

  3. 防止代码注入:避免动态执行不可信代码。

  4. 内容安全策略:实施严格的 Content Security Policy(CSP),防止跨站脚本攻击。

  5. 证书验证:始终验证服务器证书,防止中间人攻击。

  6. 加密通信:使用 WSS 和 SRTP 保护所有通信。

  7. 输入过滤:过滤用户输入,防止恶意内容。

  8. 安全注销:在用户注销时清除所有敏感数据和会话信息。

5.4.3 服务器端安全措施

服务器端的安全同样重要,应采取以下措施:

  1. 认证与授权:实施强认证机制,如摘要认证或证书认证。

  2. 访问控制:限制对敏感资源的访问,基于用户角色和权限。

  3. 速率限制:防止暴力破解和拒绝服务攻击。

  4. 日志记录:记录所有安全相关事件,如登录尝试、异常活动。

  5. 漏洞管理:定期扫描和修复服务器漏洞。

  6. 补丁管理:及时应用安全补丁和更新。

  7. 监控与警报:实施实时监控,对异常活动发出警报。

  8. 安全配置:使用安全的服务器配置,如禁用弱加密算法。

5.4.4 安全开发流程

将安全融入开发流程的各个阶段:

  1. 安全需求分析:在项目开始阶段明确安全需求。

  2. 安全设计:在系统设计中融入安全机制。

  3. 安全编码:遵循安全编码规范,使用安全的开发工具。

  4. 安全测试:在开发过程中进行安全测试,如静态代码分析、动态测试。

  5. 安全部署:实施安全的部署流程,保护生产环境。

  6. 安全运维:建立安全的运维流程,监控和响应安全事件。

  7. 安全培训:对开发和运维团队进行安全培训。

  8. 安全审计:定期进行安全审计,评估系统安全性。

5.4.5 应急响应计划

即使采取了所有安全措施,安全事件仍可能发生。应制定应急响应计划:

  1. 事件分类:定义不同类型的安全事件及其严重程度。

  2. 报告机制:建立明确的事件报告流程。

  3. 响应团队:组建专门的安全响应团队。

  4. 响应流程:制定标准化的响应流程,包括遏制、调查、恢复和缓解措施。

  5. 沟通计划:确定如何与内部团队和外部相关方沟通安全事件。

  6. 恢复计划:制定系统恢复策略,确保业务连续性。

  7. 事后分析:对安全事件进行彻底分析,总结经验教训。

  8. 改进措施:根据事件分析结果,改进安全措施和流程。

六、性能优化与最佳实践

6.1 连接管理优化

连接管理是影响 JSSIP 应用性能的关键因素。本节将介绍如何优化 WebSocket 连接和 SIP 注册过程。

6.1.1 连接池技术

连接池技术可以复用已建立的 WebSocket 连接,减少连接建立和关闭的开销。

实现连接池

class ConnectionPool {&#x20;   constructor(url, maxConnections = 5) {&#x20;       this.url = url;&#x20;       this.maxConnections = maxConnections;&#x20;       this.connections = \[];&#x20;       this.availableConnections = \[];&#x20;       this.createConnections();&#x20;   }&#x20;  &#x20;&#x20;   createConnections() {&#x20;       for (let i = 0; i < this.maxConnections; i++) {&#x20;           const socket = new JsSIP.WebSocketInterface(this.url);&#x20;           this.connections.push(socket);&#x20;           this.availableConnections.push(socket);&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   getConnection() {&#x20;       if (this.availableConnections.length > 0) {&#x20;           return this.availableConnections.pop();&#x20;       } else {&#x20;           // 创建新连接(如果未达到最大连接数)&#x20;           if (this.connections.length < this.maxConnections) {&#x20;               const socket = new JsSIP.WebSocketInterface(this.url);&#x20;               this.connections.push(socket);&#x20;               return socket;&#x20;           } else {&#x20;               // 等待连接释放(示例实现,需更复杂的逻辑)&#x20;               return new Promise(resolve => {&#x20;                   setInterval(() => {&#x20;                       if (this.availableConnections.length > 0) {&#x20;                           resolve(this.availableConnections.pop());&#x20;                       }&#x20;                   }, 100);&#x20;               });&#x20;           }&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   releaseConnection(socket) {&#x20;       this.availableConnections.push(socket);&#x20;   }&#x20;  &#x20;&#x20;   closeAllConnections() {&#x20;       this.connections.forEach(socket => {&#x20;           socket.close();&#x20;       });&#x20;       this.connections = \[];&#x20;       this.availableConnections = \[];&#x20;   }}// 使用连接池const connectionPool = new ConnectionPool('wss://sip.example.com');const socket = connectionPool.getConnection();

连接池配置参数

const connectionPool = new ConnectionPool('wss://sip.example.com', {&#x20;   maxConnections: 5, // 最大连接数&#x20;   connectionTimeout: 30000, // 连接超时时间(毫秒)&#x20;   idleTimeout: 60000 // 空闲连接超时时间(毫秒)});

连接健康检查

setInterval(function() {&#x20;   connectionPool.availableConnections.forEach(socket => {&#x20;       if (socket.isClosed()) {&#x20;           connectionPool.connections.splice(connectionPool.connections.indexOf(socket), 1);&#x20;           connectionPool.availableConnections.splice(connectionPool.availableConnections.indexOf(socket), 1);&#x20;           connectionPool.createConnections(1);&#x20;       }&#x20;   });}, 30000); // 每30秒检查一次
6.1.2 连接恢复策略

在网络不稳定的环境中,连接恢复策略对应用的可用性至关重要。

连接恢复配置

JSSIP 提供了内置的连接恢复机制,可以通过配置参数调整:

const configuration = {&#x20;   // ...其他配置...&#x20;   connection\_recovery\_min\_interval: 5000, // 最小重连间隔(毫秒)&#x20;   connection\_recovery\_max\_interval: 60000, // 最大重连间隔(毫秒)&#x20;   connection\_recovery\_factor: 1.5 // 重连间隔增长因子};

指数退避算法

JSSIP 默认使用指数退避算法实现连接恢复:

// 示例:手动实现指数退避let retryCount = 0;const maxRetries = 5;function connect() {&#x20;   const socket = new JsSIP.WebSocketInterface('wss://sip.example.com');&#x20;  &#x20;&#x20;   socket.onconnect = function() {&#x20;       console.log('连接成功');&#x20;       retryCount = 0;&#x20;   };&#x20;  &#x20;&#x20;   socket.ondisconnect = function(error) {&#x20;       console.log('连接断开,将在', (2 \*\* retryCount) \* 1000, '毫秒后重试');&#x20;       if (retryCount < maxRetries) {&#x20;           retryCount++;&#x20;           setTimeout(connect, (2 \*\* retryCount) \* 1000);&#x20;       } else {&#x20;           console.error('连接失败,超过最大重试次数');&#x20;       }&#x20;   };&#x20;  &#x20;&#x20;   socket.connect();}

连接优先级

在多连接环境中,可以为不同类型的通信设置优先级:

class PriorityConnectionPool {&#x20;   constructor(url, maxConnections) {&#x20;       this.url = url;&#x20;       this.maxConnections = maxConnections;&#x20;       this.connections = \[];&#x20;       this.availableConnections = \[];&#x20;       this.createConnections();&#x20;   }&#x20;  &#x20;&#x20;   createConnections() {&#x20;       for (let i = 0; i < this.maxConnections; i++) {&#x20;           const socket = new JsSIP.WebSocketInterface(this.url);&#x20;           this.connections.push(socket);&#x20;           this.availableConnections.push(socket);&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   getConnection(priority = 0) {&#x20;       // 按优先级排序可用连接&#x20;       this.availableConnections.sort((a, b) => b.priority - a.priority);&#x20;      &#x20;&#x20;       if (this.availableConnections.length > 0) {&#x20;           const socket = this.availableConnections.pop();&#x20;           socket.priority = priority;&#x20;           return socket;&#x20;       } else {&#x20;           // 处理无可用连接的情况&#x20;           return null;&#x20;       }&#x20;   }&#x20;  &#x20;&#x20;   releaseConnection(socket) {&#x20;       this.availableConnections.push(socket);&#x20;   }}
6.1.3 注册优化

SIP 注册是应用启动时的关键操作,优化注册过程可以提高应用性能。

批量注册

在多账户环境中,可以批量处理注册请求:

function registerAccounts(accounts) {&#x20;   return Promise.all(accounts.map(account => {&#x20;       const configuration = {&#x20;           sockets: \[new JsSIP.WebSocketInterface(account.url)],&#x20;           uri: account.uri,&#x20;           password: account.password&#x20;       };&#x20;      &#x20;&#x20;       const ua = new JsSIP.UA(configuration);&#x20;       return ua.start();&#x20;   }));}

注册预取

在应用启动时预注册常用账户:

const accounts = \[&#x20;   { url: 'wss://sip.example.com', uri: 'sip:alice@example.com', password: 'password' },&#x20;   { url: 'wss://sip.example.com', uri: 'sip:bob@example.com', password: 'password' }];registerAccounts(accounts);

智能注册

根据用户行为预测需要注册的账户:

function predictAccountsToRegister() {&#x20;   // 基于历史数据或用户行为预测需要注册的账户&#x20;   return \['sip:alice@example.com'];}const predictedAccounts = predictAccountsToRegister();registerAccounts(predictedAccounts);
6.1.4 心跳机制

在长时间空闲的连接上,心跳机制可以保持连接活跃,防止被中间设备关闭。

实现心跳机制

function keepAlive(socket) {&#x20;   setInterval(function() {&#x20;       if (socket.isConnected()) {&#x20;           const message = new JsSIP.Message('OPTIONS sip:example.com SIP/2.0');&#x20;           socket.send(message);&#x20;       }&#x20;   }, 30000); // 每30秒发送一次心跳}// 在连接建立后启动心跳socket.onconnect = function() {&#x20;   keepAlive(socket);};

心跳消息优化

// 使用轻量级的心跳消息const heartbeatMessage = new JsSIP.Message('OPTIONS \* SIP/2.0');heartbeatMessage.headers.set('Content-Length', 0);function sendHeartbeat(socket) {&#x20;   socket.send(heartbeatMessage);}

心跳响应处理

ua.on('newMessage', function(data) {&#x20;   if (data.message.method === 'OPTIONS') {&#x20;       // 发送心跳响应&#x20;       data.message.respond(200, 'OK');&#x20;   }});

6.2 媒体处理优化

媒体处理是实时通信应用的核心,优化媒体处理可以提高通话质量和用户体验。

6.2.1 编解码器优化

选择合适的编解码器并优化其参数,可以显著提高媒体传输效率。

选择高效编解码器

// 优先使用高效编解码器(如OPUS)session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       // 重新排列编解码器顺序,将OPUS设为首选&#x20;       data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2')&#x20;                           .replace('pcmu/8000', 'pcmu/8000');&#x20;   }});

编解码器参数优化

// 设置OPUS编解码器的比特率session.on('sdp', function(data) {&#x20;   if (data.type === 'offer') {&#x20;       data.sdp = data.sdp.replace('opus/48000/2', 'opus/48000/2/128000'); // 设置比特率为128kbps&#x20;   }});

动态调整编解码器

// 根据网络状况动态调整编解码器function adjustCodecs(networkQuality) {&#x20;   if (networkQuality === 'poor') {&#x20;       // 降低视频分辨率和帧率,使用更高效的编解码器&#x20;       session.mediaConstraints = {&#x20;           audio: true,&#x20;           video: {&#x20;               width: 320,&#x20;               height: 240,&#x20;               frameRate: 15&#x20;           }&#x20;       };&#x20;      &#x20;&#x20;       session.renegotiate();&#x20;   } else if (networkQuality === 'good') {&#x20;       // 提高视频分辨率和帧率&#x20;       session.mediaConstraints = {&#x20;           audio: true,&#x20;           video: {&#x20;               width: 1280,&#x20;               height: 720,&#x20;               frameRate: 30&#x20;           }&#x20;       };&#x20;      &#x20;&#x20;       session.renegotiate();&#x20;   }}
6.2.2 媒体流优化

媒体流优化可以减少带宽使用,提高播放性能。

分辨率适配

// 根据显示区域自动调整视频分辨率function adaptResolution() {&#x20;   const displayWidth = window.innerWidth;&#x20;   const displayHeight = window.innerHeight;&#x20;  &#x20;&#x20;   session.mediaConstraints = {&#x20;       audio: true,&#x20;       video: {&#x20;           width: { ideal: displayWidth },&#x20;           height: { ideal: displayHeight },&#x20;           frameRate: 30&#x20;       }&#x20;   };&#x20;  &#x20;&#x20;   session.renegotiate();}// 监听窗口大小变化window.addEventListener('resize', adaptResolution);

帧率控制

// 根据网络状况调整帧率function adjustFrameRate(networkQuality) {&#x20;   if (networkQuality === 'poor') {&#x20;       session.mediaConstraints = {&#x20;           audio: true,&#x20;           video: {&#x20;               frameRate: 15&#x20;           }&#x20;       };&#x20;   } else {&#x20;       session.mediaConstraints = {&#x20;           audio: true,&#x20;           video: {&#x20;               frameRate: 30&#x20;           }&#x20;       };&#x20;   }&#x20;  &#x20;&#x20;   session.renegotiate();}

码率控制

// 限制视频码率session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.getSenders().forEach(function(sender) {&#x20;       if (sender.track.kind === 'video') {&#x20;           sender.setParameters({&#x20;               codecPreferences: \[{&#x20;                   mimeType: 'video/VP8',&#x20;                   parameters: {&#x20;                       'x-google-start-bitrate': 1000, // 初始码率(kbps)&#x20;                       'x-google-max-bitrate': 2000, // 最大码率(kbps)&#x20;                       'x-google-min-bitrate': 500 // 最小码率(kbps)&#x20;                   }&#x20;               }]&#x20;           });&#x20;       }&#x20;   });});
6.2.3 网络适配

在不同网络环境下,应用需要自适应调整行为,确保通信质量。

网络质量检测

function monitorNetworkQuality() {&#x20;   const networkInformation = navigator.connection || navigator.mozConnection || navigator.webkitConnection;&#x20;  &#x20;&#x20;   if (networkInformation) {&#x20;       const type = networkInformation.effectiveType;&#x20;       const downlink = networkInformation.downlink;&#x20;       const rtt = networkInformation.rtt;&#x20;      &#x20;&#x20;       console.log('网络类型:', type);&#x20;       console.log('下载速度:', downlink, 'Mbps');&#x20;       console.log('往返时间:', rtt, 'ms');&#x20;      &#x20;&#x20;       // 根据网络状况调整媒体参数&#x20;       if (type === '4g') {&#x20;           adjustCodecs('good');&#x20;       } else if (type === '3g') {&#x20;           adjustCodecs('medium');&#x20;       } else {&#x20;           adjustCodecs('poor');&#x20;       }&#x20;   }}// 定期检测网络质量setInterval(monitorNetworkQuality, 5000);

自适应比特率

// 实现自适应比特率控制session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.addEventListener('outbound-rtp', function(event) {&#x20;       const bitrate = event.bytesSent / (event.elapsedTime / 1000); // 计算当前码率&#x20;       console.log('当前码率:', bitrate, 'bps');&#x20;      &#x20;&#x20;       if (bitrate > 2000000) {&#x20;           // 码率过高,降低分辨率&#x20;           adjustResolution('low');&#x20;       } else if (bitrate < 1000000) {&#x20;           // 码率过低,提高分辨率&#x20;           adjustResolution('high');&#x20;       }&#x20;   });});

前向纠错

// 启用前向纠错(FEC)session.on('peerconnection', function(data) {&#x20;   const peerConnection = data.peerconnection;&#x20;  &#x20;&#x20;   peerConnection.getSenders().forEach(function(sender) {&#x20;       if (sender.track.kind === 'video') {&#x20;           sender.setParameters({&#x20;               codecPreferences: \[{&#x20;                   mimeType: 'video/VP8',&#x20;                   parameters: {&#x20;                       'x-google-max-cpu-usage': 1.5,&#x20;                       'x-google-min-bitrate': 500,&#x20;                       'x-google-max-bitrate': 2000,&#x20;                       'x-google-start-bitrate': 1000,&#x20;                       'x-google-use-remote-bitrate-estimation': true,&#x20;                       'x-google-max-fec-packets': 2 // 启用FEC,最多2个冗余包&#x20;                   }&#x20;               }]&#x20;           });&#x20;       }&#x20;   });});
6.2.4 资源管理

合理管理媒体资源可以防止内存泄漏和性能下降。

媒体流清理

// 停止并释放媒体流资源function stopMediaStream(stream) {&#x20;   if (stream) {&#x20;       stream.getTracks().forEach(function(track) {&#x20;           track.stop();&#x20;       });&#x20;   }}// 在会话结束时清理媒体流session.on('ended', function() {&#x20;   stopMediaStream(localStream);&#x20;   stopMediaStream(remoteStream);});

视频预览优化

// 优化视频预览,避免重复创建元素function showVideoPreview(stream, element) {&#x20;   if (element.srcObject !== stream) {&#x20;       element.srcObject = stream;&#x20;   }}// 在不再需要时隐藏视频元素function hideVideoPreview(element) {&#x20;   element.style.display = 'none';&#x20;   element.srcObject = null;}

资源预加载

// 预加载常用媒体资源function preloadResources() {&#x20;   // 预加载音频提示&#x20;   const audioContext = new (window.AudioContext || window.webkitAudioContext)();&#x20;   const beepSound = new Audio('beep.wav');&#x20;   beepSound.decodeAudioData(audioContext);&#x20;  &#x20;&#x20;   // 预创建媒体元素&#x20;   const videoElement = document.createElement('video');&#x20;   videoElement.style.display = 'none';&#x20;   document.body.appendChild(videoElement);}

6.3 代码优化与最佳实践

代码优化是提高 JSSIP 应用性能的基础。本节将介绍 JavaScript 代码优化的最佳实践。

6.3.1 事件处理优化

高效的事件处理对于实时应用至关重要,可以减少延迟,提高响应速度。

事件委托

使用事件委托减少事件监听器数量:

// 错误示例:为每个按钮添加单独的事件监听器document.querySelectorAll('.call-button').forEach(function(button) {&#x20;   button.addEventListener('click', function() {&#x20;       // 处理呼叫&#x20;   });});// 正确示例:使用事件委托document.body.addEventListener('click', function(event) {&#x20;   if (event.target.classList.contains('call-button')) {&#x20;       // 处理呼叫&#x20;   }});

防抖与节流

防止频繁触发事件处理函数:

// 防抖函数function debounce(func, delay) {&#x20;   let timeoutId;&#x20;   return function() {&#x20;       clearTimeout(timeoutId);&#x20;       timeoutId = setTimeout(() => func.apply(this, arguments), delay);&#x20;   };}// 节流函数function throttle(func, limit) {&#x20;   let lastCall = 0;&#x20;   return function() {&#x20;       const now

**参考资料 **

[1] jssip - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers https://cdnjs.com/libraries/jssip/3.0.25

[2] jssip - npm https://www.npmjs.com/package/jssip

[3] SIP.js 0.20.0版本简单Demo-CSDN博客 https://blog.csdn.net/Web_ChuXia/article/details/121694595

[4] JsSIP+FreeSwitch+Vue实现WebRtc音视频通话_vue jssip freeswitch-CSDN博客 https://blog.csdn.net/weixin_41978174/article/details/140974900

[5] jssip中文开发文档(完整版)-CSDN博客 https://blog.csdn.net/wh8_2011/article/details/80978220

[6] freeswitch + webRtc +jssip 实现web端语音通话_jssip+freeswitch+webrtc-CSDN博客 https://blog.csdn.net/leng778590995/article/details/107655781

[7] JS全栈开发路线 - 2025-抖音 https://www.iesdouyin.com/share/video/7529152637850258739/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7529153067754015540&region=&scene_from=dy_open_search_video&share_sign=WhsznwJNPORQwYwz9wNZ1JimDYg4AqTozOZg4QMp80M-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1

[8] 2025年防控白皮书最新解读!-抖音 https://www.iesdouyin.com/share/video/7523131209551973673/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7523131152048196386&region=&scene_from=dy_open_search_video&share_sign=KVkyA1uRLIEKTb7Rr5y.SJqRfs55cQzxypMEH_1ewJ4-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1

[9] 每天10分钟,听一听web高频考点,万一面试就问了这个呢 forwardRef 和useImperativeHandle是用来解决什么问题的(下)?-抖音 https://www.iesdouyin.com/share/video/7530917640827669812/?did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&from_aid=1128&from_ssr=1&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&mid=7530916334725647131&region=&scene_from=dy_open_search_video&share_sign=7B2yvDcBL_6N0WgIK9XlxfX74itWWHO7eYLNpdzGLx0-&share_version=280700&titleType=title&ts=1753487629&u_code=0&video_share_track_ver=&with_sec_did=1

[10] JSSIP Fail over behaviour https://groups.google.com/g/jssip/c/UscFATnFD_o

[11] jssip - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers https://cdnjs.com/libraries/jssip/3.9.0

[12] Application Security Best Practices in 2025 | Beetroot https://beetroot.co/cybersecurity/application-security-best-practices-for-modern-development-teams/

[13] JS Performance Tips: How to Speed up JavaScript Load Time [2025] https://brainhub.eu/library/js-performance-tips

[14] Job Scheduling Strategies for Parallel Processing https://www.jsspp.org/

[15] Software Performance Optimization Tips for 2025: The Ultimate Guide https://techlasi.com/savvy/software-performance-optimization-tips/

[16] Web Architecture & Performance - International JavaScript Conference https://javascript-conference.com/architecture-performance/

[17] UNPKG - jssip-sl-x https://unpkg.com/browse/jssip-sl-x@5.0.0/README.md

[18] GitHub - versatica/jssip-node-websocket: JsSIP.Socket interface for the Node.js based on the websocket module https://github.com/versatica/jssip-node-websocket

[19] JsSSIP and Asterisk SIP - Asterisk WebRTC - Asterisk Community https://community.asterisk.org/t/jsssip-and-asterisk-sip/88670

[20] jssip-node-websocket - npm https://www.npmjs.com/package/jssip-node-websocket

[21] How to setup JsSIP (WebRTC client) – Interoperability https://docs.brekeke.com/interop/how-to-setup-jssip-webrtc-client

[22] WebRTC Integrator’s Guide https://subscription.packtpub.com/book/business-and-other/9781783981267/1/ch01lvl1sec11/running-webrtc-with-sip

[23] WebRTC with SIP Over WebSockets | SignalWire Developer Portal https://developer.signalwire.com/platform/basics/guides/webrtc-with-sip-over-websockets/

[24] GitHub - versatica/JsSIP: JsSIP, the JavaScript SIP library https://github.com/versatica/JsSIP

[25] api documentation for jssip (v3.0.7) https://npmdoc.github.io/node-npmdoc-jssip/build/apidoc.html

[26] GitHub - jpotts18/react-native-jssip: JsSIP, the JavaScript SIP library https://github.com/jpotts18/react-native-jssip

[27] GitHub - versatica/tryit-jssip: New tryit-jssip application https://github.com/versatica/tryit-jssip

[28] GitHub - paneru-rajan/asterisk-jssip: This is the complete guide to install Sipml5 and Asterisk. I have used Vagrant, however, I will describe how to install on Ubuntu alone. https://github.com/paneru-rajan/asterisk-jssip

[29] SSL/TLS — PJSIP Project 2.15-dev documentation https://docs.pjsip.org/en/latest/specific-guides/security/ssl.html

[30] JsSip - Failed to parse SessionDescription. a=fingerprint:SHA-256 Failed to create fingerprint from the digest - Asterisk WebRTC - Asterisk Community https://community.asterisk.org/t/jssip-failed-to-parse-sessiondescription-a-fingerprint-sha-256-failed-to-create-fingerprint-from-the-digest/81863

[31] JSSE Utility :: Red Hat Integration https://access.redhat.com/webassets/avalon/d/red_hat_integration/2021.q3/apache-camel-3.10-doc/manual/latest/camel-configuration-utilities.html

[32] JavaMadeSoEasy.com (JMSE): Connection Pooling in java with example https://www.javamadesoeasy.com/2015/12/connection-pooling-in-java-with-example.html?m=1

[33] Configuring connection pooling for JMS connections https://www.ibm.com/docs/en/was-liberty/nd?topic=dmal-configuring-connection-pooling-jms-connections

[34] How to create a pool of connection in jsmpp? · Issue #147 · opentelecoms-org/jsmpp · GitHub https://github.com/opentelecoms-org/jsmpp/issues/147

[35] SignalWire RELAY | WebRTC with SIP over WebSockets | SignalWire https://signalwire.com/blogs/product/webrtc-using-sip-over-websockets

[36] Implement a connection pooling - Sun: Servlets and JavaServer Pages (JSP) | Tek-Tips https://www.tek-tips.com/threads/implement-a-connection-pooling.896673/

[37] Asynchronous Programming in JavaScript https://openreplay.hashnode.dev/best-practices-for-async-programming-in-javascript

(注:文档部分内容可能由 AI 生成)

http://www.dtcms.com/a/299217.html

相关文章:

  • QT核心————信号槽
  • Qt 多线程编程最佳实践
  • 《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——6. 传统算法实战:用OpenCV测量螺丝尺寸
  • 基于粒子群算法优化高斯过程回归(PSO-GPR)的多输出回归
  • 数据科学与大数据技术专业的核心课程体系及发展路径全解析
  • Jenkins运行pytest时指令失效的原因以及解决办法
  • Java集合体系详解
  • docker常用命令集(3)
  • 【守护】同为科技SPD:AP-20D/4P产品解析
  • C语言--青蛙跳台阶问题
  • 《聪明人的个人成长》读书笔记
  • DAY31 整数矩阵及其运算
  • FitCoach AI:基于React+CloudBase的智能健身教练应用开发全解析
  • LeetCode 1074:元素和为目标值的子矩阵数量
  • Qt 网络编程进阶:网络安全与加密
  • Spring Cloud Gateway:微服务架构下的 API 网关详解
  • GRE及MGRE应用综合实验
  • ICMPv4报文类型详解表
  • OpenCV学习探秘之二 :数字图像的矩阵原理,OpenCV图像类与常用函数接口说明,及其常见操作核心技术详解
  • 生猪产业新生态:结构调整与种养结合,筑牢农业强国根基
  • Linux内核设计与实现 - 课程大纲
  • Android WorkManager 详解:高效管理后台任务
  • Ruby 数据库访问 - DBI 教程
  • 基于深度学习的胸部 X 光图像肺炎分类系统(七)
  • 基于POD和DMD的压气机叶片瞬态流场分析与神经网络预测
  • java8 List常用基本操作(去重,排序,转换等)
  • 联表实现回显功能
  • 经典IDE之Turbo C
  • HAProxy 实验指南:从零开始搭建高可用负载均衡系统
  • haproxy原理及实战部署