user-controller.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import Event from '../utils/event.js'
  2. import User from '../model/user.js'
  3. import Stream from '../model/stream.js'
  4. import { EVENT } from '../common/constants.js'
  5. const TAG_NAME = 'UserController'
  6. /**
  7. * 通讯成员管理
  8. */
  9. class UserController {
  10. constructor(componentContext) {
  11. // userMap 用于存储完整的数据结构
  12. this.userMap = new Map()
  13. // userList 用于存储简化的用户数据 Object,包括 {userID hasMainAudio hasMainVideo hasAuxAudio hasAuxVideo}
  14. this.userList = []
  15. // streamList 存储steam 对象列表,用于 trtc-room 渲染 player
  16. this.streamList = []
  17. this._emitter = new Event()
  18. this.componentContext = componentContext
  19. this.isNewVersion = componentContext.isNewVersion
  20. }
  21. userEventHandler(event) {
  22. const code = event.detail.code
  23. let data
  24. if (event.detail.message && typeof event.detail.message === 'string') {
  25. try {
  26. data = JSON.parse(event.detail.message)
  27. } catch (exception) {
  28. console.warn(TAG_NAME, 'userEventHandler 数据格式错误', exception)
  29. return false
  30. }
  31. } else {
  32. console.warn(TAG_NAME, 'userEventHandler 数据格式错误')
  33. return false
  34. }
  35. switch (code) {
  36. case 1020:
  37. // console.log(TAG_NAME, '远端用户全量列表更新:', code)
  38. if (!this.isNewVersion) {
  39. // TODO 旧版SDK处理逻辑,返回全量的用户列表,需要对userList 进行前后对比,筛选出新增用户,暂不实现
  40. }
  41. break
  42. case 1031:
  43. // console.log(TAG_NAME, '远端用户进房通知:', code)
  44. // 1031 有新用户
  45. // {
  46. // "userlist":[
  47. // {
  48. // "userid":"webrtc11"
  49. // }
  50. // ]
  51. // }
  52. this.addUser(data)
  53. break
  54. case 1032:
  55. // console.log(TAG_NAME, '远端用户退房通知:', code)
  56. // 1032 有用户退出
  57. this.removeUser(data)
  58. break
  59. case 1033:
  60. // console.log(TAG_NAME, '远端用户视频状态位变化通知:', code)
  61. // 1033 用户视频状态变化,新增stream或者更新stream 状态
  62. // {
  63. // "userlist":[
  64. // {
  65. // "userid":"webrtc11",
  66. // "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
  67. // "streamtype":"main",
  68. // "hasvideo":true
  69. // }
  70. // ]
  71. // }
  72. this.updateUserVideo(data)
  73. break
  74. case 1034:
  75. // console.log(TAG_NAME, '远端用户音频状态位变化通知:', code)
  76. // 1034 用户音频状态变化
  77. // {
  78. // "userlist":[
  79. // {
  80. // "userid":"webrtc11",
  81. // "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
  82. // "hasaudio":false
  83. // }
  84. // ]
  85. // }
  86. this.updateUserAudio(data)
  87. break
  88. }
  89. }
  90. /**
  91. * 处理用户进房事件
  92. * @param {Object} data pusher 下发的数据
  93. */
  94. addUser(data) {
  95. // console.log(TAG_NAME, 'addUser', data)
  96. const incomingUserList = data.userlist
  97. const userMap = this.userMap
  98. if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
  99. incomingUserList.forEach((item) => {
  100. const userID = item.userid
  101. // 已经在 map 中的用户
  102. let user = this.getUser(userID)
  103. if (!user) {
  104. // 新增用户
  105. user = new User({ userID: userID })
  106. this.userList.push({
  107. userID: userID,
  108. })
  109. }
  110. userMap.set(userID, user)
  111. this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: userID, userList: this.userList })
  112. // console.log(TAG_NAME, 'addUser', item, userMap.get(userID), this.userMap)
  113. })
  114. }
  115. }
  116. /**
  117. * 处理用户退房事件
  118. * @param {Object} data pusher 下发的数据 {userlist}
  119. */
  120. removeUser(data) {
  121. // console.log(TAG_NAME, 'removeUser', data)
  122. const incomingUserList = data.userlist
  123. if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
  124. incomingUserList.forEach((item) => {
  125. const userID = item.userid
  126. let user = this.getUser(userID)
  127. // 偶现SDK触发退房事件前没有触发进房事件
  128. if (!user || !user.streams) {
  129. return
  130. }
  131. // 从userList 里删除指定的用户和 stream
  132. this._removeUserAndStream(userID)
  133. // 重置
  134. user.streams['main'] && user.streams['main'].reset()
  135. user.streams['aux'] && user.streams['aux'].reset()
  136. // 用户退出,释放引用,外部调用该 user 所有stream 的 playerContext.stop() 方法停止播放
  137. // TODO 触发时机提前了,方便外部用户做出处理,时机仍需进一步验证
  138. this._emitter.emit(EVENT.REMOTE_USER_LEAVE, { userID: userID, userList: this.userList, streamList: this.streamList })
  139. user = undefined
  140. this.userMap.delete(userID)
  141. // console.log(TAG_NAME, 'removeUser', this.userMap)
  142. })
  143. }
  144. }
  145. /**
  146. * 处理用户视频通知事件
  147. * @param {Object} data pusher 下发的数据 {userlist}
  148. */
  149. updateUserVideo(data) {
  150. console.log(TAG_NAME, 'updateUserVideo', data)
  151. const incomingUserList = data.userlist
  152. if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
  153. incomingUserList.forEach((item) => {
  154. const userID = item.userid
  155. const streamType = item.streamtype
  156. const streamID = userID + '_' + streamType
  157. const hasVideo = item.hasvideo
  158. const src = item.playurl
  159. const user = this.getUser(userID)
  160. // 更新指定用户的属性
  161. if (user) {
  162. // 查找对应的 stream
  163. let stream = user.streams[streamType]
  164. console.log(TAG_NAME, 'updateUserVideo start', user, streamType, stream)
  165. // 常规逻辑
  166. // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户(有音频或视频)的 stream
  167. if (!stream) {
  168. // 不在 user streams 里,需要新建
  169. user.streams[streamType] = stream = new Stream({ userID, streamID, hasVideo, src, streamType })
  170. this._addStream(stream)
  171. } else {
  172. // 更新 stream 属性
  173. stream.setProperty({ hasVideo })
  174. if (!hasVideo && !stream.hasAudio) {
  175. this._removeStream(stream)
  176. }
  177. // or
  178. // if (hasVideo) {
  179. // stream.setProperty({ hasVideo })
  180. // } else if (!stream.hasAudio) {
  181. // // hasVideo == false && hasAudio == false
  182. // this._removeStream(stream)
  183. // }
  184. }
  185. // 特殊逻辑
  186. if (streamType === 'aux') {
  187. if (hasVideo) {
  188. // 辅流需要修改填充模式
  189. stream.objectFit = 'contain'
  190. this._addStream(stream)
  191. } else {
  192. // 如果是辅流要移除该 stream,否则需要移除 player
  193. this._removeStream(stream)
  194. }
  195. }
  196. // 更新所属user 的 hasXxx 值
  197. this.userList.find((item)=>{
  198. if (item.userID === userID) {
  199. item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Video`] = hasVideo
  200. return true
  201. }
  202. })
  203. console.log(TAG_NAME, 'updateUserVideo end', user, streamType, stream)
  204. const eventName = hasVideo ? EVENT.REMOTE_VIDEO_ADD : EVENT.REMOTE_VIDEO_REMOVE
  205. this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
  206. // console.log(TAG_NAME, 'updateUserVideo', user, stream, this.userMap)
  207. }
  208. })
  209. }
  210. }
  211. /**
  212. * 处理用户音频通知事件
  213. * @param {Object} data pusher 下发的数据 {userlist}
  214. */
  215. updateUserAudio(data) {
  216. // console.log(TAG_NAME, 'updateUserAudio', data)
  217. const incomingUserList = data.userlist
  218. if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
  219. incomingUserList.forEach((item) => {
  220. const userID = item.userid
  221. // 音频只跟着 stream main ,这里只修改 main
  222. const streamType = 'main'
  223. const streamID = userID + '_' + streamType
  224. const hasAudio = item.hasaudio
  225. const src = item.playurl
  226. const user = this.getUser(userID)
  227. if (user) {
  228. let stream = user.streams[streamType]
  229. // if (!stream) {
  230. // user.streams[streamType] = stream = new Stream({ streamType: streamType })
  231. // this._addStream(stream)
  232. // }
  233. // 常规逻辑
  234. // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户的 stream
  235. if (!stream) {
  236. // 不在 user streams 里,需要新建
  237. user.streams[streamType] = stream = new Stream({ userID, streamID, hasAudio, src, streamType })
  238. this._addStream(stream)
  239. } else {
  240. // 更新 stream 属性
  241. stream.setProperty({ hasAudio })
  242. if (!hasAudio && !stream.hasVideo) {
  243. this._removeStream(stream)
  244. }
  245. // or
  246. // if (hasAudio) {
  247. // stream.setProperty({ hasAudio })
  248. // } else if (!stream.hasVideo) {
  249. // // hasVideo == false && hasAudio == false
  250. // this._removeStream(stream)
  251. // }
  252. }
  253. // stream.userID = userID
  254. // stream.streamID = userID + '_' + streamType
  255. // stream.hasAudio = hasAudio
  256. // stream.src = src
  257. // 更新所属 user 的 hasXxx 值
  258. this.userList.find((item)=>{
  259. if (item.userID === userID) {
  260. item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Audio`] = hasAudio
  261. return true
  262. }
  263. })
  264. const eventName = hasAudio ? EVENT.REMOTE_AUDIO_ADD : EVENT.REMOTE_AUDIO_REMOVE
  265. this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
  266. // console.log(TAG_NAME, 'updateUserAudio', user, stream, this.userMap)
  267. }
  268. })
  269. }
  270. }
  271. /**
  272. *
  273. * @param {String} userID 用户ID
  274. * @returns {Object}
  275. */
  276. getUser(userID) {
  277. return this.userMap.get(userID)
  278. }
  279. getStream({ userID, streamType }) {
  280. const user = this.userMap.get(userID)
  281. if (user) {
  282. return user.streams[streamType]
  283. }
  284. return undefined
  285. }
  286. getUserList() {
  287. return this.userList
  288. }
  289. getStreamList() {
  290. return this.streamList
  291. }
  292. /**
  293. * 重置所有user 和 steam
  294. * @returns {Object}
  295. */
  296. reset() {
  297. this.streamList.forEach((item)=>{
  298. item.reset()
  299. })
  300. this.streamList = []
  301. this.userList = []
  302. this.userMap.clear()
  303. return {
  304. userList: this.userList,
  305. streamList: this.streamList,
  306. }
  307. }
  308. on(eventCode, handler, context) {
  309. this._emitter.on(eventCode, handler, context)
  310. }
  311. off(eventCode, handler) {
  312. this._emitter.off(eventCode, handler)
  313. }
  314. /**
  315. * 删除用户和所有的 stream
  316. * @param {String} userID 用户ID
  317. */
  318. _removeUserAndStream(userID) {
  319. this.streamList = this.streamList.filter((item)=>{
  320. return item.userID !== userID && item.userID !== ''
  321. })
  322. this.userList = this.userList.filter((item)=>{
  323. return item.userID !== userID
  324. })
  325. }
  326. _addStream(stream) {
  327. if (!this.streamList.includes(stream)) {
  328. this.streamList.push(stream)
  329. }
  330. }
  331. _removeStream(stream) {
  332. this.streamList = this.streamList.filter((item)=>{
  333. if (item.userID === stream.userID && item.streamType === stream.streamType) {
  334. return false
  335. }
  336. return true
  337. })
  338. const user = this.getUser(stream.userID)
  339. user.streams[stream.streamType] = undefined
  340. }
  341. }
  342. export default UserController