@tencentcloud/roomkit-web-vue3
Version:
<h1 align="center"> TUIRoomKit</h1> Conference (TUIRoomKit) is a product suitable for multi-person audio and video conversation scenarios such as business meetings, webinars, and online education. By integrating this product, you can add room management,
422 lines (387 loc) • 11.9 kB
text/typescript
import {
TRTCVideoEncParam,
TRTCVideoResolution,
TUIRoomInfo,
TUIRoomType,
TUISeatMode,
TUISeatInfo,
TUIVideoStreamType,
TUIUserInfo,
} from '@tencentcloud/tuiroom-engine-js';
import { EventType, IRoomService, RoomParam } from '../types';
import { isMobile, isWeChat } from '../../utils/environment';
import logger from '../../utils/common/logger';
import { useAudioDeviceState, useVideoDeviceState } from '../../core';
const { microphone, speaker } = useAudioDeviceState();
const { camera } = useVideoDeviceState();
const logPrefix = '[RoomService:roomActionManager]';
const smallParam = new TRTCVideoEncParam();
smallParam.videoResolution = TRTCVideoResolution.TRTCVideoResolution_640_360;
smallParam.videoFps = 10;
smallParam.videoBitrate = 550;
export interface DeviceParams {
isOpenCamera?: boolean;
isOpenMicrophone?: boolean;
defaultCameraId?: string;
defaultMicrophoneId?: string;
defaultSpeakerId?: string;
}
export interface StartParams extends DeviceParams {
roomName?: string;
isSeatEnabled?: boolean;
password?: string;
}
export interface JoinParams extends DeviceParams {
password?: string;
}
export type RoomParamsInfo = {
roomId: string;
roomType: TUIRoomType;
};
export class RoomActionManager {
private service: IRoomService;
constructor(service: IRoomService) {
this.service = service;
}
public async start(roomId: string, params: StartParams = {}) {
const {
roomName,
isSeatEnabled = false,
isOpenCamera = false,
isOpenMicrophone = false,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
password,
} = params;
const roomMode = isSeatEnabled ? 'SpeakAfterTakingSeat' : 'FreeToSpeak';
this.service.roomStore.resetRoomData();
await this.createRoom({
roomId,
roomName,
roomMode,
roomParam: {
isOpenCamera,
isOpenMicrophone,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
password,
},
});
await this.enterRoom({
roomId,
roomParam: {
isOpenCamera,
isOpenMicrophone,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
password,
},
});
this.service.emit(EventType.ROOM_START, { roomId });
}
public async join(roomId: string, params: JoinParams = {}) {
const {
isOpenCamera = false,
isOpenMicrophone = false,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
password,
} = params;
this.service.roomStore.resetRoomData();
await this.enterRoom({
roomId,
roomParam: {
isOpenCamera,
isOpenMicrophone,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
password,
},
});
this.service.emit(EventType.ROOM_JOIN, { roomId });
}
public async leaveRoom() {
try {
this.closeMediaBeforeLeave();
const response = await this.service.roomEngine.instance?.exitRoom();
this.service.resetStore();
logger.log(`${logPrefix}leaveRoom:`, response);
this.service.emit(EventType.ROOM_LEAVE, response);
} catch (error) {
logger.error(`${logPrefix}leaveRoom error:`, error);
}
}
public async dismissRoom() {
try {
logger.log(`${logPrefix}dismissRoom: enter`);
this.closeMediaBeforeLeave();
await this.service.roomEngine.instance?.destroyRoom();
this.service.resetStore();
this.service.emit(EventType.ROOM_DISMISS, {});
} catch (error) {
logger.error(`${logPrefix}dismissRoom error:`, error);
}
}
public async createRoom(options: {
roomId: string;
roomName?: string;
roomMode?: 'FreeToSpeak' | 'SpeakAfterTakingSeat';
roomParam?: StartParams;
}) {
try {
const { roomId, roomName, roomMode, roomParam } = options;
const roomParams = {
roomId,
roomName,
roomType: TUIRoomType.kConference,
isSeatEnabled: roomMode !== 'FreeToSpeak',
seatMode:
roomMode === 'SpeakAfterTakingSeat'
? TUISeatMode.kApplyToTake
: undefined,
password: roomParam?.password || '',
};
await this.handleRoomCreation(roomParams, options);
} catch (error) {
logger.error(`${logPrefix}createRoom error:`, error);
this.service.errorHandler.handleError(error, 'createRoom');
throw error;
}
}
private async handleRoomCreation(roomParams: any, options: any) {
const { roomEngine } = this.service;
if (!roomEngine.instance) {
return;
}
this.service.basicStore.setRoomId(roomParams.roomId);
logger.debug(`${logPrefix}createRoom:`, roomParams, options);
await roomEngine.instance?.createRoom(roomParams);
}
public async enterRoom(options: { roomId: string; roomParam?: RoomParam }) {
try {
const { roomId, roomParam } = options;
const roomInfo = await this.doEnterRoom({
roomId,
roomType: TUIRoomType.kConference,
password: roomParam?.password || '',
});
this.service.roomStore.setRoomInfo(roomInfo);
await this.getUserList();
await this.syncUserInfo(this.service.basicStore.userId);
await this.fetchAttendeeList(roomId);
await this.getInvitationList(roomId);
if (roomInfo.isSeatEnabled) {
await this.getSeatList();
if (this.service.roomStore.isMaster) {
await this.service.roomEngine.instance?.takeSeat({
seatIndex: -1,
timeout: 0,
});
// fix: Fix the issue where onSeatListChanged was not updated timely after takeSeat success
this.service.roomStore.updateUserInfo({
userId: this.service.basicStore.userId,
onSeat: true,
});
}
}
this.setRoomParams(roomParam);
} catch (error) {
logger.error(`${logPrefix}enterRoom error:`, error);
this.service.errorHandler.handleError(error, 'enterRoom');
throw error;
}
}
private async setRoomParams(roomParam?: RoomParam) {
if (!roomParam) {
return;
}
const {
isOpenCamera,
isOpenMicrophone,
defaultCameraId,
defaultMicrophoneId,
defaultSpeakerId,
} = roomParam;
if (defaultCameraId) {
camera.setCurrentDevice({ deviceId: defaultCameraId });
}
if (defaultMicrophoneId) {
microphone.setCurrentDevice({ deviceId: defaultMicrophoneId });
}
if (defaultSpeakerId) {
speaker.setCurrentDevice({ deviceId: defaultSpeakerId });
}
const {
isMaster,
isMicrophoneDisableForAllUser,
isCameraDisableForAllUser,
isFreeSpeakMode,
} = this.service.roomStore;
const isCanOpenMicrophone =
isMaster || (!isMicrophoneDisableForAllUser && isFreeSpeakMode);
if (isCanOpenMicrophone) {
if (isOpenMicrophone) {
await microphone.openLocalMicrophone();
await microphone.unmuteLocalAudio();
} else {
await microphone.muteLocalAudio();
}
}
// Whether camera can be opened automatically
const isCanOpenCamera =
isMaster || (!isCameraDisableForAllUser && isFreeSpeakMode);
if (isCanOpenCamera && isOpenCamera) {
camera.openLocalCamera();
}
}
private async doEnterRoom(params: {
roomId: string;
roomType: TUIRoomType;
password: string;
}) {
const { roomEngine } = this.service;
const { roomId, roomType, password } = params;
this.service.basicStore.setRoomId(roomId);
const isH5 = isMobile && !isWeChat;
const trtcCloud = roomEngine.instance?.getTRTCCloud();
trtcCloud?.setDefaultStreamRecvMode(true, false);
const roomInfo = (await roomEngine.instance?.enterRoom({
roomId,
roomType,
options: {
password,
},
})) as TUIRoomInfo;
// roomEngine enabled small stream by default in enterRoom api
trtcCloud?.enableSmallVideoStream(!isH5, smallParam);
microphone.muteLocalAudio();
if (!roomInfo.isSeatEnabled) {
microphone.openLocalMicrophone();
}
return roomInfo;
}
private async getUserList() {
const { roomEngine } = this.service;
let nextSequence = 0;
try {
do {
const result = (await roomEngine.instance?.getUserList({
nextSequence,
})) as any;
result.userInfoList.forEach((user: TUIUserInfo) => {
this.service.roomStore.addUserInfo(
Object.assign(user, { isInRoom: true })
);
if (this.service.roomStore.isFreeSpeakMode) {
this.service.roomStore.addStreamInfo(
user.userId,
TUIVideoStreamType.kCameraStream
);
}
});
nextSequence = result.nextSequence;
} while (nextSequence !== 0);
} catch (error: any) {
logger.error('TUIRoomEngine.getUserList', error.code, error.message);
}
}
private async getInvitationList(
roomId: string,
cursor = '',
result: any[] = []
) {
const res =
await this.service.conferenceInvitationManager.getInvitationList({
roomId,
cursor,
count: 20,
});
if (!res?.invitationList) return [];
// eslint-disable-next-line no-unsafe-optional-chaining
result.push(...res?.invitationList);
if (res.cursor !== '') {
await this.getInvitationList(roomId, res.cursor, result);
}
const list = result.map(({ invitee, status }) => ({
...invitee,
status,
}));
this.service.roomStore.updateInviteeList(list as any);
}
private async fetchAttendeeList(
roomId: string,
cursor = '',
result: any[] = []
) {
const res = await this.service.scheduleConferenceManager.fetchAttendeeList({
roomId,
cursor,
count: 20,
});
if (!res?.attendeeList) return [];
// eslint-disable-next-line no-unsafe-optional-chaining
result.push(...res?.attendeeList);
if (res.cursor !== '') {
await this.fetchAttendeeList(roomId, res.cursor, result);
}
const inviteeList = result.filter(user => {
return !this.service.roomStore.userList.some(
item => item.userId === user.userId
);
});
this.service.roomStore.updateInviteeList(inviteeList);
}
private async syncUserInfo(userId: string) {
const { roomEngine } = this.service;
const userInfo = (await roomEngine.instance?.getUserInfo({
userId,
})) as any;
const { isMessageDisabled } = userInfo;
this.service.chatStore.setSendMessageDisableChanged(isMessageDisabled);
}
private async getSeatList() {
const { roomEngine } = this.service;
try {
const seatList = (await roomEngine.instance?.getSeatList()) as any;
seatList.forEach((seat: TUISeatInfo) => {
const { userId } = seat;
if (!userId) {
return;
}
const user = this.service.roomStore.userInfoObj[userId];
if (user) {
this.service.roomStore.updateUserInfo({ userId, onSeat: true });
} else {
this.service.roomStore.addUserInfo({
userId,
onSeat: true,
isInRoom: true,
});
}
this.service.roomStore.addStreamInfo(
userId,
TUIVideoStreamType.kCameraStream
);
});
} catch (error: any) {
logger.error('TUIRoomEngine.getSeatList', error.code, error.message);
}
}
private closeMediaBeforeLeave() {
const { roomEngine } = this.service;
if (this.service.roomStore.localUser.hasAudioStream) {
roomEngine.instance?.closeLocalMicrophone();
}
if (this.service.roomStore.localUser.hasVideoStream) {
roomEngine.instance?.closeLocalCamera();
}
}
public async fetchRoomInfo(options?: RoomParamsInfo) {
return await this.service.roomEngine.instance?.fetchRoomInfo(options);
}
}