ssfg před 5 roky
rodič
revize
a6f937b074
71 změnil soubory, kde provedl 4906 přidání a 231 odebrání
  1. 85 0
      components/trtc-room/common/constants.js
  2. 346 0
      components/trtc-room/controller/user-controller.js
  3. 14 0
      components/trtc-room/libs/mta_analysis.js
  4. 1 0
      components/trtc-room/libs/tim-wx.js
  5. 33 0
      components/trtc-room/model/pusher.js
  6. 38 0
      components/trtc-room/model/stream.js
  7. 17 0
      components/trtc-room/model/user.js
  8. binární
      components/trtc-room/static/audio-active.png
  9. binární
      components/trtc-room/static/audio-false.png
  10. binární
      components/trtc-room/static/audio-true.png
  11. binární
      components/trtc-room/static/back.png
  12. binární
      components/trtc-room/static/beauty-false.png
  13. binární
      components/trtc-room/static/beauty-true.png
  14. binární
      components/trtc-room/static/beauty.png
  15. binární
      components/trtc-room/static/camera-false.png
  16. binární
      components/trtc-room/static/camera-true.png
  17. binární
      components/trtc-room/static/camera.png
  18. binární
      components/trtc-room/static/close-white-big.png
  19. binární
      components/trtc-room/static/close-white.png
  20. binární
      components/trtc-room/static/display-pause-false.png
  21. binární
      components/trtc-room/static/display-pause-true.png
  22. binární
      components/trtc-room/static/display-pause-white.png
  23. binární
      components/trtc-room/static/display-play-false.png
  24. binární
      components/trtc-room/static/display-play-true.png
  25. binární
      components/trtc-room/static/display-play-white.png
  26. binární
      components/trtc-room/static/fullscreen-white.png
  27. binární
      components/trtc-room/static/fullscreen.png
  28. binární
      components/trtc-room/static/hangup-red.png
  29. binární
      components/trtc-room/static/hangup.png
  30. binární
      components/trtc-room/static/im-disable.png
  31. binární
      components/trtc-room/static/im-white.png
  32. binární
      components/trtc-room/static/im.png
  33. binární
      components/trtc-room/static/list-white.png
  34. binární
      components/trtc-room/static/list.png
  35. binární
      components/trtc-room/static/loading.png
  36. binární
      components/trtc-room/static/micro-open.png
  37. binární
      components/trtc-room/static/more-disable.png
  38. binární
      components/trtc-room/static/more-enable.png
  39. binární
      components/trtc-room/static/more-white.png
  40. binární
      components/trtc-room/static/music-white.png
  41. binární
      components/trtc-room/static/mute-camera-gray.png
  42. binární
      components/trtc-room/static/mute-camera-white.png
  43. binární
      components/trtc-room/static/mute-mic-gray.png
  44. binární
      components/trtc-room/static/mute-mic-white.png
  45. binární
      components/trtc-room/static/phone.png
  46. binární
      components/trtc-room/static/setting-white.png
  47. binární
      components/trtc-room/static/setting.png
  48. binární
      components/trtc-room/static/slide-up.png
  49. binární
      components/trtc-room/static/speaker-false.png
  50. binární
      components/trtc-room/static/speaker-true.png
  51. binární
      components/trtc-room/static/speaker-white.png
  52. binární
      components/trtc-room/static/switch.png
  53. 104 0
      components/trtc-room/template/1v1/1v1.wxml
  54. 112 0
      components/trtc-room/template/1v1/1v1.wxss
  55. 75 0
      components/trtc-room/template/custom/custom.wxml
  56. 11 0
      components/trtc-room/template/custom/custom.wxss
  57. 261 0
      components/trtc-room/template/grid/grid.wxml
  58. 563 0
      components/trtc-room/template/grid/grid.wxss
  59. 2407 0
      components/trtc-room/trtc-room.js
  60. 4 0
      components/trtc-room/trtc-room.json
  61. 70 0
      components/trtc-room/trtc-room.wxml
  62. 228 0
      components/trtc-room/trtc-room.wxss
  63. 21 0
      components/trtc-room/utils/compare-version.js
  64. 50 0
      components/trtc-room/utils/environment.js
  65. 62 0
      components/trtc-room/utils/event.js
  66. 334 225
      pages/zbDetails/zbDetails.js
  67. 4 2
      pages/zbDetails/zbDetails.json
  68. 5 4
      pages/zbDetails/zbDetails.wxml
  69. 56 0
      utils/getUserSig.js
  70. 2 0
      utils/lib-generate-test-usersig-es.min.js
  71. 3 0
      utils/util.js

+ 85 - 0
components/trtc-room/common/constants.js

@@ -0,0 +1,85 @@
+export const EVENT = {
+  LOCAL_JOIN: 'LOCAL_JOIN', // 本地进房成功
+  LOCAL_LEAVE: 'LOCAL_LEAVE', // 本地退房
+  REMOTE_USER_JOIN: 'REMOTE_USER_JOIN', // 远端用户进房
+  REMOTE_USER_LEAVE: 'REMOTE_USER_LEAVE', // 远端用户退房
+  REMOTE_VIDEO_ADD: 'REMOTE_VIDEO_ADD', // 远端视频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_VIDEO_REMOVE: 'REMOTE_VIDEO_REMOVE', // 远端视频流移出事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_ADD: 'REMOTE_AUDIO_ADD', // 远端音频流添加事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_AUDIO_REMOVE: 'REMOTE_AUDIO_REMOVE', // 远端音频流移除事件,当远端用户取消发布音频流后会收到该通知
+  REMOTE_STATE_UPDATE: 'REMOTE_STATE_UPDATE', // 远端用户播放状态变更
+  LOCAL_NET_STATE_UPDATE: 'LOCAL_NET_STATE_UPDATE', // 本地推流网络状态变更
+  REMOTE_NET_STATE_UPDATE: 'REMOTE_NET_STATE_UPDATE', // 远端用户网络状态变更
+  LOCAL_AUDIO_VOLUME_UPDATE: 'LOCAL_AUDIO_VOLUME_UPDATE', // 本地音量变更
+  REMOTE_AUDIO_VOLUME_UPDATE: 'REMOTE_AUDIO_VOLUME_UPDATE', // 远端用户音量变更
+  VIDEO_FULLSCREEN_UPDATE: 'VIDEO_FULLSCREEN_UPDATE', // 调用 player requestFullScreen 或者 exitFullScreen 后触发
+  BGM_PLAY_START: 'BGM_PLAY_START', // 调用 LivePusherContext.playBGM(Object object)
+  BGM_PLAY_FAIL: 'BGM_PLAY_FAIL', //
+  BGM_PLAY_PROGRESS: 'BGM_PLAY_PROGRESS', // bgm 播放时间戳变更
+  BGM_PLAY_COMPLETE: 'BGM_PLAY_COMPLETE', // bgm 播放结束 或者 调用 LivePusherContext.stopBGM() ?
+  ERROR: 'ERROR', // pusher 出现错误
+  IM_READY: 'IM_READY', // IM SDK 可用
+  IM_MESSAGE_RECEIVED: 'IM_MESSAGE_RECEIVED', // 收到IM 消息
+  IM_NOT_READY: 'IM_NOT_READY', // IM SDK 不可用
+  IM_KICKED_OUT: 'IM_KICKED_OUT', // IM SDK 下线
+  IM_ERROR: 'IM_ERROR', // IM SDK 下线
+}
+
+export const DEFAULT_COMPONENT_CONFIG = {
+  sdkAppID: '',
+  userID: '',
+  userSig: '',
+  template: '',
+  debugMode: false, // 是否开启调试模式
+  enableIM: false, // 是否开启 IM
+}
+
+export const DEFAULT_PUSHER_CONFIG = {
+  url: '',
+  mode: 'RTC', // RTC:实时通话(trtc sdk) live:直播模式(liteav sdk)
+  autopush: false, // 自动推送
+  enableCamera: false, // 是否开启摄像头
+  enableMic: false, // 是否开启麦克风
+  enableAgc: false, // 是否开启音频自动增益
+  enableAns: false, // 是否开启音频噪声抑制
+  enableEarMonitor: false, // 是否开启耳返(目前只在iOS平台有效)
+  enableAutoFocus: true, // 是否自动对焦
+  enableZoom: false, // 是否支持调整焦距
+  minBitrate: 600, // 最小码率
+  maxBitrate: 900, // 最大码率
+  videoWidth: 360, // 视频宽(若设置了视频宽高就会忽略aspect)
+  videoHeight: 640, // 视频高(若设置了视频宽高就会忽略aspect)
+  beautyLevel: 0, // 美颜,取值范围 0-9 ,0 表示关闭
+  whitenessLevel: 0, // 美白,取值范围 0-9 ,0 表示关闭
+  videoOrientation: 'vertical', // vertical horizontal
+  videoAspect: '9:16', // 宽高比,可选值有 3:4,9:16
+  frontCamera: 'front', // 前置或后置摄像头,可选值:front,back
+  enableRemoteMirror: false, // 设置推流画面是否镜像,产生的效果会表现在 live-player
+  localMirror: 'auto', // auto:前置摄像头镜像,后置摄像头不镜像(系统相机的表现)enable:前置摄像头和后置摄像头都镜像 disable: 前置摄像头和后置摄像头都不镜像
+  enableBackgroundMute: false, // 进入后台时是否静音
+  audioQuality: 'high', // 高音质(48KHz)或低音质(16KHz),可选值:high,low
+  audioVolumeType: 'voicecall', // 声音类型 可选值: media: 媒体音量,voicecall: 通话音量
+  audioReverbType: 0, // 音频混响类型 0: 关闭 1: KTV 2: 小房间 3:大会堂 4:低沉 5:洪亮 6:金属声 7:磁性
+  // waitingImage: 'https://web-player-1252463788.cos.ap-shanghai.myqcloud.com/demo/1px.png', // 当微信切到后台时的垫片图片 trtc暂不支持
+  waitingImage: 'https://mc.qcloudimg.com/static/img/daeed8616ac5df256c0591c22a65c4d3/pause_publish.jpg', // 当微信切到后台时的垫片图片 trtc暂不支持
+  waitingImageHash: '',
+  beautyStyle: 'smooth', // 美颜类型,取值有:smooth: 光滑 、nature: 自然
+  filter: '', // standard: 标准 pink: 粉嫩 nostalgia: 怀旧 blues: 蓝调 romantic: 浪漫  cool: 清凉 fresher: 清新 solor: 日系 aestheticism: 唯美 whitening:美白 cerisered: 樱红
+}
+
+export const DEFAULT_PLAYER_CONFIG = {
+  src: '',
+  mode: 'RTC',
+  autoplay: true, // 7.0.9 必须设置为true,否则 Android 有概率调用play()失败
+  muteAudio: true, // 默认不拉取音频,需要手动订阅,如果要快速播放,需要设置false
+  muteVideo: true, // 默认不拉取视频,需要手动订阅,如果要快速播放,需要设置false
+  orientation: 'vertical', // 画面方向 vertical horizontal
+  objectFit: 'fillCrop', // 填充模式,可选值有 contain,fillCrop
+  enableBackgroundMute: false, // 进入后台时是否静音(已废弃,默认退台静音)
+  minCache: 1, // 最小缓冲区,单位s(RTC 模式推荐 0.2s)
+  maxCache: 2, // 最大缓冲区,单位s(RTC 模式推荐 0.8s)
+  soundMode: 'speaker', // 声音输出方式 ear speaker
+  enableRecvMessage: 'false', // 是否接收SEI消息
+  autoPauseIfNavigate: true, // 当跳转到其它小程序页面时,是否自动暂停本页面的实时音视频播放
+  autoPauseIfOpenNative: true, // 当跳转到其它微信原生页面时,是否自动暂停本页面的实时音视频播放
+}

+ 346 - 0
components/trtc-room/controller/user-controller.js

