@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,
444 lines (413 loc) • 14.3 kB
text/typescript
import TUIRoomEngine, {
TUIRoomEvents,
TUIUserInfo,
TUIChangeReason,
TUIVideoStreamType,
TUISeatInfo,
TRTCVolumeInfo,
TUIRole,
} from '@tencentcloud/tuiroom-engine-js';
import { IRoomService } from '../types';
import { StreamInfo, UserInfo } from '../../stores/room';
import { isMobile } from '../../utils/environment';
import {
throttle,
createComparator,
combineComparators,
Comparator,
} from '../../utils/utils';
interface IUserManager {
setSelfInfo(options: SelfInfoOptions): Promise<void>;
setCustomInfoForUser(options: CustomInfoForUser): Promise<void>;
}
export type CustomInfoForUser = {
userId: string;
customInfo: Record<string, string>;
};
export type SelfInfoOptions = {
userName?: string;
avatarUrl?: string;
customInfo?: Record<string, any>;
};
export class UserManager implements IUserManager {
private service: IRoomService;
private userListCompareFunction:
| ((userInfoA: UserInfo, userInfoB: UserInfo) => number)
| null;
private streamListCompareFunction:
| ((streamInfoA: StreamInfo, streamInfoB: StreamInfo) => number)
| null;
constructor(service: IRoomService) {
this.service = service;
this.userListCompareFunction = null;
this.streamListCompareFunction = null;
this.onRemoteUserEnterRoom = this.onRemoteUserEnterRoom.bind(this);
this.onRemoteUserLeaveRoom = this.onRemoteUserLeaveRoom.bind(this);
this.onSeatListChanged = this.onSeatListChanged.bind(this);
this.onUserVideoStateChanged = this.onUserVideoStateChanged.bind(this);
this.onUserAudioStateChanged = this.onUserAudioStateChanged.bind(this);
this.onUserVoiceVolumeChanged = this.onUserVoiceVolumeChanged.bind(this);
this.bindRoomEngineEvents();
}
public async setSelfInfo(options: SelfInfoOptions): Promise<void> {
const { avatarUrl, userName } = await TUIRoomEngine.getSelfInfo();
const info = {
userName: options.userName || userName,
avatarUrl: options.avatarUrl || avatarUrl,
};
this.service.basicStore.setBasicInfo(info);
return TUIRoomEngine.setSelfInfo(info);
}
public async setCustomInfoForUser(options: CustomInfoForUser) {
const roomEngine = this.service.roomEngine.instance;
return roomEngine?.setCustomInfoForUser(options);
}
public getDisplayName(options: UserInfo) {
const { nameCard, userName, userId } = options;
return nameCard || userName || userId;
}
public setLocalUser(userInfo: { userId: string }) {
this.service.roomStore.addUserInfo(userInfo);
this.service.roomStore.addStreamInfo(
userInfo.userId,
TUIVideoStreamType.kCameraStream
);
}
public setUserListSortComparator(comparator: Comparator<UserInfo>) {
this.userListCompareFunction = comparator;
}
public getUserListSortComparator() {
const defaultUserListCompareFunction = combineComparators(
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.userId === this.service.basicStore.userId)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.userRole === TUIRole.kRoomOwner)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.userRole === TUIRole.kAdministrator)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.hasScreenStream)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.hasVideoStream && userInfo.hasAudioStream)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.hasVideoStream)
),
createComparator((userInfo: UserInfo) =>
Boolean(userInfo.hasAudioStream)
),
createComparator((userInfo: UserInfo) => Boolean(userInfo.onSeat)),
createComparator((userInfoA: UserInfo, userInfoB: UserInfo) =>
Boolean(Number(userInfoA.timestamp) < Number(userInfoB.timestamp))
)
);
return this.userListCompareFunction || defaultUserListCompareFunction;
}
public setStreamListSortComparator(comparator: Comparator<StreamInfo>) {
this.streamListCompareFunction = comparator;
}
public getStreamListSortComparator() {
const defaultUserListCompareFunction = combineComparators(
createComparator((streamInfo: StreamInfo) =>
Boolean(streamInfo.streamType === TUIVideoStreamType.kScreenStream)
),
createComparator((streamInfo: StreamInfo) =>
Boolean(streamInfo.userId === this.service.roomStore.masterUserId)
),
createComparator((streamInfo: StreamInfo) =>
Boolean(streamInfo.userId === this.service.basicStore.userId)
),
createComparator((streamInfoA: StreamInfo) =>
Boolean(streamInfoA.hasAudioStream && streamInfoA.hasVideoStream)
),
createComparator((streamInfoA: StreamInfo) =>
Boolean(streamInfoA.hasVideoStream)
),
createComparator((streamInfoA: StreamInfo) =>
Boolean(streamInfoA.hasAudioStream)
),
createComparator((streamInfoA: StreamInfo, streamInfoB: StreamInfo) =>
Boolean(Number(streamInfoA.timestamp) - Number(streamInfoB.timestamp))
)
);
return this.streamListCompareFunction || defaultUserListCompareFunction;
}
private onRemoteUserEnterRoom(eventInfo: { userInfo: TUIUserInfo }) {
const { userInfo } = eventInfo;
this.service.roomStore.addUserInfo(
Object.assign(userInfo, { isInRoom: true, timestamp: Date.now() })
);
if (this.service.roomStore.isFreeSpeakMode) {
this.service.roomStore.addStreamInfo(
userInfo.userId,
TUIVideoStreamType.kCameraStream
);
}
}
private onRemoteUserLeaveRoom(eventInfo: { userInfo: TUIUserInfo }) {
const { userId } = eventInfo.userInfo;
this.service.roomStore.removeUserInfo(userId);
this.service.roomStore.removeStreamInfo(
userId,
TUIVideoStreamType.kCameraStream
);
this.service.roomStore.removeStreamInfo(
userId,
TUIVideoStreamType.kScreenStream
);
}
private onSeatListChanged(eventInfo: {
seatList: TUISeatInfo[];
seatedList: TUISeatInfo[];
leftList: TUISeatInfo[];
}) {
const { seatedList, leftList } = eventInfo;
seatedList.forEach(seat => {
const { userId } = seat;
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
);
});
leftList?.forEach(seat => {
const { userId } = seat;
const user = this.service.roomStore.userInfoObj[userId];
if (user) {
this.service.roomStore.updateUserInfo({ userId, onSeat: false });
}
this.service.roomStore.removeStreamInfo(
userId,
TUIVideoStreamType.kCameraStream
);
this.service.roomStore.removeStreamInfo(
userId,
TUIVideoStreamType.kScreenStream
);
});
}
private onUserAudioStateChanged(eventInfo: {
userId: string;
hasAudio: boolean;
reason: TUIChangeReason;
}) {
const { userId, hasAudio } = eventInfo;
let userInfo = this.service.roomStore.userInfoObj[userId];
if (!userInfo && hasAudio) {
this.service.roomStore.addUserInfo({ userId, isInRoom: true });
}
userInfo = this.service.roomStore.userInfoObj[userId];
if (!userInfo) {
return;
}
this.service.roomStore.updateUserInfo({ userId, hasAudioStream: hasAudio });
const streamInfo =
this.service.roomStore.streamInfoObj[
`${userId}_${TUIVideoStreamType.kCameraStream}`
];
if (!streamInfo) {
this.service.roomStore.addStreamInfo(
userId,
TUIVideoStreamType.kCameraStream
);
}
this.service.roomStore.updateStreamInfo({
userId,
streamType: TUIVideoStreamType.kCameraStream,
hasAudioStream: hasAudio,
});
}
private onUserVideoStateChanged = (eventInfo: {
userId: string;
streamType: TUIVideoStreamType;
hasVideo: boolean;
reason: TUIChangeReason;
}) => {
const { userId, streamType, hasVideo } = eventInfo;
let userInfo = this.service.roomStore.userInfoObj[userId];
if (!userInfo && hasVideo) {
this.service.roomStore.addUserInfo({ userId, isInRoom: true });
}
userInfo = this.service.roomStore.userInfoObj[userId];
if (!userInfo) {
return;
}
const updateInfo =
streamType === TUIVideoStreamType.kScreenStream
? { hasScreenStream: hasVideo }
: { hasVideoStream: hasVideo };
this.service.roomStore.updateUserInfo({ userId, ...updateInfo });
if (
streamType === TUIVideoStreamType.kCameraStream ||
(streamType === TUIVideoStreamType.kScreenStream && hasVideo)
) {
const streamInfo =
this.service.roomStore.streamInfoObj[`${userId}_${streamType}`];
if (!streamInfo) {
this.service.roomStore.addStreamInfo(userId, streamType);
}
this.service.roomStore.updateStreamInfo({
userId,
streamType,
hasVideoStream: hasVideo,
});
} else if (streamType === TUIVideoStreamType.kScreenStream && !hasVideo) {
this.service.roomStore.removeStreamInfo(userId, streamType);
}
};
// Calculate the userId of the loudest speaker in the room
// Calculate the userId of the remote user who speaks the loudest in the current room.
private handleUserVoiceVolume(userVolumeList: Array<typeof TRTCVolumeInfo>) {
const localUserVolume = {
userId: this.service.basicStore.userId,
volume: 0,
};
const largestRemoteUserVolume = {
userId: '',
volume: 0,
};
userVolumeList.forEach((item: typeof TRTCVolumeInfo) => {
if (
item.userId === this.service.basicStore.userId &&
this.service.roomStore.localStream?.hasAudioStream
) {
localUserVolume.volume = item.volume;
} else if (
item.userId !== this.service.basicStore.userId &&
this.service.roomStore.userInfoObj[item.userId]?.hasAudioStream
) {
const { userId, volume } = item;
if (volume > largestRemoteUserVolume.volume) {
largestRemoteUserVolume.userId = userId;
largestRemoteUserVolume.volume = volume;
}
}
});
const largestUserVolume =
localUserVolume.volume > largestRemoteUserVolume.volume
? localUserVolume
: largestRemoteUserVolume;
if (this.service.roomStore.currentSpeakerInfo.remoteSpeakerUserId) {
const lastRemoteSpeakerUserVolumeInfo = userVolumeList.find(
(item: typeof TRTCVolumeInfo) =>
item.userId ===
this.service.roomStore.currentSpeakerInfo.remoteSpeakerUserId
);
if (
!lastRemoteSpeakerUserVolumeInfo ||
(lastRemoteSpeakerUserVolumeInfo?.volume === 0 &&
largestRemoteUserVolume.volume > 0)
) {
this.service.roomStore.setCurrentSpeakerInfo({
remoteSpeakerUserId: largestRemoteUserVolume.userId,
});
}
} else {
if (largestRemoteUserVolume.volume > 0) {
this.service.roomStore.setCurrentSpeakerInfo({
remoteSpeakerUserId: largestRemoteUserVolume.userId,
});
}
}
if (this.service.roomStore.currentSpeakerInfo.speakerUserId) {
const lastSpeakerUserVolumeInfo: typeof TRTCVolumeInfo | undefined =
userVolumeList.find(
(item: typeof TRTCVolumeInfo) =>
item.userId ===
this.service.roomStore.currentSpeakerInfo.speakerUserId
);
if (
!lastSpeakerUserVolumeInfo ||
(lastSpeakerUserVolumeInfo.volume === 0 && largestUserVolume.volume > 0)
) {
this.service.roomStore.setCurrentSpeakerInfo({
speakerUserId: largestUserVolume.userId,
});
}
} else {
if (largestUserVolume.volume > 0) {
this.service.roomStore.setCurrentSpeakerInfo({
speakerUserId: largestUserVolume.userId,
});
}
}
}
private handleUserVoiceVolumeThrottle = throttle(
this.handleUserVoiceVolume,
1000
);
private onUserVoiceVolumeChanged(eventInfo: { userVolumeList: [] }) {
const { userVolumeList } = eventInfo;
this.service.roomStore.setAudioVolume(userVolumeList);
// Mobile needs to count the current speaker
if (isMobile) {
this.handleUserVoiceVolumeThrottle(userVolumeList);
}
}
private bindRoomEngineEvents() {
TUIRoomEngine.once('ready', () => {
this.service.roomEngine.instance?.on(
TUIRoomEvents.onRemoteUserEnterRoom,
this.onRemoteUserEnterRoom
);
this.service.roomEngine.instance?.on(
TUIRoomEvents.onRemoteUserLeaveRoom,
this.onRemoteUserLeaveRoom
);
this.service.roomEngine.instance?.on(
TUIRoomEvents.onSeatListChanged,
this.onSeatListChanged
);
this.service.roomEngine.instance?.on(
TUIRoomEvents.onUserVideoStateChanged,
this.onUserVideoStateChanged
);
this.service.roomEngine.instance?.on(
TUIRoomEvents.onUserAudioStateChanged,
this.onUserAudioStateChanged
);
this.service.roomEngine.instance?.on(
TUIRoomEvents.onUserVoiceVolumeChanged,
this.onUserVoiceVolumeChanged
);
});
}
private unbindRoomEngineEvents() {
this.service.roomEngine.instance?.off(
TUIRoomEvents.onRemoteUserEnterRoom,
this.onRemoteUserEnterRoom
);
this.service.roomEngine.instance?.off(
TUIRoomEvents.onRemoteUserLeaveRoom,
this.onRemoteUserLeaveRoom
);
this.service.roomEngine.instance?.off(
TUIRoomEvents.onSeatListChanged,
this.onSeatListChanged
);
this.service.roomEngine.instance?.off(
TUIRoomEvents.onUserVideoStateChanged,
this.onUserVideoStateChanged
);
this.service.roomEngine.instance?.off(
TUIRoomEvents.onUserAudioStateChanged,
this.onUserAudioStateChanged
);
this.service.roomEngine.instance?.off(
TUIRoomEvents.onUserVoiceVolumeChanged,
this.onUserVoiceVolumeChanged
);
}
}