import { reactive, ref, Ref } from 'vue' import * as RongIMLib from '@rongcloud/imlib-next' import * as RTC from '@rongcloud/plugin-rtc' import request from '/src/helpers/request' import event, { LIVE_EVENT_MESSAGE } from './event' import { removeMedia } from './helpers' type imConnectStatus = 'connecting' | 'connected' | 'disconnect' type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading' type TrackType = 'microphone' | 'camera' | 'screen' type ActiveTracks = { [key in TrackType]: RTC.RCLocalTrack | null } const runtime = reactive({ /** IM连接状态 */ imConnectStatus: 'connecting' as imConnectStatus, // 屏幕分享状态 screenShareStatus: false, // 视频节点 videoRef: ref(null), // RTC实例 rtcClient: null as RTC.RCRTCClient | null, /** 加入房间实例 */ joinedRoom: null as RTC.RCLivingRoom | null, // Tracks mediaStreamTrack: [] as MediaStreamTrack[], // 媒体流 mediaStreams: null as MediaStream | null, // 视频状态 videoStatus: 'init' as VideoStatus, // 麦克风设备列表 microphones: [] as MediaDeviceInfo[], // 摄像头设备列表 cameras: [] as MediaDeviceInfo[], // 摄像头设备 selectedCamera: null as MediaDeviceInfo | null, // 麦克风设备 selectedMicrophone: null as MediaDeviceInfo | null, // 点赞数量 likeCount: 0, // 上一次点赞数量 lastLikeCount: 0, /** 当前活跃的数据流 */ activeTracks: {} as ActiveTracks, }) export default runtime const RONG_IM_TOKEN = 'c9kqb3rdc451j' RongIMLib.init({ appkey: RONG_IM_TOKEN, }) type MessageProps = { messageType: 'RC:Chatroom:Welcome' | 'RC:TxtMsg' | 'RC:Chatroom:Barrage' | 'RC:Chatroom:Like', content: any, } type MessageEvent = { messages: MessageProps[], } const Events = RongIMLib.Events /** * 监听消息通知 */ const { MESSAGES, ...RestMessage } = Events RongIMLib.addEventListener(Events.MESSAGES, (evt: MessageEvent) => { const { messages } = evt for (const message of messages) { if (LIVE_EVENT_MESSAGE[message.messageType]) { event.emit(LIVE_EVENT_MESSAGE[message.messageType], {...message.content, $EventMessage: message}) } } }) for (const Message of Object.values(RestMessage)) { RongIMLib.addEventListener(Message, (evt: any) => { console.log(Message, evt) // chatroomDestroyed event.emit(Message, {$EventMessage: null}) }) } /** * 监听 IM 连接状态变化 */ RongIMLib.addEventListener(Events.CONNECTING, () => { console.log('connecting') runtime.imConnectStatus = 'connecting' }) RongIMLib.addEventListener(Events.CONNECTED, () => { console.log('connected') runtime.imConnectStatus = 'connected' }) RongIMLib.addEventListener(Events.DISCONNECT, () => { console.log('disconnect') runtime.imConnectStatus = 'disconnect' }) export const connectIM = async (imToken: string) => { try { const user = await RongIMLib.connect(imToken) runtime.rtcClient = RongIMLib.installPlugin(RTC.installer, {}) console.log('connect success', user.data?.userId) return user } catch (error) { throw error } } /** * 设置video视频流 */ export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams: MediaStream | null) => { if (video && mediaStreams) { video.srcObject = mediaStreams video.onloadedmetadata = () => { video.play() } } } /** * 发起屏幕共享 */ export const shareScreenVideo = async () => { if (runtime.rtcClient) { const screenTrack = await getTrack('screen') const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack removeTrack([oldTrack], 'camera') setTrack([screenTrack as RTC.RCLocalTrack], 'screen') screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => { runtime.screenShareStatus = false removeTrack([track], 'screen') setTrack([oldTrack as RTC.RCLocalTrack], 'camera') // setVideoSrcObject(runtime.videoRef, this.mediaStreams) }) } } /** * * 获取所有音频输入设备 * @returns {Promise} */ export const getMicrophones = async () => { const microphones = await RTC.device.getMicrophones() runtime.microphones = microphones return microphones } /** * * 获取所有视频输入设备 * @returns {Promise} */ export const getCameras = async () => { const cameras = await RTC.device.getCameras() runtime.cameras = cameras return cameras } /** * * 设置当前视频设备 * @param camera MediaDeviceInfo */ export const setSelectCamera = (camera: MediaDeviceInfo) => { runtime.selectedCamera = camera } /** * * 设置当前麦克风设备 * @param microphone MediaDeviceInfo */ export const setSelectMicrophone = (microphone: MediaDeviceInfo) => { runtime.selectedMicrophone = microphone } type TrackResult = { code: RTC.RCRTCCode, track: RTC.RCMicphoneAudioTrack | RTC.RCCameraVideoTrack | RTC.RCScreenVideoTrack | undefined } export const getTrack = async (trackType: TrackType): Promise => { let res: TrackResult | undefined let Track: RTC.RCLocalTrack | null = null if (trackType === 'microphone') { res = await runtime.rtcClient?.createMicrophoneAudioTrack('RongCloudRTC', { micphoneId: runtime.selectedMicrophone?.deviceId, }) as TrackResult } else if (trackType === 'camera') { res = await runtime.rtcClient?.createCameraVideoTrack('RongCloudRTC', { cameraId: runtime.selectedCamera?.deviceId, faceMode: 'user', frameRate: RTC.RCFrameRate.FPS_15, resolution: RTC.RCResolution.W1280_H720, }) as TrackResult } else { res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult } Track = res?.track as RTC.RCLocalTrack if (res.code !== RTC.RCRTCCode.SUCCESS || !Track) { throw new Error('获取数据流失败') } return Track } /** * 添加视频流,会同步修改当先视频与推送的流 * @param track */ export const setTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType) => { for (const track of tracks) { // @ts-ignore await runtime.mediaStreams?.addTrack(track._msTrack) runtime.activeTracks[trackType] = track } await runtime.joinedRoom?.publish(tracks) } /** * 删除视频流,会同步修改当先视频与推送的流 * @param track */ export const removeTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType) => { for (const track of tracks) { // @ts-ignore await runtime.mediaStreams?.removeTrack(track._msTrack) runtime.activeTracks[trackType] = null } await runtime.joinedRoom?.unpublish(tracks) } export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => { await RongIMLib.joinChatRoom(roomId, {count: 0}) const join = await runtime.rtcClient?.joinLivingRoom(roomId, type) if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败') join.room?.registerRoomEventListener(listenEvents) return join } /** * 开始直播 */ export const startLive = async () => { if (runtime.videoStatus !== 'stream') throw Error('当前无视频流') } /** * 关闭直播 */ export const closeLive = async () => { removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack) runtime.videoStatus = 'stopped' } /** * 同步点赞数量 */ export const loopSyncLike = async () => { if (runtime.likeCount !== runtime.lastLikeCount) { try { await request.post('/api/live/like', {}) runtime.lastLikeCount = runtime.likeCount } catch (error) {} } setTimeout(() => { loopSyncLike() }, 1000 * 60 * 5) }