概述
JsSIP是一个纯JavaScript的SIP客户端库,利用WebRTC和SIP over WebSocket技术,让任何网站都能轻松实现实时通信功能。
主要特性
- 基于WebSocket的SIP传输
- 支持音频/视频通话、即时消息和在线状态
- 轻量级,100%纯JavaScript实现
- 易于使用且功能强大的API
- 兼容OverSIP、Kamailio、Asterisk和FreeSWITCH服务器
支持的SIP标准
JsSIP实现了以下SIP规范:
- RFC 3261 “SIP: Session Initiation Protocol”
- RFC 3311 “SIP UPDATE Method”
- RFC 3326 “The Reason Header Field for SIP”
- RFC 3327 “SIP Extension Header Field for Registering Non-Adjacent Contacts” (Path header)
- RFC 3428 “SIP Extension for Instant Messaging” (MESSAGE method)
- RFC 3515 “The SIP Refer Method”
- RFC 3891 “The SIP Replaces Header”
- RFC 4028 “Session Timers in SIP”
- RFC 5589 “The SIP Call Control – Transfer”
- RFC 5626 “Managing Client-Initiated Connections in SIP” (Outbound mechanism)
- RFC 7118 “The WebSocket Protocol as a Transport for SIP”
核心API类
1. JsSIP.UA (User Agent)
UA是JsSIP的核心类,代表SIP用户代理。
1.1 创建UA实例
var socket = new JsSIP.WebSocketInterface('wss://sip.example.com');
var configuration = {
sockets: [socket],
uri: 'sip:alice@example.com',
password: 'superpassword'
};
var ua = new JsSIP.UA(configuration);
1.2 UA配置参数
参数 | 类型 | 必需 | 默认值 | 说明 |
---|---|---|---|---|
uri | String | 是 | - | SIP URI (sip:user@domain) |
password | String | 否 | - | SIP密码 |
display_name | String | 否 | - | 显示名称 |
authorization_user | String | 否 | uri中的用户名 | 认证用户名 |
realm | String | 否 | uri中的域名 | 认证域 |
ha1 | String | 否 | - | HA1哈希值 |
sockets | Array | 是 | - | WebSocket接口数组 |
contact_uri | String | 否 | - | Contact URI |
instance_id | String | 否 | 随机生成 | 实例ID |
session_timers | Boolean | 否 | true | 会话定时器 |
no_answer_timeout | Number | 否 | 60 | 无应答超时(秒) |
use_preloaded_route | Boolean | 否 | false | 使用预加载路由 |
hack_via_tcp | Boolean | 否 | false | TCP Via头部修正 |
hack_ip_in_contact | Boolean | 否 | false | Contact中IP修正(Asterisk兼容) |
1.3 UA方法
// 启动UA
ua.start();
// 停止UA
ua.stop();
// 注册
ua.register();
// 注销
ua.unregister();
// 检查是否已注册
ua.isRegistered();
// 检查是否已连接
ua.isConnected();
// 发起呼叫
var session = ua.call('sip:bob@example.com', options);
// 发送消息
ua.sendMessage('sip:bob@example.com', 'Hello World!', options);
1.4 UA事件
ua.on('connecting', function(e) {
console.log('正在连接...');
});
ua.on('connected', function(e) {
console.log('已连接');
});
ua.on('disconnected', function(e) {
console.log('连接断开:', e.cause);
});
ua.on('registered', function(e) {
console.log('注册成功');
});
ua.on('unregistered', function(e) {
console.log('注销成功');
});
ua.on('registrationFailed', function(e) {
console.log('注册失败:', e.cause);
});
ua.on('newRTCSession', function(e) {
console.log('新的通话会话');
var session = e.session;
// 处理会话事件
});
ua.on('newMessage', function(e) {
console.log('收到新消息:', e.message.body);
});
2. JsSIP.RTCSession (通话会话)
RTCSession处理音频/视频通话会话。
2.1 发起呼叫
var eventHandlers = {
'progress': function(e) {
console.log('呼叫进行中');
},
'confirmed': function(e) {
console.log('呼叫已建立');
},
'ended': function(e) {
console.log('呼叫结束');
},
'failed': function(e) {
console.log('呼叫失败:', e.cause);
}
};
var options = {
'eventHandlers': eventHandlers,
'mediaConstraints': {
'audio': true,
'video': true
}
};
var session = ua.call('sip:bob@example.com', options);
2.2 接听呼叫
ua.on('newRTCSession', function(e) {
var session = e.session;
if (session.direction === 'incoming') {
// 接听呼叫
session.answer({
'mediaConstraints': {
'audio': true,
'video': true
}
});
// 或者拒绝呼叫
// session.terminate();
}
});
2.3 RTCSession方法
// 接听
session.answer(options);
// 挂断
session.terminate(options);
// 保持
session.hold(options);
// 取消保持
session.unhold(options);
// 静音
session.mute(options);
// 取消静音
session.unmute(options);
// 发送DTMF
session.sendDTMF('1234');
// 转接
session.refer('sip:charlie@example.com');
// 重新协商
session.renegotiate(options);
2.4 RTCSession事件
session.on('connecting', function(e) {
console.log('会话连接中');
});
session.on('progress', function(e) {
console.log('会话进行中');
if (e.response) {
console.log('响应码:', e.response.status_code);
}
});
session.on('confirmed', function(e) {
console.log('会话已确认');
});
session.on('ended', function(e) {
console.log('会话结束');
console.log('原因:', e.cause);
});
session.on('failed', function(e) {
console.log('会话失败');
console.log('原因:', e.cause);
});
session.on('hold', function(e) {
console.log('会话保持');
});
session.on('unhold', function(e) {
console.log('取消保持');
});
session.on('muted', function(e) {
console.log('静音');
});
session.on('unmuted', function(e) {
console.log('取消静音');
});
3. JsSIP.Message (即时消息)
3.1 发送消息
var eventHandlers = {
'succeeded': function(e) {
console.log('消息发送成功');
},
'failed': function(e) {
console.log('消息发送失败:', e.cause);
}
};
var options = {
'eventHandlers': eventHandlers
};
ua.sendMessage('sip:bob@example.com', 'Hello World!', options);
3.2 接收消息
ua.on('newMessage', function(e) {
var message = e.message;
if (message.direction === 'incoming') {
console.log('收到消息:', message.body);
console.log('发送者:', message.remote_identity.uri);
// 回复消息
message.accept();
}
});
4. JsSIP.WebSocketInterface (WebSocket接口)
4.1 创建WebSocket接口
var socket = new JsSIP.WebSocketInterface('wss://sip.example.com');
// 带参数的WebSocket
var socket = new JsSIP.WebSocketInterface('wss://sip.example.com', 'sip');
// 多个WebSocket服务器
var sockets = [
new JsSIP.WebSocketInterface('wss://sip1.example.com'),
new JsSIP.WebSocketInterface('wss://sip2.example.com')
];
4.2 WebSocket事件
socket.on('connected', function(e) {
console.log('WebSocket连接成功');
});
socket.on('disconnected', function(e) {
console.log('WebSocket连接断开');
});
完整示例
基本SIP客户端
<!DOCTYPE html>
<html>
<head>
<title>JsSIP Demo</title>
<script src="https://cdn.jsdelivr.net/npm/jssip@3.10.0/dist/jssip.min.js"></script>
</head>
<body>
<h1>JsSIP SIP客户端</h1>
<div>
<button id="register">注册</button>
<button id="unregister">注销</button>
<span id="status">未连接</span>
</div>
<div>
<input type="text" id="target" placeholder="目标号码">
<button id="call">呼叫</button>
<button id="hangup">挂断</button>
</div>
<div>
<input type="text" id="message" placeholder="消息内容">
<button id="sendMessage">发送消息</button>
</div>
<div id="log"></div>
<script>
// 配置
var socket = new JsSIP.WebSocketInterface('wss://your-freeswitch-server:7443');
var configuration = {
sockets: [socket],
uri: 'sip:1001@your-domain.com',
password: 'your-password',
hack_ip_in_contact: true // Asterisk/FreeSWITCH兼容
};
var ua = new JsSIP.UA(configuration);
var currentSession = null;
// UI元素
var statusEl = document.getElementById('status');
var logEl = document.getElementById('log');
function log(message) {
logEl.innerHTML += '<div>' + new Date().toLocaleTimeString() + ': ' + message + '</div>';
logEl.scrollTop = logEl.scrollHeight;
}
// UA事件处理
ua.on('connecting', function(e) {
statusEl.textContent = '连接中...';
log('正在连接到SIP服务器');
});
ua.on('connected', function(e) {
statusEl.textContent = '已连接';
log('已连接到SIP服务器');
});
ua.on('disconnected', function(e) {
statusEl.textContent = '连接断开';
log('与SIP服务器连接断开: ' + e.cause);
});
ua.on('registered', function(e) {
statusEl.textContent = '已注册';
log('SIP注册成功');
});
ua.on('unregistered', function(e) {
statusEl.textContent = '已注销';
log('SIP注销成功');
});
ua.on('registrationFailed', function(e) {
statusEl.textContent = '注册失败';
log('SIP注册失败: ' + e.cause);
});
ua.on('newRTCSession', function(e) {
var session = e.session;
currentSession = session;
if (session.direction === 'incoming') {
log('收到来电: ' + session.remote_identity.uri);
// 自动接听(实际应用中应该询问用户)
session.answer({
mediaConstraints: {
audio: true,
video: false
}
});
}
// 会话事件处理
session.on('confirmed', function(e) {
log('通话已建立');
});
session.on('ended', function(e) {
log('通话结束: ' + e.cause);
currentSession = null;
});
session.on('failed', function(e) {
log('通话失败: ' + e.cause);
currentSession = null;
});
});
ua.on('newMessage', function(e) {
var message = e.message;
if (message.direction === 'incoming') {
log('收到消息: ' + message.body + ' (来自: ' + message.remote_identity.uri + ')');
message.accept();
}
});
// 按钮事件
document.getElementById('register').onclick = function() {
ua.start();
};
document.getElementById('unregister').onclick = function() {
ua.stop();
};
document.getElementById('call').onclick = function() {
var target = document.getElementById('target').value;
if (!target) {
alert('请输入目标号码');
return;
}
var eventHandlers = {
'progress': function(e) {
log('呼叫进行中...');
},
'confirmed': function(e) {
log('通话已建立');
},
'ended': function(e) {
log('通话结束: ' + e.cause);
currentSession = null;
},
'failed': function(e) {
log('呼叫失败: ' + e.cause);
currentSession = null;
}
};
var options = {
eventHandlers: eventHandlers,
mediaConstraints: {
audio: true,
video: false
}
};
currentSession = ua.call('sip:' + target + '@your-domain.com', options);
};
document.getElementById('hangup').onclick = function() {
if (currentSession) {
currentSession.terminate();
}
};
document.getElementById('sendMessage').onclick = function() {
var target = document.getElementById('target').value;
var message = document.getElementById('message').value;
if (!target || !message) {
alert('请输入目标号码和消息内容');
return;
}
var eventHandlers = {
'succeeded': function(e) {
log('消息发送成功');
},
'failed': function(e) {
log('消息发送失败: ' + e.cause);
}
};
ua.sendMessage('sip:' + target + '@your-domain.com', message, {
eventHandlers: eventHandlers
});
document.getElementById('message').value = '';
};
</script>
</body>
</html>
与FreeSWITCH集成
FreeSWITCH配置
1. 启用WebSocket支持
在conf/sip_profiles/internal.xml
中添加:
<param name="ws-binding" value=":5066"/>
<param name="wss-binding" value=":7443"/>
2. 配置用户
在conf/directory/default/
目录下创建用户配置文件:
<include>
<user id="1001">
<params>
<param name="password" value="your-password"/>
<param name="vm-password" value="1001"/>
</params>
<variables>
<variable name="toll_allow" value="domestic,international,local"/>
<variable name="accountcode" value="1001"/>
<variable name="user_context" value="default"/>
<variable name="effective_caller_id_name" value="Extension 1001"/>
<variable name="effective_caller_id_number" value="1001"/>
</variables>
</user>
</include>
3. 配置拨号计划
在conf/dialplan/default.xml
中添加:
<extension name="Local_Extension">
<condition field="destination_number" expression="^(10[01][0-9])$">
<action application="export" data="dialed_extension=$1"/>
<action application="bridge" data="user/${dialed_extension}@${domain_name}"/>
</condition>
</extension>
JavaScript客户端配置
var socket = new JsSIP.WebSocketInterface('wss://your-freeswitch-server:7443');
var configuration = {
sockets: [socket],
uri: 'sip:1001@your-freeswitch-domain',
password: 'your-password',
hack_ip_in_contact: true, // FreeSWITCH兼容性
session_timers: false // 可选:禁用会话定时器
};
常见问题和注意事项
1. 浏览器兼容性
JsSIP需要现代浏览器支持:
- Chrome 23+
- Firefox 22+
- Safari 11+
- Edge 79+
2. HTTPS要求
WebRTC要求HTTPS环境:
- 生产环境必须使用HTTPS
- 本地开发可以使用localhost
- 需要有效的SSL证书
3. Asterisk兼容性
使用Asterisk时需要特殊配置:
var configuration = {
// ... 其他配置
hack_ip_in_contact: true // 解决Asterisk的Contact头部问题
};
4. 防火墙和NAT
- 确保WebSocket端口开放
- 配置STUN/TURN服务器处理NAT
- 考虑使用ICE服务器
5. 媒体权限
// 请求媒体权限
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
}).then(function(stream) {
// 权限获取成功
}).catch(function(error) {
console.error('媒体权限获取失败:', error);
});
6. 错误处理
ua.on('registrationFailed', function(e) {
switch(e.cause) {
case JsSIP.C.causes.AUTHENTICATION_ERROR:
console.log('认证失败,请检查用户名和密码');
break;
case JsSIP.C.causes.CONNECTION_ERROR:
console.log('连接失败,请检查网络和服务器');
break;
default:
console.log('注册失败:', e.cause);
}
});
7. 性能优化
- 合理设置会话定时器
- 及时清理事件监听器
- 使用连接池管理多个会话
- 实现断线重连机制
8. 安全考虑
- 使用WSS(WebSocket Secure)
- 实施适当的认证机制
- 限制跨域访问
- 定期更新JsSIP版本
调试技巧
1. 启用调试日志
JsSIP.debug.enable('JsSIP:*');
2. 监控WebSocket连接
socket.on('connected', function() {
console.log('WebSocket连接建立');
});
socket.on('disconnected', function() {
console.log('WebSocket连接断开');
});
3. 检查SIP消息
ua.on('sipEvent', function(e) {
console.log('SIP事件:', e.event, e.data);
});
最佳实践
- 错误处理: 始终为关键操作添加错误处理
- 用户体验: 提供清晰的连接状态指示
- 资源管理: 及时清理不再使用的会话和事件监听器
- 安全性: 使用HTTPS和WSS,实施适当的认证
- 兼容性: 测试不同浏览器和SIP服务器的兼容性
- 性能: 合理配置超时参数,避免内存泄漏
这个文档涵盖了JsSIP的主要功能和集成要点,可以作为开发WebRTC SIP应用的完整参考指南。