trtc-room.js 92 KB

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