<template>
	<view class="trtc-room-container">
		<template v-if="template === '1v1'">
			<one-v-one compName="oneVOne" :data="{pusher, streamList, debug}"></one-v-one>
		</template>
		<template v-if="template === 'grid'">
			<grid compName="grid" :data="{ pusher, streamList, visibleStreamList, debug, panelName, gridPagePlaceholderStreamList, gridPageCount, gridCurrentPage, gridPlayerPerPage, tabBarHeight }"></grid>
		</template>
		<template v-if="template === 'custom'">
			<custom compName="custom" :data="{ pusher, streamList, debug }"></custom>
		</template>

		<view class="im-panel" v-if="enableIM && showIMPanel">
			<view class="message-panel-body">
				<scroll-view :scroll-y="true" class="message-scroll-container" :scroll-into-view="scrollPosition"
					:scroll-with-animation="true" @scrolltoupper="onScrollToUpper">
					<view class="message-list">
						<view :id="'message' + item.msgID" v-for="(item, index) in messageList" :key="index">
							<view :style="'display: ' + (item.msgType === '1' ? 'template' : 'none')">
								<view
									:class="'message-item ' + (item.direction === 'right' ? 'right-flex-direction' : '')">
									<span :class="'user-name ' + (item.direction === 'left' ? '' : 'mine')">
										{{
                                            item.direction === 'left'
                                                ? config && config.doctorName
                                                    ? config.doctorName
                                                    : '医生'
                                                : config && config.patName
                                                ? config.patName
                                                : '自己'
                                        }}
									</span>
									<span v-if="item.consultText ? true : false"
										class="message-content">{{ item.consultText }}</span>
									<image @tap.stop.prevent="viewOriginal" :data-page="item" class="image"
										:src="item.picture" alt="" v-if="item.picture ? true : false" mode="widthFix">
									</image>
								</view>
							</view>

							<view class="col-3"
								:style="'display: ' + (item.msgType === '2' || item.msgType === '3' ? 'template' : 'none')">
								<view v-if="item.msgType === '2' ? true : false">
									{{ item.direction === 'left' ? '对方撤回了一条消息' : '你撤回了一条消息' }}
									<text class="re-edit"
										:style="'display: ' + (item.direction === 'right' && item.isModity ? 'inline-template' : 'none')">重新编辑</text>
								</view>
								<text v-if="item.msgType === '3' ? true : false">{{ item.sendTime }}</text>
							</view>
						</view>
						<view id="message-bottom"></view>
					</view>
				</scroll-view>
			</view>
			<view class="message-panel-bottom">
				<view class="message-input-container">
					<input class="message-input" type="text" maxlength="200" :value="messageContent"
						@input="inputIMMessageFun" @confirm="sendIMMessageFun" confirm-type="send" placeholder="请输入消息"
						placeholder-style="opacity: 0.55; color: #999;font-size: 32rpx;" />
				</view>
				<view class="message-send-btn">
					<i class="iconfont icon-tupian" style="font-size: 80rpx" @tap="onChooseImage"></i>
					<button class="btn" @tap="sendIMMessageFun" hover-class="btn-hover">发送</button>
				</view>
			</view>
			<view @tap="toggleIMPanelFun" class="close-btn">X</view>
		</view>

		<view :class="'debug-info-btn ' + (debugMode && !debugPanel ? '' : 'none')">
			<button class="debug-btn" @tap="debugTogglePanelFun" hover-class="button-hover">Debug</button>
		</view>
		<view :class="'debug-info ' + (debugMode && debugPanel ? '' : 'none')">
			<view @tap="debugTogglePanelFun" class="close-btn">X</view>
			<view>appVersion: {{ appVersion }}</view>
			<view>libVersion: {{ libVersion }}</view>
			<view>template: {{ template }}</view>
			<view>debug: <button :class="(debug ? '' : 'false') + ' debug-btn'" @tap="debugToggleVideoDebugFun"
					hover-class="button-hover">{{ debug }}</button></view>
			<view>userID: {{ pusher.userID }}</view>
			<view>roomID: {{ pusher.roomID }}</view>
			<view>camera: <button :class="(pusher.enableCamera ? '' : 'false') + ' debug-btn'" @tap="toggleVideoFun"
					hover-class="button-hover">{{ pusher.enableCamera }}</button></view>
			<view>mic: <button :class="(pusher.enableMic ? '' : 'false') + ' debug-btn'" @tap="toggleAudioFun"
					hover-class="button-hover">{{ pusher.enableMic }}</button></view>
			<view>switch camera:<button class="debug-btn" @tap="switchCamera"
					hover-class="button-hover">{{ cameraPosition || pusher.frontCamera }}</button></view>
			<view>
				Room:
				<button class="debug-btn" @tap="debugEnterRoomFun" hover-class="button-hover">确定</button>
				<button class="debug-btn" @tap="debugExitRoomFun" hover-class="button-hover">退出</button>
				<button class="debug-btn" @tap="debugGoBackFun" hover-class="button-hover">返回</button>
			</view>
			<view>IM:<button class="debug-btn" @tap="debugSendRandomMessageFun" hover-class="button-hover">send</button>
			</view>
			<view>user count: {{ userList.length }}</view>
			<view v-for="(item, index) in userList" :key="index">
				{{ item.userID }}| mainV:<span
					:class="'text ' + (item.hasMainVideo ? 'true' : 'false')">{{ item.hasMainVideo || false }}</span>
				| mainA:<span
					:class="'text ' + (item.hasMainAudio ? 'true' : 'false')">{{ item.hasMainAudio || false }}</span>
				| auxV:<span
					:class="'text ' + (item.hasAuxVideo ? 'true' : 'false')">{{ item.hasAuxVideo || false }}</span>
			</view>
			<view>stream count: {{ streamList.length }}</view>
			<view v-for="(item, index) in streamList" :key="index">
				{{ item.userID }}|{{ item.streamType }}| SubV:
				<button :class="(!item.muteVideo ? '' : 'false') + ' debug-btn'" @tap="debugToggleRemoteVideoFun"
					hover-class="button-hover" :data-user-i-d="item.userID" :data-stream-type="item.streamType">
					{{ !item.muteVideo }}
				</button>
				| SubA:
				<button :class="(!item.muteAudio ? '' : 'false') + ' debug-btn'" @tap="debugToggleRemoteAudioFun"
					hover-class="button-hover" :data-user-i-d="item.userID" :data-stream-type="item.streamType">
					{{ !item.muteAudio }}
				</button>
			</view>
		</view>
		<van-toast id="van-toast" />
	</view>
</template>

