更新记录
1.0.0(2023-06-08)
基于linphone 开发uni插件,把晦涩难懂混乱的API做了归类简化。理论上linphone能做的都可以实现。
- 高清音频和视频通话
- 语音/视频会议和群呼
- 端到端加密的安全通信
- 即时通讯、群聊、文件共享
- 完全基于sip,支持所有呼叫、在线状态和即时消息功能
平台兼容性
Android | Android CPU类型 | iOS |
---|---|---|
适用版本区间:6.0 - 12.0 | armeabi-v7a:未测试,arm64-v8a:未测试,x86:未测试 | × |
原生插件通用使用流程:
- 购买插件,选择该插件绑定的项目。
- 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
- 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
- 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
- 开发完毕后正式云打包
付费原生插件目前不支持离线打包。
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的应用市场下载
安装
- 购买插件,选择该插件绑定的项目。
- 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块。
- 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座。
开始
怎样快速测试使用
- 在uniapp应用市场安装或试用
- 打包自定义基座
- 全局事件监听
- 插件提供的事件调用
流程
流程相对简单:全局事件监听->调用API->事件处理
- 事件
事件都是通过提供的全局事件进行监听获取,全局监听 查看事件详情
- 注册状态(onRegistrationStateChanged)
- 通话状态(onCallStateChanged)
- 音频设备(onAudioDeviceChanged)
- 音频设备列表(onAudioDevicesListUpdated)
- 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();
拨电话
参数
- 第一个为对应SIP账号或具体手机号码
- 第二个回调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: ""}
});
接听
一个参数
- 回调e为json,result空为正常,异常为异常信息
在通话状态 onCallStateChanged事件中监听执行状态
uniLinphone.acceptCall((e)=>{
// e: { result: ""}
});
挂断/拒接
参数
- 回调e为json,result空为正常,异常为异常信息
在通话状态 onCallStateChanged事件中监听执行状态
uniLinphone.handUp((e)=>{
// e: { result: ""}
});
暂停/恢复通话
该方法表示对自己静音或恢复对方显示忙音,会自动判断当前是静音还是通话中进行反向切换
// 静音和恢复
uniLinphone.pauseOrResume();
视频
视频以组件的形式进行嵌套,注意两点
- uni的原生组件要引用要用.nvue文件后缀进行开发,注意项目参考官网。
- 组件必须在注册成功后再显示,可使用指令v-if
组件引入
<!-- 对方的视频 -->
<sipplugin-remotevideo style="width: 200rpx; height: 200rpx;"></sipplugin-remotevideo>
<!-- 我的视频 -->
<sipplugin-localvideo style="width: 200rpx; height: 200rpx;"></sipplugin-localvideo>
拨打视频
参数
- 第一个为对应SIP账号或具体手机号码
- 第二个回调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);
});
进阶
通话
当前通话状态
前提:有通话时
可以通过此方法获取主叫、被叫人信息、音频状态如果需要其他需求联系我加
回调参数
- 被叫或主叫人号码
- 当前音频状态,静音/恢复状态,具体枚举值参考通话状态
uniLinphone.getCurrentCall((e)=>{
// { remoteAddress : '158xxxx1363', callState: 'Pausing' }
});
设备
插件做了自动监听新接入设备自动切换功能
切换顺序:蓝牙耳机->有线耳机->默认(听筒或扬声器)
扬声器播放
// 扬声器播放
uniLinphone.toggleSpeaker();
听筒播放
// 听筒播放
uniLinphone.toggleMicrophone();
蓝牙播放
IOS系统限制可能无效,但是IOS会自己切换
// 指定蓝牙播放
uniLinphone.toggleBluetooth();
耳机播放
IOS系统限制可能无效,但是IOS会自己切换
// 指定耳机播放:IOS系统限制可能无效
uniLinphone.toggleHeadset();
编码
获取音频编码格式
注册成功后再进行设置才会生效
一个参数
- 编码数组
uniLinphone.getAudioPayloadTypes(res=>{
// res: []
});
设置音频编码格式
res返回为空则设置成功,可通过再次调用getAudioPayloadTypes查看是否启用成功,参数params是数组,可通过getAudioPayloadTypes获取。
两个参数
- 需启用的编码数组,传空数组为全部启用
- 回调e为json,result空为正常,异常为异常信息
/**
* params: []
*/
uniLinphone.enableAudioPayloadTypes(params, e=>{
// e: { result: "" } 返回为空则设置成功
});
获取视频编码格式
注册成功后再进行设置才会生效
一个参数
- 编码数组
uniLinphone.getVideoPayloadTypes(res=>{
// res: []
});
设置视频编码格式
res返回为空则设置成功,可通过再次调用getAudioPayloadTypes查看是否启用成功,参数params是数组,可通过getAudioPayloadTypes获取。
两个参数
- 需启用的编码数组,传空数组为全部启用
- 回调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" ]
}
}
常见问题
- 通话没声音
打开APP看下录音权限是否已获取了录音权限
- 视频没画面
打开APP看下录音权限是否已获取了录音权限
- 息屏后没反应
安卓需做保活处理允许后台运行、ios需要添加UIBackgroundModes后台权限
- 注册没反应
注册执行方法与回调是分离的,注意回调是否在函数生命周期内,网络不通等待的时间会相对时间长点