trtc-room.vue 97 KB

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