trtc-room.js 91 KB

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