tim.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. import {action, observable} from 'mobx-miniprogram'
  2. import TIM from "tim-wx-sdk";
  3. import Tim from "../model/tim";
  4. import {toast} from "../utils/utils";
  5. import EduTRTC from "../model/edu-trct";
  6. import Api from "../model/api";
  7. export const timStore = observable({
  8. sdkReady: false,
  9. conversationList: [],
  10. messageList: [],
  11. maxMessageListLength: 1000,
  12. _targetUserId: null,//单聊对象id
  13. intoView: null,//结合scrollview 设置每个消息的id为xxx+index,然后设置intoView为消息长度-1,滑动到这个位置为最下方
  14. isCompleted: false,//单聊消息是否下拉完了
  15. canScroll: true,//群聊消息是否可以滑动 后期可以在触摸聊天内容的时候设置成false,在主动滑到最底部设置成true来提高用户体验
  16. user: {
  17. nickname: '',
  18. avatar: '',
  19. gender: TIM.TYPES.GENDER_MALE,
  20. },
  21. userId: '',
  22. userSig: '',
  23. groupId: '',
  24. groupReady: false,
  25. isCanShare: 0,
  26. isAudVideo: 0,
  27. isAudText: 0,
  28. isStuVideo: 0,
  29. handReply: false,
  30. //live
  31. pusher: {},
  32. playerList: [],
  33. initLive: action(function (page) {
  34. try {
  35. EduTRTC.getInstance().initPage(page);
  36. this.bindTRTCRoomEvent()
  37. this.pusher = EduTRTC.getInstance().createPusher();
  38. const config = {
  39. userID: this.userId,
  40. sdkAppID: Tim.SDKAppID,
  41. userSig: this.userSig,
  42. strRoomID: this.groupId,
  43. scene: 'live'
  44. }
  45. this.pusher = EduTRTC.getInstance().getSDK().enterRoom(config);
  46. } catch (e) {
  47. console.log("异常", e)
  48. }
  49. }),
  50. exitRoom: action(function () {
  51. const result = EduTRTC.getInstance().exitRoom()
  52. this.pusher = result.pusher;
  53. this.playerList = result.playerList;
  54. }),
  55. setScroll: action(function (canScroll) {
  56. this.canScroll = canScroll;
  57. }),
  58. setUser: action(async function (user, groupId, userSig) {
  59. if (this.userId != user.userID && this.groupId != groupId && this.sdkReady) {
  60. await this.logout()
  61. }
  62. this.user = user;
  63. this.userSig = userSig;
  64. this.userId = user.userID;
  65. this.groupId = groupId;
  66. }),
  67. login: action(async function () {
  68. if (this.sdkReady) {
  69. if (!this.groupReady) {
  70. await this.joinGroup();
  71. }
  72. return;
  73. }
  74. this._runListener()
  75. await Tim.getInstance().login(this.userId, this.userSig)
  76. }),
  77. logout: action(async function () {
  78. await this.quitGroup();
  79. await Tim.getInstance().logout()
  80. }),
  81. isReady: action(function () {
  82. return this.sdkReady
  83. }),
  84. quitGroup: action(async function () {
  85. this.groupReady = false;
  86. this.isCanShare = 0
  87. this.isAudVideo = 0
  88. this.isAudText = 0
  89. this.isStuVideo = 0
  90. await Tim.getInstance().quitGroup(this.groupId)
  91. }),
  92. getConversationList: action(async function () {
  93. this.conversationList = await Tim.getInstance().getConversationList()
  94. }),
  95. getMessageList: action(async function () {
  96. if (!this._targetUserId) {
  97. throw Error("未指定目标用户 id")
  98. }
  99. this.messageList = await Tim.getInstance()
  100. .reset()
  101. .getMessageList(this._targetUserId)
  102. this.intoView = this.messageList.length - 1
  103. await Tim.getInstance().setMessageRead(this._targetUserId)
  104. }),
  105. setTargetUserId: action(function (targetUserId) {
  106. this._targetUserId = targetUserId
  107. }),
  108. pushMessage: action(function (message) {
  109. if (this.messageList.length === this.maxMessageListLength) {
  110. this.messageList.shift()
  111. }
  112. message = {
  113. name: message.nick || message.from,
  114. message: message.payload.text
  115. }
  116. this.messageList = this.messageList.concat([message])
  117. this.intoView = this.messageList.length - 1
  118. if (this.canScroll) {
  119. wx.pageScrollTo({scrollTop: 99999})
  120. }
  121. }),
  122. scrollMessageList: action(async function () {
  123. const messageList = await Tim.getInstance()
  124. .getMessageList(this._targetUserId);
  125. this.intoView = this.messageList.length === Tim.getInstance().messageList.length ?
  126. messageList.length : messageList.length - 1
  127. /**
  128. * tips
  129. * 1. MobX 中属性的值是 Array 的时候,他是一个被包装过的 Array,并非原生 Array,它是一个响应式对象
  130. * 2. 经过包装的 Array 同样具备大多数原生 Array 所具备的方法。
  131. * 3. 想把响应式的对象数组变成普通数组,可以调用slice()函数遍历所有对象元素生成一个新的普通数组
  132. */
  133. this.messageList = messageList.concat(this.messageList.slice())
  134. }),
  135. resetMessage: action(function () {
  136. this.messageList = []
  137. this._targetUserId = null
  138. this.intoView = 0
  139. this.isCompleted = false
  140. }),
  141. _runListener() {
  142. const sdk = Tim.getInstance().getSDK();
  143. sdk.on(TIM.EVENT.SDK_READY, this.onSDKReady, this)
  144. sdk.on(TIM.EVENT.SDK_NOT_READY, this.onSdkNotReady, this);
  145. sdk.on(TIM.EVENT.KICKED_OUT, this.onSdkNotReady, this);
  146. sdk.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated, this);
  147. sdk.on(TIM.EVENT.MESSAGE_RECEIVED, this._handleMessageReceived, this);
  148. sdk.on(TIM.EVENT.ERROR, this._handleError, this);
  149. },
  150. async joinGroup() {
  151. //加入群
  152. this.resetMessage();
  153. const groupCustomField = await Tim.getInstance().joinGroup(this.groupId);
  154. if (groupCustomField) {
  155. groupCustomField.forEach(item => {
  156. this[item.key] = item.value;
  157. })
  158. }
  159. if (this.isCanShare == 1) {
  160. wx.showShareMenu();
  161. } else {
  162. wx.hideShareMenu();
  163. }
  164. this.groupReady = true;
  165. this.pushMessage({
  166. nick: '系统通知',
  167. payload: {
  168. text: `欢迎${this.user.nickname}加入`
  169. }
  170. })
  171. },
  172. async onConversationListUpdated(event) {
  173. if (!event.data.length) {
  174. return
  175. }
  176. this.conversationList = event.data
  177. const unreadCount = event.data.reduce(
  178. (sum, item) => sum + item.unreadCount, 0)
  179. },
  180. async onSDKReady() {
  181. this.sdkReady = true
  182. //SDK准备好了 更新用户信息
  183. await Tim.getInstance().updateMyProfile(this.user)
  184. await this.joinGroup();
  185. },
  186. onSdkNotReady() {
  187. this.sdkReady = false
  188. this.groupReady = false
  189. this.isCanShare = 0
  190. this.isAudVideo = 0
  191. this.isAudText = 0
  192. this.isStuVideo = 0
  193. const sdk = Tim.getInstance().getSDK()
  194. sdk.off(TIM.EVENT.SDK_READY, this.onSDKReady);
  195. sdk.off(TIM.EVENT.SDK_NOT_READY, this.onSdkNotReady);
  196. sdk.off(TIM.EVENT.KICKED_OUT, this.onSdkNotReady);
  197. sdk.off(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated);
  198. sdk.off(TIM.EVENT.MESSAGE_RECEIVED, this._handleMessageReceived);
  199. sdk.off(TIM.EVENT.ERROR, this._handleError);
  200. },
  201. async _handleMessageReceived(event) {
  202. console.log("收到消息", event)
  203. const filterMsgs = event.data
  204. .filter(item => (item.to === this.groupId || item.to === this.userId));
  205. for (const message of filterMsgs) {
  206. if (message.type == TIM.TYPES.MSG_GRP_SYS_NOTICE) {
  207. let text = this.parseGroupSystemNotice(message.payload);
  208. message.nick = "系统通知"
  209. message.payload.text = text
  210. console.log(text);
  211. }
  212. if (message.type == TIM.TYPES.MSG_GRP_TIP) {
  213. let text = this.parseGroupTipContent(message.payload);
  214. message.nick = "系统提示"
  215. message.payload.text = text
  216. console.log(text);
  217. }
  218. if (message.type === TIM.TYPES.MSG_GRP_TIP) {
  219. let groupCustomField = message.payload?.newGroupProfile?.groupCustomField;
  220. if (groupCustomField) {
  221. groupCustomField.forEach(item => {
  222. console.log('收到群资料变更', item.key, item.value);
  223. this[item.key] = item.value;
  224. })
  225. }
  226. if (this.isCanShare == 1) {
  227. wx.showShareMenu();
  228. } else {
  229. wx.hideShareMenu();
  230. }
  231. }
  232. if (message.type === TIM.TYPES.MSG_CUSTOM) {
  233. await this.parseGroupCustom(message.payload);
  234. }
  235. if (message.type === TIM.TYPES.MSG_TEXT) {
  236. this.pushMessage(message)
  237. }
  238. }
  239. // if (!this._targetUserId) {
  240. // return
  241. // }
  242. //
  243. // const currentConversationMessage = event.data
  244. // .filter(item => item.from === this._targetUserId)
  245. // if (currentConversationMessage.length) {
  246. // this.messageList = this.messageList.concat(currentConversationMessage)
  247. // this.intoView = this.messageList.length - 1
  248. // await Tim.getInstance().setMessageRead(this._targetUserId)
  249. // }
  250. },
  251. _handleError(event) {
  252. console.log("IM错误", event)
  253. // event.name - TIM.EVENT.ERROR
  254. // event.data.code - 错误码
  255. // event.data.message - 错误信息
  256. },
  257. parseGroupSystemNotice(payload) {
  258. const groupName =
  259. payload.groupProfile.groupName || payload.groupProfile.groupID
  260. switch (payload.operationType) {
  261. case 1:
  262. return `${payload.operatorID} 申请加入群组:${groupName}`
  263. case 2:
  264. return `成功加入群组:${groupName}`
  265. case 3:
  266. return `申请加入群组:${groupName}被拒绝`
  267. case 4:
  268. return `被管理员${payload.operatorID}踢出群组:${groupName}`
  269. case 5:
  270. toast('直播结束');
  271. wx.navigateBack();
  272. return `群:${groupName} 已被${payload.operatorID}解散`
  273. case 6:
  274. return `${payload.operatorID}创建群:${groupName}`
  275. case 7:
  276. return `${payload.operatorID}邀请你加群:${groupName}`
  277. case 8:
  278. return `你退出群组:${groupName}`
  279. case 9:
  280. return `你被${payload.operatorID}设置为群:${groupName}的管理员`
  281. case 10:
  282. return `你被${payload.operatorID}撤销群:${groupName}的管理员身份`
  283. case 255:
  284. return '自定义群系统通知'
  285. }
  286. },
  287. parseGroupTipContent(payload) {
  288. switch (payload.operationType) {
  289. case TIM.TYPES.GRP_TIP_MBR_PROFILE_UPDATED: // 群成员资料变更,例如:群成员被禁言
  290. const memberList = message.payload.memberList;
  291. for (let member of memberList) {
  292. console.log(`${member.userID} 被禁言${member.muteTime}秒`);
  293. }
  294. break;
  295. case TIM.TYPES.GRP_TIP_MBR_JOIN:
  296. return `群成员:${payload.userIDList.join(',')},加入群组`
  297. case TIM.TYPES.GRP_TIP_MBR_QUIT:
  298. return `群成员:${payload.userIDList.join(',')},退出群组`
  299. case TIM.TYPES.GRP_TIP_MBR_KICKED_OUT:
  300. return `群成员:${payload.userIDList.join(',')},被${payload.operatorID}踢出群组`
  301. case TIM.TYPES.GRP_TIP_MBR_SET_ADMIN:
  302. return `群成员:${payload.userIDList.join(',')},成为管理员`
  303. case TIM.TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
  304. return `群成员:${payload.userIDList.join(',')},被撤销管理员`
  305. default:
  306. return '[群提示消息]'
  307. }
  308. },
  309. async parseGroupCustom(payload) {
  310. let data = JSON.parse(payload.data);
  311. let to = data.to;
  312. let version = data.version;
  313. let action = data.action;
  314. if (to == this.userId) {
  315. if (version == Tim.VERSION) {
  316. switch (action) {
  317. case Tim.IM_ACTION_HAND:
  318. const res = await wx.showModal({
  319. title: "老师邀请你上麦",
  320. confirmText: '同意',
  321. cancelText: '拒绝'
  322. })
  323. if (res.confirm) {
  324. await Tim.getInstance().sendCMD(Tim.getInstance().createHandOKMsg(data.time, this.groupId));
  325. this.setPusherAttributesHandler({enableCamera: true, enableMic: true})
  326. this.uploadLink();
  327. } else {
  328. await Tim.getInstance().sendCMD(Tim.getInstance().createHandCancelMsg(data.time, this.groupId));
  329. this.setPusherAttributesHandler({enableCamera: false, enableMic: false})
  330. }
  331. break
  332. case Tim.IM_ACTION_HAND_OK:
  333. toast("老师同意连麦")
  334. this.setPusherAttributesHandler({enableCamera: true, enableMic: true})
  335. this.uploadLink();
  336. break
  337. case Tim.IM_ACTION_HAND_CANCEL:
  338. toast("老师拒绝连麦")
  339. this.setPusherAttributesHandler({enableCamera: false, enableMic: false})
  340. break
  341. case Tim.IM_ACTION_QUIT_LINK:
  342. this.setPusherAttributesHandler({enableCamera: false, enableMic: false})
  343. toast("您已被老师下麦");
  344. break
  345. }
  346. }
  347. }
  348. },
  349. uploadLink(){
  350. try {
  351. Api.uploadLinkOk({
  352. scheduleIdStr: this.groupId,
  353. eStuIdStr: this.userId.split("_")[1],
  354. });
  355. }catch (e){
  356. console.log(e)
  357. }
  358. },
  359. bindTRTCRoomEvent() {
  360. let sdk = EduTRTC.getInstance().getSDK();
  361. const TRTC_EVENT = sdk.EVENT
  362. // 初始化事件订阅
  363. sdk.on(TRTC_EVENT.LOCAL_JOIN, (event) => {
  364. console.log('本地加入房间', event)
  365. })
  366. sdk.on(TRTC_EVENT.LOCAL_LEAVE, (event) => {
  367. console.log('本地离开房间', event)
  368. })
  369. sdk.on(TRTC_EVENT.KICKED_OUT, (event) => {
  370. console.log('服务端踢人或房间被解散退房', event)
  371. })
  372. sdk.on(TRTC_EVENT.ERROR, (event) => {
  373. console.log('TRTC错误', event)
  374. })
  375. sdk.on(TRTC_EVENT.REMOTE_USER_JOIN, (event) => {
  376. console.log('远端用户加入', event)
  377. })
  378. sdk.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event) => {
  379. console.log('远端用户离开', event)
  380. const {userID, playerList} = event.data
  381. this.playerList = event.data.playerList
  382. })
  383. sdk.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event) => {
  384. console.log('远端用户推送视频', event)
  385. const {player} = event.data
  386. // 开始播放远端的视频流,默认是不播放的
  387. this.setPlayerAttributesHandler(player, {muteVideo: false})
  388. })
  389. sdk.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event) => {
  390. console.log('远端用户取消推送视频', event)
  391. const {player} = event.data
  392. this.setPlayerAttributesHandler(player, {muteVideo: true})
  393. })
  394. sdk.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event) => {
  395. console.log('远端用户推送音频', event)
  396. const {player} = event.data
  397. this.setPlayerAttributesHandler(player, {muteAudio: false})
  398. })
  399. sdk.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event) => {
  400. console.log('远端用户取消推送音频', event)
  401. const {player} = event.data
  402. this.setPlayerAttributesHandler(player, {muteAudio: true})
  403. })
  404. sdk.on(TRTC_EVENT.REMOTE_AUDIO_VOLUME_UPDATE, (event) => {
  405. console.log('远端用户音频大小更新', event)
  406. this.playerList = event.data.playerList
  407. })
  408. sdk.on(TRTC_EVENT.LOCAL_AUDIO_VOLUME_UPDATE, (event) => {
  409. console.log('本地用户音频大小更新', event)
  410. this.pusher = event.data.pusher
  411. })
  412. },
  413. // 设置 pusher 属性
  414. setPusherAttributesHandler(options) {
  415. let sdk = EduTRTC.getInstance().getSDK();
  416. this.pusher = sdk.setPusherAttributes(options);
  417. this.handReply = !this.handReply;
  418. },
  419. // 设置某个 player 属性
  420. setPlayerAttributesHandler(player, options) {
  421. let sdk = EduTRTC.getInstance().getSDK();
  422. this.playerList = sdk.setPlayerAttributes(player.streamID, options);
  423. },
  424. })