更新记录

1.0.15(2024-11-18)

  1. Android socket增加data类型不对强制转换问题

1.0.14(2024-11-18)

  1. ios增加UTSVoipMgr.setVOIPEnable(enable)接口

1.0.13(2024-11-13)

  1. 增加cancelHandleBusiness取消接口
查看更多

平台兼容性

Vue2 Vue3
App 快应用 微信小程序 支付宝小程序 百度小程序 字节小程序 QQ小程序
HBuilderX 3.6.8,Android:5.0,iOS:10,HarmonyNext:不确定 × × × × × ×
钉钉小程序 快手小程序 飞书小程序 京东小程序
× × × ×
H5-Safari Android Browser 微信浏览器(Android) QQ浏览器(Android) Chrome IE Edge Firefox PC-Safari
× × × × × × × × ×

VOIP,唤醒未启动app、后台app

后台保活插件(支持熄屏)https://ext.dcloud.net.cn/plugin?id=20810

集成步骤

  1. 拷贝demo里的nativeResources、Info.plist、AndroidManifest.xml文件到项目根目录
  2. 勾选manifest.json里的app模块配置Push(消息推送),如果勾选了Push模块,删除nativeResources/ios/UniApp.entitlements
  3. nativeResources/ios/UniApp.entitlements文件里的aps-environment对应的值是development、production,一般开发阶段使用development,生产发布使用production,根据情况设置
  4. ios打包的.mobileprovision需要包含消息推送功能
  5. 集成插件,集成插件步骤请参考 https://www.cnblogs.com/wenrisheng/p/18323027
  6. android需要在app设置里开启“自启动”、“悬浮窗”权限,在系统设置里面搜索“无障碍”,进入“已下载的服务”里开启无障碍服务,在系统设置里面搜索“使用情况访问权限”,开启“允许查看使用情况”

iOS VOIP服务器配置

  1. 登录苹果开发者系统生成VOIP证书(百度有很多资料),这个证书是voip的证书(Certificates->Services->VoIP Services Certificate),不是消息推送的证书
  2. 将cer证书和p12文件生成各自后端语言支持的证书,如:
  3. 其他语言可以参考苹果官方https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns

nodeJS语言服务器

服务器代码在demo/static/project-voip-server下 nodeJS语言需要cer和p12转成pem


cer转pem:
openssl x509 -inform DER -in voip_services.cer -outform PEM -out cer.pem

p12转pem:
openssl pkcs12 -in voiptext.p12 -nodes -password pass:1234 -legacy -out key.pem

或者去掉-legacy,有些版本的openssl不需要-legacy参数:
openssl pkcs12 -in voiptext.p12 -nodes -password pass:1234 -legacy -out key.pem

将cer.pem、key.pem文件分别替换project-voip-server/customer_cer/mine下的文件 修改project-voip-server/src/app.controller.ts代码下的passphrase证书密码:


 // 证书密码
const passphrase = '123456';

进入project-voip-server文件夹执行启动安装依赖和启动服务命令:


// 安装nestJS
npm i -g @nestjs/cli

// 安装依赖
npm install

// 启动服务
npm run start:dev

浏览器或postman调用接口:

token的值替换成app项目里获取到的token


http://127.0.0.1:3000/apn?token=xxxxxx

java语言服务器

依赖下面第三方,具体实现接口百度有很多,有需要可以咨询作者 java语言需要cer和p12转成一个p12


 <dependency>
          <groupId>com.turo</groupId>
          <artifactId>pushy</artifactId>
          <version>0.13.10</version>
 </dependency>

接口

ios voip原理:

  1. app端生成token,上传给服务器,服务器调用苹果的voip接口,然后app无论是否启动都会收到voip来电,点击接听/拒绝按钮都可以唤醒app

Android voip原理: android有2个方案唤醒app

  • 方案1: 运行webSocket服务

    1. Android开启无障碍服务后会自动连接提前设置好的websocket,通过webSocket接收数据来唤醒app
  • 方案2:监听手机来电

    1. Android开启无障碍服务后,当收到voip电话或普通来电后,会调用app提前设置好的服务器接口,根据服务器接口返回数据决定是否唤醒app,无论app有没有启动都可以唤醒
    2. app被唤醒后一般需要通知拨打方挂断电话

import {
    UTSVoipMgr,
    UTSLocalNotification
} from "@/uni_modules/wrs-uts-voip"
  • 请求通知权限,仅支持ios

// 请求通知权限
UTSLocalNotification.requestAuthorization({
    types: ["badge", "sound", "alert"]
}, (resp) => {
    let flag = resp.flag
    if (!flag) { // 请求权限失败
        console.log("requestAuthorization:" + JSON.stringify(resp))
    }

})
  • 设置VOIP回调

// ios流程:
// 1. app把token上传给后端
// 2. 后端调用苹果的apn接口发送数据给苹果服务器
// 3. 苹果服务器会把消息转发给手机
// 4. 手机app收到后,先回调didReceiveIncomingPush接口
// 5. 如果消息体里的数据符合插件里定义的voip数据(消息体参考文档),则会自动拉起接听电话界面
// 6. 用户接听或拒绝电话

