trtc-room.js 87 KB

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