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),没有 windownavigator 对象,无法直接使用 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. 通话没有声音

  • 确认 mediaConstraintsaudio: 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
  • 注意处理权限、证书、页面生命周期

该方案适用于大多数需要跨端语音通话的业务场景,可以在此基础上扩展视频通话、通话记录、多方会议等功能。


参考资料:

Logo

一站式 AI 云服务平台

更多推荐