trtc-room.js 87 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408
  1. import UserController from './controller/user-controller.js'
  2. import Pusher from './model/pusher.js'
  3. import { EVENT, DEFAULT_COMPONENT_CONFIG } from './common/constants.js'
  4. import Event from './utils/event.js'
  5. import * as ENV from './utils/environment.js'
  6. import TIM from './libs/tim-wx.js'
  7. import MTA from './libs/mta_analysis.js'
  8. const TAG_NAME = 'TRTC-ROOM'
  9. const IM_GROUP_TYPE = TIM.TYPES.GRP_CHATROOM // TIM.TYPES.GRP_CHATROOM 体验版IM无数量限制,成员20个, TIM.TYPES.GRP_AVCHATROOM IM体验版最多10个,升级后无限制
  10. let touchX = 0
  11. let touchY = 0
  12. Component({
  13. /**
  14. * 组件的属性列表
  15. */
  16. properties: {
  17. // 必要的初始化参数
  18. config: {
  19. type: Object,
  20. value: {
  21. sdkAppID: '',
  22. userID: '',
  23. userSig: '',
  24. template: '',
  25. debugMode: false, // 是否开启调试模式
  26. enableIM: false, // 是否开启 IM
  27. },
  28. observer: function(newVal, oldVal) {
  29. this._propertyObserver({
  30. 'name': 'config', newVal, oldVal,
  31. })
  32. },
  33. },
  34. },
  35. /**
  36. * 组件的初始数据
  37. */
  38. data: {
  39. pusher: null,
  40. debugPanel: true, // 是否打开组件调试面板
  41. debug: false, // 是否打开player pusher 的调试信息
  42. streamList: [], // 用于渲染player列表,存储stram
  43. visibleStreamList: [], // 有音频或者视频的StreamList
  44. userList: [], // 扁平化的数据用来返回给用户
  45. template: '', // 不能设置默认值,当默认值和传入组件的值不一致时,iOS渲染失败
  46. cameraPosition: '', // 摄像头位置,用于debug
  47. panelName: '', // 控制面板名称,包括 setting-panel memberlist-panel
  48. localVolume: 0,
  49. remoteVolumeList: [],
  50. enableIM: false, // 用于组件内渲染
  51. showIMPanel: false,
  52. exitIMThrottle: false,
  53. messageContent: '',
  54. messageList: [], // 仅保留10条消息
  55. maxMessageListLength: 10,
  56. messageListScrollTop: 0,
  57. appVersion: ENV.APP_VERSION,
  58. libVersion: ENV.LIB_VERSION,
  59. hasGridPageTipsShow: false,
  60. gridPageCount: 0, // grid 布局 player 分页的总页数
  61. gridCurrentPage: 1, // grid 布局 当前页码
  62. gridPlayerPerPage: 4, // grid 布局每页 player的数量, 如果大于3,在逻辑里第一页需要减1。等于3 pusher 在每一页都出现。可选值: 3,4
  63. gridPagePlaceholderStreamList: [], // 占位数量
  64. isFullscreenDevice: ENV.IS_FULLSCREEN_DEVICE,
  65. isShowMoreMenu: false,
  66. MICVolume: 50,
  67. BGMVolume: 50,
  68. BGMProgress: 0,
  69. beautyStyle: 'smooth',
  70. beautyStyleArray: [
  71. { value: 'smooth', label: '光滑', checked: true },
  72. { value: 'nature', label: '自然', checked: false },
  73. { value: 'close', label: '关闭', checked: false },
  74. ],
  75. filterIndex: 0,
  76. filterArray: [
  77. { value: 'standard', label: '标准' },
  78. { value: 'pink', label: '粉嫩' },
  79. { value: 'nostalgia', label: '怀旧' },
  80. { value: 'blues', label: '蓝调' },
  81. { value: 'romantic', label: '浪漫' },
  82. { value: 'cool', label: '清凉' },
  83. { value: 'fresher', label: '清新' },
  84. { value: 'solor', label: '日系' },
  85. { value: 'aestheticism', label: '唯美' },
  86. { value: 'whitening', label: '美白' },
  87. { value: 'cerisered', label: '樱红' },
  88. ],
  89. audioReverbType: 0,
  90. audioReverbTypeArray: ['关闭', 'KTV', '小房间', '大会堂', '低沉', '洪亮', '金属声', '磁性'],
  91. },
  92. /**
  93. * 生命周期方法
  94. */
  95. lifetimes: {
  96. created: function() {
  97. // 在组件实例刚刚被创建时执行
  98. console.log(TAG_NAME, 'created', ENV)
  99. MTA.App.init({
  100. appID: '500710685',
  101. eventID: '500710697',
  102. autoReport: true,
  103. statParam: true,
  104. })
  105. },
  106. attached: function() {
  107. // 在组件实例进入页面节点树时执行
  108. console.log(TAG_NAME, 'attached')
  109. this._init()
  110. MTA.Page.stat()
  111. },
  112. ready: function() {
  113. // 在组件在视图层布局完成后执行
  114. console.log(TAG_NAME, 'ready')
  115. },
  116. detached: function() {
  117. // 在组件实例被从页面节点树移除时执行
  118. console.log(TAG_NAME, 'detached')
  119. // 停止所有拉流,并重置数据
  120. this.exitRoom()
  121. },
  122. error: function(error) {
  123. // 每当组件方法抛出错误时执行
  124. console.log(TAG_NAME, 'error', error)
  125. },
  126. },
  127. pageLifetimes: {
  128. show: function() {
  129. // 组件所在的页面被展示时执行
  130. console.log(TAG_NAME, 'show status:', this.status)
  131. if (this.status.isPending) {
  132. // 经历了 5000 挂起事件
  133. this.status.isPending = false
  134. // 修复iOS 最小化触发5000事件后,音频推流失败的问题
  135. // if (ENV.IS_IOS && this.data.pusher.enableMic) {
  136. // this.unpublishLocalAudio().then(()=>{
  137. // this.publishLocalAudio()
  138. // })
  139. // }
  140. // 经历了 5001 浮窗关闭事件,小程序底层会自动退房,恢复小程序时组件需要重新进房
  141. // 重新进房
  142. this.enterRoom({ roomID: this.data.config.roomID }).then(()=>{
  143. // 进房后开始推送视频或音频
  144. // setTimeout(()=>{
  145. // this.publishLocalVideo()
  146. // this.publishLocalAudio()
  147. // }, 2000)
  148. })
  149. } else if (ENV.IS_ANDROID && this.status.pageLife === 'hide' && this.status.isOnHideAddStream && this.data.streamList.length > 0) {
  150. // 微信没有提供明确的最小化事件,onHide事件,不一定是最小化
  151. // 获取所有的player 清空 src 重新赋值 验证无效
  152. // 清空 visibleStreamList 重新赋值, 验证无效
  153. // 退房重新进房,有效但是成本比较高
  154. // 将标记了 isOnHideAdd 的 stream 的 palyer 销毁并重新渲染
  155. const streamList = this.data.streamList
  156. let tempStreamList = []
  157. // 过滤 onHide 时新增的 stream
  158. for (let i = 0; i < streamList.length; i++) {
  159. if (streamList[i].isOnHideAdd && streamList[i].playerContext) {
  160. const stream = streamList[i]
  161. tempStreamList.push(stream)
  162. stream.playerContext = undefined
  163. streamList.splice(i, 1)
  164. }
  165. }
  166. // 设置渲染,销毁onHide 时新增的 player
  167. this._setList({
  168. streamList: streamList,
  169. }).then(() => {
  170. for (let i = 0; i < tempStreamList.length; i++) {
  171. streamList.push(tempStreamList[i])
  172. }
  173. // 设置渲染,重新创建 onHide 时新增的 player
  174. // setTimeout(()=>{
  175. this._setList({
  176. streamList: streamList,
  177. }).then(() => {
  178. for (let i = 0; i < tempStreamList.length; i++) {
  179. tempStreamList[i] = wx.createLivePlayerContext(tempStreamList[i].streamID, this)
  180. }
  181. tempStreamList = []
  182. })
  183. // }, 500)
  184. })
  185. this.status.isOnHideAddStream = false
  186. }
  187. this.status.pageLife = 'show'
  188. },
  189. hide: function() {
  190. // 组件所在的页面被隐藏时执行
  191. console.log(TAG_NAME, 'hide')
  192. this.status.pageLife = 'hide'
  193. },
  194. resize: function(size) {
  195. // 组件所在的页面尺寸变化时执行
  196. console.log(TAG_NAME, 'resize', size)
  197. },
  198. },
  199. /**
  200. * 组件的方法列表
  201. */
  202. methods: {
  203. /**
  204. * 初始化各项参数和用户控制模块,在组件实例触发 attached 时调用,此时不建议对View进行变更渲染(调用setData方法)
  205. */
  206. _init() {
  207. console.log(TAG_NAME, '_init')
  208. this.userController = new UserController(this)
  209. this._emitter = new Event()
  210. this.EVENT = EVENT
  211. this._initStatus()
  212. this._bindEvent()
  213. this._gridBindEvent()
  214. this._keepScreenOn()
  215. console.log(TAG_NAME, '_init success component:', this)
  216. },
  217. _initStatus() {
  218. this.status = {
  219. isPush: false, // 推流状态
  220. isPending: false, // 挂起状态,触发5000事件标记为true,onShow后标记为false
  221. pageLife: '', // 页面生命周期 hide, show
  222. isOnHideAddStream: false, // onHide后有新增Stream
  223. }
  224. this._lastTapTime = 0 // 点击时间戳 用于判断双击事件
  225. this._beforeLastTapTime = 0 // 点击时间戳 用于判断双击事件
  226. this._lastTapCoordinate = { x: 0, y: 0 }, // 点击时的坐标
  227. this._isFullscreen = false // 是否进入全屏状态
  228. },
  229. /**
  230. * 监听组件属性变更,外部变更组件属性时触发该监听
  231. * @param {Object} data newVal,oldVal
  232. */
  233. _propertyObserver(data) {
  234. console.log(TAG_NAME, '_propertyObserver', data, this.data.config)
  235. if (data.name === 'config') {
  236. const config = Object.assign({}, DEFAULT_COMPONENT_CONFIG, data.newVal)
  237. console.log(TAG_NAME, '_propertyObserver config:', config)
  238. // 由于 querystring 只支持 String 类型,做一个类型防御
  239. if (typeof config.debugMode === 'string') {
  240. config.debugMode = config.debugMode === 'true' ? true : false
  241. }
  242. // 初始化IM
  243. if (config.enableIM && config.sdkAppID) {
  244. this._initIM(config)
  245. }
  246. if (config.sdkAppID && data.oldVal.sdkAppID !== config.sdkAppID && MTA) {
  247. MTA.Event.stat('sdkAppID', { 'value': config.sdkAppID })
  248. }
  249. // 独立设置与pusher无关的配置
  250. this.setData({
  251. enableIM: config.enableIM,
  252. template: config.template,
  253. debugMode: config.debugMode || false,
  254. debug: config.debugMode || false,
  255. })
  256. this._setPusherConfig(config)
  257. }
  258. },
  259. // _______ __ __ __
  260. // | \ | \ | \| \
  261. // | $$$$$$$\ __ __ | $$____ | $$ \$$ _______
  262. // | $$__/ $$| \ | \| $$ \ | $$| \ / \
  263. // | $$ $$| $$ | $$| $$$$$$$\| $$| $$| $$$$$$$
  264. // | $$$$$$$ | $$ | $$| $$ | $$| $$| $$| $$
  265. // | $$ | $$__/ $$| $$__/ $$| $$| $$| $$_____
  266. // | $$ \$$ $$| $$ $$| $$| $$ \$$ \
  267. // \$$ \$$$$$$ \$$$$$$$ \$$ \$$ \$$$$$$$
  268. /**
  269. * 进房
  270. * @param {Object} params 必传 roomID 取值范围 1 ~ 4294967295
  271. * @returns {Promise}
  272. */
  273. enterRoom(params) {
  274. return new Promise((resolve, reject) => {
  275. console.log(TAG_NAME, 'enterRoom')
  276. console.log(TAG_NAME, 'params', params)
  277. console.log(TAG_NAME, 'config', this.data.config)
  278. console.log(TAG_NAME, 'pusher', this.data.pusher)
  279. // 1. 补齐进房参数,校验必要参数是否齐全
  280. if (params) {
  281. Object.assign(this.data.pusher, params)
  282. Object.assign(this.data.config, params)
  283. }
  284. console.log(this.data.config,'参数');
  285. if (!this._checkParam(this.data.config)) {
  286. reject(new Error('缺少必要参数'))
  287. return
  288. }
  289. // 2. 根据参数拼接 push url,赋值给 live-pusher,
  290. this._getPushUrl(this.data.config).then((pushUrl)=> {
  291. this.data.pusher.url = pushUrl
  292. this.setData({
  293. pusher: this.data.pusher,
  294. }, () => {
  295. // 真正进房成功需要通过 1018 事件通知
  296. console.log(TAG_NAME, 'enterRoom', this.data.pusher)
  297. // view 渲染成功回调后,开始推流
  298. this.data.pusher.getPusherContext().start()
  299. this.status.isPush = true
  300. resolve()
  301. })
  302. }).catch((res)=> {
  303. // 进房失败需要通过 pusher state 事件通知,目前还没有准确的事件通知
  304. console.error(TAG_NAME, 'enterRoom error', res)
  305. reject(res)
  306. })
  307. // 初始化 IM SDK
  308. // this._initIM(this.data.config)
  309. // 登录IM
  310. this._loginIM({ ...this.data.config, roomID: params.roomID })
  311. })
  312. },
  313. /**
  314. * 退房,停止推流和拉流,并重置数据
  315. * @returns {Promise}
  316. */
  317. exitRoom() {
  318. if (this.status.pageLife === 'hide') {
  319. // 如果是退后台触发 onHide,不能调用 pusher API
  320. console.warn(TAG_NAME, '小程序最小化时不能调用 exitRoom,如果不想听到远端声音,可以调用取消订阅,如果不想远端听到声音,可以调用取消发布')
  321. }
  322. return new Promise((resolve, reject) => {
  323. console.log(TAG_NAME, 'exitRoom')
  324. this._exitIM()
  325. this.data.pusher.reset()
  326. this.status.isPush = false
  327. const result = this.userController.reset()
  328. this.setData({
  329. pusher: this.data.pusher,
  330. userList: result.userList,
  331. streamList: result.streamList,
  332. visibleStreamList: this._filterVisibleStream(result.streamList),
  333. }, () => {
  334. // 在销毁页面时调用exitRoom时,不会走到这里
  335. resolve({ userList: this.data.userList, streamList: this.data.streamList })
  336. console.log(TAG_NAME, 'exitRoom success', this.data.pusher, this.data.streamList, this.data.userList)
  337. // 20200421 iOS 仍然没有1019事件通知退房,退房事件移动到 exitRoom 方法里,但不是后端通知的退房成功
  338. this._emitter.emit(EVENT.LOCAL_LEAVE, { userID: this.data.pusher.userID })
  339. })
  340. })
  341. },
  342. /**
  343. * 开启摄像头
  344. * @returns {Promise}
  345. */
  346. publishLocalVideo() {
  347. // 设置 pusher enableCamera
  348. console.log(TAG_NAME, 'publishLocalVideo 开启摄像头')
  349. return this._setPusherConfig({ enableCamera: true })
  350. },
  351. /**
  352. * 关闭摄像头
  353. * @returns {Promise}
  354. */
  355. unpublishLocalVideo() {
  356. // 设置 pusher enableCamera
  357. console.log(TAG_NAME, 'unpublshLocalVideo 关闭摄像头')
  358. return this._setPusherConfig({ enableCamera: false })
  359. },
  360. /**
  361. * 开启麦克风
  362. * @returns {Promise}
  363. */
  364. publishLocalAudio() {
  365. // 设置 pusher enableCamera
  366. console.log(TAG_NAME, 'publishLocalAudio 开启麦克风')
  367. return this._setPusherConfig({ enableMic: true })
  368. },
  369. /**
  370. * 关闭麦克风
  371. * @returns {Promise}
  372. */
  373. unpublishLocalAudio() {
  374. // 设置 pusher enableCamera
  375. console.log(TAG_NAME, 'unpublshLocalAudio 关闭麦克风')
  376. return this._setPusherConfig({ enableMic: false })
  377. },
  378. /**
  379. * 订阅远端视频 主流 小画面 辅流
  380. * @param {Object} params {userID,streamType} streamType 传入 small 时修改对应的主流 url 的 _definitionType 参数为 small, stream.streamType 仍为 main
  381. * @returns {Promise}
  382. */
  383. subscribeRemoteVideo(params) {
  384. console.log(TAG_NAME, 'subscribeRemoteVideo', params)
  385. // 设置指定 user streamType 的 muteVideo 为 false
  386. const config = {
  387. muteVideo: false,
  388. }
  389. // 本地数据结构里的 streamType 只支持 main 和 aux ,订阅 small 也是对 main 进行处理
  390. const streamType = params.streamType === 'small' ? 'main' : params.streamType
  391. const stream = this.userController.getStream({
  392. userID: params.userID,
  393. streamType: streamType,
  394. })
  395. stream.muteVideoPrev = false // 用于分页切换时保留player当前的订阅状态
  396. if (params.streamType === 'small' || params.streamType === 'main') {
  397. if (stream && stream.streamType === 'main') {
  398. console.log(TAG_NAME, 'subscribeRemoteVideo switch small', stream.src)
  399. if (params.streamType === 'small') {
  400. config.src = stream.src.replace('main', 'small')
  401. config._definitionType = 'small' // 用于设置面板的渲染
  402. } else if (params.streamType === 'main') {
  403. stream.src = stream.src.replace('small', 'main')
  404. config._definitionType = 'main'
  405. }
  406. console.log(TAG_NAME, 'subscribeRemoteVideo', stream.src)
  407. }
  408. }
  409. return this._setPlayerConfig({
  410. userID: params.userID,
  411. streamType: streamType,
  412. config: config,
  413. })
  414. },
  415. /**
  416. * 取消订阅远端视频
  417. * @param {Object} params {userID,streamType}
  418. * @returns {Promise}
  419. */
  420. unsubscribeRemoteVideo(params) {
  421. console.log(TAG_NAME, 'unsubscribeRemoteVideo', params)
  422. const stream = this.userController.getStream({
  423. userID: params.userID,
  424. streamType: params.streamType,
  425. })
  426. stream.muteVideoPrev = true // 用于分页切换时保留player当前的订阅状态
  427. // 设置指定 user streamType 的 muteVideo 为 true
  428. return this._setPlayerConfig({
  429. userID: params.userID,
  430. streamType: params.streamType,
  431. config: {
  432. muteVideo: true,
  433. },
  434. })
  435. },
  436. /**
  437. * 订阅远端音频
  438. * @param {Object} params userID 用户ID
  439. * @returns {Promise}
  440. */
  441. subscribeRemoteAudio(params) {
  442. console.log(TAG_NAME, 'subscribeRemoteAudio', params)
  443. return this._setPlayerConfig({
  444. userID: params.userID,
  445. streamType: 'main',
  446. config: {
  447. muteAudio: false,
  448. },
  449. })
  450. },
  451. /**
  452. * 取消订阅远端音频
  453. * @param {Object} params userID 用户ID
  454. * @returns {Promise}
  455. */
  456. unsubscribeRemoteAudio(params) {
  457. console.log(TAG_NAME, 'unsubscribeRemoteAudio', params)
  458. return this._setPlayerConfig({
  459. userID: params.userID,
  460. streamType: 'main',
  461. config: {
  462. muteAudio: true,
  463. },
  464. })
  465. },
  466. on(eventCode, handler, context) {
  467. this._emitter.on(eventCode, handler, context)
  468. },
  469. off(eventCode, handler) {
  470. this._emitter.off(eventCode, handler)
  471. },
  472. getRemoteUserList() {
  473. return this.data.userList
  474. },
  475. /**
  476. * 切换前后摄像头
  477. */
  478. switchCamera() {
  479. if (!this.data.cameraPosition) {
  480. // this.data.pusher.cameraPosition 是初始值,不支持动态设置
  481. this.data.cameraPosition = this.data.pusher.frontCamera
  482. }
  483. console.log(TAG_NAME, 'switchCamera', this.data.cameraPosition)
  484. this.data.cameraPosition = this.data.cameraPosition === 'front' ? 'back' : 'front'
  485. this.setData({
  486. cameraPosition: this.data.cameraPosition,
  487. }, () => {
  488. console.log(TAG_NAME, 'switchCamera success', this.data.cameraPosition)
  489. })
  490. // wx 7.0.9 不支持动态设置 pusher.frontCamera ,只支持调用 API switchCamer() 设置,这里修改 cameraPosition 是为了记录状态
  491. this.data.pusher.getPusherContext().switchCamera()
  492. },
  493. /**
  494. * 设置指定player view的渲染坐标和尺寸
  495. * @param {object} params
  496. * userID: string
  497. * streamType: string
  498. * xAxis: number
  499. * yAxis: number
  500. * width: number
  501. * height: number
  502. * @returns {Promise}
  503. */
  504. setViewRect(params) {
  505. console.log(TAG_NAME, 'setViewRect', params)
  506. if (this.data.template !== 'custom') {
  507. console.warn(`如需使用setViewRect方法,请初始化时设置template:"custom", 当前 template:"${this.data.template}"`)
  508. }
  509. console.info(`不建议使用该方法动态修改样式,避免引起微信小程序渲染问题,建议直接修改 wxml wxss 进行样式定制化`)
  510. if (this.data.pusher.userID === params.userID) {
  511. return this._setPusherConfig({
  512. xAxis: params.xAxis,
  513. yAxis: params.yAxis,
  514. width: params.width,
  515. height: params.height,
  516. })
  517. }
  518. return this._setPlayerConfig({
  519. userID: params.userID,
  520. streamType: params.streamType,
  521. config: {
  522. xAxis: params.xAxis,
  523. yAxis: params.yAxis,
  524. width: params.width,
  525. height: params.height,
  526. },
  527. })
  528. },
  529. /**
  530. * 设置指定 player 或者 pusher view 是否可见
  531. * @param {object} params
  532. * userID: string
  533. * streamType: string
  534. * isVisible:boolean
  535. * @returns {Promise}
  536. */
  537. setViewVisible(params) {
  538. console.log(TAG_NAME, 'setViewVisible', params)
  539. if (this.data.template !== 'custom') {
  540. console.warn(`如需使用setViewVisible方法,请初始化时设置template:"custom", 当前 template:"${this.data.template}"`)
  541. }
  542. console.info(`不建议使用该方法动态修改样式,避免引起微信小程序渲染问题,建议直接修改 wxml wxss 进行样式定制化`)
  543. if (this.data.pusher.userID === params.userID) {
  544. return this._setPusherConfig({
  545. isVisible: params.isVisible,
  546. })
  547. }
  548. return this._setPlayerConfig({
  549. userID: params.userID,
  550. streamType: params.streamType,
  551. config: {
  552. isVisible: params.isVisible,
  553. },
  554. })
  555. },
  556. /**
  557. * 设置指定player view的层级
  558. * @param {Object} params
  559. * userID: string
  560. * streamType: string
  561. * zIndex: number
  562. * @returns {Promise}
  563. */
  564. setViewZIndex(params) {
  565. console.log(TAG_NAME, 'setViewZIndex', params)
  566. if (this.data.template !== 'custom') {
  567. console.warn(`如需使用setViewZIndex方法,请初始化时设置template:"custom", 当前 template:"${this.data.template}"`)
  568. }
  569. console.info(`不建议使用该方法动态修改样式,避免引起微信小程序渲染问题,建议直接修改 wxml wxss 进行样式定制化`)
  570. if (this.data.pusher.userID === params.userID) {
  571. return this._setPusherConfig({
  572. zIndex: params.zindex || params.zIndex,
  573. })
  574. }
  575. return this._setPlayerConfig({
  576. userID: params.userID,
  577. streamType: params.streamType,
  578. config: {
  579. zIndex: params.zindex || params.zIndex,
  580. },
  581. })
  582. },
  583. /**
  584. * 播放背景音
  585. * @param {Object} params url
  586. * @returns {Promise}
  587. */
  588. playBGM(params) {
  589. return new Promise((resolve, reject) => {
  590. this.data.pusher.getPusherContext().playBGM({
  591. url: params.url,
  592. // 已经有相关事件不需要在这里监听,目前用于测试
  593. success: () => {
  594. console.log(TAG_NAME, '播放背景音成功')
  595. // this._emitter.emit(EVENT.BGM_PLAY_START)
  596. resolve()
  597. },
  598. fail: () => {
  599. console.log(TAG_NAME, '播放背景音失败')
  600. this._emitter.emit(EVENT.BGM_PLAY_FAIL)
  601. reject(new Error('播放背景音失败'))
  602. },
  603. // complete: () => {
  604. // console.log(TAG_NAME, '背景完成')
  605. // this._emitter.emit(EVENT.BGM_PLAY_COMPLETE)
  606. // },
  607. })
  608. })
  609. },
  610. stopBGM() {
  611. this.data.pusher.getPusherContext().stopBGM()
  612. },
  613. pauseBGM() {
  614. this.data.pusher.getPusherContext().pauseBGM()
  615. },
  616. resumeBGM() {
  617. this.data.pusher.getPusherContext().resumeBGM()
  618. },
  619. /**
  620. * 设置背景音音量
  621. * @param {Object} params volume
  622. */
  623. setBGMVolume(params) {
  624. console.log(TAG_NAME, 'setBGMVolume', params)
  625. this.data.pusher.getPusherContext().setBGMVolume({ volume: params.volume })
  626. },
  627. /**
  628. * 设置麦克风音量
  629. * @param {Object} params volume
  630. */
  631. setMICVolume(params) {
  632. console.log(TAG_NAME, 'setMICVolume', params)
  633. this.data.pusher.getPusherContext().setMICVolume({ volume: params.volume })
  634. },
  635. /**
  636. * 发送SEI消息
  637. * @param {Object} params message
  638. * @returns {Promise}
  639. */
  640. sendSEI(params) {
  641. return new Promise((resolve, reject) => {
  642. this.data.pusher.getPusherContext().sendMessage({
  643. msg: params.message,
  644. success: function(result) {
  645. resolve(result)
  646. },
  647. })
  648. })
  649. },
  650. /**
  651. * pusher 和 player 的截图并保存
  652. * @param {Object} params userID streamType
  653. * @returns {Promise}
  654. */
  655. snapshot(params) {
  656. console.log(TAG_NAME, 'snapshot', params)
  657. return new Promise((resolve, reject) => {
  658. this.captureSnapshot(params).then((result)=>{
  659. wx.saveImageToPhotosAlbum({
  660. filePath: result.tempImagePath,
  661. success(res) {
  662. wx.showToast({
  663. title: '已保存到相册',
  664. })
  665. console.log('save photo is success', res)
  666. resolve(result)
  667. },
  668. fail: function(error) {
  669. wx.showToast({
  670. icon: 'none',
  671. title: '保存失败',
  672. })
  673. console.log('save photo is fail', error)
  674. reject(error)
  675. },
  676. })
  677. }).catch((error)=>{
  678. reject(error)
  679. })
  680. })
  681. },
  682. /**
  683. * 获取pusher 和 player 的截图
  684. * @param {Object} params userID streamType
  685. * @returns {Promise}
  686. */
  687. captureSnapshot(params) {
  688. return new Promise((resolve, reject) => {
  689. if (params.userID === this.data.pusher.userID) {
  690. // pusher
  691. this.data.pusher.getPusherContext().snapshot({
  692. quality: 'raw',
  693. complete: (result) => {
  694. console.log(TAG_NAME, 'snapshot pusher', result)
  695. if (result.tempImagePath) {
  696. resolve(result)
  697. } else {
  698. console.log('snapShot 回调失败', result)
  699. reject(new Error('截图失败'))
  700. }
  701. },
  702. })
  703. } else {
  704. // player
  705. this.userController.getStream(params).playerContext.snapshot({
  706. quality: 'raw',
  707. complete: (result) => {
  708. console.log(TAG_NAME, 'snapshot player', result)
  709. if (result.tempImagePath) {
  710. resolve(result)
  711. } else {
  712. console.log('snapShot 回调失败', result)
  713. reject(new Error('截图失败'))
  714. }
  715. },
  716. })
  717. }
  718. })
  719. },
  720. /**
  721. * 将远端视频全屏
  722. * @param {Object} params userID streamType direction
  723. * @returns {Promise}
  724. */
  725. enterFullscreen(params) {
  726. console.log(TAG_NAME, 'enterFullscreen', params)
  727. return new Promise((resolve, reject) => {
  728. this.userController.getStream(params).playerContext.requestFullScreen({
  729. direction: params.direction || 0,
  730. success: (event) => {
  731. console.log(TAG_NAME, 'enterFullscreen success', event)
  732. resolve(event)
  733. },
  734. fail: (event) => {
  735. console.log(TAG_NAME, 'enterFullscreen fail', event)
  736. reject(event)
  737. },
  738. })
  739. })
  740. },
  741. /**
  742. * 将远端视频取消全屏
  743. * @param {Object} params userID streamType
  744. * @returns {Promise}
  745. */
  746. exitFullscreen(params) {
  747. console.log(TAG_NAME, 'exitFullscreen', params)
  748. return new Promise((resolve, reject) => {
  749. this.userController.getStream(params).playerContext.exitFullScreen({
  750. success: (event) => {
  751. console.log(TAG_NAME, 'exitFullScreen success', event)
  752. resolve(event)
  753. },
  754. fail: (event) => {
  755. console.log(TAG_NAME, 'exitFullScreen fail', event)
  756. reject(event)
  757. },
  758. })
  759. })
  760. },
  761. /**
  762. * 设置 player 视图的横竖屏显示
  763. * @param {Object} params userID streamType orientation: vertical, horizontal
  764. * @returns {Promise}
  765. */
  766. setRemoteOrientation(params) {
  767. return this._setPlayerConfig({
  768. userID: params.userID,
  769. streamType: params.streamType,
  770. config: {
  771. orientation: params.orientation,
  772. },
  773. })
  774. },
  775. // 改为:
  776. setViewOrientation(params) {
  777. return this._setPlayerConfig({
  778. userID: params.userID,
  779. streamType: params.streamType,
  780. config: {
  781. orientation: params.orientation,
  782. },
  783. })
  784. },
  785. /**
  786. * 设置 player 视图的填充模式
  787. * @param {Object} params userID streamType fillMode: contain,fillCrop
  788. * @returns {Promise}
  789. */
  790. setRemoteFillMode(params) {
  791. return this._setPlayerConfig({
  792. userID: params.userID,
  793. streamType: params.streamType,
  794. config: {
  795. objectFit: params.fillMode,
  796. },
  797. })
  798. },
  799. // 改为:
  800. setViewFillMode(params) {
  801. return this._setPlayerConfig({
  802. userID: params.userID,
  803. streamType: params.streamType,
  804. config: {
  805. objectFit: params.fillMode,
  806. },
  807. })
  808. },
  809. /**
  810. * 发送C2C文本消息
  811. * @param {*} params userID,message
  812. * @returns {Promise}
  813. */
  814. sendC2CTextMessage(params) {
  815. if (!this.tim) {
  816. console.warn(TAG_NAME, '未开启IM功能,该方法无法使用', params)
  817. return
  818. }
  819. console.log(TAG_NAME, 'sendC2CTextMessage', params)
  820. const message = this.tim.createTextMessage({
  821. to: params.userID + '',
  822. conversationType: TIM.TYPES.CONV_C2C,
  823. payload: {
  824. text: params.message,
  825. },
  826. })
  827. const promise = this.tim.sendMessage(message)
  828. promise.then(function(imResponse) {
  829. // 发送成功
  830. console.log(TAG_NAME, 'sendC2CTextMessage success', imResponse)
  831. }).catch(function(imError) {
  832. // 发送失败
  833. console.warn(TAG_NAME, 'sendC2CTextMessage error:', imError)
  834. })
  835. return promise
  836. },
  837. /**
  838. * 发送C2C自定义消息
  839. * @param {*} params: userID payload
  840. * @returns {Promise}
  841. *
  842. */
  843. sendC2CCustomMessage(params) {
  844. if (!this.tim) {
  845. console.warn(TAG_NAME, '未开启IM功能,该方法无法使用', params)
  846. return
  847. }
  848. console.log(TAG_NAME, 'sendC2CCustomMessage', params)
  849. const message = this.tim.createCustomMessage({
  850. to: params.userID + '',
  851. conversationType: TIM.TYPES.CONV_C2C,
  852. payload: params.payload,
  853. })
  854. const promise = this.tim.sendMessage(message)
  855. promise.then(function(imResponse) {
  856. // 发送成功
  857. console.log(TAG_NAME, 'sendMessage success', imResponse)
  858. }).catch(function(imError) {
  859. // 发送失败
  860. console.warn(TAG_NAME, 'sendMessage error:', imError)
  861. })
  862. return promise
  863. },
  864. /**
  865. * 发送群组文本消息
  866. * @param {*} params roomID message
  867. * @returns {Promise}
  868. *
  869. */
  870. sendGroupTextMessage(params) {
  871. if (!this.tim) {
  872. console.warn(TAG_NAME, '未开启IM功能,该方法无法使用', params)
  873. return
  874. }
  875. console.log(TAG_NAME, 'sendGroupTextMessage', params)
  876. const message = this.tim.createTextMessage({
  877. to: params.roomID + '',
  878. conversationType: TIM.TYPES.CONV_GROUP,
  879. payload: {
  880. text: params.message,
  881. },
  882. })
  883. const promise = this.tim.sendMessage(message)
  884. promise.then(function(imResponse) {
  885. // 发送成功
  886. console.log(TAG_NAME, 'sendGroupTextMessage success', imResponse)
  887. }).catch(function(imError) {
  888. // 发送失败
  889. console.warn(TAG_NAME, 'sendGroupTextMessage error:', imError)
  890. })
  891. return promise
  892. },
  893. /**
  894. * 发送群组自定义消息
  895. * @param {*} params roomID payload
  896. * @returns {Promise}
  897. *
  898. */
  899. sendGroupCustomMessage(params) {
  900. if (!this.tim) {
  901. console.warn(TAG_NAME, '未开启IM功能,该方法无法使用', params)
  902. return
  903. }
  904. console.log(TAG_NAME, 'sendGroupCustomMessage', params)
  905. const message = this.tim.createCustomMessage({
  906. to: params.roomID + '',
  907. conversationType: TIM.TYPES.CONV_GROUP,
  908. payload: params.payload,
  909. })
  910. const promise = this.tim.sendMessage(message)
  911. promise.then(function(imResponse) {
  912. // 发送成功
  913. console.log(TAG_NAME, 'sendMessage success', imResponse)
  914. }).catch(function(imError) {
  915. // 发送失败
  916. console.warn(TAG_NAME, 'sendMessage error:', imError)
  917. })
  918. return promise
  919. },
  920. // ______ __ __
  921. // | \ | \ | \
  922. // \$$$$$$ _______ _| $$_ ______ ______ _______ ______ | $$
  923. // | $$ | \| $$ \ / \ / \ | \ | \ | $$
  924. // | $$ | $$$$$$$\\$$$$$$ | $$$$$$\| $$$$$$\| $$$$$$$\ \$$$$$$\| $$
  925. // | $$ | $$ | $$ | $$ __ | $$ $$| $$ \$$| $$ | $$ / $$| $$
  926. // _| $$_ | $$ | $$ | $$| \| $$$$$$$$| $$ | $$ | $$| $$$$$$$| $$
  927. // | $$ \| $$ | $$ \$$ $$ \$$ \| $$ | $$ | $$ \$$ $$| $$
  928. // \$$$$$$ \$$ \$$ \$$$$ \$$$$$$$ \$$ \$$ \$$ \$$$$$$$ \$$
  929. /**
  930. * 设置推流参数并触发页面渲染更新
  931. * @param {Object} config live-pusher 的配置
  932. * @returns {Promise}
  933. */
  934. _setPusherConfig(config, skipLog = false) {
  935. if (!skipLog) {
  936. console.log(TAG_NAME, '_setPusherConfig', config, this.data.pusher)
  937. }
  938. return new Promise((resolve, reject) => {
  939. if (!this.data.pusher) {
  940. this.data.pusher = new Pusher(config)
  941. } else {
  942. Object.assign(this.data.pusher, config)
  943. }
  944. this.setData({
  945. pusher: this.data.pusher,
  946. }, () => {
  947. if (!skipLog) {
  948. console.log(TAG_NAME, '_setPusherConfig setData compelete', 'config:', config, 'pusher:', this.data.pusher)
  949. }
  950. resolve(config)
  951. })
  952. })
  953. },
  954. /**
  955. * 设置指定 player 属性并触发页面渲染
  956. * @param {Object} params include userID,streamType,config
  957. * @returns {Promise}
  958. */
  959. _setPlayerConfig(params) {
  960. const userID = params.userID
  961. const streamType = params.streamType
  962. const config = params.config
  963. console.log(TAG_NAME, '_setPlayerConfig', params)
  964. return new Promise((resolve, reject) => {
  965. // 获取指定的userID streamType 的 stream
  966. const user = this.userController.getUser(userID)
  967. if (user && user.streams[streamType]) {
  968. Object.assign(user.streams[streamType], config)
  969. // user.streams引用的对象和 streamList 里的是同一个
  970. this.setData({
  971. streamList: this.data.streamList,
  972. visibleStreamList: this._filterVisibleStream(this.data.streamList, true),
  973. }, () => {
  974. // console.log(TAG_NAME, '_setPlayerConfig complete', params, 'streamList:', this.data.streamList)
  975. resolve(params)
  976. })
  977. } else {
  978. // 不需要reject,静默处理
  979. console.warn(TAG_NAME, '指定 userID 或者 streamType 不存在')
  980. // reject(new Error('指定 userID 或者 streamType 不存在'))
  981. }
  982. })
  983. },
  984. /**
  985. * 设置列表数据,并触发页面渲染
  986. * @param {Object} params include userList, stramList
  987. * @returns {Promise}
  988. */
  989. _setList(params) {
  990. console.log(TAG_NAME, '_setList', params, this.data.template)
  991. const { userList, streamList } = params
  992. return new Promise((resolve, reject) => {
  993. let visibleStreamList = []
  994. const data = {
  995. userList: userList || this.data.userList,
  996. streamList: streamList || this.data.streamList,
  997. }
  998. if (this.data.template === 'grid') {
  999. visibleStreamList = this._filterVisibleStream(streamList)
  1000. data.visibleStreamList = visibleStreamList || this.data.visibleStreamList
  1001. data.gridPagePlaceholderStreamList = this.data.gridPagePlaceholderStreamList
  1002. data.gridCurrentPage = this.data.gridCurrentPage
  1003. data.gridPageCount = this.data.gridPageCount
  1004. }
  1005. this.setData(data, () => {
  1006. resolve(params)
  1007. })
  1008. })
  1009. },
  1010. /**
  1011. * 必选参数检测
  1012. * @param {Object} rtcConfig rtc参数
  1013. * @returns {Boolean}
  1014. */
  1015. _checkParam(rtcConfig) {
  1016. console.log(TAG_NAME, 'checkParam config:', rtcConfig)
  1017. if (!rtcConfig.sdkAppID) {
  1018. console.error('未设置 sdkAppID')
  1019. return false
  1020. }
  1021. if (rtcConfig.roomID === undefined) {
  1022. console.error('未设置 roomID')
  1023. return false
  1024. }
  1025. if (rtcConfig.roomID < 1 || rtcConfig.roomID > 4294967296) {
  1026. console.error('roomID 超出取值范围 1 ~ 4294967295')
  1027. return false
  1028. }
  1029. if (!rtcConfig.userID) {
  1030. console.error('未设置 userID')
  1031. return false
  1032. }
  1033. if (!rtcConfig.userSig) {
  1034. console.error('未设置 userSig')
  1035. return false
  1036. }
  1037. if (!rtcConfig.template) {
  1038. console.error('未设置 template')
  1039. return false
  1040. }
  1041. return true
  1042. },
  1043. _getPushUrl(rtcConfig) {
  1044. // 拼接 puhser url rtmp 方案
  1045. console.log(TAG_NAME, '_getPushUrl', rtcConfig)
  1046. if (ENV.IS_TRTC) {
  1047. // 版本高于7.0.8,基础库版本高于2.10.0 使用新的 url
  1048. return new Promise((resolve, reject) => {
  1049. // appscene videocall live
  1050. // cloudenv PRO CCC DEV UAT
  1051. // encsmall 0
  1052. // 对外的默认值是rtc ,对内的默认值是videocall
  1053. rtcConfig.scene = !rtcConfig.scene || rtcConfig.scene === 'rtc' ? 'videocall' : rtcConfig.scene
  1054. rtcConfig.enableBlackStream = rtcConfig.enableBlackStream || '' // 是否支持在纯音频下推送SEI消息,注意:在关闭enable-recv-message后还是无法接收
  1055. rtcConfig.encsmall = rtcConfig.encsmall || 0 // 是否编小画面,这个特性不建议学生默认开启,只有老师端才比较有意义
  1056. rtcConfig.cloudenv = rtcConfig.cloudenv || 'PRO'
  1057. rtcConfig.streamID = rtcConfig.streamID || '' // 指定旁边路直播的流ID
  1058. rtcConfig.userDefineRecordID = rtcConfig.userDefineRecordID || '' // 指定录制文件的recordid
  1059. rtcConfig.privateMapKey = rtcConfig.privateMapKey || '' // 字符串房间号
  1060. rtcConfig.pureAudioMode = rtcConfig.pureAudioMode || ''// 指定是否纯音频推流及录制,默认不填,值为1 或 2,其他值非法不处理
  1061. rtcConfig.recvMode = rtcConfig.recvMode || 1 // 1. 自动接收音视频 2. 仅自动接收音频 3. 仅自动接收视频 4. 音视频都不自动接收, 不能绑定player
  1062. let roomID = ''
  1063. if (/^\d+$/.test(rtcConfig.roomID)) {
  1064. // 数字房间号
  1065. roomID = '&roomid=' + rtcConfig.roomID
  1066. } else {
  1067. // 字符串房间号
  1068. roomID = '&strroomid=' + rtcConfig.roomID
  1069. }
  1070. setTimeout(()=> {
  1071. const pushUrl = 'room://cloud.tencent.com/rtc?sdkappid=' + rtcConfig.sdkAppID +
  1072. roomID +
  1073. '&userid=' + rtcConfig.userID +
  1074. '&usersig=' + rtcConfig.userSig +
  1075. '&appscene=' + rtcConfig.scene +
  1076. '&encsmall=' + rtcConfig.encsmall +
  1077. '&cloudenv=' + rtcConfig.cloudenv +
  1078. '&enableBlackStream=' + rtcConfig.enableBlackStream +
  1079. '&streamid=' + rtcConfig.streamID +
  1080. '&userdefinerecordid=' + rtcConfig.userDefineRecordID +
  1081. '&privatemapkey=' + rtcConfig.privateMapKey +
  1082. '&pureaudiomode=' + rtcConfig.pureAudioMode +
  1083. '&recvmode=' + rtcConfig.recvMode
  1084. console.warn(TAG_NAME, 'getPushUrl result:', pushUrl)
  1085. resolve(pushUrl)
  1086. }, 0)
  1087. })
  1088. }
  1089. console.error(TAG_NAME, '组件仅支持微信 App iOS >=7.0.9, Android >= 7.0.8, 小程序基础库版 >= 2.10.0')
  1090. console.error(TAG_NAME, '需要真机运行,开发工具不支持实时音视频')
  1091. },
  1092. /**
  1093. * 获取签名和推流地址
  1094. * @param {Object} rtcConfig 进房参数配置
  1095. * @returns {Promise}
  1096. */
  1097. _requestSigServer(rtcConfig) {
  1098. console.log(TAG_NAME, '_requestSigServer:', rtcConfig)
  1099. const sdkAppID = rtcConfig.sdkAppID
  1100. const userID = rtcConfig.userID
  1101. const userSig = rtcConfig.userSig
  1102. const roomID = rtcConfig.roomID
  1103. const privateMapKey = rtcConfig.privateMapKey
  1104. rtcConfig.useCloud = rtcConfig.useCloud === undefined ? true : rtcConfig.useCloud
  1105. let url = rtcConfig.useCloud ? 'https://official.opensso.tencent-cloud.com/v4/openim/jsonvideoapp' : 'https://yun.tim.qq.com/v4/openim/jsonvideoapp'
  1106. url += '?sdkappid=' + sdkAppID + '&identifier=' + userID + '&usersig=' + userSig + '&random=' + Date.now() + '&contenttype=json'
  1107. const reqHead = {
  1108. 'Cmd': 1,
  1109. 'SeqNo': 1,
  1110. 'BusType': 7,
  1111. 'GroupId': roomID,
  1112. }
  1113. const reqBody = {
  1114. 'PrivMapEncrypt': privateMapKey,
  1115. 'TerminalType': 1,
  1116. 'FromType': 3,
  1117. 'SdkVersion': 26280566,
  1118. }
  1119. console.log(TAG_NAME, '_requestSigServer:', url, reqHead, reqBody)
  1120. return new Promise((resolve, reject) => {
  1121. wx.request({
  1122. url: url,
  1123. data: {
  1124. 'ReqHead': reqHead,
  1125. 'ReqBody': reqBody,
  1126. },
  1127. method: 'POST',
  1128. success: (res) => {
  1129. console.log('_requestSigServer success:', res)
  1130. if (res.data['ErrorCode'] || res.data['RspHead']['ErrorCode'] !== 0) {
  1131. // console.error(res.data['ErrorInfo'] || res.data['RspHead']['ErrorInfo'])
  1132. console.error('获取roomsig失败')
  1133. reject(res)
  1134. }
  1135. const roomSig = JSON.stringify(res.data['RspBody'])
  1136. let pushUrl = 'room://cloud.tencent.com?sdkappid=' + sdkAppID + '&roomid=' + roomID + '&userid=' + userID + '&roomsig=' + encodeURIComponent(roomSig)
  1137. // TODO 需要重新整理的逻辑 TRTC尚未支持 20200213
  1138. // 如果有配置纯音频推流或者recordId参数
  1139. if (rtcConfig.pureAudioPushMod || rtcConfig.recordId) {
  1140. const bizbuf = {
  1141. Str_uc_params: {
  1142. pure_audio_push_mod: 0,
  1143. record_id: 0,
  1144. },
  1145. }
  1146. // 纯音频推流
  1147. if (rtcConfig.pureAudioPushMod) {
  1148. bizbuf.Str_uc_params.pure_audio_push_mod = rtcConfig.pureAudioPushMod
  1149. } else {
  1150. delete bizbuf.Str_uc_params.pure_audio_push_mod
  1151. }
  1152. // 自动录制时业务自定义id
  1153. if (rtcConfig.recordId) {
  1154. bizbuf.Str_uc_params.record_id = rtcConfig.recordId
  1155. } else {
  1156. delete bizbuf.Str_uc_params.record_id
  1157. }
  1158. pushUrl += '&bizbuf=' + encodeURIComponent(JSON.stringify(bizbuf))
  1159. }
  1160. console.log('roomSigInfo', pushUrl)
  1161. resolve(pushUrl)
  1162. },
  1163. fail: (res) => {
  1164. console.log(TAG_NAME, 'requestSigServer fail:', res)
  1165. reject(res)
  1166. },
  1167. })
  1168. })
  1169. },
  1170. _doubleTabToggleFullscreen(event) {
  1171. const curTime = event.timeStamp
  1172. const lastTime = this._lastTapTime
  1173. const lastTapCoordinate = this._lastTapCoordinate
  1174. const currentTapCoordinate = event.detail
  1175. // 计算两次点击的距离
  1176. const distence = Math.sqrt(Math.pow(Math.abs(currentTapCoordinate.x - lastTapCoordinate.x), 2) + Math.pow(Math.abs(currentTapCoordinate.y - lastTapCoordinate.y), 2))
  1177. this._lastTapCoordinate = currentTapCoordinate
  1178. // 已知问题:上次全屏操作后,必须等待1.5s后才能再次进行全屏操作,否则引发SDK全屏异常,因此增加节流逻辑
  1179. const beforeLastTime = this._beforeLastTapTime
  1180. console.log(TAG_NAME, '_doubleTabToggleFullscreen', event, lastTime, beforeLastTime, distence)
  1181. if (curTime - lastTime > 0 && curTime - lastTime < 300 && lastTime - beforeLastTime > 1500 && distence < 20) {
  1182. const userID = event.currentTarget.dataset.userid
  1183. const streamType = event.currentTarget.dataset.streamtype
  1184. if (this._isFullscreen) {
  1185. this.exitFullscreen({ userID, streamType }).then(() => {
  1186. this._isFullscreen = false
  1187. }).catch(() => {
  1188. })
  1189. } else {
  1190. // const stream = this.userController.getStream({ userID, streamType })
  1191. let direction
  1192. // // 已知问题:视频的尺寸需要等待player触发NetStatus事件才能获取到,如果进房就双击全屏,全屏后的方向有可能不对。
  1193. // if (stream && stream.videoWidth && stream.videoHeight) {
  1194. // // 如果是横视频,全屏时进行横屏处理。如果是竖视频,则为0
  1195. // direction = stream.videoWidth > stream.videoHeight ? 90 : 0
  1196. // }
  1197. this.enterFullscreen({ userID, streamType, direction }).then(() => {
  1198. this._isFullscreen = true
  1199. }).catch(() => {
  1200. })
  1201. }
  1202. this._beforeLastTapTime = lastTime
  1203. }
  1204. this._lastTapTime = curTime
  1205. },
  1206. /**
  1207. * TRTC-room 远端用户和音视频状态处理
  1208. */
  1209. _bindEvent() {
  1210. // 远端用户进房
  1211. this.userController.on(EVENT.REMOTE_USER_JOIN, (event)=>{
  1212. console.log(TAG_NAME, '远端用户进房', event, event.data.userID)
  1213. this.setData({
  1214. userList: event.data.userList,
  1215. }, () => {
  1216. this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: event.data.userID })
  1217. })
  1218. console.log(TAG_NAME, 'REMOTE_USER_JOIN', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1219. })
  1220. // 远端用户离开
  1221. this.userController.on(EVENT.REMOTE_USER_LEAVE, (event)=>{
  1222. console.log(TAG_NAME, '远端用户离开', event, event.data.userID)
  1223. if (event.data.userID) {
  1224. this._setList({
  1225. userList: event.data.userList,
  1226. streamList: event.data.streamList,
  1227. }).then(() => {
  1228. this._emitter.emit(EVENT.REMOTE_USER_LEAVE, { userID: event.data.userID })
  1229. })
  1230. }
  1231. console.log(TAG_NAME, 'REMOTE_USER_LEAVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1232. })
  1233. // 视频状态 true
  1234. this.userController.on(EVENT.REMOTE_VIDEO_ADD, (event)=>{
  1235. console.log(TAG_NAME, '远端视频可用', event, event.data.stream.userID)
  1236. const stream = event.data.stream
  1237. // 如果Android onHide 时,新增的player 无法播放 记录标识位
  1238. if (this.status.pageLife === 'hide') {
  1239. this.status.isOnHideAddStream = true
  1240. stream.isOnHideAdd = true
  1241. }
  1242. this._setList({
  1243. userList: event.data.userList,
  1244. streamList: event.data.streamList,
  1245. }).then(() => {
  1246. // 完善 的stream 的 playerContext
  1247. stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
  1248. // 新增的需要触发一次play 默认属性才能生效
  1249. // stream.playerContext.play()
  1250. // console.log(TAG_NAME, 'REMOTE_VIDEO_ADD playerContext.play()', stream)
  1251. this._emitter.emit(EVENT.REMOTE_VIDEO_ADD, { userID: stream.userID, streamType: stream.streamType })
  1252. })
  1253. console.log(TAG_NAME, 'REMOTE_VIDEO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1254. })
  1255. // 视频状态 false
  1256. this.userController.on(EVENT.REMOTE_VIDEO_REMOVE, (event)=>{
  1257. console.log(TAG_NAME, '远端视频移除', event, event.data.stream.userID)
  1258. const stream = event.data.stream
  1259. this._setList({
  1260. userList: event.data.userList,
  1261. streamList: event.data.streamList,
  1262. }).then(() => {
  1263. // 有可能先触发了退房事件,用户名下的所有stream都已清除
  1264. if (stream.userID && stream.streamType) {
  1265. this._emitter.emit(EVENT.REMOTE_VIDEO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
  1266. }
  1267. })
  1268. console.log(TAG_NAME, 'REMOTE_VIDEO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1269. })
  1270. // 音频可用
  1271. this.userController.on(EVENT.REMOTE_AUDIO_ADD, (event)=>{
  1272. console.log(TAG_NAME, '远端音频可用', event)
  1273. const stream = event.data.stream
  1274. this._setList({
  1275. userList: event.data.userList,
  1276. streamList: event.data.streamList,
  1277. }).then(() => {
  1278. stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
  1279. // 新增的需要触发一次play 默认属性才能生效
  1280. // stream.playerContext.play()
  1281. // console.log(TAG_NAME, 'REMOTE_AUDIO_ADD playerContext.play()', stream)
  1282. this._emitter.emit(EVENT.REMOTE_AUDIO_ADD, { userID: stream.userID, streamType: stream.streamType })
  1283. })
  1284. console.log(TAG_NAME, 'REMOTE_AUDIO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1285. })
  1286. // 音频不可用
  1287. this.userController.on(EVENT.REMOTE_AUDIO_REMOVE, (event)=>{
  1288. console.log(TAG_NAME, '远端音频移除', event, event.data.stream.userID)
  1289. const stream = event.data.stream
  1290. this._setList({
  1291. userList: event.data.userList,
  1292. streamList: event.data.streamList,
  1293. }).then(() => {
  1294. // 有可能先触发了退房事件,用户名下的所有stream都已清除
  1295. if (stream.userID && stream.streamType) {
  1296. this._emitter.emit(EVENT.REMOTE_AUDIO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
  1297. }
  1298. })
  1299. console.log(TAG_NAME, 'REMOTE_AUDIO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
  1300. })
  1301. },
  1302. /**
  1303. * pusher event handler
  1304. * @param {*} event 事件实例
  1305. */
  1306. _pusherStateChangeHandler(event) {
  1307. const code = event.detail.code
  1308. const message = event.detail.message
  1309. console.log(TAG_NAME, 'pusherStateChange:', code, event)
  1310. switch (code) {
  1311. case 0: // 未知状态码,不做处理
  1312. console.log(TAG_NAME, message, code)
  1313. break
  1314. case 1001:
  1315. console.log(TAG_NAME, '已经连接推流服务器', code)
  1316. break
  1317. case 1002:
  1318. console.log(TAG_NAME, '已经与服务器握手完毕,开始推流', code)
  1319. break
  1320. case 1003:
  1321. console.log(TAG_NAME, '打开摄像头成功', code)
  1322. break
  1323. case 1004:
  1324. console.log(TAG_NAME, '录屏启动成功', code)
  1325. break
  1326. case 1005:
  1327. console.log(TAG_NAME, '推流动态调整分辨率', code)
  1328. break
  1329. case 1006:
  1330. console.log(TAG_NAME, '推流动态调整码率', code)
  1331. break
  1332. case 1007:
  1333. console.log(TAG_NAME, '首帧画面采集完成', code)
  1334. break
  1335. case 1008:
  1336. console.log(TAG_NAME, '编码器启动', code)
  1337. break
  1338. case 1018:
  1339. console.log(TAG_NAME, '进房成功', code)
  1340. this._emitter.emit(EVENT.LOCAL_JOIN, { userID: this.data.pusher.userID })
  1341. break
  1342. case 1019:
  1343. console.log(TAG_NAME, '退出房间', code)
  1344. // 20200421 iOS 仍然没有1019事件通知退房,退房事件移动到 exitRoom 方法里,但不是后端通知的退房成功
  1345. // this._emitter.emit(EVENT.LOCAL_LEAVE, { userID: this.data.pusher.userID })
  1346. break
  1347. case 2003:
  1348. console.log(TAG_NAME, '渲染首帧视频', code)
  1349. break
  1350. case 1020:
  1351. case 1031:
  1352. case 1032:
  1353. case 1033:
  1354. case 1034:
  1355. // 通过 userController 处理 1020 1031 1032 1033 1034
  1356. this.userController.userEventHandler(event)
  1357. break
  1358. case -1301:
  1359. console.error(TAG_NAME, '打开摄像头失败: ', code)
  1360. this._emitter.emit(EVENT.ERROR, { code, message })
  1361. break
  1362. case -1302:
  1363. console.error(TAG_NAME, '打开麦克风失败: ', code)
  1364. this._emitter.emit(EVENT.ERROR, { code, message })
  1365. break
  1366. case -1303:
  1367. console.error(TAG_NAME, '视频编码失败: ', code)
  1368. this._emitter.emit(EVENT.ERROR, { code, message })
  1369. break
  1370. case -1304:
  1371. console.error(TAG_NAME, '音频编码失败: ', code)
  1372. this._emitter.emit(EVENT.ERROR, { code, message })
  1373. break
  1374. case -1307:
  1375. console.error(TAG_NAME, '推流连接断开: ', code)
  1376. this._emitter.emit(EVENT.ERROR, { code, message })
  1377. break
  1378. case -100018:
  1379. console.error(TAG_NAME, '进房失败: userSig 校验失败,请检查 userSig 是否填写正确', code, message)
  1380. this._emitter.emit(EVENT.ERROR, { code, message })
  1381. break
  1382. case 5000:
  1383. console.log(TAG_NAME, '小程序被挂起: ', code)
  1384. // 20200421 iOS 微信点击胶囊圆点会触发该事件
  1385. // 触发 5000 后,底层SDK会退房,返回前台后会自动进房
  1386. break
  1387. case 5001:
  1388. // 20200421 仅有 Android 微信会触发该事件
  1389. console.log(TAG_NAME, '小程序悬浮窗被关闭: ', code)
  1390. this.status.isPending = true
  1391. if (this.status.isPush) {
  1392. this.exitRoom()
  1393. }
  1394. break
  1395. case 1021:
  1396. console.log(TAG_NAME, '网络类型发生变化,需要重新进房', code)
  1397. break
  1398. case 2007:
  1399. console.log(TAG_NAME, '本地视频播放loading: ', code)
  1400. break
  1401. case 2004:
  1402. console.log(TAG_NAME, '本地视频播放开始: ', code)
  1403. break
  1404. default:
  1405. console.log(TAG_NAME, message, code)
  1406. }
  1407. },
  1408. _pusherNetStatusHandler(event) {
  1409. // 触发 LOCAL_NET_STATE_UPDATE
  1410. this._emitter.emit(EVENT.LOCAL_NET_STATE_UPDATE, event)
  1411. },
  1412. _pusherErrorHandler(event) {
  1413. // 触发 ERROR
  1414. console.warn(TAG_NAME, 'pusher error', event)
  1415. try {
  1416. const code = event.detail.errCode
  1417. const message = event.detail.errMsg
  1418. this._emitter.emit(EVENT.ERROR, { code, message })
  1419. } catch (exception) {
  1420. console.error(TAG_NAME, 'pusher error data parser exception', event, exception)
  1421. }
  1422. },
  1423. _pusherBGMStartHandler(event) {
  1424. // 触发 BGM_START 已经在playBGM方法中进行处理
  1425. // this._emitter.emit(EVENT.BGM_PLAY_START, { data: event })
  1426. },
  1427. _pusherBGMProgressHandler(event) {
  1428. // BGM_PROGRESS
  1429. this._emitter.emit(EVENT.BGM_PLAY_PROGRESS, event)
  1430. },
  1431. _pusherBGMCompleteHandler(event) {
  1432. // BGM_COMPLETE
  1433. this._emitter.emit(EVENT.BGM_PLAY_COMPLETE, event)
  1434. },
  1435. _pusherAudioVolumeNotify: function(event) {
  1436. // console.log(TAG_NAME, '_pusherAudioVolumeNotify', event)
  1437. this._emitter.emit(EVENT.LOCAL_AUDIO_VOLUME_UPDATE, event)
  1438. },
  1439. // player event handler
  1440. // 获取 player ID 再进行触发
  1441. _playerStateChange(event) {
  1442. // console.log(TAG_NAME, '_playerStateChange', event)
  1443. this._emitter.emit(EVENT.REMOTE_STATE_UPDATE, event)
  1444. },
  1445. _playerFullscreenChange(event) {
  1446. // console.log(TAG_NAME, '_playerFullscreenChange', event)
  1447. this._emitter.emit(EVENT.REMOTE_FULLSCREEN_UPDATE, event)
  1448. this._emitter.emit(EVENT.VIDEO_FULLSCREEN_UPDATE, event)
  1449. },
  1450. _playerNetStatus(event) {
  1451. // console.log(TAG_NAME, '_playerNetStatus', event)
  1452. // 获取player 视频的宽高
  1453. const stream = this.userController.getStream({
  1454. userID: event.currentTarget.dataset.userid,
  1455. streamType: event.currentTarget.dataset.streamtype,
  1456. })
  1457. if (stream && (stream.videoWidth !== event.detail.info.videoWidth || stream.videoHeight !== event.detail.info.videoHeight)) {
  1458. console.log(TAG_NAME, '_playerNetStatus update video size', event)
  1459. stream.videoWidth = event.detail.info.videoWidth
  1460. stream.videoHeight = event.detail.info.videoHeight
  1461. }
  1462. this._emitter.emit(EVENT.REMOTE_NET_STATE_UPDATE, event)
  1463. },
  1464. _playerAudioVolumeNotify(event) {
  1465. // console.log(TAG_NAME, '_playerAudioVolumeNotify', event)
  1466. this._emitter.emit(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, event)
  1467. },
  1468. _filterVisibleStream(streamList, skipPagination) {
  1469. const list = streamList.filter((item) => {
  1470. // 全部显示
  1471. // return true
  1472. // 只显示有视频或者有音频的 stream
  1473. return (item.hasVideo || item.hasAudio)
  1474. })
  1475. // 按 userID 进行排序
  1476. list.sort((item1, item2)=>{
  1477. const id1 = item1.userID.toUpperCase()
  1478. const id2 = item2.userID.toUpperCase()
  1479. if (id1 < id2) {
  1480. return -1
  1481. }
  1482. if (id1 > id2) {
  1483. return 1
  1484. }
  1485. return 0
  1486. })
  1487. if (this.data.template === 'grid' && !skipPagination) {
  1488. this._filterGridPageVisibleStream(list)
  1489. // console.log(TAG_NAME, '_filterVisibleStream gridPagePlaceholderStreamList:', this.data.gridPagePlaceholderStreamList)
  1490. if (// list.length > this.data.gridPlayerPerPage - 2 &&
  1491. this.data.gridCurrentPage > 1 &&
  1492. this.data.gridPagePlaceholderStreamList.length === this.data.gridPlayerPerPage) {
  1493. // 如果stream 数量大于每页可显示数量,当前页面已经没有可显示的stream(占位数量==3) 回到上一个页面。
  1494. this._gridPageToPrev(list)
  1495. }
  1496. }
  1497. // console.log(TAG_NAME, '_filterVisibleStream list:', list)
  1498. return list
  1499. },
  1500. _filterGridPageVisibleStream(list) {
  1501. // 最多只显示 gridPlayerPerPage 个stream
  1502. const length = list.length
  1503. // +1 pusher
  1504. this.data.gridPageCount = Math.ceil((length + 1) / this.data.gridPlayerPerPage)
  1505. this.data.gridPagePlaceholderStreamList = []
  1506. let visibleCount = 0
  1507. // 需要显示的player区间
  1508. let interval
  1509. if (this.data.gridPlayerPerPage > 3) {
  1510. if (this.data.gridCurrentPage === 1) {
  1511. interval = [-1, this.data.gridPlayerPerPage - 1]
  1512. } else {
  1513. // 每页显示4个时,第一页显示3个,pusher只在第一页
  1514. // -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  1515. // 1 2 3 4
  1516. // -1 3
  1517. // 2 7
  1518. // 6 11
  1519. interval = [this.data.gridCurrentPage * this.data.gridPlayerPerPage - (this.data.gridPlayerPerPage + 2), this.data.gridCurrentPage * this.data.gridPlayerPerPage - 1]
  1520. }
  1521. } else {
  1522. // 每页显示3个,每页都有pusher
  1523. interval = [this.data.gridCurrentPage * this.data.gridPlayerPerPage - (this.data.gridPlayerPerPage + 1), this.data.gridCurrentPage * this.data.gridPlayerPerPage]
  1524. }
  1525. for (let i = 0; i < length; i++) {
  1526. if ( i > interval[0] && i < interval[1]) {
  1527. list[i].isVisible = true
  1528. list[i].muteVideo = list[i].muteVideoPrev === undefined ? list[i].muteVideo : list[i].muteVideoPrev
  1529. visibleCount++
  1530. } else {
  1531. list[i].isVisible = false
  1532. list[i].muteVideo = true
  1533. }
  1534. }
  1535. // 第一页,不需要占位
  1536. if (this.data.gridCurrentPage !== 1) {
  1537. for (let i = 0; i < this.data.gridPlayerPerPage - visibleCount; i++) {
  1538. this.data.gridPagePlaceholderStreamList.push({ id: 'holder-' + i })
  1539. }
  1540. }
  1541. return list
  1542. },
  1543. /**
  1544. * 保持屏幕常亮
  1545. */
  1546. _keepScreenOn() {
  1547. setInterval(() => {
  1548. wx.setKeepScreenOn({
  1549. keepScreenOn: true,
  1550. })
  1551. }, 20000)
  1552. },
  1553. // ______ __ __ ______ __ __
  1554. // | \| \ / \ | \ | \ | \
  1555. // \$$$$$$| $$\ / $$ \$$$$$$ _______ _| $$_ ______ ______ _______ ______ | $$
  1556. // | $$ | $$$\ / $$$ | $$ | \| $$ \ / \ / \ | \ | \ | $$
  1557. // | $$ | $$$$\ $$$$ | $$ | $$$$$$$\\$$$$$$ | $$$$$$\| $$$$$$\| $$$$$$$\ \$$$$$$\| $$
  1558. // | $$ | $$\$$ $$ $$ | $$ | $$ | $$ | $$ __ | $$ $$| $$ \$$| $$ | $$ / $$| $$
  1559. // _| $$_ | $$ \$$$| $$ _| $$_ | $$ | $$ | $$| \| $$$$$$$$| $$ | $$ | $$| $$$$$$$| $$
  1560. // | $$ \| $$ \$ | $$ | $$ \| $$ | $$ \$$ $$ \$$ \| $$ | $$ | $$ \$$ $$| $$
  1561. // \$$$$$$ \$$ \$$ \$$$$$$ \$$ \$$ \$$$$ \$$$$$$$ \$$ \$$ \$$ \$$$$$$$ \$$
  1562. /**
  1563. * 初始化 IM SDK
  1564. * @param {Object} config sdkAppID
  1565. */
  1566. _initIM(config) {
  1567. if (!config.enableIM || !config.sdkAppID || this.tim) {
  1568. return
  1569. }
  1570. console.log(TAG_NAME, '_initIM', config)
  1571. // 初始化 sdk 实例
  1572. const tim = TIM.create({
  1573. SDKAppID: config.sdkAppID,
  1574. })
  1575. // 0 普通级别,日志量较多,接入时建议使用
  1576. // 1 release级别,SDK 输出关键信息,生产环境时建议使用
  1577. // 2 告警级别,SDK 只输出告警和错误级别的日志
  1578. // 3 错误级别,SDK 只输出错误级别的日志
  1579. // 4 无日志级别,SDK 将不打印任何日志
  1580. if (config.debugMode) {
  1581. tim.setLogLevel(1)
  1582. } else {
  1583. tim.setLogLevel(4)
  1584. }
  1585. // 取消监听
  1586. tim.off(TIM.EVENT.SDK_READY, this._onIMReady)
  1587. tim.off(TIM.EVENT.MESSAGE_RECEIVED, this._onIMMessageReceived)
  1588. tim.off(TIM.EVENT.SDK_NOT_READY, this._onIMNotReady)
  1589. tim.off(TIM.EVENT.KICKED_OUT, this._onIMKickedOut)
  1590. tim.off(TIM.EVENT.ERROR, this._onIMError)
  1591. // 监听事件
  1592. tim.on(TIM.EVENT.SDK_READY, this._onIMReady, this)
  1593. tim.on(TIM.EVENT.MESSAGE_RECEIVED, this._onIMMessageReceived, this)
  1594. tim.on(TIM.EVENT.SDK_NOT_READY, this._onIMNotReady, this)
  1595. tim.on(TIM.EVENT.KICKED_OUT, this._onIMKickedOut, this)
  1596. tim.on(TIM.EVENT.ERROR, this._onIMError, this)
  1597. this.tim = tim
  1598. wx.tim = tim
  1599. },
  1600. _loginIM(params) {
  1601. if (!this.tim) {
  1602. return
  1603. }
  1604. console.log(TAG_NAME, '_loginIM', params)
  1605. return this.tim.login({
  1606. userID: params.userID,
  1607. userSig: params.userSig,
  1608. })
  1609. },
  1610. _logoutIM() {
  1611. if (!this.tim) {
  1612. return
  1613. }
  1614. console.log(TAG_NAME, '_logoutIM')
  1615. return this.tim.logout()
  1616. },
  1617. _exitIM() {
  1618. // 方法需要调用限制,否则重复解散群 退群会有warn
  1619. if (this.data.exitIMThrottle || !this.tim) {
  1620. return
  1621. }
  1622. this.data.exitIMThrottle = true
  1623. const userList = this.getRemoteUserList()
  1624. const roomID = this.data.config.roomID
  1625. const userID = this.data.config.userID
  1626. this._searchGroup({ roomID }).then((imResponse) => {
  1627. // 查询群资料,判断是否为群主
  1628. if (imResponse.data.group.ownerID === userID && userList.length === 0) {
  1629. // 如果 userList 为 0 群主可以解散群,并登出IM
  1630. this._dismissGroup({ roomID }).then(()=>{
  1631. this.data.exitIMThrottle = false
  1632. this._logoutIM()
  1633. }).catch((imError) => {
  1634. this.data.exitIMThrottle = false
  1635. this._logoutIM()
  1636. })
  1637. } else if (imResponse.data.group.ownerID === userID) {
  1638. this.data.exitIMThrottle = false
  1639. // 群主不能退群只能登出
  1640. this._logoutIM()
  1641. } else {
  1642. // 普通成员退群并登出IM
  1643. this._quitGroup({ roomID }).then(()=>{
  1644. this.data.exitIMThrottle = false
  1645. this._logoutIM()
  1646. }).catch((imError) => {
  1647. this.data.exitIMThrottle = false
  1648. this._logoutIM()
  1649. })
  1650. }
  1651. }).catch((imError) => {
  1652. this.data.exitIMThrottle = false
  1653. // 查询异常直接登出
  1654. this._logoutIM()
  1655. })
  1656. },
  1657. _searchGroup(params) {
  1658. if (!this.tim) {
  1659. return
  1660. }
  1661. console.log(TAG_NAME, '_searchGroup', params)
  1662. const tim = this.tim
  1663. const promise = tim.searchGroupByID(params.roomID + '')
  1664. promise.then(function(imResponse) {
  1665. // const group = imResponse.data.group // 群组信息
  1666. console.log(TAG_NAME, '_searchGroup success', imResponse)
  1667. }).catch(function(imError) {
  1668. console.warn(TAG_NAME, '_searchGroup fail,TIM 报错信息不影响后续逻辑,可以忽略', imError) // 搜素群组失败的相关信息
  1669. })
  1670. return promise
  1671. },
  1672. /**
  1673. * 创建 AVchatroom
  1674. * @param {*} params roomID
  1675. * @returns {Promise}
  1676. */
  1677. _createGroup(params) {
  1678. if (!this.tim) {
  1679. return
  1680. }
  1681. console.log(TAG_NAME, '_createGroup', params)
  1682. const promise = this.tim.createGroup({
  1683. groupID: params.roomID + '',
  1684. name: params.roomID + '',
  1685. type: IM_GROUP_TYPE,
  1686. })
  1687. promise.then((imResponse) => { // 创建成功
  1688. console.log(TAG_NAME, '_createGroup success', imResponse.data.group) // 创建的群的资料
  1689. }).catch((imError) => {
  1690. console.warn(TAG_NAME, '_createGroup error', imError) // 创建群组失败的相关信息
  1691. })
  1692. return promise
  1693. },
  1694. /**
  1695. * 进入 AVchatroom
  1696. * @param {*} params roomID
  1697. * @returns {Promise}
  1698. */
  1699. _joinGroup(params) {
  1700. if (!this.tim) {
  1701. return
  1702. }
  1703. console.log(TAG_NAME, '_joinGroup', params)
  1704. const promise = this.tim.joinGroup({ groupID: params.roomID + '', type: IM_GROUP_TYPE })
  1705. promise.then((imResponse) => {
  1706. switch (imResponse.data.status) {
  1707. case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL: // 等待管理员同意
  1708. break
  1709. case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
  1710. case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
  1711. // console.log(imResponse.data.group) // 加入的群组资料
  1712. // wx.showToast({
  1713. // title: '进群成功',
  1714. // })
  1715. console.log(TAG_NAME, '_joinGroup success', imResponse)
  1716. break
  1717. default:
  1718. break
  1719. }
  1720. }).catch((imError) => {
  1721. console.warn(TAG_NAME, 'joinGroup error', imError) // 申请加群失败的相关信息
  1722. })
  1723. return promise
  1724. },
  1725. _quitGroup(params) {
  1726. if (!this.tim) {
  1727. return
  1728. }
  1729. console.log(TAG_NAME, '_quitGroup', params)
  1730. const promise = this.tim.quitGroup(params.roomID + '')
  1731. promise.then((imResponse) => {
  1732. console.log(TAG_NAME, '_quitGroup success', imResponse)
  1733. }).catch((imError) => {
  1734. console.warn(TAG_NAME, 'quitGroup error', imError)
  1735. })
  1736. return promise
  1737. },
  1738. _dismissGroup(params) {
  1739. if (!this.tim) {
  1740. return
  1741. }
  1742. console.log(TAG_NAME, '_dismissGroup', params)
  1743. const promise = this.tim.dismissGroup(params.roomID + '')
  1744. promise.then((imResponse) => {
  1745. console.log(TAG_NAME, '_dismissGroup success', imResponse)
  1746. }).catch((imError) => {
  1747. console.warn(TAG_NAME, '_dismissGroup error', imError)
  1748. })
  1749. return promise
  1750. },
  1751. _onIMReady(event) {
  1752. console.log(TAG_NAME, 'IM.READY', event)
  1753. this._emitter.emit(EVENT.IM_READY, event)
  1754. const roomID = this.data.config.roomID
  1755. // 查询群组是否存在
  1756. this._searchGroup({ roomID }).then((res) => {
  1757. // console.log(TAG_NAME, 'searchGroup', res)
  1758. // 存在直接进群
  1759. this._joinGroup({ roomID })
  1760. }).catch(() => {
  1761. // 不存在则创建,如果是avchatroom 创建后进群
  1762. this._createGroup({ roomID }).then((res) => {
  1763. // 进群
  1764. this._joinGroup({ roomID })
  1765. }).catch((imError)=> {
  1766. if (imError.code === 10021) {
  1767. console.log(TAG_NAME, '群已存在,直接进群', event)
  1768. this._joinGroup({ roomID })
  1769. }
  1770. })
  1771. })
  1772. // 收到离线消息和会话列表同步完毕通知,接入侧可以调用 sendMessage 等需要鉴权的接口
  1773. // event.name - TIM.EVENT.IM_READY
  1774. },
  1775. _onIMMessageReceived(event) {
  1776. // 收到推送的单聊、群聊、群提示、群系统通知的新消息,可通过遍历 event.data 获取消息列表数据并渲染到页面
  1777. console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', event)
  1778. // messageList 仅保留10条消息
  1779. const messageData = event.data
  1780. const roomID = this.data.config.roomID + ''
  1781. const userID = this.data.config.userID + ''
  1782. for (let i = 0; i < messageData.length; i++) {
  1783. const message = messageData[i]
  1784. // console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', message, this.data.config, TIM.TYPES.MSG_TEXT)
  1785. if (message.to === roomID + '' || message.to === userID) {
  1786. // 遍历messageData 获取当前room 或者当前user的消息
  1787. console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', message, message.type, TIM.TYPES.MSG_TEXT)
  1788. if (message.type === TIM.TYPES.MSG_TEXT) {
  1789. this._pushMessageList({
  1790. name: message.from,
  1791. message: message.payload.text,
  1792. })
  1793. } else {
  1794. if (message.type === TIM.TYPES.MSG_GRP_SYS_NOTICE && message.payload.operationType === 2) {
  1795. // 群系统通知
  1796. this._pushMessageList({
  1797. name: '系统通知',
  1798. message: `欢迎 ${userID}`,
  1799. })
  1800. }
  1801. // 其他消息暂不处理
  1802. }
  1803. }
  1804. }
  1805. this._emitter.emit(EVENT.IM_MESSAGE_RECEIVED, event)
  1806. },
  1807. _onIMNotReady(event) {
  1808. console.log(TAG_NAME, 'IM.NOT_READY', event)
  1809. this._emitter.emit(EVENT.IM_NOT_READY, event)
  1810. // 收到 SDK 进入 not ready 状态通知,此时 SDK 无法正常工作
  1811. // event.name - TIM.EVENT.IM_NOT_READY
  1812. },
  1813. _onIMKickedOut(event) {
  1814. console.log(TAG_NAME, 'IM.KICKED_OUT', event)
  1815. this._emitter.emit(EVENT.IM_KICKED_OUT, event)
  1816. // 收到被踢下线通知
  1817. // event.name - TIM.EVENT.KICKED_OUT
  1818. // event.data.type - 被踢下线的原因,例如 :
  1819. // - TIM.TYPES.KICKED_OUT_MULT_ACCOUNT 多实例登录被踢
  1820. // - TIM.TYPES.KICKED_OUT_MULT_DEVICE 多终端登录被踢
  1821. // - TIM.TYPES.KICKED_OUT_USERSIG_EXPIRED 签名过期被踢。使用前需要将SDK版本升级至v2.4.0或以上。
  1822. },
  1823. _onIMError(event) {
  1824. console.log(TAG_NAME, 'IM.ERROR', event)
  1825. this._emitter.emit(EVENT.IM_ERROR, event)
  1826. // 收到 SDK 发生错误通知,可以获取错误码和错误信息
  1827. // event.name - TIM.EVENT.ERROR
  1828. // event.data.code - 错误码
  1829. // event.data.message - 错误信息
  1830. },
  1831. // ________ __ __
  1832. // | \ | \ | \
  1833. // \$$$$$$$$______ ______ ____ ______ | $$ ______ _| $$_ ______
  1834. // | $$ / \ | \ \ / \ | $$ | \| $$ \ / \
  1835. // | $$ | $$$$$$\| $$$$$$\$$$$\| $$$$$$\| $$ \$$$$$$\\$$$$$$ | $$$$$$\
  1836. // | $$ | $$ $$| $$ | $$ | $$| $$ | $$| $$ / $$ | $$ __ | $$ $$
  1837. // | $$ | $$$$$$$$| $$ | $$ | $$| $$__/ $$| $$| $$$$$$$ | $$| \| $$$$$$$$
  1838. // | $$ \$$ \| $$ | $$ | $$| $$ $$| $$ \$$ $$ \$$ $$ \$$ \
  1839. // \$$ \$$$$$$$ \$$ \$$ \$$| $$$$$$$ \$$ \$$$$$$$ \$$$$ \$$$$$$$
  1840. // | $$
  1841. // | $$
  1842. // \$$
  1843. // 以下为 debug & template 相关函数
  1844. _toggleVideo() {
  1845. if (this.data.pusher.enableCamera) {
  1846. this.unpublishLocalVideo()
  1847. } else {
  1848. this.publishLocalVideo()
  1849. }
  1850. },
  1851. _toggleAudio() {
  1852. if (this.data.pusher.enableMic) {
  1853. this.unpublishLocalAudio()
  1854. } else {
  1855. this.publishLocalAudio()
  1856. }
  1857. },
  1858. _debugToggleRemoteVideo(event) {
  1859. console.log(TAG_NAME, '_debugToggleRemoteVideo', event.currentTarget.dataset)
  1860. const userID = event.currentTarget.dataset.userID
  1861. const streamType = event.currentTarget.dataset.streamType
  1862. const stream = this.data.streamList.find((item)=>{
  1863. return item.userID === userID && item.streamType === streamType
  1864. })
  1865. if (stream.muteVideo) {
  1866. this.subscribeRemoteVideo({ userID, streamType })
  1867. // this.setViewVisible({ userID, streamType, isVisible: true })
  1868. } else {
  1869. this.unsubscribeRemoteVideo({ userID, streamType })
  1870. // this.setViewVisible({ userID, streamType, isVisible: false })
  1871. }
  1872. },
  1873. _debugToggleRemoteAudio(event) {
  1874. console.log(TAG_NAME, '_debugToggleRemoteAudio', event.currentTarget.dataset)
  1875. const userID = event.currentTarget.dataset.userID
  1876. const streamType = event.currentTarget.dataset.streamType
  1877. const stream = this.data.streamList.find((item)=>{
  1878. return item.userID === userID && item.streamType === streamType
  1879. })
  1880. if (stream.muteAudio) {
  1881. this.subscribeRemoteAudio({ userID })
  1882. } else {
  1883. this.unsubscribeRemoteAudio({ userID })
  1884. }
  1885. },
  1886. _debugToggleVideoDebug() {
  1887. this.setData({
  1888. debug: !this.data.debug,
  1889. })
  1890. },
  1891. _debugExitRoom() {
  1892. this.exitRoom()
  1893. },
  1894. _debugEnterRoom() {
  1895. Object.assign(this.data.pusher, this.data.config)
  1896. this.enterRoom({ roomID: this.data.config.roomID }).then(()=>{
  1897. setTimeout(()=>{
  1898. this.publishLocalVideo()
  1899. this.publishLocalAudio()
  1900. }, 2000)
  1901. // 进房后开始推送视频或音频
  1902. })
  1903. },
  1904. _debugGoBack() {
  1905. wx.navigateBack({
  1906. delta: 1,
  1907. })
  1908. },
  1909. _debugTogglePanel() {
  1910. this.setData({
  1911. debugPanel: !this.data.debugPanel,
  1912. })
  1913. },
  1914. _debugSendRandomMessage() {
  1915. const userList = this.getRemoteUserList()
  1916. if (userList.length === 0 || !this.tim) {
  1917. return false
  1918. }
  1919. const roomID = this.data.config.roomID
  1920. const message = `Hello! ${userList[0].userID} ${9999 * Math.random()}`
  1921. const userID = userList[0].userID
  1922. this.sendC2CTextMessage({
  1923. userID: userID,
  1924. message: message,
  1925. })
  1926. const promise = this.sendGroupTextMessage({
  1927. roomID: roomID,
  1928. message: message,
  1929. })
  1930. // 消息上屏
  1931. this._pushMessageList({
  1932. name: userID,
  1933. message: message,
  1934. })
  1935. promise.then(function(imResponse) {
  1936. // 发送成功
  1937. console.log(TAG_NAME, '_debugSendRandomMessage success', imResponse)
  1938. wx.showToast({
  1939. title: '发送成功',
  1940. icon: 'success',
  1941. duration: 1000,
  1942. })
  1943. }).catch(function(imError) {
  1944. // 发送失败
  1945. console.warn(TAG_NAME, '_debugSendRandomMessage error', imError)
  1946. wx.showToast({
  1947. title: '发送失败',
  1948. icon: 'none',
  1949. duration: 1000,
  1950. })
  1951. })
  1952. },
  1953. _toggleAudioVolumeType() {
  1954. if (this.data.pusher.audioVolumeType === 'voicecall') {
  1955. this._setPusherConfig({
  1956. audioVolumeType: 'media',
  1957. })
  1958. } else {
  1959. this._setPusherConfig({
  1960. audioVolumeType: 'voicecall',
  1961. })
  1962. }
  1963. },
  1964. _toggleSoundMode() {
  1965. if (this.data.userList.length === 0 ) {
  1966. return
  1967. }
  1968. const stream = this.userController.getStream({
  1969. userID: this.data.userList[0].userID,
  1970. streamType: 'main',
  1971. })
  1972. if (stream) {
  1973. if (stream.soundMode === 'speaker') {
  1974. stream['soundMode'] = 'ear'
  1975. } else {
  1976. stream['soundMode'] = 'speaker'
  1977. }
  1978. this._setPlayerConfig({
  1979. userID: stream.userID,
  1980. streamType: 'main',
  1981. config: {
  1982. soundMode: stream['soundMode'],
  1983. },
  1984. })
  1985. }
  1986. },
  1987. /**
  1988. * 退出通话
  1989. */
  1990. _hangUp() {
  1991. this.exitRoom()
  1992. wx.navigateBack({
  1993. delta: 1,
  1994. })
  1995. },
  1996. /**
  1997. * 切换订阅音频状态
  1998. */
  1999. handleSubscribeAudio() {
  2000. if (this.data.pusher.enableMic) {
  2001. this.unpublishLocalAudio()
  2002. } else {
  2003. this.publishLocalAudio()
  2004. }
  2005. },
  2006. /**
  2007. * 切换订阅远端视频状态
  2008. * @param {Object} event native 事件对象
  2009. */
  2010. _handleSubscribeRemoteVideo(event) {
  2011. const userID = event.currentTarget.dataset.userID
  2012. const streamType = event.currentTarget.dataset.streamType
  2013. const stream = this.data.streamList.find((item)=>{
  2014. return item.userID === userID && item.streamType === streamType
  2015. })
  2016. if (stream.muteVideo) {
  2017. this.subscribeRemoteVideo({ userID, streamType })
  2018. } else {
  2019. this.unsubscribeRemoteVideo({ userID, streamType })
  2020. }
  2021. },
  2022. /**
  2023. *
  2024. * @param {Object} event native 事件对象
  2025. */
  2026. _handleSubscribeRemoteAudio(event) {
  2027. const userID = event.currentTarget.dataset.userID
  2028. const streamType = event.currentTarget.dataset.streamType
  2029. const stream = this.data.streamList.find((item)=>{
  2030. return item.userID === userID && item.streamType === streamType
  2031. })
  2032. if (stream.muteAudio) {
  2033. this.subscribeRemoteAudio({ userID })
  2034. } else {
  2035. this.unsubscribeRemoteAudio({ userID })
  2036. }
  2037. },
  2038. /**
  2039. * grid布局, 唤起 memberlist-panel
  2040. */
  2041. _switchMemberListPanel() {
  2042. this.setData({
  2043. panelName: this.data.panelName !== 'memberlist-panel' ? 'memberlist-panel' : '',
  2044. })
  2045. },
  2046. /**
  2047. * grid布局, 唤起 setting-panel
  2048. */
  2049. _switchSettingPanel() {
  2050. this.setData({
  2051. panelName: this.data.panelName !== 'setting-panel' ? 'setting-panel' : '',
  2052. })
  2053. },
  2054. _switchBGMPanel() {
  2055. this.setData({
  2056. panelName: this.data.panelName !== 'bgm-panel' ? 'bgm-panel' : '',
  2057. })
  2058. },
  2059. _handleMaskerClick() {
  2060. this.setData({
  2061. panelName: '',
  2062. })
  2063. },
  2064. _setPuserProperty(event) {
  2065. console.log(TAG_NAME, '_setPuserProperty', event)
  2066. const key = event.currentTarget.dataset.key
  2067. const valueType = event.currentTarget.dataset.valueType
  2068. let value = event.currentTarget.dataset.value
  2069. const config = {}
  2070. if (valueType === 'boolean') {
  2071. value = value === 'true' ? true : false
  2072. config[key] = !this.data.pusher[key]
  2073. }
  2074. if (valueType === 'number' && value.indexOf('|') > 0) {
  2075. value = value.split('|')
  2076. // console.log(this.data.pusher, this.data.pusher[key], key, value)
  2077. if ( this.data.pusher[key] === Number(value[0])) {
  2078. config[key] = Number(value[1])
  2079. } else {
  2080. config[key] = Number(value[0])
  2081. }
  2082. }
  2083. if (valueType === 'string' && value.indexOf('|') > 0) {
  2084. value = value.split('|')
  2085. if ( this.data.pusher[key] === value[0]) {
  2086. config[key] = value[1]
  2087. } else {
  2088. config[key] = value[0]
  2089. }
  2090. }
  2091. this._setPusherConfig(config)
  2092. },
  2093. _setPlayerProperty(event) {
  2094. console.log(TAG_NAME, '_setPlayerProperty', event)
  2095. const userID = event.currentTarget.dataset.userid
  2096. const streamType = event.currentTarget.dataset.streamtype
  2097. const key = event.currentTarget.dataset.key
  2098. let value = event.currentTarget.dataset.value
  2099. const stream = this.userController.getStream({
  2100. userID: userID,
  2101. streamType: streamType,
  2102. })
  2103. if (!stream) {
  2104. return
  2105. }
  2106. const config = {}
  2107. if (value === 'true') {
  2108. value = true
  2109. } else if (value === 'false') {
  2110. value = false
  2111. }
  2112. if (typeof value === 'boolean') {
  2113. config[key] = !stream[key]
  2114. } else if (typeof value === 'string' && value.indexOf('|') > 0) {
  2115. value = value.split('|')
  2116. if (stream[key] === value[0]) {
  2117. config[key] = value[1]
  2118. } else {
  2119. config[key] = value[0]
  2120. }
  2121. }
  2122. console.log(TAG_NAME, '_setPlayerProperty', config)
  2123. this._setPlayerConfig({ userID, streamType, config })
  2124. },
  2125. _changeProperty(event) {
  2126. const propertyName = event.currentTarget.dataset.propertyName
  2127. const newData = {}
  2128. newData[propertyName] = event.detail.value
  2129. this.setData(newData)
  2130. const volume = newData[propertyName] / 100
  2131. switch (propertyName) {
  2132. case 'MICVolume':
  2133. this.setMICVolume({ volume })
  2134. break
  2135. case 'BGMVolume':
  2136. this.setBGMVolume({ volume })
  2137. break
  2138. }
  2139. },
  2140. _switchStreamType(event) {
  2141. const userID = event.currentTarget.dataset.userid
  2142. const streamType = event.currentTarget.dataset.streamtype
  2143. const stream = this.userController.getStream({
  2144. userID: userID,
  2145. streamType: streamType,
  2146. })
  2147. if (stream && stream.streamType === 'main') {
  2148. if (stream._definitionType === 'small') {
  2149. this.subscribeRemoteVideo({ userID, streamType: 'main' })
  2150. } else {
  2151. this.subscribeRemoteVideo({ userID, streamType: 'small' })
  2152. }
  2153. }
  2154. },
  2155. _handleSnapshotClick(event) {
  2156. wx.showToast({
  2157. title: '开始截屏',
  2158. icon: 'none',
  2159. duration: 1000,
  2160. })
  2161. const userID = event.currentTarget.dataset.userid
  2162. const streamType = event.currentTarget.dataset.streamtype
  2163. this.snapshot({ userID, streamType })
  2164. },
  2165. /**
  2166. * grid布局, 绑定事件
  2167. */
  2168. _gridBindEvent() {
  2169. // 远端音量变更
  2170. this.on(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, (event) => {
  2171. const data = event.data
  2172. const userID = data.currentTarget.dataset.userid
  2173. const streamType = data.currentTarget.dataset.streamtype
  2174. const volume = data.detail.volume
  2175. // console.log(TAG_NAME, '远端音量变更', userID, streamType, volume, event)
  2176. const stream = this.userController.getStream({
  2177. userID: userID,
  2178. streamType: streamType === 'aux' ? 'main' : streamType, // 远端推辅流后,音量回调会从辅流的 player 返回,而不是主流player 返回。需要等 native SDK修复。
  2179. })
  2180. if (stream) {
  2181. stream.volume = volume
  2182. }
  2183. this.setData({
  2184. streamList: this.data.streamList,
  2185. visibleStreamList: this._filterVisibleStream(this.data.streamList, true),
  2186. }, () => {
  2187. })
  2188. })
  2189. this.on(EVENT.BGM_PLAY_PROGRESS, (event) => {
  2190. // console.log(TAG_NAME, '_gridBindEvent on BGM_PLAY_PROGRESS', event)
  2191. const BGMProgress = event.data.detail.progress / event.data.detail.duration * 100
  2192. this.setData({ BGMProgress })
  2193. })
  2194. this.on(EVENT.LOCAL_AUDIO_VOLUME_UPDATE, (event) => {
  2195. // console.log(TAG_NAME, '_gridBindEvent on LOCAL_AUDIO_VOLUME_UPDATE', event)
  2196. // const data = event.data
  2197. const volume = event.data.detail.volume
  2198. // 避免频繁输出log
  2199. this._setPusherConfig({ volume }, true)
  2200. })
  2201. },
  2202. _handleGridTouchStart(event) {
  2203. touchX = event.changedTouches[0].clientX
  2204. touchY = event.changedTouches[0].clientY
  2205. },
  2206. _handleGridTouchEnd(event) {
  2207. const x = event.changedTouches[0].clientX
  2208. const y = event.changedTouches[0].clientY
  2209. if (x - touchX > 50 && Math.abs(y - touchY) < 50) {
  2210. // console.log(TAG_NAME, '向右滑 当前页面', this.data.gridCurrentPage, this.data.gridPageCount)
  2211. this._gridPagePrev()
  2212. } else if (x - touchX < -50 && Math.abs(y - touchY) < 50) {
  2213. // console.log(TAG_NAME, '向左滑 当前页面', this.data.gridCurrentPage, this.data.gridPageCount)
  2214. this._gridPageNext()
  2215. }
  2216. },
  2217. _gridPageToPrev(streamList) {
  2218. const visibleStreamList = this._filterGridPageVisibleStream(streamList)
  2219. if (this.data.gridPagePlaceholderStreamList.length === this.data.gridPlayerPerPage) {
  2220. this.data.gridCurrentPage--
  2221. this._gridPageToPrev(streamList)
  2222. } else {
  2223. return visibleStreamList
  2224. }
  2225. },
  2226. _gridPageNext() {
  2227. this.data.gridCurrentPage++
  2228. if (this.data.gridCurrentPage > this.data.gridPageCount) {
  2229. this.data.gridCurrentPage = 1
  2230. }
  2231. this._gridPageSetData()
  2232. },
  2233. _gridPagePrev() {
  2234. this.data.gridCurrentPage--
  2235. if (this.data.gridCurrentPage < 1) {
  2236. this.data.gridCurrentPage = this.data.gridPageCount
  2237. }
  2238. this._gridPageSetData()
  2239. },
  2240. _gridPageSetData() {
  2241. this._gridShowPageTips()
  2242. const visibleStreamList = this._filterVisibleStream(this.data.streamList)
  2243. this.setData({
  2244. gridCurrentPage: this.data.gridCurrentPage,
  2245. gridPageCount: this.data.gridPageCount,
  2246. visibleStreamList: visibleStreamList,
  2247. streamList: this.data.streamList,
  2248. gridPagePlaceholderStreamList: this.data.gridPagePlaceholderStreamList,
  2249. }, () => {
  2250. })
  2251. },
  2252. _gridShowPageTips(event) {
  2253. if (this.data.gridPageCount < 2) {
  2254. return
  2255. }
  2256. console.log(TAG_NAME, '_gridShowPageTips', this.data)
  2257. if (this.data.hasGridPageTipsShow) {
  2258. clearTimeout(this.data.hasGridPageTipsShow)
  2259. }
  2260. this.animate('.pages-container', [
  2261. { opacity: 1 },
  2262. ], 100, ()=>{
  2263. })
  2264. this.data.hasGridPageTipsShow = setTimeout(()=>{
  2265. this.animate('.pages-container', [
  2266. { opacity: 1 },
  2267. { opacity: 0.3 },
  2268. ], 600, ()=>{
  2269. })
  2270. }, 3000)
  2271. },
  2272. _toggleFullscreen(event) {
  2273. console.log(TAG_NAME, '_toggleFullscreen', event)
  2274. const userID = event.currentTarget.dataset.userID
  2275. const streamType = event.currentTarget.dataset.streamType
  2276. if (this._isFullscreen) {
  2277. this.exitFullscreen({ userID, streamType }).then(() => {
  2278. this._isFullscreen = false
  2279. }).catch(() => {
  2280. })
  2281. } else {
  2282. // const stream = this.userController.getStream({ userID, streamType })
  2283. const direction = 0
  2284. // 已知问题:视频的尺寸需要等待player触发NetStatus事件才能获取到,如果进房就双击全屏,全屏后的方向有可能不对。
  2285. // if (stream && stream.videoWidth && stream.videoHeight) {
  2286. // // 如果是横视频,全屏时进行横屏处理。如果是竖视频,则为0
  2287. // direction = stream.videoWidth > stream.videoHeight ? 90 : 0
  2288. // }
  2289. this.enterFullscreen({ userID, streamType, direction }).then(() => {
  2290. this._isFullscreen = true
  2291. }).catch(() => {
  2292. })
  2293. }
  2294. },
  2295. _toggleMoreMenu() {
  2296. this.setData({
  2297. isShowMoreMenu: !this.data.isShowMoreMenu,
  2298. })
  2299. },
  2300. _toggleIMPanel() {
  2301. if (!this.data.enableIM) {
  2302. wx.showToast({
  2303. icon: 'none',
  2304. title: '当前没有开启IM功能,请设置 enableIM:true',
  2305. })
  2306. }
  2307. this.setData({
  2308. showIMPanel: !this.data.showIMPanel,
  2309. })
  2310. },
  2311. _handleBGMOperation(event) {
  2312. const operationName = event.currentTarget.dataset.operationName
  2313. if (this[operationName]) {
  2314. this[operationName]({ url: 'https://trtc-1252463788.cos.ap-guangzhou.myqcloud.com/web/assets/bgm-test.mp3' })
  2315. }
  2316. },
  2317. _selectBeautyStyle: function(event) {
  2318. console.log(TAG_NAME, '_selectBeautyStyle', event)
  2319. // this.data.beauty = (event.detail.value === 'close' ? 0 : 9)
  2320. const value = event.detail.value
  2321. this.setData({
  2322. // beauty: (value === 'close' ? 0 : 9),
  2323. beautyStyle: value,
  2324. }, () => {
  2325. this._setPusherConfig({
  2326. beautyLevel: value === 'close' ? 0 : 9,
  2327. beautyStyle: value === 'close' ? 'smooth' : value,
  2328. })
  2329. })
  2330. },
  2331. _selectFilter: function(event) {
  2332. console.log(TAG_NAME, '_selectFilter', event)
  2333. const index = parseInt(event.detail.value)
  2334. this.setData({ filterIndex: index }, () => {
  2335. this._setPusherConfig({
  2336. filter: this.data.filterArray[index].value,
  2337. })
  2338. })
  2339. },
  2340. _selectAudioReverbType: function(event) {
  2341. console.log(TAG_NAME, '_selectAudioReverbType', event)
  2342. const audioReverbType = parseInt(event.detail.value)
  2343. this._setPusherConfig({ audioReverbType })
  2344. },
  2345. _sendIMMessage(event) {
  2346. console.log(TAG_NAME, '_sendIMMessage', event)
  2347. if (!this.data.messageContent) {
  2348. return
  2349. }
  2350. const roomID = this.data.config.roomID
  2351. const message = this.data.messageContent
  2352. const userID = this.data.config.userID
  2353. this.sendGroupTextMessage({ roomID, message })
  2354. // 消息上屏
  2355. this._pushMessageList({
  2356. name: userID,
  2357. message: message,
  2358. })
  2359. this.setData({
  2360. messageContent: '',
  2361. })
  2362. },
  2363. _inputIMMessage(event) {
  2364. // console.log(TAG_NAME, '_inputIMMessage', event)
  2365. this.setData({
  2366. messageContent: event.detail.value,
  2367. })
  2368. },
  2369. _pushMessageList(params) {
  2370. if (this.data.messageList.length === this.data.maxMessageListLength) {
  2371. this.data.messageList.shift()
  2372. }
  2373. this.data.messageList.push(params)
  2374. this.setData({
  2375. messageList: this.data.messageList,
  2376. messageListScrollTop: this.data.messageList.length * 100,
  2377. }, () => {
  2378. })
  2379. },
  2380. },
  2381. })