tim.js 20 KB

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