@@ -0,0 +1,346 @@
+import Event from '../utils/event.js'
+import User from '../model/user.js'
+import Stream from '../model/stream.js'
+import { EVENT } from '../common/constants.js'
+
+const TAG_NAME = 'UserController'
+/**
+ * 通讯成员管理
+ */
+class UserController {
+  constructor(componentContext) {
+    // userMap 用于存储完整的数据结构
+    this.userMap = new Map()
+    // userList 用于存储简化的用户数据 Object,包括 {userID hasMainAudio hasMainVideo hasAuxAudio hasAuxVideo}
+    this.userList = []
+    // streamList 存储steam 对象列表,用于 trtc-room 渲染 player
+    this.streamList = []
+    this._emitter = new Event()
+    this.componentContext = componentContext
+    this.isNewVersion = componentContext.isNewVersion
+  }
+  userEventHandler(event) {
+    const code = event.detail.code
+    let data
+    if (event.detail.message && typeof event.detail.message === 'string') {
+      try {
+        data = JSON.parse(event.detail.message)
+      } catch (exception) {
+        console.warn(TAG_NAME, 'userEventHandler 数据格式错误', exception)
+        return false
+      }
+    } else {
+      console.warn(TAG_NAME, 'userEventHandler 数据格式错误')
+      return false
+    }
+    switch (code) {
+      case 1020:
+        // console.log(TAG_NAME, '远端用户全量列表更新:', code)
+        if (!this.isNewVersion) {
+          // TODO 旧版SDK处理逻辑,返回全量的用户列表,需要对userList 进行前后对比,筛选出新增用户,暂不实现
+        }
+        break
+      case 1031:
+        // console.log(TAG_NAME, '远端用户进房通知:', code)
+        // 1031 有新用户
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11"
+        //          }
+        //      ]
+        // }
+        this.addUser(data)
+        break
+      case 1032:
+        // console.log(TAG_NAME, '远端用户退房通知:', code)
+        // 1032 有用户退出
+        this.removeUser(data)
+        break
+      case 1033:
+        // console.log(TAG_NAME, '远端用户视频状态位变化通知:', code)
+        // 1033 用户视频状态变化,新增stream或者更新stream 状态
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "streamtype":"main",
+        //              "hasvideo":true
+        //          }
+        //      ]
+        // }
+        this.updateUserVideo(data)
+        break
+      case 1034:
+        // console.log(TAG_NAME, '远端用户音频状态位变化通知:', code)
+        // 1034 用户音频状态变化
+        // {
+        //   "userlist":[
+        //          {
+        //              "userid":"webrtc11",
+        //              "playurl":" room://rtc.tencent.com?userid=xxx&streamtype=main",
+        //              "hasaudio":false
+        //          }
+        //      ]
+        // }
+        this.updateUserAudio(data)
+        break
+    }
+  }
+  /**
+   * 处理用户进房事件
+   * @param {Object} data pusher 下发的数据
+   */
+  addUser(data) {
+    // console.log(TAG_NAME, 'addUser', data)
+    const incomingUserList = data.userlist
+    const userMap = this.userMap
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 已经在 map 中的用户
+        let user = this.getUser(userID)
+        if (!user) {
+          // 新增用户
+          user = new User({ userID: userID })
+          this.userList.push({
+            userID: userID,
+          })
+        }
+        userMap.set(userID, user)
+        this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: userID, userList: this.userList })
+        // console.log(TAG_NAME, 'addUser', item, userMap.get(userID), this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户退房事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  removeUser(data) {
+    // console.log(TAG_NAME, 'removeUser', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        let user = this.getUser(userID)
+        // 偶现SDK触发退房事件前没有触发进房事件
+        if (!user || !user.streams) {
+          return
+        }
+        // 从userList 里删除指定的用户和 stream
+        this._removeUserAndStream(userID)
+        // 重置
+        user.streams['main'] && user.streams['main'].reset()
+        user.streams['aux'] && user.streams['aux'].reset()
+        // 用户退出,释放引用,外部调用该 user 所有stream 的 playerContext.stop() 方法停止播放
+        // TODO 触发时机提前了,方便外部用户做出处理,时机仍需进一步验证
+        this._emitter.emit(EVENT.REMOTE_USER_LEAVE, { userID: userID, userList: this.userList, streamList: this.streamList })
+        user = undefined
+        this.userMap.delete(userID)
+        // console.log(TAG_NAME, 'removeUser', this.userMap)
+      })
+    }
+  }
+  /**
+   * 处理用户视频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserVideo(data) {
+    console.log(TAG_NAME, 'updateUserVideo', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        const streamType = item.streamtype
+        const streamID = userID + '_' + streamType
+        const hasVideo = item.hasvideo
+        const src = item.playurl
+        const user = this.getUser(userID)
+        // 更新指定用户的属性
+        if (user) {
+          // 查找对应的 stream
+          let stream = user.streams[streamType]
+          console.log(TAG_NAME, 'updateUserVideo start', user, streamType, stream)
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户(有音频或视频)的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasVideo, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasVideo })
+            if (!hasVideo && !stream.hasAudio) {
+              this._removeStream(stream)
+            }
+            // or
+            // if (hasVideo) {
+            //   stream.setProperty({ hasVideo })
+            // } else if (!stream.hasAudio) {
+            //   // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+          // 特殊逻辑
+          if (streamType === 'aux') {
+            if (hasVideo) {
+              // 辅流需要修改填充模式
+              stream.objectFit = 'contain'
+              this._addStream(stream)
+            } else {
+              // 如果是辅流要移除该 stream,否则需要移除 player
+              this._removeStream(stream)
+            }
+          }
+          // 更新所属user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Video`] = hasVideo
+              return true
+            }
+          })
+          console.log(TAG_NAME, 'updateUserVideo end', user, streamType, stream)
+          const eventName = hasVideo ? EVENT.REMOTE_VIDEO_ADD : EVENT.REMOTE_VIDEO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserVideo', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   * 处理用户音频通知事件
+   * @param {Object} data pusher 下发的数据 {userlist}
+   */
+  updateUserAudio(data) {
+    // console.log(TAG_NAME, 'updateUserAudio', data)
+    const incomingUserList = data.userlist
+    if (Array.isArray(incomingUserList) && incomingUserList.length > 0) {
+      incomingUserList.forEach((item) => {
+        const userID = item.userid
+        // 音频只跟着 stream main ,这里只修改 main
+        const streamType = 'main'
+        const streamID = userID + '_' + streamType
+        const hasAudio = item.hasaudio
+        const src = item.playurl
+        const user = this.getUser(userID)
+        if (user) {
+          let stream = user.streams[streamType]
+          // if (!stream) {
+          //   user.streams[streamType] = stream = new Stream({ streamType: streamType })
+          //   this._addStream(stream)
+          // }
+
+          // 常规逻辑
+          // 新来的stream,新增到 user.steams 和 streamList,streamList 包含所有用户的 stream
+          if (!stream) {
+            // 不在 user streams 里,需要新建
+            user.streams[streamType] = stream = new Stream({ userID, streamID, hasAudio, src, streamType })
+            this._addStream(stream)
+          } else {
+            // 更新 stream 属性
+            stream.setProperty({ hasAudio })
+            if (!hasAudio && !stream.hasVideo) {
+              this._removeStream(stream)
+            }
+            // or
+            // if (hasAudio) {
+            //   stream.setProperty({ hasAudio })
+            // } else if (!stream.hasVideo) {
+            // // hasVideo == false && hasAudio == false
+            //   this._removeStream(stream)
+            // }
+          }
+
+          // stream.userID = userID
+          // stream.streamID = userID + '_' + streamType
+          // stream.hasAudio = hasAudio
+          // stream.src = src
+          // 更新所属 user 的 hasXxx 值
+          this.userList.find((item)=>{
+            if (item.userID === userID) {
+              item[`has${streamType.replace(/^\S/, (s) => s.toUpperCase())}Audio`] = hasAudio
+              return true
+            }
+          })
+          const eventName = hasAudio ? EVENT.REMOTE_AUDIO_ADD : EVENT.REMOTE_AUDIO_REMOVE
+          this._emitter.emit(eventName, { stream: stream, streamList: this.streamList, userList: this.userList })
+          // console.log(TAG_NAME, 'updateUserAudio', user, stream, this.userMap)
+        }
+      })
+    }
+  }
+  /**
+   *
+   * @param {String} userID 用户ID
+   * @returns {Object}
+   */
+  getUser(userID) {
+    return this.userMap.get(userID)
+  }
+  getStream({ userID, streamType }) {
+    const user = this.userMap.get(userID)
+    if (user) {
+      return user.streams[streamType]
+    }
+    return undefined
+  }
+  getUserList() {
+    return this.userList
+  }
+  getStreamList() {
+    return this.streamList
+  }
+  /**
+   * 重置所有user 和 steam
+   * @returns {Object}
+   */
+  reset() {
+    this.streamList.forEach((item)=>{
+      item.reset()
+    })
+    this.streamList = []
+    this.userList = []
+    this.userMap.clear()
+    return {
+      userList: this.userList,
+      streamList: this.streamList,
+    }
+  }
+  on(eventCode, handler, context) {
+    this._emitter.on(eventCode, handler, context)
+  }
+  off(eventCode, handler) {
+    this._emitter.off(eventCode, handler)
+  }
+  /**
+   * 删除用户和所有的 stream
+   * @param {String} userID 用户ID
+   */
+  _removeUserAndStream(userID) {
+    this.streamList = this.streamList.filter((item)=>{
+      return item.userID !== userID && item.userID !== ''
+    })
+    this.userList = this.userList.filter((item)=>{
+      return item.userID !== userID
+    })
+  }
+  _addStream(stream) {
+    if (!this.streamList.includes(stream)) {
+      this.streamList.push(stream)
+    }
+  }
+  _removeStream(stream) {
+    this.streamList = this.streamList.filter((item)=>{
+      if (item.userID === stream.userID && item.streamType === stream.streamType) {
+        return false
+      }
+      return true
+    })
+    const user = this.getUser(stream.userID)
+    user.streams[stream.streamType] = undefined
+  }
+}
+
+export default UserController

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 14 - 0
components/trtc-room/libs/mta_analysis.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
components/trtc-room/libs/tim-wx.js


+ 33 - 0
components/trtc-room/model/pusher.js

@@ -0,0 +1,33 @@
+import { DEFAULT_PUSHER_CONFIG } from '../common/constants.js'
+
+class Pusher {
+  constructor(options) {
+    Object.assign(this, DEFAULT_PUSHER_CONFIG, {
+      isVisible: true, // 手Q初始化时不能隐藏 puser和player 否则黑屏
+    }, options)
+  }
+  /**
+   * 通过wx.createLivePusherContext 获取<live-pusher> context
+   * @param {Object} context 组件上下文
+   * @returns {Object} livepusher context
+   */
+  getPusherContext(context) {
+    if (!this.pusherContext) {
+      this.pusherContext = wx.createLivePusherContext(context)
+    }
+    return this.pusherContext
+  }
+  reset() {
+    console.log('Pusher reset', this.pusherContext)
+    if (this.pusherContext) {
+      console.log('Pusher pusherContext.stop()')
+      this.pusherContext.stop()
+      this.pusherContext = null
+    }
+    Object.assign(this, DEFAULT_PUSHER_CONFIG, {
+      isVisible: true,
+    })
+  }
+}
+
+export default Pusher

+ 38 - 0
components/trtc-room/model/stream.js

@@ -0,0 +1,38 @@
+// 一个stream 对应一个 player
+import { DEFAULT_PLAYER_CONFIG } from '../common/constants.js'
+
+class Stream {
+  constructor(options) {
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '', // userID + '_' + streamType
+      isVisible: true, // 手Q初始化时不能隐藏 puser和player 否则黑屏。iOS 微信初始化时不能隐藏,否则同层渲染失败,player会置顶
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined, // playerContext 依赖component context来获取,目前只能在渲染后获取
+    }, options)
+  }
+  setProperty(options) {
+    Object.assign(this, options)
+  }
+  reset() {
+    if (this.playerContext) {
+      this.playerContext.stop()
+      this.playerContext = undefined
+    }
+    Object.assign(this, DEFAULT_PLAYER_CONFIG, {
+      userID: '', // 该stream 关联的userID
+      streamType: '', // stream 类型 [main small] aux
+      streamID: '',
+      isVisible: true,
+      hasVideo: false,
+      hasAudio: false,
+      volume: 0, // 音量大小 0~100
+      playerContext: undefined,
+    })
+  }
+}
+
+export default Stream

+ 17 - 0
components/trtc-room/model/user.js

@@ -0,0 +1,17 @@
+class User {
+  constructor(options) {
+    Object.assign(this, {
+      userID: '',
+      // hasMainStream: false, // 触发 1034 且stream type 为 main 即为true
+      // hasAuxStream: false, // 触发 1034 且stream type 为 aux 即为true
+      // hasSmallStream: false, // 触发 1034 且stream type 为 small 即为true
+      streams: {
+        // main: mainStream
+        // aux: auxStream
+      }, // 有0~2个Stream, 进房没有推流,main aux, small 特殊处理,small 和 main 同时只播放一路
+      // stream 是用于渲染 live-player 的数据源
+    }, options)
+  }
+}
+
+export default User

binární
components/trtc-room/static/audio-active.png


binární
components/trtc-room/static/audio-false.png


binární
components/trtc-room/static/audio-true.png


binární
components/trtc-room/static/back.png


binární
components/trtc-room/static/beauty-false.png


binární
components/trtc-room/static/beauty-true.png


binární
components/trtc-room/static/beauty.png


binární
components/trtc-room/static/camera-false.png


binární
components/trtc-room/static/camera-true.png


binární
components/trtc-room/static/camera.png


binární
components/trtc-room/static/close-white-big.png


binární
components/trtc-room/static/close-white.png


binární
components/trtc-room/static/display-pause-false.png


