| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 | 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 { state } from '/src/state'import event, { LIVE_EVENT_MESSAGE } from './event'import dayjs from 'dayjs'// import { SeatsCtrl } from './message-type'type imConnectStatus = 'connecting' | 'connected' | 'disconnect'type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'export type TrackType = 'microphone' | 'camera' | 'screen'type ActiveTracks = {  [key in TrackType]: RTC.RCLocalTrack | null}type DeviceStatus = {  [key in TrackType]: 'init' | 'granted' | 'denied' | 'closed' | 'none'}export const START_LIVE_TIME = 'start-live-time'export const START_LIVE_STATUS = 'start-live-status'export const VIDEO_DEVICE_ID = 'video-deviceId'export const AUDIO_DEVICE_ID = 'audio-deviceId'export const AUDIO_DEVICE_VOLUME = 'audio-device-volume'const runtime = reactive({  /** 房间id */  roomUid: 'LIVE-2112263-12345',  /** IM连接状态 */  imConnectStatus: 'connecting' as imConnectStatus,  // 屏幕分享状态  screenShareStatus: false,  // 视频节点  videoRef: ref<HTMLVideoElement | null>(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,  /** 是否关闭连麦 */  allowSeatsCtrl: true,  /** 是否关闭发言 */  allowChatCtrl: true,  /** 当前设备获取状态 */  deviceStatus: {    microphone: 'init',    camera: 'init',    screen: 'init'  } as DeviceStatus})export default runtimeconst RONG_IM_TOKEN = 'c9kqb3rdc451j'RongIMLib.init({  appkey: RONG_IM_TOKEN,})// 注册自定义消息类型// 控制是否允许连麦const MessageSeatsCtrl = RongIMLib.registerMessageType('RC:Chatroom:SeatsCtrl', true, true)// 控制是否允许发言const MessageChatBan = RongIMLib.registerMessageType('RC:Chatroom:ChatBan', true, true)// 连麦消息const MessageSeatApply = RongIMLib.registerMessageType('RC:Chatroom:SeatApply', true, true)// 响应连麦消息const MessageSeatResponse = RongIMLib.registerMessageType('RC:Chatroom:SeatResponse', true, true)type MessageProps = {  messageType: 'RC:Chatroom:Welcome' | 'RC:TxtMsg' | 'RC:Chatroom:Barrage' | 'RC:Chatroom:Like' | 'RC:Chatroom:SeatsCtrl' | 'RC:Chatroom:ChatBan' | 'RC:Chatroom:SeatApply',  content: any,}type MessageEvent = {  messages: MessageProps[],}const Events = RongIMLib.Events/** * 监听消息通知 */ const { MESSAGES, ...RestMessage } = Events RongIMLib.addEventListener(Events.MESSAGES, (evt: MessageEvent) => {   console.log(evt, '收到消息')   const { messages } = evt   for (const message of messages) {    //  console.log(LIVE_EVENT_MESSAGE[message.messageType], message)     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   } }/** * 设置声音 * @param video * @param Value 声音大小 */export const setVolume = (value: number) => {  localStorage.setItem(AUDIO_DEVICE_VOLUME, value.toString())  if(runtime.videoRef) {    runtime.videoRef.volume = value / 100  }  // @ts-ignore  if (runtime.activeTracks.microphone && runtime.activeTracks.microphone._element) {    // @ts-ignore    runtime.activeTracks.microphone._element.volume = value / 100  }}/** * 设置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 && !runtime.screenShareStatus) {    const screenTrack = await getTrack('screen')    const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack    // removeTrack([oldTrack], 'camera')    runtime.joinedRoom?.unpublish([oldTrack])    setTrack([screenTrack as RTC.RCLocalTrack], 'screen')    if (runtime.videoRef) {      screenTrack.play(runtime.videoRef)      runtime.screenShareStatus = true    }    screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => {      runtime.screenShareStatus = false      track.destroy()      // removeTrack([track], 'screen')      if (oldTrack) {        setTrack([oldTrack as RTC.RCLocalTrack], 'camera')        if (runtime.videoRef) {          oldTrack.play(runtime.videoRef)        }      }      // setVideoSrcObject(runtime.videoRef, this.mediaStreams)    })  }}/** * * 获取所有音频输入设备 * @returns {Promise<void>} */export const getMicrophones = async () => {  const microphones = await RTC.device.getMicrophones()  runtime.microphones = microphones  return microphones}/** * * 获取所有视频输入设备 * @returns {Promise<void>} */export const getCameras = async () => {  const cameras = await RTC.device.getCameras()  runtime.cameras = cameras  return cameras}/** * * 设置当前视频设备 * @param camera MediaDeviceInfo */export const setSelectCamera = async (camera: MediaDeviceInfo) => {  runtime.selectedCamera = camera  localStorage.setItem(VIDEO_DEVICE_ID, camera.deviceId)  const oldTrack = runtime.activeTracks.camera as RTC.RCLocalTrack  if (oldTrack) {    await removeTrack([oldTrack], 'camera', oldTrack.isPublished())  }  const track = await getTrack('camera')  setTrack([track], 'camera', runtime.videoStatus === 'liveing')}/** * * 设置当前麦克风设备 * @param microphone MediaDeviceInfo */export const setSelectMicrophone = async (microphone: MediaDeviceInfo) => {  runtime.selectedMicrophone = microphone  localStorage.setItem(AUDIO_DEVICE_ID, microphone.deviceId)  const oldTrack = runtime.activeTracks.microphone as RTC.RCLocalTrack  if (oldTrack) {    await removeTrack([oldTrack], 'microphone', oldTrack.isPublished())  }  const track = await getTrack('microphone')  setTrack([track], 'microphone', runtime.videoStatus === 'liveing')}type TrackResult = {  code: RTC.RCRTCCode,  track: RTC.RCMicphoneAudioTrack | RTC.RCCameraVideoTrack | RTC.RCScreenVideoTrack | undefined}export const getTrack = async (trackType: TrackType): Promise<RTC.RCLocalTrack> => {  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_24,      resolution: RTC.RCResolution.W1920_H1080,    }) as TrackResult  } else {    res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult  }  Track = res?.track as RTC.RCLocalTrack  if (trackType === 'camera' && !runtime.cameras.length) {    runtime.deviceStatus[trackType] = 'none'  } else if (trackType === 'microphone' && !runtime.microphones.length) {    runtime.deviceStatus[trackType] = 'none'  } else if (trackType === 'screen' && !runtime.screenShareStatus) {    runtime.deviceStatus[trackType] = 'none'  }  if (res.code === RTC.RCRTCCode.PERMISSION_DENIED) {    runtime.deviceStatus[trackType] = 'denied'  } else {    runtime.deviceStatus[trackType] = 'granted'  }  // if (res.code !== RTC.RCRTCCode.SUCCESS || !Track) {  //   throw new Error('获取数据流失败')  // }  return Track}/** * 添加视频流,会同步修改当先视频与推送的流 * @param track */export const setTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType, needPublish = true) => {  for (const track of tracks) {    // @ts-ignore    // await runtime.mediaStreams?.addTrack(track._msTrack)    if (trackType === 'microphone') {      console.log('添加麦克风')      track.play()    }    runtime.activeTracks[trackType] = track  }  if (needPublish) {    // console.log('publish', runtime.joinedRoom)    await runtime.joinedRoom?.publish(tracks)  }}/** * 删除视频流,会同步修改当先视频与推送的流 * @param track */export const removeTrack = async (tracks: RTC.RCLocalTrack[], trackType: TrackType, needPublish = true) => {  if (needPublish) {    await runtime.joinedRoom?.unpublish(tracks)  }  for (const track of tracks) {    // @ts-ignore    // await runtime.mediaStreams?.removeTrack(track._msTrack)    // runtime.activeTracks[trackType].destroy()    // console.log(runtime.activeTracks[trackType])    track?.destroy()    runtime.activeTracks[trackType] = null  }}export const joinIMRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {  await RongIMLib.joinChatRoom(roomId, {count: -1})  const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)  if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败')  join.room?.registerRoomEventListener(listenEvents)  return join}export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {  try {    await request.get('/api-web/imLiveBroadcastRoom/joinRoom', {      params: {        roomUid: runtime.roomUid,        userId: state.user?.speakerId,      }    })  } catch (error) {}  return await joinIMRoom(roomId, type, listenEvents)}/** * 开始直播 */export const startLive = async (resetTime = true) => {  if (runtime.videoStatus !== 'stream') throw Error('当前无视频流')  const room = runtime.joinedRoom  if (room) {    // const microphoneAudioTrack = await getTrack('microphone')    // const cameraVideoTrack = await getTrack('camera')    // await setTrack([cameraVideoTrack], 'camera')    // await setTrack([microphoneAudioTrack], 'microphone')    // const builder = await runtime.joinedRoom?.getMCUConfigBuilder()    // // @ts-ignore    // await builder.setOutputVideoRenderMode?.(RTC.MixVideoRenderMode.WHOLE)    // // @ts-ignore    // await builder.flush()    runtime.videoStatus = 'liveing'  }  if (resetTime) {    sessionStorage.setItem(START_LIVE_TIME, dayjs().valueOf().toString())  }  sessionStorage.setItem(START_LIVE_STATUS, 'liveing')}/** * 关闭直播 */export const closeLive = async () => {  // removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)  // await request.post('/api-im/user/statusImUser', {  //   data: {  //     os: 'PC',  //     status: 3,  //     userId: state.user?.id,  //   }  // })  sessionStorage.removeItem(START_LIVE_TIME)  sessionStorage.removeItem(START_LIVE_STATUS)  runtime.videoStatus = 'stream'  for (const key in runtime.activeTracks) {    if (Object.prototype.hasOwnProperty.call(runtime.activeTracks, key)) {      const track = runtime.activeTracks[key as TrackType] as RTC.RCLocalTrack      if (track) {        removeTrack([track], key as TrackType)      }    }  }}/** * 同步点赞数量 */export const loopSyncLike = async () => {  if ((runtime.likeCount !== runtime.lastLikeCount || runtime.likeCount === 0) && state.user) {    try {      await request.get('/api-web/imLiveBroadcastRoom/syncLike', {        hideLoading: true,        hideMessage: true,        params: {          likeNum: runtime.likeCount,          roomUid: runtime.roomUid,        }      })      runtime.lastLikeCount = runtime.likeCount    } catch (error) {}  }  setTimeout(() => {    loopSyncLike()  }, 1000 * 60)}type SendMessageType = 'text' | 'image' | 'audio' | 'video' | 'file' | 'SeatsCtrl' | 'ChatBan' | 'SeatApply' | 'SeatResponse'export const getSendMessageUser = () => {  return {    id: state.user?.speakerId,    name: state.user?.speakerName,    userId: state.user?.speakerId,    userName: state.user?.speakerName,  }}/** * * @param msg 消息内容 * @param type 消息类型 * @returns null 或者 发送消息的结果 */export const sendMessage = async (msg: any, type: SendMessageType = 'text') => {  let message: RongIMLib.BaseMessage<unknown> | null = null  if (!msg) return  const conversation = {    conversationType: RongIMLib.ConversationType.CHATROOM,    targetId: runtime.joinedRoom?._roomId as string,  }  if (type === 'text') {    message = new RongIMLib.TextMessage({      user: getSendMessageUser(),      content: msg    })  } else if (type === 'SeatsCtrl') {    message = new MessageSeatsCtrl(msg)  } else if (type === 'ChatBan') {    message = new MessageChatBan(msg)  } else if (type === 'SeatApply') {    message = new MessageSeatApply(msg)  } else if (type === 'SeatResponse') {    message = new MessageSeatResponse(msg)  }  if (!message) return  console.log(message)  return await RongIMLib.sendMessage(conversation, message)}export const openDevice = async (trackType: TrackType, needPublish = true) => {  if (trackType === 'microphone' && runtime.activeTracks[trackType]) {    runtime.activeTracks[trackType]?.unmute()  } else {    const track = await getTrack(trackType)    await setTrack([track], trackType, needPublish)    if (runtime.videoRef) {      track.play(runtime.videoRef)    }  }}export const closeDevice = async (trackType: TrackType, needPublish = true) => {  const track = runtime.activeTracks[trackType]  if (trackType !== 'microphone') {    // console.log('closeDevice', track)    // track?.destroy()    await removeTrack([track] as RTC.RCLocalTrack[], trackType, needPublish)  } else {    track?.mute()  }}export const toggleDevice = async (trackType: TrackType) => {  const track = runtime.activeTracks[trackType]  const needPublish = runtime.videoStatus === 'liveing'  if (track) {    if (trackType === 'camera') {      runtime.deviceStatus.camera = 'closed'    }    closeDevice(trackType, needPublish)  } else {    if (trackType === 'camera') {      runtime.deviceStatus.camera = 'granted'    }    openDevice(trackType, needPublish)  }}export const leaveIMRoom = async () => {  if (runtime.joinedRoom) {    // @ts-ignore    await runtime.rtcClient?.leaveRoom(runtime.joinedRoom)    runtime.joinedRoom = null  }}
 |