UTSVoipMgr.onCallback((resp) => {
    this.showMsg(JSON.stringify(resp))
    let opt = resp.opt
    switch (opt) {
        //  仅支持iOS,获取到本机token,需要上传给后端,后端发送voip时需要token参数,token可以理解为手机的ID
        case "didUpdatePushCredentials": {
            let token = resp.token
            console.log("token:" + token)
        }
        break;
        //  仅支持iOS,收到后端发送的数据
        case "didReceiveIncomingPush": {
            console.log("didReceiveIncomingPush:" + JSON.stringify(resp))
        }
        break;
        //  仅支持iOS,拉起接听电话界面结果
        case "startCall": {
            getApp().globalData.uuid = resp.uuid
            // 用户接听电话后,如果有定时器关闭电话则调用接口deleteWaitingResponseUuid取消定时器
            UTSVoipMgr.deleteWaitingResponseUuid(resp.uuid)
        }
        break;
        //  仅支持iOS,用户接听了电话
        case "performAnswerCall": {
            console.log("performAnswerCall:" + JSON.stringify(resp))
        }
        break;
        //  仅支持iOS,电话挂断
        case "performEndCall": {
            console.log("performEndCall:" + JSON.stringify(resp))
        }
        break;
        //  仅支持Android,websocket连接成功
        case "opt_onConnectSuc": {

        }
        break;
        //  仅支持Android,websocket连接失败
        case "opt_onConnectFail": {

        }
        break;
        //  仅支持Android,websocket收到数据
        case "onMessage": {

        }
        break;
        //  仅支持Android,websocket关闭
        case "onClosed": {

        }
        break;
        //  仅支持Android,websocket连接成功
        case "onCallStateChanged": {
            let state = resp.state
            switch (state) {
                // 手机空闲 CALL_STATE_IDLE
                case 0:
                    break;
                    // 手机响铃/来电 CALL_STATE_RINGING
                case 1:
                    break;
                    // 电话挂起/去电 CALL_STATE_OFFHOOK
                case 2:
                    break;
            }
        }
        break;

        default:
            break;
    }
})

后端接口发起voip参数如下:


      const payload = {
        business: {
          type: 0, // 0: 自动拉起电话接听界面
          params: { // 参数
            hasVideo: true,
            localizedCallerName: "张三来电",
            timeout: 5,// 可选参数,单位秒,传了timeout参数,无论用户有没有接听电话,定时器到了都会挂断电话,如果想取消定时器请调用deleteWaitingResponseUuid接口取消定时器
            remoteHandle: {
              type: 1, // 1: generic 2:phoneNumber 3: emailAddress
              value: "18820406059"
            }
          }
        }
      };
  • 设置通知回调,仅支持iOS
  • 初始化本地通知,仅支持iOS

UTSLocalNotification.notificationInit()
  • 发送本地通知,仅支持iOS

// 发送本地通知
UTSLocalNotification.add({
    title: "张三来电",
    subtitle: "VOIP",
    body: "您收到了一条新消息",
    sound: "default",
    trigger: {
        timeInterval: 0.2,
        repeats: false
    },
    identifier: "123456"
}, (notResp)=>{

})
  • 来电,app主动拉起来电界面,仅支持iOS

let params = { // 参数
    hasVideo: true,
    localizedCallerName: "张三来电",
    timeout: 5, // 可选参数,单位秒,传了timeout参数,无论用户有没有接听电话,定时器到了都会挂断电话,如果想取消定时器请调用deleteWaitingResponseUuid接口取消定时器
    remoteHandle: {
        type: 1, // 1: generic 2:phoneNumber 3: emailAddress
        value: "18820406059"
    }
}
UTSVoipMgr.startCall(params, (resp) => {
   let flag = resp.flag
   if(flag) {
      getApp().globalData.uuid = resp.uuid
   }
})
  • 挂断电话,包括来电和去电

let params = {}
if (this.isAndroid) {

} else { // ios
    params.uuid = getApp().globalData.uuid
}
UTSVoipMgr.endCall(params, (resp) => {
    console.log(JSON.stringify(resp))
})
  • 接听来电,仅支持iOS

let params = {}
if (this.isAndroid) {

} else { // ios
    params.uuid = getApp().globalData.uuid
}
UTSVoipMgr.answerCall(params, (resp) => {
    console.log(JSON.stringify(resp))
})
  • 去电,仅支持iOS

let params = { // 参数
    type: "generic",// generic、phoneNumber、emailAddress
    value: "张三号码",
}
UTSVoipMgr.startOffCall(params, (resp) => {
   let flag = resp.flag
   if(flag) {
      getApp().globalData.uuid = resp.uuid
   }
})
  • 是否静音,仅支持iOS

