UNPKG

@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,

364 lines (335 loc) 10.9 kB
import TUIRoomEngine, { TUIRoomEvents, TUIChangeReason, TUIVideoStreamType, TRTCVideoStreamType, TRTCVideoFillMode, TRTCVideoMirrorType, TRTCVideoRotation, } from '@tencentcloud/tuiroom-engine-js'; import { IRoomService, EventType } from '../types'; import { isMobile } from '../../utils/environment'; import { MESSAGE_DURATION } from '../../constants/message'; import logger from '../../utils/common/logger'; interface ObserverData { userId: string; streamType: TUIVideoStreamType; isIntersection?: boolean; } export enum StreamPlayMode { PLAY = 'play', STOP = 'stop', PLAY_IN_VISIBLE = 'playInVisible', } export enum StreamPlayQuality { HIGH = 'high', LOW = 'low', Default = 'default', } export class MediaManager { private service: IRoomService; intersectionObserver: IntersectionObserver | null = null; observerDataMap: Map<HTMLElement | string, ObserverData> = new Map(); observerRoot: HTMLElement | null = null; constructor(service: IRoomService) { this.service = service; this.bindRoomEngineEvents(); } public async startPlayVideo(options: { userId: string; streamType: TUIVideoStreamType; view: HTMLElement; observerViewInVisible: boolean; }) { const { userId, streamType, view, observerViewInVisible } = options; logger.info( 'MediaManager.startPlayVideo', userId, streamType, view, observerViewInVisible ); const streamInfo = this.service.roomStore.getStreamInfo(userId, streamType); if (!streamInfo) { return; } streamInfo!.playDomMap?.set(view, streamType); if (!observerViewInVisible) { await this.doStartPlayVideo({ userId, streamType }); return; } this.initIntersectionObserver(); this.observerDataMap.set( view, Object.assign(this.observerDataMap.get(view) || {}, { userId, streamType, }) ); this.intersectionObserver?.observe(view); // The dom that was playing stream A is going to play stream B. // At this point, the dom already exists and will no longer trigger the intersection event, // so when you realize it's already visible, you should play it immediately. if (this.observerDataMap.get(view)?.isIntersection) { await this.doStartPlayVideo({ userId, streamType }); } } // If only one view wants to stop playing, update the viewList, but don't stop the stream. // If no view is passed in, the stream is stopped. public async stopPlayVideo(options: { userId: string; streamType: TUIVideoStreamType; view: HTMLElement; }) { const { userId, streamType, view } = options; logger.info('MediaManager.stopPlayVideo', userId, streamType, view); const streamInfo = this.service.roomStore.getStreamInfo(userId, streamType); if (!streamInfo?.playDomMap || streamInfo?.playDomMap.size === 0) { return; } if (this.observerDataMap.get(view)) { this.observerDataMap.delete(view); this.intersectionObserver?.unobserve(view); } streamInfo?.playDomMap.delete(view); if (streamInfo?.playDomMap.size > 0) { await this.doStartPlayVideo({ userId, streamType, }); } else { await this.doStopPlayVideo(options); } } private initIntersectionObserver() { if ( !this.intersectionObserver || document.getElementById('roomContainer') !== this.observerRoot ) { const observerRoot = document.getElementById('roomContainer'); this.intersectionObserver = new IntersectionObserver( this.intersectionObserverCallback.bind(this), { root: observerRoot, rootMargin: '0px', } ); this.observerDataMap = new Map(); this.observerRoot = observerRoot; } } private intersectionObserverCallback(entries: IntersectionObserverEntry[]) { entries.forEach((entry: IntersectionObserverEntry) => { const { isIntersecting, target } = entry; const observerData = this.observerDataMap.get(target as HTMLElement); if (!observerData) { return; } const { userId, streamType } = observerData; const streamInfo = this.service.roomStore.getStreamInfo( userId, streamType ); if (!streamInfo) { return; } if (isIntersecting) { observerData.isIntersection = true; if (streamInfo?.playDomMap && streamInfo?.playDomMap.size > 0) { this.doStartPlayVideo({ userId, streamType }); } } else { observerData.isIntersection = false; const isContinuePlay = Array.from( streamInfo?.playDomMap?.keys() || [] ).find( item => !this.observerDataMap.get(item) || this.observerDataMap.get(item)?.isIntersection ); if (!isContinuePlay) { this.doStopPlayVideo({ userId, streamType }); } } }); } private getPlayStreamType(userId: string, streamType: TUIVideoStreamType) { if (streamType === TUIVideoStreamType.kScreenStream) { return streamType; } const streamInfo = this.service.roomStore.getStreamInfo(userId, streamType); if (streamInfo?.playDomMap && streamInfo?.playDomMap.size > 0) { const playStreamTypeList = Array.from(streamInfo?.playDomMap?.values()); if (playStreamTypeList.includes(TUIVideoStreamType.kCameraStream)) { return TUIVideoStreamType.kCameraStream; } return TUIVideoStreamType.kCameraStreamLow; } return streamType; } private async doStartPlayVideo(options: { userId: string; streamType: TUIVideoStreamType; }) { const { userId, streamType } = options; const streamInfo = this.service.roomStore.getStreamInfo(userId, streamType); if (!streamInfo) { return; } const playStreamType = this.getPlayStreamType(userId, streamType); const viewIdList = Array.from(streamInfo!.playDomMap!.keys()).map(item => { if (item instanceof HTMLElement) { return item?.id; } return item; }); this.service.roomStore.updateStreamInfo({ userId, streamType, isLoading: true, }); if ( userId === this.service.basicStore.userId && streamType === TUIVideoStreamType.kCameraStream ) { this.service.roomEngine.instance?.setLocalVideoView({ view: viewIdList }); } else { this.service.roomEngine.instance?.setRemoteVideoView({ userId, streamType: playStreamType, view: viewIdList, }); await this.setVideoRenderParams({ userId, streamType }); await this.service.roomEngine.instance?.startPlayRemoteVideo({ userId, streamType: playStreamType, }); } this.service.roomStore.updateStreamInfo({ userId, streamType, isLoading: false, }); } private async setVideoRenderParams(options: { userId: string; streamType: TUIVideoStreamType; }) { const { userId, streamType } = options; if (userId !== this.service.basicStore.userId) { const trtcCloud = this.service.roomEngine.instance?.getTRTCCloud(); const trtcStreamType = streamType === TUIVideoStreamType.kScreenStream ? TRTCVideoStreamType.TRTCVideoStreamTypeSub : TRTCVideoStreamType.TRTCVideoStreamTypeBig; let trtcFillMode = TRTCVideoFillMode.TRTCVideoFillMode_Fit; if (isMobile && streamType !== TUIVideoStreamType.kScreenStream) { trtcFillMode = TRTCVideoFillMode.TRTCVideoFillMode_Fill; } await trtcCloud?.setRemoteRenderParams(userId, trtcStreamType, { mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Disable, rotation: TRTCVideoRotation.TRTCVideoRotation0, fillMode: trtcFillMode, }); } } private async doStopPlayVideo(options: { userId: string; streamType: TUIVideoStreamType; }) { const { userId, streamType } = options; this.service.roomStore.updateStreamInfo({ userId, streamType, isLoading: false, }); if ( userId === this.service.basicStore.userId && streamType === TUIVideoStreamType.kCameraStream ) { this.service.roomEngine.instance?.setLocalVideoView({ view: null }); } else { await this.service.roomEngine.instance?.stopPlayRemoteVideo({ userId, streamType, }); } } private onUserAudioStateChanged(eventInfo: { userId: string; hasAudio: boolean; reason: TUIChangeReason; }) { const { userId, hasAudio, reason } = eventInfo; if ( userId === this.service.basicStore.userId && !hasAudio && reason === TUIChangeReason.kChangedByAdmin ) { this.service.emit(EventType.ROOM_NOTICE_MESSAGE, { type: 'warning', message: this.service.t('Your microphone has been turned off'), duration: MESSAGE_DURATION.NORMAL, }); } } private onUserVideoStateChanged = (eventInfo: { userId: string; streamType: TUIVideoStreamType; hasVideo: boolean; reason: TUIChangeReason; }) => { const { userId, streamType, hasVideo, reason } = eventInfo; // Handle status changes if ( userId === this.service.basicStore.userId && !hasVideo && reason === TUIChangeReason.kChangedByAdmin ) { // The host turns off the camera if (streamType === TUIVideoStreamType.kCameraStream) { this.service.emit(EventType.ROOM_NOTICE_MESSAGE, { type: 'warning', message: this.service.t('Your camera has been turned off'), duration: MESSAGE_DURATION.NORMAL, }); } // Host turns off screen sharing if (streamType === TUIVideoStreamType.kScreenStream) { this.service.emit(EventType.ROOM_NOTICE_MESSAGE_BOX, { title: this.service.t('Your screen sharing has been stopped'), message: this.service.t( 'Your screen sharing has been stopped, Now only the host/admin can share the screen' ), confirmButtonText: this.service.t('I got it'), }); } } }; dispose() { this.intersectionObserver = null; this.observerDataMap = new Map(); } private bindRoomEngineEvents() { TUIRoomEngine.once('ready', () => { this.service.roomEngine.instance?.on( TUIRoomEvents.onUserVideoStateChanged, this.onUserVideoStateChanged.bind(this) ); this.service.roomEngine.instance?.on( TUIRoomEvents.onUserAudioStateChanged, this.onUserAudioStateChanged.bind(this) ); }); } private unbindRoomEngineEvents() { this.service.roomEngine.instance?.off( TUIRoomEvents.onUserVideoStateChanged, this.onUserVideoStateChanged ); this.service.roomEngine.instance?.off( TUIRoomEvents.onUserAudioStateChanged, this.onUserAudioStateChanged ); } }