新增功能
This commit is contained in:
Generated
Vendored
+174
@@ -0,0 +1,174 @@
|
||||
import { ButtonState, FeatureButton, LayoutMode, NAME, StoreName } from "../const/index";
|
||||
import { deepClone } from "../utils/index";
|
||||
import isEmpty from "../utils/is-empty";
|
||||
export interface IUIDesign {
|
||||
updateViewBackgroundUserId: (viewType: 'local' | 'remote') => void;
|
||||
hideFeatureButton: (buttonName: FeatureButton) => void;
|
||||
setLocalViewBackgroundImage: (url: string) => void;
|
||||
setRemoteViewBackgroundImage: (userId: string, url: string) => void;
|
||||
setLayoutMode: (layoutMode: LayoutMode) => void;
|
||||
setCameraDefaultState: (isOpen: boolean) => void;
|
||||
setEngineInstance: (engineInstance: any) => void;
|
||||
setTUIStore: (tuiStore: any) => void;
|
||||
}
|
||||
|
||||
const DEFAULT_LOCAL_USER_ID = '_local_user_id';
|
||||
|
||||
export class UIDesign implements IUIDesign {
|
||||
static instance: IUIDesign;
|
||||
static getInstance() {
|
||||
if (!UIDesign.instance) {
|
||||
UIDesign.instance = new UIDesign();
|
||||
}
|
||||
return UIDesign.instance;
|
||||
}
|
||||
|
||||
private _viewConfig = {
|
||||
viewBackground: {
|
||||
local: {},
|
||||
remote: {},
|
||||
}
|
||||
};
|
||||
private _isSetViewBackgroundConfig = { remote: false, local: false };
|
||||
private _tuiCallEngine = null;
|
||||
private _tuiStore = null;
|
||||
private _updateViewBackground() {
|
||||
const customUIConfig = this._tuiStore?.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
|
||||
const { userId } = this._tuiStore?.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
|
||||
if (Object.keys(this._viewConfig.viewBackground.remote).includes(userId)) {
|
||||
delete this._viewConfig.viewBackground.remote[userId];
|
||||
}
|
||||
|
||||
this._tuiStore?.update(
|
||||
StoreName.CALL,
|
||||
NAME.CUSTOM_UI_CONFIG,
|
||||
{
|
||||
...customUIConfig,
|
||||
viewBackground: {
|
||||
...this._viewConfig.viewBackground.remote,
|
||||
...this._viewConfig.viewBackground.local,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setEngineInstance(engineInstance) {
|
||||
this._tuiCallEngine = engineInstance;
|
||||
}
|
||||
setTUIStore(tuiStore) {
|
||||
this._tuiStore = tuiStore;
|
||||
}
|
||||
public updateViewBackgroundUserId(name) {
|
||||
if (name === 'local') {
|
||||
const { userId } = this._tuiStore?.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
|
||||
if (Object.keys(this._viewConfig.viewBackground.remote).includes(userId)) {
|
||||
delete this._viewConfig.viewBackground.remote[userId];
|
||||
this._updateViewBackground();
|
||||
}
|
||||
|
||||
if (!this._isSetViewBackgroundConfig.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localViewBackgroundConfig = this._viewConfig.viewBackground.local;
|
||||
const url = localViewBackgroundConfig[userId] || localViewBackgroundConfig[DEFAULT_LOCAL_USER_ID];
|
||||
localViewBackgroundConfig[userId] = localViewBackgroundConfig[DEFAULT_LOCAL_USER_ID];
|
||||
this._viewConfig.viewBackground.local = { [userId]: url };
|
||||
|
||||
this._updateViewBackground();
|
||||
} else {
|
||||
let remoteViewBackgroundConfig = this._viewConfig.viewBackground.remote;
|
||||
|
||||
if (this._isSetViewBackgroundConfig.remote && Object.keys(remoteViewBackgroundConfig).includes('*')) {
|
||||
const remoteUserInfoList = this._tuiStore?.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const remoteUserIdList = remoteUserInfoList.map((item) => item.userId);
|
||||
remoteUserIdList.forEach((userId) => {
|
||||
if (!Object.keys(remoteViewBackgroundConfig).includes(userId)) {
|
||||
remoteViewBackgroundConfig[userId] = remoteViewBackgroundConfig['*'];
|
||||
}
|
||||
});
|
||||
|
||||
this._viewConfig.viewBackground.remote = remoteViewBackgroundConfig;
|
||||
this._updateViewBackground();
|
||||
}
|
||||
}
|
||||
}
|
||||
public hideFeatureButton(buttonName: FeatureButton) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.hideFeatureButton.start',
|
||||
data: { buttonName },
|
||||
});
|
||||
const customUIConfig = this._tuiStore?.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
|
||||
this._tuiStore?.update(
|
||||
StoreName.CALL,
|
||||
NAME.CUSTOM_UI_CONFIG,
|
||||
{
|
||||
...customUIConfig,
|
||||
button: {
|
||||
...customUIConfig.button,
|
||||
[buttonName]: { ...(customUIConfig.button?.[buttonName] || {}), show: false, },
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
public setLocalViewBackgroundImage(url: string) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.setLocalViewBackgroundImage.start',
|
||||
data: { url },
|
||||
});
|
||||
this._isSetViewBackgroundConfig.local = true;
|
||||
let { userId } = this._tuiStore?.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
|
||||
if (isEmpty(userId)) {
|
||||
userId = DEFAULT_LOCAL_USER_ID;
|
||||
}
|
||||
|
||||
this._viewConfig.viewBackground.local = { [userId]: url };
|
||||
this._updateViewBackground();
|
||||
}
|
||||
public setRemoteViewBackgroundImage(userId: string, url:string) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.setRemoteViewBackgroundImage.start',
|
||||
data: { userId, url },
|
||||
});
|
||||
this._isSetViewBackgroundConfig.remote = true;
|
||||
if (userId === '*') {
|
||||
this._viewConfig.viewBackground.remote = {};
|
||||
}
|
||||
|
||||
this._viewConfig.viewBackground.remote[userId] = url;
|
||||
this._updateViewBackground();
|
||||
}
|
||||
|
||||
public setLayoutMode(layoutMode: LayoutMode) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.setLayoutMode.start',
|
||||
data: { layoutMode },
|
||||
});
|
||||
const customUIConfig = this._tuiStore.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
|
||||
this._tuiStore.update(
|
||||
StoreName.CALL,
|
||||
NAME.CUSTOM_UI_CONFIG,
|
||||
{
|
||||
...customUIConfig,
|
||||
layoutMode,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public setCameraDefaultState(isOpen: boolean) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.setCameraDefaultState.start',
|
||||
data: { isOpen },
|
||||
});
|
||||
const customUIConfig = deepClone(this._tuiStore.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG));
|
||||
|
||||
if (!Object.keys(customUIConfig.button).includes(FeatureButton.Camera)) {
|
||||
customUIConfig.button[FeatureButton.Camera] = {};
|
||||
}
|
||||
customUIConfig.button[FeatureButton.Camera].state = isOpen ? ButtonState.Open : ButtonState.Close;
|
||||
this._tuiStore.update(StoreName.CALL, NAME.CUSTOM_UI_CONFIG, customUIConfig);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+169
@@ -0,0 +1,169 @@
|
||||
import { CallStatus, NAME, CallRole } from '../const/index';
|
||||
import { IBellParams } from '../interface/index';
|
||||
import { isUndefined } from '../utils/common-utils';
|
||||
import DEFAULT_CALLER_BELL_FILEPATH from '../assets/phone_dialing.mp3';
|
||||
import DEFAULT_CALLEE_BELL_FILEPATH from '../assets/phone_ringing.mp3';
|
||||
|
||||
export class BellContext {
|
||||
private _bellContext: any = null;
|
||||
private _isMuteBell: boolean = false;
|
||||
private _calleeBellFilePath: string = DEFAULT_CALLEE_BELL_FILEPATH;
|
||||
private _callRole: string = CallRole.UNKNOWN;
|
||||
private _callStatus: string = CallStatus.IDLE;
|
||||
private _isPlaying: boolean = false;
|
||||
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
this._bellContext = wx.createInnerAudioContext({ useWebAudioImplement: false });
|
||||
this._addListenBellContextEvent();
|
||||
this._bellContext.loop = true;
|
||||
}
|
||||
|
||||
setBellSrc() {
|
||||
// @ts-ignore
|
||||
const fs = wx.getFileSystemManager();
|
||||
try {
|
||||
let playBellFilePath = DEFAULT_CALLER_BELL_FILEPATH;
|
||||
if (this._callRole === CallRole.CALLEE) {
|
||||
playBellFilePath = this._calleeBellFilePath || DEFAULT_CALLEE_BELL_FILEPATH;
|
||||
}
|
||||
fs.readFileSync(playBellFilePath, 'utf8', 0);
|
||||
this._bellContext.src = playBellFilePath;
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}Failed to setBellSrc, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
setBellProperties(bellParams: IBellParams) {
|
||||
this._callRole = bellParams.callRole || this._callRole;
|
||||
this._callStatus = bellParams.callStatus || this._callStatus;
|
||||
this._calleeBellFilePath = bellParams.calleeBellFilePath || this._calleeBellFilePath;
|
||||
// undefined/false || isMuteBell => isMuteBell (不符合预期)
|
||||
this._isMuteBell = isUndefined(bellParams.isMuteBell) ? this._isMuteBell : bellParams.isMuteBell;
|
||||
}
|
||||
|
||||
async play() {
|
||||
try {
|
||||
if (this._callStatus !== CallStatus.CALLING) {
|
||||
return;
|
||||
}
|
||||
// iOS 优化:增加更严格的状态检查
|
||||
if (this._callStatus !== CallStatus.CALLING || this._isPlaying) {
|
||||
console.warn(`${NAME.PREFIX}play skipped, callStatus: ${this._callStatus}, isPlaying: ${this._isPlaying}`);
|
||||
return;
|
||||
}
|
||||
this._isPlaying = true;
|
||||
this.setBellSrc();
|
||||
if (this._callRole === CallRole.CALLEE && !this._isMuteBell) {
|
||||
// 再次检查状态,避免在设置过程中状态已变更
|
||||
if (!this._isPlaying || this._callStatus !== CallStatus.CALLING) return;
|
||||
await this._bellContext.play();
|
||||
return;
|
||||
}
|
||||
if (this._callRole === CallRole.CALLER) {
|
||||
// 再次检查状态,避免在设置过程中状态已变更
|
||||
if (!this._isPlaying || this._callStatus !== CallStatus.CALLING) return;
|
||||
await this._bellContext.play();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}Failed to play audio file, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
try {
|
||||
this._isPlaying = false;
|
||||
// iOS 小程序音频停止优化:先暂停再停止,并添加延时确保操作完成
|
||||
if (this._bellContext) {
|
||||
// 先暂停音频
|
||||
this._bellContext.pause();
|
||||
// iOS 需要短暂延时确保暂停操作完成
|
||||
await this._delay(100);
|
||||
|
||||
// 再停止音频
|
||||
this._bellContext.stop();
|
||||
|
||||
// 重置音频源,确保彻底停止
|
||||
this._bellContext.src = '';
|
||||
|
||||
// 再次延时确保所有操作完成
|
||||
await this._delay(100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}Failed to stop audio file, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async setBellMute(enable: boolean) {
|
||||
if (this._callStatus !== CallStatus.CALLING && this._callRole !== CallRole.CALLEE) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
await this.stop();
|
||||
} else {
|
||||
await this.play();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
try {
|
||||
this._isMuteBell = false;
|
||||
this._calleeBellFilePath = '';
|
||||
this._callRole = CallRole.UNKNOWN;
|
||||
this._callStatus = CallStatus.IDLE;
|
||||
this._isPlaying = false;
|
||||
this?._removeListenBellContextEvent();
|
||||
this._bellContext.destroy();
|
||||
this._bellContext = null;
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}Failed to destroy, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private _delay(ms: number): any {
|
||||
return new Promise((resolve: any) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private _delayCallback(callback: () => void, ms: number) {
|
||||
setTimeout(callback, ms);
|
||||
}
|
||||
|
||||
private _handleAudioInterruptionBegin = async () => {
|
||||
await this.stop();
|
||||
};
|
||||
|
||||
private _handleAudioInterruptionEnd = async () => {
|
||||
if (this._callStatus !== CallStatus.CALLING) {
|
||||
await this.stop();
|
||||
} else {
|
||||
// 音频中断结束后,延时一下再重新播放,确保系统音频资源释放完成
|
||||
this._delayCallback(async () => {
|
||||
if (this._callStatus === CallStatus.CALLING && !this._isPlaying) {
|
||||
await this.play();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
private _addListenBellContextEvent() {
|
||||
this._bellContext.onPlay(() => {
|
||||
if (!this._isPlaying) {
|
||||
this._bellContext?.pause();
|
||||
this._bellContext?.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
wx.onAudioInterruptionBegin(this._handleAudioInterruptionBegin);
|
||||
// @ts-ignore
|
||||
wx.onAudioInterruptionEnd(this._handleAudioInterruptionEnd);
|
||||
}
|
||||
|
||||
private _removeListenBellContextEvent() {
|
||||
// @ts-ignore
|
||||
wx.offAudioInterruptionBegin(this._handleAudioInterruptionBegin);
|
||||
// @ts-ignore
|
||||
wx.offAudioInterruptionEnd(this._handleAudioInterruptionEnd);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+299
@@ -0,0 +1,299 @@
|
||||
import { TUICore, TUILogin, TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core-lite';
|
||||
import { CallMediaType, AudioCallIcon, VideoCallIcon, LOG_LEVEL, COMPONENT, StoreName, NAME, CallStatus, CallType, ACTION_TYPE } from '../const/index';
|
||||
import { isUndefined, formatTime, JSONToObject } from '../utils/common-utils';
|
||||
import { getRemoteUserProfile } from './utils';
|
||||
import { ITUIStore } from '../interface/ITUIStore';
|
||||
import TuiStore from '../TUIStore/tuiStore';
|
||||
// @ts-ignore
|
||||
import TencentCloudChat from '@tencentcloud/lite-chat/basic'; // 仅支持 calls 接口
|
||||
import { t } from '../locales/index';
|
||||
const TUIStore: ITUIStore = TuiStore.getInstance();
|
||||
|
||||
const cmd2messageCardContentMap = {
|
||||
audioCall: () => 'Voice call',
|
||||
videoCall: () => 'Video call',
|
||||
switchToAudio: () => 'Switch audio call',
|
||||
switchToVideo: () => 'Switch video call',
|
||||
hangup: ({ callDuration }) => `${t('Call duration')}:${callDuration}`,
|
||||
};
|
||||
export default class ChatCombine {
|
||||
static instance: ChatCombine;
|
||||
private _callService: any;
|
||||
|
||||
constructor(options) {
|
||||
this._callService = options.callService;
|
||||
|
||||
// 下面:TUICore注册事件,注册组件服务,注册界面拓展
|
||||
TUICore.registerEvent(TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS, this); // onNotifyEvent
|
||||
// @ts-ignore
|
||||
if (TUIConstants.TUIChat?.EVENT) {
|
||||
// @ts-ignore
|
||||
TUICore.registerEvent(TUIConstants.TUIChat.EVENT?.CHAT_STATE_CHANGED, TUIConstants.TUIChat.EVENT_SUB_KEY?.CHAT_OPENED, this); // onNotifyEvent
|
||||
}
|
||||
TUICore.registerService(TUIConstants.TUICalling.SERVICE.NAME, this); // onCall
|
||||
TUICore.registerExtension(TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID, this); // onGetExtension
|
||||
}
|
||||
|
||||
static getInstance(options) {
|
||||
if (!ChatCombine.instance) {
|
||||
ChatCombine.instance = new ChatCombine(options);
|
||||
}
|
||||
return ChatCombine.instance;
|
||||
}
|
||||
// ================ 【】 ================
|
||||
/**
|
||||
* message on screen
|
||||
* @param {Any} params Parameters for message up-screening
|
||||
*/
|
||||
public callTUIService(params) {
|
||||
const { message } = params || {};
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUIChat.SERVICE.NAME,
|
||||
method: TUIConstants.TUIChat.SERVICE.METHOD.UPDATE_MESSAGE_LIST,
|
||||
params: { message },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* tuicore getExtension
|
||||
* @param {String} extensionID extension id
|
||||
* @param {Any} params tuicore pass parameters
|
||||
* @returns {Any[]} return extension
|
||||
*/
|
||||
public onGetExtension(extensionID: string, params: any) {
|
||||
if (extensionID === TUIConstants.TUIChat.EXTENSION.INPUT_MORE.EXT_ID) {
|
||||
this._callService.getTUICallEngineInstance()?.reportLog?.({ name: 'TUICallKit.onGetExtension', data: { extensionID, params } });
|
||||
|
||||
if (isUndefined(params)) return [];
|
||||
// room and customer_service ChatType not show audio and video icon.
|
||||
// @ts-ignore
|
||||
if ([TUIConstants.TUIChat.TYPE.ROOM, TUIConstants.TUIChat.TYPE.CUSTOMER_SERVICE].includes(params.chatType)) return [];
|
||||
|
||||
let list = [];
|
||||
const audioCallExtension: ExtensionInfo = {
|
||||
weight: 1000,
|
||||
text: '语音通话',
|
||||
icon: AudioCallIcon,
|
||||
data: {
|
||||
name: 'voiceCall',
|
||||
},
|
||||
listener: {
|
||||
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.AUDIO),
|
||||
},
|
||||
};
|
||||
const videoCallExtension: ExtensionInfo = {
|
||||
weight: 900,
|
||||
text: '视频通话',
|
||||
icon: VideoCallIcon,
|
||||
data: {
|
||||
name: 'videoCall',
|
||||
},
|
||||
listener: {
|
||||
onClicked: async options => await this._handleTUICoreOnClick(options, options.type || CallMediaType.VIDEO),
|
||||
},
|
||||
};
|
||||
|
||||
if (params?.chatType) {
|
||||
list = [audioCallExtension, videoCallExtension];
|
||||
} else {
|
||||
!params?.filterVoice && list.push(audioCallExtension);
|
||||
!params?.filterVideo && list.push(videoCallExtension);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
public async onCall(method: String, params: any) {
|
||||
if (method === TUIConstants.TUICalling.SERVICE.METHOD.START_CALL) {
|
||||
await this._handleTUICoreOnClick(params, params.type);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* tuicore notify event manager
|
||||
* @param {String} eventName event name
|
||||
* @param {String} subKey sub key
|
||||
* @param {Any} options tuicore event parameters
|
||||
*/
|
||||
public async onNotifyEvent(eventName: string, subKey: string, options?: any) {
|
||||
try {
|
||||
if (eventName === TUIConstants.TUILogin.EVENT.LOGIN_STATE_CHANGED) {
|
||||
// TUICallkit executes its own business logic when it receives a successful login.
|
||||
if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGIN_SUCCESS) {
|
||||
// @ts-ignore
|
||||
const { chat, userID, userSig, SDKAppID } = TUILogin.getContext();
|
||||
|
||||
await this._callService?.init({ tim: chat, userID, userSig, sdkAppID: SDKAppID, isFromChat: true, component: COMPONENT.TIM_CALL_KIT });
|
||||
this._callService?.setIsFromChat(true);
|
||||
this._callService?.setLogLevel(LOG_LEVEL.NORMAL); // setLogLevel to 0 in tuikit. easy to find out problem via logs.
|
||||
this._addListenChatEvent();
|
||||
} else if (subKey === TUIConstants.TUILogin.EVENT_SUB_KEY.USER_LOGOUT_SUCCESS) {
|
||||
this._removeListenChatEvent();
|
||||
await this._callService?.destroyed();
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
if (TUIConstants.TUIChat?.EVENT && eventName === TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED) {
|
||||
// @ts-ignore
|
||||
if (subKey === TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED) {
|
||||
this._callService?.setCurrentGroupId(options?.groupID || '');
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
|
||||
|
||||
const currentGroupId = this._callService?.getCurrentGroupId();
|
||||
const groupAttributes = currentGroupId ? await this.getGroupAttributes(this._callService?.getTim(), currentGroupId) : {};
|
||||
await this.updateStoreBasedOnGroupAttributes(groupAttributes);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}TUICore onNotifyEvent failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
// Handling the chat+call scenario, data required for the joinInGroupCall API: update store / clear relevant store data
|
||||
public async updateStoreBasedOnGroupAttributes(groupAttributes: any) {
|
||||
this._callService?.getTUICallEngineInstance()?.reportLog?.({
|
||||
name: 'TUICallKit.getJoinGroupCallInfo.success',
|
||||
data: { groupAttributes },
|
||||
});
|
||||
try {
|
||||
const {
|
||||
call_id: callId = '', // callEngine v3.1 support
|
||||
group_id: groupId = '',
|
||||
room_id: roomId = 0,
|
||||
room_id_type: roomIdType = 0,
|
||||
call_media_type: callType = NAME.UNKNOWN,
|
||||
// @ts-ignore
|
||||
user_list: userList, // The default value of the user list returned by the background is null
|
||||
} = groupAttributes[NAME.INNER_ATTR_KIT_INFO] ? JSON.parse(groupAttributes[NAME.INNER_ATTR_KIT_INFO]) : {};
|
||||
|
||||
let userListInfo = (userList || []).map(user => user.userid);
|
||||
if (userListInfo.length > 0) {
|
||||
userListInfo = await getRemoteUserProfile(userListInfo, this._callService?.getTim());
|
||||
}
|
||||
const updateStoreParams = {
|
||||
[NAME.CALL_ID]: callId,
|
||||
[NAME.GROUP_ID]: groupId,
|
||||
[NAME.GROUP_CALL_MEMBERS]: userListInfo,
|
||||
[NAME.ROOM_ID]: roomId,
|
||||
[NAME.CALL_MEDIA_TYPE]: CallType[callType],
|
||||
[NAME.ROOM_ID_TYPE]: roomIdType,
|
||||
};
|
||||
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}updateStoreBasedOnGroupAttributes fail, error: ${error}`);
|
||||
}
|
||||
}
|
||||
// Get group attribute
|
||||
public async getGroupAttributes(tim: any, groupId: string) {
|
||||
if (!groupId) return {};
|
||||
|
||||
try {
|
||||
const { data } = await tim.getGroupAttributes({
|
||||
groupID: groupId,
|
||||
keyList: []
|
||||
});
|
||||
return data?.groupAttributes || {};
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}getGroupAttributes fail: ${error}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
isLineBusy(message) {
|
||||
const callMessage: any = JSONToObject(message.payload.data);
|
||||
const objectData = JSONToObject(callMessage?.data);
|
||||
|
||||
return objectData?.line_busy === 'line_busy' || objectData?.line_busy === '' || objectData?.data?.message === 'lineBusy';
|
||||
}
|
||||
async getCallKitMessage(message: any, tim: any) {
|
||||
const callMessage: any = JSONToObject(message.payload.data);
|
||||
if (callMessage?.businessID !== 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let messageCardContent = '';
|
||||
const objectData = JSONToObject(callMessage?.data);
|
||||
const callMediaType = objectData.call_type;
|
||||
const inviteeList = callMessage.inviteeList;
|
||||
const inviter = objectData?.data?.inviter;
|
||||
const localUserId = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO).userId;
|
||||
const isInviter = inviter === localUserId;
|
||||
|
||||
const cmd = objectData?.data?.cmd;
|
||||
switch (callMessage?.actionType) {
|
||||
case ACTION_TYPE.INVITE: {
|
||||
messageCardContent = cmd2messageCardContentMap[cmd]({ callDuration: formatTime(objectData?.call_end) });
|
||||
break;
|
||||
}
|
||||
case ACTION_TYPE.CANCEL_INVITE:
|
||||
messageCardContent = isInviter ? 'Call Cancel' : 'Other Side Cancel';
|
||||
break;
|
||||
case ACTION_TYPE.ACCEPT_INVITE:
|
||||
if (['switchToAudio', 'switchToVideo'].includes(cmd)) {
|
||||
messageCardContent = cmd2messageCardContentMap?.[cmd]?.();
|
||||
} else {
|
||||
messageCardContent = t('Answered');
|
||||
}
|
||||
|
||||
break;
|
||||
case ACTION_TYPE.REJECT_INVITE:
|
||||
if (this.isLineBusy(message)) {
|
||||
messageCardContent = isInviter ? 'Line Busy' : 'Other Side Line Busy';
|
||||
} else {
|
||||
messageCardContent = isInviter ? 'Other Side Decline' : 'Decline';
|
||||
}
|
||||
|
||||
break;
|
||||
case ACTION_TYPE.INVITE_TIMEOUT:
|
||||
if (['switchToAudio', 'switchToVideo'].includes(cmd)) {
|
||||
messageCardContent = cmd2messageCardContentMap?.[cmd]?.();
|
||||
} else {
|
||||
messageCardContent = isInviter ? 'Other Side No Answer' : 'No answer';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return { messageCardContent, callMediaType, inviteeList };
|
||||
}
|
||||
// =========================【chat: event listening】=========================
|
||||
private _addListenChatEvent() {
|
||||
if (!this._callService?.getTim()) {
|
||||
console.warn(`${NAME.PREFIX}add tim event listener failed, tim is empty.`);
|
||||
return;
|
||||
}
|
||||
this._callService?.getTim().on(TencentCloudChat.EVENT.GROUP_ATTRIBUTES_UPDATED, this._handleGroupAttributesUpdated, this);
|
||||
}
|
||||
private _removeListenChatEvent() {
|
||||
if (!this._callService?.getTim()) {
|
||||
console.warn(`${NAME.PREFIX}remove tim event listener failed, tim is empty.`);
|
||||
return;
|
||||
}
|
||||
this._callService?.getTim().off(TencentCloudChat.EVENT.GROUP_ATTRIBUTES_UPDATED, this._handleGroupAttributesUpdated, this);
|
||||
}
|
||||
/**
|
||||
* chat start audio/video call via click
|
||||
* @param {Any} options Parameters passed in when clicking on an audio/video call from chat
|
||||
* @param {CallMediaType} type call media type. 0 - audio; 1 - video.
|
||||
*
|
||||
* priority: isForceUseV2API > version
|
||||
*/
|
||||
private async _handleTUICoreOnClick(options, type: CallMediaType) {
|
||||
try {
|
||||
const { groupID, userIDList = [], version = '', ...rest } = options;
|
||||
|
||||
await this._callService?.calls({ chatGroupID: groupID, userIDList, type, ...rest });
|
||||
} catch (error: any) {
|
||||
// call update to lite-chat only support calls interface
|
||||
if (!options?.version) {
|
||||
console.warn('Please upgrade to the latest Chat UIKit components.');
|
||||
}
|
||||
console.debug(error);
|
||||
}
|
||||
}
|
||||
private async _handleGroupAttributesUpdated(event) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
|
||||
|
||||
const data = event?.data || {};
|
||||
const { groupID: groupId = '', groupAttributes = {} } = data;
|
||||
|
||||
if (groupId !== this._callService?.getCurrentGroupId()) return;
|
||||
|
||||
await this.updateStoreBasedOnGroupAttributes(groupAttributes);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+438
@@ -0,0 +1,438 @@
|
||||
import { ITUIStore } from '../interface/ITUIStore';
|
||||
// @ts-ignore
|
||||
import { TUICallEvent } from '@trtc/call-engine-lite-wx';
|
||||
import { IUserInfo } from '../interface/ICallService';
|
||||
import { StoreName, CallStatus, CallMediaType, NAME, CallRole, StatusChange, ErrorCode, ErrorMessage, AudioPlayBackDevice,
|
||||
NETWORK_QUALITY_THRESHOLD } from '../const/index';
|
||||
import { CallTips, t } from '../locales/index';
|
||||
import { initAndCheckRunEnv } from './miniProgram';
|
||||
import { getMyProfile, getRemoteUserProfile, updateRoomIdAndRoomIdType, analyzeEventData, deleteRemoteUser } from './utils';
|
||||
import promiseRetryDecorator from '../utils/decorators/promise-retry';
|
||||
import { UIDesign } from './UIDesign';
|
||||
import TuiStore from '../TUIStore/tuiStore';
|
||||
const TUIStore: ITUIStore = TuiStore.getInstance();
|
||||
const uiDesign = UIDesign.getInstance();
|
||||
|
||||
|
||||
export default class EngineEventHandler {
|
||||
static instance: EngineEventHandler;
|
||||
private _callService: any;
|
||||
|
||||
constructor(options) {
|
||||
this._callService = options.callService;
|
||||
}
|
||||
|
||||
static getInstance(options) {
|
||||
if (!EngineEventHandler.instance) {
|
||||
EngineEventHandler.instance = new EngineEventHandler(options);
|
||||
}
|
||||
return EngineEventHandler.instance;
|
||||
}
|
||||
public addListenTuiCallEngineEvent() {
|
||||
const callEngine = this._callService?.getTUICallEngineInstance();
|
||||
|
||||
if (!callEngine) {
|
||||
console.warn(`${NAME.PREFIX}add engine event listener failed, engine is empty.`);
|
||||
return;
|
||||
}
|
||||
|
||||
callEngine.on(TUICallEvent.ERROR, this._handleError, this);
|
||||
callEngine.on(TUICallEvent.ON_CALL_RECEIVED, this._handleNewInvitationReceived, this); // 收到邀请事件
|
||||
TUICallEvent?.ON_CALL_BEGIN && callEngine.on(TUICallEvent.ON_CALL_BEGIN, this._handleOnCallBegin, this); // 主叫收到被叫接通事件
|
||||
callEngine.on(TUICallEvent.USER_ENTER, this._handleUserEnter, this); // 有用户进房事件
|
||||
callEngine.on(TUICallEvent.USER_LEAVE, this._handleUserLeave, this); // 有用户离开通话事件
|
||||
callEngine.on(TUICallEvent.REJECT, this._handleInviteeReject, this); // 主叫收到被叫的拒绝通话事件
|
||||
callEngine.on(TUICallEvent.NO_RESP, this._handleNoResponse, this); // 主叫收到被叫的无应答事件
|
||||
callEngine.on(TUICallEvent.LINE_BUSY, this._handleLineBusy, this); // 主叫收到被叫的忙线事件
|
||||
callEngine.on(TUICallEvent.ON_CALL_NOT_CONNECTED, this._handleCallNotConnected, this); // 主被叫在通话未建立时, 收到的取消事件
|
||||
callEngine.on(TUICallEvent.ON_USER_INVITING, this._handleOnUserInviting, this); // 通话存在邀请他人时, 通话里的所有人都会抛出
|
||||
callEngine.on(TUICallEvent.SDK_READY, this._handleSDKReady, this); // SDK Ready 回调
|
||||
callEngine.on(TUICallEvent.KICKED_OUT, this._handleKickedOut, this); // 未开启多端登录时, 多端登录收到的被踢事件
|
||||
callEngine.on(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe, this);
|
||||
// @ts-ignore
|
||||
TUICallEvent.CALL_MESSAGE && callEngine.on(TUICallEvent.CALL_MESSAGE, this._handleCallMessage, this); // call message card display event
|
||||
// @ts-ignore
|
||||
TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED && callEngine.on(TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED, this._handleNetworkQuality, this); // 用户网络质量
|
||||
callEngine.on(TUICallEvent.USER_VIDEO_AVAILABLE, this._handleUserVideoAvailable, this);
|
||||
callEngine.on(TUICallEvent.USER_AUDIO_AVAILABLE, this._handleUserAudioAvailable, this);
|
||||
callEngine.on(TUICallEvent.ON_CALL_END, this._handleCallingEnd, this); // 主被叫在通话结束时, 收到的通话结束事件
|
||||
// @ts-ignore
|
||||
callEngine.on(TUICallEvent.CALL_MODE, this._handleCallTypeChange, this);
|
||||
// @ts-ignore
|
||||
callEngine.on(TUICallEvent.USER_UPDATE, this._handleUserUpdate, this); // mini: user data update
|
||||
}
|
||||
public removeListenTuiCallEngineEvent() {
|
||||
const callEngine = this._callService?.getTUICallEngineInstance();
|
||||
|
||||
callEngine.off(TUICallEvent.ERROR, this._handleError, this);
|
||||
callEngine.off(TUICallEvent.ON_CALL_RECEIVED, this._handleNewInvitationReceived, this);
|
||||
TUICallEvent?.ON_CALL_BEGIN && callEngine.off(TUICallEvent.ON_CALL_BEGIN, this._handleOnCallBegin, this);
|
||||
callEngine.off(TUICallEvent.USER_ENTER, this._handleUserEnter, this);
|
||||
callEngine.off(TUICallEvent.USER_LEAVE, this._handleUserLeave, this);
|
||||
callEngine.off(TUICallEvent.REJECT, this._handleInviteeReject, this);
|
||||
callEngine.off(TUICallEvent.NO_RESP, this._handleNoResponse, this);
|
||||
callEngine.off(TUICallEvent.LINE_BUSY, this._handleLineBusy, this);
|
||||
callEngine.off(TUICallEvent.ON_CALL_NOT_CONNECTED, this._handleCallNotConnected, this);
|
||||
callEngine.off(TUICallEvent.ON_USER_INVITING, this._handleOnUserInviting, this);
|
||||
callEngine.off(TUICallEvent.SDK_READY, this._handleSDKReady, this);
|
||||
callEngine.off(TUICallEvent.KICKED_OUT, this._handleKickedOut, this);
|
||||
callEngine.off(TUICallEvent.MESSAGE_SENT_BY_ME, this._messageSentByMe, this);
|
||||
// @ts-ignore
|
||||
TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED && callEngine.off(TUICallEvent.ON_USER_NETWORK_QUALITY_CHANGED, this._handleNetworkQuality, this);
|
||||
callEngine.off(TUICallEvent.USER_VIDEO_AVAILABLE, this._handleUserVideoAvailable, this);
|
||||
callEngine.off(TUICallEvent.USER_AUDIO_AVAILABLE, this._handleUserAudioAvailable, this);
|
||||
callEngine.off(TUICallEvent.CALL_END, this._handleCallingEnd, this);
|
||||
// @ts-ignore
|
||||
callEngine.off(TUICallEvent.CALL_MODE, this._handleCallTypeChange, this); // 切换通话事件 miniProgram CALL_MODE
|
||||
// @ts-ignore
|
||||
callEngine.off(TUICallEvent.USER_UPDATE, this._handleUserUpdate, this); // mini: user data update
|
||||
}
|
||||
private _callerChangeToConnected() {
|
||||
const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE);
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
|
||||
if (callStatus === CallStatus.CALLING && callRole === CallRole.CALLER) {
|
||||
// iOS 优化:主叫状态变更时立即停止铃声
|
||||
this._callService?._bellContext?.stop();
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
|
||||
this._callService?.startTimer();
|
||||
}
|
||||
}
|
||||
private _unNormalEventsManager(event: any, eventName: TUICallEvent): void {
|
||||
console.log(`${NAME.PREFIX}${eventName} event data: ${JSON.stringify(event)}.`);
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
|
||||
switch (eventName) {
|
||||
case TUICallEvent.REJECT:
|
||||
case TUICallEvent.LINE_BUSY: {
|
||||
const { userID: userId } = analyzeEventData(event);
|
||||
let callTipsKey = eventName === TUICallEvent.REJECT ? CallTips.OTHER_SIDE_REJECT_CALL : CallTips.OTHER_SIDE_LINE_BUSY;
|
||||
let userListNeedToShow = '';
|
||||
if (isGroup) {
|
||||
userListNeedToShow = (remoteUserInfoList.find(obj => obj.userId === userId) || {}).displayUserInfo || userId;
|
||||
callTipsKey = eventName === TUICallEvent.REJECT ? CallTips.REJECT_CALL : CallTips.IN_BUSY;
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { content: { key: callTipsKey, options: { userList: userListNeedToShow } } });
|
||||
userId && deleteRemoteUser([userId]);
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST).length === 0) {
|
||||
this._callService?._resetCallStore();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TUICallEvent.NO_RESP: {
|
||||
const { userIDList = [] } = analyzeEventData(event);
|
||||
const callTipsKey = isGroup ? CallTips.TIMEOUT : CallTips.CALL_TIMEOUT;
|
||||
const userInfoList: string[] = userIDList.map(userId => {
|
||||
const userInfo: IUserInfo = remoteUserInfoList.find(obj => obj.userId === userId) || {};
|
||||
return userInfo.displayUserInfo || userId;
|
||||
});
|
||||
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { content: { key: callTipsKey, options: { userList: userInfoList.join() } } });
|
||||
userIDList.length > 0 && deleteRemoteUser(userIDList);
|
||||
break;
|
||||
}
|
||||
case TUICallEvent.ON_CALL_NOT_CONNECTED: {
|
||||
this._callService?._resetCallStore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleError(event: any): void {
|
||||
const { code, message } = event || {};
|
||||
const index = Object.values(ErrorCode).indexOf(code);
|
||||
let callTips = '';
|
||||
|
||||
if (index !== -1) {
|
||||
const key = Object.keys(ErrorCode)[index];
|
||||
callTips = t(ErrorMessage[key]);
|
||||
callTips && TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { content: ErrorMessage[key], type: NAME.ERROR });
|
||||
}
|
||||
this._callService?.executeExternalAfterCalling();
|
||||
console.error(`${NAME.PREFIX}_handleError, errorCode: ${code}; errorMessage: ${callTips || message}.`);
|
||||
}
|
||||
private async _handleNewInvitationReceived(event: any) {
|
||||
console.log(`${NAME.PREFIX}onCallReceived event data: ${JSON.stringify(event)}.`);
|
||||
const { callerId = '', callMediaType, inviteData = {}, calleeIdList = [], chatGroupID: groupID = '', roomID, strRoomID } = analyzeEventData(event);
|
||||
const currentUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
const remoteUserIdList: string[] = [callerId, ...calleeIdList.filter((userId: string) => userId !== currentUserInfo.userId)];
|
||||
const type = callMediaType || inviteData.callType;
|
||||
const callTipsKey = type === CallMediaType.AUDIO ? CallTips.CALLEE_CALLING_AUDIO_MSG : CallTips.CALLEE_CALLING_VIDEO_MSG;
|
||||
let updateStoreParams = {
|
||||
[NAME.CALL_ROLE]: CallRole.CALLEE,
|
||||
[NAME.IS_GROUP]: (!!groupID || calleeIdList.length > 1),
|
||||
[NAME.CALL_STATUS]: CallStatus.CALLING,
|
||||
[NAME.CALL_MEDIA_TYPE]: type,
|
||||
[NAME.CALL_TIPS]: callTipsKey,
|
||||
[NAME.CALLER_USER_INFO]: { userId: callerId },
|
||||
[NAME.GROUP_ID]: groupID,
|
||||
};
|
||||
initAndCheckRunEnv();
|
||||
const pusher = { enableCamera: type === CallMediaType.VIDEO, enableMic: true }; // mini 默认打开麦克风
|
||||
updateStoreParams = { ...updateStoreParams, [NAME.PUSHER]: pusher };
|
||||
(type === CallMediaType.VIDEO) && this._callService.openCamera(NAME.LOCAL_VIDEO);
|
||||
this._callService.openMicrophone()
|
||||
const deviceMap = {
|
||||
microphone: true,
|
||||
camera: type === CallMediaType.VIDEO,
|
||||
};
|
||||
this._callService._preDevicePermission = await this._callService._tuiCallEngine.deviceCheck(deviceMap);
|
||||
updateRoomIdAndRoomIdType(roomID, strRoomID);
|
||||
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
|
||||
this._callService?.executeExternalBeforeCalling();
|
||||
this._callService?.statusChanged && this._callService?.statusChanged({ oldStatus: StatusChange.IDLE, newStatus: StatusChange.BE_INVITED });
|
||||
|
||||
const remoteUserInfoList = await getRemoteUserProfile(remoteUserIdList, this._callService?.getTim());
|
||||
const [userInfo] = remoteUserInfoList.filter((userInfo: IUserInfo) => userInfo.userId === callerId);
|
||||
|
||||
remoteUserInfoList.length > 0 && TUIStore.updateStore({
|
||||
[NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
|
||||
[NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
|
||||
[NAME.CALLER_USER_INFO]: {
|
||||
userId: callerId,
|
||||
nick: userInfo?.nick || '',
|
||||
avatar: userInfo?.avatar || '',
|
||||
displayUserInfo: userInfo?.remark || userInfo?.nick || callerId,
|
||||
},
|
||||
}, StoreName.CALL);
|
||||
}
|
||||
private async _handleOnCallBegin(event: any): Promise<void> {
|
||||
// iOS 优化:ON_CALL_BEGIN 事件时立即停止铃声
|
||||
this._callService?._bellContext?.stop();
|
||||
this._callerChangeToConnected();
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, { text: 'answered', duration: 2000 });
|
||||
await this._callService.openMicrophone();
|
||||
console.log(`${NAME.PREFIX}accept event data: ${JSON.stringify(event)}.`);
|
||||
}
|
||||
private async _handleUserEnter(event: any): Promise<void> {
|
||||
const { userID: userId, data } = analyzeEventData(event);
|
||||
if (userId && TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE) === CallRole.CALLEE) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CALLING) {
|
||||
this._callService._handleAcceptResponse({});
|
||||
this._callService._cleanupAvoidRepeatCallState('accept');
|
||||
}
|
||||
}
|
||||
this._callerChangeToConnected();
|
||||
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
|
||||
await this._addUserToRemoteUserInfoList(userId);
|
||||
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
remoteUserInfoList = remoteUserInfoList.map((obj: IUserInfo) => {
|
||||
if (obj.userId === userId) obj.isEnter = true;
|
||||
return obj;
|
||||
});
|
||||
|
||||
if (remoteUserInfoList.length > 0) {
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
|
||||
uiDesign.updateViewBackgroundUserId('remote');
|
||||
}
|
||||
console.log(`${NAME.PREFIX}userEnter event data: ${JSON.stringify(event)}.`);
|
||||
}
|
||||
private _handleUserLeave(event: any): void {
|
||||
console.log(`${NAME.PREFIX}userLeave event data: ${JSON.stringify(event)}.`);
|
||||
const { data, userID: userId } = analyzeEventData(event);
|
||||
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
|
||||
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.IS_GROUP)) {
|
||||
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const userListNeedToShow: string = (remoteUserInfoList.find(obj => obj.userId === userId) || {}).displayUserInfo || userId;
|
||||
TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { content: { key: CallTips.END_CALL, options: { userList: userListNeedToShow } } });
|
||||
}
|
||||
userId && deleteRemoteUser([userId]);
|
||||
}
|
||||
private _handleInviteeReject(event: any): void {
|
||||
this._unNormalEventsManager(event, TUICallEvent.REJECT);
|
||||
}
|
||||
private _handleNoResponse(event: any): void {
|
||||
this._unNormalEventsManager(event, TUICallEvent.NO_RESP);
|
||||
}
|
||||
private _handleLineBusy(event: any): void {
|
||||
this._unNormalEventsManager(event, TUICallEvent.LINE_BUSY);
|
||||
}
|
||||
private _handleCallNotConnected(event: any): void {
|
||||
this?._callService?._cleanupAvoidRepeatCallState?.();
|
||||
this._callService?.executeExternalAfterCalling();
|
||||
this._unNormalEventsManager(event, TUICallEvent.ON_CALL_NOT_CONNECTED);
|
||||
}
|
||||
private async _handleOnUserInviting(event: any) {
|
||||
const { userID: userId } = analyzeEventData(event);
|
||||
if (!userId) return;
|
||||
|
||||
if (userId !== TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO).userId) {
|
||||
await this._addUserToRemoteUserInfoList(userId);
|
||||
}
|
||||
}
|
||||
private _handleCallingEnd(event: any): void {
|
||||
console.log(`${NAME.PREFIX}callEnd event data: ${JSON.stringify(event)}.`);
|
||||
this._callService?.executeExternalAfterCalling();
|
||||
this._callService?._resetCallStore();
|
||||
}
|
||||
// SDK_READY 后才能调用 tim 接口, 否则登录后立刻获取导致调用接口失败. v2.27.4+、v3 接口 login 后会抛出 SDK_READY
|
||||
private async _handleSDKReady(event: any): Promise<void> {
|
||||
let localUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
localUserInfo = await getMyProfile(localUserInfo.userId, this._callService?.getTim());
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, localUserInfo);
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
|
||||
}
|
||||
private _handleKickedOut(event: any): void {
|
||||
console.log(`${NAME.PREFIX}kickOut event data: ${JSON.stringify(event)}.`);
|
||||
|
||||
this._callService?.kickedOut && this._callService?.kickedOut(event);
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, CallTips.KICK_OUT);
|
||||
this._callService?._resetCallStore();
|
||||
}
|
||||
private _messageSentByMe(event: any): void {
|
||||
const message = event?.data;
|
||||
|
||||
this._callService?.onMessageSentByMe && this._callService?.onMessageSentByMe(message);
|
||||
}
|
||||
private _handleCallMessage(event: any) {
|
||||
const message = analyzeEventData(event);
|
||||
this._callService._chatCombine.callTUIService({ message });
|
||||
}
|
||||
private _handleCallTypeChange(event: any): void {
|
||||
const { newCallType, type } = analyzeEventData(event);
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_MEDIA_TYPE, newCallType || type);
|
||||
|
||||
this._callService?.setSoundMode(AudioPlayBackDevice.EAR);
|
||||
}
|
||||
private _handleNetworkQuality(event) {
|
||||
const { networkQualityList = [] } = analyzeEventData(event);
|
||||
TUIStore.update(StoreName.CALL, NAME.NETWORK_STATUS, networkQualityList);
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
|
||||
if(!isGroup) {
|
||||
const isRemoteNetworkPoor = networkQualityList.find(user => remoteUserInfoList[0]?.userId === user?.userId && user?.quality >= NETWORK_QUALITY_THRESHOLD);
|
||||
if(isRemoteNetworkPoor) {
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, CallTips.REMOTE_NETWORK_IS_POOR);
|
||||
return;
|
||||
};
|
||||
const isLocalNetworkPoor = networkQualityList.find(user => localUserInfo?.userId === user?.userId && user?.quality >= NETWORK_QUALITY_THRESHOLD);
|
||||
if(isLocalNetworkPoor) {
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, CallTips.LOCAL_NETWORK_IS_POOR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// =============================【 WEB 私有事件】==============================
|
||||
@promiseRetryDecorator({
|
||||
retries: 5,
|
||||
timeout: 200,
|
||||
onRetrying(retryCount) {
|
||||
console.warn(`${NAME.PREFIX}_startRemoteView, retrying [${retryCount}]`);
|
||||
},
|
||||
})
|
||||
private async _startRemoteView(userId: string) {
|
||||
if (!userId) {
|
||||
console.warn(`${NAME.PREFIX}_startRemoteView userID is empty`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const displayMode = TUIStore.getData(StoreName.CALL, NAME.DISPLAY_MODE);
|
||||
await this._callService?.getTUICallEngineInstance().startRemoteView({ userID: userId, videoViewDomID: `${userId}_0`, options: { objectFit: displayMode } });
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}_startRemoteView error: ${error}.`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
private _setRemoteUserInfoAudioVideoAvailable(isAvailable: boolean, type: string, userId: string) {
|
||||
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
|
||||
remoteUserInfoList = remoteUserInfoList.map((obj: IUserInfo) => {
|
||||
if (obj.userId === userId) {
|
||||
if (type === NAME.AUDIO) {
|
||||
return { ...obj, isAudioAvailable: isAvailable };
|
||||
}
|
||||
if (type === NAME.VIDEO) {
|
||||
return { ...obj, isVideoAvailable: isAvailable };
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
if (remoteUserInfoList.length > 0) {
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
|
||||
}
|
||||
}
|
||||
private async _handleUserVideoAvailable(event: any): Promise<any> {
|
||||
const { userID: userId, isVideoAvailable } = analyzeEventData(event);
|
||||
console.log(`${NAME.PREFIX}_handleUserVideoAvailable event data: ${JSON.stringify(event)}.`);
|
||||
|
||||
this._setRemoteUserInfoAudioVideoAvailable(isVideoAvailable, NAME.VIDEO, userId);
|
||||
try {
|
||||
isVideoAvailable && await this._startRemoteView(userId);
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}_startRemoteView failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
private _handleUserAudioAvailable(event: any): void {
|
||||
const { userID: userId, isAudioAvailable } = analyzeEventData(event);
|
||||
console.log(`${NAME.PREFIX}_handleUserAudioAvailable event data: ${JSON.stringify(event)}.`);
|
||||
|
||||
this._setRemoteUserInfoAudioVideoAvailable(isAudioAvailable, NAME.AUDIO, userId);
|
||||
}
|
||||
private _handleUserVoiceVolume(event: any): void {
|
||||
try {
|
||||
const { volumeMap: volumeList } = analyzeEventData(event);
|
||||
if ((volumeList || []).length === 0) return; // 减少不必要的更新
|
||||
|
||||
const localUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
let remoteUserInfoList: IUserInfo[] = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const [localUserVolumeObj] = volumeList.filter((obj: any) => obj.userId === localUserInfo.userId);
|
||||
const remoteUserVolumeObj = volumeList.reduce((acc: any, obj: any) => {
|
||||
if (obj.userId !== localUserInfo.userId) {
|
||||
return { ...acc, [obj.userId]: obj.audioVolume };
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
localUserInfo.volume = localUserVolumeObj.audioVolume;
|
||||
remoteUserInfoList = remoteUserInfoList.map((obj: any) => ({ ...obj, volume: remoteUserVolumeObj[obj.userId] }));
|
||||
const updateStoreParams = {
|
||||
[NAME.LOCAL_USER_INFO]: localUserInfo,
|
||||
[NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
|
||||
};
|
||||
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
}
|
||||
}
|
||||
private _handleDeviceUpdate(event: any): void {
|
||||
const { cameraList, microphoneList, speakerList, currentCamera, currentMicrophone, currentSpeaker } = event;
|
||||
TUIStore.update(StoreName.CALL, NAME.DEVICE_LIST, { cameraList, microphoneList, speakerList, currentCamera, currentMicrophone, currentSpeaker });
|
||||
}
|
||||
// ==========================【 miniProgram 私有事件】==========================
|
||||
private _handleUserUpdate(event: any): void {
|
||||
const data = analyzeEventData(event);
|
||||
|
||||
data?.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, data.pusher);
|
||||
data?.playerList && TUIStore.update(StoreName.CALL, NAME.PLAYER, data.playerList);
|
||||
}
|
||||
private async _addUserToRemoteUserInfoList(userId: string) {
|
||||
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const isInRemoteUserList = remoteUserInfoList.find(item => item?.userId === userId);
|
||||
|
||||
if (!isInRemoteUserList) {
|
||||
remoteUserInfoList.push({ userId });
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
|
||||
|
||||
const [userInfo] = await getRemoteUserProfile([userId], this._callService?.getTim());
|
||||
remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
remoteUserInfoList.forEach((obj) => {
|
||||
if (obj?.userId === userId) {
|
||||
obj = Object.assign(obj, userInfo);
|
||||
}
|
||||
});
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+860
@@ -0,0 +1,860 @@
|
||||
import {
|
||||
IUserInfo, ICallbackParam, ISelfInfoParams, IBellParams, IInviteUserParams, IInitParams, ICallsParams,
|
||||
} from '../interface/ICallService';
|
||||
import {
|
||||
StoreName, CallStatus, CallMediaType, NAME, CALL_DATA_KEY, LanguageType, CallRole, LOG_LEVEL, VideoDisplayMode,
|
||||
VideoResolution, StatusChange, AudioPlayBackDevice, CameraPosition, COMPONENT, FeatureButton, ButtonState,
|
||||
LayoutMode, DEFAULT_BLUR_LEVEL,
|
||||
} from '../const/index';
|
||||
// @ts-ignore
|
||||
import { TUICallEngine, devicePermissions } from '@trtc/call-engine-lite-wx';
|
||||
import { beforeCall, handlePackageError, handleNoPusherCapabilityError } from './miniProgram';
|
||||
import { CallTips, t } from '../locales/index';
|
||||
import { BellContext } from './bellContext';
|
||||
import { avoidRepeatedCall } from '../utils/validate/index';
|
||||
import { handleRepeatedCallError, formatTime, performanceNow, initBrowserCloseDetection } from '../utils/common-utils';
|
||||
import { getRemoteUserProfile, generateStatusChangeText, noDevicePermissionToast, setLocalUserInfoAudioVideoAvailable,
|
||||
getGroupMemberList, getGroupProfile, updateRoomIdAndRoomIdType, updateDeviceList } from './utils';
|
||||
import timer from '../utils/timer';
|
||||
import { ITUIGlobal, ITUIStore } from '../interface/index';
|
||||
import TuiGlobal from '../TUIGlobal/tuiGlobal';
|
||||
import TuiStore from '../TUIStore/tuiStore';
|
||||
import { UIDesign } from './UIDesign';
|
||||
import ChatCombine from './chatCombine';
|
||||
import EngineEventHandler from './engineEventHandler';
|
||||
const TUIGlobal: ITUIGlobal = TuiGlobal.getInstance();
|
||||
const TUIStore: ITUIStore = TuiStore.getInstance();
|
||||
const uiDesign = UIDesign.getInstance();
|
||||
uiDesign.setTUIStore(TUIStore);
|
||||
const version = '4.2.7';
|
||||
export { TUIGlobal, TUIStore, uiDesign };
|
||||
|
||||
export default class TUICallService {
|
||||
static instance: TUICallService;
|
||||
public _tuiCallEngine: any;
|
||||
private _tim: any = null;
|
||||
private _TUICore: any = null;
|
||||
private _timerId: number = -1;
|
||||
private _startTimeStamp: number = performanceNow();
|
||||
private _bellContext: any = null;
|
||||
private _isFromChat: boolean = false;
|
||||
private _currentGroupId: string = ''; // The currentGroupId of the group chat that the user is currently in
|
||||
private _offlinePushInfo = null;
|
||||
private _permissionCheckTimer: any = null;
|
||||
private _chatCombine: any = null;
|
||||
private _engineEventHandler: any = null;
|
||||
// wasm ready
|
||||
private _wasmReadyPromise;
|
||||
private _wasmReadyResolve;
|
||||
private _isInitialized = false;
|
||||
|
||||
constructor() {
|
||||
console.log(`${NAME.PREFIX}version: ${version}`);
|
||||
this._wasmReadyPromise = new Promise(resolve => {
|
||||
this._wasmReadyResolve = resolve;
|
||||
});
|
||||
this._loadWasm();
|
||||
this._watchTUIStore();
|
||||
this._engineEventHandler = EngineEventHandler.getInstance({ callService: this });
|
||||
|
||||
this._chatCombine = ChatCombine.getInstance({ callService: this });
|
||||
}
|
||||
static getInstance() {
|
||||
if (!TUICallService.instance) {
|
||||
TUICallService.instance = new TUICallService();
|
||||
}
|
||||
return TUICallService.instance;
|
||||
}
|
||||
private _loadWasm() {
|
||||
TUICallEngine.once('ready', () => this._wasmReadyResolve());
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async init(params: IInitParams) {
|
||||
if (this._isInitialized) {
|
||||
console.warn('TUICallKit has already been initialized.');
|
||||
return;
|
||||
}
|
||||
await this._wasmReadyPromise;
|
||||
|
||||
if (!this._isInitialized) {
|
||||
this._doInit(params);
|
||||
this._isInitialized = true;
|
||||
}
|
||||
}
|
||||
private async _doInit(params: IInitParams) {
|
||||
try {
|
||||
if (this._tuiCallEngine) return;
|
||||
// @ts-ignore
|
||||
let { userID, tim, userSig, sdkAppID, SDKAppID, isFromChat, component = COMPONENT.TUI_CALL_KIT } = params;
|
||||
if (this._TUICore) {
|
||||
sdkAppID = this._TUICore.SDKAppID;
|
||||
tim = this._TUICore.tim;
|
||||
}
|
||||
this._tim = tim;
|
||||
console.log(`${NAME.PREFIX}init sdkAppId: ${sdkAppID || SDKAppID}, userId: ${userID}`);
|
||||
|
||||
let scene = `web`;
|
||||
// To distinguish whether the UniApp mini-program chooses Vue 2 or Vue 3
|
||||
let vueVersion = 2;
|
||||
// #ifdef VUE3
|
||||
vueVersion = 3;
|
||||
// #endif
|
||||
scene = `wx-uniapp-vue${vueVersion}`;
|
||||
|
||||
this._tuiCallEngine = TUICallEngine.createInstance({
|
||||
tim,
|
||||
// @ts-ignore
|
||||
sdkAppID: sdkAppID || SDKAppID, // 兼容传入 SDKAppID 的问题
|
||||
callkitVersion: version,
|
||||
isFromChat: isFromChat || false,
|
||||
component,
|
||||
scene,
|
||||
});
|
||||
uiDesign.setEngineInstance(this._tuiCallEngine);
|
||||
this._addListenTuiCallEngineEvent();
|
||||
if (this._bellContext) {
|
||||
this._bellContext?.destroy();
|
||||
}
|
||||
this._bellContext = new BellContext();
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { userId: userID });
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { userId: userID });
|
||||
uiDesign.updateViewBackgroundUserId('local');
|
||||
|
||||
await this._tuiCallEngine.login({ userID, userSig, assetsPath: '' }); // web && mini
|
||||
const uiConfig = TUIStore.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallkit.init',
|
||||
data: {
|
||||
uiConfig,
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}init failed, error: ${error}.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// component destroy
|
||||
public async destroyed() {
|
||||
try {
|
||||
this._isInitialized = false;
|
||||
const currentCallStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
if (currentCallStatus !== CallStatus.IDLE) {
|
||||
throw new Error(`please destroyed when status is idle, current status: ${currentCallStatus}`);
|
||||
}
|
||||
if (this._tuiCallEngine) {
|
||||
this._removeListenTuiCallEngineEvent();
|
||||
await this._tuiCallEngine.destroyInstance();
|
||||
this._tuiCallEngine = null;
|
||||
}
|
||||
this._bellContext?.destroy();
|
||||
this._bellContext = null;
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}destroyed failed, error: ${error}.`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// ===============================【通话操作】===============================
|
||||
@avoidRepeatedCall()
|
||||
public async inviteUser(params: IInviteUserParams) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.IDLE) return; // avoid double click when application stuck
|
||||
try {
|
||||
const { userIDList } = params;
|
||||
let inviteUserInfoList = await getRemoteUserProfile(userIDList, this.getTim());
|
||||
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const userIDListNotInRemoteUserInfoList = userIDList.filter(userId => {
|
||||
return !remoteUserInfoList.some(remoteUserInfo => remoteUserInfo.userId === userId);
|
||||
});
|
||||
if (userIDListNotInRemoteUserInfoList.length === 0) {
|
||||
return;
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, [...remoteUserInfoList, ...inviteUserInfoList]);
|
||||
this._tuiCallEngine && await this._tuiCallEngine.inviteUser(params);
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}inviteUser failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async calls(callsParams: ICallsParams) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return; // avoid double click when application stuck
|
||||
try {
|
||||
TUIStore.update(StoreName.CALL, NAME.PUSHER_ID, NAME.NEW_PUSHER);
|
||||
const { userIDList, type, chatGroupID, offlinePushInfo } = callsParams;
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
|
||||
const remoteUserInfoList = userIDList.map(userId => ({ userId }));
|
||||
await this._updateCallStoreBeforeCall(type, remoteUserInfoList, chatGroupID);
|
||||
this.executeExternalBeforeCalling();
|
||||
callsParams.offlinePushInfo = { ...this.getDefaultOfflinePushInfo(), ...offlinePushInfo };
|
||||
const response = await this._tuiCallEngine.calls(callsParams);
|
||||
await this._updateCallStoreAfterCall(userIDList, response);
|
||||
} catch (error: any) {
|
||||
this._handleCallError(error, 'calls');
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async join(params) {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CONNECTED) return; // avoid double click when application stuck
|
||||
try {
|
||||
TUIStore.update(StoreName.CALL, NAME.PUSHER_ID, NAME.NEW_PUSHER);
|
||||
const updateStoreParams = {
|
||||
[NAME.CALL_ROLE]: CallRole.CALLEE,
|
||||
[NAME.IS_GROUP]: true,
|
||||
[NAME.CALL_STATUS]: CallStatus.CONNECTED,
|
||||
[NAME.CALL_MEDIA_TYPE]: TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE) || CallMediaType.AUDIO, // default audio callMediaType
|
||||
};
|
||||
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
|
||||
|
||||
const response = await this._tuiCallEngine.join(params);
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
|
||||
this.startTimer();
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.PUSHER, response);
|
||||
this.setSoundMode(params.type === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
|
||||
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
|
||||
|
||||
await this.openMicrophone();
|
||||
setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO);
|
||||
|
||||
// // Current policy: By default, the camera remains off when joining GroupCall
|
||||
// if (TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE) === CallMediaType.VIDEO) {
|
||||
// await this.openCamera(NAME.LOCAL_VIDEO);
|
||||
// setLocalUserInfoAudioVideoAvailable(true, NAME.VIDEO);
|
||||
// }
|
||||
} catch (error) {
|
||||
this._handleCallError(error, 'join');
|
||||
}
|
||||
}
|
||||
// ===============================【其它对外接口】===============================
|
||||
public getTUICallEngineInstance(): any {
|
||||
return this?._tuiCallEngine || null;
|
||||
}
|
||||
public setLogLevel(level: LOG_LEVEL) {
|
||||
this?._tuiCallEngine?.setLogLevel(level);
|
||||
}
|
||||
public setLanguage(language: string) {
|
||||
console.warn(`${NAME.PREFIX}The miniProgram does not support setLanguage`);
|
||||
}
|
||||
public enableFloatWindow(enable: boolean) {
|
||||
TUIStore.update(StoreName.CALL, NAME.ENABLE_FLOAT_WINDOW, enable);
|
||||
}
|
||||
public async setSelfInfo(params: ISelfInfoParams) {
|
||||
const { nickName, avatar } = params;
|
||||
try {
|
||||
await this._tuiCallEngine.setSelfInfo(nickName, avatar);
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}setSelfInfo failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
public async enableVirtualBackground(enable: boolean) {
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_SHOW_ENABLE_VIRTUAL_BACKGROUND, enable);
|
||||
}
|
||||
// 修改默认铃声:只支持本地铃声文件,不支持在线铃声文件;修改铃声修改的是被叫的铃声
|
||||
public async setCallingBell(filePath?: string) {
|
||||
let isCheckFileExist: boolean = true;
|
||||
if (!isCheckFileExist) {
|
||||
console.warn(`${NAME.PREFIX}setCallingBell failed, filePath: ${filePath}.`);
|
||||
return ;
|
||||
}
|
||||
const bellParams: IBellParams = { calleeBellFilePath: filePath };
|
||||
this._bellContext.setBellProperties(bellParams);
|
||||
}
|
||||
public async enableMuteMode(enable: boolean) {
|
||||
try {
|
||||
const bellParams: IBellParams = { isMuteBell: enable };
|
||||
this._bellContext.setBellProperties(bellParams);
|
||||
await this._bellContext.setBellMute(enable);
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}enableMuteMode failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
public hideFeatureButton(buttonName: FeatureButton) {
|
||||
uiDesign.hideFeatureButton(buttonName);
|
||||
}
|
||||
public setLocalViewBackgroundImage(url: string) {
|
||||
uiDesign.setLocalViewBackgroundImage(url);
|
||||
}
|
||||
public setRemoteViewBackgroundImage(userId: string, url: string) {
|
||||
uiDesign.setRemoteViewBackgroundImage(userId, url);
|
||||
}
|
||||
public setLayoutMode(layoutMode: LayoutMode) {
|
||||
uiDesign.setLayoutMode(layoutMode);
|
||||
}
|
||||
public setCameraDefaultState(isOpen: boolean) {
|
||||
uiDesign.setCameraDefaultState(isOpen);
|
||||
}
|
||||
// =============================【实验性接口】=============================
|
||||
public callExperimentalAPI(jsonStr: string) {
|
||||
const jsonObj = JSON.parse(jsonStr);
|
||||
if (jsonObj === jsonStr) return;
|
||||
|
||||
const { api, params } = jsonObj;
|
||||
if (!api || !params) return;
|
||||
|
||||
try {
|
||||
switch(api) {
|
||||
case 'forceUseV2API':
|
||||
const { enable } = params;
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_FORCE_USE_V2_API, !!enable);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
this._tuiCallEngine?.reportLog?.({ name: 'TUICallKit.callExperimentalAPI.fail', data: { error } });
|
||||
}
|
||||
}
|
||||
// =============================【内部按钮操作方法】=============================
|
||||
@avoidRepeatedCall()
|
||||
public async accept() {
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.accept.start',
|
||||
data: { callStatus },
|
||||
});
|
||||
if (callStatus === CallStatus.CONNECTED) return; // avoid double click when application stuck, especially for miniProgram
|
||||
try {
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
const deviceMap = {
|
||||
microphone: true,
|
||||
camera: callMediaType === CallMediaType.VIDEO,
|
||||
};
|
||||
const currentDevicePermission = await this._tuiCallEngine.deviceCheck(deviceMap);
|
||||
if (!currentDevicePermission) {
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
if (typeof devicePermissions === 'function') {
|
||||
await devicePermissions(callMediaType);
|
||||
}
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.PUSHER_ID, NAME.NEW_PUSHER);
|
||||
const response = await this._tuiCallEngine.accept();
|
||||
await this._handleAcceptResponse(response);
|
||||
} catch (error) {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallKit.accept.fail',
|
||||
level: 'error',
|
||||
error,
|
||||
});
|
||||
if (handleRepeatedCallError(error)) return;
|
||||
noDevicePermissionToast(error, CallMediaType.AUDIO, this._tuiCallEngine);
|
||||
this._resetCallStore();
|
||||
}
|
||||
}
|
||||
private async _handleAcceptResponse(response) {
|
||||
if (response) {
|
||||
// 小程序接通时会进行授权弹框, 状态需要放在 accept 后, 否则先接通后再拉起权限设置
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CONNECTED) return;
|
||||
|
||||
// C++ 回调异常时, 此时需要 callkit 主动打开音频, 否则出现小程序没有采集推音频流
|
||||
this.openMicrophone();
|
||||
// iOS 优化:立即停止铃声,避免状态变更延迟导致的铃声持续
|
||||
this._bellContext?.stop();
|
||||
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
|
||||
this._chatCombine?.callTUIService({ message: response?.data?.message });
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
|
||||
this.startTimer();
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
|
||||
(callMediaType === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
|
||||
response.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, response.pusher);
|
||||
this.setSoundMode(callMediaType === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
|
||||
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
|
||||
setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini default open audio
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async hangup() {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.IDLE) return; // avoid double click when application stuck
|
||||
try {
|
||||
const response = await this._tuiCallEngine.hangup();
|
||||
response?.forEach((item) => {
|
||||
if (item?.code === 0) {
|
||||
this._chatCombine?.callTUIService({ message: item?.data?.message });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
}
|
||||
this._resetCallStore();
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async reject() {
|
||||
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.IDLE) return; // avoid double click when application stuck
|
||||
try {
|
||||
const response = await this._tuiCallEngine.reject();
|
||||
if (response?.code === 0) {
|
||||
this._chatCombine?.callTUIService({ message: response?.data?.message });
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
}
|
||||
this._resetCallStore();
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async openCamera(videoViewDomID: string) {
|
||||
try {
|
||||
if (TUIGlobal.isH5 || TUIGlobal.isWeChat) {
|
||||
const currentPosition = TUIStore.getData(StoreName.CALL, NAME.CAMERA_POSITION);
|
||||
const isFrontCamera = currentPosition === CameraPosition.FRONT ? true : false;
|
||||
this._tuiCallEngine.openCamera(videoViewDomID, isFrontCamera);
|
||||
} else {
|
||||
await this._tuiCallEngine.openCamera(videoViewDomID);
|
||||
}
|
||||
setLocalUserInfoAudioVideoAvailable(true, NAME.VIDEO);
|
||||
} catch (error: any) {
|
||||
noDevicePermissionToast(error, CallMediaType.VIDEO, this._tuiCallEngine);
|
||||
console.error(`${NAME.PREFIX}openCamera error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async closeCamera() {
|
||||
try {
|
||||
await this._tuiCallEngine.closeCamera();
|
||||
setLocalUserInfoAudioVideoAvailable(false, NAME.VIDEO);
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}closeCamera error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async openMicrophone() {
|
||||
try {
|
||||
await this._tuiCallEngine.openMicrophone();
|
||||
setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO);
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}openMicrophone failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async closeMicrophone() {
|
||||
try {
|
||||
await this._tuiCallEngine.closeMicrophone();
|
||||
setLocalUserInfoAudioVideoAvailable(false, NAME.AUDIO);
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}closeMicrophone failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public switchScreen(userId: string) {
|
||||
if(!userId) return;
|
||||
TUIStore.update(StoreName.CALL, NAME.BIG_SCREEN_USER_ID, userId);
|
||||
}
|
||||
// support video to audio; not support audio to video
|
||||
@avoidRepeatedCall()
|
||||
public async switchCallMediaType() {
|
||||
try {
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
if (callMediaType === CallMediaType.AUDIO) {
|
||||
console.warn(`${NAME.PREFIX}switchCallMediaType failed, ${callMediaType} not support.`);
|
||||
return;
|
||||
}
|
||||
const response = await this._tuiCallEngine.switchCallMediaType(CallMediaType.AUDIO);
|
||||
if (response?.code === 0) {
|
||||
this._chatCombine?.callTUIService({ message: response?.data?.message });
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_MEDIA_TYPE, CallMediaType.AUDIO);
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
const oldStatus = isGroup ? StatusChange.CALLING_GROUP_VIDEO : StatusChange.CALLING_C2C_VIDEO;
|
||||
const newStatus = generateStatusChangeText();
|
||||
this.statusChanged && this.statusChanged({ oldStatus, newStatus });
|
||||
this.setSoundMode(AudioPlayBackDevice.EAR);
|
||||
} catch (error: any) {
|
||||
console.error(`${NAME.PREFIX}switchCallMediaType failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async switchCamera() {
|
||||
const currentPosition = TUIStore.getData(StoreName.CALL, NAME.CAMERA_POSITION);
|
||||
const targetPosition = currentPosition === CameraPosition.BACK ? CameraPosition.FRONT : CameraPosition.BACK;
|
||||
try {
|
||||
await this._tuiCallEngine.switchCamera(targetPosition);
|
||||
TUIStore.update(StoreName.CALL, NAME.CAMERA_POSITION, targetPosition);
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}_switchCamera failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public setSoundMode(type?: string): void {
|
||||
try {
|
||||
let isEarPhone = TUIStore.getData(StoreName.CALL, NAME.IS_EAR_PHONE);
|
||||
const soundMode = type || (isEarPhone ? AudioPlayBackDevice.SPEAKER : AudioPlayBackDevice.EAR); // UI 层切换时传参数
|
||||
this._tuiCallEngine?.selectAudioPlaybackDevice(soundMode);
|
||||
if (type) {
|
||||
isEarPhone = type === AudioPlayBackDevice.EAR;
|
||||
} else {
|
||||
isEarPhone = !isEarPhone;
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_EAR_PHONE, isEarPhone);
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}setSoundMode failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
@avoidRepeatedCall()
|
||||
public async setBlurBackground(enable: boolean) {
|
||||
try {
|
||||
TUIStore.update(StoreName.CALL, NAME.ENABLE_VIRTUAL_BACKGROUND, enable);
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}_setBlurBackground failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
// ==========================【TUICallEngine 事件处理】==========================
|
||||
private _addListenTuiCallEngineEvent() {
|
||||
this._engineEventHandler.addListenTuiCallEngineEvent();
|
||||
}
|
||||
private _removeListenTuiCallEngineEvent() {
|
||||
this._engineEventHandler.removeListenTuiCallEngineEvent();
|
||||
}
|
||||
// ========================【原 Web CallKit 提供的方法】========================
|
||||
public beforeCalling: ((...args: any[]) => void) | undefined; // 原来
|
||||
public afterCalling: ((...args: any[]) => void) | undefined;
|
||||
public onMinimized: ((...args: any[]) => void) | undefined;
|
||||
public onMessageSentByMe: ((...args: any[]) => void) | undefined;
|
||||
public kickedOut: ((...args: any[]) => void) | undefined;
|
||||
public statusChanged: ((...args: any[]) => void) | undefined;
|
||||
public setCallback(params: ICallbackParam) {
|
||||
const { beforeCalling, afterCalling, onMinimized, onMessageSentByMe, kickedOut, statusChanged } = params;
|
||||
beforeCalling && (this.beforeCalling = beforeCalling);
|
||||
afterCalling && (this.afterCalling = afterCalling);
|
||||
onMinimized && (this.onMinimized = onMinimized);
|
||||
onMessageSentByMe && (this.onMessageSentByMe = onMessageSentByMe);
|
||||
kickedOut && (this.kickedOut = kickedOut);
|
||||
statusChanged && (this.statusChanged = statusChanged);
|
||||
}
|
||||
public toggleMinimize() {
|
||||
const isMinimized = TUIStore.getData(StoreName.CALL, NAME.IS_MINIMIZED);
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_MINIMIZED, !isMinimized);
|
||||
console.log(`${NAME.PREFIX}toggleMinimize: ${isMinimized} -> ${!isMinimized}.`);
|
||||
this.onMinimized && this.onMinimized(isMinimized, !isMinimized);
|
||||
}
|
||||
public executeExternalBeforeCalling(): void {
|
||||
this.beforeCalling && this.beforeCalling();
|
||||
}
|
||||
public executeExternalAfterCalling(): void {
|
||||
this.afterCalling && this.afterCalling();
|
||||
}
|
||||
// 处理用户异常退出的情况, 小程序 ”右滑“、"左上角退出"; web 页面关闭浏览器或关闭 tab 页面
|
||||
public async handleExceptionExit(event?: any) {
|
||||
try {
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
const callRole = TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE);
|
||||
this._tuiCallEngine?.reportLog?.({ name: 'TUICallkit.handleExceptionExit', data: { callStatus, callRole } });
|
||||
|
||||
if (callStatus === CallStatus.IDLE) return;
|
||||
// 在呼叫状态下,被叫调用 reject,主叫调用 hangup
|
||||
if (callStatus === CallStatus.CALLING) {
|
||||
if (callRole === CallRole.CALLER) {
|
||||
await this?.hangup();
|
||||
} else {
|
||||
await this?.reject();
|
||||
}
|
||||
}
|
||||
if (callStatus === CallStatus.CONNECTED) {
|
||||
await this?.hangup();
|
||||
}
|
||||
this?._resetCallStore();
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX} handleExceptionExit failed, error: ${error}.`);
|
||||
}
|
||||
if (event) {
|
||||
event.returnValue = '';
|
||||
}
|
||||
}
|
||||
// 处理 pusher 内部错误,没有 live-pusher 能力时做出弹窗提示。
|
||||
public handlePusherError(event) {
|
||||
if(event?.detail?.errMsg === 'fail:access denied') {
|
||||
handleNoPusherCapabilityError();
|
||||
}
|
||||
}
|
||||
// ========================【TUICallKit 组件属性设置方法】========================
|
||||
public setVideoDisplayMode(displayMode: VideoDisplayMode) {
|
||||
TUIStore.update(StoreName.CALL, NAME.DISPLAY_MODE, displayMode);
|
||||
}
|
||||
public async setVideoResolution(resolution: VideoResolution) {
|
||||
try {
|
||||
if (!resolution) return;
|
||||
TUIStore.update(StoreName.CALL, NAME.VIDEO_RESOLUTION, resolution);
|
||||
await this._tuiCallEngine?.setVideoQuality(resolution);
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}setVideoResolution failed, error: ${error}.`);
|
||||
}
|
||||
}
|
||||
// 通话时长更新
|
||||
public startTimer(): void {
|
||||
if (this._timerId === -1) {
|
||||
this._startTimeStamp = performanceNow();
|
||||
this._timerId = timer.run(NAME.TIMEOUT, this._updateCallDuration.bind(this), { delay: 1000 });
|
||||
}
|
||||
}
|
||||
// =========================【private methods for service use】=========================
|
||||
// 处理 “呼叫” 抛出的异常
|
||||
private _handleCallError(error: any, methodName?: string) {
|
||||
this._permissionCheckTimer && clearInterval(this._permissionCheckTimer);
|
||||
|
||||
if (handleRepeatedCallError(error)) return;
|
||||
handlePackageError(error); // 无套餐提示, 小程序 engine 不抛出 onError
|
||||
noDevicePermissionToast(error, CallMediaType.AUDIO, this._tuiCallEngine);
|
||||
console.error(`${NAME.PREFIX}${methodName} failed, error: ${error}.`);
|
||||
this._resetCallStore();
|
||||
throw error;
|
||||
}
|
||||
private async _updateCallStoreBeforeCall(type: number, remoteUserInfoList: IUserInfo[], groupID?: string): Promise<void> {
|
||||
let callTips = CallTips.CALLER_CALLING_MSG;
|
||||
if (groupID || TUIStore.getData(StoreName.CALL, NAME.IS_MINIMIZED) || remoteUserInfoList.length > 1) {
|
||||
callTips = CallTips.CALLER_GROUP_CALLING_MSG;
|
||||
}
|
||||
|
||||
console.warn(`呼叫前更新 call status: ${(!!groupID || remoteUserInfoList.length > 1)}, ${groupID}, ${JSON.stringify(remoteUserInfoList)}`)
|
||||
|
||||
let updateStoreParams: any = {
|
||||
[NAME.CALL_MEDIA_TYPE]: type,
|
||||
[NAME.CALL_ROLE]: CallRole.CALLER,
|
||||
[NAME.REMOTE_USER_INFO_LIST]: remoteUserInfoList,
|
||||
[NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST]: remoteUserInfoList,
|
||||
[NAME.IS_GROUP]: (!!groupID || remoteUserInfoList.length > 1),
|
||||
[NAME.CALL_TIPS]: callTips,
|
||||
[NAME.GROUP_ID]: groupID
|
||||
};
|
||||
const pusher = { enableCamera: type === CallMediaType.VIDEO, enableMic: true }; // mini 默认打开麦克风
|
||||
updateStoreParams = { ...updateStoreParams, [NAME.PUSHER]: pusher };
|
||||
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
|
||||
const callStatus = await beforeCall(type, this); // 如果没有权限, 此时为 false. 因此需要在 call 后设置为 calling. 和 web 存在差异
|
||||
console.log(`${NAME.PREFIX}mini beforeCall return callStatus: ${callStatus}.`);
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, callStatus);
|
||||
const remoteUserInfoLists = await getRemoteUserProfile(remoteUserInfoList.map(obj => obj.userId), this.getTim());
|
||||
|
||||
if (remoteUserInfoLists.length > 0) {
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoLists);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoLists);
|
||||
}
|
||||
|
||||
const deviceMap = {
|
||||
microphone: true,
|
||||
camera: type === CallMediaType.VIDEO,
|
||||
};
|
||||
let hasDevicePermission = await this._tuiCallEngine.deviceCheck(deviceMap);
|
||||
if (!hasDevicePermission) {
|
||||
this._permissionCheckTimer && clearInterval(this._permissionCheckTimer);
|
||||
this._permissionCheckTimer = setInterval(async () => {
|
||||
hasDevicePermission = await this._tuiCallEngine.deviceCheck(deviceMap);
|
||||
|
||||
if (hasDevicePermission && this._permissionCheckTimer) {
|
||||
clearInterval(this._permissionCheckTimer);
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CALLING);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
private async _updateCallStoreAfterCall(userIdList: string[], response: any) {
|
||||
if (response) {
|
||||
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
|
||||
updateRoomIdAndRoomIdType(response?.roomID, response?.strRoomID);
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
this._chatCombine?.callTUIService({ message: response?.data?.message });
|
||||
response.pusher && TUIStore.update(StoreName.CALL, NAME.PUSHER, response.pusher);
|
||||
this.setSoundMode(callMediaType === CallMediaType.AUDIO ? AudioPlayBackDevice.EAR : AudioPlayBackDevice.SPEAKER);
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CALLING); // 小程序未授权时, 此时状态为 idle; web 直接设置为 calling
|
||||
const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
|
||||
(callMediaType === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
|
||||
const localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, { ...localUserInfo, isEnter: true });
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, { ...localUserInfo, isEnter: true });
|
||||
setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO); // web && mini, default open audio
|
||||
} else {
|
||||
this._permissionCheckTimer && clearInterval(this._permissionCheckTimer);
|
||||
this._permissionCheckTimer = null;
|
||||
this._resetCallStore();
|
||||
}
|
||||
}
|
||||
private _getFeatureButtonDefaultState(buttonName: FeatureButton) {
|
||||
const { button: buttonConfig } = TUIStore.getData(StoreName.CALL, NAME.CUSTOM_UI_CONFIG);
|
||||
return buttonConfig?.[buttonName]?.state;
|
||||
}
|
||||
private _updateCallDuration(): void {
|
||||
const callDurationNum = Math.round((performanceNow() - this._startTimeStamp) / 1000); // miniProgram stop timer when background
|
||||
const callDurationStr = formatTime(callDurationNum);
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_DURATION, callDurationStr);
|
||||
}
|
||||
private _stopTimer(): void {
|
||||
if (this._timerId !== -1) {
|
||||
timer.clearTask(this._timerId);
|
||||
this._timerId = -1;
|
||||
}
|
||||
}
|
||||
// clear all use avoidRepeatCall decorator state
|
||||
private _cleanupAvoidRepeatCallState(methodName?: string) {
|
||||
this._tuiCallEngine?.reportLog?.({ name: 'TUICallkit._cleanupAvoidRepeatCallState', data: { } });
|
||||
let methodsToClean = [];
|
||||
if (methodName) {
|
||||
methodsToClean = [(this as any)[methodName]];
|
||||
} else {
|
||||
methodsToClean = [
|
||||
(this as any).calls,
|
||||
(this as any).accept,
|
||||
(this as any).hangup,
|
||||
(this as any).reject,
|
||||
];
|
||||
}
|
||||
|
||||
methodsToClean?.forEach(method => {
|
||||
method?.clearCallState?.(this);
|
||||
});
|
||||
}
|
||||
private _resetCallStore() {
|
||||
this._cleanupAvoidRepeatCallState();
|
||||
this._bellContext?.stop();
|
||||
|
||||
const oldStatusStr = generateStatusChangeText();
|
||||
this._stopTimer();
|
||||
// localUserInfo, language 在通话结束后不需要清除
|
||||
// callStatus 清除需要通知; isMinimized 也需要通知(basic-vue3 中切小窗关闭后, 再呼叫还是小窗, 因此需要通知到组件侧)
|
||||
// isGroup 也不清除(engine 先抛 cancel 事件, 再抛 reject 事件)
|
||||
// displayMode、videoResolution 也不能清除, 组件不卸载, 这些属性也需保留, 否则采用默认值.
|
||||
// enableFloatWindow 不清除:开启/关闭悬浮窗功能。
|
||||
let notResetOrNotifyKeys = Object.keys(CALL_DATA_KEY).filter((key) => {
|
||||
switch (CALL_DATA_KEY[key]) {
|
||||
case NAME.CALL_STATUS:
|
||||
case NAME.LANGUAGE:
|
||||
case NAME.IS_GROUP:
|
||||
case NAME.DISPLAY_MODE:
|
||||
case NAME.VIDEO_RESOLUTION:
|
||||
case NAME.ENABLE_FLOAT_WINDOW:
|
||||
case NAME.LOCAL_USER_INFO:
|
||||
case NAME.IS_SHOW_ENABLE_VIRTUAL_BACKGROUND:
|
||||
case NAME.IS_FORCE_USE_V2_API:
|
||||
case NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN: {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
notResetOrNotifyKeys = notResetOrNotifyKeys.map(key => CALL_DATA_KEY[key]);
|
||||
TUIStore.reset(StoreName.CALL, notResetOrNotifyKeys);
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
callStatus !== CallStatus.IDLE && TUIStore.reset(StoreName.CALL, [NAME.CALL_STATUS], true); // callStatus reset need notify
|
||||
TUIStore.reset(StoreName.CALL, [NAME.IS_MINIMIZED], true); // isMinimized reset need notify
|
||||
TUIStore.reset(StoreName.CALL, [NAME.IS_EAR_PHONE], true); // isEarPhone reset need notify
|
||||
TUIStore.reset(StoreName.CALL, [NAME.PUSHER_ID], true); // pusher unload reset need notify
|
||||
TUIStore.reset(StoreName.CALL, [NAME.ENABLE_VIRTUAL_BACKGROUND], true); // ENABLE_VIRTUAL_BACKGROUND reset need notify
|
||||
TUIStore.reset(StoreName.CALL, [NAME.IS_MUTE_SPEAKER], true); // IS_MUTE_SPEAKER reset need notify
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, {
|
||||
...TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO),
|
||||
isVideoAvailable: false,
|
||||
isAudioAvailable: false,
|
||||
});
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, {
|
||||
...TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN),
|
||||
isVideoAvailable: false,
|
||||
isAudioAvailable: false,
|
||||
});
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, []);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, []);
|
||||
TUIStore.update(StoreName.CALL, NAME.CAMERA_POSITION, CameraPosition.FRONT);
|
||||
|
||||
const newStatusStr = generateStatusChangeText();
|
||||
if (oldStatusStr !== newStatusStr) {
|
||||
this.statusChanged && this.statusChanged({ oldStatus: oldStatusStr, newStatus: newStatusStr });
|
||||
}
|
||||
}
|
||||
// =========================【Calling the Chat SDK APi】=========================
|
||||
// 获取群成员
|
||||
public async getGroupMemberList(count: number, offset: number) {
|
||||
const groupID = TUIStore.getData(StoreName.CALL, NAME.GROUP_ID);
|
||||
let groupMemberList = await getGroupMemberList(groupID, this.getTim(), count, offset);
|
||||
return groupMemberList;
|
||||
}
|
||||
// 获取群信息
|
||||
public async getGroupProfile() {
|
||||
const groupID: string = TUIStore.getData(StoreName.CALL, NAME.GROUP_ID);
|
||||
return await getGroupProfile(groupID, this.getTim());
|
||||
}
|
||||
// =========================【监听 TUIStore 中的状态及处理】=========================
|
||||
private _handleCallStatusChange = async (value: CallStatus) => {
|
||||
try {
|
||||
const bellParams: IBellParams = {
|
||||
callRole: TUIStore.getData(StoreName.CALL, NAME.CALL_ROLE),
|
||||
callStatus: TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS),
|
||||
};
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallkit._handleCallStatusChange.start',
|
||||
data: { bellParams }
|
||||
});
|
||||
this._bellContext.setBellProperties(bellParams);
|
||||
if (value === CallStatus.CALLING) {
|
||||
this?._bellContext?.play();
|
||||
} else {
|
||||
this._tuiCallEngine?.reportLog?.({
|
||||
name: 'TUICallkit._bellContext.stop',
|
||||
});
|
||||
this?._bellContext?.stop();
|
||||
// 状态变更通知
|
||||
if (value === CallStatus.CONNECTED) {
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
const remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
const oldStatus = isGroup ? StatusChange.DIALING_GROUP : StatusChange.DIALING_C2C;
|
||||
TUIStore.update(StoreName.CALL, NAME.CALL_TIPS, '');
|
||||
this.statusChanged && this.statusChanged({ oldStatus, newStatus: generateStatusChangeText() });
|
||||
if (!isGroup && callMediaType === CallMediaType.VIDEO) {
|
||||
this.switchScreen(remoteUserInfoList[0].domId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`${NAME.PREFIX}handleCallStatusChange, ${error}.`);
|
||||
}
|
||||
};
|
||||
private _watchTUIStore() {
|
||||
TUIStore?.watch(StoreName.CALL, {
|
||||
[NAME.CALL_STATUS]: this._handleCallStatusChange,
|
||||
});
|
||||
}
|
||||
private _unwatchTUIStore() {
|
||||
TUIStore?.unwatch(StoreName.CALL, {
|
||||
[NAME.CALL_STATUS]: this._handleCallStatusChange,
|
||||
});
|
||||
}
|
||||
// =========================【融合 chat 】=========================
|
||||
public bindTUICore(TUICore: any) {
|
||||
this._TUICore = TUICore;
|
||||
}
|
||||
// =========================【set、get methods】=========================
|
||||
public getTim() {
|
||||
if (this._tim) return this._tim;
|
||||
if (!this._tuiCallEngine) {
|
||||
console.warn(`${NAME.PREFIX}getTim warning: _tuiCallEngine Instance is not available.`);
|
||||
return null;
|
||||
}
|
||||
return this._tuiCallEngine?.tim || this._tuiCallEngine?.getTim(); // mini support getTim interface
|
||||
}
|
||||
public setIsFromChat(isFromChat: boolean) {
|
||||
this._isFromChat = isFromChat;
|
||||
}
|
||||
public setCurrentGroupId(groupId: string) {
|
||||
this._currentGroupId = groupId;
|
||||
}
|
||||
public getCurrentGroupId() {
|
||||
return this._currentGroupId;
|
||||
}
|
||||
public setDefaultOfflinePushInfo(offlinePushInfo) {
|
||||
this._offlinePushInfo = offlinePushInfo;
|
||||
}
|
||||
public getDefaultOfflinePushInfo() {
|
||||
const localUserInfo: IUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
if (this._offlinePushInfo) {
|
||||
return this._offlinePushInfo;
|
||||
}
|
||||
|
||||
return {
|
||||
title: localUserInfo?.displayUserInfo || '',
|
||||
description: t('you have a new call'),
|
||||
};
|
||||
}
|
||||
public async getCallMessage(message) {
|
||||
return await this._chatCombine.getCallKitMessage(message, this.getTim());
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
import { CallMediaType, CallStatus } from '../const/index';
|
||||
|
||||
export function initialUI() {
|
||||
// 收起键盘
|
||||
// @ts-ignore
|
||||
wx.hideKeyboard && wx.hideKeyboard({
|
||||
complete: () => {},
|
||||
});
|
||||
};
|
||||
// 检测运行时环境, 当是微信开发者工具时, 提示用户需要手机调试
|
||||
export function checkRunPlatform() {
|
||||
// @ts-ignore
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
if (systemInfo.platform === 'devtools') {
|
||||
// 当前运行在微信开发者工具里
|
||||
// @ts-ignore
|
||||
wx.showModal({
|
||||
icon: 'none',
|
||||
title: '运行环境提醒',
|
||||
content: '微信开发者工具不支持原生推拉流组件(即 <live-pusher> 和 <live-player> 标签),请使用真机调试或者扫码预览。',
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
export function initAndCheckRunEnv() {
|
||||
initialUI(); // miniProgram 收起键盘, 隐藏 tabBar
|
||||
checkRunPlatform(); // miniProgram 检测运行时环境
|
||||
}
|
||||
export async function beforeCall(type: CallMediaType, that: any) {
|
||||
try {
|
||||
initAndCheckRunEnv();
|
||||
// 检查设备权限
|
||||
const deviceMap = {
|
||||
microphone: true,
|
||||
camera: type === CallMediaType.VIDEO,
|
||||
};
|
||||
const hasDevicePermission = await that._tuiCallEngine.deviceCheck(deviceMap); // miniProgram 检查设备权限
|
||||
return hasDevicePermission ? CallStatus.CALLING : CallStatus.IDLE;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return CallStatus.IDLE;
|
||||
}
|
||||
}
|
||||
// 套餐问题提示, 小程序最低需要群组通话版, 1v1 通话版本使用 TRTC 就会报错
|
||||
export function handlePackageError(error) {
|
||||
if (error?.code === -1002) {
|
||||
// @ts-ignore
|
||||
wx.showModal({
|
||||
icon: 'none',
|
||||
title: 'error',
|
||||
content: error?.message || '',
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function handleNoPusherCapabilityError(){
|
||||
// @ts-ignore
|
||||
wx.showModal({
|
||||
icon: 'none',
|
||||
title: '权限提示',
|
||||
content: '当前小程序 appid 不具备 <live-pusher> 和 <live-player> 的使用权限,您将无法正常使用实时通话能力,请使用企业小程序账号申请权限后再进行开发体验',
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
Generated
Vendored
+277
@@ -0,0 +1,277 @@
|
||||
import { NAME, StoreName, CallStatus, StatusChange, CallMediaType, ROOM_ID_TYPE } from '../const/index';
|
||||
import { handleNoDevicePermissionError } from '../utils/common-utils';
|
||||
import { IUserInfo } from '../interface/ICallService';
|
||||
import { ITUIStore } from '../interface/ITUIStore';
|
||||
import { CallTips, t } from '../locales/index';
|
||||
import TuiStore from '../TUIStore/tuiStore';
|
||||
// @ts-ignore
|
||||
const TUIStore: ITUIStore = TuiStore.getInstance();
|
||||
|
||||
// 设置默认的 UserInfo 信息
|
||||
export function setDefaultUserInfo(userId: string, domId?: string): IUserInfo {
|
||||
const userInfo: IUserInfo = {
|
||||
userId,
|
||||
nick: '',
|
||||
avatar: '',
|
||||
remark: '',
|
||||
displayUserInfo: '',
|
||||
isAudioAvailable: false,
|
||||
isVideoAvailable: false,
|
||||
isEnter: false,
|
||||
domId: domId || userId,
|
||||
};
|
||||
return domId ? userInfo : { ...userInfo, isEnter: false }; // localUserInfo 没有 isEnter, remoteUserInfoList 有 isEnter
|
||||
}
|
||||
// 获取个人用户信息
|
||||
export async function getMyProfile(myselfUserId: string, tim: any): Promise<IUserInfo> {
|
||||
let localUserInfo: IUserInfo = setDefaultUserInfo(myselfUserId, NAME.LOCAL_VIDEO);
|
||||
try {
|
||||
if (!tim) return localUserInfo;
|
||||
const res = await tim.getMyProfile();
|
||||
const currentLocalUserInfo = TUIStore?.getData(StoreName.CALL, NAME.LOCAL_USER_INFO); // localUserInfo may have been updated
|
||||
if (res?.code === 0) {
|
||||
localUserInfo = {
|
||||
...localUserInfo,
|
||||
...currentLocalUserInfo,
|
||||
userId: res?.data?.userID,
|
||||
nick: res?.data?.nick,
|
||||
avatar: res?.data?.avatar,
|
||||
displayUserInfo: res?.data?.nick || res?.data?.userID,
|
||||
};
|
||||
}
|
||||
return localUserInfo;
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}getMyProfile failed, error: ${error}.`);
|
||||
return localUserInfo;
|
||||
}
|
||||
}
|
||||
// 获取远端用户列表信息
|
||||
export async function getRemoteUserProfile(userIdList: Array<string>, tim: any): Promise<any> {
|
||||
let remoteUserInfoList: IUserInfo[] = userIdList.map((userId: string) => setDefaultUserInfo(userId));
|
||||
try {
|
||||
if (!tim) return remoteUserInfoList;
|
||||
|
||||
if (tim?.getFriendProfile) {
|
||||
const res = await tim.getFriendProfile({ userIDList: userIdList });
|
||||
if (res.code === 0) {
|
||||
const { friendList = [], failureUserIDList = [] } = res.data;
|
||||
let unFriendList: IUserInfo[] = failureUserIDList.map((obj: any) => obj.userID);
|
||||
if (failureUserIDList.length > 0) {
|
||||
const res = await tim.getUserProfile({ userIDList: failureUserIDList.map((obj: any) => obj.userID) });
|
||||
if (res?.code === 0) {
|
||||
unFriendList = res?.data || [];
|
||||
}
|
||||
}
|
||||
const currentRemoteUserInfoList = TUIStore?.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST); // remoteUserInfoList may have been updated
|
||||
const tempFriendIdList: string[] = friendList.map((obj: any) => obj.userID);
|
||||
const tempUnFriendIdList: string[] = unFriendList.map((obj: any) => obj.userID);
|
||||
remoteUserInfoList = userIdList.map((userId: string) => {
|
||||
const defaultUserInfo: IUserInfo = setDefaultUserInfo(userId);
|
||||
const friendListIndex: number = tempFriendIdList.indexOf(userId);
|
||||
const unFriendListIndex: number = tempUnFriendIdList.indexOf(userId);
|
||||
let remark = '';
|
||||
let nick = '';
|
||||
let displayUserInfo = '' ;
|
||||
let avatar = '';
|
||||
if (friendListIndex !== -1) {
|
||||
remark = friendList[friendListIndex]?.remark || '';
|
||||
nick = friendList[friendListIndex]?.profile?.nick || '';
|
||||
displayUserInfo = remark || nick || defaultUserInfo.userId || '';
|
||||
avatar = friendList[friendListIndex]?.profile?.avatar || '';
|
||||
}
|
||||
if (unFriendListIndex !== -1) {
|
||||
nick = unFriendList[unFriendListIndex]?.nick || '';
|
||||
displayUserInfo = nick || defaultUserInfo.userId || '';
|
||||
avatar = unFriendList[unFriendListIndex]?.avatar || '';
|
||||
}
|
||||
const userInfo = currentRemoteUserInfoList.find(subObj => subObj.userId === userId) || {};
|
||||
return { ...defaultUserInfo, ...userInfo, remark, nick, displayUserInfo, avatar };
|
||||
});
|
||||
}
|
||||
return remoteUserInfoList;
|
||||
} else {
|
||||
const res = await tim.getUserProfile({ userIDList: userIdList });
|
||||
const currentRemoteUserInfoList = TUIStore?.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST); // remoteUserInfoList may have been updated
|
||||
|
||||
remoteUserInfoList = userIdList.map((userId: string) => {
|
||||
const defaultUserInfo: IUserInfo = setDefaultUserInfo(userId);
|
||||
const userInfo = currentRemoteUserInfoList.find(subObj => subObj.userId === userId) || {};
|
||||
const userData = (res.data || []).find(obj => obj.userID === userId) || {};
|
||||
|
||||
return {
|
||||
...defaultUserInfo,
|
||||
...userInfo,
|
||||
nick: userData?.nick || '',
|
||||
displayUserInfo: userData?.nick || userId,
|
||||
avatar: userData?.avatar || '',
|
||||
};
|
||||
});
|
||||
return remoteUserInfoList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${NAME.PREFIX}getRemoteUserProfile failed, error: ${error}.`);
|
||||
return remoteUserInfoList;
|
||||
}
|
||||
}
|
||||
// 生成弹框提示文案
|
||||
export function generateText(key: string, prefix?: string, suffix?: string): string {
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
let callTips = `${t(key)}`;
|
||||
if (isGroup) {
|
||||
callTips = prefix ? `${prefix} ${callTips}` : callTips;
|
||||
callTips = suffix ? `${callTips} ${suffix}` : callTips;
|
||||
}
|
||||
return callTips;
|
||||
}
|
||||
// 生成 statusChange 抛出的字符串
|
||||
export function generateStatusChangeText(): string {
|
||||
const callStatus = TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS);
|
||||
if (callStatus === CallStatus.IDLE) {
|
||||
return StatusChange.IDLE;
|
||||
}
|
||||
const isGroup = TUIStore.getData(StoreName.CALL, NAME.IS_GROUP);
|
||||
if (callStatus === CallStatus.CALLING) {
|
||||
return isGroup ? StatusChange.DIALING_GROUP : StatusChange.DIALING_C2C;
|
||||
}
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
if (isGroup) {
|
||||
return callMediaType === CallMediaType.AUDIO ? StatusChange.CALLING_GROUP_AUDIO : StatusChange.CALLING_GROUP_VIDEO;
|
||||
}
|
||||
return callMediaType === CallMediaType.AUDIO ? StatusChange.CALLING_C2C_AUDIO : StatusChange.CALLING_C2C_VIDEO;
|
||||
}
|
||||
// 获取群组[offset, count + offset]区间成员
|
||||
export async function getGroupMemberList(groupID: string, tim: any, count, offset) {
|
||||
let groupMemberList = [];
|
||||
try {
|
||||
const res = await tim.getGroupMemberList({ groupID, count, offset });
|
||||
if (res.code === 0) {
|
||||
return res.data.memberList || groupMemberList;
|
||||
}
|
||||
} catch(error) {
|
||||
console.error(`${NAME.PREFIX}getGroupMember failed, error: ${error}.`);
|
||||
return groupMemberList;
|
||||
}
|
||||
}
|
||||
// 获取 IM 群信息
|
||||
export async function getGroupProfile(groupID: string, tim: any): Promise<any> {
|
||||
let groupProfile = {};
|
||||
try {
|
||||
const res = await tim.getGroupProfile({ groupID });
|
||||
return res.data.group || groupProfile;
|
||||
} catch(error) {
|
||||
console.warn(`${NAME.PREFIX}getGroupProfile failed, error: ${error}.`);
|
||||
return groupProfile;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* update roomId and roomIdType
|
||||
* @param {number} roomId number roomId
|
||||
* @param {string} strRoomId string roomId
|
||||
*/
|
||||
export function updateRoomIdAndRoomIdType(roomId, strRoomId) {
|
||||
if (roomId === 0 && strRoomId) { // use strRoomID
|
||||
TUIStore.update(StoreName.CALL, NAME.ROOM_ID, strRoomId);
|
||||
TUIStore.update(StoreName.CALL, NAME.ROOM_ID_TYPE, ROOM_ID_TYPE.STRING_ROOM_ID);
|
||||
} else {
|
||||
TUIStore.update(StoreName.CALL, NAME.ROOM_ID, roomId);
|
||||
TUIStore.update(StoreName.CALL, NAME.ROOM_ID_TYPE, ROOM_ID_TYPE.NUMBER_ROOM_ID);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* web and miniProgram call engine throw event data structure are different
|
||||
* @param {any} event call engine throw out data
|
||||
* @returns {any} data
|
||||
*/
|
||||
export function analyzeEventData(event: any): any {
|
||||
return event || {};
|
||||
}
|
||||
/**
|
||||
* delete user from remoteUserInfoList
|
||||
* @param {string[]} userIdList to be deleted userIdList
|
||||
* @param {ITUIStore} TUIStore TUIStore instance
|
||||
*/
|
||||
export function deleteRemoteUser(userIdList: string[]): void {
|
||||
if (userIdList.length === 0) return;
|
||||
let remoteUserInfoList = TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST);
|
||||
userIdList.forEach((userId) => {
|
||||
remoteUserInfoList = remoteUserInfoList.filter((obj: IUserInfo) => obj.userId !== userId);
|
||||
});
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST, remoteUserInfoList);
|
||||
TUIStore.update(StoreName.CALL, NAME.REMOTE_USER_INFO_EXCLUDE_VOLUMN_LIST, remoteUserInfoList);
|
||||
}
|
||||
export function updateDeviceList(tuiCallEngine) {
|
||||
tuiCallEngine?.getDeviceList('speaker').then((result)=>{
|
||||
const deviceList = TUIStore.getData(StoreName.CALL, NAME.DEVICE_LIST);
|
||||
const currentSpeaker = result?.[0] || {};
|
||||
TUIStore.update(
|
||||
StoreName.CALL,
|
||||
NAME.DEVICE_LIST,
|
||||
{ ...deviceList, speakerList: result, currentSpeaker },
|
||||
);
|
||||
}).catch(error =>{
|
||||
console.error(`${NAME.PREFIX}updateSpeakerList failed, error: ${JSON.stringify(error)}.`);
|
||||
});
|
||||
const callMediaType = TUIStore.getData(StoreName.CALL, NAME.CALL_MEDIA_TYPE);
|
||||
if (callMediaType === CallMediaType.VIDEO) {
|
||||
tuiCallEngine?.getDeviceList('camera').then((result) => {
|
||||
const deviceList = TUIStore.getData(StoreName.CALL, NAME.DEVICE_LIST);
|
||||
const currentCamera = result?.[0] || {};
|
||||
TUIStore.update(
|
||||
StoreName.CALL,
|
||||
NAME.DEVICE_LIST,
|
||||
{ ...deviceList, cameraList: result, currentCamera },
|
||||
);
|
||||
}).catch(error => {
|
||||
console.error(`${NAME.PREFIX}updateCameraList failed, error: ${error}.`);
|
||||
});
|
||||
}
|
||||
tuiCallEngine?.getDeviceList('microphones').then((result) => {
|
||||
const deviceList = TUIStore.getData(StoreName.CALL, NAME.DEVICE_LIST);
|
||||
const currentMicrophone = result?.[0] || {};
|
||||
TUIStore.update(
|
||||
StoreName.CALL,
|
||||
NAME.DEVICE_LIST,
|
||||
{ ...deviceList, microphoneList: result, currentMicrophone },
|
||||
);
|
||||
}).catch(error => {
|
||||
console.error(`${NAME.PREFIX}updateMicrophoneList failed, error: ${error}.`);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* update the no device permission toast
|
||||
* @param {any} error error
|
||||
* @param {CallMediaType} type call midia type
|
||||
* @param {any} tuiCallEngine TUICallEngine instance
|
||||
*/
|
||||
export function noDevicePermissionToast(error, type: CallMediaType, tuiCallEngine: any) {
|
||||
let toastInfoKey = '';
|
||||
if (handleNoDevicePermissionError(error)) {
|
||||
if (type === CallMediaType.AUDIO) {
|
||||
toastInfoKey = CallTips.NO_MICROPHONE_DEVICE_PERMISSION;
|
||||
}
|
||||
if (type === CallMediaType.VIDEO) {
|
||||
toastInfoKey = CallTips.NO_CAMERA_DEVICE_PERMISSION;
|
||||
}
|
||||
|
||||
|
||||
toastInfoKey && TUIStore.update(StoreName.CALL, NAME.TOAST_INFO, { content: toastInfoKey, type: NAME.ERROR });
|
||||
console.error(`${NAME.PREFIX}call failed, error: ${error.message}.`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* set localUserInfo audio/video available
|
||||
* @param {boolean} isAvailable is available
|
||||
* @param {string} type callMediaType 'audio' | 'video'
|
||||
* @param {ITUIStore} TUIStore TUIStore instance
|
||||
*/
|
||||
export function setLocalUserInfoAudioVideoAvailable(isAvailable: boolean, type: string) {
|
||||
let localUserInfo = TUIStore.getData(StoreName.CALL, NAME.LOCAL_USER_INFO);
|
||||
if (type === NAME.AUDIO) {
|
||||
localUserInfo = { ...localUserInfo, isAudioAvailable: isAvailable };
|
||||
}
|
||||
if (type === NAME.VIDEO) {
|
||||
localUserInfo = { ...localUserInfo, isVideoAvailable: isAvailable };
|
||||
}
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO, localUserInfo);
|
||||
TUIStore.update(StoreName.CALL, NAME.LOCAL_USER_INFO_EXCLUDE_VOLUMN, localUserInfo);
|
||||
}
|
||||
Reference in New Issue
Block a user