更新记录

1.0.0(2023-06-08)

基于linphone 开发uni插件,把晦涩难懂混乱的API做了归类简化。理论上linphone能做的都可以实现。

  • 高清音频和视频通话
  • 语音/视频会议和群呼
  • 端到端加密的安全通信
  • 即时通讯、群聊、文件共享
  • 完全基于sip,支持所有呼叫、在线状态和即时消息功能

平台兼容性

Android Android CPU类型 iOS
适用版本区间:6.0 - 12.0 armeabi-v7a:未测试,arm64-v8a:未测试,x86:未测试 ×

原生插件通用使用流程:

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
  3. 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
  4. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
  5. 开发完毕后正式云打包

付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios

注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择


介绍

是什么?

基于linphone 开发的uni插件,把晦涩难懂混乱的API做了归类简化。理论上linphone能做的都可以实现。

  • 高清音频和视频通话
  • 语音/视频会议和群呼
  • 端到端加密的安全通信
  • 即时通讯、群聊、文件共享
  • 完全基于sip,支持所有呼叫、在线状态和即时消息功能

示例项目

  • uni的应用市场下载

安装

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块。
  3. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座。

开始

怎样快速测试使用

  1. 在uniapp应用市场安装或试用
  2. 打包自定义基座
  3. 全局事件监听
  4. 插件提供的事件调用

流程

流程相对简单:全局事件监听->调用API->事件处理

  1. 事件

事件都是通过提供的全局事件进行监听获取,全局监听 查看事件详情

  • 注册状态(onRegistrationStateChanged)
  • 通话状态(onCallStateChanged)
  • 音频设备(onAudioDeviceChanged)
  • 音频设备列表(onAudioDevicesListUpdated)
  1. API

核心是拨打和接听电话

  • 直接参考API

登录注册

  • 全局监听引入 -> 事件监听 -> 注册 -> onRegistrationStateChanged监听注册结果

接听拨打

接听和拨打需建立在已注册完成的基础上

  • 拨打:插件引入 → 调用call → onCallStateChanged 通话状态监听
  • 接听:插件引入 → onCallStateChanged 通话状态监听 -> 调用accept

拒听或挂断

  • 拒听或挂断:插件引入 → onCallStateChanged 通话状态监听 -> 调用reject

音频设备变换

接入蓝牙、耳机会触发此监听方法,默认系统监听到有新的设备接入会自动切换,当然如果你不想,可以在这个回调里自行调用需要播放的音频设备

  • onAudioDevicesListUpdated

核心概念

全局监听

监听事件都需通过uniapp提供的globalEvent进行监听

var globalEvent = uni.requireNativePlugin("globalEvent");

注册状态

当调用注册(register)注销(unregister)会触发此方法的监听回调

  • 使用
globalEvent.addEventListener("onRegistrationStateChanged",  (e) => {
    // e = { state: 'None' }
  uni.showToast({
    title: "onRegistrationStateChanged:" + JSON.stringify(e),
    icon: "none",
  });
});
  • 枚举值
export const REGISTER_STATUS = Object.freeze({
  "None": "None",        ///Initial state for registrations.
  "Process":"Process",   ///Registration is in progress.
  "Ok": "Ok",            ///Registration is successful.
  "Cleared": "Cleared",  ///Unregistration succeeded.
  "Failed": "Failed",    ///Registration failed.
});

通话状态

当拨打语音/视频电话、切换语音/视频、有来电等等与通话相关的操作都会触发此方法的监听回调,可根据对应枚举值做对应业务处理

  • 使用
globalEvent.addEventListener("onCallStateChanged",  (e) => {
  // e = { state: 'Idle' }
  uni.showToast({
    title: "onCallStateChanged收到:" + JSON.stringify(e),
    icon: "none",
  });
});
  • 枚举值
/**
 * 通话状态
 */
