2023-03-06 17:57:39 +08:00

696 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import TUICallEngine, { EVENT, MEDIA_TYPE, AUDIO_PLAYBACK_DEVICE, STATUS } from 'tuicall-engine-wx';
// 组件旨在跨终端维护一个通话状态管理机以事件发布机制驱动上层进行管理并通过API调用进行状态变更。
// 组件设计思路将UI和状态管理分离。您可以通过修改`component`文件夹下的文件,适配您的业务场景,
// 在UI展示上您可以通过属性的方式将上层的用户头像名称等数据传入组件内部`static`下的icon和默认头像图片
// 只是为了展示基础的效果,您需要根据业务场景进行修改。
Component({
properties: {
config: {
type: Object,
value: {
sdkAppID: 0,
userID: '',
userSig: '',
type: 1,
tim: null,
},
},
backgroundMute: {
type: Boolean,
value: false,
},
},
observers: {
},
data: {
callStatus: STATUS.IDLE, // idle、calling、connection
isSponsor: false, // 呼叫者身份 true为呼叫者 false为被叫者
pusher: {}, // TRTC 本地流
playerList: [], // TRTC 远端流
playerProcess: {}, // 经过处理的的远端流(多人通话)
isGroup: false, // 是否为多人通话
remoteUsers: [], // 单人通话远程用户资料(不包含自身)
allUsers: [], // 多人通话用户资料(包含自身)
sponsor: '', // 主叫方
screen: 'pusher', // 视屏通话中显示大屏幕的流只限1v1聊天
soundMode: AUDIO_PLAYBACK_DEVICE.SPEAKER, // 声音模式 听筒/扬声器
showToatTime: 0, // 弹窗时长
ownUserId: '',
},
methods: {
resetUI() {
// 收起键盘
wx.hideKeyboard();
},
// 新的邀请回调事件
handleNewInvitationReceived(event) {
this.resetUI();
this.data.config.type = event.data.inviteData.callType;
// 判断是否为多人通话
if (event.data.isFromGroup) {
this.setData({
isGroup: true,
sponsor: event.data.sponsor,
});
// 将主叫方和被叫列表合并在一起 组成全部通话人数
const newList = [...event.data.inviteeList, event.data.sponsor];
// 获取用户信息
this.getUserProfile(newList);
} else {
this.getUserProfile([event.data.sponsor]);
}
this.setData({
config: this.data.config,
callStatus: STATUS.CALLING,
isSponsor: false,
});
},
// 用户接听
handleUserAccept(event) {
// 主叫方则唤起通话页面
if (this.data.isSponsor) {
this.setData({
callStatus: STATUS.CONNECTED,
});
}
},
// 远端用户进入通话
handleUserEnter(res) {
const newList = this.data.allUsers;
// 多人通话
if (this.data.isGroup) {
// 改变远端用户信息中的isEnter属性
for (let i = 0;i < newList.length;i++) {
if (newList[i].userID === res.data.userID) {
newList[i].isEnter = true;
}
}
}
this.setData({
playerList: res.playerList,
allUsers: newList,
});
},
// 远端用户离开通话
handleUserLeave(res) {
// 多人通话
if (this.data.isGroup) {
wx.showToast({
title: `${res.data.userID}离开通话`,
});
this.deleteUsers(this.data.allUsers, res.data.userID);
}
this.setData({
playerList: res.data.playerList,
});
},
// 用户数据更新
handleUserUpdate(res) {
this.handleNetStatus(res.data);
const newplayer = {};
const newres = res.data.playerList;
// 多人通话
if (this.data.isGroup) {
// 处理远端流
for (let i = 0;i < newres.length;i++) {
const { userID } = newres[i];
newplayer[userID] = newres[i];
}
};
this.setData({
pusher: res.data.pusher,
playerList: res.data.playerList,
playerProcess: newplayer,
});
},
// 判断网络状态
handleNetStatus(data) {
if (data.pusher.netQualityLevel > 4) {
wx.showToast({
icon: 'none',
title: '您当前网络不佳',
});
}
data.playerList.map((item) => {
if (item.netStatus.netQualityLevel > 4) {
const name = data.playerList.length > 1 ? item.nick : '对方';
wx.showToast({
icon: 'none',
title: `${name}当前网络不佳`,
});
}
return item;
});
},
// 用户拒绝
handleInviteeReject(event) {
if (this.data.isGroup) {
this.deleteUsers(this.data.allUsers, event.data.invitee);
}
wx.showToast({
title: `${event.data.invitee}已拒绝`,
});
},
// 用户不在线
handleNoResponse(event) {
if (this.data.isGroup) {
this.deleteUsers(this.data.allUsers, event.data.timeoutUserList);
}
wx.showToast({
icon: 'none',
title: `${event.data.timeoutUserList}无应答`,
});
},
// 用户忙线
handleLineBusy(event) {
if (this.data.isGroup) {
this.deleteUsers(this.data.allUsers, event.data.invitee);
}
this.showToast(event.data.invitee);
},
showToast(event) {
this.setData({
showToatTime: this.data.showToatTime + 500,
});
setTimeout(() => {
wx.showToast({
title: `${event}忙线中`,
});
}, this.data.showToatTime);
},
// 用户取消
handleCallingCancel(event) {
if (event.data.invitee !== this.data.config.userID) {
wx.showToast({
title: `${event.data.invitee}取消通话`,
});
}
this.reset();
},
// 通话超时未应答
handleCallingTimeout(event) {
if (this.data.isGroup) {
// 若是自身未应答 则不弹窗
if (this.data.config.userID === event.data.timeoutUserList[0]) {
this.reset();
return;
}
const newList = this.deleteUsers(this.data.allUsers, event.data.timeoutUserList);
this.setData({
allUsers: newList,
});
}
if (this.data.playerList.length === 0) {
this.reset();
}
wx.showToast({
title: `${event.data.timeoutUserList[0]}超时无应答`,
});
},
handleCallingUser(userIDList) {
const remoteUsers = [...this.data.remoteUsers];
const userProfile = remoteUsers.filter(item => userIDList.some(userItem => `${userItem}` === item.userID));
this.setData({
remoteUsers: remoteUsers.filter(item => userIDList.some(userItem => userItem !== item.userID)),
});
let nick = '';
for (let i = 0; i < userProfile.length; i++) {
nick += `${userProfile[i].nick}`;
}
return nick.slice(0, -1);
},
// 通话结束
handleCallingEnd(event) {
wx.showToast({
title: '通话结束',
duration: 800,
});
this.reset();
},
// SDK Ready 回调
handleSDKReady() {
// 呼叫需在sdk ready之后
},
// 被踢下线
handleKickedOut() {
wx.showToast({
title: '您已被踢下线',
});
},
// 切换通话模式
handleCallMode(event) {
this.data.config.type = event.data.type;
this.setSoundMode(AUDIO_PLAYBACK_DEVICE.EAR);
this.setData({
config: this.data.config,
});
},
// 删除用户列表操作
deleteUsers(usersList, userID) {
// 若userID不是数组,则将其转换为数组
if (!Array.isArray(userID)) {
userID = [userID];
}
const list = usersList.filter(item => !userID.includes(item.userID));
this.setData({
allUsers: list,
});
},
// 增加用户列表操作
addUsers(usersList, userID) {
// 若userID不是数组,则将其转换为数组
if (!Array.isArray(userID)) {
userID = [userID];
}
const newList = [...usersList, ...userID];
return newList;
},
// 增加 tsignaling 事件监听
_addTSignalingEvent() {
// 被邀请通话
wx.$TUICallEngine.on(EVENT.INVITED, this.handleNewInvitationReceived, this);
// 用户接听
wx.$TUICallEngine.on(EVENT.USER_ACCEPT, this.handleUserAccept, this);
// 用户进入通话
wx.$TUICallEngine.on(EVENT.USER_ENTER, this.handleUserEnter, this);
// 用户离开通话
wx.$TUICallEngine.on(EVENT.USER_LEAVE, this.handleUserLeave, this);
// 用户更新数据
wx.$TUICallEngine.on(EVENT.USER_UPDATE, this.handleUserUpdate, this);
// 用户拒绝通话
wx.$TUICallEngine.on(EVENT.REJECT, this.handleInviteeReject, this);
// 用户无响应
wx.$TUICallEngine.on(EVENT.NO_RESP, this.handleNoResponse, this);
// 用户忙线
wx.$TUICallEngine.on(EVENT.LINE_BUSY, this.handleLineBusy, this);
// 通话被取消
wx.$TUICallEngine.on(EVENT.CALLING_CANCEL, this.handleCallingCancel, this);
// 通话超时未应答
wx.$TUICallEngine.on(EVENT.CALLING_TIMEOUT, this.handleCallingTimeout, this);
// 通话结束
wx.$TUICallEngine.on(EVENT.CALL_END, this.handleCallingEnd, this);
// SDK Ready 回调
wx.$TUICallEngine.on(EVENT.SDK_READY, this.handleSDKReady, this);
// 被踢下线
wx.$TUICallEngine.on(EVENT.KICKED_OUT, this.handleKickedOut, this);
// 切换通话模式
wx.$TUICallEngine.on(EVENT.CALL_MODE, this.handleCallMode, this);
// 自己发送消息
wx.$TUICallEngine.on(EVENT.MESSAGE_SENT_BY_ME, this.messageSentByMe, this);
},
// 取消 tsignaling 事件监听
_removeTSignalingEvent() {
// 被邀请通话
wx.$TUICallEngine.off(EVENT.INVITED, this.handleNewInvitationReceived);
// 用户接听
wx.$TUICallEngine.off(EVENT.USER_ACCEPT, this.handleUserAccept);
// 用户进入通话
wx.$TUICallEngine.off(EVENT.USER_ENTER, this.handleUserEnter);
// 用户离开通话
wx.$TUICallEngine.off(EVENT.USER_LEAVE, this.handleUserLeave);
// 用户更新数据
wx.$TUICallEngine.off(EVENT.USER_UPDATE, this.handleUserUpdate);
// 用户拒绝通话
wx.$TUICallEngine.off(EVENT.REJECT, this.handleInviteeReject);
// 用户无响应
wx.$TUICallEngine.off(EVENT.NO_RESP, this.handleNoResponse);
// 用户忙线
wx.$TUICallEngine.off(EVENT.LINE_BUSY, this.handleLineBusy);
// 通话被取消
wx.$TUICallEngine.off(EVENT.CALLING_CANCEL, this.handleCallingCancel);
// 通话超时未应答
wx.$TUICallEngine.off(EVENT.CALLING_TIMEOUT, this.handleCallingTimeout);
// 通话结束
wx.$TUICallEngine.off(EVENT.CALL_END, this.handleCallingEnd);
// SDK Ready 回调
wx.$TUICallEngine.off(EVENT.SDK_READY, this.handleSDKReady);
// 被踢下线
wx.$TUICallEngine.off(EVENT.KICKED_OUT, this.handleKickedOut);
// 切换通话模式
wx.$TUICallEngine.off(EVENT.CALL_MODE, this.handleCallMode);
// 自己发送消息
wx.$TUICallEngine.off(EVENT.MESSAGE_SENT_BY_ME, this.messageSentByMe);
},
/**
* C2C邀请通话被邀请方会收到的回调
* 如果当前处于通话中,可以调用该函数以邀请第三方进入通话
*
* @param userID 被邀请方
* @param type 0-为之, 1-语音通话2-视频通话
*/
async call(params) {
this.resetUI();
if (this.data.callStatus !== STATUS.IDLE) {
return;
}
await wx.$TUICallEngine.call({ userID: params.userID, type: params.type }).then((res) => {
this.data.config.type = params.type;
this.getUserProfile([params.userID]);
this.setData({
pusher: res.pusher,
config: this.data.config,
callStatus: STATUS.CALLING,
isSponsor: true,
});
this.setSoundMode(this.data.config.type === MEDIA_TYPE.AUDIO ? AUDIO_PLAYBACK_DEVICE.EAR : AUDIO_PLAYBACK_DEVICE.SPEAKER);
});
},
/**
* IM群组邀请通话被邀请方会收到的回调
* 如果当前处于通话中,可以继续调用该函数继续邀请他人进入通话,同时正在通话的用户会收到的回调
*
* @param userIDList 邀请列表
* @param type 1-语音通话2-视频通话
* @param groupID IM群组ID
*/
async groupCall(params) {
// 判断是否存在groupID
if (!params.groupID) {
wx.showToast({
title: '群ID为空',
});
return;
}
this.resetUI();
if (this.data.callStatus !== STATUS.IDLE) {
return;
}
wx.$TUICallEngine.groupCall({ userIDList: params.userIDList, type: params.type, groupID: params.groupID }).then((res) => {
this.data.config.type = params.type;
this.setData({
pusher: res.pusher,
config: this.data.config,
callStatus: STATUS.CALLING,
isSponsor: true,
isGroup: true,
sponsor: this.data.config.userID,
});
// 将自身的userID插入到邀请列表中,组成完整的用户信息
const list = JSON.parse(JSON.stringify(params.userIDList));
list.unshift(this.data.config.userID);
// 获取用户信息
this.getUserProfile(list);
});
},
/**
* 当您作为被邀请方收到 {@link TRTCCallingDelegate#onInvited } 的回调时,可以调用该函数接听来电
*/
async accept() {
wx.$TUICallEngine.accept().then((res) => {
this.setData({
pusher: res.pusher,
callStatus: STATUS.CONNECTED,
});
// 多人通话需要对自身位置进行修正,将其放到首位
if (this.data.isGroup) {
const newList = this.data.allUsers;
for (let i = 0;i < newList.length;i++) {
if (newList[i].userID === this.data.config.userID) {
newList[i].isEnter = true;
[newList[i], newList[0]] = [newList[0], newList[i]];
}
}
this.setData({
allUsers: newList,
});
}
})
.catch((error) => {
wx.showModal({
icon: 'none',
title: 'error',
content: error.message,
showCancel: false,
});
});
},
/**
* 当您作为被邀请方收到的回调时,可以调用该函数拒绝来电
*/
async reject() {
wx.$TUICallEngine.reject().then((res) => {
this.reset();
});
},
messageSentByMe(event) {
const message = event.data.data;
this.triggerEvent('sendMessage', {
message,
});
},
// xml层是否开启扬声器
setSoundMode(type) {
this.setData({
soundMode: wx.$TUICallEngine.selectAudioPlaybackDevice(type),
});
},
// xml层挂断
async _hangUp() {
await wx.$TUICallEngine.hangup();
this.reset();
},
// 切换大小屏 (仅支持1v1聊天)
toggleViewSize(event) {
this.setData({
// FIXME _toggleViewSize 不应该为TUICallEngine的方法 后续修改
screen: wx.$TUICallEngine._toggleViewSize(event),
});
},
// 数据重置
reset() {
this.setData({
callStatus: STATUS.IDLE,
isSponsor: false,
isGroup: false,
soundMode: AUDIO_PLAYBACK_DEVICE.SPEAKER,
pusher: {}, // TRTC 本地流
playerList: [], // TRTC 远端流
showToatTime: 0,
});
},
// 呼叫中的事件处理
handleCallingEvent(data) {
const { name } = data.detail;
switch (name) {
case 'accept':
this.setSoundMode(this.data.config.type === MEDIA_TYPE.AUDIO ? AUDIO_PLAYBACK_DEVICE.EAR : AUDIO_PLAYBACK_DEVICE.SPEAKER);
this.accept();
break;
case 'hangup':
this._hangUp();
break;
case 'reject':
this.reject();
break;
case 'toggleSwitchCamera':
wx.$TUICallEngine.switchCamera();
break;
case 'switchAudioCall':
wx.$TUICallEngine.switchCallMediaType(MEDIA_TYPE.AUDIO).then((res) => {
this.data.config.type = MEDIA_TYPE.AUDIO;
this.setSoundMode(AUDIO_PLAYBACK_DEVICE.EAR);
this.setData({
config: this.data.config,
});
});
break;
default:
break;
}
},
// 通话中的事件处理
handleConnectedEvent(data) {
const { name, event } = data.detail;
switch (name) {
case 'toggleViewSize':
this.toggleViewSize(event);
break;
case 'pusherNetStatus':
wx.$TUICallEngine._pusherNetStatus(event);
break;
case 'playNetStatus':
wx.$TUICallEngine._playNetStatus(event);
break;
case 'pusherStateChangeHandler':
wx.$TUICallEngine._pusherStateChangeHandler(event);
break;
case 'pusherAudioVolumeNotify':
wx.$TUICallEngine._pusherAudioVolumeNotify(event);
break;
case 'playerStateChange':
wx.$TUICallEngine._playerStateChange(event);
break;
case 'playerAudioVolumeNotify':
wx.$TUICallEngine._playerAudioVolumeNotify(event);
break;
case 'pusherAudioHandler':
wx.$TUICallEngine._pusherAudioHandler(event);
break;
case 'hangup':
this._hangUp();
break;
case 'toggleSoundMode':
this.setSoundMode(this.data.soundMode === AUDIO_PLAYBACK_DEVICE.EAR ? AUDIO_PLAYBACK_DEVICE.SPEAKER : AUDIO_PLAYBACK_DEVICE.EAR);
break;
case 'pusherVideoHandler':
wx.$TUICallEngine._pusherVideoHandler(event);
break;
case 'toggleSwitchCamera':
wx.$TUICallEngine.switchCamera(event);
break;
case 'switchAudioCall':
wx.$TUICallEngine.switchCallMediaType(MEDIA_TYPE.AUDIO).then((res) => {
this.data.config.type = MEDIA_TYPE.AUDIO;
this.setData({
config: this.data.config,
});
this.setSoundMode(AUDIO_PLAYBACK_DEVICE.EAR);
});
break;
default:
break;
}
},
// 设置用户的头像、昵称
setSelfInfo(nickName, avatar) {
return wx.$TUICallEngine.setSelfInfo(nickName, avatar);
},
// 获取用户资料
async getUserProfile(userList) {
const imResponse = await this.getTim().getUserProfile({ userIDList: userList });
// 修正用户资料
this.modifyUser(imResponse.data);
},
// 修正用户资料
modifyUser(userIDList) {
const { sponsor } = this.data;
if (this.data.isGroup) {
// 多人通话需要将呼叫者放到第一位 isEnter的作用是区分用户是否进入房间
for (let i = 0;i < userIDList.length;i++) {
// 主叫方的标志位设置成true
if (userIDList[i].userID === sponsor) {
userIDList[i].isEnter = true;
// 对主叫方位置进行修正 将其放到首位
[userIDList[i], userIDList[0]] = [userIDList[0], userIDList[i]];
} else {
// 其他用户默认未进入房间 设置为false
userIDList[i].isEnter = false;
}
}
this.setData({
allUsers: userIDList,
});
}
this.setData({
remoteUsers: userIDList,
});
},
// 获取 tim 实例
getTim() {
return wx.$TUICallEngine.getTim();
},
// 初始化TRTCCalling
async init(params) {
// 兼容从config和init中传值
const { sdkAppID, tim, userID, userSig, SDKAppID } = { ...this.data.config, ...params };
this.setData({
ownUserId: userID,
config: {
...this.data.config,
sdkAppID: sdkAppID || SDKAppID,
userID,
userSig,
},
});
if (!wx.$TUICallEngine) {
wx.$TUICallEngine = TUICallEngine.createInstance({
tim,
sdkAppID: sdkAppID || SDKAppID,
});
try {
await wx.$TUICallEngine.init({
userID,
userSig,
});
} catch (error) {
console.error(error);
}
}
this._addTSignalingEvent();
},
// 销毁 TUICallEngine
destroyed() {
if (wx.$TUICallEngine) {
this._removeTSignalingEvent();
}
if (!wx.$globalCallSign) {
wx.$TUICallEngine.destroyInstance();
wx.$TUICallEngine = null;
}
},
},
/**
* 生命周期方法
*/
lifetimes: {
created() {
},
attached() {
},
ready() {
this.reset();
if (wx.$globalCallSign) {
wx.$CallManagerInstance.removeEngineInvite();
}
},
detached() {
this.destroyed();
this.reset();
if (wx.$globalCallSign) {
wx.$CallManagerInstance.addEngineInvite();
}
},
error() {
},
},
pageLifetimes: {
show() {
},
hide() {
},
resize() {
},
},
});