1V1语音通话流程
先要了解流程是怎样的,起初写的时候就像一只无头苍蝇
上图来自于腾讯云 TRTC云助手cloud.tencent.com/document/pr…
官方文档
1V1音视频通话场景通常需要依赖腾讯云 即时通信 IM 和 实时音视频 TRTC 两项付费 PaaS 服务构建。
- 腾讯云即时通信:cloud.tencent.com/document/pr…
- 腾讯云实时音视频:cloud.tencent.com/document/pr…
创建uts插件
整体目录结构
在app-android文件下创建config.json文件,添加远程依赖(doc.dcloud.net.cn/uni-app-x/p…
{
"minSdkVersion": "21",
"abis": [
"arm64-v8a","armeabi-v7a"
],
"dependencies": [
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20",
"com.tencent.liteav:LiteAVSDK_TRTC:12.6.0.18891",
"com.tencent.imsdk:imsdk-plus:8.6.7019"
]
}
鉴权凭证(cloud.tencent.com/document/pr…
正确的 UserSig 签发方式是将 UserSig 的计算代码集成到服务端,为了方便测试放在了客户端
GenerateTestUserSig.java文件中SDKAPPI和SECRETKEY值 需要修改 (开通了在控制台查看)
在index.uts 中调用
import { GenerateTestUserSig } from "com.tencent.qcloud.tim.demo.signature";
// 获取签名 鉴权凭证
export function generateSign(userID:string):string{
const signText = GenerateTestUserSig.genTestUserSig(userID) // 静态方法应该通过类名直接调用
return signText
}
如果这一步测试的话,一定要打基座 !!!
主要代码index.uts
引入IM 和 TRTC 需要用到的类
// IM
import V2TIMManager from 'com.tencent.imsdk.v2.V2TIMManager';
import V2TIMSDKListener from 'com.tencent.imsdk.v2.V2TIMSDKListener'
import V2TIMSDKConfig from 'com.tencent.imsdk.v2.V2TIMSDKConfig'
import V2TIMLogListener from 'com.tencent.imsdk.v2.V2TIMLogListener'
import V2TIMSignalingManager from 'com.tencent.imsdk.v2.V2TIMSignalingManager'
import V2TIMOfflinePushInfo from 'com.tencent.imsdk.v2.V2TIMOfflinePushInfo'
// Trtc
import TRTCCloud from 'com.tencent.trtc.TRTCCloud';
import TRTCCloudDef from 'com.tencent.trtc.TRTCCloudDef';
创建IM 相关类 封装登录 信令相关的方法 index.uts文件中
export class CallIM {
config : V2TIMSDKConfig
private instance : V2TIMManager
private imListener : V2TIMSDKListener
private signalingInstance : V2TIMSignalingManager
private callObserver : SignalingListener
private listenerMap : Map<string, Array<(res : any) => void>> = new Map();
constructor() {
this.config = new V2TIMSDKConfig()
// 指定 log 输出级别
this.config.setLogLevel(V2TIMSDKConfig.V2TIM_LOG_INFO);
// 2. 创建日志监听器
const loglistener = new MyLogListener()
this.config.setLogListener(loglistener)
this.instance = V2TIMManager.getInstance() // 获取 V2TIMManager 管理器实例
this.signalingInstance = V2TIMManager.getSignalingManager() // 信令管理类实例
// 添加事件监听器
this.imListener = new ImSdkListener({
listener: (eventName : string, data : any) => {
this.handleEvent(eventName, data)
},
})
this.instance.addIMSDKListener(this.imListener)
// 添加信令监听器
const callObserve : ICallObserve = {
listener: (eventName : string, data : any) => {
this.handleEvent(eventName, data)
},
};
this.callObserver = new SignalingListener(callObserve)
this.signalingInstance.addSignalingListener(this.callObserver)
// 初始化
this.instance.initSDK(context, sdkAppId.toInt(), this.config)
}
// 处理事件分发
private handleEvent(eventName : string, data : any) {
const listeners = this.listenerMap.get(eventName)
if (listeners != null) {
listeners.forEach(listener => {
try {
listener(data)
} catch (e) {
console.error(`事件监听器执行错误 [${eventName}]`, e)
}
})
}
}
// 登录
public login(options : LoginOptions) {
const cb : ICallback = {
apiName: "login",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
console.log('options===', options)
this.instance.login(options.userID, options.userSig, new Callback(cb))
}
// 登出
public logout(options : CallbackOptions) {
const cb : ICallback = {
apiName: "logout",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
this.instance.logout(new Callback(cb))
}
// 发送邀请
public inviteCall(options : InviteCallOptions):string|null {
const cb : ICallback = {
apiName: "inviteCall",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
console.log('inviteCall===', options)
let v2TIMOfflinePushInfo = new V2TIMOfflinePushInfo();
// v2TIMOfflinePushInfo.setExt(new Gson().toJson(containerBean).getBytes());
// // OPPO 必须设置 ChannelID 才可以收到推送消息,这个 ChannelID 需要和控制台一致
// v2TIMOfflinePushInfo.setAndroidOPPOChannelID("tuikit");
// v2TIMOfflinePushInfo.setAndroidHuaWeiCategory("IM");
// v2TIMOfflinePushInfo.setAndroidVIVOCategory("IM");
v2TIMOfflinePushInfo.setTitle(options.offlinePushInfo.title as string);
v2TIMOfflinePushInfo.setDesc(options.offlinePushInfo.description as string);
// v2TIMOfflinePushInfo.setDesc("You have a new call invitation");
// // 设置自定义铃声
// v2TIMOfflinePushInfo.setIOSSound("phone_ringing.mp3");
// v2TIMOfflinePushInfo.setAndroidSound("phone_ringing");
return this.signalingInstance.invite(options.invitee, options.data, options.onlineUserOnly, v2TIMOfflinePushInfo, options.timeout.toInt(), new Callback(cb))
}
// 取消邀请
public cancelCall(options : AcceptCallOptions) {
const cb : ICallback = {
apiName: "cancelCall",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
this.signalingInstance.cancel(options.inviteId, options.data, new Callback(cb))
}
// 同意接听信令
public acceptCall(options : AcceptCallOptions) {
const cb : ICallback = {
apiName: "acceptCall",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
this.signalingInstance.accept(options.inviteId, options.data, new Callback(cb))
}
// 拒绝接听信令
public rejectCall(options : AcceptCallOptions) {
const cb : ICallback = {
apiName: "rejectCall",
success: () => {
options.success?.();
},
fail: (errCode : number, errMsg : string) => {
options.fail?.(errCode, errMsg);
},
};
this.signalingInstance.reject(options.inviteId, options.data, new Callback(cb))
}
// 信令相关事件监听 如收到邀请
@UTSJS.keepAlive
public on(eventName : string, listener : (res : any) => void) : void {
const listeners : Array<(res : any) => void> = [listener];
this.listenerMap.get(eventName)?.forEach((item) => {
listeners.push(item);
});
this.listenerMap.set(eventName, listeners);
}
// 取消事件监听
public off(eventName : string) : void {
this.listenerMap.delete(eventName)
// this.signalingInstance.removeSignalingListener(this.callObserver)
}
// 销毁
public destroy() {
this.instance.removeIMSDKListener(this.imListener)
this.signalingInstance.removeSignalingListener(this.callObserver)
this.instance.unInitSDK()
}
}
type IMSDkListenerType = {
listener : (eventType : string, data : any) => void
}
// 实现IM监听
class ImSdkListener extends V2TIMSDKListener{
// 必须调用父类构造函数
private listener : (eventType : string, data : any) => void;
constructor(options:IMSDkListenerType) {
super()
this.listener = options.listener;
}
override onConnecting() {
console.log('IM SDK 正在连接到腾讯云服务器')
}
override onConnectSuccess() {
console.log('IM SDK 已经成功连接到腾讯云服务器')
}
override onKickedOffline() {
this.listener('onKickedOffline',{})
console.log('当前用户被踢下线,此时可以 UI 提示用户')
}
}
// 日志监听
class MyLogListener extends V2TIMLogListener {
// 必须调用父类构造函数
constructor() {
super() // 显式调用父类构造函数
}
override onLog(logLevel: Int, logContent: string) {
console.log(`[CustomLog][${logLevel}] ${logContent}`)
// 这里添加您的日志处理逻辑
// logContent 为 SDK 日志内容
}
}
trtc 相关类 接听后进入房间 退出房间
// TRTC 类 进房通话
export class TrtcCloud {
private mTRTCCloud:TRTCCloud; // 声明成员变量
private trtcListener:TrtcListener;
private listenerMap: Map<string, Array<(res: any) => void>> = new Map();
constructor(){
// 创建 TRTC 实例
this.mTRTCCloud = TRTCCloud.sharedInstance(context);
// 设置监听
const trtcObserve: trtcListenerType = {
listener: (eventName: string, data: any) => {
this.handleEvent(eventName, data)
},
};
this.trtcListener = new TrtcListener(trtcObserve)
this.mTRTCCloud.addListener(this.trtcListener)
}
// 处理事件分发
private handleEvent(eventName: string, data: any) {
const listeners = this.listenerMap.get(eventName)
if (listeners != null) {
listeners.forEach(listener => {
try {
listener(data)
} catch (e) {
console.error(`事件监听器执行错误 [${eventName}]`, e)
}
})
}
}
// 事件监听
@UTSJS.keepAlive
public on(eventName: string, listener: (res: any) => void): void {
const listeners: Array<(res: any) => void> = [listener];
this.listenerMap.get(eventName)?.forEach((item) => {
listeners.push(item);
});
this.listenerMap.set(eventName, listeners);
}
// 取消事件监听
public off(eventName: string): void {
this.listenerMap.delete(eventName)
this.mTRTCCloud.removeListener(this.trtcListener)
}
// 进房开启通话
public startAudioCall(options:StartAudioCallOptions) {
let params = new TRTCCloudDef.TRTCParams();
params.sdkAppId = sdkAppId.toInt(); // TRTC应用标识, 在控制台获取
params.userSig = options.userSig; // TRTC鉴权凭证, 在服务端生成
params.roomId = options.roomId.toInt(); // 房间号,数字
params.userId = options.userID; // 用户名, 建议和IM保持同步
console.log('params===',params)
this.mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_SPEECH);
this.mTRTCCloud.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER); // 扬声器播放
this.mTRTCCloud.enterRoom(params, TRTCCloudDef.TRTC_APP_SCENE_AUDIOCALL);
}
// 挂断通话
public endAudioCall() {
this.mTRTCCloud.stopLocalAudio();
this.mTRTCCloud.exitRoom();
}
}
回调封装 #### 登录,登出,同意接听信令,拒绝接听信令操作回调
import V2TIMCallback from 'com.tencent.imsdk.v2.V2TIMCallback'
export type ICallback = {
apiName : string,
success : () => void,
fail : (errCode : number, errMsg : string) => void,
}
// 实现回调
export class Callback extends V2TIMCallback{
private apiName : string;
private success : () => void;
private fail : (errCode : number, errMsg : string) => void;
constructor(options : ICallback) {
super();
this.apiName = options.apiName;
this.success = options.success;
this.fail = options.fail;
}
override onSuccess() {
console.log(`${this.apiName} success`);
this.success();
}
override onError(code:Int, desc:string) {
// 如果返回以下错误码,表示使用 UserSig 已过期,请您使用新签发的 UserSig 进行再次登录。
// 1. ERR_USER_SIG_EXPIRED(6206)
// 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED(70001)
// 注意:其他的错误码,请不要在这里调用登录接口,避免 IM SDK 登录进入死循环。
console.log(`${this.apiName} failure, code:${code}, desc:${desc}`);
this.fail(code as number,desc);
}
}
实现信令监听
import V2TIMSignalingListener from 'com.tencent.imsdk.v2.V2TIMSignalingListener'
export type ICallObserve = {
listener : (eventType : string, data : any) => void
}
// 实现信令监听
export class SignalingListener extends V2TIMSignalingListener{
private listener : (eventType : string, data : any) => void;
// 必须调用父类构造函数
constructor(options : ICallObserve) {
super()
this.listener = options.listener;
console.log(`SignalingListener ok`);
}
// 邀请已被接受
override onInviteeAccepted(inviteID:string,invitee:string,data:string) {
console.log(`inviteID${inviteID}inviter${invitee}data${data}`)
this.listener('onInvitationAccepted',{
inviteID,
invitee,
data
})
}
// 被邀请者拒绝邀请
override onInviteeRejected(inviteID:string,invitee:string,data:string) {
console.log(`inviteID${inviteID}invitee${invitee}data${data}`)
this.listener('onInviteeRejected',{
inviteID,
invitee,
data
})
}
// 收到邀请
override onReceiveNewInvitation(
inviteID:string,inviter:string, groupId:string,inviteeList:List<String>,data:string) {
console.log('test')
this.listener('onReceiveNewInvitation',{
inviteID,
inviter,
groupId,
inviteeList,
data
})
}
// 邀请被取消
override onInvitationCancelled(inviteID:string,inviter:string,data:string) {
console.log(`inviteID${inviteID}inviter${inviter}data${data}`)
this.listener('onInvitationCancelled',{
inviteID,
inviter,
data
})
}
// 邀请超时
override onInvitationTimeout(inviteID:string,inviteeList:List<String>) {
console.log(`inviteID${inviteID}inviteeList${inviteeList}`)
this.listener('onInvitationTimeout',{
inviteID,
inviteeList,
})
}
}
trtc 监听回调事件
import TRTCCloudListener from 'com.tencent.trtc.TRTCCloudListener';
import Bundle from 'android.os.Bundle';
export type trtcListenerType = {
listener : (eventType : string, data : any) => void
}
export class TrtcListener extends TRTCCloudListener {
private listener : (eventType : string, data : any) => void;
constructor(options : trtcListenerType) {
super();
this.listener = options.listener;
}
override onError( errCode:Int, errMsg:string, extraInfo:Bundle|null) {
console.log('onError',errCode,errMsg)
this.listener('onError',{
errCode,
errMsg,
extraInfo
})
}
// 进入房间成功与否的事件回调
override onEnterRoom(result:Long) {
// super.onEnterRoom(result);
console.log('onEnterRoom', result);
this.listener('onEnterRoom',{
result
})
}
// 退出房间回调
override onExitRoom(reason:Int) {
console.log('onExitRoom', reason);
// 离开房间原因,0:主动调用 exitRoom 退出房间;1:被服务器踢出当前房间;2:当前房间整个被解散 3: 服务器状态异常。
this.listener('onExitRoom',{
reason
})
}
// 远端进房回调
override onRemoteUserEnterRoom( userId:string) {
console.log('onRemoteUserEnterRoom', userId);
this.listener('onRemoteUserEnterRoom',{
userId
})
}
// 远端离开房间
override onRemoteUserLeaveRoom( userId:string,reason:Int) {
console.log('onRemoteUserLeaveRoom', userId);
// userId远端用户的用户标识。
// 离开原因,0表示用户主动退出房间,1表示用户超时退出,2表示被踢出房间,3表示主播因切换到观众退出房间
this.listener('onRemoteUserLeaveRoom',{
userId,
reason
})
}
}
使用
项目引入
import { CallIM, generateSign, TrtcCloud } from "@/uni_modules/xm-tim";
初始化
const callStore = reactive({
imEngine: null,
trtcCloud: null,
callStatus: CallStatus.IDLE,
inviteID: '',// 邀请id
inviteer: '', // 邀请的用户
roomId: 0,// 房间id
userId: '', // 当前登录的用户
userSig: '',
callDuration: '00:00:00'
} as storeType)
callStore.imEngine = new CallIM()
登录
callStore.userSig = generateSign(callStore.userId);
callStore.imEngine?.login({
userID: callStore.userId,
userSig: callStore.userSig,
success: () => {
console.log('call login success')
},
fail: (errCode : number, errMsg : string) => {
console.log('call login fail', errCode, errMsg)
}
})
登出
callStore.imEngine?.logout({
success: () => {
console.log('logout success')
},
fail: (errCode : number, errMsg : string) => {
console.log('logout fail', errCode, errMsg)
}
})
发送邀请
//返回邀请id 需要保存下来 在后续取消通话时需要用到
let id = callStore.imEngine?.inviteCall({
invitee: callStore.inviteer,
data:'',
onlineUserOnly: false, //
offlinePushInfo: {
disablePush: false, // 如果接收方不在线,则进行离线推送
disableVoipPush: false, // 开启 voip 推送需要同时开启离线推送
title: '测试', // 离线推送标题
description: '', // 离线推送内容
androidOPPOChannelID: '' // 离线推送设置 OPPO 手机 8.0 系统及以上的渠道 ID
},
timeout: 30, // 设置 30s 超时
success: () => {
},
fail: (errCode : number, errMsg : string) => {
console.log('inviteCall fail', errCode, errMsg)
}
})
if(id!= null){
callStore.inviteID = id
}
取消通话
if (callStore.inviteID != '') {
callStore.imEngine?.cancelCall({
inviteId: callStore.inviteID,
data:'',
success: () => {
console.log('cancelCall success')
},
fail: (errCode : number, errMsg : string) => {
console.log('cancelCall fail', errCode, errMsg)
}
})
}
事件监听
// 被邀请人接受了邀请 开启语音通话
callStore.imEngine?.on('onInvitationAccepted', (res) => {
console.log('onInvitationAccepted', res);
initTRTC() // 初始化 TRTTC
if (callStore.trtcCloud != null) {
listenTRTC() // 监听进房状态
enterRoom() // 进入房间
}
callStore.callStatus = CallStatus.ACCEPTED;
})
callStore.imEngine?.on('onInviteeRejected', (res) => {
console.log('onInviteeRejected', res);
uni.showToast({
title: '对方正在通话中,请稍后再拨',
icon: 'none',
duration: 3000,
})
})
// 取消邀请
callStore.imEngine?.on('onInvitationCancelled', (res) => {
console.log('onInvitationCancelled', res);
callStore.callStatus = CallStatus.CANCELLED;
setTimeout(() => {
uni.showToast({
title:'已取消通话!',
icon: 'none',
duration: 3000,
})
}, 100)
});
// 邀请超时
callStore.imEngine?.on('onInvitationTimeout', (res) => {
console.log('onInvitationTimeout', res);
setTimeout(() => {
uni.showToast({
title: '呼叫超时',
icon: 'none',
duration: 3000,
})
resetCall()
}, 2000)
});
// 被踢下线
callStore.imEngine?.on('onKickedOffline', (res) => {
console.log('onKickedOffline', res);
uni.showModal({
content: '其他用户已登录和您相同的账号!'
})
})
取消监听
callStore.imEngine?.off('onReceiveNewInvitation')
callStore.imEngine?.off('onInvitationCancelled')
callStore.imEngine?.off('onInvitationTimeout')
callStore.imEngine?.off('onKickedOffline')
初始化trtc
callStore.trtcCloud = new TrtcCloud();
事件监听
callStore.trtcCloud?.on('onError', (res) => {
console.log('onError');
})
callStore.trtcCloud?.on('onEnterRoom', (res) => {
console.log('onEnterRoom', res);
let resData = res as UTSJSONObject
let result = resData.result as number
if (result > 0) {
console.log(`enter room success`)
} else {
console.log(`enter room failed,error code = ${result}`)
}
})
callStore.trtcCloud?.on('onExitRoom', (res) => {
console.log('onExitRoom', res);
})
callStore.trtcCloud?.on('onRemoteUserEnterRoom', (res) => {
console.log('onRemoteUserEnterRoom', res);
// 远端用户进房成功
callStore.callStatus = CallStatus.CONNECTED;
})
callStore.trtcCloud?.on('onRemoteUserLeaveRoom', (res) => {
console.log('onRemoteUserLeaveRoom', res);
setTimeout(() => {
uni.showToast({
title: '对方已挂断电话,通话已结束!',
icon: 'none',
duration: 2000
})
}, 500)
})
进入房间开始通话
callStore.trtcCloud?.startAudioCall({
roomId: callStore.roomId,
userID: callStore.userId as string,
userSig: callStore.userSig,
})
挂断通话退出房间
callStore.trtcCloud?.endAudioCall()
取消监听事件
callStore.trtcCloud?.off('onEnterRoom')
callStore.trtcCloud?.off('onExitRoom')
callStore.trtcCloud?.off('onRemoteUserEnterRoom')
callStore.trtcCloud?.off('onRemoteUserLeaveRoom')
完整源代码:gitee.com/WTYHC/tim