export const CALL_STATUS = {
  "Idle":"Idle",                                  /// Initial state.
  "IncomingReceived": "IncomingReceived",         /// Incoming call received.
  "PushIncomingReceived": "PushIncomingReceived", /// PushIncoming call received.
  "OutgoingInit": "OutgoingInit",                 /// Outgoing call initialized.
  "OutgoingProgress": "OutgoingProgress",         /// Outgoing call in progress.
  "OutgoingRinging": "OutgoingRinging",           /// Outgoing call ringing.
  "OutgoingEarlyMedia": "OutgoingEarlyMedia",     /// Outgoing call early media.
  "Connected": "Connected",                       /// Connected.
  "StreamsRunning": "StreamsRunning",             /// Streams running.
  "Pausing": "Pausing",                           /// Pausing.
  "Paused": "Paused",                             /// Paused.
  "Resuming": "Resuming",                         /// Resuming.
  "Referred": "Referred",                         /// Referred.
  "Error": "Error",                               /// Error.
  "End": "End",                                   /// Call end.
  "PausedByRemote": "PausedByRemote",             /// Paused by remote.
  "UpdatedByRemote": "UpdatedByRemote",           /// The call's parameters are updated for example when video is asked by remote.
  "IncomingEarlyMedia": "IncomingEarlyMedia",     /// We are proposing early media to an incoming call.
  "Updating": "Updating",                         /// We have initiated a call update.
  "Released": "Released",                         /// The call object is now released.
  "EarlyUpdatedByRemote": "EarlyUpdatedByRemote", /// The call is updated by remote while not yet answered (SIP UPDATE in early dialog received)
  "EarlyUpdating": "EarlyUpdating",               /// We are updating the call while not yet answered (SIP UPDATE in early dialog sent)
};

设备状态

插件已经做了当有新的设备接入时自动切换到新设备的功能

当音频硬件发生变化时(即当插拔用手机,连接断开蓝牙)会触发这个回调

globalEvent.addEventListener("onAudioDevicesListUpdated",  (e) => {
  uni.showToast({
    title: "onAudioDevicesListUpdated:" + JSON.stringify(e),
    icon: "none",
  });
});

常用API

API 引用使用sipplugin-phone

let uniLinphone = uni.requireNativePlugin("sipplugin-phone");

通话

注册

可通过监听注册状态onRegistrationStateChanged事件判断注册成功或失败,回调e为json,result空为正常,异常为异常信息

最后一个回调只是返回register的执行结果有异常会在此抛出,并不是真正注册成功失败事件

/**
 * 参数dommain:服务端IP或域名
 * 参数username:账号
 * 参数passwd:密码
 * 参数transportType: 传输协议,默认UDP协议,枚举值 0:Udp 1:Tcp 2:Tls 3:Dtls  
 * 参数ringStoneUrl: 铃声URL(目前只对ios有用)
 * 参数4:回调方法,回调参数方法是否执行成功,注册成功或失败在onRegistrationStateChanged监听
 */
uniLinphone.register({"domain":"xxx.xxx.com","username":"02010020","passwd":"xxxx","transportType": "0",  "ringStoneUrl":"https://www.cambridgeenglish.org/images/153149-movers-sample-listening-test-vol2.mp3"},(e)=>{
  // e: { result: ""}
});

注销

可通过监听注册状态onRegistrationStateChanged事件判断注销成功或失败

// 注销
uniLinphone.unregister();

拨电话

参数

  1. 第一个为对应SIP账号或具体手机号码
  2. 第二个回调e为json,result空为正常,异常为异常信息

通话状态 onCallStateChanged事件中监听执行状态

// 拨打sip电话
uniLinphone.callPhone("sip:xxx@xxx.xxx.xxx", (e)=>{
 // e: { result: ""}
});

// 拨打真实号码
uniLinphone.callPhone("sip:158xxxxxxx@xxx.xxx.xxx", (e)=>{
 // e: { result: ""}
});

接听

一个参数

  1. 回调e为json,result空为正常,异常为异常信息

通话状态 onCallStateChanged事件中监听执行状态

uniLinphone.acceptCall((e)=>{
  // e: { result: ""}
});

挂断/拒接

参数

  1. 回调e为json,result空为正常,异常为异常信息

通话状态 onCallStateChanged事件中监听执行状态

uniLinphone.handUp((e)=>{
  // e: { result: ""}
});

暂停/恢复通话

该方法表示对自己静音或恢复对方显示忙音,会自动判断当前是静音还是通话中进行反向切换

// 静音和恢复
uniLinphone.pauseOrResume();

视频

视频以组件的形式进行嵌套,注意两点

  1. uni的原生组件要引用要用.nvue文件后缀进行开发,注意项目参考官网。
  2. 组件必须在注册成功后再显示,可使用指令v-if

组件引入

<!-- 对方的视频 -->
<sipplugin-remotevideo style="width: 200rpx; height: 200rpx;"></sipplugin-remotevideo>
<!-- 我的视频 -->
<sipplugin-localvideo  style="width: 200rpx; height: 200rpx;"></sipplugin-localvideo>

拨打视频

