@tencentcloud/call-uikit-vue2
Version:
An Open-source Voice & Video Calling UI Component Based on Tencent Cloud Service.
842 lines (829 loc) • 38 kB
text/typescript
import {
ITUICallService, ICallParams, IGroupCallParams, IUserInfo, ICallbackParam, ISelfInfoParams, IBellParams,
IInviteUserParams, IJoinInGroupCallParams, 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 } from '@tencentcloud/call-engine-js';
import { checkLocalMP3FileExists } from '../utils/index';
import { CallTips, t } from '../locales/index';
import { BellContext } from './bellContext';
import { VALIDATE_PARAMS, avoidRepeatedCall, paramValidate, statusValidate } from '../utils/validate/index';
import { handleRepeatedCallError, formatTime, performanceNow } 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.0.4';
const frameWork = 'vue2.7';
export { TUIGlobal, TUIStore, uiDesign };
export default class TUICallService implements ITUICallService {
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;
constructor() {
console.log(`${NAME.PREFIX}version: ${version}`);
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;
}
public async init(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}`);
this._tuiCallEngine = TUICallEngine.createInstance({
tim,
SDKAppID: sdkAppID || SDKAppID, // 兼容传入 SDKAppID 的问题
frameWork,
// @ts-ignore
language: 5,
callkitVersion: version,
isFromChat: isFromChat || false,
component,
});
uiDesign.setEngineInstance(this._tuiCallEngine);
this._addListenTuiCallEngineEvent();
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 {
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;
}
}
// ===============================【通话操作】===============================
public async call(callParams: ICallParams) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return; // avoid double click when application stuck
try {
const { type, userID, offlinePushInfo } = callParams;
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
await this._updateCallStoreBeforeCall(type, [{ userId: userID }]);
this.executeExternalBeforeCalling(); // 执行外部传入的 beforeCall 方法
callParams.offlinePushInfo = { ...this.getDefaultOfflinePushInfo(), ...offlinePushInfo };
const response = await this._tuiCallEngine.call(callParams);
await this._updateCallStoreAfterCall([userID], response);
} catch (error: any) {
this._handleCallError(error, 'call');
}
};
public async groupCall(groupCallParams: IGroupCallParams) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return; // avoid double click when application stuck
try {
const { userIDList, type, groupID, offlinePushInfo } = groupCallParams;
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return;
const remoteUserInfoList = userIDList.map(userId => ({ userId }));
await this._updateCallStoreBeforeCall(type, remoteUserInfoList, groupID);
this.executeExternalBeforeCalling();
groupCallParams.offlinePushInfo = { ...this.getDefaultOfflinePushInfo(), ...offlinePushInfo };
const response = await this._tuiCallEngine.groupCall(groupCallParams);
await this._updateCallStoreAfterCall(userIDList, response);
} catch (error: any) {
this._handleCallError(error, 'groupCall');
}
}
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}.`);
}
}
public async joinInGroupCall(params: IJoinInGroupCallParams) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CONNECTED) return; // avoid double click when application stuck
try {
const updateStoreParams = {
[NAME.CALL_ROLE]: CallRole.CALLEE,
[NAME.IS_GROUP]: true,
[NAME.CALL_STATUS]: CallStatus.CONNECTED,
[NAME.CALL_MEDIA_TYPE]: params.type,
[NAME.GROUP_ID]: params.groupID,
[NAME.ROOM_ID]: params.roomID,
};
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
const response = await this._tuiCallEngine.joinInGroupCall(params);
const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
(params.type === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
this.startTimer();
updateDeviceList(this._tuiCallEngine);;
await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
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);
} catch (error) {
this._handleCallError(error, 'joinInGroupCall');
}
}
public async calls(callsParams: ICallsParams) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) !== CallStatus.IDLE) return; // avoid double click when application stuck
try {
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');
}
}
public async join(params) {
if (TUIStore.getData(StoreName.CALL, NAME.CALL_STATUS) === CallStatus.CONNECTED) return; // avoid double click when application stuck
try {
const response = await this._tuiCallEngine.join(params);
const isCameraDefaultStateClose = this._getFeatureButtonDefaultState(FeatureButton.Camera) === ButtonState.Close;
(params.type === CallMediaType.VIDEO) && !isCameraDefaultStateClose && await this.openCamera(NAME.LOCAL_VIDEO);
TUIStore.update(StoreName.CALL, NAME.IS_CLICKABLE, true);
this.startTimer();
const updateStoreParams = {
[NAME.CALL_ROLE]: CallRole.CALLEE,
[NAME.IS_GROUP]: true,
[NAME.CALL_STATUS]: CallStatus.CONNECTED,
[NAME.CALL_MEDIA_TYPE]: params.type,
[NAME.GROUP_ID]: params.groupID,
[NAME.ROOM_ID]: params.roomID,
};
TUIStore.updateStore(updateStoreParams, StoreName.CALL);
updateDeviceList(this._tuiCallEngine);;
await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
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);
} 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) {
if (language && Object.values(LanguageType).includes(language as LanguageType)) {
TUIStore.update(StoreName.CALL, NAME.LANGUAGE, language);
TUIStore.update(StoreName.CALL, NAME.TRANSLATE, t.bind(null));
}
}
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 enableAIVoice(enable: boolean) {
try {
await this._tuiCallEngine.enableAIVoice(enable);
console.log(`${NAME.PREFIX}enableAIVoice: ${enable}.`);
} catch (error: any) {
console.error(`${NAME.PREFIX}enableAIVoice failed, error: ${error}.`);
throw error;
}
}
// 修改默认铃声:只支持本地铃声文件,不支持在线铃声文件;修改铃声修改的是被叫的铃声
public async setCallingBell(filePath?: string) {
let isCheckFileExist: boolean = true;
isCheckFileExist = await checkLocalMP3FileExists(filePath);
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 } });
}
}
// =============================【内部按钮操作方法】=============================
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 {
TUIStore.update(StoreName.CALL, NAME.CALL_STATUS, CallStatus.CONNECTED);
updateDeviceList(this._tuiCallEngine);;
const response = await this._tuiCallEngine.accept();
if (response) {
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);
await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
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
}
} catch (error) {
this._tuiCallEngine?.reportLog?.({
name: 'TUICallKit.accept.fail',
level: 'error',
error,
});
if (handleRepeatedCallError(error)) return;
noDevicePermissionToast(error, CallMediaType.AUDIO, this._tuiCallEngine);
this._resetCallStore();
}
}
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();
}
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();
}
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}.`);
}
}
public async closeCamera() {
try {
await this._tuiCallEngine.closeCamera();
setLocalUserInfoAudioVideoAvailable(false, NAME.VIDEO);
} catch (error: any) {
console.error(`${NAME.PREFIX}closeCamera error: ${error}.`);
}
}
public async openMicrophone() {
try {
await this._tuiCallEngine.openMicrophone();
setLocalUserInfoAudioVideoAvailable(true, NAME.AUDIO);
} catch (error: any) {
console.error(`${NAME.PREFIX}openMicrophone failed, error: ${error}.`);
}
}
public async closeMicrophone() {
try {
await this._tuiCallEngine.closeMicrophone();
setLocalUserInfoAudioVideoAvailable(false, NAME.AUDIO);
} catch (error: any) {
console.error(`${NAME.PREFIX}closeMicrophone failed, error: ${error}.`);
}
}
public unMuteSpeaker() {
try {
const trtcCloudInstance = this._tuiCallEngine?.getTRTCCloudInstance?.();
if (trtcCloudInstance) {
trtcCloudInstance.muteAllRemoteAudio(false);
TUIStore.update(StoreName.CALL, NAME.IS_MUTE_SPEAKER, false);
}
} catch (error: any) {
console.error(`${NAME.PREFIX}unMuteSpeaker failed, error: ${error}.`);
}
}
public muteSpeaker() {
try {
const trtcCloudInstance = this._tuiCallEngine?.getTRTCCloudInstance?.();
if (trtcCloudInstance) {
trtcCloudInstance.muteAllRemoteAudio(true);
TUIStore.update(StoreName.CALL, NAME.IS_MUTE_SPEAKER, true);
}
} catch (error: any) {
console.error(`${NAME.PREFIX}muteSpeaker failed, error: ${error}.`);
}
}
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
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 });
} catch (error: any) {
console.error(`${NAME.PREFIX}switchCallMediaType failed, error: ${error}.`);
}
}
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}.`);
}
}
public async setBlurBackground(enable: boolean) {
try {
await this._tuiCallEngine.setBlurBackground(enable ? DEFAULT_BLUR_LEVEL : 0); // 0 indicate close blurBackground
TUIStore.update(StoreName.CALL, NAME.ENABLE_VIRTUAL_BACKGROUND, enable);
} catch (error) {
console.error(`${NAME.PREFIX}_setBlurBackground failed, error: ${error}.`);
}
}
public async switchDevice(params) {
try {
await this._tuiCallEngine.switchDevice(params);
} catch (error) {
console.error(`${NAME.PREFIX}_switchDevice failed, error: ${error}.`);
}
}
public async getDeviceList(deviceType: string) {
try {
const response = await this._tuiCallEngine.getDeviceList(deviceType);
return response;
} catch (error: any) {
this._handleCallError(error, 'call');
}
};
// ==========================【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();
}
// ========================【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;
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;
}
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
};
TUIStore.updateStore({ ...updateStoreParams, [NAME.CALL_STATUS]: CallStatus.CALLING }, StoreName.CALL);
this.statusChanged && this.statusChanged({
oldStatus: StatusChange.IDLE,
newStatus: (groupID || remoteUserInfoList.length > 1) ? StatusChange.DIALING_GROUP : StatusChange.DIALING_C2C,
});
updateDeviceList(this._tuiCallEngine);;
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);
}
}
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);
if (response.code === 0) {
this._chatCombine?.callTUIService({ message: response?.data?.message });
try {
await this._tuiCallEngine.setVideoQuality(TUIStore.getData(StoreName.CALL, NAME.VIDEO_RESOLUTION));
} catch (error) {
console.warn(`${NAME.PREFIX}setVideoQuality failed, error: ${error}.`);
}
} else {
this._resetCallStore();
return;
}
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;
}
}
private _resetCallStore() {
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.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._bellContext.setBellProperties(bellParams);
if (value === CallStatus.CALLING) {
await this?._bellContext?.play();
} else {
// 状态变更通知
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);
}
}
if (value === CallStatus.IDLE) {
if (this._isFromChat) {
const groupAttributes = this._currentGroupId ? await this._chatCombine?.getGroupAttributes(this._tim, this._currentGroupId) : {};
await this._chatCombine?.updateStoreBasedOnGroupAttributes(groupAttributes, TUIStore, this);
}
}
await this?._bellContext?.stop();
}
} 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());
}
}