runtime.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { reactive, ref, Ref } from 'vue'
  2. import * as RongIMLib from '@rongcloud/imlib-next'
  3. import * as RTC from '@rongcloud/plugin-rtc'
  4. import request from '/src/helpers/request'
  5. import event, { LIVE_EVENT_MESSAGE } from './event'
  6. import { removeMedia } from './helpers'
  7. type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
  8. type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
  9. const runtime = reactive({
  10. /** IM连接状态 */
  11. imConnectStatus: 'connecting' as imConnectStatus,
  12. // 屏幕分享状态
  13. screenShareStatus: false,
  14. // 视频节点
  15. videoRef: ref<HTMLVideoElement | null>(null),
  16. // RTC实例
  17. rtcClient: null as RTC.RCRTCClient | null,
  18. /** 加入房间实例 */
  19. joinedRoom: null as RTC.RCLivingRoom | null,
  20. // Tracks
  21. mediaStreamTrack: [] as MediaStreamTrack[],
  22. // 媒体流
  23. mediaStreams: null as MediaStream | null,
  24. // 视频状态
  25. videoStatus: 'init' as VideoStatus,
  26. // 麦克风设备列表
  27. microphones: [] as MediaDeviceInfo[],
  28. // 摄像头设备列表
  29. cameras: [] as MediaDeviceInfo[],
  30. // 摄像头设备
  31. selectedCamera: null as MediaDeviceInfo | null,
  32. // 麦克风设备
  33. selectedMicrophone: null as MediaDeviceInfo | null,
  34. // 点赞数量
  35. likeCount: 0,
  36. // 上一次点赞数量
  37. lastLikeCount: 0,
  38. /** 当前活跃的数据流 */
  39. // activeTracks null as RTC.RCLiveStream | null,
  40. })
  41. export default runtime
  42. const RONG_IM_TOKEN = 'c9kqb3rdc451j'
  43. RongIMLib.init({
  44. appkey: RONG_IM_TOKEN,
  45. })
  46. type MessageProps = {
  47. messageType: 'RC:Chatroom:Welcome' | 'RC:TxtMsg' | 'RC:Chatroom:Barrage' | 'RC:Chatroom:Like',
  48. content: any,
  49. }
  50. type MessageEvent = {
  51. messages: MessageProps[],
  52. }
  53. const Events = RongIMLib.Events
  54. /**
  55. * 监听消息通知
  56. */
  57. const { MESSAGES, ...RestMessage } = Events
  58. RongIMLib.addEventListener(Events.MESSAGES, (evt: MessageEvent) => {
  59. const { messages } = evt
  60. for (const message of messages) {
  61. if (LIVE_EVENT_MESSAGE[message.messageType]) {
  62. event.emit(LIVE_EVENT_MESSAGE[message.messageType], {...message.content, $EventMessage: message})
  63. }
  64. }
  65. })
  66. for (const Message of Object.values(RestMessage)) {
  67. RongIMLib.addEventListener(Message, () => {
  68. event.emit(Message, {$EventMessage: null})
  69. })
  70. }
  71. /**
  72. * 监听 IM 连接状态变化
  73. */
  74. RongIMLib.addEventListener(Events.CONNECTING, () => {
  75. console.log('connecting')
  76. runtime.imConnectStatus = 'connecting'
  77. })
  78. RongIMLib.addEventListener(Events.CONNECTED, () => {
  79. console.log('connected')
  80. runtime.imConnectStatus = 'connected'
  81. })
  82. RongIMLib.addEventListener(Events.DISCONNECT, () => {
  83. console.log('disconnect')
  84. runtime.imConnectStatus = 'disconnect'
  85. })
  86. export const connectIM = async (imToken: string) => {
  87. try {
  88. const user = await RongIMLib.connect(imToken)
  89. runtime.rtcClient = RongIMLib.installPlugin(RTC.installer, {})
  90. console.log('connect success', user.data?.userId)
  91. return user
  92. } catch (error) {
  93. throw error
  94. }
  95. }
  96. /**
  97. * 设置video视频流
  98. */
  99. export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams: MediaStream | null) => {
  100. if (video && mediaStreams) {
  101. video.srcObject = mediaStreams
  102. video.onloadedmetadata = () => {
  103. video.play()
  104. }
  105. }
  106. }
  107. /**
  108. * 发起屏幕共享
  109. */
  110. export const shareScreenVideo = async () => {
  111. if (runtime.rtcClient) {
  112. const screenTrack = await getTrack('screen')
  113. console.log(screenTrack)
  114. setTrack([screenTrack as RTC.RCLocalTrack])
  115. screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => {
  116. runtime.screenShareStatus = false
  117. console.log('end')
  118. // setVideoSrcObject(runtime.videoRef, this.mediaStreams)
  119. })
  120. }
  121. }
  122. /**
  123. *
  124. * 获取所有音频输入设备
  125. * @returns {Promise<void>}
  126. */
  127. export const getMicrophones = async () => {
  128. const microphones = await RTC.device.getMicrophones()
  129. runtime.microphones = microphones
  130. return microphones
  131. }
  132. /**
  133. *
  134. * 获取所有视频输入设备
  135. * @returns {Promise<void>}
  136. */
  137. export const getCameras = async () => {
  138. const cameras = await RTC.device.getCameras()
  139. runtime.cameras = cameras
  140. return cameras
  141. }
  142. /**
  143. *
  144. * 设置当前视频设备
  145. * @param camera MediaDeviceInfo
  146. */
  147. export const setSelectCamera = (camera: MediaDeviceInfo) => {
  148. runtime.selectedCamera = camera
  149. }
  150. /**
  151. *
  152. * 设置当前麦克风设备
  153. * @param microphone MediaDeviceInfo
  154. */
  155. export const setSelectMicrophone = (microphone: MediaDeviceInfo) => {
  156. runtime.selectedMicrophone = microphone
  157. }
  158. type TrackResult = {
  159. code: RTC.RCRTCCode,
  160. track: RTC.RCMicphoneAudioTrack | RTC.RCCameraVideoTrack | RTC.RCScreenVideoTrack | undefined
  161. }
  162. export const getTrack = async (trackType: 'microphone' | 'camera' | 'screen'): Promise<RTC.RCLocalTrack> => {
  163. let res: TrackResult | undefined
  164. let Track: RTC.RCLocalTrack | null = null
  165. if (trackType === 'microphone') {
  166. res = await runtime.rtcClient?.createMicrophoneAudioTrack('RongCloudRTC', {
  167. micphoneId: runtime.selectedMicrophone?.deviceId,
  168. }) as TrackResult
  169. } else if (trackType === 'camera') {
  170. res = await runtime.rtcClient?.createCameraVideoTrack('RongCloudRTC', {
  171. cameraId: runtime.selectedCamera?.deviceId,
  172. faceMode: 'user',
  173. frameRate: RTC.RCFrameRate.FPS_15,
  174. resolution: RTC.RCResolution.W1280_H720,
  175. }) as TrackResult
  176. } else {
  177. res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult
  178. }
  179. Track = res?.track as RTC.RCLocalTrack
  180. if (res.code !== RTC.RCRTCCode.SUCCESS || !Track) {
  181. throw new Error('获取数据流失败')
  182. }
  183. return Track
  184. }
  185. /**
  186. * 添加视频流,会同步修改当先视频与推送的流
  187. * @param track
  188. */
  189. export const setTrack = (tracks: [RTC.RCLocalTrack]) => {
  190. for (const track of tracks) {
  191. // @ts-ignore
  192. runtime.mediaStreams?.addTrack(track._msTrack)
  193. }
  194. runtime.joinedRoom?.publish(tracks)
  195. }
  196. /**
  197. * 删除视频流,会同步修改当先视频与推送的流
  198. * @param track
  199. */
  200. export const removeTrack = (tracks: [RTC.RCLocalTrack]) => {
  201. for (const track of tracks) {
  202. // @ts-ignore
  203. runtime.mediaStreams?.removeTrack(track._msTrack)
  204. }
  205. runtime.joinedRoom?.unpublish(tracks)
  206. }
  207. export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
  208. await RongIMLib.joinChatRoom(roomId, {count: 0})
  209. const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)
  210. if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败')
  211. join.room?.registerRoomEventListener(listenEvents)
  212. return join
  213. }
  214. /**
  215. * 开始直播
  216. */
  217. export const startLive = async () => {
  218. if (runtime.videoStatus !== 'stream') throw Error('当前无视频流')
  219. }
  220. /**
  221. * 关闭直播
  222. */
  223. export const closeLive = async () => {
  224. removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
  225. runtime.videoStatus = 'stopped'
  226. }
  227. /**
  228. * 同步点赞数量
  229. */
  230. export const loopSyncLike = async () => {
  231. if (runtime.likeCount !== runtime.lastLikeCount) {
  232. try {
  233. await request.post('/api/live/like', {})
  234. runtime.lastLikeCount = runtime.likeCount
  235. } catch (error) {}
  236. }
  237. setTimeout(() => {
  238. loopSyncLike()
  239. }, 1000 * 60 * 5)
  240. }