Sfoglia il codice sorgente

feat:接入腾讯云音视频

徐志豪 5 anni fa
parent
commit
581c0ccef9

+ 85 - 0
miniprogram/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
miniprogram/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

File diff suppressed because it is too large
+ 14 - 0
miniprogram/components/trtc-room/libs/mta_analysis.js


File diff suppressed because it is too large
+ 1 - 0
miniprogram/components/trtc-room/libs/tim-wx.js


+ 33 - 0
miniprogram/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
miniprogram/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
miniprogram/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

+ 75 - 0
miniprogram/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
miniprogram/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;
+}

File diff suppressed because it is too large
+ 2407 - 0
miniprogram/components/trtc-room/trtc-room.js


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

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

+ 7 - 0
miniprogram/components/trtc-room/trtc-room.wxml

@@ -0,0 +1,7 @@
+<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':''}}">
+  <template is='custom' data="{{pusher, streamList, debug}}"></template>
+</view>

+ 13 - 0
miniprogram/components/trtc-room/trtc-room.wxss

@@ -0,0 +1,13 @@
+@import "./template/custom/custom.wxss";
+
+.pusher {
+  width: 100%;
+  height: 100%;
+  visibility: hidden;
+}
+
+.player {
+  width: 100%;
+  height: 100%;
+  visibility: hidden;
+}

+ 21 - 0
miniprogram/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
miniprogram/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
miniprogram/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

+ 4 - 4
miniprogram/config/config.js

@@ -1,5 +1,5 @@
-// export const IM_HOST = 'ws://haowantest.4dkankan.com'
-// export const API_BASE_URL = 'http://haowantest.4dkankan.com'
+export const IM_HOST = 'ws://haowantest.4dkankan.com'
+export const API_BASE_URL = 'http://haowantest.4dkankan.com'
 
 
 // export const IM_HOST = 'ws://192.168.0.83:8075'
@@ -7,5 +7,5 @@
 // export const API_BASE_URL = 'http://192.168.0.83:8075'
 
 
-export const IM_HOST = 'wss://haowan.4dkankan.com'
-export const API_BASE_URL = 'https://haowan.4dkankan.com'
+// export const IM_HOST = 'wss://haowan.4dkankan.com'
+// export const API_BASE_URL = 'https://haowan.4dkankan.com'

+ 85 - 26
miniprogram/pages/web/web.js

@@ -1,5 +1,7 @@
 import * as SocketHandle from '../../utils/socket-handle'
-import { getQueryString } from './../../utils/tools'
+import {
+  getQueryString
+} from './../../utils/tools'
 import ImSend from './../../utils/imSend'
 import ImApi from './../../apis/im'
 import CompanyApi from './../../apis/company'