<script>
	import grid from "./template/grid/grid.vue";
	import oneVOne from "./template/oneVOne/oneVOne.vue";
	import custom from "./template/custom/custom.vue";
	import UserController from "./controller/user-controller.js";
	import Pusher from "./model/pusher.js";
	import {
		EVENT,
		DEFAULT_COMPONENT_CONFIG
	} from "./common/constants.js";
	import Event from "./utils/event.js";
	import * as ENV from './utils/environment.js';
	import TIM from "./libs/tim-wx.js";
	import MTA from "./libs/mta_analysis.js";
	import GenerateTestUserSig from '@/debug/GenerateTestUserSig';
	// #ifdef MP
	import IMGTXTTIM from 'tim-wx-sdk';
	import COS from "cos-wx-sdk-v5";
	// #endif
	// #ifdef H5
	import IMGTXTTIM from 'tim-js-sdk';
	import COS from "cos-js-sdk-v5";
	// #endif
	import $http from "@/utils/https";
	import Utils from '@/utils/utils';
	import httpConfig from '@/utils/httpConfig';
	const TAG_NAME = 'TRTC-ROOM';
	const IM_GROUP_TYPE = TIM.TYPES.GRP_CHATROOM; // TIM.TYPES.GRP_CHATROOM 体验版IM无数量限制，成员20个， TIM.TYPES.GRP_AVCHATROOM IM体验版最多10个，升级后无限制
	const app = getApp();
	let touchX = 0;
	let touchY = 0;
	export default {
		components: {
			"one-v-one": oneVOne,
			grid,
			custom
		},
		data() {
			return {
				pusher: null,
				debugPanel: true,

				// 是否打开组件调试面板
				debug: false,

				// 是否打开player pusher 的调试信息
				streamList: [],

				// 用于渲染player列表,存储stram
				visibleStreamList: [],

				// 有音频或者视频的StreamList
				userList: [],

				// 扁平化的数据用来返回给用户
				template: '',

				// 不能设置默认值，当默认值和传入组件的值不一致时，iOS渲染失败
				cameraPosition: '',

				// 摄像头位置，用于debug
				panelName: '',

				// 控制面板名称，包括 setting-panel  memberlist-panel
				localVolume: 0,

				remoteVolumeList: [],
				enableIM: false,

				// 用于组件内渲染
				showIMPanel: false,

				exitIMThrottle: false,
				messageContent: '',
				messageList: [],

				// 仅保留10条消息
				maxMessageListLength: 10,

				messageListScrollTop: 0,
				appVersion: ENV.APP_VERSION,
				libVersion: ENV.LIB_VERSION,
				hasGridPageTipsShow: false,
				gridPageCount: 0,

				// grid 布局 player 分页的总页数
				gridCurrentPage: 1,

				// grid 布局 当前页码
				gridPlayerPerPage: 4,

				// grid 布局每页 player的数量, 如果大于3，在逻辑里第一页需要减1。等于3 pusher 在每一页都出现。可选值: 3,4
				gridPagePlaceholderStreamList: [],

				// 占位数量
				moreMsgFlag: "Y",

				lastMsgID: "",
				scrollPosition: "",
				isKickedOut: true,
				tabBarHeight: app.globalData.height * 2 + 20,
				debugMode: ""
			};
		},
		/**
		 * 组件的属性列表
		 */
		props: {
			// 必要的初始化参数
			config: {
				type: Object,

				default: () => ({
					sdkAppID: '',
					userID: '',
					userSig: '',
					template: '',
					debugMode: false,

					// 是否开启调试模式
					enableIM: false,

					// 是否开启 IM
					patID: '',

					frUserID: '',
					toUserID: '',
					doctorName: '',
					patName: '',
					isTextChat: ''
				})
			}
		},
		created: function() {
			// 在组件实例刚刚被创建时执行
			console.log(TAG_NAME, 'created', ENV);
			MTA.App.init({
				appID: '500710685',
				eventID: '500710697',
				autoReport: true,
				statParam: true
			});
		},
		mounted() {
			// 处理小程序 attached 生命周期
			this.attached();
			// 处理小程序 ready 生命周期
			this.$nextTick(() => this.ready());
		},
		destroyed: function() {
			// 在组件实例被从页面节点树移除时执行
			console.log(TAG_NAME, 'detached');
			// 停止所有拉流，并重置数据
			this.exitRoom();
		},
		error: function(error) {
			// 每当组件方法抛出错误时执行
			console.log(TAG_NAME, 'error', error);
		},
		/**
		 * 组件的方法列表
		 */
		methods: {
			handlePageShow: function() {
				// 组件所在的页面被展示时执行
				console.log(TAG_NAME, "show status:", this.status);
				if (this.status.isPending) {
					// 经历了 5000 挂起事件
					this.status.isPending = false;
					// 修复iOS 最小化触发5000事件后，音频推流失败的问题 20200409注释修复逻辑
					// if (ENV.IS_IOS && this.data.pusher.enableMic) {
					//   this.unpublishLocalAudio().then(()=>{
					//     this.publishLocalAudio()
					//   })
					// }
				}

				if (this.status.isPush) {}
			},

			handlePageHide: function() {
				// 组件所在的页面被隐藏时执行
				console.log(TAG_NAME, "hide");
			},

			handlePageResize: function(size) {
				// 组件所在的页面尺寸变化时执行
				console.log(TAG_NAME, "resize", size);
			},

			ready: function() {
				// 在组件在视图层布局完成后执行
				console.log(TAG_NAME, "ready");
				// 初始化图咨询插件
				// this.initImgTxtRoom();
				let that = this;
				// if (app.globalData && app.globalData.tim) {
				//   app.globalData.tim.on(TIM.EVENT.MESSAGE_RECEIVED, (event) => this.handleMessageReceived(event, _this));
				// }
				// app.globalData.tim.on(TIM.EVENT.KICKED_OUT, (event) => this.monitorKickedOut(event, _this));
			},

			attached: function() {
				// 在组件实例进入页面节点树时执行
				console.log(TAG_NAME, "attached");
				this.initFun();
				MTA.Page.stat();
			},

			// 监听接收新消息
			handleMessageReceived: function(event) {
				let {
					config,
					showIMPanel
				} = this;
				let from = event.data[0].from;
				console.log(event);
				let that = this;
				if (config.toUserID === from) {
					try {
						let newMessageJSON = event.data && event.data.length > 0 ? JSON.parse(event.data[0].payload
							.text) : {};
						if (newMessageJSON && newMessageJSON.type === "M") {
							if (!showIMPanel) {
								this.setData({
									showIMPanel: true
								}, () => {
									// if (this.data.showIMPanel && !(this.data.messageList && this.data.messageList.length > 0)) {
									//   this.getChatData("bottom"); // 获取消息
									// }
									this.getChatData("bottom"); // 获取消息
								});
							} else {
								that.getChatData("bottom");
							}
						}
					} catch (error) {
						console.log("CatchClause", error);
						console.log("CatchClause", error);
						console.log(error);
					}
				}
			},

			// 监听是否被踢下线
			// monitorKickedOut: function (event, _this) {
			//   let { isKickedOut, config } = _this.data;
			//   if (isKickedOut) {
			//     _this.setData({ isKickedOut: false });
			//     Dialog.confirm({
			//       title: '账号异常',
			//       message: '您的账号在别处登录，是否重新登录???'
			//     }).then(() => {
			//       _this.loginImgTxt(config.frUserID);
			//     }).catch(() => {
			//       _this.setData({ showIMPanel: false, messageList: [] })
			//     });
			//   }
			// },

			// 获取消息列表数据
			getChatData: function(scroll) {
				let {
					lastMsgID,
					config
				} = this;
				let data = {
					params: [{
						admID: config.admID,
						frUser: config.frUserID,
						toUser: config.toUserID,
						lastMsgID
					}]
				};
				$http.post("urlS", this, {
					code: "02090005",
					data: data,
					namespace: "Y",
					success: function(res) {
						console.log(res);
						if (+res.errorCode === 0) {
							// console.log(res)
							let chatData = res.result && res.result.msgData instanceof Array ? res.result
								.msgData : [];
							for (var i = 0; i < chatData.length; i++) {
								chatData[i].navBarVisible = false;
								chatData[i].key = i + 1;
							}
							this.setData({
								messageList: chatData,
								moreMsgFlag: res.result.moreMsgFlag
							}, () => {
								let scrollPosition = "";
								if (scroll === 'bottom') {
									scrollPosition = "message" + this.messageList[this.messageList
										.length - 1].msgID;
								} else if (scroll === 'noBottom') {
									scrollPosition = "message" + lastMsgID;
								}
								console.log(scroll, scrollPosition);
								this.setData({
									scrollPosition
								});
							});
						} else {
							this.setData({
								messageList: [],
								moreMsgFlag: "N"
							});
						}
					}
				});
			},

			// 发送文字
			handleSend(textVal) {
				let {
					config
				} = this;
				let data = {
					params: [{
						admID: config.admID,
						frUser: config.frUserID,
						toUser: config.toUserID,
						consultText: textVal,
						picture: ""
					}]
				};
				$http.post("urlS", this, {
					code: "02090004",
					data: data,
					namespace: "Y",
					success: function(res) {
						if (+res.errorCode === 0) {
							let obj = {
								consultText: textVal,
								direction: "right",
								msgType: "1",
								isWithdraw: true,
								key: this.messageList.length + 1,
								msgID: res.result && res.result.msgID ? res.result.msgID : ""
							};
							this.setData({
								messageContent: "",
								messageList: [...this.messageList, {
									...obj
								}]
							}, () => {
								let scrollPosition = "message" + this.messageList[this.messageList.length - 1].msgID;
								this.setData({
									scrollPosition
								});
								this.handleSendText(res, textVal);
							});
						} else {
							uni.showToast({
								icon: 'none',
								title: res.errorMessage
							})
						}
					}
				});
			},

			// 文字发送
			async handleSendText(res, textVal) {
				let {
					config
				} = this;
				let messageContent = {
					userID: config.toUser ? config.toUser.slice(4) : '',
					admID: config.admID || "",
					patID: config.patID ? config.patID : '',
					patName: config.patName || "",
					userName: config.doctorName || "",
					type: 'M',
					text: textVal
				};
				let message = app.globalData.tim.createTextMessage({
					to: config.toUserID,
					conversationType: IMGTXTTIM.TYPES.CONV_C2C,
					payload: {
						text: JSON.stringify(messageContent)
					}
				});
				// 2. 发送消息
				let promise = app.globalData.tim.sendMessage(message);
				promise.then(imResponse => {
					// 发送成功
					if (imResponse && imResponse.code === 0) {
						console.log("IM发送成功。");
					}
				}).catch(function(imError) {
					// 发送失败
					// Toast('发送失败:', imError);
					console.log(imError);
				});
			},

			// 滚动到顶部
			async onScrollToUpper() {
				let {
					moreMsgFlag,
					messageList
				} = this;
				if (moreMsgFlag === "N") {
					if (messageList && messageList[0].sendTime === "没有更多消息记录了。") {
						return;
					}
					let obj = {
						direction: "right",
						msgType: "3",
						sendTime: "没有更多消息记录了。"
					};
					messageList.unshift(obj);
					this.setData({
						messageList
					});
					return;
				} else {
					this.setData({
						lastMsgID: messageList.length > 0 ? messageList[0].msgID : ""
					}, () => {
						this.getChatData("noBottom");
					});
				}
			},

			/**
			 * 初始化各项参数和用户控制模块，在组件实例触发 attached 时调用，此时不建议对View进行变更渲染（调用setData方法）
			 */
			initFun() {
				console.log(TAG_NAME, '_init');
				this.userController = new UserController(this);
				this._emitter = new Event();
				this.EVENT = EVENT;
				this.initStatusFun();
				this.bindEventFun();
				this.gridBindEventFun();
				this.keepScreenOnFun();
				console.log(TAG_NAME, '_init success component:', this);
			},

			initStatusFun() {
				this.status = {
					isPush: false,
					// 推流状态
					isPending: false // 挂起状态，触发5000事件标记为true，onShow后标记为false
				};

				this._lastTapTime = 0; // 点击时间戳 用于判断双击事件
				this._beforeLastTapTime = 0; // 点击时间戳 用于判断双击事件
				// 是否进入全屏状态
				this._lastTapCoordinate = {
					x: 0,
					y: 0
				};
				// 点击时的坐标
				this._isFullscreen = false;
			},

			/**
			 * 监听组件属性变更，外部变更组件属性时触发该监听
			 * @param {Object} data newVal，oldVal
			 */
			propertyObserverFun(data) {
				console.log(TAG_NAME, '_propertyObserver', data, this.config);
				if (data.name === 'config') {
					const config = Object.assign({}, DEFAULT_COMPONENT_CONFIG, data.newVal);
					console.log(TAG_NAME, '_propertyObserver config:', config);
					// 由于 querystring 只支持 String 类型，做一个类型防御
					if (typeof config.debugMode === 'string') {
						config.debugMode === 'true' ? true : false;
					}
					// 初始化IM
					if (config.enableIM && config.sdkAppID) {
						this.initIMFun(config);
					}
					if (config.sdkAppID && data.oldVal.sdkAppID !== config.sdkAppID && MTA) {
						MTA.Event.stat('sdkAppID', {
							'value': config.sdkAppID
						});
					}
					// 独立设置与pusher无关的配置
					this.setData({
						enableIM: config.enableIM || true,
						template: config.template,
						debugMode: config.debugMode || false,
						debug: config.debugMode || false
					});
					this.setPusherConfigFun(config);
				}
			},

			//  _______             __        __  __
			//  |       \           |  \      |  \|  \
			//  | $$$$$$$\ __    __ | $$____  | $$ \$$  _______
			//  | $$__/ $$|  \  |  \| $$    \ | $$|  \ /       \
			//  | $$    $$| $$  | $$| $$$$$$$\| $$| $$|  $$$$$$$
			//  | $$$$$$$ | $$  | $$| $$  | $$| $$| $$| $$
			//  | $$      | $$__/ $$| $$__/ $$| $$| $$| $$_____
			//  | $$       \$$    $$| $$    $$| $$| $$ \$$     \
			//   \$$        \$$$$$$  \$$$$$$$  \$$ \$$  \$$$$$$$

			/**
			 * 进房
			 * @param {Object} params 必传 roomID 取值范围 1 ~ 4294967295
			 * @returns {Promise}
			 */
			enterRoom(params) {
				return new Promise((resolve, reject) => {
					console.log(TAG_NAME, 'enterRoom');
					console.log(TAG_NAME, 'params', params);
					console.log(TAG_NAME, 'config', this.config);
					console.log(TAG_NAME, 'pusher', this.pusher);
					// 1. 补齐进房参数，校验必要参数是否齐全
					if (params) {
						Object.assign(this.pusher, params);
						Object.assign(this.config, params);
					}
					if (!this.checkParamFun(this.config)) {
						reject(new Error('缺少必要参数'));
						return;
					}
					// 2. 根据参数拼接 push url，赋值给 live-pusher，
					this.getPushUrlFun(this.config).then(pushUrl => {
						this.pusher.url = pushUrl;
						this.setData({
							pusher: this.pusher
						}, () => {
							// 真正进房成功需要通过 1018 事件通知
							console.log(TAG_NAME, 'enterRoom', this.pusher);
							// view 渲染成功回调后，开始推流
							this.pusher.getPusherContext().start();
							this.status.isPush = true;
							resolve();
						});
					}).catch(res => {
						// 进房失败需要通过 pusher state 事件通知，目前还没有准确的事件通知
						console.error(TAG_NAME, 'enterRoom error', res);
						reject(res);
					});
					// 初始化 IM SDK
					// this._initIM(this.data.config)
					// 登录IM
					this.loginIMFun({
						...this.config,
						roomID: params.roomID
					});
				});
			},

			/**
			 * 退房，停止推流和拉流，并重置数据
			 * @returns {Promise}
			 */
			exitRoom() {
				return new Promise((resolve, reject) => {
					console.log(TAG_NAME, 'exitRoom');
					this.exitIMFun();
					this.pusher.reset();
					this.status.isPush = false;
					const result = this.userController.reset();
					this.setData({
						pusher: this.pusher,
						userList: result.userList,
						streamList: result.streamList,
						visibleStreamList: this.filterVisibleStreamFun(result.streamList)
					}, () => {
						// 在销毁页面时调用，不会走到这里
						resolve({
							userList: this.userList,
							streamList: this.streamList
						});
						console.log(TAG_NAME, 'exitRoom success', this.pusher, this.streamList, this.userList);
					});
				});
			},

			/**
			 * 开启摄像头
			 * @returns {Promise}
			 */
			publishLocalVideo() {
				// 设置 pusher enableCamera
				console.log(TAG_NAME, 'publishLocalVideo 开启摄像头');
				return this.setPusherConfigFun({
					enableCamera: true
				});
			},

			/**
			 * 关闭摄像头
			 * @returns {Promise}
			 */
			unpublishLocalVideo() {
				// 设置 pusher enableCamera
				console.log(TAG_NAME, 'unpublshLocalVideo 关闭摄像头');
				return this.setPusherConfigFun({
					enableCamera: false
				});
			},

			/**
			 * 开启麦克风
			 * @returns {Promise}
			 */
			publishLocalAudio() {
				// 设置 pusher enableCamera
				console.log(TAG_NAME, 'publishLocalAudio 开启麦克风');
				return this.setPusherConfigFun({
					enableMic: true
				});
			},

			/**
			 * 关闭麦克风
			 * @returns {Promise}
			 */
			unpublishLocalAudio() {
				// 设置 pusher enableCamera
				console.log(TAG_NAME, 'unpublshLocalAudio 关闭麦克风');
				return this.setPusherConfigFun({
					enableMic: false
				});
			},

			/**
			 * 订阅远端视频 主流 小画面 辅流
			 * @param {Object} params {userID,streamType} streamType 传入 small 时修改对应的主流 url 的 _definitionType 参数为 small, stream.streamType 仍为 main
			 * @returns {Promise}
			 */
			subscribeRemoteVideo(params) {
				console.log(TAG_NAME, 'subscribeRemoteVideo', params);
				// 设置指定 user streamType 的 muteVideo 为 false
				const config = {
					muteVideo: false
				};
				// 本地数据结构里的 streamType 只支持 main 和 aux ，订阅 small 也是对 main 进行处理
				const streamType = params.streamType === 'small' ? 'main' : params.streamType;
				const stream = this.userController.getStream({
					userID: params.userID,
					streamType: streamType
				});
				stream.muteVideoPrev = false; // 用于分页切换时保留player当前的订阅状态

				if (params.streamType === 'small' || params.streamType === 'main') {
					if (stream && stream.streamType === 'main') {
						console.log(TAG_NAME, 'subscribeRemoteVideo switch small', stream.src);
						if (params.streamType === 'small') {
							config.src = stream.src.replace('main', 'small');
							config._definitionType = 'small'; // 用于设置面板的渲染
						} else if (params.streamType === 'main') {
							stream.src = stream.src.replace('small', 'main');
							config._definitionType = 'main';
						}
						console.log(TAG_NAME, 'subscribeRemoteVideo', stream.src);
					}
				}
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: streamType,
					config: config
				});
			},

			/**
			 * 取消订阅远端视频
			 * @param {Object} params {userID,streamType}
			 * @returns {Promise}
			 */
			unsubscribeRemoteVideo(params) {
				console.log(TAG_NAME, 'unsubscribeRemoteVideo', params);
				const stream = this.userController.getStream({
					userID: params.userID,
					streamType: params.streamType
				});
				stream.muteVideoPrev = true; // 用于分页切换时保留player当前的订阅状态
				// 设置指定 user streamType 的 muteVideo 为 true
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						muteVideo: true
					}
				});
			},

			/**
			 * 订阅远端音频
			 * @param {Object} params userID 用户ID
			 * @returns {Promise}
			 */
			subscribeRemoteAudio(params) {
				console.log(TAG_NAME, 'subscribeRemoteAudio', params);
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: 'main',
					config: {
						muteAudio: false
					}
				});
			},

			/**
			 * 取消订阅远端音频
			 * @param {Object} params userID 用户ID
			 * @returns {Promise}
			 */
			unsubscribeRemoteAudio(params) {
				console.log(TAG_NAME, 'unsubscribeRemoteAudio', params);
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: 'main',
					config: {
						muteAudio: true
					}
				});
			},

			on(eventCode, handler, context) {
				this._emitter.on(eventCode, handler, context);
			},

			off(eventCode, handler) {
				this._emitter.off(eventCode, handler);
			},

			getRemoteUserList() {
				return this.userList;
			},

			/**
			 * 切换前后摄像头
			 */
			switchCamera() {
				if (!this.cameraPosition) {
					// this.data.pusher.cameraPosition 是初始值，不支持动态设置
					this.cameraPosition = this.pusher.frontCamera;
				}
				console.log(TAG_NAME, 'switchCamera', this.cameraPosition);
				this.cameraPosition = this.cameraPosition === 'front' ? 'back' : 'front';
				this.setData({
					cameraPosition: this.cameraPosition
				}, () => {
					console.log(TAG_NAME, 'switchCamera success', this.cameraPosition);
				});
				// wx 7.0.9 不支持动态设置 pusher.frontCamera ，只支持调用 API switchCamer() 设置，这里修改 cameraPosition 是为了记录状态
				this.pusher.getPusherContext().switchCamera();
			},

			/**
			 * 设置指定player view的渲染坐标和尺寸
			 * @param {object} params
			 * userID: string
			 * streamType: string
			 * xAxis: number
			 * yAxis: number
			 * width: number
			 * height: number
			 * @returns {Promise}
			 */
			setViewRect(params) {
				console.log(TAG_NAME, 'setViewRect', params);
				if (this.template !== 'custom') {
					console.warn(`如需使用setViewRect方法，请初始化时设置template:"custom", 当前 template:"${this.template}"`);
				}
				console.info(`不建议使用该方法动态修改样式，避免引起小程序渲染层问题，建议直接修改 wxml wxss 进行样式定制化`);
				if (this.pusher.userID === params.userID) {
					return this.setPusherConfigFun({
						xAxis: params.xAxis,
						yAxis: params.yAxis,
						width: params.width,
						height: params.height
					});
				}
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						xAxis: params.xAxis,
						yAxis: params.yAxis,
						width: params.width,
						height: params.height
					}
				});
			},

			/**
			 * 设置指定 player 或者 pusher view 是否可见
			 * @param {object} params
			 * userID: string
			 * streamType: string
			 * isVisible：boolean
			 * @returns {Promise}
			 */
			setViewVisible(params) {
				console.log(TAG_NAME, 'setViewVisible', params);
				if (this.template !== 'custom') {
					console.warn(`如需使用setViewVisible方法，请初始化时设置template:"custom", 当前 template:"${this.template}"`);
				}
				console.info(`不建议使用该方法动态修改样式，避免引起小程序渲染层问题，建议直接修改 wxml wxss 进行样式定制化`);
				if (this.pusher.userID === params.userID) {
					return this.setPusherConfigFun({
						isVisible: params.isVisible
					});
				}
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						isVisible: params.isVisible
					}
				});
			},

			/**
			 * 设置指定player view的层级
			 * @param {Object} params
			 * userID: string
			 * streamType: string
			 * zIndex: number
			 * @returns {Promise}
			 */
			setViewZIndex(params) {
				console.log(TAG_NAME, 'setViewZIndex', params);
				if (this.template !== 'custom') {
					console.warn(`如需使用setViewZIndex方法，请初始化时设置template:"custom", 当前 template:"${this.template}"`);
				}
				console.info(`不建议使用该方法动态修改样式，避免引起小程序渲染层问题，建议直接修改 wxml wxss 进行样式定制化`);
				if (this.pusher.userID === params.userID) {
					return this.setPusherConfigFun({
						zIndex: params.zindex || params.zIndex
					});
				}
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						zIndex: params.zindex || params.zIndex
					}
				});
			},

			/**
			 * 播放背景音
			 * @param {Object} params url
			 * @returns {Promise}
			 */
			playBGM(params) {
				return new Promise((resolve, reject) => {
					this.pusher.getPusherContext().playBGM({
						url: params.url,
						// 已经有相关事件不需要在这里监听,目前用于测试
						success: () => {
							console.log(TAG_NAME, '播放背景音成功');
							// this._emitter.emit(EVENT.BGM_PLAY_START)
							resolve();
						},
						fail: () => {
							console.log(TAG_NAME, '播放背景音失败');
							this._emitter.emit(EVENT.BGM_PLAY_FAIL);
							reject(new Error('播放背景音失败'));
						}
						// complete: () => {
						//   console.log(TAG_NAME, '背景完成')
						//   this._emitter.emit(EVENT.BGM_PLAY_COMPLETE)
						// },
					});
				});
			},

			stopBGM() {
				this.pusher.getPusherContext().stopBGM();
			},

			pauseBGM() {
				this.pusher.getPusherContext().pauseBGM();
			},

			resumeBGM() {
				this.pusher.getPusherContext().resumeBGM();
			},

			/**
			 * 设置背景音音量
			 * @param {Object} params volume
			 */
			setBGMVolume(params) {
				this.pusher.getPusherContext().setBGMVolume({
					volume: params.volume
				});
			},

			/**
			 * 设置麦克风音量
			 * @param {Object} params volume
			 */
			setMICVolume(params) {
				this.pusher.getPusherContext().setMICVolume({
					volume: params.volume
				});
			},

			/**
			 * 发送SEI消息
			 * @param {Object} params message
			 * @returns {Promise}
			 */
			sendSEI(params) {
				return new Promise((resolve, reject) => {
					this.pusher.getPusherContext().sendMessage({
						msg: params.message,
						success: function(result) {
							resolve(result);
						}
					});
				});
			},

			/**
			 * pusher 和 player 的截图并保存
			 * @param {Object} params userID streamType
			 * @returns {Promise}
			 */
			snapshot(params) {
				console.log(TAG_NAME, 'snapshot', params);
				return new Promise((resolve, reject) => {
					this.captureSnapshot(params).then(result => {
						uni.saveImageToPhotosAlbum({
							filePath: result.tempImagePath,
							success(res) {
								uni.showToast({
									title: '已保存到相册'
								});
								console.log('save photo is success', res);
								resolve(result);
							},
							fail: function(error) {
								uni.showToast({
									icon: 'none',
									title: '保存失败'
								});
								console.log('save photo is fail', error);
								reject(error);
							}
						});
					}).catch(error => {
						reject(error);
					});
				});
			},

			/**
			 * 获取pusher 和 player 的截图
			 * @param {Object} params userID streamType
			 * @returns {Promise}
			 */
			captureSnapshot(params) {
				return new Promise((resolve, reject) => {
					if (params.userID === this.pusher.userID) {
						// pusher
						this.pusher.getPusherContext().snapshot({
							quality: 'raw',
							complete: result => {
								console.log(TAG_NAME, 'snapshot pusher', result);
								if (result.tempImagePath) {
									resolve(result);
								} else {
									console.log('snapShot 回调失败', result);
									reject(new Error('截图失败'));
								}
							}
						});
					} else {
						// player
						this.userController.getStream(params).playerContext.snapshot({
							quality: 'raw',
							complete: result => {
								console.log(TAG_NAME, 'snapshot player', result);
								if (result.tempImagePath) {
									resolve(result);
								} else {
									console.log('snapShot 回调失败', result);
									reject(new Error('截图失败'));
								}
							}
						});
					}
				});
			},

			/**
			 * 将远端视频全屏
			 * @param {Object} params userID streamType direction
			 * @returns {Promise}
			 */
			enterFullscreen(params) {
				console.log(TAG_NAME, 'enterFullscreen', params);
				return new Promise((resolve, reject) => {
					this.userController.getStream(params).playerContext.requestFullScreen({
						direction: params.direction || 0,
						success: event => {
							console.log(TAG_NAME, 'enterFullscreen success', event);
							resolve(event);
						},
						fail: event => {
							console.log(TAG_NAME, 'enterFullscreen fail', event);
							reject(event);
						}
					});
				});
			},

			/**
			 * 将远端视频取消全屏
			 * @param {Object} params userID streamType
			 * @returns {Promise}
			 */
			exitFullscreen(params) {
				console.log(TAG_NAME, 'exitFullscreen', params);
				return new Promise((resolve, reject) => {
					this.userController.getStream(params).playerContext.exitFullScreen({
						success: event => {
							console.log(TAG_NAME, 'exitFullScreen success', event);
							resolve(event);
						},
						fail: event => {
							console.log(TAG_NAME, 'exitFullScreen fail', event);
							reject(event);
						}
					});
				});
			},

			/**
			 * 设置 player 视图的横竖屏显示
			 * @param {Object} params userID streamType orientation: vertical, horizontal
			 * @returns {Promise}
			 */
			setRemoteOrientation(params) {
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						orientation: params.orientation
					}
				});
			},

			// 改为：
			setViewOrientation(params) {
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						orientation: params.orientation
					}
				});
			},

			/**
			 * 设置 player 视图的填充模式
			 * @param {Object} params userID streamType fillMode: contain，fillCrop
			 * @returns {Promise}
			 */
			setRemoteFillMode(params) {
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						objectFit: params.fillMode
					}
				});
			},

			// 改为：
			setViewFillMode(params) {
				return this.setPlayerConfigFun({
					userID: params.userID,
					streamType: params.streamType,
					config: {
						objectFit: params.fillMode
					}
				});
			},

			/**
			 * 发送C2C文本消息
			 * @param {*} params userID,message
			 * @returns {Promise}
			 */
			sendC2CTextMessage(params) {
				if (!this.tim) {
					console.warn(TAG_NAME, '未开启IM功能，该方法无法使用', params);
					return;
				}
				console.log(TAG_NAME, 'sendC2CTextMessage', params);
				const message = this.tim.createTextMessage({
					to: params.userID + '',
					conversationType: TIM.TYPES.CONV_C2C,
					payload: {
						text: params.message
					}
				});
				const promise = this.tim.sendMessage(message);
				promise.then(function(imResponse) {
					// 发送成功
					console.log(TAG_NAME, 'sendC2CTextMessage success', imResponse);
				}).catch(function(imError) {
					// 发送失败
					console.warn(TAG_NAME, 'sendC2CTextMessage error:', imError);
				});
				return promise;
			},

			/**
			 * 发送C2C自定义消息
			 * @param {*} params: userID payload
			 * @returns {Promise}
			 *
			 */
			sendC2CCustomMessage(params) {
				if (!this.tim) {
					console.warn(TAG_NAME, '未开启IM功能，该方法无法使用', params);
					return;
				}
				console.log(TAG_NAME, 'sendC2CCustomMessage', params);
				const message = this.tim.createCustomMessage({
					to: params.userID + '',
					conversationType: TIM.TYPES.CONV_C2C,
					payload: params.payload
				});
				const promise = this.tim.sendMessage(message);
				promise.then(function(imResponse) {
					// 发送成功
					console.log(TAG_NAME, 'sendMessage success', imResponse);
				}).catch(function(imError) {
					// 发送失败
					console.warn(TAG_NAME, 'sendMessage error:', imError);
				});
				return promise;
			},

			/**
			 * 发送群组文本消息
			 * @param {*} params roomID message
			 * @returns {Promise}
			 *
			 */
			sendGroupTextMessage(params) {
				if (!this.tim) {
					console.warn(TAG_NAME, '未开启IM功能，该方法无法使用', params);
					return;
				}
				console.log(TAG_NAME, 'sendGroupTextMessage', params);
				const message = this.tim.createTextMessage({
					to: params.roomID + '',
					conversationType: TIM.TYPES.CONV_GROUP,
					payload: {
						text: params.message
					}
				});
				const promise = this.tim.sendMessage(message);
				promise.then(function(imResponse) {
					// 发送成功
					console.log(TAG_NAME, 'sendGroupTextMessage success', imResponse);
				}).catch(function(imError) {
					// 发送失败
					console.warn(TAG_NAME, 'sendGroupTextMessage error:', imError);
				});
				return promise;
			},

			/**
			 * 发送群组自定义消息
			 * @param {*} params roomID payload
			 * @returns {Promise}
			 *
			 */
			sendGroupCustomMessage(params) {
				if (!this.tim) {
					console.warn(TAG_NAME, '未开启IM功能，该方法无法使用', params);
					return;
				}
				console.log(TAG_NAME, 'sendGroupCustomMessage', params);
				const message = this.tim.createCustomMessage({
					to: params.roomID + '',
					conversationType: TIM.TYPES.CONV_GROUP,
					payload: params.payload
				});
				const promise = this.tim.sendMessage(message);
				promise.then(function(imResponse) {
					// 发送成功
					console.log(TAG_NAME, 'sendMessage success', imResponse);
				}).catch(function(imError) {
					// 发送失败
					console.warn(TAG_NAME, 'sendMessage error:', imError);
				});
				return promise;
			},

			// ______             __                                              __
			// |      \           |  \                                            |  \
			//  \$$$$$$ _______  _| $$_     ______    ______   _______    ______  | $$
			//   | $$  |       \|   $$ \   /      \  /      \ |       \  |      \ | $$
			//   | $$  | $$$$$$$\\$$$$$$  |  $$$$$$\|  $$$$$$\| $$$$$$$\  \$$$$$$\| $$
			//   | $$  | $$  | $$ | $$ __ | $$    $$| $$   \$$| $$  | $$ /      $$| $$
			//  _| $$_ | $$  | $$ | $$|  \| $$$$$$$$| $$      | $$  | $$|  $$$$$$$| $$
			// |   $$ \| $$  | $$  \$$  $$ \$$     \| $$      | $$  | $$ \$$    $$| $$
			//  \$$$$$$ \$$   \$$   \$$$$   \$$$$$$$ \$$       \$$   \$$  \$$$$$$$ \$$
			/**
			 * 设置推流参数并触发页面渲染更新
			 * @param {Object} config live-pusher 的配置
			 * @returns {Promise}
			 */
			setPusherConfigFun(config) {
				console.log(TAG_NAME, '_setPusherConfig', config, this.pusher);
				return new Promise((resolve, reject) => {
					if (!this.pusher) {
						this.pusher = new Pusher(config);
					} else {
						Object.assign(this.pusher, config);
					}
					this.setData({
						pusher: this.pusher
					}, () => {
						// console.log(TAG_NAME, '_setPusherConfig setData compelete', 'config:', config, 'pusher:', this.data.pusher)
						resolve(config);
					});
				});
			},

			/**
			 * 设置指定 player 属性并触发页面渲染
			 * @param {Object} params include userID,streamType,config
			 * @returns {Promise}
			 */
			setPlayerConfigFun(params) {
				const userID = params.userID;
				const streamType = params.streamType;
				const config = params.config;
				console.log(TAG_NAME, '_setPlayerConfig', params);
				return new Promise((resolve, reject) => {
					// 获取指定的userID streamType 的 stream
					const user = this.userController.getUser(userID);
					if (user && user.streams[streamType]) {
						Object.assign(user.streams[streamType], config);
						// user.streams引用的对象和 streamList 里的是同一个
						this.setData({
							streamList: this.streamList,
							visibleStreamList: this.filterVisibleStreamFun(this.streamList, true)
						}, () => {
							// console.log(TAG_NAME, '_setPlayerConfig complete', params, 'streamList:', this.data.streamList)
							resolve(params);
						});
					} else {
						// 不需要reject，静默处理
						console.warn(TAG_NAME, '指定 userID 或者 streamType 不存在');
						// reject(new Error('指定 userID 或者 streamType 不存在'))
					}
				});
			},

			/**
			 * 设置列表数据，并触发页面渲染
			 * @param {Object} params include userList, stramList
			 * @returns {Promise}
			 */
			setListFun(params) {
				console.log(TAG_NAME, '_setList', params, this.template);
				const {
					userList,
					streamList
				} = params;
				return new Promise((resolve, reject) => {
					let visibleStreamList = [];
					const data = {
						userList: userList || this.userList,
						streamList: streamList || this.streamList
					};
					if (this.template === 'grid') {
						visibleStreamList = this.filterVisibleStreamFun(streamList);
						data.visibleStreamList = visibleStreamList || this.visibleStreamList;
						data.gridPagePlaceholderStreamList = this.gridPagePlaceholderStreamList;
						data.gridCurrentPage = this.gridCurrentPage;
						data.gridPageCount = this.gridPageCount;
					}
					this.setData(data, () => {
						resolve(params);
					});
				});
			},

			/**
			 * 必选参数检测
			 * @param {Object} rtcConfig rtc参数
			 * @returns {Boolean}
			 */
			checkParamFun(rtcConfig) {
				console.log(TAG_NAME, 'checkParam config:', rtcConfig);
				if (!rtcConfig.sdkAppID) {
					console.error('未设置 sdkAppID');
					return false;
				}
				if (rtcConfig.roomID === undefined) {
					console.error('未设置 roomID');
					return false;
				}
				if (rtcConfig.roomID < 1 || rtcConfig.roomID > 4294967296) {
					console.error('roomID 超出取值范围 1 ~ 4294967295');
					return false;
				}
				if (!rtcConfig.userID) {
					console.error('未设置 userID');
					return false;
				}
				if (!rtcConfig.userSig) {
					console.error('未设置 userSig');
					return false;
				}
				if (!rtcConfig.template) {
					console.error('未设置 template');
					return false;
				}
				return true;
			},

			getPushUrlFun(rtcConfig) {
				// 拼接 puhser url rtmp 方案
				console.log(TAG_NAME, '_getPushUrl', rtcConfig);
				if (ENV.IS_TRTC) {
					// 版本高于7.0.8，基础库版本高于2.10.0 使用新的 url
					return new Promise((resolve, reject) => {
						// appscene videocall live
						// cloudenv PRO CCC DEV UAT
						// encsmall 0
						// 对外的默认值是rtc ，对内的默认值是videocall
						rtcConfig.scene = !rtcConfig.scene || rtcConfig.scene === 'rtc' ? 'videocall' : 'live';
						rtcConfig.enableBlackStream = rtcConfig.enableBlackStream || 1;
						rtcConfig.encsmall = rtcConfig.encsmall || 0;
						rtcConfig.cloudenv = rtcConfig.cloudenv || 'PRO';
						setTimeout(() => {
							const pushUrl = 'room://cloud.tencent.com/rtc?sdkappid=' + rtcConfig.sdkAppID +
								'&roomid=' + rtcConfig.roomID + '&userid=' + rtcConfig.userID +
								'&usersig=' + rtcConfig.userSig + '&appscene=' + rtcConfig.scene +
								'&encsmall=' + rtcConfig.encsmall + '&cloudenv=' + rtcConfig.cloudenv;
							console.log(TAG_NAME, 'getPushUrl result:', pushUrl);
							resolve(pushUrl);
						}, 0);
					});
				}
				console.error(TAG_NAME, '组件仅支持微信 App iOS >=7.0.9, Android >= 7.0.8, 小程序基础库版 >= 2.10.0');
			},

			/**
			 * 获取签名和推流地址
			 * @param {Object} rtcConfig 进房参数配置
			 * @returns {Promise}
			 */
			requestSigServerFun(rtcConfig) {
				console.log(TAG_NAME, '_requestSigServer:', rtcConfig);
				const sdkAppID = rtcConfig.sdkAppID;
				const userID = rtcConfig.userID;
				const userSig = rtcConfig.userSig;
				const roomID = rtcConfig.roomID;
				const privateMapKey = rtcConfig.privateMapKey;
				rtcConfig.useCloud = rtcConfig.useCloud === undefined ? true : rtcConfig.useCloud;
				let url = rtcConfig.useCloud ? 'https://official.opensso.tencent-cloud.com/v4/openim/jsonvideoapp' :
					'https://yun.tim.qq.com/v4/openim/jsonvideoapp';
				url += '?sdkappid=' + sdkAppID + '&identifier=' + userID + '&usersig=' + userSig + '&random=' + Date
				.now() + '&contenttype=json';
				const reqHead = {
					'Cmd': 1,
					'SeqNo': 1,
					'BusType': 7,
					'GroupId': roomID
				};
				const reqBody = {
					'PrivMapEncrypt': privateMapKey,
					'TerminalType': 1,
					'FromType': 3,
					'SdkVersion': 26280566
				};
				console.log(TAG_NAME, '_requestSigServer:', url, reqHead, reqBody);
				return new Promise((resolve, reject) => {
					uni.request({
						url: url,
						data: {
							'ReqHead': reqHead,
							'ReqBody': reqBody
						},
						method: 'POST',
						success: res => {
							console.log('_requestSigServer success:', res);
							if (res.data['ErrorCode'] || res.data['RspHead']['ErrorCode'] !== 0) {
								// console.error(res.data['ErrorInfo'] || res.data['RspHead']['ErrorInfo'])
								console.error('获取roomsig失败');
								reject(res);
							}
							const roomSig = JSON.stringify(res.data['RspBody']);
							let pushUrl = 'room://cloud.tencent.com?sdkappid=' + sdkAppID +
								'&roomid=' + roomID + '&userid=' + userID + '&roomsig=' +
								encodeURIComponent(roomSig);
							// TODO 需要重新整理的逻辑 TRTC尚未支持 20200213
							// 如果有配置纯音频推流或者recordId参数
							if (rtcConfig.pureAudioPushMod || rtcConfig.recordId) {
								const bizbuf = {
									Str_uc_params: {
										pure_audio_push_mod: 0,
										record_id: 0
									}
								};
								// 纯音频推流
								if (rtcConfig.pureAudioPushMod) {
									bizbuf.Str_uc_params.pure_audio_push_mod = rtcConfig
										.pureAudioPushMod;
								} else {
									delete bizbuf.Str_uc_params.pure_audio_push_mod;
								}
								// 自动录制时业务自定义id
								if (rtcConfig.recordId) {
									bizbuf.Str_uc_params.record_id = rtcConfig.recordId;
								} else {
									delete bizbuf.Str_uc_params.record_id;
								}
								pushUrl += '&bizbuf=' + encodeURIComponent(JSON.stringify(bizbuf));
							}
							console.log('roomSigInfo', pushUrl);
							resolve(pushUrl);
						},
						fail: res => {
							console.log(TAG_NAME, 'requestSigServer fail:', res);
							reject(res);
						}
					});
				});
			},

			doubleTabToggleFullscreenFun(event) {
				const curTime = event.timeStamp;
				const lastTime = this._lastTapTime;
				const lastTapCoordinate = this._lastTapCoordinate;
				const currentTapCoordinate = event.detail;
				// 计算两次点击的距离
				const distence = Math.sqrt(Math.pow(Math.abs(currentTapCoordinate.x - lastTapCoordinate.x), 2) + Math.pow(
					Math.abs(currentTapCoordinate.y - lastTapCoordinate.y), 2));
				this._lastTapCoordinate = currentTapCoordinate;
				// 已知问题：上次全屏操作后，必须等待1.5s后才能再次进行全屏操作，否则引发SDK全屏异常，因此增加节流逻辑
				const beforeLastTime = this._beforeLastTapTime;
				console.log(TAG_NAME, '_doubleTabToggleFullscreen', event, lastTime, beforeLastTime, distence);
				if (curTime - lastTime > 0 && curTime - lastTime < 300 && lastTime - beforeLastTime > 1500 && distence <
					20) {
					const userID = event.currentTarget.dataset.userid;
					const streamType = event.currentTarget.dataset.streamtype;
					if (this._isFullscreen) {
						this.exitFullscreen({
							userID,
							streamType
						}).then(() => {
							this._isFullscreen = false;
						}).catch(() => {});
					} else {
						// const stream = this.userController.getStream({ userID, streamType })
						let direction;
						// // 已知问题：视频的尺寸需要等待player触发NetStatus事件才能获取到，如果进房就双击全屏，全屏后的方向有可能不对。
						// if (stream && stream.videoWidth && stream.videoHeight) {
						//   // 如果是横视频，全屏时进行横屏处理。如果是竖视频，则为0
						//   direction = stream.videoWidth > stream.videoHeight ? 90 : 0
						// }
						this.enterFullscreen({
							userID,
							streamType,
							direction
						}).then(() => {
							this._isFullscreen = true;
						}).catch(() => {});
					}
					this._beforeLastTapTime = lastTime;
				}
				this._lastTapTime = curTime;
			},

			/**
			 * TRTC-room 远端用户和音视频状态处理
			 */
			bindEventFun() {
				// 远端用户进房
				this.userController.on(EVENT.REMOTE_USER_JOIN, event => {
					console.log(TAG_NAME, '远端用户进房', event, event.data.userID);
					this.setData({
						userList: event.data.userList
					}, () => {
						this._emitter.emit(EVENT.REMOTE_USER_JOIN, {
							userID: event.data.userID
						});
					});
					console.log(TAG_NAME, 'REMOTE_USER_JOIN', 'streamList:', this.streamList, 'userList:', this
						.userList);
				});
				// 远端用户离开
				this.userController.on(EVENT.REMOTE_USER_LEAVE, event => {
					let {
						toUserID
					} = this.config;
					console.log("trtc-room1453", '远端用户离开', event, event.data.userID);
					if (event.data.userID) {
						this.setListFun({
							userList: event.data.userList,
							streamList: event.data.streamList
						}).then(() => {
							this._emitter.emit(EVENT.REMOTE_USER_LEAVE, {
								userID: event.data.userID
							});
						});
						console.log("config1461", this.config);
						if (toUserID === event.data.userID) {
							console.log("医生退出了。");
							uni.showToast({
								title: "医生已退出房间，视频结束。",
								duration: 500,
								icon: 'none',
								success: () => {
									setTimeout(() => {
										this.hangUpFun();
									}, 500);
								}
							});
						}
					}
					console.log(TAG_NAME, 'REMOTE_USER_LEAVE1476streamList', 'streamList:', this.streamList,
						'userList:', this.userList);
				});
				// 视频状态 true
				this.userController.on(EVENT.REMOTE_VIDEO_ADD, event => {
					console.log(TAG_NAME, '远端视频可用', event, event.data.stream.userID);
					const stream = event.data.stream;
					this.setListFun({
						userList: event.data.userList,
						streamList: event.data.streamList
					}).then(() => {
						// 完善 的stream 的 playerContext
						stream.playerContext = uni.createLivePlayerContext(stream.streamID, this);
						// 新增的需要触发一次play 默认属性才能生效
						// stream.playerContext.play()
						// console.log(TAG_NAME, 'REMOTE_VIDEO_ADD playerContext.play()', stream)
						this._emitter.emit(EVENT.REMOTE_VIDEO_ADD, {
							userID: stream.userID,
							streamType: stream.streamType
						});
					});
					console.log(TAG_NAME, 'REMOTE_VIDEO_ADD', 'streamList:', this.streamList, 'userList:', this
						.userList);
				});
				// 视频状态 false
				this.userController.on(EVENT.REMOTE_VIDEO_REMOVE, event => {
					console.log(TAG_NAME, '远端视频移除', event, event.data.stream.userID);
					const stream = event.data.stream;
					this.setListFun({
						userList: event.data.userList,
						streamList: event.data.streamList
					}).then(() => {
						// 有可能先触发了退房事件，用户名下的所有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.streamList, 'userList:', this
						.userList);
				});
				// 音频可用
				this.userController.on(EVENT.REMOTE_AUDIO_ADD, event => {
					console.log(TAG_NAME, '远端音频可用', event);
					const stream = event.data.stream;
					this.setListFun({
						userList: event.data.userList,
						streamList: event.data.streamList
					}).then(() => {
						stream.playerContext = uni.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.streamList, 'userList:', this
						.userList);
				});
				// 音频不可用
				this.userController.on(EVENT.REMOTE_AUDIO_REMOVE, event => {
					console.log(TAG_NAME, '远端音频移除', event, event.data.stream.userID);
					const stream = event.data.stream;
					this.setListFun({
						userList: event.data.userList,
						streamList: event.data.streamList
					}).then(() => {
						// 有可能先触发了退房事件，用户名下的所有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.streamList, 'userList:', this
						.userList);
				});
			},

			/**
			 * pusher event handler
			 * @param {*} event 事件实例
			 */
			pusherStateChangeHandlerFun(event) {
				const code = event.detail.code;
				const message = event.detail.message;
				console.log(TAG_NAME, 'pusherStateChange：', code, event);
				switch (code) {
					case 0:
						console.log(message, code);
						break;
					case 1001:
						console.log('已经连接推流服务器', code);
						break;
					case 1002:
						console.log('已经与服务器握手完毕,开始推流', code);
						break;
					case 1003:
						console.log('打开摄像头成功', code);
						break;
					case 1004:
						console.log('录屏启动成功', code);
						break;
					case 1005:
						console.log('推流动态调整分辨率', code);
						break;
					case 1006:
						console.log('推流动态调整码率', code);
						break;
					case 1007:
						console.log('首帧画面采集完成', code);
						break;
					case 1008:
						console.log('编码器启动', code);
						break;
					case 1018:
						console.log('进房成功', code);
						this._emitter.emit(EVENT.LOCAL_JOIN, {
							userID: this.pusher.userID
						});
						break;
					case 1019:
						console.log('退出房间', code);
						this._emitter.emit(EVENT.LOCAL_LEAVE, {
							userID: this.pusher.userID
						});
						break;
					case 2003:
						console.log('渲染首帧视频', code);
						break;
					case 1022:
						uni.showModal({
							title: '温馨提示',
							content: "网络异常，如果进入页面一直黑屏，请退出黑屏，并切换网络为手机流量或其他wifi网络，等待医生重新呼叫您！",
							cancelColor: '#0079fe',
							confirmText: '关闭',
							showCancel: false
						});
						console.log('失败', message, 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('打开摄像头失败: ', code);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -1302:
						console.error('打开麦克风失败: ', code);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -1303:
						console.error('视频编码失败: ', code);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -1304:
						console.error('音频编码失败: ', code);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -1307:
						console.error('推流连接断开: ', code);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -100018:
						console.error('进房失败: ', code, message);
						this._emitter.emit(EVENT.ERROR, {
							code,
							message
						});
						break;
					case -3301:
						console.error('失败: ', code, message);
						break;
					case 5000:
						console.log('小程序被挂起: ', code);
						// 终端 sdk 建议执行退房操作，唤起时重新进房，临时解决方案，待小程序SDK完全实现自动重新推流后可以去掉
						this.status.isPending = true;
						// if (this.status.isPush) {
						//   // this.exitRoom()
						//   const tempUrl = this.data.pusher.url
						//   this.data.pusher.url = ''
						//   // console.log('5000 小程序被挂起后更换pusher', this.data.pusher.getPusherContext().webviewId)
						//   this.setData({
						//     pusher: this.data.pusher,
						//   }, () => {
						//     this.data.pusher.url = tempUrl
						//     this.setData({
						//       pusher: this.data.pusher,
						//     }, () => {
						//       this.data.pusher.getPusherContext().start()
						//       console.log('5000 小程序被挂起后更换pusher', this.data.pusher)
						//     })
						//   })
						// }
						break;
					case 1021:
						console.log('网络类型发生变化，需要重新进房', code);
						break;
					case 2007:
						console.log('本地视频播放loading: ', code);
						break;
					case 2004:
						console.log('本地视频播放开始: ', code);
						break;
					case 3001:
						console.log('RTMP -DNS解析失败:', code);
						break;
					case 3002:
						console.log('RTMP服务器连接失败:', code);
						break;
					case 3003:
						console.log('RTMP服务器握手失败:', code);
						break;
					case 3004:
						console.log('RTMP服务器主动断开，请检查推流地址的合法性或防盗链有效期:', code);
						break;
					default:
						console.log(message, code);
				}
				this._emitter.emit(EVENT.LOCAL_STATE_UPDATE, event);
			},

			pusherNetStatusHandlerFun(event) {
				// 触发 LOCAL_NET_STATE_UPDATE
				this._emitter.emit(EVENT.LOCAL_NET_STATE_UPDATE, event);
			},

			pusherErrorHandlerFun(event) {
				// 触发 ERROR
				console.warn(TAG_NAME, 'pusher error', event);
				try {
					const code = event.detail.errCode;
					const message = event.detail.errMsg;
					this._emitter.emit(EVENT.ERROR, {
						code,
						message
					});
					console.log(code, message);
				} catch (exception) {
					console.log("CatchClause", exception);
					console.log("CatchClause", exception);
					console.error(TAG_NAME, 'pusher error data parser exception', event, exception);
				}
			},

			pusherBGMStartHandlerFun(event) {
				// 触发 BGM_START 已经在playBGM方法中进行处理
				// this._emitter.emit(EVENT.BGM_PLAY_START, { data: event })
			},

			pusherBGMProgressHandlerFun(event) {
				// BGM_PROGRESS
				this._emitter.emit(EVENT.BGM_PLAY_PROGRESS, event);
			},

			pusherBGMCompleteHandlerFun(event) {
				// BGM_COMPLETE
				this._emitter.emit(EVENT.BGM_PLAY_COMPLETE, event);
			},

			// player event handler
			// 获取 player ID 再进行触发
			playerStateChangeFun(event) {
				// console.log(TAG_NAME, '_playerStateChange', event)
				this._emitter.emit(EVENT.REMOTE_STATE_UPDATE, event);
			},

			playerFullscreenChangeFun(event) {
				// console.log(TAG_NAME, '_playerFullscreenChange', event)
				this._emitter.emit(EVENT.REMOTE_FULLSCREEN_UPDATE, event);
				this._emitter.emit(EVENT.VIDEO_FULLSCREEN_UPDATE, event);
			},

			playerNetStatusFun(event) {
				// console.log(TAG_NAME, '_playerNetStatus', event)
				// 获取player 视频的宽高
				const stream = this.userController.getStream({
					userID: event.currentTarget.dataset.userid,
					streamType: event.currentTarget.dataset.streamtype
				});
				if (stream && (stream.videoWidth !== event.detail.info.videoWidth || stream.videoHeight !== event.detail
						.info.videoHeight)) {
					console.log(TAG_NAME, '_playerNetStatus update video size', event);
					stream.videoWidth = event.detail.info.videoWidth;
					stream.videoHeight = event.detail.info.videoHeight;
				}
				this._emitter.emit(EVENT.REMOTE_NET_STATE_UPDATE, event);
			},

			playerAudioVolumeNotifyFun(event) {
				// console.log(TAG_NAME, '_playerAudioVolumeNotify', event)
				this._emitter.emit(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, event);
			},

			filterVisibleStreamFun(streamList, skipPagination) {
				const list = streamList.filter(item => {
					// 全部显示
					// return true
					// 只显示有视频或者有音频的 stream
					return item.hasVideo || item.hasAudio;
				});
				// 按 userID 进行排序
				list.sort((item1, item2) => {
					const id1 = item1.userID.toUpperCase();
					const id2 = item2.userID.toUpperCase();
					if (id1 < id2) {
						return -1;
					}
					if (id1 > id2) {
						return 1;
					}
					return 0;
				});
				if (this.template === 'grid' && !skipPagination) {
					this.filterGridPageVisibleStreamFun(list);
					console.log(TAG_NAME, '_filterVisibleStream gridPagePlaceholderStreamList:', this
						.gridPagePlaceholderStreamList);
					if (
						// list.length > this.data.gridPlayerPerPage - 2 &&
						this.gridCurrentPage > 1 && this.gridPagePlaceholderStreamList.length === this.gridPlayerPerPage) {
						// 如果stream 数量大于每页可显示数量，当前页面已经没有可显示的stream(占位数量==3) 回到上一个页面。
						this.gridPageToPrevFun(list);
					}
				}
				console.log(TAG_NAME, '_filterVisibleStream list:', list);
				return list;
			},

			filterGridPageVisibleStreamFun(list) {
				// 最多只显示 gridPlayerPerPage 个stream
				const length = list.length;
				// +1 pusher
				this.gridPageCount = Math.ceil((length + 1) / this.gridPlayerPerPage);
				this.gridPagePlaceholderStreamList = [];
				let visibleCount = 0;
				// 需要显示的player区间
				let interval;
				if (this.gridPlayerPerPage > 3) {
					if (this.gridCurrentPage === 1) {
						interval = [-1, this.gridPlayerPerPage - 1];
					} else {
						// 每页显示4个时，第一页显示3个，pusher只在第一页
						// -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
						//    1     2       3        4
						// -1 3
						// 2 7
						// 6 11
						interval = [this.gridCurrentPage * this.gridPlayerPerPage - (this.gridPlayerPerPage + 2), this
							.gridCurrentPage * this.gridPlayerPerPage - 1
						];
					}
				} else {
					// 每页显示3个，每页都有pusher
					interval = [this.gridCurrentPage * this.gridPlayerPerPage - (this.gridPlayerPerPage + 1), this
						.gridCurrentPage * this.gridPlayerPerPage
					];
				}
				for (let i = 0; i < length; i++) {
					if (i > interval[0] && i < interval[1]) {
						list[i].isVisible = true;
						list[i].muteVideo = list[i].muteVideoPrev || false;
						visibleCount++;
					} else {
						list[i].isVisible = false;
						list[i].muteVideo = true;
					}
				}
				for (let i = 0; i < this.gridPlayerPerPage - visibleCount; i++) {
					this.gridPagePlaceholderStreamList.push({
						id: 'holder-' + i
					});
				}
				return list;
			},

			/**
			 * 保持屏幕常亮
			 */
			keepScreenOnFun() {
				setInterval(() => {
					uni.setKeepScreenOn({
						keepScreenOn: true
					});
				}, 20000);
			},

			//  ______  __       __        ______             __                                              __
			//  |      \|  \     /  \      |      \           |  \                                            |  \
			//   \$$$$$$| $$\   /  $$       \$$$$$$ _______  _| $$_     ______    ______   _______    ______  | $$
			//    | $$  | $$$\ /  $$$        | $$  |       \|   $$ \   /      \  /      \ |       \  |      \ | $$
			//    | $$  | $$$$\  $$$$        | $$  | $$$$$$$\\$$$$$$  |  $$$$$$\|  $$$$$$\| $$$$$$$\  \$$$$$$\| $$
			//    | $$  | $$\$$ $$ $$        | $$  | $$  | $$ | $$ __ | $$    $$| $$   \$$| $$  | $$ /      $$| $$
			//   _| $$_ | $$ \$$$| $$       _| $$_ | $$  | $$ | $$|  \| $$$$$$$$| $$      | $$  | $$|  $$$$$$$| $$
			//  |   $$ \| $$  \$ | $$      |   $$ \| $$  | $$  \$$  $$ \$$     \| $$      | $$  | $$ \$$    $$| $$
			//   \$$$$$$ \$$      \$$       \$$$$$$ \$$   \$$   \$$$$   \$$$$$$$ \$$       \$$   \$$  \$$$$$$$ \$$

			/**
			 * 初始化 IM SDK
			 * @param {Object} config sdkAppID
			 */
			initIMFun(config) {
				if (!config.enableIM || !config.sdkAppID || this.tim) {
					return;
				}
				console.log(TAG_NAME, '_initIM', config);
				// 初始化 sdk 实例
				const tim = TIM.create({
					SDKAppID: config.sdkAppID
				});
				// 0 普通级别，日志量较多，接入时建议使用
				// 1 release级别，SDK 输出关键信息，生产环境时建议使用
				// 2 告警级别，SDK 只输出告警和错误级别的日志
				// 3 错误级别，SDK 只输出错误级别的日志
				// 4 无日志级别，SDK 将不打印任何日志
				if (config.debugMode) {
					tim.setLogLevel(1);
				} else {
					tim.setLogLevel(4);
				}
				// 取消监听
				tim.off(TIM.EVENT.SDK_READY, this._onIMReady);
				tim.off(TIM.EVENT.MESSAGE_RECEIVED, this._onIMMessageReceived);
				tim.off(TIM.EVENT.SDK_NOT_READY, this._onIMNotReady);
				tim.off(TIM.EVENT.KICKED_OUT, this._onIMKickedOut);
				tim.off(TIM.EVENT.ERROR, this._onIMError);
				// 监听事件
				tim.on(TIM.EVENT.SDK_READY, this._onIMReady, this);
				tim.on(TIM.EVENT.MESSAGE_RECEIVED, this._onIMMessageReceived, this);
				tim.on(TIM.EVENT.SDK_NOT_READY, this._onIMNotReady, this);
				tim.on(TIM.EVENT.KICKED_OUT, this._onIMKickedOut, this);
				tim.on(TIM.EVENT.ERROR, this._onIMError, this);
				this.tim = tim;
				uni.tim = tim;
			},

			loginIMFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_loginIM', params);
				return this.tim.login({
					userID: params.userID,
					userSig: params.userSig
				});
			},

			logoutIMFun() {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_logoutIM');
				return this.tim.logout();
			},

			exitIMFun() {
				// 方法需要调用限制，否则重复解散群 退群会有warn
				if (this.exitIMThrottle || !this.tim) {
					return;
				}
				this.exitIMThrottle = true;
				const userList = this.getRemoteUserList();
				const roomID = this.config.roomID;
				const userID = this.config.userID;
				this.searchGroupFun({
					roomID
				}).then(imResponse => {
					// 查询群资料，判断是否为群主
					if (imResponse.data.group.ownerID === userID && userList.length === 0) {
						// 如果 userList 为 0 群主可以解散群，并登出IM
						this.dismissGroupFun({
							roomID
						}).then(() => {
							this.exitIMThrottle = false;
							this.logoutIMFun();
						}).catch(imError => {
							this.exitIMThrottle = false;
							this.logoutIMFun();
						});
					} else if (imResponse.data.group.ownerID === userID) {
						this.exitIMThrottle = false;
						// 群主不能退群只能登出
						this.logoutIMFun();
					} else {
						// 普通成员退群并登出IM
						this.quitGroupFun({
							roomID
						}).then(() => {
							this.exitIMThrottle = false;
							this.logoutIMFun();
						}).catch(imError => {
							this.exitIMThrottle = false;
							this.logoutIMFun();
						});
					}
				}).catch(imError => {
					this.exitIMThrottle = false;
					// 查询异常直接登出
					this.logoutIMFun();
				});
			},

			searchGroupFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_searchGroup', params);
				const tim = this.tim;
				const promise = tim.searchGroupByID(params.roomID + '');
				promise.then(function(imResponse) {
					// const group = imResponse.data.group // 群组信息
					console.log(TAG_NAME, '_searchGroup success', imResponse);
				}).catch(function(imError) {
					console.warn(TAG_NAME, '_searchGroup fail，TIM 报错信息不影响后续逻辑，可以忽略', imError); // 搜素群组失败的相关信息
				});

				return promise;
			},

			/**
			 * 创建 AVchatroom
			 * @param {*} params roomID
			 * @returns {Promise}
			 */
			createGroupFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_createGroup', params);
				const promise = this.tim.createGroup({
					groupID: params.roomID + '',
					name: params.roomID + '',
					type: IM_GROUP_TYPE
				});
				promise.then(imResponse => {
					// 创建成功
					console.log(TAG_NAME, '_createGroup success', imResponse.data.group); // 创建的群的资料
				}).catch(imError => {
					console.warn(TAG_NAME, '_createGroup error', imError); // 创建群组失败的相关信息
				});

				return promise;
			},

			/**
			 * 进入 AVchatroom
			 * @param {*} params roomID
			 * @returns {Promise}
			 */
			joinGroupFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_joinGroup', params);
				const promise = this.tim.joinGroup({
					groupID: params.roomID + '',
					type: IM_GROUP_TYPE
				});
				promise.then(imResponse => {
					switch (imResponse.data.status) {
						case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL:
							// 等待管理员同意
							break;
						case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
						case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP:
							// 已经在群中
							// console.log(imResponse.data.group) // 加入的群组资料
							// uni.showToast({
							//   title: '进群成功',
							// })
							console.log(TAG_NAME, '_joinGroup success', imResponse);
							break;
						default:
							break;
					}
				}).catch(imError => {
					console.warn(TAG_NAME, 'joinGroup error', imError); // 申请加群失败的相关信息
				});

				return promise;
			},

			quitGroupFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_quitGroup', params);
				const promise = this.tim.quitGroup(params.roomID + '');
				promise.then(imResponse => {
					console.log(TAG_NAME, '_quitGroup success', imResponse);
				}).catch(imError => {
					console.warn(TAG_NAME, 'quitGroup error', imError);
				});
				return promise;
			},

			dismissGroupFun(params) {
				if (!this.tim) {
					return;
				}
				console.log(TAG_NAME, '_dismissGroup', params);
				const promise = this.tim.dismissGroup(params.roomID + '');
				promise.then(imResponse => {
					console.log(TAG_NAME, '_dismissGroup success', imResponse);
				}).catch(imError => {
					console.warn(TAG_NAME, '_dismissGroup error', imError);
				});
				return promise;
			},

			onIMReadyFun(event) {
				console.log(TAG_NAME, 'IM.SDK_READY', event);
				this._emitter.emit(EVENT.IM_SDK_READY, event);
				const roomID = this.config.roomID;
				// 查询群组是否存在
				this.searchGroupFun({
					roomID
				}).then(res => {
					// console.log(TAG_NAME, 'searchGroup', res)
					// 存在直接进群
					this.joinGroupFun({
						roomID
					});
				}).catch(() => {
					// 不存在则创建，如果是avchatroom 创建后进群
					this.createGroupFun({
						roomID
					}).then(res => {
						// 进群
						this.joinGroupFun({
							roomID
						});
					}).catch(imError => {
						if (imError.code === 10021) {
							console.log(TAG_NAME, '群已存在，直接进群', event);
							this.joinGroupFun({
								roomID
							});
						}
					});
				});
				// 收到离线消息和会话列表同步完毕通知，接入侧可以调用 sendMessage 等需要鉴权的接口
				// event.name - TIM.EVENT.SDK_READY
			},

			onIMMessageReceivedFun(event) {
				// 收到推送的单聊、群聊、群提示、群系统通知的新消息，可通过遍历 event.data 获取消息列表数据并渲染到页面
				console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', event);
				// messageList 仅保留10条消息
				const messageData = event.data;
				const roomID = this.config.roomID + '';
				const userID = this.config.userID + '';
				for (let i = 0; i < messageData.length; i++) {
					const message = messageData[i];
					// console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', message, this.data.config, TIM.TYPES.MSG_TEXT)
					if (message.to === roomID + '' || message.to === userID) {
						// 遍历messageData 获取当前room 或者当前user的消息
						console.log(TAG_NAME, 'IM.MESSAGE_RECEIVED', message, message.type, TIM.TYPES.MSG_TEXT);
						if (message.type === TIM.TYPES.MSG_TEXT) {
							this.pushMessageListFun({
								name: message.from,
								message: message.payload.text
							});
						} else {
							if (message.type === TIM.TYPES.MSG_GRP_SYS_NOTICE && message.payload.operationType === 2) {
								// 群系统通知
								this.pushMessageListFun({
									name: '系统通知',
									message: `欢迎 ${userID}`
								});
							}
							// 其他消息暂不处理
						}
					}
				}

				this._emitter.emit(EVENT.IM_MESSAGE_RECEIVED, event);
			},

			onIMNotReadyFun(event) {
				console.log(TAG_NAME, 'IM.SDK_NOT_READY', event);
				this._emitter.emit(EVENT.IM_SDK_NOT_READY, event);
				// 收到 SDK 进入 not ready 状态通知，此时 SDK 无法正常工作
				// event.name - TIM.EVENT.SDK_NOT_READY
			},

			onIMKickedOutFun(event) {
				console.log(TAG_NAME, 'IM.KICKED_OUT', event);
				this._emitter.emit(EVENT.IM_KICKED_OUT, event);
				// 收到被踢下线通知
				// event.name - TIM.EVENT.KICKED_OUT
				// event.data.type - 被踢下线的原因，例如 :
				//    - TIM.TYPES.KICKED_OUT_MULT_ACCOUNT 多实例登录被踢
				//    - TIM.TYPES.KICKED_OUT_MULT_DEVICE 多终端登录被踢
				//    - TIM.TYPES.KICKED_OUT_USERSIG_EXPIRED 签名过期被踢。使用前需要将SDK版本升级至v2.4.0或以上。
			},

			onIMErrorFun(event) {
				console.log(TAG_NAME, 'IM.ERROR', event);
				this._emitter.emit(EVENT.IM_ERROR, event);
				// 收到 SDK 发生错误通知，可以获取错误码和错误信息
				// event.name - TIM.EVENT.ERROR
				// event.data.code - 错误码
				// event.data.message - 错误信息
			},

			//  ________                                  __             __
			//  |        \                                |  \           |  \
			//   \$$$$$$$$______   ______ ____    ______  | $$  ______  _| $$_     ______
			//     | $$  /      \ |      \    \  /      \ | $$ |      \|   $$ \   /      \
			//     | $$ |  $$$$$$\| $$$$$$\$$$$\|  $$$$$$\| $$  \$$$$$$\\$$$$$$  |  $$$$$$\
			//     | $$ | $$    $$| $$ | $$ | $$| $$  | $$| $$ /      $$ | $$ __ | $$    $$
			//     | $$ | $$$$$$$$| $$ | $$ | $$| $$__/ $$| $$|  $$$$$$$ | $$|  \| $$$$$$$$
			//     | $$  \$$     \| $$ | $$ | $$| $$    $$| $$ \$$    $$  \$$  $$ \$$     \
			//      \$$   \$$$$$$$ \$$  \$$  \$$| $$$$$$$  \$$  \$$$$$$$   \$$$$   \$$$$$$$
			//                                  | $$
			//                                  | $$
			//                                   \$$
			// 以下为 debug & template 相关函数

			toggleVideoFun() {
				if (this.pusher.enableCamera) {
					this.unpublishLocalVideo();
				} else {
					this.publishLocalVideo();
				}
			},

			toggleAudioFun() {
				if (this.pusher.enableMic) {
					this.unpublishLocalAudio();
				} else {
					this.publishLocalAudio();
				}
			},

			debugToggleRemoteVideoFun(event) {
				console.log(TAG_NAME, '_debugToggleRemoteVideo', event.currentTarget.dataset);
				const userID = event.currentTarget.dataset.userID;
				const streamType = event.currentTarget.dataset.streamType;
				const stream = this.streamList.find(item => {
					return item.userID === userID && item.streamType === streamType;
				});
				if (stream.muteVideo) {
					this.subscribeRemoteVideo({
						userID,
						streamType
					});
					// this.setViewVisible({ userID, streamType, isVisible: true })
				} else {
					this.unsubscribeRemoteVideo({
						userID,
						streamType
					});
					// this.setViewVisible({ userID, streamType, isVisible: false })
				}
			},

			debugToggleRemoteAudioFun(event) {
				console.log(TAG_NAME, '_debugToggleRemoteAudio', event.currentTarget.dataset);
				const userID = event.currentTarget.dataset.userID;
				const streamType = event.currentTarget.dataset.streamType;
				const stream = this.streamList.find(item => {
					return item.userID === userID && item.streamType === streamType;
				});
				if (stream.muteAudio) {
					this.subscribeRemoteAudio({
						userID
					});
				} else {
					this.unsubscribeRemoteAudio({
						userID
					});
				}
			},

			debugToggleVideoDebugFun() {
				this.setData({
					debug: !this.debug
				});
			},

			debugExitRoomFun() {
				this.exitRoom();
			},

			debugEnterRoomFun() {
				this.enterRoom({
					roomID: this.config.roomID
				}).then(() => {
					setTimeout(() => {
						this.publishLocalVideo();
						this.publishLocalAudio();
					}, 2000);
					// 进房后开始推送视频或音频
				});
			},

			debugGoBackFun() {
				uni.navigateBack({
					delta: 1
				});
			},

			debugTogglePanelFun() {
				this.setData({
					debugPanel: !this.debugPanel
				});
			},

			debugSendRandomMessageFun() {
				const userList = this.getRemoteUserList();
				if (userList.length === 0 || !this.tim) {
					return false;
				}
				const roomID = this.config.roomID;
				const message = `Hello! ${userList[0].userID} ${9999 * Math.random()}`;
				const userID = userList[0].userID;
				this.sendC2CTextMessage({
					userID: userID,
					message: message
				});
				const promise = this.sendGroupTextMessage({
					roomID: roomID,
					message: message
				});
				// 消息上屏
				this.pushMessageListFun({
					name: userID,
					message: message
				});
				promise.then(function(imResponse) {
					// 发送成功
					console.log(TAG_NAME, '_debugSendRandomMessage success', imResponse);
					uni.showToast({
						title: '发送成功',
						icon: 'success',
						duration: 1000
					});
				}).catch(function(imError) {
					// 发送失败
					console.warn(TAG_NAME, '_debugSendRandomMessage error', imError);
					uni.showToast({
						title: '发送失败',
						icon: 'none',
						duration: 1000
					});
				});
			},

			toggleAudioVolumeTypeFun() {
				if (this.pusher.audioVolumeType === 'voicecall') {
					this.setPusherConfigFun({
						audioVolumeType: 'media'
					});
				} else {
					this.setPusherConfigFun({
						audioVolumeType: 'voicecall'
					});
				}
			},

			toggleSoundModeFun() {
				if (this.userList.length === 0) {
					return;
				}
				const stream = this.userController.getStream({
					userID: this.userList[0].userID,
					streamType: 'main'
				});
				if (stream) {
					if (stream.soundMode === 'speaker') {
						stream['soundMode'] = 'ear';
					} else {
						stream['soundMode'] = 'speaker';
					}
					this.setPlayerConfigFun({
						userID: stream.userID,
						streamType: 'main',
						config: {
							soundMode: stream['soundMode']
						}
					});
				}
			},

			/**
			 * 退出通话
			 */
			hangUpFun() {
				this.exitRoom();
				uni.navigateBack({
					delta: 1
				});
			},

			/**
			 * 切换订阅音频状态
			 */
			handleSubscribeAudio() {
				if (this.pusher.enableMic) {
					this.unpublishLocalAudio();
				} else {
					this.publishLocalAudio();
				}
			},

			/**
			 * 切换订阅远端视频状态
			 * @param {Object} event native 事件对象
			 */
			handleSubscribeRemoteVideoFun(event) {
				const userID = event.currentTarget.dataset.userID;
				const streamType = event.currentTarget.dataset.streamType;
				const stream = this.streamList.find(item => {
					return item.userID === userID && item.streamType === streamType;
				});
				if (stream.muteVideo) {
					this.subscribeRemoteVideo({
						userID,
						streamType
					});
				} else {
					this.unsubscribeRemoteVideo({
						userID,
						streamType
					});
				}
			},

			/**
			 *
			 * @param {Object} event native 事件对象
			 */
			handleSubscribeRemoteAudioFun(event) {
				const userID = event.currentTarget.dataset.userID;
				const streamType = event.currentTarget.dataset.streamType;
				const stream = this.streamList.find(item => {
					return item.userID === userID && item.streamType === streamType;
				});
				if (stream.muteAudio) {
					this.subscribeRemoteAudio({
						userID
					});
				} else {
					this.unsubscribeRemoteAudio({
						userID
					});
				}
			},

			/**
			 * grid布局, 唤起 memberlist-panel
			 */
			switchMemberListPanelFun() {
				this.setData({
					panelName: this.panelName !== 'memberlist-panel' ? 'memberlist-panel' : ''
				});
			},

			/**
			 * grid布局, 唤起 setting-panel
			 */
			switchSettingPanelFun() {
				this.setData({
					panelName: this.panelName !== 'setting-panel' ? 'setting-panel' : ''
				});
			},

			handleMaskerClickFun() {
				this.setData({
					panelName: ''
				});
			},

			setPuserPropertyFun(event) {
				console.log(TAG_NAME, '_setPuserProperty', event);
				const key = event.currentTarget.dataset.key;
				const valueType = event.currentTarget.dataset.valueType;
				let value = event.currentTarget.dataset.value;
				const config = {};
				if (valueType === 'boolean') {
					value = value === 'true' ? true : false;
					config[key] = !this.pusher[key];
				}
				if (valueType === 'number' && value.indexOf('|') > 0) {
					value = value.split('|');
					// console.log(this.data.pusher, this.data.pusher[key], key, value)
					if (this.pusher[key] === Number(value[0])) {
						config[key] = Number(value[1]);
					} else {
						config[key] = Number(value[0]);
					}
				}
				if (valueType === 'string' && value.indexOf('|') > 0) {
					value = value.split('|');
					if (this.pusher[key] === value[0]) {
						config[key] = value[1];
					} else {
						config[key] = value[0];
					}
				}
				this.setPusherConfigFun(config);
			},

			setPlayerPropertyFun(event) {
				console.log(TAG_NAME, '_setPlayerProperty', event);
				const userID = event.currentTarget.dataset.userid;
				const streamType = event.currentTarget.dataset.streamtype;
				const key = event.currentTarget.dataset.key;
				let value = event.currentTarget.dataset.value;
				const stream = this.userController.getStream({
					userID: userID,
					streamType: streamType
				});
				if (!stream) {
					return;
				}
				const config = {};
				if (value === 'true') {
					value = true;
				} else if (value === 'false') {
					value = false;
				}
				if (typeof value === 'boolean') {
					config[key] = !stream[key];
				} else if (typeof value === 'string' && value.indexOf('|') > 0) {
					value = value.split('|');
					if (stream[key] === value[0]) {
						config[key] = value[1];
					} else {
						config[key] = value[0];
					}
				}
				console.log(TAG_NAME, '_setPlayerProperty', config);
				this.setPlayerConfigFun({
					userID,
					streamType,
					config
				});
			},

			switchStreamTypeFun(event) {
				const userID = event.currentTarget.dataset.userid;
				const streamType = event.currentTarget.dataset.streamtype;
				const stream = this.userController.getStream({
					userID: userID,
					streamType: streamType
				});
				if (stream && stream.streamType === 'main') {
					if (stream._definitionType === 'small') {
						this.subscribeRemoteVideo({
							userID,
							streamType: 'main'
						});
					} else {
						this.subscribeRemoteVideo({
							userID,
							streamType: 'small'
						});
					}
				}
			},

			handleSnapshotClickFun(event) {
				uni.showToast({
					title: '开始截屏',
					icon: 'none',
					duration: 1000
				});
				const userID = event.currentTarget.dataset.userid;
				const streamType = event.currentTarget.dataset.streamtype;
				this.snapshot({
					userID,
					streamType
				});
			},

			/**
			 * grid布局, 绑定事件
			 */
			gridBindEventFun() {
				// 远端音量变更
				this.on(EVENT.REMOTE_AUDIO_VOLUME_UPDATE, event => {
					const data = event.data;
					const userID = data.currentTarget.dataset.userid;
					const streamType = data.currentTarget.dataset.streamtype;
					const volume = data.detail.volume;
					// console.log(TAG_NAME, '远端音量变更', userID, streamType, volume, event)
					const stream = this.userController.getStream({
						userID: userID,
						streamType: streamType === 'aux' ? 'main' :
							streamType // 远端推辅流后，音量回调会从辅流的 player 返回，而不是主流player 返回。需要等 native SDK修复。
					});

					if (stream) {
						stream.volume = volume;
					}
					this.setData({
						streamList: this.streamList,
						visibleStreamList: this.filterVisibleStreamFun(this.streamList, true)
					}, () => {});
				});
			},

			handleGridTouchStartFun(event) {
				touchX = event.changedTouches[0].clientX;
				touchY = event.changedTouches[0].clientY;
			},

			handleGridTouchEndFun(event) {
				const x = event.changedTouches[0].clientX;
				const y = event.changedTouches[0].clientY;
				if (x - touchX > 50 && Math.abs(y - touchY) < 50) {
					// console.log(TAG_NAME, '向右滑 当前页面', this.data.gridCurrentPage, this.data.gridPageCount)
					this.gridPagePrevFun();
				} else if (x - touchX < -50 && Math.abs(y - touchY) < 50) {
					// console.log(TAG_NAME, '向左滑 当前页面', this.data.gridCurrentPage, this.data.gridPageCount)
					this.gridPageNextFun();
				}
			},

			gridPageToPrevFun(streamList) {
				const visibleStreamList = this.filterGridPageVisibleStreamFun(streamList);
				if (this.gridPagePlaceholderStreamList.length === this.gridPlayerPerPage) {
					this.gridCurrentPage--;
					this.gridPageToPrevFun(streamList);
				} else {
					return visibleStreamList;
				}
			},

			gridPageNextFun() {
				this.gridCurrentPage++;
				if (this.gridCurrentPage > this.gridPageCount) {
					this.gridCurrentPage = 1;
				}
				this.gridPageSetDataFun();
			},

			gridPagePrevFun() {
				this.gridCurrentPage--;
				if (this.gridCurrentPage < 1) {
					this.gridCurrentPage = this.gridPageCount;
				}
				this.gridPageSetDataFun();
			},

			gridPageSetDataFun() {
				this.gridShowPageTipsFun();
				const visibleStreamList = this.filterVisibleStreamFun(this.streamList);
				this.setData({
					gridCurrentPage: this.gridCurrentPage,
					gridPageCount: this.gridPageCount,
					visibleStreamList: visibleStreamList,
					streamList: this.streamList,
					gridPagePlaceholderStreamList: this.gridPagePlaceholderStreamList
				}, () => {});
			},

			gridShowPageTipsFun(event) {
				if (this.gridPageCount < 2) {
					return;
				}
				console.log(TAG_NAME, '_gridShowPageTips', this);
				if (this.hasGridPageTipsShow) {
					clearTimeout(this.hasGridPageTipsShow);
				}
				// #ifdef MP-WEIXIN
				this.$scope.animate(".pages-container", [{
					opacity: 1
				}], 100, () => {})
				// #endif
				// #ifndef MP-WEIXIN
				this.animate('.pages-container', [{
					opacity: 1
				}], 100, () => {});
				// #endif
				this.hasGridPageTipsShow = setTimeout(() => {
					// #ifdef MP-WEIXIN
					this.$scope.animate(".pages-container", [{
						opacity: 1
					}, {
						opacity: 0.3
					}], 600, () => {})
					// #endif
					// #ifndef MP-WEIXIN
					this.animate('.pages-container', [{
						opacity: 1
					}, {
						opacity: 0.3
					}], 600, () => {});
					// #endif
				}, 3000);
			},

			toggleFullscreenFun(event) {
				console.log(TAG_NAME, '_toggleFullscreen', event);
				const userID = event.currentTarget.dataset.userID;
				const streamType = event.currentTarget.dataset.streamType;
				if (this._isFullscreen) {
					this.exitFullscreen({
						userID,
						streamType
					}).then(() => {
						this._isFullscreen = false;
					}).catch(() => {});
				} else {
					// const stream = this.userController.getStream({ userID, streamType })
					const direction = 0;
					// 已知问题：视频的尺寸需要等待player触发NetStatus事件才能获取到，如果进房就双击全屏，全屏后的方向有可能不对。
					// if (stream && stream.videoWidth && stream.videoHeight) {
					//   // 如果是横视频，全屏时进行横屏处理。如果是竖视频，则为0
					//   direction = stream.videoWidth > stream.videoHeight ? 90 : 0
					// }
					this.enterFullscreen({
						userID,
						streamType,
						direction
					}).then(() => {
						this._isFullscreen = true;
					}).catch(() => {});
				}
			},

			// 打开消息弹窗
			toggleIMPanelFun() {
				let {
					config
				} = this;
				// if (isKickedOut) {
				if (config && config.isTextChat === 'yes') {
					return;
				}
				this.setData({
					showIMPanel: !this.showIMPanel
				}, () => {
					if (this.showIMPanel && !(this.messageList && this.messageList.length > 0)) {
						this.getChatData("bottom"); // 获取消息
					}
				});
				// } else {
				//   this.loginImgTxt(config.frUserID, 'noSignOut', 'showMessage'); // 重新登陆
				// }
			},

			sendIMMessageFun: Utils.debounce(function(event) {
				if (!this.messageContent) {
					uni.showToast({
						title: '发送内容不能为空',
						icon: 'none'
					});
					return;
				}
				if (!app.globalData.isLogOut) {
					// 判断是否离线
					this.$emit('handleLoginShow'); // 首页弹出消息列
					return;
				}
				// const roomID = this.data.config.roomID;
				const message = this.messageContent;
				// const userID = this.data.config.userID;
				this.handleSend(message);
				// this.sendGroupTextMessage({ roomID, message })
				// 消息上屏
				// this._pushMessageList({
				//   name: userID,
				//   message: message,
				// })
				// this.setData({
				//   messageContent: '',
				// })
			}),

			inputIMMessageFun(event) {
				// console.log(TAG_NAME, '_inputIMMessage', event)
				this.setData({
					messageContent: event.detail.value
				});
			},

			pushMessageListFun(params) {
				if (this.messageList.length === this.maxMessageListLength) {
					this.messageList.shift();
				}
				this.messageList.push(params);
				this.setData({
					messageList: this.messageList,
					messageListScrollTop: this.messageList.length * 100
				}, () => {});
			},

			// 发送图片
			async onChooseImage() {
				uni.chooseImage({
					count: 1,
					sizeType: ['original', 'compressed'],
					sourceType: ['album', 'camera'],
					success: async res => {
						console.log(res);
						// 图片loading
						let len = this.messageList && this.messageList.length ? this.messageList.length :
						0;
						let obj = {
							pictureLoading: true,
							direction: "right",
							msgType: "1",
							isWithdraw: true,
							key: len + 1,
							msgID: "loading" + (len + 1)
						};
						this.setData({
							messageList: [...this.messageList, {
								...obj
							}]
						}, () => {
							let scrollPosition = "message" + this.messageList[this.messageList
								.length - 1].msgID;
							this.setData({
								scrollPosition
							}, () => {
								this.handleUploadFile(res);
							});
						});
					}
				});
			},

			// 图片上传
			handleUploadFile: function(fileData) {
				let {
					config
				} = this;
				let newUserData = uni.getStorageSync('userData');
				let path = fileData.tempFilePaths instanceof Array && fileData.tempFilePaths.length > 0 ? fileData
					.tempFilePaths[0] : "";
				let imgType = path.split('.')[path.split('.').length - 1];
				// console.log(imgType)
				if (imgType === "jpg" || imgType === "png" || imgType === "jpeg" || imgType === "gif") {
					// console.log("触发了")
					uni.uploadFile({
						url: newUserData && newUserData.upLoadUrl ? newUserData.upLoadUrl : httpConfig.ipDeault +'/uploadFile?fileType=consult',
						//仅为示例，非真实的接口地址
						filePath: path,
						name: 'file',
						success: unloadres => {
							if (unloadres.statusCode === 200) {
								let imgName = JSON.parse(unloadres.data).filePath;
								let data = {
									params: [{
										admID: config.admID,
										frUser: config.frUserID,
										toUser: config.toUserID,
										consultText: '',
										picture: imgName
									}]
								};
								$http.post("urlS", this, {
									code: "02090004",
									data: data,
									namespace: "Y",
									success: res => {
										if (+res.errorCode === 0) {
											let imgPath = (newUserData && newUserData.downLoadUrl ? newUserData.downLoadUrl :httpConfig.ipDeault +"/images/consult/") + imgName;
											let imgKey = this.messageList.length;
											let obj = {
												picture: imgPath,
												direction: "right",
												msgType: "1",
												isWithdraw: true,
												key: imgKey,
												msgID: res.result && res.result.msgID ? res
													.result.msgID : ""
											};
											this.setData({
												messageList: this.replaceLoading([...
													this.messageList
												], obj),
												scrollPosition: 'message' + obj.message
											}, () => {
												this.handleSendImg();
											});
											// console.log("发送图片成功");
										} else {
											this.deleteLoading();
											uni.showToast({
												title: '网络异常，请稍后再试。',
												icon: 'none'
											});
										}
									},
									error: () => {
										this.deleteLoading();
										uni.showToast({
											title: '网络异常，请稍后再试。',
											icon: 'none'
										});
									}
								});
							} else {
								this.deleteLoading();
								uni.showToast({
									title: '网络异常，请稍后再试。',
									icon: 'none'
								});
							}
						},
						fail: () => {
							this.deleteLoading();
							uni.showToast({
								title: '网络异常，请稍后再试。',
								icon: 'none'
							});
						}
					});
				} else {
					uni.showToast({
						title: '发送失败：图片类型有误或图片图片太大。',
						icon: 'none'
					});
					setTimeout(() => {
						this.deleteLoading();
						// 发送失败
					}, 1000);
				}
			},

			// 过滤发送失败的图片loading
			deleteLoading() {
				if (this.messageList && this.messageList.length > 1) {
					let obj = {};
					this.setData({
						messageList: this.replaceLoading([...this.messageList], obj)
					}, () => {
						let scrollPosition = "message" + this.messageList[this.messageList.length - 1].msgID;
						this.setData({
							scrollPosition
						});
					});
				}
			},

			// 提交图片
			handleSendImg() {
				let {
					config
				} = this;
				let messageContent = {
					userID: config.toUserID ? config.toUserID.slice(4) : '',
					admID: config.admID || "",
					patID: config.userID ? config.userID : '',
					patName: config.patName || "",
					userName: config.toUserName || "",
					type: 'M',
					text: "",
					image: 'Y'
				};
				let message = app.globalData.tim.createTextMessage({
					to: config.toUserID,
					conversationType: TIM.TYPES.CONV_C2C,
					payload: {
						text: JSON.stringify(messageContent)
					}
				});
				// 2. 发送消息
				let promise = app.globalData.tim.sendMessage(message);
				if (promise) {
					promise.then(imResponse => {
						// 发送成功
						console.log("IM发送成功。");
					}).catch(imError => {
						this.deleteLoading();
						// 发送失败
						console.log("IM发送失败。", imError);
					});
				} else {
					console.log("IM发送失败。");
				}
			},

			// 图片加载成功之后替换加载中占位图
			replaceLoading(arr, obj) {
				let newArr = JSON.parse(JSON.stringify(arr));
				if (obj && obj.key) {
					let msgID = "loading" + obj.key;
					// console.log(newArr, msgID)
					newArr = newArr.map(item => item.msgID === msgID ? obj : item);
					return newArr;
				} else {
					let key = newArr[newArr.length - 1].key;
					return newArr.filter(item => item.key !== key);
				}
			},

			// 查看原图
			viewOriginal(e) {
				let {
					messageList
				} = this;
				let {
					picture
				} = e.currentTarget.dataset.page;
				let urls = [];
				for (var i = 0; i < messageList.length; i++) {
					if (messageList[i].picture && messageList[i].msgType === '1') {
						urls.push(messageList[i].picture);
					}
				}
				console.log(urls);
				uni.previewImage({
					current: picture,
					// 当前显示图片的http链接
					urls // 需要预览的图片http链接列表
				});
			}
		},
		watch: {
			config: {
				handler: function(newVal, oldVal) {
					this.propertyObserverFun({
						'name': 'config',
						newVal,
						oldVal
					});
				},

				immediate: true,
				deep: true
			}
		}
	};