binární
components/trtc-room/static/display-pause-true.png


binární
components/trtc-room/static/display-pause-white.png


binární
components/trtc-room/static/display-play-false.png


binární
components/trtc-room/static/display-play-true.png


binární
components/trtc-room/static/display-play-white.png


binární
components/trtc-room/static/fullscreen-white.png


binární
components/trtc-room/static/fullscreen.png


binární
components/trtc-room/static/hangup-red.png


binární
components/trtc-room/static/hangup.png


binární
components/trtc-room/static/im-disable.png


binární
components/trtc-room/static/im-white.png


binární
components/trtc-room/static/im.png


binární
components/trtc-room/static/list-white.png


binární
components/trtc-room/static/list.png


binární
components/trtc-room/static/loading.png


binární
components/trtc-room/static/micro-open.png


binární
components/trtc-room/static/more-disable.png


binární
components/trtc-room/static/more-enable.png


binární
components/trtc-room/static/more-white.png


binární
components/trtc-room/static/music-white.png


binární
components/trtc-room/static/mute-camera-gray.png


binární
components/trtc-room/static/mute-camera-white.png


binární
components/trtc-room/static/mute-mic-gray.png


binární
components/trtc-room/static/mute-mic-white.png


binární
components/trtc-room/static/phone.png


binární
components/trtc-room/static/setting-white.png


binární
components/trtc-room/static/setting.png


binární
components/trtc-room/static/slide-up.png


binární
components/trtc-room/static/speaker-false.png


binární
components/trtc-room/static/speaker-true.png


binární
components/trtc-room/static/speaker-white.png


binární
components/trtc-room/static/switch.png


+ 104 - 0
components/trtc-room/template/1v1/1v1.wxml

@@ -0,0 +1,104 @@
+<!-- template 1v1 -->
+<template name='1v1'>
+  <view class="template-1v1">
+    <view wx:for="{{streamList}}" wx:key="streamID" wx:if="{{item.src && (item.hasVideo || item.hasAudio)}}" class="view-container player-container {{item.isVisible?'':'none'}}">
+      <live-player
+        class="player"
+        id="{{item.streamID}}"
+        data-userid="{{item.userID}}"
+        data-streamid="{{item.streamID}}"
+        data-streamtype="{{item.streamType}}"
+        src= "{{item.src}}"
+        mode= "RTC"
+        autoplay= "{{item.autoplay}}"
+        mute-audio= "{{item.muteAudio}}"
+        mute-video= "{{item.muteVideo}}"
+        orientation= "{{item.orientation}}"
+        object-fit= "{{item.objectFit}}"
+        background-mute= "{{item.enableBackgroundMute}}"
+        min-cache= "{{item.minCache}}"
+        max-cache= "{{item.maxCache}}"
+        sound-mode= "{{item.soundMode}}"
+        enable-recv-message= "{{item.enableRecvMessage}}"
+        auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"
+        auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"
+        debug="{{debug}}"
+        bindstatechange="_playerStateChange"
+        bindfullscreenchange="_playerFullscreenChange"
+        bindnetstatus="_playerNetStatus"
+        bindaudiovolumenotify  ="_playerAudioVolumeNotify"
+      />
+    </view>
+    <view class="view-container pusher-container {{pusher.isVisible?'':'none'}} {{streamList.length===0? 'fullscreen':''}}">
+      <live-pusher
+        class="pusher"
+        url="{{pusher.url}}"
+        mode="{{pusher.mode}}"
+        autopush="{{pusher.autopush}}"
+        enable-camera="{{pusher.enableCamera}}"
+        enable-mic="{{pusher.enableMic}}"
+        muted="{{!pusher.enableMic}}"
+        enable-agc="{{pusher.enableAgc}}"
+        enable-ans="{{pusher.enableAns}}"
+        enable-ear-monitor="{{pusher.enableEarMonitor}}"
+        auto-focus="{{pusher.enableAutoFocus}}"
+        zoom="{{pusher.enableZoom}}"
+        min-bitrate="{{pusher.minBitrate}}"
+        max-bitrate="{{pusher.maxBitrate}}"
+        video-width="{{pusher.videoWidth}}"
+        video-height="{{pusher.videoHeight}}"
+        beauty="{{pusher.beautyLevel}}"
+        whiteness="{{pusher.whitenessLevel}}"
+        orientation="{{pusher.videoOrientation}}"
+        aspect="{{pusher.videoAspect}}"
+        device-position="{{pusher.frontCamera}}"
+        remote-mirror="{{pusher.enableRemoteMirror}}"
+        local-mirror="{{pusher.localMirror}}"
+        background-mute="{{pusher.enableBackgroundMute}}"
+        audio-quality="{{pusher.audioQuality}}"
+        audio-volume-type="{{pusher.audioVolumeType}}"
+        audio-reverb-type="{{pusher.audioReverbType}}"
+        waiting-image="{{pusher.waitingImage}}"
+        debug="{{debug}}"
+        bindstatechange="_pusherStateChangeHandler"
+        bindnetstatus="_pusherNetStatusHandler"
+        binderror="_pusherErrorHandler"
+        bindbgmstart="_pusherBGMStartHandler"
+        bindbgmprogress="_pusherBGMProgressHandler"
+        bindbgmcomplete="_pusherBGMCompleteHandler"
+        bindaudiovolumenotify="_pusherAudioVolumeNotify"
+      />
+      <view class="loading" wx:if="{{streamList.length === 0}}">
+        <view class="loading-img">
+          <image src="./static/loading.png" class="rotate-img"></image>
+        </view>
+        <view class="loading-text">等待接听中...</view>
+      </view>
+    </view>
+    <view class="handle-btns">
+      <view class="btn-normal" bindtap="_toggleAudio">
+        <image class="btn-image" src="{{pusher.enableMic? './static/audio-true.png': './static/audio-false.png'}} "></image>
+      </view>
+      <view class="btn-normal" bindtap="switchCamera" >
+        <image class="btn-image" src="./static/switch.png"></image>
+      </view>
+      <!-- <view class="btn-normal" bindtap="_toggleVideo">
+        <image class="btn-image" src="{{pusher.enableCamera? './static/camera-true.png': './static/camera-false.png'}} "></image>
+      </view> -->
+      <view class="btn-normal" bindtap="_toggleSoundMode">
+        <image class="btn-image" src="{{streamList[0].soundMode === 'ear' ? './static/phone.png': './static/speaker-true.png'}} "></image>
+      </view>
+    </view>
+    <view class="bottom-btns">
+      <view class="btn-normal" data-key="beautyLevel" data-value="9|0" data-value-type="number" bindtap="_setPuserProperty">
+        <image class="btn-image" src="{{pusher.beautyLevel == 9 ? './static/beauty-true.png': './static/beauty-false.png'}} "></image>
+      </view>
+      <view class="btn-hangup" bindtap="_hangUp">
+        <image class="btn-image" src="./static/hangup.png"></image>
+      </view>
+      <view class="btn-normal" bindtap="_toggleIMPanel">
+        <image class="btn-image" src="{{enableIM? './static/im.png': './static/im-disable.png'}}"></image>
+      </view>
+    </view>
+  </view>
+</template>

+ 112 - 0
components/trtc-room/template/1v1/1v1.wxss