参数

  1. 第一个为对应SIP账号或具体手机号码
  2. 第二个回调e为json,result空为正常,异常为异常信息

通话状态 onCallStateChanged事件中监听执行状态

/**
* 拨打视频电话
*/
uniLinphone.callVideoPhone("sip:xxx@xxx.xx.xxx", (e) => {
  this.info = JSON.stringify(e);
});

打开/关闭视频

关闭视频后就是语音通话

uniLinphone.toggleVideo();

切换摄像头

前置后置摄像头切换

uniLinphone.toggleCamera();

挂断/拒接

和音频的一样,直接看这挂断/拒接

DTMF

发送DTMF

通话过程中会有发送数字或字符需求,可以过该方法发送

/**
* 发送DTMF
* @dtmft 需要发送的字符
* @res 回调结果,result为空,执行功能不然为错误信息
*/
uniLinphone.sendDTMF({
  "dtmfs": "#"
},(res) => {
  this.info  = JSON.stringify(e);
});

进阶

通话

当前通话状态

前提:有通话时

可以通过此方法获取主叫、被叫人信息、音频状态如果需要其他需求联系我加

回调参数

  1. 被叫或主叫人号码
  2. 当前音频状态,静音/恢复状态,具体枚举值参考通话状态
uniLinphone.getCurrentCall((e)=>{
    // { remoteAddress : '158xxxx1363', callState: 'Pausing' }
});

设备

插件做了自动监听新接入设备自动切换功能

切换顺序:蓝牙耳机->有线耳机->默认(听筒或扬声器)

扬声器播放

// 扬声器播放
uniLinphone.toggleSpeaker();

听筒播放

// 听筒播放
uniLinphone.toggleMicrophone();

蓝牙播放

IOS系统限制可能无效,但是IOS会自己切换

// 指定蓝牙播放
uniLinphone.toggleBluetooth();

耳机播放

IOS系统限制可能无效,但是IOS会自己切换

// 指定耳机播放:IOS系统限制可能无效
uniLinphone.toggleHeadset();

编码

获取音频编码格式

注册成功后再进行设置才会生效

一个参数

  1. 编码数组
uniLinphone.getAudioPayloadTypes(res=>{
  // res: []
});

设置音频编码格式

res返回为空则设置成功,可通过再次调用getAudioPayloadTypes查看是否启用成功,参数params是数组,可通过getAudioPayloadTypes获取。

两个参数

  1. 需启用的编码数组,传空数组为全部启用
  2. 回调e为json,result空为正常,异常为异常信息
/**
*  params: []
*/
uniLinphone.enableAudioPayloadTypes(params, e=>{
  // e: { result: "" } 返回为空则设置成功
});

获取视频编码格式

注册成功后再进行设置才会生效

一个参数

  1. 编码数组
uniLinphone.getVideoPayloadTypes(res=>{
  // res: []
});

设置视频编码格式

res返回为空则设置成功,可通过再次调用getAudioPayloadTypes查看是否启用成功,参数params是数组,可通过getAudioPayloadTypes获取。

两个参数

  1. 需启用的编码数组,传空数组为全部启用
  2. 回调e为json,result空为正常,异常为异常信息
/**
*  params: []
*/
uniLinphone.enableVideoPayloadTypes(params, e=>{
  // e: { result: "" } 返回为空则设置成功
});

注意事项

权限

云打包(打基座或正式包)

模块配置勾选: Bluetooth、Camera&Gallery、Record

![image-20230512114241051](/Users/pee/Library/Application Support/typora-user-images/image-20230512114241051.png)

权限配置勾选:

"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",

APP原生插件配置:

根据自个情况选择本地或云端插件,不能都选

其他配置

ios需添加UIBackgroundModes

"distribute" : {
    /* ios打包配置 */
    "ios" : {
      "UIBackgroundModes" : [ "audio" ]
    }
}

常见问题

  1. 通话没声音

打开APP看下录音权限是否已获取了录音权限

  1. 视频没画面

打开APP看下录音权限是否已获取了录音权限

  1. 息屏后没反应

安卓需做保活处理允许后台运行、ios需要添加UIBackgroundModes后台权限

  1. 注册没反应

注册执行方法与回调是分离的,注意回调是否在函数生命周期内,网络不通等待的时间会相对时间长点

隐私、权限声明

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

"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", "<uses-permission android:name=\"android.permission.BLUETOOTH\"/>", "<uses-feature android:name=\"android.hardware.camera\"/>", "<uses-permission android:name=\"android.permission.CAMERA\"/>",

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

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

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