</script>
<style lang="scss">
	@use '../../static/iconfont.scss' as iconfont;

	.pusher {
		width: 100%;
		height: 100%;
	}

	.player {
		width: 100%;
		height: 100%;
	}

	.debug-info {
		max-width: 100vw;
		max-height: 100vh;
		box-sizing: border-box;
		overflow-y: scroll;
		position: absolute;
		z-index: 9999;
		background-color: rgba(0, 0, 0, 0.5);
		color: #fff;
		bottom: 20rpx;
		left: 0;
		padding: 10rpx;
		font-size: 12px;
	}

	.debug-info-btn .debug-btn,
	.debug-info .debug-btn {
		padding: 0 8px;
		min-height: 18px;
		width: auto;
		font-size: 12px;
		line-height: 18px;
		display: inline-template;
		color: #06ae56;
		background-color: #f2f2f2;
	}

	.debug-info .debug-btn.false {
		color: rgb(114, 114, 114);
	}

	.debug-info-btn .debug-btn,
	.debug-info .button-hover {
		background-color: rgb(219, 219, 219);
	}

	.debug-info .close-btn {
		position: absolute;
		top: 0;
		right: 0;
		padding: 5px 10px;
	}

	.debug-info .text.true {
		color: #1fff8b;
	}

	.debug-info .text.false {
		color: #ff2e2e;
	}

	.debug-info-btn {
		position: absolute;
		z-index: 9998;
		bottom: 20rpx;
		left: 0;
	}

	.trtc-room-container .btn {
		display: inline-template;
		width: auto;
		height: 60rpx;
		min-height: 60rpx;
		line-height: 60rpx;
		font-size: 12px;
		font-weight: normal;
		padding: 0 10rpx;
		color: #006eff;
		background-color: #f2f2f2;
		margin: 0 16rpx;
	}

	.trtc-room-container .btn.active {
		color: #f2f2f2;
		background-color: #006eff;
	}

	.trtc-room-container .btn-hover {
		background-color: #d1d1d1;
	}

	.im-panel {
		position: absolute;
		z-index: 9;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		/* width: 100vw; */
		width: 90vw;
		height: 40vh;
		top: 50vh;
		left: 50vw;
		transform: translate(-50%, -50%);
		padding: 20rpx 0;
		border-radius: 10rpx;
		font-size: 12px;
		/* bottom: 25vh; */
		color: #fff;
		background-color: rgba(0, 0, 0, 0.8);
	}

	.im-panel .close-btn {
		position: absolute;
		top: 0;
		right: -3px;
		padding: 5px 10px;
		z-index: 99;
	}

	.message-panel-body {
		width: 100%;
		height: 80%;
		overflow-x: hidden;
		overflow-y: scroll;
	}

	.message-scroll-container {
		height: 100%;
		/* box-sizing: border-box;
  padding: 0 20rpx; */
	}

	.message-list {
		width: 100%;
		box-sizing: border-box;
		padding: 0 20rpx;
		/* display: flex;
  flex-direction: column; */
	}

	.message-item {
		width: 100%;
		/* height: 36rpx; */
		padding-bottom: 10rpx;
		display: flex;
		flex-direction: row;
	}

	.right-flex-direction {
		flex-direction: row-reverse;
	}

	.message-item .user-name {
		width: 20%;
		color: #2483ff;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		font-size: 40rpx;
	}

	.right-flex-direction .user-name {
		text-align: right;
	}

	.user-name.mine {
		color: #ff7424;
	}

	.message-item .separate {
		padding: 0 5px;
		color: #fff;
	}

	.message-item .message-content {
		word-wrap: break-word;
		word-break: break-all;
		padding-left: 20rpx;
		position: relative;
		max-width: 80%;
		box-sizing: border-box;
		font-size: 40rpx;
	}

	.message-content::after {
		content: ':';
		position: absolute;
		left: 0;
		top: 0;
	}

	.right-flex-direction .message-content {
		padding-right: 20rpx;
		padding-left: 0;
	}

	.right-flex-direction .message-content::after {
		left: auto;
		right: 0;
	}

	.message-panel-bottom {
		width: 100%;
		box-sizing: border-box;
		padding: 0 20rpx 0;
		margin-top: 20rpx;
		display: flex;
		flex-direction: row;
		align-items: center;
		height: 76rpx;
	}

	.message-input-container {
		flex-grow: 1;
		margin-right: 12rpx;
		height: 66rpx;
		line-height: 66rpx;
	}

	.message-input-container .message-input {
		font-size: 12px;
		padding-left: 12rpx;
		border-radius: 10rpx;
		height: 100%;
		background-color: #eee;
		color: #333;
		font-size: 32rpx;
	}

	.message-send-btn {
		display: flex;
		align-items: center;
	}

	.message-send-btn .btn {
		margin-right: 0;
		height: 66rpx;
		line-height: 66rpx;
		margin-left: 6rpx;
		width: 120rpx;
		font-size: 32rpx;
	}

	.message-item .image {
		min-width: 200rpx;
		max-width: 200rpx;
		max-height: 300rpx;
	}

	.none,
	.view-container.none,
	.template-grid .view-container.none,
	.template-1v1 .view-container.none {
		display: none !important;
	}

	.col-3 {
		width: 100%;
		text-align: center;
	}

	.col-3 text {
		display: inline-template;
		padding: 2rpx 12rpx;
		border-radius: 12rpx;
		background-color: #f4f4f4;
		margin: 12rpx 0;
		color: #a5b5c1;
	}
</style>