|
@@ -0,0 +1,849 @@
|
|
|
+import EventEmitter from './utils/event.js'
|
|
|
+import TSignaling from './utils/tsignaling-wx'
|
|
|
+import * as ENV from 'utils/environment.js'
|
|
|
+import MTA from 'libs/mta_analysis.js'
|
|
|
+import { EVENT, TRTC_EVENT } from './common/constants.js'
|
|
|
+import UserController from './controller/user-controller'
|
|
|
+
|
|
|
+const app = getApp()
|
|
|
+
|
|
|
+const TAG_NAME = 'TRTCCalling-Component'
|
|
|
+// 组件旨在跨终端维护一个通话状态管理机,以事件发布机制驱动上层进行管理,并通过API调用进行状态变更。
|
|
|
+// 组件设计思路将UI和状态管理分离。您可以通过修改`template`文件夹下的文件,适配您的业务场景,
|
|
|
+// 在UI展示上,您可以通过属性的方式,将上层的用户头像,名称等数据传入组件内部,`static`下的用户头像图片,
|
|
|
+// 只是为了展示基础的效果,您需要根据业务场景进行修改。
|
|
|
+Component({
|
|
|
+ properties: {
|
|
|
+ config: {
|
|
|
+ type: Object,
|
|
|
+ value: {
|
|
|
+ sdkAppID: app.globalData.sdkAppID,
|
|
|
+ userID: '',
|
|
|
+ userSig: '',
|
|
|
+ type: 0,
|
|
|
+ },
|
|
|
+ observer: function(newVal, oldVal) {
|
|
|
+ this.setData({
|
|
|
+ config: newVal,
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
+ pusherAvatar: {
|
|
|
+ type: String,
|
|
|
+ value: '',
|
|
|
+ },
|
|
|
+ remoteAvatar: {
|
|
|
+ type: String,
|
|
|
+ value: '',
|
|
|
+ },
|
|
|
+
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ soundMode: 'speaker', // 声音模式 听筒/扬声器
|
|
|
+ callingFlag: false,
|
|
|
+ active: false,
|
|
|
+ pusherConfig: { // 本地上行状态管理
|
|
|
+ pushUrl: '',
|
|
|
+ frontCamera: 'front',
|
|
|
+ enableMic: true,
|
|
|
+ enableCamera: true,
|
|
|
+ volume: 0,
|
|
|
+ },
|
|
|
+ playerList: [], // 通话成员列表
|
|
|
+ streamList: [],
|
|
|
+ invitation: { // 接收到的邀请
|
|
|
+ inviteID: '',
|
|
|
+ },
|
|
|
+ invitationAccept: { // 发出的邀请,以及返回的状态
|
|
|
+ inviteID: '',
|
|
|
+ acceptFlag: false,
|
|
|
+ rejectFlag: false,
|
|
|
+ },
|
|
|
+ _historyUserList: [], // 房间内进过房的成员列表,用于发送call_end
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ _initEventEmitter() {
|
|
|
+ // 监听TSignaling事件
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.NEW_INVITATION_RECEIVED, (event) => {
|
|
|
+ console.log(TAG_NAME, 'onNewInvitationReceived', `是否在通话:${this.data.callingFlag || this.data.invitationAccept.acceptFlag}, inviteID:${event.data.inviteID} inviter:${event.data.inviter} inviteeList:${event.data.inviteeList} data:${event.data.data}`)
|
|
|
+ const data = JSON.parse(event.data.data)
|
|
|
+ // 新的通话邀请
|
|
|
+ // {
|
|
|
+ // "version":0,
|
|
|
+ // "call_type":1,
|
|
|
+ // "room_id":"123"
|
|
|
+ // }
|
|
|
+ // 通话结束,发出的 call_end
|
|
|
+ // {
|
|
|
+ // "version":0,
|
|
|
+ // "call_type":1,
|
|
|
+ // "call_end":123123
|
|
|
+ // }
|
|
|
+ // 通话中,接收的新的邀请时,忙线拒绝
|
|
|
+ if (this.data.callingFlag || this.data.invitationAccept.acceptFlag) {
|
|
|
+ this.tsignaling.reject({
|
|
|
+ inviteID: event.data.inviteID,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: data.call_type,
|
|
|
+ line_busy: '',
|
|
|
+ }),
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (data.call_end) {
|
|
|
+ // 小程序端对 call_end 的接收不做业务处理,这里只是向外抛出这个事件
|
|
|
+ this._emitter.emit(EVENT.CALL_END, {
|
|
|
+ call_end: data.call_end,
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.data.invitation.inviteID = event.data.inviteID
|
|
|
+ this.data.invitation.inviter = event.data.inviter
|
|
|
+
|
|
|
+ this.data.invitation.type = data.call_type
|
|
|
+ this.data.invitation.roomID = data.room_id
|
|
|
+ this.setData({
|
|
|
+ invitation: this.data.invitation,
|
|
|
+ callingFlag: true, // 当前invitation未处理完成时,下一个invitation都将会忙线
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME} NEW_INVITATION_RECEIVED invitation: `, this.data.callingFlag, this.data.invitation)
|
|
|
+ this._emitter.emit(EVENT.INVITED, {
|
|
|
+ inviter: this.data.invitation.inviter,
|
|
|
+ type: this.data.invitation.type,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.INVITEE_ACCEPTED, (event) => {
|
|
|
+ // 发出的邀请收到接受的回调
|
|
|
+ console.log(`${TAG_NAME} INVITEE_ACCEPTED inviteID:${event.data.inviteID} invitee:${event.data.invitee} data:${event.data.data}`)
|
|
|
+ if (this.data.invitationAccept.inviteID === event.data.inviteID) {
|
|
|
+ this.data.invitationAccept.acceptFlag = true
|
|
|
+ this.setData({
|
|
|
+ invitationAccept: this.data.invitationAccept,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.INVITEE_REJECTED, (event) => {
|
|
|
+ // 发出的邀请收到拒绝的回调
|
|
|
+ console.log(`${TAG_NAME} INVITEE_REJECTED inviteID:${event.data.inviteID} invitee:${event.data.invitee} data:${event.data.data}`)
|
|
|
+ // 小程序使用双向绑定模式,这里只向外抛出拒绝的用户 userID 业务逻辑由业务层进行处理
|
|
|
+ const data = JSON.parse(event.data.data)
|
|
|
+ if (this.data.invitationAccept.inviteID === event.data.inviteID) {
|
|
|
+ this.data.invitationAccept.rejectFlag = true
|
|
|
+ this.setData({
|
|
|
+ invitationAccept: this.data.invitationAccept,
|
|
|
+ }, () => {
|
|
|
+ if (data.line_busy === '') {
|
|
|
+ this._emitter.emit(EVENT.LINE_BUSY, {
|
|
|
+ inviteID: event.data.inviteID,
|
|
|
+ invitee: event.data.invitee,
|
|
|
+ reason: 'line busy',
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this._emitter.emit(EVENT.REJECT, {
|
|
|
+ inviteID: event.data.inviteID,
|
|
|
+ invitee: event.data.invitee,
|
|
|
+ reason: 'reject',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.INVITATION_CANCELLED, (event) => {
|
|
|
+ // 收到的邀请收到该邀请取消的回调
|
|
|
+ console.log('demo | onInvitationCancelled', `inviteID:${event.data.inviteID} inviter:${event.data.invitee} data:${event.data.data}`)
|
|
|
+ // invitation取消,收到此消息的时候应该还没有接通,为防止时序的问题,还是走一下挂断流程
|
|
|
+ this.setData({
|
|
|
+ callingFlag: false,
|
|
|
+ })
|
|
|
+ this._emitter.emit(EVENT.CALLING_CANCEL, {
|
|
|
+ inviteID: event.data.inviteID,
|
|
|
+ invitee: event.data.invitee,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.INVITATION_TIMEOUT, (event) => {
|
|
|
+ console.log(TAG_NAME, 'onInvitationTimeout 邀请超时', `inviteID:${event.data.inviteID} inviteeList:${event.data.inviteeList}`)
|
|
|
+ // 邀请超时, 无人应答
|
|
|
+ this._emitter.emit(EVENT.NO_RESP, {
|
|
|
+ inviteID: event.data.inviteID,
|
|
|
+ inviteeList: event.data.inviteeList,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.SDK_READY, () => {
|
|
|
+ console.log(TAG_NAME, 'TSignaling SDK ready')
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.SDK_NOT_READY, () => {
|
|
|
+ this._emitter.emit(EVENT.ERROR, {
|
|
|
+ errorMsg: 'TSignaling SDK not ready !!! 如果想使用发送消息等功能,接入侧需驱动 SDK 进入 ready 状态',
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.TEXT_MESSAGE_RECEIVED, () => {
|
|
|
+
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.CUSTOM_MESSAGE_RECEIVED, () => {
|
|
|
+
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.REMOTE_USER_JOIN, () => {
|
|
|
+ //
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.REMOTE_USER_LEAVE, () => {
|
|
|
+ // 离开
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.KICKED_OUT, () => {
|
|
|
+ // 被踢下线 TODO
|
|
|
+ wx.showToast({
|
|
|
+ title: '您已被踢下线',
|
|
|
+ })
|
|
|
+ this.hangup()
|
|
|
+ })
|
|
|
+ this.tsignaling.on(TSignaling.EVENT.NET_STATE_CHANGE, () => {
|
|
|
+
|
|
|
+ })
|
|
|
+ // 监听TRTC SDK抛出的事件
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_USER_JOIN, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端用户进房', event, event.data.userID)
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ }, () => {
|
|
|
+ // this._emitter.emit(EVENT.REMOTE_USER_JOIN, { userID: event.data.userID })
|
|
|
+ this._emitter.emit(EVENT.USER_ENTER, {
|
|
|
+ userID: event.data.userID,
|
|
|
+ })
|
|
|
+ if (this.data._historyUserList.indexOf(event.data.userID) > -1) {
|
|
|
+ this.data._historyUserList.push(event.data.userID)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log(TAG_NAME, 'REMOTE_USER_JOIN', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ // 远端用户离开
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端用户离开', event, event.data.userID)
|
|
|
+ if (event.data.userID) {
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ streamList: event.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ // TODO: 房间内没有远端用户时就退房, 并且发出invite消息,带call_end信息
|
|
|
+ this._emitter.emit(EVENT.USER_LEAVE, {
|
|
|
+ userID: event.data.userID,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ console.log(TAG_NAME, 'REMOTE_USER_LEAVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ // 视频状态 true
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端视频可用', event, event.data.stream.userID)
|
|
|
+ const stream = event.data.stream
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ streamList: event.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
|
|
|
+ })
|
|
|
+ console.log(TAG_NAME, 'REMOTE_VIDEO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ // 视频状态 false
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端视频移除', event, event.data.stream.userID)
|
|
|
+ const stream = event.data.stream
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ streamList: event.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ // 有可能先触发了退房事件,用户名下的所有stream都已清除
|
|
|
+ if (stream.userID && stream.streamType) {
|
|
|
+ // this._emitter.emit(EVENT.REMOTE_VIDEO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log(TAG_NAME, 'REMOTE_VIDEO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ // 音频可用
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端音频可用', event)
|
|
|
+ const stream = event.data.stream
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ streamList: event.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ stream.playerContext = wx.createLivePlayerContext(stream.streamID, this)
|
|
|
+ // 新增的需要触发一次play 默认属性才能生效
|
|
|
+ // stream.playerContext.play()
|
|
|
+ // console.log(TAG_NAME, 'REMOTE_AUDIO_ADD playerContext.play()', stream)
|
|
|
+ // this._emitter.emit(EVENT.REMOTE_AUDIO_ADD, { userID: stream.userID, streamType: stream.streamType })
|
|
|
+ })
|
|
|
+ console.log(TAG_NAME, 'REMOTE_AUDIO_ADD', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ // 音频不可用
|
|
|
+ this.userController.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event)=>{
|
|
|
+ console.log(TAG_NAME, '远端音频移除', event, event.data.stream.userID)
|
|
|
+ const stream = event.data.stream
|
|
|
+ this.setData({
|
|
|
+ playerList: event.data.userList,
|
|
|
+ streamList: event.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ // 有可能先触发了退房事件,用户名下的所有stream都已清除
|
|
|
+ if (stream.userID && stream.streamType) {
|
|
|
+ // this._emitter.emit(EVENT.REMOTE_AUDIO_REMOVE, { userID: stream.userID, streamType: stream.streamType })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log(TAG_NAME, 'REMOTE_AUDIO_REMOVE', 'streamList:', this.data.streamList, 'userList:', this.data.userList)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 登录IM接口,所有功能需要先进行登录后才能使用
|
|
|
+ *
|
|
|
+ */
|
|
|
+ login() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.tsignaling.setLogLevel(0)
|
|
|
+ MTA.Page.stat()
|
|
|
+ this.tsignaling.login({
|
|
|
+ userID: this.data.config.userID,
|
|
|
+ userSig: this.data.config.userSig,
|
|
|
+ }).then( () => {
|
|
|
+ console.log(TAG_NAME, 'login', 'IM登入成功')
|
|
|
+ this._initEventEmitter()
|
|
|
+ resolve()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 登出接口,登出后无法再进行拨打操作
|
|
|
+ */
|
|
|
+ logout() {
|
|
|
+ this.tsignaling.logout({
|
|
|
+ userID: this.data.config.userID,
|
|
|
+ userSig: this.data.config.userSig,
|
|
|
+ }).then( () => {
|
|
|
+ console.log(TAG_NAME, 'login', 'IM登出成功')
|
|
|
+ }).catch( () => {
|
|
|
+ console.error(TAG_NAME, 'login', 'IM登出失败')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 监听事件
|
|
|
+ *
|
|
|
+ * @param eventCode 事件名
|
|
|
+ * @param handler 事件响应回调
|
|
|
+ */
|
|
|
+ on(eventCode, handler, context) {
|
|
|
+ this._emitter.on(eventCode, handler, context)
|
|
|
+ },
|
|
|
+
|
|
|
+ off(eventCode, handler) {
|
|
|
+ this._emitter.off(eventCode, handler)
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * C2C邀请通话,被邀请方会收到的回调
|
|
|
+ * 如果当前处于通话中,可以调用该函数以邀请第三方进入通话
|
|
|
+ *
|
|
|
+ * @param userID 被邀请方
|
|
|
+ * @param type 0-为之, 1-语音通话,2-视频通话
|
|
|
+ */
|
|
|
+ call({ userID, type }) {
|
|
|
+ // 生成房间号,拼接URL地址
|
|
|
+ const roomID = Math.floor(Math.random() * 100000000 + 1) // 随机生成房间号
|
|
|
+ this._getPushUrl(roomID)
|
|
|
+ this._enterTRTCRoom()
|
|
|
+ this.tsignaling.invite({
|
|
|
+ userID: userID,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: type,
|
|
|
+ room_id: roomID,
|
|
|
+ }),
|
|
|
+ timeout: 30,
|
|
|
+ }).then( (res) => {
|
|
|
+ console.log(`${TAG_NAME} call(userID: ${userID}, type: ${type}) success`)
|
|
|
+ this.data.invitationAccept.inviteID = res.inviteID
|
|
|
+ this.setData({
|
|
|
+ invitationAccept: this.data.invitationAccept,
|
|
|
+ callingFlag: true,
|
|
|
+ })
|
|
|
+ }).catch((error) => {
|
|
|
+ console.log(`${TAG_NAME} call(userID:${userID},type:${type}) failed', error: ${error}`)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * IM群组邀请通话,被邀请方会收到的回调
|
|
|
+ * 如果当前处于通话中,可以继续调用该函数继续邀请他人进入通话,同时正在通话的用户会收到的回调
|
|
|
+ *
|
|
|
+ * @param userIDList 邀请列表
|
|
|
+ * @param type 1-语音通话,2-视频通话
|
|
|
+ * @param groupID IM群组ID
|
|
|
+ */
|
|
|
+ groupCall(params) {
|
|
|
+ this.tsignaling.inviteInGroup({
|
|
|
+ groupID: params.groupID,
|
|
|
+ inviteeList: params.userIDList,
|
|
|
+ timeout: 30,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: params.type,
|
|
|
+ room_id: Math.floor(Math.random() * 100000000 + 1),
|
|
|
+ }),
|
|
|
+ }).then(function(res) {
|
|
|
+ console.log(TAG_NAME, 'inviteInGroup OK', res)
|
|
|
+ }).catch(function(error) {
|
|
|
+ console.log(TAG_NAME, 'inviteInGroup failed', error)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 当您作为被邀请方收到 {@link TRTCCallingDelegate#onInvited } 的回调时,可以调用该函数接听来电
|
|
|
+ */
|
|
|
+ accept() {
|
|
|
+ // 拼接pusherURL进房
|
|
|
+ console.log(TAG_NAME, 'accept() inviteID: ', this.data.invitation.inviteID)
|
|
|
+ this.tsignaling.accept({
|
|
|
+ inviteID: this.data.invitation.inviteID,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: this.data.config.type,
|
|
|
+ }),
|
|
|
+ }).then( () => {
|
|
|
+ console.log('接受成功')
|
|
|
+ }).catch( () => {
|
|
|
+ console.error('接受失败')
|
|
|
+ })
|
|
|
+ this._getPushUrl(this.data.invitation.roomID)
|
|
|
+ this._enterTRTCRoom()
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 当您作为被邀请方收到的回调时,可以调用该函数拒绝来电
|
|
|
+ */
|
|
|
+ reject() {
|
|
|
+ if (this.data.invitation.inviteID) {
|
|
|
+ this.tsignaling.reject({
|
|
|
+ inviteID: this.data.invitation.inviteID,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: this.data.config.type,
|
|
|
+ }),
|
|
|
+ }).then( (res) => {
|
|
|
+ console.log('demo reject OK', res)
|
|
|
+ this._reset()
|
|
|
+ }).catch( (error) => {
|
|
|
+ console.log('demo reject failed', error)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ console.warn(`${TAG_NAME} 未收到邀请,无法拒绝`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 当您处于通话中,可以调用该函数结束通话
|
|
|
+ */
|
|
|
+ hangup() {
|
|
|
+ const inviterFlag = !this.data.invitation.inviteID && this.data.invitationAccept.inviteID && true // 是否是邀请者
|
|
|
+ if (inviterFlag && !this.data.invitationAccept.acceptFlag && !this.data.invitationAccept.rejectFlag) {
|
|
|
+ console.log(TAG_NAME, 'cancel() inviteID: ', this.data.invitationAccept.inviteID)
|
|
|
+ this.tsignaling.cancel({
|
|
|
+ inviteID: this.data.invitationAccept.inviteID,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (this.data.playerList.length === 0 && this.data.invitationAccept.acceptFlag) {
|
|
|
+ const currentTime = Date.parse(new Date())
|
|
|
+ // console.log('发送call_end信息', currentTime)
|
|
|
+ this.data._historyUserList.forEach( (user) => {
|
|
|
+ this.tsignaling.invite({
|
|
|
+ userID: user,
|
|
|
+ data: JSON.stringify({
|
|
|
+ version: 0,
|
|
|
+ call_type: this.data.config.type,
|
|
|
+ call_end: currentTime,
|
|
|
+ }),
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this._emitter.emit(EVENT.CALL_END, {
|
|
|
+ inviteID: this.data.invitationAccept.inviteID,
|
|
|
+ call_end: currentTime,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ this._reset().then( () => {
|
|
|
+ this._emitter.emit(EVENT.HANG_UP)
|
|
|
+ console.log(TAG_NAME, 'hangup() pusherConfig: ', this.data.pusherConfig)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ _reset() {
|
|
|
+ return new Promise( (resolve, reject) => {
|
|
|
+ console.log(TAG_NAME, ' _reset()')
|
|
|
+ const result = this.userController.reset()
|
|
|
+ this.data.pusherConfig = {
|
|
|
+ pushUrl: '',
|
|
|
+ frontCamera: 'front',
|
|
|
+ enableMic: true,
|
|
|
+ enableCamera: true,
|
|
|
+ volume: 0,
|
|
|
+ },
|
|
|
+ // 清空状态
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ soundMode: 'speaker',
|
|
|
+ invitation: {
|
|
|
+ inviteID: '',
|
|
|
+ },
|
|
|
+ playerList: result.userList,
|
|
|
+ streamList: result.streamList,
|
|
|
+ _historyUserList: [],
|
|
|
+ active: false,
|
|
|
+ callingFlag: false,
|
|
|
+ invitationAccept: {
|
|
|
+ inviteID: '',
|
|
|
+ acceptFlag: false,
|
|
|
+ rejectFlag: false,
|
|
|
+ },
|
|
|
+ }, () => {
|
|
|
+ resolve()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * @param userID 远端用户id
|
|
|
+ */
|
|
|
+ startRemoteView(userID) {
|
|
|
+ this.data.streamList.forEach( (stream) => {
|
|
|
+ if (stream.userID === userID) {
|
|
|
+ stream.muteVideo = false
|
|
|
+ this.setData({
|
|
|
+ streamList: this.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, startRemoteView(${userID})`)
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 当您收到 onUserVideoAvailable 回调为false时,可以停止渲染数据
|
|
|
+ *
|
|
|
+ * @param userID 远端用户id
|
|
|
+ */
|
|
|
+ stopRemoteView(userID) {
|
|
|
+ this.data.streamList.forEach( (stream) => {
|
|
|
+ if (stream.userID === userID) {
|
|
|
+ stream.muteVideo = true
|
|
|
+ this.setData({
|
|
|
+ streamList: this.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, stopRemoteView(${userID})`)
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 您可以调用该函数开启摄像头
|
|
|
+ */
|
|
|
+ openCamera() {
|
|
|
+ this.data.pusherConfig.enableCamera = true
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, closeCamera() pusherConfig: ${this.data.pusherConfig}`)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 您可以调用该函数关闭摄像头
|
|
|
+ * 处于通话中的用户会收到回调
|
|
|
+ */
|
|
|
+ closeCamera() {
|
|
|
+ this.data.pusherConfig.enableCamera = false
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, closeCamera() pusherConfig: ${this.data.pusherConfig}`)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 是否静音mic
|
|
|
+ *
|
|
|
+ * @param isMute true:麦克风关闭 false:麦克风打开
|
|
|
+ */
|
|
|
+ // setMicMute(isMute) {
|
|
|
+ // this.data.pusherConfig.enableMic = !isMute
|
|
|
+ // this.setData({
|
|
|
+ // pusherConfig: this.data.pusherConfig,
|
|
|
+ // }, () => {
|
|
|
+ // console.log(`${TAG_NAME}, setMicMute(${isMute}) enableMic: ${this.data.pusherConfig.enableMic}`)
|
|
|
+ // })
|
|
|
+ // },
|
|
|
+ setMicMute(isMute) {
|
|
|
+ this.data.pusherConfig.enableMic = !isMute
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, setMicMute(${isMute}) enableMic: ${this.data.pusherConfig.enableMic}`)
|
|
|
+ wx.createLivePusherContext().setMICVolume({ volume: isMute ? 0 : 1 })
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ switchCamera(isFrontCamera) {
|
|
|
+ this.data.pusherConfig.frontCamera = isFrontCamera ? 'front' : 'back'
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, switchCamera(), frontCamera${this.data.pusherConfig.frontCamera}`)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ setHandsFree(isHandsFree) {
|
|
|
+ this.data.soundMode = isHandsFree ? 'speaker' : 'ear'
|
|
|
+ this.setData({
|
|
|
+ soundMode: this.data.soundMode,
|
|
|
+ }, () => {
|
|
|
+ console.log(`${TAG_NAME}, setHandsFree() result: ${this.data.soundMode}`)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ _toggleAudio() {
|
|
|
+ if (this.data.pusherConfig.enableMic) {
|
|
|
+ this.setMicMute(true)
|
|
|
+ } else {
|
|
|
+ this.setMicMute(false)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _toggleSoundMode() {
|
|
|
+ if (this.data.soundMode === 'speaker') {
|
|
|
+ this.setHandsFree(false)
|
|
|
+ } else {
|
|
|
+ this.setHandsFree(true)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _getPushUrl(roomId) {
|
|
|
+ // 拼接 puhser url rtmp 方案
|
|
|
+ console.log(TAG_NAME, '_getPushUrl', roomId)
|
|
|
+ // TODO: 解注释
|
|
|
+ if (ENV.IS_TRTC) {
|
|
|
+ // 版本高于7.0.8,基础库版本高于2.10.0 使用新的 url
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.setData({
|
|
|
+ active: true,
|
|
|
+ })
|
|
|
+ let roomID = ''
|
|
|
+ if (/^\d+$/.test(roomId)) {
|
|
|
+ // 数字房间号
|
|
|
+ roomID = '&roomid=' + roomId
|
|
|
+ } else {
|
|
|
+ // 字符串房间号
|
|
|
+ roomID = '&strroomid=' + roomId
|
|
|
+ }
|
|
|
+ setTimeout(()=> {
|
|
|
+ const pushUrl = 'room://cloud.tencent.com/rtc?sdkappid=' + this.data.config.sdkAppID +
|
|
|
+ roomID +
|
|
|
+ '&userid=' + this.data.config.userID +
|
|
|
+ '&usersig=' + this.data.config.userSig +
|
|
|
+ '&appscene=videocall' +
|
|
|
+ '&cloudenv=PRO' // ios此参数必填
|
|
|
+ console.warn(TAG_NAME, 'getPushUrl result:', pushUrl)
|
|
|
+ this.data.pusherConfig.pushUrl = pushUrl
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ })
|
|
|
+ resolve(pushUrl)
|
|
|
+ }, 0)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ console.error(TAG_NAME, '组件仅支持微信 App iOS >=7.0.9, Android >= 7.0.8, 小程序基础库版 >= 2.10.0')
|
|
|
+ console.error(TAG_NAME, '需要真机运行,开发工具不支持实时音视频')
|
|
|
+ },
|
|
|
+
|
|
|
+ _enterTRTCRoom() {
|
|
|
+ // 开始推流
|
|
|
+ wx.createLivePusherContext().start()
|
|
|
+ },
|
|
|
+
|
|
|
+ _hangUp() {
|
|
|
+ this.hangup()
|
|
|
+ },
|
|
|
+
|
|
|
+ _pusherStateChangeHandler(event) {
|
|
|
+ const code = event.detail.code
|
|
|
+ const message = event.detail.message
|
|
|
+ const TAG_NAME = 'TRTCCalling pusherStateChange: '
|
|
|
+ switch (code) {
|
|
|
+ case 0: // 未知状态码,不做处理
|
|
|
+ console.log(TAG_NAME, message, code)
|
|
|
+ break
|
|
|
+ case 1001:
|
|
|
+ console.log(TAG_NAME, '已经连接推流服务器', code)
|
|
|
+ break
|
|
|
+ case 1002:
|
|
|
+ console.log(TAG_NAME, '已经与服务器握手完毕,开始推流', code)
|
|
|
+ break
|
|
|
+ case 1003:
|
|
|
+ console.log(TAG_NAME, '打开摄像头成功', code)
|
|
|
+ break
|
|
|
+ case 1005:
|
|
|
+ console.log(TAG_NAME, '推流动态调整分辨率', code)
|
|
|
+ break
|
|
|
+ case 1006:
|
|
|
+ console.log(TAG_NAME, '推流动态调整码率', code)
|
|
|
+ break
|
|
|
+ case 1007:
|
|
|
+ console.log(TAG_NAME, '首帧画面采集完成', code)
|
|
|
+ break
|
|
|
+ case 1008:
|
|
|
+ console.log(TAG_NAME, '编码器启动', code)
|
|
|
+ break
|
|
|
+ case 1018:
|
|
|
+ console.log(TAG_NAME, '进房成功', code)
|
|
|
+ break
|
|
|
+ case 1019:
|
|
|
+ console.log(TAG_NAME, '退出房间', code)
|
|
|
+ // 20200421 iOS 仍然没有1019事件通知退房,退房事件移动到 exitRoom 方法里,但不是后端通知的退房成功
|
|
|
+ // this._emitter.emit(EVENT.LOCAL_LEAVE, { userID: this.data.pusher.userID })
|
|
|
+ break
|
|
|
+ case 2003:
|
|
|
+ console.log(TAG_NAME, '渲染首帧视频', code)
|
|
|
+ break
|
|
|
+ case 1020:
|
|
|
+ case 1031:
|
|
|
+ case 1032:
|
|
|
+ case 1033:
|
|
|
+ case 1034:
|
|
|
+ // 通过 userController 处理 1020 1031 1032 1033 1034
|
|
|
+ this.userController.userEventHandler(event)
|
|
|
+ break
|
|
|
+ case -1301:
|
|
|
+ console.error(TAG_NAME, '打开摄像头失败: ', code)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case -1302:
|
|
|
+ console.error(TAG_NAME, '打开麦克风失败: ', code)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case -1303:
|
|
|
+ console.error(TAG_NAME, '视频编码失败: ', code)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case -1304:
|
|
|
+ console.error(TAG_NAME, '音频编码失败: ', code)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case -1307:
|
|
|
+ console.error(TAG_NAME, '推流连接断开: ', code)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case -100018:
|
|
|
+ console.error(TAG_NAME, '进房失败: userSig 校验失败,请检查 userSig 是否填写正确', code, message)
|
|
|
+ this._emitter.emit(EVENT.ERROR, { code, message })
|
|
|
+ break
|
|
|
+ case 5000:
|
|
|
+ console.log(TAG_NAME, '小程序被挂起: ', code)
|
|
|
+ // 20200421 iOS 微信点击胶囊圆点会触发该事件
|
|
|
+ // 触发 5000 后,底层SDK会退房,返回前台后会自动进房
|
|
|
+ break
|
|
|
+ case 5001:
|
|
|
+ // 20200421 仅有 Android 微信会触发该事件
|
|
|
+ console.log(TAG_NAME, '小程序悬浮窗被关闭: ', code)
|
|
|
+ break
|
|
|
+ case 1021:
|
|
|
+ console.log(TAG_NAME, '网络类型发生变化,需要重新进房', code)
|
|
|
+ break
|
|
|
+ case 2007:
|
|
|
+ console.log(TAG_NAME, '本地视频播放loading: ', code)
|
|
|
+ break
|
|
|
+ case 2004:
|
|
|
+ console.log(TAG_NAME, '本地视频播放开始: ', code)
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ console.log(TAG_NAME, message, code)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ _playerStateChange(event) {
|
|
|
+ // console.log(TAG_NAME, '_playerStateChange', event)
|
|
|
+ this._emitter.emit(EVENT.REMOTE_STATE_UPDATE, event)
|
|
|
+ },
|
|
|
+
|
|
|
+ _playerAudioVolumeNotify(event) {
|
|
|
+ const userID = event.target.dataset.userid
|
|
|
+ const volume = event.detail.volume
|
|
|
+ const stream = this.userController.getStream({
|
|
|
+ userID: userID,
|
|
|
+ streamType: 'main',
|
|
|
+ })
|
|
|
+ if (stream) {
|
|
|
+ stream.volume = volume
|
|
|
+ }
|
|
|
+ this.setData({
|
|
|
+ streamList: this.data.streamList,
|
|
|
+ }, () => {
|
|
|
+ this._emitter.emit(EVENT.USER_VOICE_VOLUME, {
|
|
|
+ userID: userID,
|
|
|
+ volume: volume,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ _pusherAudioVolumeNotify(event) {
|
|
|
+ this.data.pusherConfig.volume = event.detail.volume
|
|
|
+ this._emitter.emit(EVENT.USER_VOICE_VOLUME, {
|
|
|
+ userID: this.data.config.userID,
|
|
|
+ volume: event.detail.volume,
|
|
|
+ })
|
|
|
+ this.setData({
|
|
|
+ pusherConfig: this.data.pusherConfig,
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生命周期方法
|
|
|
+ */
|
|
|
+ lifetimes: {
|
|
|
+ created: function() {
|
|
|
+ // 在组件实例刚刚被创建时执行
|
|
|
+ console.log(TAG_NAME, 'created', ENV)
|
|
|
+ this.tsignaling = new TSignaling({ SDKAppID: this.data.config.sdkAppID })
|
|
|
+ wx.setKeepScreenOn({
|
|
|
+ keepScreenOn: true,
|
|
|
+ })
|
|
|
+ MTA.App.init({
|
|
|
+ 'appID': '500728728',
|
|
|
+ 'eventID': '500730148',
|
|
|
+ 'autoReport': true,
|
|
|
+ 'statParam': true,
|
|
|
+ 'ignoreParams': [],
|
|
|
+ })
|
|
|
+ },
|
|
|
+ attached: function() {
|
|
|
+ // 在组件实例进入页面节点树时执行
|
|
|
+ console.log(TAG_NAME, 'attached')
|
|
|
+ this.EVENT = EVENT
|
|
|
+ this._emitter = new EventEmitter()
|
|
|
+ this.userController = new UserController()
|
|
|
+ MTA.Page.stat()
|
|
|
+ },
|
|
|
+ ready: function() {
|
|
|
+ // 在组件在视图层布局完成后执行
|
|
|
+ console.log(TAG_NAME, 'ready')
|
|
|
+ },
|
|
|
+ detached: function() {
|
|
|
+ // 在组件实例被从页面节点树移除时执行
|
|
|
+ console.log(TAG_NAME, 'detached')
|
|
|
+ this._reset()
|
|
|
+ },
|
|
|
+ error: function(error) {
|
|
|
+ // 每当组件方法抛出错误时执行
|
|
|
+ console.log(TAG_NAME, 'error', error)
|
|
|
+ },
|
|
|
+ },
|
|
|
+ pageLifetimes: {
|
|
|
+ show: function() {
|
|
|
+ },
|
|
|
+ hide: function() {
|
|
|
+ // 组件所在的页面被隐藏时执行
|
|
|
+ console.log(TAG_NAME, 'hide')
|
|
|
+ },
|
|
|
+ resize: function(size) {
|
|
|
+ // 组件所在的页面尺寸变化时执行
|
|
|
+ console.log(TAG_NAME, 'resize', size)
|
|
|
+ },
|
|
|
+ },
|
|
|
+})
|