runtime.ts 7.5 KB

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