@@ -31,7 +33,9 @@ Page({
    * 生命周期函数--监听页面加载
    */
   onLoad: async function (options) {
-    let { vr_link } = options
+    let {
+      vr_link
+    } = options
     console.log(options)
     vr_link = decodeURIComponent(vr_link)
     this.vrLink = vr_link
@@ -42,13 +46,14 @@ Page({
       await this.getHouseDetail(this.scene)
       this.role = this.companyDetail.guideId === (getApp().globalData.userinfo ? getApp().globalData.userinfo.viewerId : null) ? 'agent' : 'customer'
     }
-    
+
     let opts = {
       roomId: options.room_id || getQueryString(vr_link, 'room_id') || randomString(18),
       userId: getApp().globalData.userinfo.viewerId,
       role: this.role,
       scene_num: getQueryString(vr_link, 'm')
     }
+    this.roomId = opts.roomId
     this.originUrl = `${vr_link}&room_id=${opts.roomId}${options.is_auto? `&vr=1` : ''}`
     this.setData({
       socketOpts: opts,
@@ -57,7 +62,7 @@ Page({
       house_id: options.house_id || '',
       vr_link: vr_link
     })
-    
+
     this.socket = SocketHandle.initSocket(this, opts)
     if (app.globalData.token && !getQueryString(this.data.url, 'user_id') && this.role) {
       this.setData({
@@ -74,7 +79,7 @@ Page({
     }
   },
 
-  onUnload () {
+  onUnload() {
     this.socket.disconnect()
   },
   countLiveUrl(user_id) {
@@ -96,21 +101,25 @@ Page({
       this.sendVrCard()
       this.hasSend = true
     }
-    this.setData({
-      pushUrl: this.countLiveUrl(getApp().globalData.userinfo.viewerId)
+    this.socket.emit('getUserSig', {
+      userId: getApp().globalData.userinfo.viewerId
     })
   },
-  createLivePull({persons, roomId}) {
+  createLivePull({
+    persons,
+    roomId
+  }) {
     let arr = []
     persons.forEach(item => {
       if (item.userId !== getApp().globalData.userinfo.viewerId) {
         arr.push(this.countLiveUrl(item.userId))
       }
     })
-    
+
     this.setData({
       pullUrl: arr
     })
+    this.enterAudioRoom()
   },
   someoneLeave() {
     this.setData({
@@ -118,56 +127,106 @@ Page({
       pushUrl: ''
     })
   },
-  sendVrCard () {
+  sendVrCard() {
     if (this.companyDetail.guideId === getApp().globalData.userinfo.viewerId) {
       return
     }
     ImApi.addFriend(this.companyDetail.guideId).finally(() => {
-      ImSend.sendVrMsg({title: `${this.companyDetail.companyName}`, detail_images: [this.companyDetail.vrImg], vrLink: encodeURIComponent(this.vrLink)}, this.data.room_id, this.companyDetail.guideId, true)
+      ImSend.sendVrMsg({
+        title: `${this.companyDetail.companyName}`,
+        detail_images: [this.companyDetail.vrImg],
+        vrLink: encodeURIComponent(this.vrLink)
+      }, this.data.room_id, this.companyDetail.guideId, true)
     })
   },
-  getHouseDetail (scene) {
+  getHouseDetail(scene) {
     return CompanyApi.getCompanyDetailByScene(scene).then(res => {
       console.log(JSON.parse(res.data.introduceImage), 'JSON.parse(res.data.introduceImage)[')
       res.data.vrImg = JSON.parse(res.data.introduceImage)[0].img
       this.companyDetail = res.data
-      console.log(this.companyDetail)
-      // house.detail_images = JSON.parse(house.detail_images)
-      // this.setData({
-      //   house
-      // })
-      // return res
     })
   },
   onShareAppMessage: function (res) {
-    var path =`/pages/web/web?vr_link=${encodeURIComponent(this.data.vr_link)}`;
+    var path = `/pages/web/web?vr_link=${encodeURIComponent(this.data.vr_link)}`;
     var imageurl = this.companyDetail.vrImg;
     return {
       title: this.companyDetail.companyName,
       path: path,
       imageUrl: imageurl, // 分享的封面图
       success: () => {
-        
+
         // 转发成功
       },
-      fail: function(err) {
+      fail: function (err) {
         console.log(err)
         app.ShowModel('网络错误', '转发失败~');
         // 转发失败
       }
     }
   },
-  bindmessage (e) {
+  bindmessage(e) {
     const img_url = e.detail.data[0]
     getApp().globalData.shared_img = img_url
   },
-  loginSuccess () {
+  loginSuccess() {
     this.onShow()
     this.hideLogin()
   },
-  hideLogin () {
+  hideLogin() {
     this.setData({
-        showLogin: false
+      showLogin: false
+    })
+  },
+  enterAudioRoom(sig) {
+    // index.js
+    if (this.hasEnter) {
+      return
+    }
+    
+    this.setData({
+      trtcConfig: {
+        sdkAppID: '1400393268', // 开通实时音视频服务创建应用后分配的 SDKAppID
+        userID: getApp().globalData.userinfo.viewerId, // 用户 ID,可以由您的帐号系统指定
+        userSig: sig, // 身份签名,相当于登录密码的作用
+        template: 'custom', // 画面排版模式
+      }
+    }, () => {
+      let trtcRoomContext = this.selectComponent('#trtcroom')
+      let EVENT = trtcRoomContext.EVENT
+
+      if (trtcRoomContext) {
+        trtcRoomContext.on(EVENT.LOCAL_JOIN, (event) => {
+          // 进房成功后发布本地音频流和视频流 
+          trtcRoomContext.publishLocalAudio()
+        })
+        // 监听远端用户的视频流的变更事件
+        trtcRoomContext.on(EVENT.REMOTE_VIDEO_ADD, (event) => {
+          // 订阅(即播放)远端用户的视频流
+          let userID = event.data.userID
+          let streamType = event.data.streamType // 'main' or 'aux'            
+          trtcRoomContext.subscribeRemoteVideo({
+            userID: userID,
+            streamType: streamType
+          })
+        })
+
+        // 监听远端用户的音频流的变更事件
+        trtcRoomContext.on(EVENT.REMOTE_AUDIO_ADD, (event) => {
+          // 订阅(即播放)远端用户的音频流
+          let userID = event.data.userID
+          trtcRoomContext.subscribeRemoteAudio({
+            userID: userID
+          })
+        })
+
+        trtcRoomContext.enterRoom({
+          roomID: this.roomId
+        }).then(() => {
+          this.hasEnter = true
+        }).catch((res) => {
+          console.error('room joinRoom 进房失败:', res)
+        })
+      }
     })
-},
+  }
 })

+ 2 - 1
miniprogram/pages/web/web.json

@@ -1,6 +1,7 @@
 {
   "usingComponents": {
-    "login-pannel": "/components/login-pannel/login-pannel"
+    "login-pannel": "/components/login-pannel/login-pannel",
+    "trtc-room": "/components/trtc-room/trtc-room"
   },
   "navigationBarTitleText": "VR企业秀",
   "pageOrientation": "auto"

+ 4 - 2
miniprogram/pages/web/web.wxml

@@ -1,6 +1,8 @@
 <web-view src="{{url}}"></web-view>   
 
-<live-pusher mode="RTC" wx:if="{{pushUrl}}" autopush url="{{pushUrl}}" enable-ans="{{true}}" bindstatechange="statechange"  enable-camera="{{false}}"></live-pusher>
-<live-player mode="RTC" wx:for="{{pullUrl}}" wx:key="index" autoplay="{{true}}" src="{{item}}" min-cache="{{0.2}}" max-cache="{{0.8}}"></live-player>
+<!-- <live-pusher mode="RTC" wx:if="{{pushUrl}}" autopush url="{{pushUrl}}" enable-ans="{{true}}" bindstatechange="statechange"  enable-camera="{{false}}"></live-pusher>
+<live-player mode="RTC" wx:for="{{pullUrl}}" wx:key="index" autoplay="{{true}}" src="{{item}}" min-cache="{{0.2}}" max-cache="{{0.8}}"></live-player> -->
+
+<trtc-room id="trtcroom" config="{{trtcConfig}}"></trtc-room>
 
 <login-pannel wx:if="{{showLogin}}" bindloginSuccess="loginSuccess" bindhide="hideLogin" />

+ 7 - 1
miniprogram/utils/socket-handle.js

@@ -19,7 +19,8 @@ export function initSocket(page, options) {
   const eventName = {
     startPlay: 'answer', // 开始语音
     someoneInRoom: 'vr_request', // 有人加入房间
-    someoneLeaveRoom: 'putup' // 有人离开房间
+    someoneLeaveRoom: 'putup', // 有人离开房间
+    getUserSig: 'getUserSig'
   }
   // 事件监听
   io.on(eventName.startPlay, function (data) {
@@ -34,6 +35,11 @@ export function initSocket(page, options) {
   io.on(eventName.someoneLeaveRoom, function (data) {
     page.someoneLeave(data)
   })
+
+  io.on(eventName.getUserSig, sig => {
+    console.log(sig, 'sig')
+    page.enterAudioRoom(sig)
+  })
   return io
 }