let params = {}
if (this.isAndroid) {

} else { // ios
    params.uuid = getApp().globalData.uuid
}
params.muted = true // true、false
UTSVoipMgr.mutedCall(params, (resp) => {
   let flag = resp.flag
   if(flag) {
      getApp().globalData.uuid = resp.uuid
   }
})
  • 是否保持通话,仅支持iOS

let params = {}
if (this.isAndroid) {

} else { // ios
    params.uuid = getApp().globalData.uuid
}
params.onHold = true // true、false
UTSVoipMgr.heldCall(params, (resp) => {
   let flag = resp.flag
   if(flag) {
      getApp().globalData.uuid = resp.uuid
   }
})
  • 取消自动挂断电话,仅支持iOS

UTSVoipMgr.deleteWaitingResponseUuid({
    uuid: "xxx"
})
  • 获取app运行状态,仅支持iOS

let state = UTSVoipMgr.getApplicationState()
switch (state) {
    // active
    case 0:
        break;
        // unactive
    case 1:
        break;
        // background
    case 2:
        break;
    default:
        break;
}
  • 设置app是否处理后端发起的voip

let enable = true // 默认为true,当为false时,app不处理后端发送的voip业务
UTSVoipMgr.setVOIPEnable(enable)
  • 设置桌面icon角标,仅支持iOS

let badge = 0
UTSLocalNotification.setBadgeNum(badge, (resp)=>{
    console.log(JSON.stringify(resp))
})
  • 设置来电调用接口的数据,仅支持Android

let biz = {}

// 第一种:code = 0
// 设置来电时调用的接口服务器
// 服务器收到的数据如下:
// pkg、callState、appState、phoneNumber这几个字段是插件自动添加的
// pkg: 包名
// callState: 来电状态,0: 空闲/挂断  1: 来电响铃  2:去电挂起
// phoneNumber: 来电手机号,不一定能获取到
// 请求
// request: {"userId":"xxx","pkg":"uni.UNI806E8E1","appState":2,"callState":1,"phoneNumber":"18820406059"}
// 响应
// response: {"code": "0", "pkg": "uni.UNI806E8E1"}
// code: 0表示要唤醒app,pkg表示唤醒app的包名

// biz.code = 0 // 0: 收到来电时,当app未启动、后台运行、后台运行时,自动调用服务器接口
// biz.serverInfo = {
//  url: "http://192.168.0.101:3000/post",
//  method: "POST",
//  data: {
//      userId: "xxx"
//  },
//  header: {
//      token: "45asdfasfd"
//  },
//  timeout: 5
// }

// 第二种:code = 1: 采用websocket,自动调用服务器接口,通过服务端webSocket来唤醒app
// 当收到数据为:  {"code": "0", "pkg": "uni.UNI806E8E1"}          
// code: 0表示要唤醒app,pkg表示唤醒app的包名
biz.code = 1 // 1: 采用websocket,自动调用服务器接口,通过服务端webSocket来唤醒app
biz.serverInfo = {
    url: "ws://172.16.11.13:8088",
    onConnectSucSendData: { // 连接成功后会发送一次数据,一般用来校验userID/token之类的
        data: "userID:010",
        type: "txt" // type支持txt、bytes,当type为txt时,data是字符串,当type为bytes,data是十六进制字节数字
    },
    heartbeatData: { // 心跳
        // data: [0x00, 0x01, 0x00, 0x01],
        // type: "byte"
        data: "heartbeat",
        type: "txt"
    },
    heartbeatTimer: 3000 // 心跳间隔时间
}
biz.notification = this.getAndroidNotification() // 设置前台保活通知
UTSVoipMgr.setHandleBusiness(biz)

type:type支持txt、bytes

  1. 当type为txt时,data是字符串

{
    data: "userID:010",
    type: "txt" 
}
  1. 当type为bytes,data是十六进制字节数字

{
    data: [0x00, 0x01, 0x00, 0x01],
    type: "bytes" 
}

webSocket、http响应数据的处理:


{"code": "0", "pkg": "uni.UNI806E8E1"}
  • code业务码,如果更多业务请联系作者定制,0:表示唤醒app到前台运行

  • pkg: 唤醒app的包名

  • 取消HandleBusiness业务,会断开socket


UTSVoipMgr.cancelHandleBusiness()
  • 初始化电话监听,仅支持Android

UTSVoipMgr.initPhoneStateListener()
  • 去电拨打电话,仅支持Android

let params = {}
params.uri = "tel:" + this.tel
UTSVoipMgr.startCall(params)
  • 是否开启悬浮窗权限,没有开启则跳转到开启页面,仅支持Android

let hasPermission = UTSVoipMgr.canDrawOverlays();
if (!hasPermission) {
    this.showModel("当前没有悬浮窗权限,是否去打开权限?", () => {
        UTSVoipMgr.goToOpenOverlaysSetting()
    }, () => {

    })
}
  • 跳转到app设置页,仅支持Android

UTSVoipMgr.goToAppSettings()
  • 跳转到无障碍服务设置页面,仅支持Android

UTSVoipMgr.goToAccessibilitySettings()

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问