Uni-app 跨端集成 SIP 电话功能(H5 + App)实战
文章摘要: 本文介绍了在uni-app中实现跨端SIP电话功能的方案,支持H5和App端。通过jssip库实现SIP注册与拨号,针对不同端采用差异化处理:H5端直接调用逻辑层,App端通过renderjs在视图层运行。核心步骤包括安装jssip、目录结构设计、两端初始化实现、统一调用入口及拨号界面开发。重点解决了App端因JS引擎限制无法直接使用WebRTC的问题,利用renderjs访问浏览器A
Uni-app 跨端集成 SIP 电话功能(H5 + App)实战
本文介绍如何在 uni-app 中同时支持 H5 端 和 App 端 的 SIP 电话功能,基于
jssip库实现注册与拨号,并处理两端环境差异。
一、背景与需求
在很多业务系统(如 CRM、呼叫中心)中,需要直接在 Web 或 App 内集成电话能力。传统的方案是单独做 H5 或原生 App,而 uni-app 作为跨端框架,允许一套代码多端运行。但 SIP 通话 依赖浏览器原生 WebRTC 能力,App 端逻辑层无法直接操作 DOM 和原生 API,因此需要特殊处理。
本文方案:
- H5 端:直接在逻辑层使用
jssip库 - App 端:通过
renderjs在视图层运行jssip,再通过消息通信与逻辑层交互
二、技术选型
| 组件 | 说明 |
|---|---|
| uni-app | 跨端框架,支持 H5、App、小程序等 |
| jssip | 纯 JavaScript SIP 库,支持 WebSocket 传输和 WebRTC 通话 |
| renderjs | uni-app 中运行在视图层的 JS 环境,可调用浏览器原生 API(App 端) |
| FreeSWITCH | 开源的 SIP 服务器(示例使用,可替换为其他 SIP 服务器) |
三、实现步骤
1. 安装 jssip
npm install jssip
2. 目录结构说明
pages/
call/ # 通话页面
index.vue # 包含逻辑层和 renderjs 模块
3. H5 端实现(逻辑层直接调用)
在 methods 中定义初始化方法,动态导入 jssip:
// H5 初始化
initSIP_H5() {
import('jssip').then(({ default: JsSIP }) => {
const socket = new JsSIP.WebSocketInterface('wss://your-sip-server:7443')
const config = {
sockets: [socket],
uri: 'sip:1001@your-sip-server',
password: 'your_password',
}
this.ua = new JsSIP.UA(config)
this.ua.on('registered', () => console.log('H5 注册成功'))
this.ua.start()
})
}
4. App 端实现(通过 renderjs)
在 <script module="sip" lang="renderjs"> 中编写与 H5 端完全相同的 SIP 初始化代码,因为 renderjs 可以直接访问浏览器 API。
<script module="sip" lang="renderjs">
import JsSIP from 'jssip'
export default {
methods: {
initSIP() {
const socket = new JsSIP.WebSocketInterface('wss://your-sip-server:7443')
const config = {
sockets: [socket],
uri: 'sip:1001@your-sip-server',
password: 'your_password',
}
this.ua = new JsSIP.UA(config)
this.ua.on('registered', () => console.log('App 注册成功'))
this.ua.start()
},
makeCall(number) {
if (this.ua) {
const session = this.ua.call(`sip:${number}@your-sip-server`, {
mediaConstraints: { audio: true, video: false }
})
// 可监听 session 事件
}
}
}
}
</script>
5. 逻辑层统一调用入口
通过条件编译 #ifdef 分别调用不同平台的实现:
export default {
data() {
return {
ua: null, // 仅 H5 使用
}
},
methods: {
// 统一注册入口
register() {
// #ifdef H5
this.initSIP_H5()
// #endif
// #ifdef APP-PLUS
this.$refs.renderSIP.initSIP()
// #endif
},
// 统一拨号入口
makeCall(number) {
// #ifdef H5
if (this.ua) {
this.ua.call(`sip:${number}@your-sip-server`, {
mediaConstraints: { audio: true, video: false }
})
}
// #endif
// #ifdef APP-PLUS
this.$refs.renderSIP.makeCall(number)
// #endif
},
}
}
注意:
this.$refs.renderSIP需要为<sip>组件设置ref="renderSIP"。
模板部分:
<template>
<view>
<!-- 其它 UI 内容 -->
<!-- App 端通过 renderjs 组件实现 -->
<sip ref="renderSIP" v-if="isApp" />
</view>
</template>
<script>
export default {
computed: {
isApp() {
// #ifdef APP-PLUS
return true
// #endif
// #ifndef APP-PLUS
return false
// #endif
}
}
}
</script>
6. 拨号界面与调用示例
<input type="text" v-model="phoneNumber" placeholder="请输入号码" />
<button @click="makeCall(phoneNumber)">拨打电话</button>
四、关键代码解析
为什么 App 端必须使用 renderjs?
- uni-app 逻辑层运行在独立的 JS 引擎(如 V8、jscore),没有
window和navigator对象,无法直接使用 WebRTC API。 renderjs运行在 WebView 的渲染线程中,拥有完整的浏览器 API,可以调用getUserMedia、WebSocket 等。- 通过
this.$ownerInstance或组件引用,可以在 renderjs 与逻辑层之间传递数据。
配置项说明
| 配置项 | 说明 |
|---|---|
WebSocketInterface |
与 SIP 服务器建立 WebSocket 连接,替代传统 UDP/TCP |
uri |
分机标识,格式 sip:分机号@服务器域名 |
mediaConstraints |
设置通话媒体,音频必须为 true,视频按需 |
五、跨端兼容处理技巧
1. 使用条件编译区分平台
// #ifdef H5
this.initSIP_H5()
// #endif
// #ifdef APP-PLUS
this.$refs.renderSIP.initSIP()
// #endif
2. 统一状态反馈
可以通过 $emit 将 renderjs 中的注册/通话事件传回逻辑层:
renderjs 中:
this.ua.on('registered', () => {
this.$ownerInstance.callMethod('onSipRegistered', '成功')
})
逻辑层中:
onSipRegistered(msg) {
uni.showToast({ title: msg })
}
六、常见问题与注意事项
1. WebSocket 连接失败
- 检查 SIP 服务器是否开启
wss服务(端口通常为 7443) - 证书是否有效(生产环境必须使用合法证书)
- App 端需要配置
plus的网络白名单(manifest.json)
2. 通话没有声音
- 确认
mediaConstraints中audio: true - 检查麦克风权限(H5 需要 HTTPS 环境,App 需要原生权限配置)
- 部分 Android WebView 需要媒体授权,可调用
plus.android.requestPermissions
3. H5 端动态导入问题
import('jssip') 需要 Webpack 支持 code splitting。如果遇到打包异常,可改为同步导入:
import * as JsSIP from 'jssip'
// 或
const JsSIP = require('jssip')
但同步导入会增加主包体积,建议仍用动态导入。
4. App 端退出页面时销毁 UA
beforeDestroy() {
// #ifdef APP-PLUS
if (this.$refs.renderSIP && this.$refs.renderSIP.ua) {
this.$refs.renderSIP.ua.stop()
}
// #endif
}
七、完整示例代码片段
此处仅展示核心差异部分,完整代码已上传 Gist(搜索 “uni-app-sip-demo”)
<template>
<view>
<!-- 普通 UI 部分省略 -->
<sip ref="renderSIP" v-if="isApp" />
</view>
</template>
<script module="sip" lang="renderjs">
import JsSIP from 'jssip'
let ua = null
export default {
methods: {
initSIP() {
const socket = new JsSIP.WebSocketInterface('wss://your-domain:7443')
const config = { sockets: [socket], uri: 'sip:1001@your-domain', password: 'xxx' }
ua = new JsSIP.UA(config)
ua.on('registered', () => console.log('registered'))
ua.start()
},
makeCall(number) {
if (ua) ua.call(`sip:${number}@your-domain`, { mediaConstraints: { audio: true } })
}
}
}
</script>
<script>
export default {
computed: {
isApp() { /* #ifdef APP-PLUS */ return true /* #endif */ return false }
},
methods: {
register() {
// #ifdef H5
import('jssip').then(({ default: JsSIP }) => {
const socket = new JsSIP.WebSocketInterface('wss://your-domain:7443')
this.ua = new JsSIP.UA({ sockets: [socket], uri: 'sip:1001@your-domain', password: 'xxx' })
this.ua.start()
})
// #endif
// #ifdef APP-PLUS
this.$refs.renderSIP.initSIP()
// #endif
}
}
}
</script>
八、总结
通过 renderjs 和条件编译,我们成功在 同一份 uni-app 代码 中实现了 H5 和 App 端的 SIP 通话功能。核心要点:
- H5 端直接使用
jssip - App 端使用
renderjs封装jssip - 通过
$refs和条件编译提供统一 API - 注意处理权限、证书、页面生命周期
该方案适用于大多数需要跨端语音通话的业务场景,可以在此基础上扩展视频通话、通话记录、多方会议等功能。
参考资料:
更多推荐


所有评论(0)