Chrome浏览器网页版SIP客户端:从注册到通话的完整实现指南
简介这份资源面向希望基于浏览器构建呼叫中心或网页软电话的开发者提供了一套可直接运行的Chrome网页版SIP客户端示例。它解决的是传统呼叫中心依赖桌面应用或专用电话设备的问题让语音通话、视频通话与消息传递直接在网页内完成适合具备一定前端与WebRTC基础的开发者快速验证原型。压缩包共25个文件约426KB以14个JavaScript脚本为核心配合2个CSS样式、5个PNG图标、3个WAV提示音和1个HTML入口页覆盖界面渲染、SIP信令交互与通话反馈音效等环节。目前已有593人学习下载。资源内含SIPml-api.js通信库、Bootstrap响应式界面资源以及拨号音、回铃音、按键音等音频素材读者可据此理解注册、呼叫、挂断等流程并在此基础上扩展出安全、可定制的Web通信系统。1. 拆开这个压缩包Chrome 里的 SIP 网页客户端到底解决什么问题呼叫中心坐席的电脑上装软电话这件事一直很尴尬。装原生客户端吧IT 要逐台部署、升级、处理驱动冲突不装吧坐席又得在浏览器和话务系统之间来回切。标题里这个「chrome浏览器网页版SIP」压缩包本质就是把 SIP 协议栈塞进 Chrome让坐席打开一个网页就能注册分机、接打电话不用装任何本地程序。它面向的是呼叫中心、客服团队、外呼系统集成方以及想把话务能力嵌进现有 Web 后台的开发者。核心逻辑不复杂浏览器通过 WebRTC 拿到麦克风和音频通道用 JavaScript 实现的 SIP 协议栈常见的是 SIP.js 或 JsSIP跟 SIP 服务器做注册和信令交互媒体流走 WebRTC 的 SRTP。Chrome 在这里扮演的是运行容器不是通信协议本身。压缩包里通常包含前端页面、SIP 协议栈库、信令配置和呼叫控制逻辑。你拿到它之后真正要搞清楚的只有三件事SIP 服务器怎么配、Chrome 的 WebRTC 权限怎么放、呼叫流程怎么跟你的业务系统对接。下面按这个顺序拆。2. 网页 SIP 客户端的信令链路从 Chrome 到 SIP 服务器要过几道关2.1 WebRTC 负责媒体SIP over WebSocket 负责信令很多人第一次接触网页版 SIP会以为浏览器直接说 SIP 协议就行了。不是的。Chrome 不认识 UDP 上的 SIP 信令它只认 WebSocket。所以网页 SIP 客户端跟服务器之间的信令通道是 SIP over WebSocketRFC 7118默认走 80 或 443 端口用ws://或wss://连接。媒体通道则是 WebRTC 自己协商出来的走 SRTP跟信令通道完全分开。这意味着你的 SIP 服务器必须支持 WebSocket 传输。常见的 Asterisk、FreeSWITCH、Kamailio 都支持但默认配置里 WebSocket 往往是关着的。以 FreeSWITCH 为例需要在internalprofile 里确认ws-binding和wss-binding参数已经打开。Asterisk 则要在http.conf里启用enabledyes并在 SIP 配置里加上transportws或transportwss。提示如果服务器只开了 UDP 5060 而没开 WebSocket网页客户端会一直卡在「连接中」Chrome 控制台里能看到 WebSocket 握手失败的报错。2.2 注册流程Authorization 头是怎么算出来的网页 SIP 客户端的注册跟普通软电话一样走 REGISTER 方法。但浏览器环境下有一个容易翻车的点SIP 摘要认证的Authorization头需要计算 MD5而浏览器没有原生的 MD5 接口。SIP.js 和 JsSIP 内部用 JavaScript 实现了 MD5所以你在配置里填用户名和密码就行不用自己算。注册的完整链路是这样的客户端先发一个不带认证信息的 REGISTER服务器返回 401 并带上WWW-Authenticate头里面包含 realm 和 nonce。客户端用 username、password、realm、nonce、uri 这几个值算出 response再发一次带Authorization头的 REGISTER。服务器验证通过后返回 200 OK注册生效。这里有个参数必须注意expires。它决定注册有效期单位是秒。设太短客户端要频繁重注册增加服务器压力设太长分机状态更新不及时。呼叫中心场景一般设 300 到 600 秒比较稳。SIP.js 里对应的配置项是registerExpiresJsSIP 里是register_expires。2.3 呼叫建立INVITE 到 200 OK 的完整交互注册成功只是第一步真正打电话走的是 INVITE 流程。主叫方发 INVITE被叫方返回 100 Trying 表示收到了然后 180 Ringing 表示正在振铃接听后返回 200 OK。主叫方收到 200 OK 后发 ACK 确认媒体流开始传输。挂断时任意一方发 BYE对方回 200 OK会话结束。在 Chrome 里这个流程的每一步都对应 SIP.js 的事件回调。比如onInvite触发时有来电onAccepted触发时对方接听了onTerminated触发时会话结束。你需要在回调里更新页面 UI比如显示来电弹窗、切换通话状态图标、播放振铃音。这些 UI 逻辑不在 SIP 协议栈里得自己写。一个常见的坑是Chrome 要求页面必须处于「安全上下文」才能调用getUserMedia。也就是说你的网页必须走 HTTPS或者跑在localhost上。如果部署在内网用 HTTP 访问麦克风权限会被 Chrome 直接拒绝连弹窗都不弹。解决办法是给内网服务器配一个自签名证书或者用 Chrome 的--unsafely-treat-insecure-origin-as-secure启动参数临时绕过仅限调试。3. 在 Chrome 里跑通最小可用的网页 SIP 客户端3.1 环境准备与依赖确认拿到压缩包后先别急着打开 HTML 文件。你需要确认三样东西一个支持 WebSocket 的 SIP 服务器、一个能跑静态页面的 Web 服务器、以及 Chrome 的版本。Chrome 从 80 版本开始对 WebRTC 和 WebSocket 的安全策略收紧了不少建议用 100 以上的版本。如果坐席电脑还在跑 Win7 上的 Chrome 109基本功能没问题但要注意 109 是 Win7 能装的最后一个大版本后续不再更新。Web 服务器用 Nginx 或 Python 自带的http.server都行。如果只是本地测试在压缩包解压目录下执行python3 -m http.server 8080然后浏览器访问http://localhost:8080。注意必须是localhost换成127.0.0.1也行但换成局域网 IP 就会触发安全上下文限制。3.2 SIP.js 初始化与注册配置压缩包里的前端代码通常已经引入了 SIP.js 或 JsSIP。以 SIP.js 为例核心初始化代码如下// 创建 UserAgent配置 SIP 服务器地址和用户信息 const ua new SIP.UserAgent({ uri: SIP.UserAgent.makeURI(sip:1001your-sip-server.com), transportOptions: { server: wss://your-sip-server.com:7443 // WebSocket 信令地址 }, authorizationUsername: 1001, // SIP 认证用户名 authorizationPassword: your-password, // SIP 认证密码 register: true, // 启动时自动注册 registerExpires: 300, // 注册有效期 300 秒 sessionDescriptionHandlerFactoryOptions: { constraints: { audio: true, // 只取音频呼叫中心场景不需要视频 video: false } } }); // 注册状态回调 ua.delegate { onConnect: () console.log(WebSocket 已连接), onDisconnect: (error) console.error(连接断开, error), onRegistered: () console.log(SIP 注册成功), onUnregistered: () console.log(SIP 注销), onInvite: (invitation) { // 来电处理弹出接听界面 console.log(来电来自, invitation.remoteIdentity.uri.toString()); } }; // 启动 UserAgent ua.start().then(() ua.register());这段代码做了四件事建立 WebSocket 连接、发送 REGISTER 注册、监听注册状态、处理来电事件。server参数填的是 SIP 服务器的 WebSocket 地址不是 UDP 地址。registerExpires设 300 秒意味着客户端每 300 秒会重新注册一次服务器端也要把对应的超时时间设成差不多的值否则会出现客户端以为注册着、服务器已经踢掉的情况。3.3 发起呼叫与接听的处理逻辑注册成功后发起呼叫的代码大致如下// 发起呼叫 async function makeCall(targetNumber) { const target SIP.UserAgent.makeURI(sip:${targetNumber}your-sip-server.com); const inviter new SIP.Inviter(ua, target); // 监听呼叫状态 inviter.stateChange.addListener((state) { switch (state) { case SIP.SessionState.Establishing: console.log(正在呼叫...); break; case SIP.SessionState.Established: console.log(通话已建立); // 此时媒体流已经通了可以挂载音频 break; case SIP.SessionState.Terminated: console.log(通话已结束); break; } }); await inviter.invite(); // 发送 INVITE }接听来电的逻辑类似在onInvite回调里拿到invitation对象后调用invitation.accept()接听或者invitation.reject()拒接。接听后同样要监听stateChange来更新 UI。这里有一个实操中很容易忽略的点音频元素的挂载。WebRTC 的媒体流不会自动播放你需要把sessionDescriptionHandler里的remoteMediaStream挂到一个audio标签上并且设置autoplay属性。Chrome 的自动播放策略要求用户至少有一次交互点击、触摸之后才允许播放音频所以通常会在「接听」按钮的点击事件里做这个操作。3.4 关键参数速查表参数作用推荐值注意事项registerExpires注册有效期300 秒服务器端超时时间要匹配serverWebSocket 信令地址wss://开头必须用 wss 或 localhost 下的 wsconstraints.audio音频采集开关true设为 false 则无法通话sessionTimers会话超时定时器默认开启防止僵尸会话iceServersNAT 穿透配置按需配置跨网段呼叫必须配 STUN/TURN4. 避坑与排查网页 SIP 客户端最容易翻车的五个地方4.1 现象注册一直失败控制台报 401 后没有后续原因通常是密码错了或者authorizationUsername跟 SIP URI 里的用户名不一致。有些 SIP 服务器要求认证用户名是完整的分机号有些则要求带域名后缀。解决方法是先在服务器端用sip show peers或类似命令确认分机配置再对照前端代码里的authorizationUsername和uri是否匹配。另外注意密码里如果有特殊字符在 JavaScript 字符串里要转义。4.2 现象能注册成功但一打电话就没声音这是最典型的媒体问题。先检查iceServers有没有配。如果客户端和服务器在不同网段没有 STUN/TURN 的话WebRTC 协商出来的候选地址可能不可达。其次检查 Chrome 的麦克风权限有没有给在chrome://settings/content/microphone里确认当前页面没有被屏蔽。还有一个隐蔽的坑某些服务器的external_media_address没配对外网 IP导致 SDP 里的媒体地址是内网地址客户端根本连不上。4.3 现象Chrome 提示「无法访问麦克风」连权限弹窗都不出这就是前面说的安全上下文问题。Chrome 要求getUserMedia必须在 HTTPS 或localhost下调用。如果你用http://192.168.x.x访问Chrome 会直接拒绝不给弹窗机会。解决办法有两个给 Web 服务器配 HTTPS 证书或者用 Chrome 启动参数--unsafely-treat-insecure-origin-as-securehttp://192.168.x.x临时放行。生产环境必须用 HTTPS。4.4 现象通话几十秒后自动断线大概率是会话定时器Session Timer在起作用。SIP 协议里有一个Session-Expires头默认可能是 90 秒或 180 秒。如果一方没有在超时前发 UPDATE 或 re-INVITE 刷新会话另一方就会主动发 BYE 断线。解决方法是确认 SIP.js 的sessionTimers配置跟服务器端一致或者直接在服务器端把会话超时设长一点。呼叫中心场景一般设 1800 秒以上。4.5 现象Win7 上的 Chrome 109 打开页面白屏Chrome 109 是 Win7 支持的最后一个版本它不支持一些较新的 JavaScript 语法和 Web API。如果压缩包里的 SIP.js 用了 ES2020 以上的语法比如可选链?.或空值合并??在 Chrome 109 上会直接报语法错误导致白屏。解决办法是换用编译后的 ES5 版本或者在构建时加 Babel 转译。如果坐席电脑还在跑 Win7这一点必须提前确认。5. 把网页 SIP 客户端嵌进呼叫中心业务系统的三个进阶技巧5.1 用事件总线解耦 SIP 层和 UI 层很多初版实现会把 SIP.js 的回调直接写在页面逻辑里导致话务状态和业务状态搅在一起。更稳的做法是加一层事件总线SIP 层只负责发事件call:incoming、call:answered、call:endedUI 层和业务层各自订阅。这样以后换 SIP 库或者加新的业务逻辑不用动核心通话代码。我一般用一个简单的发布订阅对象就够了不需要引入重型框架。// 极简事件总线 const bus { handlers: {}, on(event, fn) { (this.handlers[event] this.handlers[event] || []).push(fn); }, emit(event, data) { (this.handlers[event] || []).forEach(fn fn(data)); } }; // SIP 层只发事件 ua.delegate.onInvite (invitation) { bus.emit(call:incoming, { from: invitation.remoteIdentity.uri.toString() }); }; // UI 层订阅 bus.on(call:incoming, ({ from }) showIncomingPopup(from));5.2 用 localStorage 缓存注册状态减少重复注册坐席页面刷新是常事每次刷新都重新注册一遍 SIP 会拖慢体验。可以在注册成功后把分机号和注册时间存到localStorage页面加载时先读缓存如果距离上次注册不到registerExpires的一半就跳过重新注册直接复用。注意这只能优化体验不能替代服务器端的注册状态管理。5.3 用 Chrome 的chrome://webrtc-internals做通话质量排查这个页面是排查 WebRTC 问题的黑匣子。通话过程中打开chrome://webrtc-internals能看到当前会话的完整统计丢包率、抖动、往返延迟、码率、编解码器。如果坐席反馈「声音断断续续」先看丢包率和抖动丢包超过 5% 或者抖动超过 50ms基本就是网络问题不是代码问题。这个工具比在代码里打日志高效得多建议每个做网页 SIP 的人都把它加到书签栏。最后说一个我自己的习惯每次改完 SIP 相关配置先不急着在业务系统里测而是用一个最简 HTML 页面单独验证注册和通话。业务系统的代码路径太长出了问题不好定位。把最简页面跑通之后再把配置搬过去能省掉大量排查时间。希望帮到你。本文还有配套的精品资源点击获取