@@ -0,0 +1,112 @@
+/* 1v1 视频电话模式 */
+.template-1v1{
+  width: 100vw;
+  height: 100vh;
+  position: relative;
+}
+.template-1v1 .pusher-container{
+  width: 240rpx;
+  height: 320rpx;
+  position: absolute;
+  right: 20rpx;
+  top: 160rpx;
+  z-index: 2;
+}
+
+.template-1v1 .pusher-container.fullscreen{
+  width: 100vw;
+  height: 100vh;
+  top: 0;
+  right: 0;
+}
+
+.template-1v1 .loading {
+  position: absolute;
+  top: 40vh;
+  left: 50vw;
+  transform: translate(-50%, 0);
+  width: 300rpx;
+  height: 250rpx;
+  border-radius: 12rpx;
+  background: rgba(0,0,0,0.6);
+  color: white;
+  padding: 40rpx;
+  display: flex;
+  flex-direction: column;
+}
+.template-1v1 .loading-img {
+  height: 200rpx;
+  display:flex;
+  justify-content: center;
+  align-items: center;
+  animation: rotate 2s linear infinite;
+}
+.template-1v1 .rotate-img {
+  width:160rpx;
+  height: 160rpx;
+}
+.template-1v1 .loading-text {
+  width: 100%;
+  padding-top: 40rpx;
+  text-align: center;
+}
+@keyframes rotate {
+  0%{ transform: rotate(0deg);}
+  50%{ transform: rotate(180deg);}
+  100%{ transform: rotate(360deg);}
+}
+.template-1v1 .player-container:nth-child(1){
+  width: 100vw;
+  height: 100vh;
+}
+
+.template-1v1 .handle-btns {
+  position: absolute;
+  z-index: 3;
+  bottom: 15vh;
+  width: 100vw;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+}
+
+.template-1v1 .bottom-btns {
+  position: absolute;
+  z-index: 3;
+  bottom: 3vh;
+  width: 100vw;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+}
+
+/* .template-1v1 image {
+  width: 4vh;
+  height: 4vh;
+} */
+
+.template-1v1 .btn-normal {
+  width: 8vh;
+  height: 8vh;
+  box-sizing: border-box;
+  display: flex;
+  background: white;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+.template-1v1 .btn-hangup .btn-image,
+.template-1v1 .btn-normal .btn-image{
+  width: 4vh;
+  height: 4vh;
+}
+.template-1v1 .btn-hangup  {
+  width: 8vh;
+  height: 8vh;
+  background: #f75c45;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}

+ 75 - 0
components/trtc-room/template/custom/custom.wxml

@@ -0,0 +1,75 @@
+<!-- template custom -->
+<template name='custom'>
+  <view class="template-custom">
+    <view class="players-container">
+      <view wx:for="{{streamList}}" wx:key="streamID" wx:if="{{item.src && (item.hasVideo || item.hasAudio)}}" class="view-container player-container {{item.isVisible?'':'none'}}" style="left:{{item.xAxis}};top:{{item.yAxis}};width:{{item.width}};height:{{item.height}};z-index:{{item.zIndex}};">
+        <live-player
+          class="player" 
+          id="{{item.streamID}}" 
+          data-userid="{{item.userID}}"
+          data-streamid="{{item.streamID}}"
+          data-streamtype="{{item.streamType}}"
+          src= "{{item.src}}"
+          mode= "{{item.mode}}"
+          autoplay= "{{item.autoplay}}"
+          mute-audio= "{{item.muteAudio}}"
+          mute-video= "{{item.muteVideo}}"
+          orientation= "{{item.orientation}}"
+          object-fit= "{{item.objectFit}}"
+          background-mute= "{{item.enableBackgroundMute}}"
+          min-cache= "{{item.minCache}}"
+          max-cache= "{{item.maxCache}}"
+          sound-mode= "{{item.soundMode}}"
+          enable-recv-message= "{{item.enableRecvMessage}}"
+          auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"
+          auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"
+          debug="{{debug}}"
+          bindstatechange="_playerStateChange"
+          bindfullscreenchange="_playerFullscreenChange"
+          bindnetstatus="_playerNetStatus"
+          bindaudiovolumenotify  ="_playerAudioVolumeNotify"
+        />
+      </view>
+    </view>
+    <view class="view-container pusher-container {{pusher.isVisible?'':'none'}}" style="left:{{pusher.xAxis}};top:{{pusher.yAxis}};width:{{pusher.width}};height:{{pusher.height}};z-index:{{pusher.zIndex}};">
+      <live-pusher 
+        class="pusher" 
+        url="{{pusher.url}}" 
+        mode="{{pusher.mode}}"
+        autopush="{{pusher.autopush}}"
+        enable-camera="{{pusher.enableCamera}}"
+        enable-mic="{{pusher.enableMic}}"
+        muted="{{!pusher.enableMic}}"
+        enable-agc="{{pusher.enableAgc}}"
+        enable-ans="{{pusher.enableAns}}"
+        enable-ear-monitor="{{pusher.enableEarMonitor}}"
+        auto-focus="{{pusher.enableAutoFocus}}"
+        zoom="{{pusher.enableZoom}}"
+        min-bitrate="{{pusher.minBitrate}}"
+        max-bitrate="{{pusher.maxBitrate}}"
+        video-width="{{pusher.videoWidth}}"
+        video-height="{{pusher.videoHeight}}"
+        beauty="{{pusher.beautyLevel}}"
+        whiteness="{{pusher.whitenessLevel}}"
+        orientation="{{pusher.videoOrientation}}"
+        aspect="{{pusher.videoAspect}}"
+        device-position="{{pusher.frontCamera}}"
+        remote-mirror="{{pusher.enableRemoteMirror}}"
+        local-mirror="{{pusher.localMirror}}"
+        background-mute="{{pusher.enableBackgroundMute}}"
+        audio-quality="{{pusher.audioQuality}}"
+        audio-volume-type="{{pusher.audioVolumeType}}"
+        audio-reverb-type="{{pusher.audioReverbType}}"
+        waiting-image="{{pusher.waitingImage}}"
+        debug="{{debug}}"
+        bindstatechange="_pusherStateChangeHandler"
+        bindnetstatus="_pusherNetStatusHandler"
+        binderror="_pusherErrorHandler"
+        bindbgmstart="_pusherBGMStartHandler"
+        bindbgmprogress="_pusherBGMProgressHandler"
+        bindbgmcomplete="_pusherBGMCompleteHandler"
+        bindaudiovolumenotify="_pusherAudioVolumeNotify"
+      />
+    </view>
+  </view>
+</template>

+ 11 - 0
components/trtc-room/template/custom/custom.wxss

@@ -0,0 +1,11 @@
+/* 通过方法自定义模式 */
+.template-custom{
+  /* 绝对定位模式 pusher 和 player 都用绝对定位*/
+  width: 100vw;
+  height: 100vh;
+  position: relative;
+}
+.template-custom .pusher-container,
+.template-custom .player-container{
+  position: absolute;
+}

+ 261 - 0
components/trtc-room/template/grid/grid.wxml

@@ -0,0 +1,261 @@
+<!-- template grid -->
+<template name='grid'>
+  <view class="template-grid">
+    <view class="column-layout">
+      <view class="column-1">
+        <view class="grid-scroll-container" bindtouchstart="_handleGridTouchStart" bindtouchend="_handleGridTouchEnd">
+          <!-- <view id="grid-container-id" class="grid-container {{visibleStreamList.length < 4 ? 'stream-' + visibleStreamList.length : visibleStreamList.length%2 == 0? 'stream-odd':'stream-even'}}"> -->
+          <view id="grid-container-id" class="grid-container {{visibleStreamList.length < 4 ? 'stream-' + visibleStreamList.length : 'stream-3'}}">
+            <view class="view-container pusher-container {{pusher.isVisible && ((gridCurrentPage === 1 && gridPlayerPerPage > 3) || gridPlayerPerPage < 4)?'':'none'}}">
+              <live-pusher
+                class="pusher"
+                url="{{pusher.url}}"
+                mode="{{pusher.mode}}"
+                autopush="{{pusher.autopush}}"
+                enable-camera="{{pusher.enableCamera}}"
+                enable-mic="{{pusher.enableMic}}"
+                muted="{{!pusher.enableMic}}"
+                enable-agc="{{pusher.enableAgc}}"
+                enable-ans="{{pusher.enableAns}}"
+                enable-ear-monitor="{{pusher.enableEarMonitor}}"
+                auto-focus="{{pusher.enableAutoFocus}}"
+                zoom="{{pusher.enableZoom}}"
+                min-bitrate="{{pusher.minBitrate}}"
+                max-bitrate="{{pusher.maxBitrate}}"
+                video-width="{{pusher.videoWidth}}"
+                video-height="{{pusher.videoHeight}}"
+                beauty="{{pusher.beautyLevel}}"
+                whiteness="{{pusher.whitenessLevel}}"
+                orientation="{{pusher.videoOrientation}}"
+                aspect="{{pusher.videoAspect}}"
+                device-position="{{pusher.frontCamera}}"
+                remote-mirror="{{pusher.enableRemoteMirror}}"
+                local-mirror="{{pusher.localMirror}}"
+                background-mute="{{pusher.enableBackgroundMute}}"
+                audio-quality="{{pusher.audioQuality}}"
+                audio-volume-type="{{pusher.audioVolumeType}}"
+                audio-reverb-type="{{pusher.audioReverbType}}"
+                waiting-image="{{pusher.waitingImage}}"
+                debug="{{debug}}"
+                beauty-style="{{pusher.beautyStyle}}"
+                filter="{{pusher.filter}}"
+                bindstatechange="_pusherStateChangeHandler"
+                bindnetstatus="_pusherNetStatusHandler"
+                binderror="_pusherErrorHandler"
+                bindbgmstart="_pusherBGMStartHandler"
+                bindbgmprogress="_pusherBGMProgressHandler"
+                bindbgmcomplete="_pusherBGMCompleteHandler"
+                bindaudiovolumenotify="_pusherAudioVolumeNotify"
+              />
+              <view class="no-video" wx:if="{{!pusher.enableCamera}}">
+                <image class="image" src="./static/mute-camera-white.png"></image>
+              </view>
+              <!-- <view class="no-audio" wx:if="{{!pusher.enableMic}}">
+                <image class="image" src="./static/mute-mic-white.png"></image>
+              </view>
+              <view class="audio-volume" wx:if="{{pusher.enableMic}}">
+                <image class="image" src="./static/micro-open.png"></image>
+                <view class="audio-active" style="height:{{pusher.volume}}%">
+                  <image class="image" src="./static/audio-active.png"></image>
+                </view>
+              </view> -->
+            </view>
+            <view wx:for="{{visibleStreamList}}" 
+              wx:key="streamID"
+              class="view-container player-container {{item.isVisible?'':'none'}}" 
+              id="{{'player-'+item.streamID}}"
+              data-userid="{{item.userID}}" 
+              data-streamtype="{{item.streamType}}" 
+              bindtap="_doubleTabToggleFullscreen">
+              <live-player
+                class="player"
+                id="{{item.streamID}}"
+                data-userid="{{item.userID}}"
+                data-streamid="{{item.streamID}}"
+                data-streamtype="{{item.streamType}}"
+                src= "{{item.src}}"
+                mode= "RTC"
+                autoplay= "{{item.autoplay}}"
+                mute-audio= "{{item.muteAudio}}"
+                mute-video= "{{item.muteVideo}}"
+                orientation= "{{item.orientation}}"
+                object-fit= "{{item.objectFit}}"
+                background-mute= "{{item.enableBackgroundMute}}"
+                min-cache= "{{item.minCache}}"
+                max-cache= "{{item.maxCache}}"
+                sound-mode= "{{item.soundMode}}"
+                enable-recv-message= "{{item.enableRecvMessage}}"
+                auto-pause-if-navigate= "{{item.autoPauseIfNavigate}}"
+                auto-pause-if-open-native= "{{item.autoPauseIfOpenNative}}"
+                debug="{{debug}}"
+                bindstatechange="_playerStateChange"
+                bindfullscreenchange="_playerFullscreenChange"
+                bindnetstatus="_playerNetStatus"
+                bindaudiovolumenotify="_playerAudioVolumeNotify"
+              />
+              <view class="no-video" wx:if="{{item.muteVideo}}">
+                <image class="image" src="./static/display-pause-white.png"></image>
+                <view class="text">
+                  <p>{{item.userID}}</p>
+                </view>
+              </view>
+              <view class="no-video" wx:if="{{!item.hasVideo && !item.muteVideo}}">
+                <image class="image" src="./static/mute-camera-white.png"></image>
+                <view class="text">
+                  <p>{{item.userID}}</p>
+                </view>
+                <view class="text">
+                  <p>对方摄像头未打开</p>
+                </view>
+              </view>
+              <view class="no-audio" wx:if="{{!item.hasAudio}}">
+                <image class="image" src="./static/mute-mic-white.png"></image>
+              </view>
+              <view class="audio-volume" wx:if="{{item.hasAudio}}">
+                <image class="image" src="./static/micro-open.png"></image>
+                <view class="audio-active" style="height:{{item.volume}}%">
+                  <image class="image" src="./static/audio-active.png"></image>
+                </view>
+              </view>
+              <view class="operation-bar">
+                <view class="operation-item-container">
+                  <view class="operation-item" catchtap="_handleSubscribeRemoteAudio" data-user-i-d="{{item.userID}}" data-stream-type="{{item.streamType}}">
+                    <image class="item-image" src="{{item.muteAudio? './static/speaker-false.png': './static/speaker-white.png'}}"></image>
+                  </view>
+                  <view class="operation-item" catchtap="_handleSubscribeRemoteVideo" data-user-i-d="{{item.userID}}" data-stream-type="{{item.streamType}}">
+                    <image class="item-image" src="{{item.muteVideo? './static/display-pause-false.png': './static/display-play-white.png'}}"></image>
+                  </view>
+                  <view class="operation-item" bindtap="_toggleFullscreen" data-user-i-d="{{item.userID}}" data-stream-type="{{item.streamType}}">
+                    <image class="item-image" src="./static/fullscreen-white.png"></image>
+                  </view>
+                </view>
+              </view>
+            </view>
+            <view wx:for="{{gridPagePlaceholderStreamList}}" wx:key="id" class="view-container player-container player-placeholder">
+              <image class="image" src="./static/mute-camera-white.png"></image>
+            </view>
+          </view>
+        </view>
+      </view>
+      <!-- <view class="column-2">
+        <view class="menu" wx:if="{{!isShowMoreMenu}}">
+          <view class="menu-item" bindtap="_switchSettingPanel">
+            <image class="image" src="./static/setting-white.png"></image>
+          </view>
+          <view class="menu-item" bindtap="_switchMemberListPanel">
+            <image class="image" src="./static/list-white.png"></image>
+          </view>
+          <view class="menu-item" bindtap="_hangUp">
+            <image class="image" src="./static/hangup-red.png"></image>
+          </view>
+          <view class="menu-item" bindtap="_toggleIMPanel">
+            <image class="image" src="{{enableIM? './static/im-white.png': './static/im-disable.png'}}"></image>
+          </view>
+        </view>
+      </view> -->
+    </view>
+
+    <view class="pages-container" wx:if="{{gridPageCount > 1}}">
+      <view wx:for="{{gridPageCount}}" wx:key="this" class="page-item {{index+1 === gridCurrentPage? 'current':''}}" ></view>
+    </view>
+    <view class="panel memberlist-panel {{panelName === 'memberlist-panel' ? '' : 'none'}}" >
+      <view bindtap="_handleMaskerClick" class='close-btn'>X</view>
+      <view class="panel-header">成员列表</view>
+      <view class="panel-body">
+        <view class="panel-tips" wx:if="{{streamList.length === 0}}">暂无成员</view>
+        <scroll-view class="scroll-container" scroll-y="true">
+          <view class="member-item" wx:for="{{streamList}}" wx:key="streamID" >
+            <view class="member-id">{{item.userID}}</view>
+            <view class="member-btns">
+              <button class="btn" hover-class="btn-hover" data-userid="{{item.userID}}" data-streamtype="{{item.streamType}}" data-key="objectFit" data-value="fillCrop|contain" bindtap="_setPlayerProperty">{{item.objectFit === 'fillCrop'? '填充':'适应'}}</button>
+              <button class="btn" hover-class="btn-hover" data-userid="{{item.userID}}" data-streamtype="{{item.streamType}}" data-key="orientation" data-value="vertical|horizontal" bindtap="_setPlayerProperty">{{item.orientation === 'vertical'? '竖屏':'横屏'}}</button>
+              <button class="btn" hover-class="btn-hover" data-userid="{{item.userID}}" data-streamtype="{{item.streamType}}" bindtap="_switchStreamType" wx:if="{{item.streamType === 'main'}}">{{item._definitionType === 'small'? '小画面':'主画面'}}</button>
+              <button class="btn" hover-class="btn-hover" data-userid="{{item.userID}}" data-streamtype="{{item.streamType}}" bindtap="_handleSnapshotClick">截屏</button>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+    </view>
+    <view class="panel setting-panel {{panelName === 'setting-panel' ? '' : 'none'}}" >
+      <view bindtap="_handleMaskerClick" class='close-btn'>X</view>
+      <view class="panel-header">推流设置</view>
+      <view class="panel-body">
+        <scroll-view class="scroll-container" scroll-y="true">
+          <view class="setting-option">
+            <view class="label">启用摄像头</view>
+            <view class="btn-normal" bindtap="_toggleVideo">
+              <image class="btn-image" src="{{pusher.enableCamera? './static/camera-true.png': './static/camera-false.png'}}"></image>
+            </view>
+          </view>
+          <view class="setting-option">
+            <view class="label">启用麦克风</view>
+            <view class="btn-normal" bindtap="_toggleAudio">
+              <image class="btn-image" src="{{pusher.enableMic? './static/audio-true.png': './static/audio-false.png'}}"></image>
+            </view>
+          </view>
+          <view class="setting-option">
+            <view class="label">切换摄像头</view>
+            <view class="btn-normal" bindtap="switchCamera" >
+              <image class="btn-image" src="./static/switch.png"></image>
+            </view>
+          </view>
+          <view class='setting-option'>
+            <view class="label">开启美颜</view>
+            <switch class="setting-switch" color="#006eff" checked="{{pusher.beautyLevel == 9 ? true: false}}" data-key="beautyLevel" data-value="9|0" data-value-type="number" bindchange="_setPuserProperty"/>
+          </view>
+          <view class='setting-option'>
+            <view class="label">开启AGC</view>
+            <switch class="setting-switch" color="#006eff" checked="{{pusher.enableAgc}}" data-key="enableAgc" data-value="true" data-value-type="boolean" bindchange="_setPuserProperty"/>
+          </view>
+          <view class='setting-option'>
+            <view class="label">开启ANS</view>
+            <switch class="setting-switch" color="#006eff" checked="{{pusher.enableAns}}" data-key="enableAns" data-value="true" data-value-type="boolean" bindchange="_setPuserProperty"/>
+          </view>
+          <view class='setting-option'>
+            <view class="label">开启横屏推流</view>
+            <switch class="setting-switch" color="#006eff" checked="{{pusher.videoOrientation === 'vertical' ? false: true}}" data-key="videoOrientation"  data-value="horizontal|vertical" data-value-type="string" bindchange="_setPuserProperty"/>
+          </view>
+        </scroll-view>
+      </view>
+    </view>
+    <view class="panel bgm-panel {{panelName === 'bgm-panel' ? '' : 'none'}}" >
+      <view bindtap="_handleMaskerClick" class='close-btn'>X</view>
+      <view class="panel-header">背景音乐</view>
+      <view class="panel-body">
+        <view class="setting-option">
+          <view class="label">MIC音量</view>
+          <view class="slider-content">
+            <slider value="{{MICVolume}}" min="0" max="100" show-value="true" activeColor="#006eff" bindchange="_changeProperty" data-property-name="MICVolume" />
+          </view>
+        </view>
+        <view class="setting-option">
+          <view class="label">BGM音量</view>
+          <view class="slider-content">
+            <slider value="{{BGMVolume}}" min="0" max="100" show-value="true" activeColor="#006eff" bindchange="_changeProperty" data-property-name="BGMVolume" />
+          </view>
+        </view>
+        <view class="setting-option">
+          <view class="label">播放进度</view>
+          <view class="slider-content">
+            <progress activeColor="#006eff" percent="{{BGMProgress}}"></progress>
+          </view>
+        </view>
+        <view class="menu">
+          <view class="menu-item" bindtap="_handleBGMOperation" data-operation-name="playBGM">
+            <view class="label">播放</view>
+          </view>
+          <view class="menu-item" bindtap="_handleBGMOperation" data-operation-name="pauseBGM">
+            <view class="label">暂停</view>
+          </view>
+          <view class="menu-item" bindtap="_handleBGMOperation" data-operation-name="resumeBGM">
+            <view class="label">继续</view>
+          </view>
+          <view class="menu-item" bindtap="_handleBGMOperation" data-operation-name="stopBGM">
+            <view class="label">停止</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="masker {{panelName =='' ? 'none' : ''}}" bindtap="_handleMaskerClick"></view>
+  </view>
+</template>

+ 563 - 0
components/trtc-room/template/grid/grid.wxss

@@ -0,0 +1,563 @@
+/* 9人 会议模版 */
+.template-grid {
+  width: 750rpx;
+  height: 500rpx;
+}
+
+.column-layout {
+  width: 750rpx;
+  height: 500rpx;
+  /* display: flex;
+  flex-direction: column; */
+  position: relative;
+}
+
+.column-layout .column-1 {
+  flex: 1;
+}
+
+/* .column-layout .column-2 {
+  position: relative;
+  height: 100rpx;
+  background-color: rgb(36, 36, 36);
+} */
+
+.fullscreen-device-fix .column-layout .column-2 {
+  height: 120rpx;
+}
+
+.menu {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-items: center;
+}
+
+.menu .menu-item {
+  text-align: center;
+  height: 100rpx;
+  flex-grow: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.menu .menu-item .image {
+  width: 46rpx;
+  height: 46rpx;
+}
+
+.more-menu {
+  position: absolute;
+  top: 0;
+}
+
+.more-menu .scroll-container {
+  width: 100%;
+  height: 100rpx;
+  white-space: nowrap;
+}
+
+.more-menu .menu-item-container {
+  width: 20%;
+  display: inline-block;
+}
+
+.template-grid .grid-container {
+  width: 750rpx;
+  height: 500rpx;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  position: relative;
+  /* position: absolute;
+  right: 0;
+  bottom: 0; */
+}
+
+.pusher-container {
+  width: 300rpx;
+  height: 300rpx;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+}
+
+.player-container {
+  width: 700rpx;
+  height: 500rpx;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.template-grid .grid-scroll-container {
+  width: 100%;
+  height: 100%;
+  /* box-sizing: border-box; */
+  /* overflow-y: scroll; */
+  background-color: #000;
+}
+
+.grid-containe.overflow {
+  height: auto;
+}
+
+.template-grid .view-container {
+  position: relative;
+}
+
+.stream-0 .view-container {
+  width: 100%;
+  height: 100%;
+}
+
+.stream-1 .view-container {
+  width: 100%;
+  height: 50%;
+}
+
+.stream-2 .view-container {
+  width: 50%;
+  height: 50%;
+}
+
+.stream-2 .view-container:nth-child(1) {
+  width: 100%;
+  height: 50%;
+}
+
+.stream-3 .view-container {
+  width: 50%;
+  height: 50%;
+}
+
+.stream-4 .view-container {
+  width: 50%;
+  height: 33.3%;
+}
+
+.stream-4 .view-container:nth-child(1) {
+  width: 100%;
+  height: 33.3%;
+}
+
+.stream-5 .view-container {
+  width: 50%;
+  height: 33.3%;
+}
+
+.stream-6 .view-container {
+  width: 33.3%;
+  height: 33.3%;
+}
+
+.stream-6 .view-container:nth-child(1) {
+  width: 100%;
+  height: 33.3%;
+}
+
+.stream-7 .view-container {
+  width: 33.3%;
+  height: 33.3%;
+}
+
+.stream-7 .view-container:nth-child(1) {
+  width: 50%;
+  height: 33.3%;
+}
+
+.stream-7 .view-container:nth-child(2) {
+  width: 50%;
+  height: 33.3%;
+}
+
+.stream-8 .view-container {
+  width: 33.3%;
+  height: 33.3%;
+}
+
+.stream-even .view-container {
+  width: 50%;
+  height: 50%;
+}
+
+.stream-odd .view-container {
+  width: 50%;
+  height: 50%;
+}
+
+.stream-odd .view-container:last-child {
+  width: 100%;
+  height: 50%;
+}
+
+.template-grid .operation-bar {
+  position: absolute;
+  bottom: 6rpx;
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+}
+
+.operation-bar .operation-item-container {
+  width: auto;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  background: rgb(0, 0, 0, .3);
+  border-radius: 10rpx;
+}
+
+.template-grid .operation-bar .operation-item {
+  width: 64rpx;
+  height: 64rpx;
+  /* flex-grow: 1; */
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+}
+
+.operation-item .item-image {
+  width: 36rpx;
+  height: 36rpx;
+}
+
+.template-grid .volume-progress {
+  width: 100%;
+  position: absolute;
+  bottom: 0;
+}
+
+.template-grid .btn-normal {
+  width: 64rpx;
+  height: 64rpx;
+  margin: 0 6rpx;
+  box-sizing: border-box;
+  display: flex;
+  background: rgba(255, 255, 255, 1);
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+}
+
+.template-grid .btn-normal .btn-image {
+  width: 36rpx;
+  height: 36rpx;
+}
+
+.template-grid .btn-hangup {
+  background: #f75c45;
+}
+
+.template-grid .panel {
+  position: absolute;
+  background: rgba(0, 0, 0, 0.8);
+  width: 90vw;
+  height: auto;
+  z-index: 999;
+  top: 50vh;
+  left: 50vw;
+  transform: translate(-50%, -50%);
+  color: white;
+  display: flex;
+  flex-direction: column;
+  padding: 20rpx 0;
+  border-radius: 10rpx;
+  box-sizing: border-box;
+  font-size: 14px;
+}
+
+.panel .close-btn {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 5px 10px;
+}
+
+.panel .panel-header {
+  text-align: center;
+  padding-bottom: 20rpx;
+}
+
+.panel .panel-tips {
+  color: #999;
+  text-align: center;
+}
+
+.panel .panel-body {
+  flex: 1;
+  max-height: 50vh;
+}
+
+.panel .panel-body .scroll-container {
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+.memberlist-panel .panel-body {
+  height: 50vh;
+}
+
+.memberlist-panel .member-item {
+  display: flex;
+  /* border-bottom: 1px solid #999; */
+  margin: 16rpx 16rpx 16rpx 32rpx;
+}
+
+.memberlist-panel .member-id {
+  width: 30%;
+  font-size: 12px;
+  line-height: 64rpx;
+}
+
+.memberlist-panel .member-btns {
+  width: 70%;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.memberlist-panel .member-btns .btn-normal {
+  margin-left: 0;
+}
+
+.memberlist-panel .member-btns .btn {
+  margin-right: 0;
+}
+
+.setting-panel .panel-body {
+  height: 50vh;
+}
+
+.setting-panel .setting-option {
+  display: flex;
+  justify-content: space-between;
+  margin: 16rpx 16rpx 16rpx 32rpx;
+  /* box-sizing: border-box;
+  padding: 12rpx 16rpx 12rpx 32rpx; */
+}
+
+.setting-panel .setting-option .label {
+  /* line-height: 64rpx; */
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.setting-panel .setting-switch {
+  transform: scale(0.8);
+  margin-right: -12rpx;
+}
+
+.bgm-panel .panel-body {
+  height: auto;
+}
+
+.bgm-panel .setting-option {
+  height: 60rpx;
+  display: flex;
+  flex-direction: row;
+  margin: 16rpx 16rpx 16rpx 32rpx;
+}
+
+.bgm-panel .setting-option .label {
+  width: 140rpx;
+  line-height: 60rpx;
+}
+
+.bgm-panel .setting-option .slider-content {
+  flex: 1;
+  line-height: 60rpx;
+}
+
+.bgm-panel .setting-option .slider-content slider {
+  transform: scale(0.9);
+  margin: 0;
+}
+
+.bgm-panel .setting-option .slider-content progress {
+  transform: scale(0.9);
+  margin-top: 28rpx;
+}
+
+.bgm-panel .menu {
+  padding: 16rpx 32rpx 16rpx 32rpx;
+  box-sizing: border-box;
+}
+
+.bgm-panel .menu .menu-item {
+  height: 80rpx;
+  background-color: #333;
+}
+
+.template-grid .masker {
+  position: absolute;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.4);
+}
+
+.template-grid .no-stream,
+.template-grid .no-video {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  color: #fff;
+  background-color: rgba(0, 0, 0, 0.4);
+  font-size: 12px;
+}
+
+.template-grid .audio-volume,
+.template-grid .no-audio {
+  position: absolute;
+  bottom: 20rpx;
+  left: 20rpx;
+  width: 36rpx;
+  height: 36rpx;
+}
+
+.no-stream .image,
+.no-video .image {
+  width: 60rpx;
+  height: 60rpx;
+}
+
+.audio-volume .image,
+.no-audio .image {
+  width: 36rpx;
+  height: 36rpx;
+  position: absolute;
+  /*android 的bug ,image absolute后会向上漂移几个像素,如果要对其必须都设置absolute*/
+}
+
+.audio-active {
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+}
+
+.audio-active .image {
+  bottom: 0;
+}
+
+.slide-up-tips {
+  position: absolute;
+  bottom: -100rpx;
+  left: 50%;
+  transform: translate(-50%, 0);
+  width: 200rpx;
+  height: auto;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  font-size: 12px;
+  color: #fff;
+  background-color: rgba(0, 0, 0, 0.4);
+  box-sizing: border-box;
+  padding: 20rpx;
+  border-radius: 10rpx;
+  opacity: 0;
+}
+
+.slide-up-tips .image {
+  width: 100rpx;
+  height: 100rpx;
+}
+
+.player-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.player-placeholder .image {
+  width: 100rpx;
+  height: 100rpx;
+}
+
+.pages-container {
+  width: auto;
+  left: 50%;
+  transform: translate(-50%, 0);
+  height: 20rpx;
+  position: absolute;
+  bottom: 12%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+}
+
+.pages-container .page-item {
+  width: 20rpx;
+  height: 20rpx;
+  border-radius: 50%;
+  margin: 0 8rpx;
+  background-color: rgb(99, 99, 99, .5);
+}
+
+.pages-container .page-item.current {
+  background-color: #fff;
+}
+
+.radio-group-no-box {
+  display: inline-block;
+  color: #006eff;
+  background-color: #ffffff;
+  border: 1px solid #006eff;
+  border-radius: 4px;
+  margin-left: 180rpx;
+  font-size: 12px;
+}
+
+.radio-group-no-box .radio-item {
+  padding: 5px 8px;
+  text-align: center;
+  border-right: 1px solid #006eff;
+  display: inline-block;
+}
+
+.radio-group-no-box .radio-item:last-child {
+  border-right: none;
+}
+
+.radio-group-no-box .radio-item.selected {
+  color: #ffffff;
+  background-color: #006eff;
+}
+
+.radio-group-no-box radio {
+  display: none;
+}
+
+.picker-label {
+  display: inline-block;
+  color: #006eff;
+  background-color: #ffffff;
+  border: 1px solid #006eff;
+  border-radius: 4px;
+  padding: 5px 8px;
+  text-align: center;
+  font-size: 12px;
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2407 - 0
components/trtc-room/trtc-room.js


+ 4 - 0
components/trtc-room/trtc-room.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 70 - 0
components/trtc-room/trtc-room.wxml

@@ -0,0 +1,70 @@
+<import src='./template/1v1/1v1.wxml'/>
+<import src='./template/grid/grid.wxml'/>
+<import src='./template/custom/custom.wxml'/>
+
+<view class="trtc-room-container {{isFullscreenDevice?'fullscreen-device-fix':''}}">
+  <block wx:if="{{template === '1v1'}}">
+    <template is='1v1' data="{{pusher, streamList, debug, enableIM}}"></template>
+  </block>
+  <block wx:if="{{template === 'grid'}}">
+    <template is='grid' data="{{pusher, streamList, visibleStreamList, debug, enableIM, panelName, gridPagePlaceholderStreamList, gridPageCount, gridCurrentPage, gridPlayerPerPage, isShowMoreMenu, MICVolume, BGMVolume, BGMProgress, beautyStyle, beautyStyleArray, filterIndex, filterArray, audioReverbTypeArray}}"></template>
+  </block>
+  <block wx:if="{{template === 'custom'}}">
+    <template is='custom' data="{{pusher, streamList, debug}}"></template>
+  </block>
+
+  <view class="im-panel" wx:if="{{enableIM && showIMPanel}}">
+    <view class="message-panel-body">
+      <scroll-view scroll-y="true" class="message-scroll-container" scroll-into-view="message{{messageList.length-1}}" scroll-with-animation="{{true}}">
+        <view class="message-list">
+          <view class="message-item" wx:for="{{messageList}}" wx:key="index" id="message{{index}}">
+            <span class="user-name {{item.name == config.userID?'mine':''}}">{{item.name}}</span>
+            <span class="message-content">{{item.message}}</span>
+          </view>
+          <view id="message-bottom"></view>
+        </view>
+      </scroll-view>
+        
+    </view>
+    <view class="message-panel-bottom">
+      <view class="message-input-container">
+        <input class="message-input" type="text" value="{{messageContent}}" bindinput='_inputIMMessage' bindconfirm='_sendIMMessage' confirm-type="send" placeholder="请输入消息" maxlength="200" placeholder-style="color:#ffffff;opacity: 0.55;"/>
+      </view>
+      <view class="message-send-btn">
+        <button class="btn" bindtap="_sendIMMessage" hover-class="btn-hover">发送</button>
+      </view>
+    </view>
+    <view bindtap="_toggleIMPanel" class='close-btn'>X</view>
+  </view>
+
+  <view class="debug-info-btn {{debugMode && !debugPanel?'':'none'}}">
+    <button class="debug-btn" bindtap="_debugTogglePanel" hover-class="button-hover">Debug</button>
+  </view>
+  <view class="debug-info {{debugMode && debugPanel?'':'none'}}">
+    <view bindtap="_debugTogglePanel" class='close-btn'>X</view>
+    <view>appVersion: {{appVersion}}</view>
+    <view>libVersion: {{libVersion}}</view>
+    <view>template: {{template}}</view>
+    <view>debug: <button class="{{debug?'':'false'}} debug-btn" bindtap="_debugToggleVideoDebug" hover-class="button-hover">{{debug}}</button></view>
+    <view>userID: {{pusher.userID}}</view>
+    <view>roomID: {{pusher.roomID}}</view>
+    <view>camera: <button class="{{pusher.enableCamera?'':'false'}} debug-btn" bindtap="_toggleVideo" hover-class="button-hover">{{pusher.enableCamera}}</button></view>
+    <view>mic: <button class="{{pusher.enableMic?'':'false'}} debug-btn" bindtap="_toggleAudio" hover-class="button-hover">{{pusher.enableMic}}</button></view>
+    <view>switch camera: <button class="debug-btn" bindtap="switchCamera" hover-class="button-hover">{{cameraPosition||pusher.frontCamera}}</button></view>
+    <view>Room:
+      <button class="debug-btn" bindtap="_debugEnterRoom" hover-class="button-hover">Enter</button>
+      <button class="debug-btn" bindtap="_debugExitRoom" hover-class="button-hover">Exit</button>
+      <button class="debug-btn" bindtap="_debugGoBack" hover-class="button-hover">Go back</button>
+    </view>
+    <view>IM: <button class="debug-btn" bindtap="_debugSendRandomMessage" hover-class="button-hover">send</button></view>
+    <view>user count: {{userList.length}}</view>
+    <view wx:for="{{userList}}" wx:key="userID">{{item.userID}}|
+      mainV:<span class="text {{item.hasMainVideo? 'true' : 'false' }}">{{item.hasMainVideo||false}}</span>|
+      mainA:<span class="text {{item.hasMainAudio? 'true' : 'false' }}">{{item.hasMainAudio||false}}</span>|
+      auxV:<span class="text {{item.hasAuxVideo? 'true' : 'false' }}">{{item.hasAuxVideo||false}}</span></view>
+    <view>stream count: {{streamList.length}}</view>
+    <view wx:for="{{streamList}}" wx:key="streamID">{{item.userID}}|{{item.streamType}}|
+      SubV:<button class="{{!item.muteVideo?'':'false'}} debug-btn" bindtap="_debugToggleRemoteVideo" hover-class="button-hover" data-user-i-d="{{item.userID}}" data-stream-type="{{item.streamType}}">{{!item.muteVideo}}</button>|
+      SubA:<button class="{{!item.muteAudio?'':'false'}} debug-btn" bindtap="_debugToggleRemoteAudio" hover-class="button-hover" data-user-i-d="{{item.userID}}" data-stream-type="{{item.streamType}}">{{!item.muteAudio}}</button></view>
+  </view>
+</view>

+ 228 - 0
components/trtc-room/trtc-room.wxss

@@ -0,0 +1,228 @@
+
+@import "./template/1v1/1v1.wxss";
+@import "./template/grid/grid.wxss";
+@import "./template/custom/custom.wxss";
+
+.pusher {
+  width: 100%;
+  height: 100%;
+}
+
+.player {
+  width: 100%;
+  height: 100%;
+}
+
+.debug-info{
+  max-width: 100vw;
+  max-height: 90vh;
+  box-sizing: border-box;
+  overflow-y: scroll;
+  position: absolute;
+  z-index: 9999;
+  background-color: rgba(0, 0, 0, .5);
+  color: #fff;
+  bottom: 20rpx;
+  left: 0;
+  padding: 10rpx;
+  font-size: 12px;
+}
+.debug-info-btn .debug-btn,
+.debug-info .debug-btn{
+  padding: 0 8px;
+  min-height: 18px;
+  width: auto;
+  font-size: 12px;
+  line-height: 18px;
+  display: inline-block;
+  color: #06ae56;
+  background-color: #f2f2f2;
+}
+.debug-info .debug-btn.false{
+  color: rgb(114, 114, 114);
+}
+.debug-info-btn .debug-btn,
+.debug-info .button-hover {
+  background-color: rgb(219, 219, 219);
+}
+.debug-info .close-btn{
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 5px 10px;
+}
+.debug-info .text.true{
+  color: #1fff8b;
+}
+.debug-info .text.false{
+  color: #ff2e2e;
+}
+.debug-info-btn{
+  position: absolute;
+  z-index: 9998;
+  bottom: 160rpx;
+  left: 0;
+}
+
+.trtc-room-container .btn {
+  display: inline-block;
+  width: auto;
+  height: 60rpx;
+  min-height: 60rpx;
+  line-height: 60rpx;
+  font-size: 12px;
+  font-weight: normal;
+  padding: 0 10rpx;
+  color: #006eff;
+  background-color: #f2f2f2;
+  margin: 0 16rpx;
+}
+.trtc-room-container .btn.active{
+  color: #f2f2f2;
+  background-color: #006eff;
+}
+.trtc-room-container .btn-hover{
+  background-color: #d1d1d1;
+}
+
+.im-panel{
+  position: absolute;
+  z-index: 9;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 90vw;
+  height: 320rpx;
+  top: 50vh;
+  left: 50vw;
+  transform: translate(-50%, -50%);
+  padding: 20rpx 0;
+  border-radius: 10rpx;
+  font-size: 12px;
+  /* bottom: 25vh; */
+  color: #fff;
+  background-color: rgba(0, 0, 0, 0.8);
+}
+.im-panel .close-btn {
+  position: absolute;
+  top: 0;
+  right: -3px;
+  padding: 5px 10px;
+  z-index: 99;
+}
+.message-panel-body{
+  width: 100%;
+  height: 80%;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.message-scroll-container{
+  height: 100%;
+  /* box-sizing: border-box;
+  padding: 0 20rpx; */
+}
+.message-list{
+  width: 100%;
+  box-sizing: border-box;
+  padding: 0 20rpx;
+  /* display: flex;
+  flex-direction: column; */
+}
+.message-item{
+  width: 100%;
+  /* height: 36rpx; */
+  /* padding: 0 20rpx; */
+  padding-bottom: 10rpx;
+  display: flex;
+  flex-direction: row;
+}
+.message-item .user-name{
+  width: 20%;
+  color: #2483ff;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.user-name.mine{
+  color: #ff7424;
+}
+.message-item .separate{
+  padding:0 5px;
+  color: #fff;
+}
+.message-item .message-content{
+  word-wrap:break-word; 
+  word-break:break-all;
+  padding-left: 20rpx;
+  position: relative;
+  max-width: 80%; 
+  box-sizing: border-box;
+}
+.message-content::after{
+  content: ':';
+  position: absolute;
+  left: 0;
+  top: 0;
+}
+.message-panel-bottom{
+  width: 100%;
+  height: 50rpx;
+  box-sizing: border-box;
+  padding: 0 20rpx 0;
+  margin-top: 20rpx;
+  display: flex;
+  flex-direction: row;
+}
+.message-input-container {
+  flex-grow: 1;
+}
+.message-input-container .message-input {
+  font-size: 12px;
+  padding-left: 20rpx;
+  border-radius: 10rpx;
+  height: 100%;
+  background-color: rgba(0,0,0,0.1);
+}
+.message-send-btn .btn{
+  margin-right: 0;
+  height: 50rpx;
+  min-height: 50rpx;
+  line-height: 50rpx;
+}
+
+.volume-animation{
+  position: absolute;
+  width: 80rpx;
+  height: 80rpx;
+  left: 0;
+  top: initial;
+  bottom: 20rpx;
+  z-index: 9;
+  /* transform: translate(-50%, 0); */
+}
+.volume-animation .image{
+  position: absolute;
+  width: 80rpx;
+  height: 80rpx;
+}
+.volume-animation .audio-active{
+  animation: viewlinear 1.5s linear infinite;
+} 
+@keyframes viewlinear {
+  
+  /** 第一种写法**/
+  0% {
+    height: 0;
+  }
+  100% {
+    height: 100%;
+  }
+}
+
+.none,
+.view-container.none,
+.template-grid .view-container.none,
+.template-1v1 .view-container.none{
+  display: none !important;
+}

+ 21 - 0
components/trtc-room/utils/compare-version.js

@@ -0,0 +1,21 @@
+export default function compareVersion(v1, v2) {
+  v1 = v1.split('.')
+  v2 = v2.split('.')
+  const len = Math.max(v1.length, v2.length)
+  while (v1.length < len) {
+    v1.push('0')
+  }
+  while (v2.length < len) {
+    v2.push('0')
+  }
+  for (let i = 0; i < len; i++) {
+    const num1 = parseInt(v1[i])
+    const num2 = parseInt(v2[i])
+    if (num1 > num2) {
+      return 1
+    } if (num1 < num2) {
+      return -1
+    }
+  }
+  return 0
+}

+ 50 - 0
components/trtc-room/utils/environment.js

@@ -0,0 +1,50 @@
+import compareVersion from './compare-version.js'
+const TAG_NAME = 'TRTC-ROOM'
+
+const env = wx ? wx : qq
+if (!env) {
+  console.error(TAG_NAME, '不支持当前小程序环境')
+}
+const systemInfo = env.getSystemInfoSync()
+const safeArea = systemInfo.safeArea
+if (systemInfo.system === 'iOS 13.3' || (systemInfo.model === 'iPhoneX' && systemInfo.system === 'iOS 13.3.1') ) {
+  // audio-volume-type = media
+  console.log('use media audio volume type')
+}
+console.log(TAG_NAME, 'SystemInfo', systemInfo)
+let isNewVersion
+if (typeof qq !== 'undefined') {
+  isNewVersion = true
+} else if (typeof wx !== 'undefined') {
+  if (compareVersion(systemInfo.version, '7.0.8') >= 0 || // mobile pc
+  (compareVersion(systemInfo.version, '2.4.0') >= 0 && compareVersion(systemInfo.version, '6.0.0') < 0) && // mac os
+  compareVersion(systemInfo.SDKVersion, '2.10.0') >= 0) {
+    isNewVersion = true
+  } else {
+    isNewVersion = false
+  }
+}
+
+export const IS_TRTC = isNewVersion
+export const IS_QQ = typeof qq !== 'undefined'
+export const IS_WX = typeof wx !== 'undefined'
+export const IS_IOS = /iOS/i.test(systemInfo.system)
+export const IS_ANDROID = /Android/i.test(systemInfo.system)
+export const IS_MAC = /mac/i.test(systemInfo.system)
+export const APP_VERSION = systemInfo.version
+export const LIB_VERSION = (function() {
+  if (systemInfo.SDKBuild) {
+    return systemInfo.SDKVersion + '-' + systemInfo.SDKBuild
+  }
+  return systemInfo.SDKVersion
+})()
+
+let isFullscreenDevie = false
+if (systemInfo.screenHeight > safeArea.bottom) {
+// if (/iphone\s{0,}x/i.test(systemInfo.model)) {
+  isFullscreenDevie = true
+}
+
+export const IS_FULLSCREEN_DEVICE = isFullscreenDevie
+
+console.log(TAG_NAME, 'APP_VERSION:', APP_VERSION, ' LIB_VERSION:', LIB_VERSION, ' is new version:', IS_TRTC)

+ 62 - 0
components/trtc-room/utils/event.js

@@ -0,0 +1,62 @@
+class Event {
+  on(event, fn, ctx) {
+    if (typeof fn !== 'function') {
+      console.error('listener must be a function')
+      return
+    }
+
+    this._stores = this._stores || {};
+    (this._stores[event] = this._stores[event] || []).push({ cb: fn, ctx: ctx })
+  }
+
+  emit(event) {
+    this._stores = this._stores || {}
+    let store = this._stores[event]
+    let args
+
+    if (store) {
+      store = store.slice(0)
+      args = [].slice.call(arguments, 1),
+      args[0] = {
+        eventCode: event,
+        data: args[0],
+      }
+      for (let i = 0, len = store.length; i < len; i++) {
+        store[i].cb.apply(store[i].ctx, args)
+      }
+    }
+  }
+
+  off(event, fn) {
+    this._stores = this._stores || {}
+
+    // all
+    if (!arguments.length) {
+      this._stores = {}
+      return
+    }
+
+    // specific event
+    const store = this._stores[event]
+    if (!store) return
+
+    // remove all handlers
+    if (arguments.length === 1) {
+      delete this._stores[event]
+      return
+    }
+
+    // remove specific handler
+    let cb
+    for (let i = 0, len = store.length; i < len; i++) {
+      cb = store[i].cb
+      if (cb === fn) {
+        store.splice(i, 1)
+        break
+      }
+    }
+    return
+  }
+}
+
+module.exports = Event

+ 334 - 225
pages/zbDetails/zbDetails.js

@@ -1,250 +1,359 @@
-const app = require('../../utils/util.js');
+// const app = require('../../utils/util.js');
 const tools = require('../../utils/tools.js');
+const getusersig = require('../../utils/getUserSig.js');
 Page({
   data: {
-    url: '',
-    title: '',
-    message: '',
-    playTimes: 0,
-    id: 0,
-    socketOpen: false,
-    chatContent: [],
-    inputValue: '',
-    bottom: "",
-    pushUrl: '',
-    videoContext: '',
-    fullScreenFlag: false,
-    livectx: "",
-    spfx: "vertical",
-    spfx1: "vertical",
-    isHands: false,
-    timeoutObj: null,
-    _timeout: 5000,
-    isCloseStatus: false
+    // url: '',
+    // title: '',
+    // message: '',
+    // playTimes: 0,
+    rtcConfig: {
+      sdkAppID: '1400398825', // 必要参数 开通实时音视频服务创建应用后分配的 sdkAppID
+      userID: '', // 必要参数 用户 ID 可以由您的帐号系统指定
+      userSig: '', // 必要参数 身份签名,相当于登录密码的作用
+      template: 'grid', // 必要参数 组件模版,支持的值 1v1 grid custom ,注意:不支持动态修改, iOS 不支持 pusher 动态渲染
+    },
+    // socketOpen: false,
+    // chatContent: [],
+    // inputValue: '',
+    // bottom: "",
+    // pushUrl: '',
+    // videoContext: '',
+    // fullScreenFlag: false,
+    // livectx: "",
+    // spfx: "vertical",
+    // spfx1: "vertical",
+    // isHands: false,
+    // timeoutObj: null,
+    // _timeout: 5000,
+    // isCloseStatus: false
   },
-  statechange: function (e) {
-    console.log(e)
+
+  // statechange: function (e) {
+  //   console.log(e)
+  // },
+  // scrollToTop() {
+  //   this.setAction({
+  //     scrollTop: 0
+  //   })
+  // },
+  // onReady: function () {},
+  // bindKeyInput: function (e) {
+  //   this.setData({
+  //     inputValue: e.detail.value
+  //   })
+  // },
+  // hands() {
+  //   console.log("我点击举手了")
+  //   this.sendSocketMessage("put up hands")
+  // },
+  // submit: function () {
+  //   let input_val = this.data.inputValue;
+  //   this.sendSocketMessage(input_val)
+  //   this.setData({
+  //     inputValue: ''
+  //   })
+  // },
+  // fullScreen: function () {
+  //   let fullScreenFlag = this.data.fullScreenFlag;
+  //   if (fullScreenFlag) {
+  //     fullScreenFlag = false;
+  //   } else {
+  //     fullScreenFlag = true;
+  //   }
+  //   if (fullScreenFlag) {
+  //     //全屏
+  //     this.data.livectx.requestFullScreen({
+  //       success: () => {
+  //         this.setData({
+  //           fullScreenFlag: fullScreenFlag,
+  //           spfx: "horizontal",
+  //           spfx1: "horizontal"
+  //         });
+  //         if (this.data.isHands == true) {
+  //           this.setData({
+  //             isHands: false
+  //           })
+  //           this.setData({
+  //             isHands: true
+  //           })
+  //         }
+  //         console.log('我要全屏执行了');
+  //       },
+  //       fail: () => {
+  //         console.log('我要全屏执行失败了');
+  //       }
+  //     });
+  //   } else {
+  //     //缩小
+  //     this.data.livectx.exitFullScreen({
+  //       success: () => {
+  //         console.log('fullscreen success');
+  //         this.setData({
+  //           fullScreenFlag: fullScreenFlag,
+  //           spfx: "vertical",
+  //           spfx1: "vertical"
+  //         });
+  //       },
+  //       fail: () => {
+  //         console.log('exit fullscreen success');
+  //       }
+  //     });
+  //   }
+  // },
+  // getZbArr(sessionKey) {
+  //   wx.request({
+  //     url: app.globalData.publicUrl + '/wx/course/selectLiveByCourseId',
+  //     method: "post",
+  //     data: {
+  //       sessionKey: sessionKey,
+  //       courseId: this.data.id
+  //     },
+  //     success: (e) => {
+  //       console.log(e, "11111111111111111")
+  //     }
+  //   })
+  // },
+  // socketConnect(sessionKey) {
+  //   let newSessionkey = encodeURIComponent(sessionKey);
+  //   wx.connectSocket({
+  //     url: app.globalData.publicSocketUrl + '/live/socket?sessionkey=' + newSessionkey + "&courseId=" + this.data.id
+  //   })
+  //   wx.onSocketOpen(() => {
+  //     console.log("我连上啦")
+  //     this.setData({
+  //       socketOpen: true
+  //     })
+  //     this.setData({
+  //       timeoutObj: setInterval(() => {
+  //         this.startHeart(sessionKey)
+  //       }, this.data._timeout)
+  //     })
+  //   })
+  //   wx.onSocketError(function () {
+  //     console.log("我连接出错了")
+  //   })
+  //   wx.onSocketClose(function () {
+  //     console.log("我关了")
+  //   })
+  //   wx.onSocketMessage((data) => {
+  //     console.log(data, "2222222222222")
+  //     let res = JSON.parse(data.data);
+  //     if (res.msg == "HeartBeat") {
+  //       return false;
+  //     }
+  //     if (res.messageType == "message" || res.messageType == "system") {
+  //       this.setData({
+  //         chatContent: this.data.chatContent.concat(res),
+  //         bottom: 'scrollBottom'
+  //       })
+  //     }
+  //     if (res.msg == "socketLiveEnd") {
+  //       this.setData({
+  //         url: "",
+  //       })
+  //       if (this.data.socketOpen) {
+  //         wx.closeSocket()
+  //       }
+  //       this.data.videoContext.stop()
+  //       wx.showModal({
+  //         showCancel: false,
+  //         content: "直播结束了",
+  //         success(res) {
+  //           if (res.confirm) {
+  //             wx.switchTab({
+  //               url: '../index/index'
+  //             })
+  //           }
+  //         }
+  //       })
+  //     }
+  //     if (res.messageType == "studentPushStream") {
+  //       console.log(res, "我收到了举手的推流")
+  //       this.data.videoContext.start();
+  //       this.setData({
+  //         isHands: true,
+  //         pushUrl: res.pushUrl
+  //       })
+  //     }
+  //     if (res.messageType == "stopStudentPushStream") {
+  //       console.log(res.pushUrl, "不推了")
+  //       this.data.videoContext.stop()
+  //       this.setData({
+  //         isHands: false,
+  //         pushUrl: ""
+  //       })
+  //     }
+  //   })
+  // },
+  // // 发送消息
+  // sendSocketMessage(msg) {
+  //   if (this.data.socketOpen) {
+  //     wx.sendSocketMessage({
+  //       data: msg
+  //     })
+  //   }
+  // },
+  // // 心跳
+  // startHeart(sessionKey) {
+  //   wx.sendSocketMessage({
+  //     data: "HeartBeat",
+  //     success: (res) => {
+  //       console.log(res, "发送心跳成功")
+  //     },
+  //     fail: (err) => {
+  //       console.log(err, "发送心跳失败")
+  //       if (this.data.isCloseStatus == false) {
+  //         wx.closeSocket();
+  //         clearInterval(this.data.timeoutObj);
+  //         this.socketConnect(sessionKey);
+  //       } else {
+  //         clearInterval(this.data.timeoutObj);
+  //       }
+  //     }
+  //   })
+  // },
+  // async onShow() {
+  //   const sessionKey = await tools.checkSessionAndLogin();
+  //   this.socketConnect(sessionKey);
+  // },
+  // onHide() {
+  //   this.setData({
+  //     isCloseStatus: true
+  //   })
+  //   if (this.data.socketOpen) {
+  //     wx.closeSocket()
+  //   }
+  //   clearInterval(this.data.timeoutObj);
+  //   this.data.videoContext.stop()
+  // },
+  // onUnload() {
+  //   this.setData({
+  //     isCloseStatus: true
+  //   })
+  //   if (this.data.socketOpen) {
+  //     wx.closeSocket()
+  //   }
+  //   clearInterval(this.data.timeoutObj);
+  //   this.data.videoContext.stop()
+  // },
+  startPushMy() {
+    this.trtcRoomContext.publishLocalVideo();
+    this.trtcRoomContext.publishLocalAudio();
   },
-  scrollToTop() {
-    this.setAction({
-      scrollTop: 0
-    })
+  stopPushMy(trtcRoomContext) {
+    trtcRoomContext.unpublishLocalVideo();
+    trtcRoomContext.unpublishLocalAudio();
   },
-  onReady: function () {},
-  bindKeyInput: function (e) {
-    this.setData({
-      inputValue: e.detail.value
+  bindTRTCRoomEvent: function () {
+    const TRTC_EVENT = this.trtcRoomContext.EVENT
+    this.trtcRoomContext.on(TRTC_EVENT.LOCAL_JOIN, (event) => {
+      console.log('* room LOCAL_JOIN', event)
     })
-  },
-  hands() {
-    console.log("我点击举手了")
-    this.sendSocketMessage("put up hands")
-  },
-  submit: function () {
-    let input_val = this.data.inputValue;
-    this.sendSocketMessage(input_val)
-    this.setData({
-      inputValue: ''
+    this.trtcRoomContext.on(TRTC_EVENT.LOCAL_LEAVE, (event) => {
+      console.log('* room LOCAL_LEAVE', event)
     })
-  },
-  fullScreen: function() {
-    let fullScreenFlag = this.data.fullScreenFlag;
-    if (fullScreenFlag) {
-      fullScreenFlag = false;
-    } else {
-      fullScreenFlag = true;
-    }
-    if (fullScreenFlag) {
-      //全屏
-      this.data.livectx.requestFullScreen({
-        success: () => {
-          this.setData({
-            fullScreenFlag: fullScreenFlag,
-            spfx: "horizontal",
-            spfx1: "horizontal"
-          });
-          if (this.data.isHands == true) {
-            this.setData({
-              isHands: false
-            })
-            this.setData({
-              isHands: true
-            })
-          }
-          console.log('我要全屏执行了');
-        },
-        fail: () => {
-          console.log('我要全屏执行失败了');
-        }
-      });
-    } else {
-      //缩小
-      this.data.livectx.exitFullScreen({
-        success: () => {
-          console.log('fullscreen success');
-          this.setData({
-            fullScreenFlag: fullScreenFlag,
-            spfx: "vertical",
-            spfx1: "vertical"
-          });
-        },
-        fail: () => {
-          console.log('exit fullscreen success');
-        }
-      });
-    }
-  },
-  getZbArr(sessionKey) {
-    wx.request({
-      url: app.globalData.publicUrl + '/wx/course/selectLiveByCourseId',
-      method: "post",
-      data: {
-        sessionKey: sessionKey,
-        courseId: this.data.id
-      },
-      success: (e) => {
-        console.log(e, "11111111111111111")
-        this.setData({
-          url: e.data.pullUrl.flvUrl,
-          // url: e.data.pullUrl.rtmpUrl,
-          title: e.data.course.courseName,
-          message: e.data.course.courseInfo,
-          playTimes: e.data.course.playTimes
-        })
-      }
+    this.trtcRoomContext.on(TRTC_EVENT.ERROR, (event) => {
+      console.log('* room ERROR', event)
     })
-  },
-  socketConnect(sessionKey) {
-    let newSessionkey = encodeURIComponent(sessionKey);
-    wx.connectSocket({
-      url: app.globalData.publicSocketUrl + '/live/socket?sessionkey=' + newSessionkey + "&courseId=" + this.data.id
+    // 远端用户进入房间
+    this.trtcRoomContext.on(TRTC_EVENT.REMOTE_USER_JOIN, (event) => {
+      console.log('远端用户进入房间')
     })
-    wx.onSocketOpen(() => {
-      console.log("我连上啦")
-      this.setData({
-        socketOpen: true
-      })
-      this.setData({
-        timeoutObj: setInterval( ()=> {
-          this.startHeart(sessionKey)
-        }, this.data._timeout)
+    // 远端用户退出
+    this.trtcRoomContext.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event) => {
+      console.log('远端用户退出', event, this.trtcRoomContext.getRemoteUserList())
+      const userList = this.trtcRoomContext.getRemoteUserList()
+      this.handleOnUserList(userList).then(() => {
+        console.log(this.data.userList)
       })
     })
-    wx.onSocketError(function () {
-      console.log("我连接出错了")
-    })
-    wx.onSocketClose(function () {
-      console.log("我关了")
-    })
-    wx.onSocketMessage((data) => {
-      console.log(data, "2222222222222")
-      let res = JSON.parse(data.data);
-      if (res.msg == "HeartBeat") {
-        return false;
-      }
-      if (res.messageType == "message" || res.messageType == "system") {
-        this.setData({
-          chatContent: this.data.chatContent.concat(res),
-          bottom: 'scrollBottom'
+    // 远端用户推送音频
+    this.trtcRoomContext.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event) => {
+      if (this.data.userList.length < 6) {
+        // 订阅音频
+        const data = event.data
+        // 如果不订阅就不会自动播放音频
+        const userList = this.trtcRoomContext.getRemoteUserList()
+        this.handleOnUserList(userList).then(() => {
+          console.log(this.data.userList)
         })
-      }
-      if (res.msg == "socketLiveEnd") {
-        this.setData({
-          url: "",
-        })
-        if (this.data.socketOpen) {
-          wx.closeSocket()
-        }
-        this.data.videoContext.stop()
-        wx.showModal({
-          showCancel: false,
-          content: "直播结束了",
-          success(res) {
-            if (res.confirm) {
-              wx.switchTab({
-                url: '../index/index'
-              })
-            }
-          }
-        })
-      }
-      if (res.messageType == "studentPushStream") {
-        console.log(res, "我收到了举手的推流")
-        this.data.videoContext.start();
-        this.setData({
-          isHands: true,
-          pushUrl: res.pushUrl
-        })
-      }
-      if (res.messageType == "stopStudentPushStream") {
-        console.log(res.pushUrl, "不推了")
-        this.data.videoContext.stop()
-        this.setData({
-          isHands: false,
-          pushUrl: ""
+        console.log('远端用户推送音频', event, this.trtcRoomContext.getRemoteUserList())
+        this.trtcRoomContext.subscribeRemoteAudio({
+          userID: data.userID
         })
       }
     })
-  },
-  // 发送消息
-  sendSocketMessage(msg) {
-    if (this.data.socketOpen) {
-      wx.sendSocketMessage({
-        data: msg
+    // 远端用户取消推送音频
+    this.trtcRoomContext.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event) => {
+      console.log('远端用户取消推送音频', event, this.trtcRoomContext.getRemoteUserList())
+      const userList = this.trtcRoomContext.getRemoteUserList()
+      this.handleOnUserList(userList).then(() => {
+        console.log(this.data.userList)
       })
-    }
-  },
-  // 心跳
-  startHeart(sessionKey) {
-    wx.sendSocketMessage({
-      data: "HeartBeat",
-      success: (res) => {
-        console.log(res, "发送心跳成功")
-      },
-      fail: (err) => {
-        console.log(err, "发送心跳失败")
-        if (this.data.isCloseStatus == false) {
-          wx.closeSocket();
-          clearInterval(this.data.timeoutObj);
-          this.socketConnect(sessionKey);
-        } else {
-          clearInterval(this.data.timeoutObj);
-        }
-      }
     })
   },
-  async onShow() {
-    const sessionKey = await tools.checkSessionAndLogin();
-    this.socketConnect(sessionKey);
-  },
-  onHide() {
-    this.setData({
-      isCloseStatus: true
+  // handleOnUserList: function (userList) {
+  //   return new Promise((resolve, reject) => {
+  //     const newUserList = []
+  //     let index = 0
+  //     const oldUserList = this.data.userList
+  //     userList.forEach((item) => {
+  //       if (item.hasMainAudio) {
+  //         const user = this.judgeWhetherExist({
+  //           userID: item.userID,
+  //           streamType: 'main'
+  //         }, oldUserList)
+  //         index += 1
+  //         if (user) {
+  //           // 已存在
+  //           newUserList.push(Object.assign(user, {
+  //             index: index
+  //           }))
+  //         } else {
+  //           newUserList.push({
+  //             userID: item.userID,
+  //             streamType: 'main',
+  //             index: index,
+  //             hasMainAudio: item.hasMainAudio,
+  //             volume: 0,
+  //           })
+  //         }
+  //       }
+  //     })
+  //     this.setData({
+  //       userList: newUserList,
+  //     }, () => {
+  //       console.log('handleOnUserList newUserList', newUserList)
+  //       resolve()
+  //     })
+  //   })
+  // },
+  // judgeWhetherExist: function(target, userList) {
+  //   userList.forEach( (item) => {
+  //     if (target.userID === item.userID && target.streamType === item.streamType) {
+  //       return item
+  //     }
+  //   })
+  //   return false
+  // },
+  async onLoad() {
+    wx.setKeepScreenOn({
+      keepScreenOn: true,
     })
-    if (this.data.socketOpen) {
-      wx.closeSocket()
-    }
-    clearInterval(this.data.timeoutObj);
-    this.data.videoContext.stop()
-  },
-  onUnload() {
+    const sig = await getusersig.genTestUserSig("12") //学生id
+    console.log(sig, "我是sig")
+    let userSig = 'rtcConfig.userSig';
+    let userID = 'rtcConfig.userID';
     this.setData({
-      isCloseStatus: true
+      [userSig]: sig.userSig,
+      [userID]: "12", //学生id
+      // trtcRoomContext: this.selectComponent('#trtcroom')
     })
-    if (this.data.socketOpen) {
-      wx.closeSocket()
-    }
-    clearInterval(this.data.timeoutObj);
-    this.data.videoContext.stop()
-  },
-  async onLoad(options) {
-    this.setData({
-      id: options.id,
-      videoContext: wx.createLivePusherContext("video-livePusher"),
-      livectx: wx.createLivePlayerContext('livectx')
+    this.trtcRoomContext = this.selectComponent('#trtcroom')
+    this.bindTRTCRoomEvent()
+    this.trtcRoomContext.enterRoom({
+      roomID: 9 //课程id
     })
-    const sessionKey = await tools.checkSessionAndLogin();
-    this.getZbArr(sessionKey);
   }
-
 })

+ 4 - 2
pages/zbDetails/zbDetails.json

@@ -1,4 +1,6 @@
 {
-  "usingComponents": {},
-  "navigationBarTitleText":"在线直播"
+  "navigationBarTitleText":"在线直播",
+  "usingComponents": {
+    "trtc-room": "../../components/trtc-room/trtc-room"
+  }
 }

+ 5 - 4
pages/zbDetails/zbDetails.wxml

@@ -1,6 +1,9 @@
 <view class="home">
 	<view class="big_box">
-		<live-player src="{{url}}" id="livectx" orientation="{{spfx}}" mode="live" autoplay bindstatechange="statechange" binderror="error" controls style="width:100%;height:100%">
+		<trtc-room id="trtcroom" config="{{rtcConfig}}" scene="rtc" videoWidth='360' videoHeight='200'></trtc-room>
+	</view>
+	<button bindtap="startPushMy">点我开始推本地</button>
+	<!-- <live-player src="{{url}}" id="livectx" orientation="{{spfx}}" mode="live" autoplay bindstatechange="statechange" binderror="error" controls style="width:100%;height:100%">
 			<cover-view wx:if="{{fullScreenFlag}}" bindtap="fullScreen" class="full_btn">
 				<button plain="false" style="color:#fff">退出全屏</button>
 			</cover-view>
@@ -33,7 +36,5 @@
 			<view style="font-size:35rpx;font-weight:900;color:#eb3f33;float:left;line-height:80rpx;margin-right:20rpx">举手</view>
 		</view>
 		<input class="weui-input" bindinput="bindKeyInput" placeholder="请输入您的问题" style="height:60rpx" value="{{inputValue}}" />
-		<view class="send" bindtap="submit">发送</view>
-		<!-- <view class="send" bindtap="clearDs">清除定时器</view> -->
-	</view>
+		<view class="send" bindtap="submit">发送</view> -->
 </view>

+ 56 - 0
utils/getUserSig.js

@@ -0,0 +1,56 @@
+import LibGenerateTestUserSig from './lib-generate-test-usersig-es.min.js'
+/**
+ * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
+ * 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId,
+ * 它是腾讯云用于区分客户的唯一标识。
+ */
+const SDKAPPID = 1400398825
+/**
+ * 签名过期时间,建议不要设置的过短
+ * <p>
+ * 时间单位:秒
+ * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
+ */
+const EXPIRETIME = 604800
+/**
+ * 计算签名用的加密密钥,获取步骤如下:
+ *
+ * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
+ * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
+ * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
+ *
+ * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
+ * 文档:https://cloud.tencent.com/document/product/647/17275#Server
+ */
+const SECRETKEY = 'e749eeec2c1951ecbf5575e4ca383a256310446cb7feaf3b2e5f074b3ecc801e'
+
+/*
+ * Module:   GenerateTestUserSig
+ *
+ * Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
+ *           其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
+ *
+ * Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
+ *
+ *            本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
+ *            这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
+ *            一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
+ *
+ *            正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
+ *            由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
+ *
+ * Reference:https://cloud.tencent.com/document/product/647/17275#Server
+ */
+function genTestUserSig(userID) {
+  const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME)
+  const userSig = generator.genTestUserSig(userID)
+
+  return {
+    sdkAppID: SDKAPPID,
+    userSig: userSig,
+  }
+}
+
+module.exports = {
+  genTestUserSig,
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
utils/lib-generate-test-usersig-es.min.js


+ 3 - 0
utils/util.js

@@ -1,6 +1,7 @@
 const globalData = {
   // 正式服务器
   publicUrl: 'https://sqdx.jiaxintech.com',
+  // publicUrl: 'http://10.16.10.139:8090',
   publicSocketUrl: 'wss://sqdx.jiaxintech.com'
   // 测试服务器
   // publicUrl: 'https://sqdx.windd.cn',
@@ -11,6 +12,8 @@ const globalData = {
   // 本地luyu 
   // publicUrl: 'http://10.16.10.139:8090', 
   // publicSocketUrl: 'ws://10.16.10.139:8090'
+  //zxq
+  //publicUrl: 'http://10.16.4.26:8080', 
 };
 module.exports = {
   globalData: globalData,