require('es6-promise').polyfill();
require('whatwg-fetch');
require('jquery-touchswipe');
require('string.fromcodepoint');
//require('messenger');

import Emiter from './Emiter';
import Sass from 'Sass';
import IpManager from 'IpManager';
import RecordPlayBackParse from 'RecordPlayBackParse';
import MD5 from "md5";
import Mcu from 'mcu';
import MessageTypes from 'MessageTypes';
import Loger from 'Loger';
import ConferApe from 'apes/ConferApe';
import ChatApe from 'apes/ChatApe';
import VideoApe from 'apes/VideoApe';
import AudioApe from 'apes/AudioApe';
import DocApe from 'apes/DocApe';
import WhiteBoardApe from 'apes/WhiteBoardApe';
import CursorApe from 'apes/CursorApe';

import EngineUtils from "EngineUtils";
import GlobalConfig from 'GlobalConfig';
import SystemConfig from 'SystemConfig';

import ApeConsts from 'apes/ApeConsts';
import Base64 from 'base64-js';
import ArrayBufferUtil from 'libs/ArrayBufferUtil';
import ServerConfig from "config/ServerConfig";
import MediaModule from 'apes/MediaModule';
import MediaSharedApe from 'apes/MediaSharedApe';
import MusicSharedApe from 'apes/MusicSharedApe';
import QuestionApe from 'apes/QuestionApe';
import UTF8 from 'utf-8';
import LogManager from 'LogManager';
import  WebRtcApe from 'apes/WebRtcApe';
import  Base64Module from 'Base64Module';

let loger = Loger.getLoger('McuClient');

//APE
let _sass;
let _ipManager;
let _mcu;
let _confer_ape;
let _chat_ape;
let _video_ape;
let _audio_ape;
let _doc_ape;
let _whiteboard_ape;
let _cursor_ape;
let _recordPlayback;
let _mediaShareApe;
let _musicShareApe;
let _questionApe;
let _webRtc;
let _base64;

//MCUClient 外部实例化主类
export default class MessageEntrance extends Emiter {
  constructor() {
    super();
    this.lastClassActiveTime=0;//最后一次课堂激活的时间戳
    //sdk 信息
    GlobalConfig.sdkVersion = "v2.38.13.20171216";
    loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
    console.log("sdkVersion:" + GlobalConfig.sdkVersion);
    //设置
    let locationProtocol = location.protocol;
    if (locationProtocol == "https:") {
      GlobalConfig.isHttps = true;
      GlobalConfig.locationProtocol = "https://";
      GlobalConfig.websocketProtocol = "wss://";
    } else {
      GlobalConfig.isHttps = false;
      GlobalConfig.locationProtocol = "http://";
      GlobalConfig.websocketProtocol = "ws://";
    }
    loger.warn("protocol:" + GlobalConfig.locationProtocol);
    //获取设备和系统信息
    SystemConfig.getSystemInfo();

    //获取用户的ip
    IpManager.getUserIp();

    this.classRecordStatusUpdateTimer = 0; //课堂录制状态发生改变后同步当前所有模块数据的计时器
    //初始化状态
    this.isGetUserIpCallback = false; //是否获取IP信息返回
    this.isGetFastestMcuCallback = false; //是否MCU测速结束
    this.isGetFastestMsCallback = false; //是否MS测速结束
    this.isGetFastestRtmpPullCallback = false; //是否RTMP拉流地址测试结束
    this.isGetFastestHlsPullCallback = false; //是否HLS拉流地址测试结束
    this.isGetFastestRsCallback = false; //是否录制回放HLS拉流地址测试结束
    this.saveClassStatusTimer = 0;//保存课堂数据的计时器间隔,防止同一瞬间多次提交
    this.joinChannelTimer = 0;//加入音视频通道的间隔
    //全局的Error处理
    this.on(MessageTypes.MCU_ERROR, this._mcuErrorHandler.bind(this));

    _webRtc = WebRtcApe;
    _webRtc.on('*', (type, data) => this._emit(type, data));
    _webRtc.on(MessageTypes.USER_DEVICE_STATUS_CHAANGE, this.userDeviecStatusChange.bind(this)); //监听摄像头和麦克风的开启状态
    _webRtc.on(MessageTypes.MEDIA_PUBLISH_STATUS_CHANGE, this.mediaPublishStatusChange.bind(this)); //webRtc推流状态发生改变
    _webRtc.on(MessageTypes.MEDIA_ENABLED_CHANGE, this._mediaEnabledChange.bind(this)); //音视频禁用状态改变  广播消息
    _webRtc.on(WebRtcApe.RE_JOIN_CHANNEL, this._webRtcRejoinChannel.bind(this)); //重先加入音视频频道
    _webRtc.on(WebRtcApe.UPDATE_USER_MEDIA_MUTED_STATUS, this._updateUserMediaMutedStatus.bind(this)); //音视频禁用状态改变(自己),同步更新

    // Sass平台层
    _sass = Sass;
    _sass.on('*', (type, data) => this._emit(type, data));
    _sass.on(_sass.SUCCESS, this._sassJoinSuccessHandler.bind(this)); //通过SASS平台验证(密码和MD5)
    _sass.on(_sass.CLASS_INIT_SUCCESS, this._sassInitSuccessHandler.bind(this)); //获取课堂初始化信息
    //_sass.on(_sass.CLASS_GET_CLASS_DETAIL, this._sassGetClassDetailSuccessHandler.bind(this));//获取课堂的基本信息
    _sass.on(_sass.CLASS_GET_CLASS_PARAM, this._sassGetClassParamSuccessHandler.bind(this)); //SAAS获取课堂的最全信息和历史保存的数据

    _sass.on(_sass.CLASS_SAVE_STATUS_INFO_SUCCESS, this._sassSaveClassStatusInfoSuccessHandler.bind(this)); //保存课堂状态信息
    _sass.on(_sass.CLASS_SAVE_RECORD_INFO_SUCCESS, this._sassSaveClassRecordInfoSuccessHandler.bind(this)); //保存课堂录制信息
    _sass.on(_sass.DELETE_DOCUMENT_SUCCESS, this._sassDeleteDocumentSuccess.bind(this)); //sass删除文档成功
    _sass.on(_sass.DELETE_MEDIASHARE_SUCCESS, this._sassDeleteMediaShareSuccess.bind(this)); //sass删除媒体文件成功
    _sass.on(_sass.DELETE_MUSICSHARE_SUCCESS, this._sassDeleteMusicShareSuccess.bind(this)); //sass删除MUSIC文件成功

    //选点模块
    _ipManager = new IpManager();
    _base64 = new Base64Module();

    // 底层MCU消息层
    _mcu = Mcu;
    _mcu.on('*', (type, data) => this._emit(type, data));
    _mcu.on(MessageTypes.CLASS_JOIN_MCU_SUCCESS, this._mcuJoinMCUClassSuccessHandler.bind(this)); //加入MCU课堂完成
    _mcu.on(MessageTypes.SWITCH_MCU_IP, this._switchMcuIpHandler.bind(this)); //切换MCU,重新选点
    _mcu.on(MessageTypes.SOCKET_MAX_RECONNECT_FAILED, this._socketMaxReconnectFailed.bind(this)); //mcu断线重连已经达到最大次数,不再重连

    //录制回放
    _recordPlayback = RecordPlayBackParse;
    _recordPlayback.on('*', (type, data) => this._emit(type, data));
    _recordPlayback.on(RecordPlayBackParse.CLASS_JOIN_RECORD_PLAYBACK_SUCCESS, this._joinRecordPlaybackSuccessHandler.bind(this)); //加入录制回放完成
    _recordPlayback.on(RecordPlayBackParse.RECORD_PLAYBACK_CLEAR_DATA, this._recordPlaybackClearDataHandler.bind(this)); //录制回放状态更新

    // 注册所有应用Ape
    _confer_ape = new ConferApe();
    _confer_ape.on('*', (type, data) => this._emit(type, data));
    _confer_ape.on(MessageTypes.CLASS_RUN_EXIT, this._runClassExit.bind(this)); //监听自己的关闭事件
    _confer_ape.on(MessageTypes.CLASS_STATUS_INFO_CHANGE, this._onClassStatusInfoChange.bind(this)); //当前课堂状态信息发生改变
    _confer_ape.on(MessageTypes.CLASS_DELETE_ROSTER, this._onClassDeleteRoster.bind(this)); //当前课堂人员离开
    _confer_ape.on(MessageTypes.CLASS_UPDATE_ROSTER, this._onClassUpdateRoster.bind(this)); //当前课堂人员更新信息
    _confer_ape.on(MessageTypes.CLASS_NONENTITY_ROSTER, this._onClassNonentityRoster.bind(this)); //当前课堂中视频或音频占用channel的nodeId ,在人员列表中不存在


    _confer_ape.on(MessageTypes.CLASS_RECORD_START, this._onClassRecordStart.bind(this)); //课堂开始录制
    _confer_ape.on(MessageTypes.CLASS_RECORD_SUCCESS, this._onClassRecordSuccess.bind(this)); //课堂开启录制成功

    //_confer_ape.on(MessageTypes.SWITCH_MS_IP, this._switchMsIpHandler.bind(this)); //MS动态选点
    //_confer_ape.on(MessageTypes.SWITCH_RTMP_PULL_IP, this._switchRtmpPullIpHandler.bind(this)); //MS 拉流地址动态选点
    //_confer_ape.on(MessageTypes.SWITCH_HLS_IP, this._switchHlsIpHandler.bind(this)); //MS HLS动态选点
    _confer_ape.on(MessageTypes.STOP_ALL_MEDIA_PUBLISH, this._stopAllMediaPublishHandler.bind(this)); //课堂状态发生改变,需要停止当前的所有推流
    _confer_ape.on(MessageTypes.CLASS_UPDATE_ROSTER, this._onRosterUpdateHandler.bind(this));
    _confer_ape.on(MessageTypes.RECEIVE_MEDIA_ENABLED_CHANGE, this._receiveMeiaEnabledChange.bind(this));

    _chat_ape = new ChatApe();
    _chat_ape.on('*', (type, data) => this._emit(type, data));

    _video_ape = new VideoApe();
    _video_ape.on('*', (type, data) => this._emit(type, data));
    _video_ape.on(MessageTypes.VIDEO_UPDATE, this.videoUpdate.bind(this)); //这个监听事件不能删除,需要通知课堂模块,检查channel占用
    _video_ape.on(MessageTypes.USER_DEVICE_STATUS_CHAANGE, this.userDeviecStatusChange.bind(this)); //监听摄像头和麦克风的开启状态

    _audio_ape = new AudioApe();
    _audio_ape.on('*', (type, data) => this._emit(type, data));
    _audio_ape.on(MessageTypes.AUDIO_UPDATE, this.audioUpdate.bind(this)); //这个监听事件不能删除,需要通知课堂模块,检查channel占用
    _audio_ape.on(MessageTypes.USER_DEVICE_STATUS_CHAANGE, this.userDeviecStatusChange.bind(this)); //监听摄像头和麦克风的开启状态

    _mediaShareApe = new MediaSharedApe();
    _mediaShareApe.on('*', (type, data) => this._emit(type, data));
    _mediaShareApe.on(MediaSharedApe.MEDIASHARED_JOIN_CHANNEL_SUCCESS, this.mediaShareApeJoinChannelSuccess.bind(this));

    _musicShareApe = new MusicSharedApe();
    _musicShareApe.on('*', (type, data) => this._emit(type, data));
    _musicShareApe.on(MusicSharedApe.MUSICSHARED_JOIN_CHANNEL_SUCCESS, this.musicShareApeJoinChannelSuccess.bind(this));

    _whiteboard_ape = new WhiteBoardApe();
    _whiteboard_ape.on('*', (type, data) => this._emit(type, data));

    _cursor_ape = new CursorApe();
    _cursor_ape.on('*', (type, data) => this._emit(type, data));

    _doc_ape = new DocApe();
    _doc_ape.on('*', (type, data) => this._emit(type, data));
    _doc_ape.on(MessageTypes.DOC_UPDATE, this.docUpdateHandler.bind(this));
    _doc_ape.on(MessageTypes.DOC_DELETE, this.docDeleteHandler.bind(this));
    _doc_ape.on(DocApe.DOC_JOIN_CHANNEL_SUCCESS, this.docJoinChannelSuccess.bind(this));

    _questionApe = new QuestionApe();
    _questionApe.on('*', (type, data) => this._emit(type, data));

    //公开外部调用的方法
    //class
    this.init = this._init.bind(this);
    this.joinClass = this._joinClass.bind(this);
    this.leaveClass = this._leaveClass.bind(this);
    this.getMcuClientStatus = this._getMcuClientStatus.bind(this);
    this.getClassStatusInfo = this._getClassStatusInfo.bind(this);
    this.sendStartClass = this._sendStartClass.bind(this);
    this.silenceClass = this._silenceClass.bind(this);
    this.sendPauseClass = this._sendPauseClass.bind(this);
    this.sendCloseClass = this._sendCloseClass.bind(this);
    this.changeHandUpStatus = this._changeHandUpStatus.bind(this); //自己切换举手状态
    this.controlHandUpStatus = this._controlHandUpStatus.bind(this); //控制别人的举手状态
    this.controlSilenceStatus = this._controlSilenceStatus.bind(this); //改变禁言状态

    this.controlDrawStatus = this._controlDrawStatus.bind(this); //控制别人的是否可以使用画笔状态

    this.sceneTableChange = this._sceneTableChange.bind(this); //切换模块显示
    this.kickOutRosterFormNodeId = this._kickOutRosterFormNodeId.bind(this); //把指定nodeId的人踢出课堂
    this.sendThridChannelMessage = this._sendThridChannelMessage.bind(this);//通道第三方消息通道发送消息
    this.changeDrawStatus = this._changeDrawStatus.bind(this);//切换绘制状态
    this.changeGiftStatus = this._changeGiftStatus.bind(this);//切换送礼物状态


    //录制回放
    this.initRecordPlayback = this._initRecordPlayback.bind(this);
    this.startRecordPlayback = this._startRecordPlayback.bind(this);
    this.stopRecordPlayback = this._stopRecordPlayback.bind(this);
    this.pauseRecordPlayback = this._pauseRecordPlayback.bind(this);
    this.seekRecordPlayback = this._seekRecordPlayback.bind(this);

    //chatApe
    this.sendChatMsg = this._sendChatMsg.bind(this);

    //屏幕共享
    this.publishScreenShare = this._publishScreenShare.bind(this);
    this.stopPublishScreenShare = this._stopPublishScreenShare.bind(this);

    //推送外部流
    this.publishExternalLink = this._publishExternalLink.bind(this);
    this.stopPublishExternalLink = this._stopPublishExternalLink.bind(this);

    //videoApe
    this.getVideoPublishPath = this._getVideoPublishPath.bind(this);
    this.getVideoAllChannelInfo = this._getVideoAllChannelInfo.bind(this);
    this.publishVideo = this._publishVideo.bind(this);
    this.stopPublishVideo = this.unPublishVideo = this._stopPublishVideo.bind(this);
    this.sendVideoBroadcastMsg = this._sendVideoBroadcastMsg.bind(this);

    //audioApe
    //this.getAudioPlayPath = this._getPlayAudioPath.bind(this);
    this.getAudioPublishPath = this._getPublishAudioPath.bind(this);
    this.getAudioAllChannelInfo = this._getAudioAllChannelInfo.bind(this);
    this.publishAudio = this._publishAudio.bind(this);
    this.stopPublishAudio = this.unPublishAudio = this._stopPublishAudio.bind(this);
    this.sendAudioBroadcastMsg = this.sendAudioCommandMsg.bind(this);

    //whiteBoradApe
    this.sendInsertAnnotaion = this._sendInsertAnnotaion.bind(this); //添加标注
    this.sendDeleteAllAnnotation = this._sendDeleteAllAnnotation.bind(this); //删除所有标注
    this.sendDeleteCurPageAnnotation = this._sendDeleteCurPageAnnotation.bind(this); //删除当前页的所有标注
    this.sendGotoPrev = this._sendGotoPrev.bind(this); //当前页撤销上一步

    // CursorApe
    this.sendInsertCursor = this._sendInsertCursor.bind(this); //添加鼠标同步

    //DocApe
    this.sendDocumentUpload = this._sendDocumentUpload.bind(this); //上传文档
    this.sendDocumentSwitchDoc = this._sendDocumentSwitchDoc.bind(this); //切换文档
    this.sendDocumentSwitchPage = this._sendDocumentSwitchPage.bind(this); //翻页
    this.sendDocumentSwitchAnimation = this._sendDocumentSwitchAnimation.bind(this); //切换当前页码的动画步骤
    this.sendDocumentDelete = this._sassDeleteDocument.bind(this);
    this.sendDocBroadcastMsg = this._sendDocBroadcastMsg.bind(this);
    //删除文档,先通过Sass删除,sass删除成功之后再同步mcu
    //this.sendDocumentDeleteAll= this._documentDeleteAll;//删除所有文档
    this.sendDocumentCommand = this._sendDocumentCommand.bind(this);
    //操作文档(翻页、缩放、滚动...)
    this.getDocImageFullPath = this._getDocImageFullPath.bind(this);
    //获取文档图片的完整路径
    this.getDocPDFFullPath = this._getDocPDFFullPath.bind(this); //获取文档的完整路径
    this.getDocFullAddress = this._getDocFullAddress.bind(this); //获取文档资源地址
    this.hideCurrentDocument = this._hideCurrentDocument.bind(this);//隐藏当前显示的文档
    this.switchToWhiteboard = this._switchToWhiteboard.bind(this); //切换到白板文档
    //媒体共享模块
    this.mediaSharedUpload = this._sendMediaSharedUpload.bind(this); //上传
    this.mediaSharedDelete = this._sassDeletMediaShare.bind(this); //删除,先通过Sass删除,删除成功之后才删除MCU的

    this.mediaSharedPlay = this._sendMediaSharedPlay.bind(this); //播放
    this.mediaSharedStop = this._sendMediaSharedStop.bind(this); //停止
    this.mediaSharedUpdate = this._sendMediaSharedUpdate.bind(this); //更新媒体的状态

    //音频共享模块
    this.musicSharedUpload = this._sendMusicSharedUpload.bind(this); //上传
    this.musicSharedDelete = this._sassDeletMusicShare.bind(this); //删除,先通过Sass删除,删除成功之后才删除MCU的

    this.musicSharedPlay = this._sendMusicSharedPlay.bind(this); //Music播放
    this.musicSharedStop = this._sendMusicSharedStop.bind(this); //Music停止
    this.musicSharedUpdate = this._sendMusicSharedUpdate.bind(this); //Music更新媒体的状态

    //答题卡
    this.creatQuestion = this._creatQuestion.bind(this);
    this.getQuestion = this._getQuestion.bind(this);
    this.getQuestionResult = this._getQuestionResult.bind(this);
    this.sendAnswer = this._sendAnswer.bind(this);
    this.stopQuestion = this._stopQuestion.bind(this);

    //webrtc
    this.publishMedia = this._publishMedia.bind(this);
    this.unpublishMedia = this._unpublishMedia.bind(this);
    this.changeDevices = this._changeDevices.bind(this);
    this.setConfigPublisher = this._setConfigPublisher.bind(this);
    this.setLocalMediaView = this._setLocalMediaView.bind(this);//设置自己的视图
    this.setHostRemoteMediaView = this._setHostRemoteMediaView.bind(this);//设置远程老师的视图
    this.setNormalRemoteMediaView = this._setNormalRemoteMediaView.bind(this);//设置远程学生的视图
    this.setInvisibleMediaView = this._setInvisibleMediaView.bind(this);//设置监课身份的视图
    this.leaveVideoChannel = this._leaveChannel.bind(this);//离开视频频道

    this.setAppConfig = this._setAppConfig.bind(this);
    this.recordControl = this._changeMediaRecordStatus.bind(this);//切换控制音视频的录制状态

    this.changeRtcVideoConfig = this._changeRtcVideoConfig.bind(this);//设置webRtc视频视图的缩放

    this.setDeviceInfo = this._setDeviceInfo.bind(this); //设置设备信息(麦克风,摄像头等等.....)
    this.setMessageDelay = this._setMessageDelay.bind(this); //设置是否延迟消息
    this.switchServer = this._switchMcuIpHandler.bind(this); //切换mcu服务器
    this.switchMediaServer = this._switchMediaServer.bind(this); //手动切换ms服务器
    this.setDebugInfo = this._setDebugInfo.bind(this); //设置debug信息
    this.setOpenSendLogToServer = this._setOpenSendLogToServer.bind(this); //设置是否上报日志

    this.reJoinChannel = this._reJoinChannel.bind(this); //重新加入频道

    this.addLog = this._addLog.bind(this);
    this.addWarn = this._addWarn.bind(this);
    this.addError = this._addError.bind(this);

    this.hasFreePublishChannel = this._hasFreePublishChannel.bind(this);//判断是否还有空闲的推流通道


    //添加外部流数据和删除外部流数据
    this.deleteMediaExternalLink=this._deleteMediaExternalLink.bind(this);
    this.addMediaExternalLink=this._addMediaExternalLink.bind(this);
  }

  //设置是否输出日志
  _setDebugInfo(_data) {
    loger.log("设置debug信息-->", _data);
    if (_data) {
      Loger.setLogDebug(_data.isDebug);
    }
  }

  //设置是否上报日志
  _setOpenSendLogToServer(_data) {
    loger.log("设置日志上报状态-->", _data);
    if (_data) {
      LogManager.IS_OPEN_SEND_LOG = _data.isOpen
    }
  }

  //--------------外部上传日志的接口------------------------
  //上传log日志
  _addLog(_data) {
    if (_data) {
      LogManager.addLog(LogManager.LOG, _data.msg || "");
    }
  }

  //上传warn日志
  _addWarn(_data) {
    if (_data) {
      LogManager.addLog(LogManager.WARN, _data.msg || "");
    }
  }

  //上传error日志
  _addError(_data) {
    if (_data) {
      LogManager.addLog(LogManager.ERROR, _data.msg || "");
    }
  }

  //--------------外部上传日志的接口--END ----------------------
  //设置设备信息
  _setDeviceInfo(_data) {
    loger.log("设置设备信息-->", _data);
    if (_data) {
      GlobalConfig.cameras = _data.cameras || [];
      GlobalConfig.microphones = _data.microphones || [];
      GlobalConfig.videoQuality = parseInt(_data.videoQuality);
      GlobalConfig.curVideoQuality = parseInt(_data.curVideoQuality);
      GlobalConfig.micGain = _data.micGain || 70;
      GlobalConfig.micNoise = _data.micNoise || 70;
      GlobalConfig.autoGain = _data.autoGain || false;
      GlobalConfig.speakerVolume = _data.speakerVolume || 80;
      GlobalConfig.micCode = _data.micCode || 0;
      GlobalConfig.curCamera = _data.curCamera || '';
      GlobalConfig.curMicrophone = _data.curMicrophone || '';

      if (!GlobalConfig.curCamera && GlobalConfig.cameras.length > 0) {
        GlobalConfig.curCamera = GlobalConfig.cameras[0];
      }
      if (!GlobalConfig.curMicrophone && GlobalConfig.microphones.length > 0) {
        GlobalConfig.curMicrophone = GlobalConfig.microphones[0];
      }
      this._updateDeviceInfo();
    }
  }

  //设置消息延迟
  _setMessageDelay(_data) {
    loger.warn("延迟消息-->", _data);
    if (_data) {
      GlobalConfig.messageDelay = _data.messageDelay || false;
    }
  }

  //mcu异常监听
  _mcuErrorHandler(_data) {
    let errorMessage = {};

    //目前只有 userId和身份相同时被踢的时候有type值判断
    switch (_data.type) {
      case MessageTypes.ERR_CLASS_KICK_OUT:
      case MessageTypes.ERR_CLASS_REMOTE_LANDING:
        if (_data) {
          errorMessage = {"code": _data.type, "reson": MessageTypes.ErrorReson[_data.type], "data": _data.data};
        }
        break;

      default :
        errorMessage = {"code": _data, "reson": MessageTypes.ErrorReson[_data], "data": {}};
        break;
    }

    this._emit(MessageTypes.ERROR_EVENT, errorMessage);
    loger.error("MCU_ERROR", errorMessage);
  }

  //获取当前的状态
  _getMcuClientStatus() {
    return GlobalConfig.getCurrentStatus();
  }

  //获取课堂信息
  _getClassDetail() {
    return GlobalConfig.getClassDetail();
  }

  //获取当前课堂的状态信息
  _getClassStatusInfo() {
    return GlobalConfig.classStatusInfo;
  }

  //执行离开课堂断开连接的流程
  _runClassExit(_type) {
    if( GlobalConfig.classExit==true){
      console.log("已经离开课堂");
      return;
    }
    this._leaveClass(_type);
    this._leaveChannel();
    //记录是否已经离开课堂,离开之后不做MCU重连
    GlobalConfig.classExit=true;
    LogManager.sendLogToServer();
    this._emit(MessageTypes.CLASS_EXIT, {type:6});
    //2秒后停止日志上报
    setTimeout(()=>{
      LogManager.IS_OPEN_SEND_LOG = false;//断开之后不再上报日志
    },2000);

    this.clearClientData();
  }
  //清除所有客户端数据
  clearClientData(){
    GlobalConfig.MCUServerIP="";
    GlobalConfig.MCUServerPort="";
    GlobalConfig.mcuListFinal=[];
    if(_mcu){
      _mcu.leaveMCU();
    }
    if(_confer_ape){
      _confer_ape.stopApe();
    }
    if(_video_ape){
      _video_ape.stopApe();
    }
  }
  //当前的课堂状态信息发生改变,需要保存课堂状态到Sass
  _onClassStatusInfoChange(_param) {
    //如果MCU连接已经断开,不发送
    if (!_mcu || !_mcu.connected) {
      loger.warn("不能保存课堂状态", GlobalConfig.getCurrentStatus());
      return;
    }
    this._sassSaveClassStatusInfo(_param);
  }

  //如果是第一次点击开始上课,需要创建录制时的文件名
  _onClassRecordStart(_param) {
    if (!_mcu || !_mcu.connected) {
      loger.warn("不能保存课堂状态", GlobalConfig.getCurrentStatus());
      return;
    }
    if (_sass) {
      _sass.saveClassRecordContrlInfo(_param);
    }
  }

  //开启录制成功
  _onClassRecordSuccess(_param) {
    clearTimeout(this.classRecordStatusUpdateTimer);
    this.classRecordStatusUpdateTimer = setTimeout(()=> {
      clearTimeout(this.classRecordStatusUpdateTimer);
      this.updaterRecordAllApeStatus(_param);
    }, 1600);
  }

  //录制状态发送改变,更新所有模块的当前数据发送到MCU
  updaterRecordAllApeStatus(_param) {
    if (GlobalConfig.isRecordPlayBack || !_confer_ape) {
      return;
    }
    if(GlobalConfig.classType==ApeConsts.CLASS_TYPE_ZHIBO){
      //直播的时候不再同步所有模块的消息
      return
    }
    //老师身份和非录制回放的时候执行,录制状态发送改变,需要更新当前的数据,否则已有的消息会录制不上
    if (_confer_ape.checkHasRecordControl()) {
      loger.warn('录制状态发送改变->更新所有模块的当前数据发送到MCU');
      //目前录制的模块[文档模块、白板模块、视频模块(包含屏幕共享)、音频模块、媒体共享,聊天模块]
      if (_doc_ape) {
        _doc_ape.updaterRecordApeStatus();
      }
      if (_whiteboard_ape) {
        _whiteboard_ape.updaterRecordApeStatus();
      }
    /*  if (_video_ape) {
        _video_ape.updaterRecordApeStatus();
      }
      if (_audio_ape) {
        _audio_ape.updaterRecordApeStatus();
      }*/
      if (_mediaShareApe) {
        _mediaShareApe.updaterRecordApeStatus();
      }
      if (_musicShareApe) {
        _musicShareApe.updaterRecordApeStatus();
      }
      //聊天模块不需要更新
    }
  }

  //人员离开
  _onClassDeleteRoster(_data) {

  }

  //人员更新
  _onClassUpdateRoster(_data) {
    if (!_data) {
      return;
    }
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS ||
      GlobalConfig.deviceType == GlobalConfig.deviceAndroid ||
      GlobalConfig.deviceType == GlobalConfig.deviceH5 ||
      _data.nodeId == GlobalConfig.nodeId) {
      return;
    }
    if (_webRtc) {
      let user = GlobalConfig.rosters[_data.nodeId];
      if (user && user.openCamera == 0) {
        _webRtc.closeRemoteVideoView(_data);
      }
    }
  }

  //当前课堂中视频或音频占用channel的nodeId ,在人员列表中不存在,这种情况是占用channel的人员掉线或离开的时候没有释放channel
  //的占用状态导致,对于这种情况,需要释放掉
  _onClassNonentityRoster(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      loger.warn("录制回放中,不处理")
      return;
    }

    if (_param == null || _param.nodeId == null) {
      loger.warn("onClassNonentityRoster.参数错误")
      return;
    }
    let data = {"nodeId": _param.nodeId};
    if (_video_ape) {
      _video_ape.stopPublishVideo(data);

      //停止屏幕共享
      if (GlobalConfig.nodeId == data.nodeId) {
        _video_ape.stopPublishScreenShare(data);
      }
    }
    if (_audio_ape) {
      _audio_ape.stopPublishAudio(data);
    }
  }

  //Sass
  //初始化
  _init(_param) {
    //{"classId":"1653304953","portal":"112.126.80.182:80","userRole":"normal","userId":0,isH5:false}
    //判断传入的参数是否存在
    if (_param == null || EngineUtils.isEmptyObject(_param)) {
      loger.error('初始化课堂失败->参数错误', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_CLASS_INIT_PARAM);
      return;
    }
    //判断必要的参数字段值
    if (_param.classId == null || isNaN(_param.classId) || _param.portal == null || _param.portal == "") {
      loger.error('初始化课堂失败->参数错误', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_CLASS_INIT_PARAM);
      return;
    }
    //添加日志捕获
    LogManager.catchConsole();

    loger.warn('=====================STEP1=======================');
    loger.log('初始化课堂->', _param);
    //保存参数
    GlobalConfig.isRecordPlayBack = false; //设置为非录制回放状态
    GlobalConfig.classId = parseInt(_param.classId);
    GlobalConfig.portal = _param.portal || "";
    GlobalConfig.openFlash = Boolean(_param.openFlash);
    if (GlobalConfig.isHttps == true) {
      //https的时候替换所有80端口
      GlobalConfig.portal = GlobalConfig.replacePort(GlobalConfig.portal, ":80", "");
    }

    GlobalConfig.paramUserId= _param.userId || "0";
    GlobalConfig.userId = "" + _param.userId || "0";
    //H5处理
    GlobalConfig.isH5 = _param.isH5 || false;//外部传入的参数,是否是H5
    if (GlobalConfig.isH5 == true) {
      GlobalConfig.platform = "h5";
      GlobalConfig.deviceType = GlobalConfig.deviceH5;
      loger.warn("设备类型是H5");
    }

    //IOS 安卓处理,需要外部传入摄像头和麦克风信息
    if (GlobalConfig.isMobile) {
      GlobalConfig.cameras = _param.cameras || [];
      GlobalConfig.microphones = _param.microphones || [];
    }

    //设置角色身份
    GlobalConfig.userRole = _param.userRole || ApeConsts.normal;
    if (GlobalConfig.userRole != ApeConsts.host &&
      GlobalConfig.userRole != ApeConsts.presenter &&
      GlobalConfig.userRole != ApeConsts.assistant &&
      GlobalConfig.userRole != ApeConsts.record &&
      GlobalConfig.userRole != ApeConsts.invisible) {
      GlobalConfig.userRole = ApeConsts.normal;
    }


    //如果没有名字的时候需要随机生成
    let randUserId =EngineUtils.creatRandomNum(3,".");//parseInt(Math.random()*1000)+"_"+parseInt(Math.random()*1000)+"_"+parseInt(Math.random()*1000);
    let randUserName=EngineUtils.creatRandomNum(2,".");
    if (GlobalConfig.userRole == ApeConsts.host) {
      randUserId = "1_" + randUserId;
      randUserName= "1_" + randUserName;
    } else if (GlobalConfig.userRole == ApeConsts.assistant) {
      randUserId = "2_" + randUserId;
      randUserName= "2_" + randUserName;
    } else if (GlobalConfig.userRole == ApeConsts.presenter) {
      randUserId = "2_" + randUserId;
      randUserName= "2_" + randUserName;
    } else  if (GlobalConfig.userRole == ApeConsts.invisible) {
      randUserId = "32_" + randUserId;
      randUserName= "32_" + randUserName;
    } else {
      randUserId = "8_" + randUserId;
      randUserName= "8_" + randUserName;
    }

    //如果没有名字,随机起一个名字
    GlobalConfig.userName = _param.userName || randUserName;
    //如果没有userId或者为"0",随机生成
    if (!GlobalConfig.userId || GlobalConfig.userId == "0") {
      GlobalConfig.userId = randUserId;
    }
    console.log("userId", GlobalConfig.userId);
    //客户端决定是否延迟接收消息
    GlobalConfig.messageDelay = _param.messageDelay || false;

    //最长允许录制的时间
    if (_param.allowRecordMaxTime) {
      GlobalConfig.allowRecordMaxTime = parseInt(_param.allowRecordMaxTime);
    }

    //获取课堂校验信息
    if (_sass) {
      _sass.getJoinParams(GlobalConfig.getClassInfo());
    }
  }

  //外部请求加入课堂
  _joinClass(_param) {
    if (_param == null || EngineUtils.isEmptyObject(_param)) {
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_CLASS_JOIN_PARAM);
      loger.log('加入课堂失败->参数错误.', _param);
      return;
    }
    //判断userName
    if (_param.userName == null || _param.userName == "") {
      loger.log('加入课堂失败->参数错误->名字不能为空');
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_CLASS_JOIN_PARAM);
      return;
    }
    //{"userName":"名字","password":"","autoLogin":""}
    GlobalConfig.userName = _param.userName || GlobalConfig.userName; //以登陆时的名字为主,登陆之前可以修改名字
    GlobalConfig.autoLogin = _param.autoLogin || "";
    GlobalConfig.password = _param.password || "";
    GlobalConfig.hasCamera = (typeof _param.hasCamera == "boolean") ? _param.hasCamera : false;
    GlobalConfig.hasMicrophone = (typeof _param.hasMicrophone == "boolean") ? _param.hasMicrophone : false;

    //loger.log("autoLoginMd5", GlobalConfig.classId, GlobalConfig.userId, GlobalConfig.userRole);
    let autoLoginMd5 = MD5("" + GlobalConfig.classId + GlobalConfig.paramUserId + GlobalConfig.userRole);
    //let autoLoginMd5 = MD5("" + GlobalConfig.classId + GlobalConfig.userId + GlobalConfig.userRole);
    //loger.log("joinClass-GlobalConfig.autoLogin", GlobalConfig.autoLogin, "autoLoginMd5-", autoLoginMd5);
    if ((GlobalConfig.autoLogin && autoLoginMd5 == GlobalConfig.autoLogin) ||
      GlobalConfig.isInvisible||GlobalConfig.isAssistant||GlobalConfig.isPresenter) {
      // MD5(classId+userId+userRole)==m
      //自动登录,跳过验证流程
      loger.log("自动登录->" + GlobalConfig.userRole);
      this._sassJoinSuccessHandler();
    } else {
      //不能自动登录,开始校验
      if (_sass) {
        _sass.passwordAndMd5Checking(GlobalConfig.getClassInfo());
      }
    }
  }

  // 用classId向SASS平台获取入会验证信息成功
  _sassInitSuccessHandler(_data) {
    //{"siteId":"h5test","passwordRequired":true,"md5":"de399d5540b3da2fbc1eb0a770d4fd66","code":0,"msType":1}
    //储存数据
    GlobalConfig.md5 = _data.md5 || ""; //这个暂时用假数据,后台接口写完就有数据了
    GlobalConfig.msType = _data.msType || 1;
    GlobalConfig.siteId = _data.siteId || "";
    GlobalConfig.classType = _data.meetingType|| ApeConsts.CLASS_TYPE_1v1;

    //host默认需要密码,Sass服务器只判断学生是否需要密码,没有判断老师的
    GlobalConfig.passwordRequired = _data.passwordRequired || false; //md5验证的时候需要Sass返回的值,不能更改
    loger.log('初始化课堂验证完成.');

    //设置当前的课堂状态
    GlobalConfig.setCurrentStatus(GlobalConfig.statusCode_1);
    //返回给客户端初始化成功的数据
    let initSuccessCallBackData = {};
    initSuccessCallBackData.siteId = GlobalConfig.siteId;
    initSuccessCallBackData.classId = GlobalConfig.classId;
    initSuccessCallBackData.userRole = GlobalConfig.userRole;
    initSuccessCallBackData.userId = GlobalConfig.userId;
    initSuccessCallBackData.userName = GlobalConfig.userName;
    initSuccessCallBackData.classType = GlobalConfig.classType;

    //host默认需要密码,Sass服务器只判断学生是否需要密码,没有判断老师的
    if (GlobalConfig.userRole == ApeConsts.host) {
      initSuccessCallBackData.passwordRequired = true;
    } else {
      initSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;
    }

    this._emit(MessageTypes.CLASS_INIT_SUCCESS, initSuccessCallBackData);
  }

  // 通过SASS平台验证(密码和MD5)
  _sassJoinSuccessHandler(_data) {
    //先获取白板配置再获取课堂完整信息
    try{
      if(_ipManager){

        _ipManager.loadWhiteboardJosn((_callbackInfo)=>{
          loger.log("获取白板配置JOSN返回->",_callbackInfo);
          if(_callbackInfo&&_callbackInfo.data){
            let siteWhiteboradData=_callbackInfo.data[GlobalConfig.siteId];
            if(siteWhiteboradData){
              /*  itemIdx: GlobalConfig.whiteboardId,//指定的白板文档ID
                name: "白板.pdf",
                creatUserId: 0,
                md5: "b153313f6f390328a30db5389b6cee53",
                pageNum: 30,
                docId: "b153313f6f390328a30db5389b6cee53",
                url: "http://pclive.xuedianyun.com/DocSharing/data/whiteboard/default/whiteboard.pdf",
                dynamicTransferStatic: "0",
                relativeUrl: "/DocSharing/data/whiteboard/default/whiteboard.pdf",
                fileType: "pdf",
                type:"pdf"*/
              GlobalConfig.whiteboardForSiteId={};
              GlobalConfig.whiteboardForSiteId.itemIdx=GlobalConfig.whiteboardId;
              GlobalConfig.whiteboardForSiteId.name=siteWhiteboradData.name;
              GlobalConfig.whiteboardForSiteId. creatUserId= siteWhiteboradData.creatUserId;
              GlobalConfig.whiteboardForSiteId. md5=siteWhiteboradData.md5;
              GlobalConfig.whiteboardForSiteId. pageNum= parseInt(siteWhiteboradData.pageNum);
              GlobalConfig.whiteboardForSiteId. docId=siteWhiteboradData.docId;
              GlobalConfig.whiteboardForSiteId. url=siteWhiteboradData.url;
              GlobalConfig.whiteboardForSiteId.dynamicTransferStatic=siteWhiteboradData.dynamicTransferStatic;
              GlobalConfig.whiteboardForSiteId.relativeUrl=siteWhiteboradData.relativeUrl;
              GlobalConfig.whiteboardForSiteId. fileType=siteWhiteboradData.fileType;
              GlobalConfig.whiteboardForSiteId. type=siteWhiteboradData.type;
              loger.log("站点已经有配置白板文档->使用站点私有的白板");
            }
          }
          this._getClassFullParam();
        });
      }else {
        this._getClassFullParam();
      }
    }catch (err){
      this._getClassFullParam();
    }
  }
  //SAAS获取课堂最完整的数据
  _getClassFullParam(){
    if (_sass) {
      _sass.getClassParam();
    }
  }
  //加载本地Server.json文件,UserIp获取ip信息,选点
  loadServerJsonAndgetUserIpInfo() {
    let _this = this;
    loger.warn('=====================STEP6=======================');
    if (_ipManager) {
      //先加载本地Server.json文件,然后获取userIp新
      _ipManager.loadServerJosn(function (_callbackData) {
        //本地Server.json加载后需要判断是否有数据,如果没有数据就用Sass的
        if (_callbackData) {
          ServerConfig.localServerJson = _callbackData.data;
          console.warn("本地SERVER数据", _callbackData);
        } else {
          ServerConfig.localServerJson = {};
        }

        if (!ServerConfig.localServerJson || !ServerConfig.localServerJson.MCU || !ServerConfig.localServerJson.MS) {
          ServerConfig.serverList = ServerConfig.sassServerJson;//本地JSON数据加载完数据无效,使用Sass的
          loger.warn("使用从Sass获取的server");
        } else {
          ServerConfig.serverList = ServerConfig.localServerJson;
          loger.warn("使用从本地获取的server");
        }
        //通过userIp获取用户的信息
        _ipManager.getUserIpInfo("", GlobalConfig.userIp, _this._getUserIpCallbackHandler.bind(_this), 2000);
      })
    }
  }

  //本地JOSN加载完成-获取IP信息完成
  _getUserIpCallbackHandler(_data) {
    //获取IP信息,返回一次就不再处理
    loger.log("获取IP信息返回->",_data);
    if (this.isGetUserIpCallback) {
      return;
    }
    this.isGetUserIpCallback = true;
    if (_data && _data.ret == "ok") {
      GlobalConfig.country = _data.country; //国家
      GlobalConfig.city = _data.city; //城市
      GlobalConfig.province = _data.province; //服务商
      GlobalConfig.isp = _data.isp; //服务商
    }
    //根据用户的userIp信息从sever.json和Sass中选择最终mcu和推流拉流数据列表
    this._choiceMcuAndMsListFromServer();
    //获取MCU和MS 推流拉流、录制回放的默认地址
    this.getMcuAndMsDefaultServerIp();
    loger.warn("加入课堂之前->开始测速->选择默认服务");
    this._startFirstTestBestServer();
  }

  //开始加入课堂前第一次测速
  _startFirstTestBestServer() {
    //加入课堂之前开始第一次选点
    let _this = this;
    loger.warn('=====================STEP7=======================');
    loger.log("加入课堂之前开始第一次选点");
    //推流地址测速
    this._getFastestIpFromServer(GlobalConfig.msListFinal,
      function (_data) {
        loger.log("推流地址测速->", _data);
        if (_data && _data.ip) {
          GlobalConfig.MS_PUBLISH_IP = _data.ip || "";
          GlobalConfig.MS_PUBLISH_PORT = _data.port || "";
        }
        _this.isGetFastestMsCallback = true;
        _this._startConnectMCU();
      });

    //录制回放HLS拉流地址测速
    this._getFastestIpFromServer(GlobalConfig.rsPullListFinal, function (_data) {
      loger.log("录制回放HLS拉流地址测速->", _data);
      if (_data && _data.ip) {
        GlobalConfig.RS_RECORD_PLAY_IP = _data.ip || "";
        GlobalConfig.RS_RECORD_PLAY_PORT = _data.port || "";
      }
      _this.isGetFastestRsCallback = true;
      _this._startConnectMCU();
    });

    //RTMP拉流地址测速
    this._getFastestIpFromServer(GlobalConfig.rtmpPullListFinal, function (_data) {
      loger.log("RTMP拉流地址测速->", _data);
      if (_data && _data.ip) {
        GlobalConfig.MS_PLAY_RTMP_IP = _data.ip || "";
        GlobalConfig.MS_PLAY_RTMP_PORT = _data.port || "";
      }
      _this.isGetFastestRtmpPullCallback = true;
      _this._startConnectMCU();
    });

    //HLS拉流地址测速
    this._getFastestIpFromServer(GlobalConfig.hlsPullListFinal, function (_data) {
      loger.log("HLS拉流地址测速->", _data);
      if (_data && _data.ip) {
        GlobalConfig.MS_PLAY_HLS_IP = _data.ip || "";
        GlobalConfig.MS_PLAY_HLS_PORT = _data.port || "";
      }
      _this.isGetFastestHlsPullCallback = true;
      _this._startConnectMCU();
    });

    //MCU测速
    this._getFastestMcuServer(function (_data) {
      loger.log("MCU测速->", _data);
      if (_data && _data.ip) {
        GlobalConfig.MCUServerIP = _data.ip || "";
        GlobalConfig.MCUServerPort = _data.port || "";
      }
      _this.isGetFastestMcuCallback = true;
      _this._startConnectMCU();
    });
  }

  //开始连接MCU(所有服务地址测试完成之后开始连接)
  _startConnectMCU() {
    if (this.isGetFastestMcuCallback &&
      this.isGetFastestMsCallback &&
      this.isGetFastestRtmpPullCallback &&
      this.isGetFastestHlsPullCallback &&
      this.isGetFastestRsCallback) {
      //mcu完成
      //ms推流测速完成
      //rtmp拉流测速完成
      //hls拉流测速完成
      //hls录制回放拉流测速完成
      this._joinMCU();
    }
  }

  //从Sever中选择的mcu、ms列表,如果server.json中存在就不使用Sass
  _choiceMcuAndMsListFromServer() {
    //1.根据user信息获取服务器列表
    if (_ipManager) {
      GlobalConfig.mcuListFinal = _ipManager.getServerListForUserInfo(
        "MCU",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.msListFinal = _ipManager.getServerListForUserInfo(
        "MS",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.rtmpPullListFinal = _ipManager.getServerListForUserInfo(
        "RTMP_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.rsPullListFinal = _ipManager.getServerListForUserInfo(
        "RS_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.hlsPullListFinal = _ipManager.getServerListForUserInfo(
        "HLS_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);
    }

    //拉流地址列表的特殊处理,
    // 1.如果RTMP拉流地址没有配置,就默认使用MS推流地址列表
    if (!GlobalConfig.rtmpPullListFinal || GlobalConfig.rtmpPullListFinal.length < 1) {
      GlobalConfig.rtmpPullListFinal = GlobalConfig.msListFinal;
    }
    // 2.如果HLS拉流地址没有配置,就默认使用录制回放RS拉流地址列表中的数据
    if (!GlobalConfig.hlsPullListFinal || GlobalConfig.hlsPullListFinal.length < 1) {
      GlobalConfig.hlsPullListFinal = GlobalConfig.rsPullListFinal;
    }

    if (!ServerConfig.localServerJson || !ServerConfig.localServerJson.MCU || !ServerConfig.localServerJson.MS) {
      loger.warn("课堂最终使用的服务列表->来自Sass");
    } else {
      loger.warn("课堂最终使用的服务列表->来自本地Server.json");
    }

    loger.warn(" MCU-List", GlobalConfig.mcuListFinal);
    loger.warn(" MS-List", GlobalConfig.msListFinal);
    loger.warn(" RTMP-List", GlobalConfig.rtmpPullListFinal);
    loger.warn(" HLS-List", GlobalConfig.hlsPullListFinal);
    loger.warn(" RS-List", GlobalConfig.rsPullListFinal);

    /* //使用webRtc不需要再使用MS列表中的数据---------
     GlobalConfig.msListFinal=[];//清空数据
     GlobalConfig.rtmpPullListFinal=[];
     //不是录制回放的时候hls的也清空
     if(!GlobalConfig.isRecordPlayBack){
     GlobalConfig.hlsPullListFinal=[];
     GlobalConfig.rsPullListFinal=[]
     }
     //-------------------------------------------*/

  }

  //从Sass中选择的mcu、ms列表
  _choiceMcuAndMsListFromSass() {
    //1.根据user信息获取服务器列表
    if (_ipManager) {
      GlobalConfig.mcuListFinal = _ipManager.getServerListForUserInfo(
        "MCU",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.msListFinal = _ipManager.getServerListForUserInfo(
        "MS",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.rtmpPullListFinal = _ipManager.getServerListForUserInfo(
        "RTMP_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.rsPullListFinal = _ipManager.getServerListForUserInfo(
        "RS_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);

      GlobalConfig.hlsPullListFinal = _ipManager.getServerListForUserInfo(
        "HLS_PULL",
        GlobalConfig.country,
        GlobalConfig.province,
        GlobalConfig.city,
        GlobalConfig.isp,
        ServerConfig.serverList);
    }

    //拉流地址列表的特殊处理,
    // 1.如果RTMP拉流地址没有配置,就默认使用MS推流地址列表
    if (!GlobalConfig.rtmpPullListFinal || GlobalConfig.rtmpPullListFinal.length < 1) {
      GlobalConfig.rtmpPullListFinal = GlobalConfig.msListFinal;
    }
    // 2.如果HLS拉流地址没有配置,就默认使用录制回放RS拉流地址列表中的数据
    if (!GlobalConfig.hlsPullListFinal || GlobalConfig.hlsPullListFinal.length < 1) {
      GlobalConfig.hlsPullListFinal = GlobalConfig.rsPullListFinal;
    }

    loger.warn("课堂最终使用的服务列表->来自Sass");
    loger.warn(" MCU-List", GlobalConfig.mcuListFinal);
    loger.warn(" MS-List", GlobalConfig.msListFinal);
    loger.warn(" RTMP-List", GlobalConfig.rtmpPullListFinal);
    loger.warn(" HLS-List", GlobalConfig.hlsPullListFinal);
    loger.warn(" RS-List", GlobalConfig.rsPullListFinal);

    /*
     //使用webRtc不需要再使用MS列表中的数据---------
     GlobalConfig.msListFinal=[];//清空数据
     GlobalConfig.rtmpPullListFinal=[];
     //不是录制回放的时候hls的也清空
     if(!GlobalConfig.isRecordPlayBack){
     GlobalConfig.hlsPullListFinal=[];
     GlobalConfig.rsPullListFinal=[]
     }
     //-------------------------------------------*/

  }


  //开始测速
  _getFastestIpFromServer(_dataArr, _callback) {
    if (_ipManager) {
      _ipManager.testFastestIpFromServer(_dataArr, _callback);
    } else {
      if (_callback) {
        _callback({'ip': ""});
      }
    }
  }

  //开始MCU选点操作
  _getFastestMcuServer(_callback) {
    //不再做MCU http测速
    if (_callback) {
      _callback(null);
    }
    return;

    //正常的测试流程
    if (_ipManager) {
      _ipManager.getFastestMcuServer(GlobalConfig.mcuListFinal, _callback);
    } else {
      if (_callback) {
        _callback({'ip': ""});
      }
    }
  }

  //开始MS选点操作
  _getFastestMsServer(_callback) {
    if (_ipManager) {
      _ipManager.getFastestMsServer(GlobalConfig.msListFinal, _callback);
    } else {
      if (_callback) {
        _callback({ip: ""});
      }
    }
  }

  //开始MS-PULL选点操作
  _getFastestRtmpPullServer(_callback) {
    if (_ipManager) {
      _ipManager.getFastestMsServer(GlobalConfig.rtmpPullListFinal, _callback);
    } else {
      if (_callback) {
        _callback({ip: ""});
      }
    }
  }

  //开始MS-HLS选点操作
  _getFastestHlsServer(_callback) {
    if (_ipManager) {
      _ipManager.getFastestMsServer(GlobalConfig.rtmpPullListFinal, _callback);
    } else {
      if (_callback) {
        _callback({ip: ""});
      }
    }
  }

  //保存课堂状态信息
  _sassSaveClassStatusInfo(_param) {
    if (!_mcu || !_mcu.connected) {
      loger.warn("不能保存课堂数据->MCU已经断开");
      return;
    }
    if (!_confer_ape) {
      return;
    }
    //{isForce:true}   isForce->是否强制提交(true为是)
    //这个是特殊权限
    let isForce = false;
    if (_param && _param.isForce == true) {
      isForce = true;
    }
    if (_confer_ape.checkHasRecordControl() || isForce) {
      //POST 保存数据
      clearTimeout(this.saveClassStatusTimer);
      this.saveClassStatusTimer = setTimeout(()=> {
        _sass.saveClassStatusInfo({"classStatusInfo": GlobalConfig.classStatusInfo}); //保存课堂状态信息
      }, 1600);
    } else {
      loger.log("没有保存课堂状态信息的权限->当前身份->" + GlobalConfig.userRole);
    }
  }

  //保存会态信息成功
  _sassSaveClassStatusInfoSuccessHandler(_data) {
    //loger.log('Saas保存课堂状态信息成功');
  }

  _sassSaveClassRecordInfoSuccessHandler(_data) {
    //loger.log('Saas保存课堂录制信息成功',_data);
  }

  //Sass校验流程结束之后,开始加入MCU
  _joinMCU() {
    loger.warn('=====================STEP8=======================');
    loger.log('加入课堂->.');
    loger.warn("最终使用课堂服务信息->");
    loger.warn('MCU服务器地址->mcu->', GlobalConfig.MCUServerIP, GlobalConfig.MCUServerPort);
    loger.warn('推流地址->MS->', GlobalConfig.MS_PUBLISH_IP, GlobalConfig.MS_PUBLISH_PORT);
    loger.warn('RTMP拉流地址->RTMP->', GlobalConfig.MS_PLAY_RTMP_IP, GlobalConfig.MS_PLAY_RTMP_PORT);
    loger.warn('HLS拉流地址->HLS->', GlobalConfig.MS_PLAY_HLS_IP, GlobalConfig.MS_PLAY_HLS_PORT);
    loger.warn('HLS录制回放拉流地址->HLS->', GlobalConfig.RS_RECORD_PLAY_IP, GlobalConfig.RS_RECORD_PLAY_PORT);

    if (!GlobalConfig.MS_PUBLISH_IP) {
      loger.error("推流MS地址地址无效");
    }
    if (!GlobalConfig.MS_PLAY_RTMP_IP) {
      loger.warn("RTMP拉流地址无效->使用推流地址作为RTMP拉流地址");
      GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.MS_PUBLISH_IP;
      GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.MS_PUBLISH_PORT
    }

    if (!GlobalConfig.MS_PLAY_HLS_IP) {
      loger.warn("HLS拉流地址无效->使用HLS回放地址作为HLS拉流地址");
      GlobalConfig.MS_PLAY_HLS_IP = GlobalConfig.MS_PLAY_HLS_PORT;
      GlobalConfig.RS_RECORD_PLAY_IP = GlobalConfig.RS_RECORD_PLAY_PORT
    }

    if (_mcu) {
      _mcu.joinMCU(GlobalConfig.getClassInfo());
    }
  }

  _switchMcuIp() {
    loger.log('切换MCU IP->.');
    if (_mcu) {
      _mcu.switchMCUIp(GlobalConfig.getClassInfo());
    }
  }

  // MCU 课堂成功
  _mcuJoinMCUClassSuccessHandler(_data) {
    //loger.log('MCU 课堂成功.');
    loger.warn('=====================STEP9=======================');
    //console.log("当前课堂人员列表",GlobalConfig.rosterNum,GlobalConfig.rosters)
    GlobalConfig.setCurrentStatus(GlobalConfig.statusCode_2);
    GlobalConfig.classJoinSuccess = true;

    GlobalConfig.screenWidth = window.screen.width;
    GlobalConfig.screenHeight = window.screen.height;

    GlobalConfig.channelId = "" + GlobalConfig.siteId + "_" + GlobalConfig.classId;
    GlobalConfig.userUid = GlobalConfig.nodeId;
    GlobalConfig.channelKey = "";
    GlobalConfig.rosters = {};//情况人员数据列表

    //判断是否需要获取加入音视频通话频道的channelKey
    if (GlobalConfig.appCertificate) {
      //loger.log("加入视频通话模块->需要先获取channelKey")
      //获取channelKey
      _sass.getChannelKeyToken((_data)=> {
        //{"code":200,"channelKey":"005AQAoAEQzQUQxNzFDOEQwOEU3OTVGMjlCMzZDRUZENTNGOTU0RDY4N0ZGMUEQANylukzO70ocgrNX9hlkNNWvpLBZ9buDAy/fuVkAAA==","uid":"751373669"}
        if (_data && _data.channelKey) {
          GlobalConfig.channelKey = _data.channelKey || "";
        }
        this._joinClassSuccessSeting();
      })
    } else {
      //loger.log("加入视频通话模块->不需要获取channelKey")
      this._joinClassSuccessSeting();
    }
  }

  //加入课堂成功之后设置本地数据和返回数据给客户端
  _joinClassSuccessSeting() {
    //返回给客户端初始化成功的数据
    let joinClassSuccessCallBackData = {};
    joinClassSuccessCallBackData.isRecordPlayBack = GlobalConfig.isRecordPlayBack;
    joinClassSuccessCallBackData.DOCServerIP = GlobalConfig.DOCServerIP;
    joinClassSuccessCallBackData.DOCServerPort = GlobalConfig.DOCServerPort;

    joinClassSuccessCallBackData.classStatus = GlobalConfig.classStatus;
    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
    joinClassSuccessCallBackData.className = GlobalConfig.className;
    joinClassSuccessCallBackData.h5Module = GlobalConfig.h5Module;
    joinClassSuccessCallBackData.isHost = GlobalConfig.isHost;
    joinClassSuccessCallBackData.maxAudioChannels = GlobalConfig.maxAudioChannels;
    joinClassSuccessCallBackData.maxVideoChannels = GlobalConfig.maxVideoChannels;
    joinClassSuccessCallBackData.maxMediaChannels = GlobalConfig.maxMediaChannels;
    joinClassSuccessCallBackData.mcuDelay = GlobalConfig.mcuDelay;

    joinClassSuccessCallBackData.msType = GlobalConfig.msType;
    joinClassSuccessCallBackData.nodeId = GlobalConfig.nodeId;
    joinClassSuccessCallBackData.password = GlobalConfig.password;
    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired; //  老师的默认是true
    joinClassSuccessCallBackData.joinTimestamp = GlobalConfig.joinTimestamp;

    //GlobalConfig.passwordRequired  老师的默认是true
    //GlobalConfig.portal=_data.portal;
    joinClassSuccessCallBackData.role = GlobalConfig.role;
    joinClassSuccessCallBackData.topNodeID = GlobalConfig.topNodeID;
    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
    joinClassSuccessCallBackData.userName = GlobalConfig.userName;
    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
    joinClassSuccessCallBackData.userType = GlobalConfig.userType;

    joinClassSuccessCallBackData.siteId = GlobalConfig.siteId;
    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;
    joinClassSuccessCallBackData.classType = GlobalConfig.classType || ApeConsts.CLASS_TYPE_1v1;

    joinClassSuccessCallBackData.country = GlobalConfig.country; //国家
    joinClassSuccessCallBackData.city = GlobalConfig.city; //城市
    joinClassSuccessCallBackData.province = GlobalConfig.province; //服务商
    joinClassSuccessCallBackData.isp = GlobalConfig.isp; //服务商

    joinClassSuccessCallBackData.classTimestamp = GlobalConfig.classTimestamp; //课堂进行的累积时间
    joinClassSuccessCallBackData.recordTimestamp = GlobalConfig.recordTimestamp; //课堂录制的累积时间
    joinClassSuccessCallBackData.recordPlaybackMaxTime = GlobalConfig.recordPlaybackMaxTime; //录制回放的总时间

    joinClassSuccessCallBackData.fps = GlobalConfig.fps;
    joinClassSuccessCallBackData.gop = GlobalConfig.gop;
    joinClassSuccessCallBackData.videoQuality = GlobalConfig.videoQuality;

    joinClassSuccessCallBackData.ssTunnelAppURL = GlobalConfig.ssTunnelAppURL;
    joinClassSuccessCallBackData.currentSceneTableId = GlobalConfig.currentSceneTableId; //文档区域的模块显示
    joinClassSuccessCallBackData.serverAndLoacTimeDistanc = GlobalConfig.serverAndLoacTimeDistanc;

    joinClassSuccessCallBackData.deviceType = GlobalConfig.deviceType;
    joinClassSuccessCallBackData.language = GlobalConfig.language;
    joinClassSuccessCallBackData.explorer = GlobalConfig.explorer;
    joinClassSuccessCallBackData.explorerVersion = GlobalConfig.explorerVersion;
    joinClassSuccessCallBackData.os = GlobalConfig.os;

    joinClassSuccessCallBackData.channelId = GlobalConfig.channelId;
    joinClassSuccessCallBackData.channelKey = GlobalConfig.channelKey;
    joinClassSuccessCallBackData.userUid = GlobalConfig.userUid;
    joinClassSuccessCallBackData.appId = GlobalConfig.appId;
    joinClassSuccessCallBackData.appCertificate = GlobalConfig.appCertificate;
    joinClassSuccessCallBackData.appRecordingKey = GlobalConfig.appRecordingKey;

    //设置日志上报所需的信息
    LogManager.serverAndLoacTimeDistanc = GlobalConfig.serverAndLoacTimeDistanc;//本地时间和服务器时间的差值(秒)
    LogManager.classId = GlobalConfig.classId;//课堂号
    LogManager.userId = GlobalConfig.userId;//userId
    LogManager.nodeId = GlobalConfig.nodeId;//nodeId
    LogManager.userRole = GlobalConfig.userRole;//userRole
    LogManager.userName = GlobalConfig.userName;//用户名称
    LogManager.logUrl = GlobalConfig.logUrl;//日志服务器地址 //http://log.3mang.com
    LogManager.platform = GlobalConfig.platform;
    loger.log('加入课堂成功->classType:'+GlobalConfig.classType);
    loger.log(joinClassSuccessCallBackData);

    //加入课堂成功,广播消息
    this._emit(MessageTypes.CLASS_JOIN_SUCCESS, joinClassSuccessCallBackData);

    //主讲人和老师可以设置旁录
    if (GlobalConfig.appId && !GlobalConfig.openFlash&&GlobalConfig.deviceType!=GlobalConfig.deviceH5) {
      //加入之前先设置旁录地址,老师和主讲人开启旁路
      if (_webRtc && GlobalConfig.isTeachOrAssistant) {
        let curTimestamp = new Date().getTime();
        let streamId = GlobalConfig.siteId + "_" + GlobalConfig.classId + "_" + GlobalConfig.userId + "_" + curTimestamp;
        //传入固定的流Id
        let publishData = this._getVideoPublishPath({streamId: streamId});
        loger.log("加入之前先设置旁录地址", publishData);
        if (publishData && publishData.code == 0) {
          _webRtc.setConfigPublisherUrl(publishData.publishUrl);
          let m3u8Stream = _video_ape.getPlayVideoPath({"type": "m3u8", "streamId": streamId});
          let rtmpStream = _video_ape.getPlayVideoPath({"type": "rtmp", "streamId": streamId});
          _webRtc.setRtmpM3u8Path({m3u8Url: m3u8Stream.playUrl, rtmpUrl: rtmpStream.playUrl});
        }
      }
      setTimeout(()=> {
        //加入音视频通话模块,延迟一秒处理,因为视频需要根据用户列表信息来判断放的位置,太早的话用户列表没有数据
        this._joinChannel({
          channelId: GlobalConfig.channelId,
          channelKey: GlobalConfig.channelKey,
          uid: GlobalConfig.userUid,
          info: "" + GlobalConfig.userRole,
          immediatePublish:false
        });
      }, 1600);
    }
  }

  //MCU已经达到最大重连次数,不在继续重连,需要退出课堂
  _socketMaxReconnectFailed(_param){
    //执行离开课堂的逻辑
    this._runClassExit();
  }
  //切换MCU ->_param->{reConnect:false} //reConnect(是否立即替换当前的ip并且重新连接)
  _switchMcuIpHandler(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      loger.log('录制回放->不进行MCU动态选点');
      return;
    }
    loger.log('MCU->动态选点');
    let _this = this;
    this._getFastestMcuServer(function (_data) {
      loger.log("MCU选点结束->", _data);
      //记录当前的IP地址,选点结束后需要判断一下是否是新的IP;
      let oldIp = GlobalConfig.MCUServerIP;

      if (_data && _data.ip) {
        GlobalConfig.MCUServerIP = _data.ip || "";
        GlobalConfig.MCUServerPort = _data.port || "";
      } else {
        if (GlobalConfig.mcuListFinal && GlobalConfig.mcuListFinal.length > 0) {
          //如果当前没有设置过mcu的ip和端口随机选择一个
          if (!GlobalConfig.MCUServerIP || GlobalConfig.mcuListFinal.length == 1) {
            let index = parseInt(Math.random() * GlobalConfig.mcuListFinal.length);
            GlobalConfig.MCUServerIP = GlobalConfig.mcuListFinal[index].ip || "";
            GlobalConfig.MCUServerPort = GlobalConfig.mcuListFinal[index].port || "";
          } else {
            //当前mcu已经有值,需要选择一个新的
            for (let i = 0; i < GlobalConfig.mcuListFinal.length; i++) {
              if (GlobalConfig.MCUServerIP == GlobalConfig.mcuListFinal[i].ip) {
                //获取下一个MCU
                let nextMcu = GlobalConfig.mcuListFinal[i + 1];
                if (!nextMcu) {
                  //如果下一个mcu不存在就使用第一个
                  nextMcu = GlobalConfig.mcuListFinal[0];
                }
                if (nextMcu) {
                  GlobalConfig.MCUServerIP = nextMcu.ip || "";
                  GlobalConfig.MCUServerPort = nextMcu.port || "";
                }
                break;
              }
            }
          }
        }
      }
      if (oldIp && oldIp != GlobalConfig.MCUServerIP) {
        loger.log('MCU->最新地址->', GlobalConfig.MCUServerIP, GlobalConfig.MCUServerPort);
        //判断是否需要主动断开当前的连接然后重连新的服务器
        if (_param && _param.reConnect == true) {
          loger.log('MCU->切换到最新的IP->', GlobalConfig.MCUServerIP, GlobalConfig.MCUServerPort);
          _this._startConnectMCU();
        } else {
          //不需要断开当前的连接,更改ip即可
          _this._switchMcuIp();
        }
      } else {
        //如果选点结束后获得的ip和当前的IP相同,不需要切换
        loger.log('MCU不需要切换->之前的IP->', oldIp, "新的IP->", GlobalConfig.MCUServerIP);
      }
    });
  }

  //课堂状态发生改变,需要停止当前的所有推流
  _stopAllMediaPublishHandler(_data) {
    loger.log('课堂状态发生改变,需要停止当前的所有推流');
    this._emit(MessageTypes.MEDIA_STOP_PUBLISH);
  }

  //用更状态数据发送变更
  _onRosterUpdateHandler(_data) {
    //数据无效/ios/android  不处理数据
    if (!_data || GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    let nodeData = _data.nodeData;
    //数据用户是pc或H5不处理
    if (!nodeData || nodeData.deviceType == GlobalConfig.devicePC || nodeData.deviceType == GlobalConfig.deviceH5) {
      return;
    }
    if (nodeData.openCamera > 0 && _webRtc) {
      loger.log("收到移动端用户数据更新,当前是开启摄像头状态,需要尝试添加一个远程视频");
      _webRtc.tryAddMobileStream(_data.nodeId);
    }
  }
  /*
  * 收到媒体禁用状态切换的消息
  * */
  _receiveMeiaEnabledChange(_data){
    if(!_data||GlobalConfig.isRecordPlayBack||GlobalConfig.isH5){
      return;
    }
    if(_webRtc){
      _webRtc.receiveWebRtcMeiaEnabledChange(_data);
    }
  }
  //手动切换MS -> {ip;"xxx.xx.xx","port":"xxxx"}
  _switchMediaServer(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      //录制回放不做操作
      loger.warn('录制回放->不能手动切换MS');
      return;
    }
    if (_param && _param.ip) {
      GlobalConfig.MS_PUBLISH_IP = _param.ip || "";
      GlobalConfig.MS_PUBLISH_PORT = _param.port || "";
    }
    GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.MS_PUBLISH_IP;
    GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.MS_PUBLISH_PORT;
    loger.warn('手动切换MS->', GlobalConfig.MS_PUBLISH_IP + ":" + GlobalConfig.MS_PUBLISH_PORT);
    loger.warn('手动切换RTMP->', GlobalConfig.MS_PLAY_RTMP_IP + ":" + GlobalConfig.MS_PLAY_RTMP_PORT);
    //更换完用户当前的MS地址,需要更新用户数据
    if (_confer_ape) {
      _confer_ape.updateUserInfo();
    }

    //音视频模块对当前正在播放的流进行更换MS
    if (_video_ape) {
      _video_ape.changeMediaMs();
    }
    if (_audio_ape) {
      _audio_ape.changeMediaMs();
    }
  }

  //切换MS ->_param->{reConnect:false} //reConnect(是否立即替换当前的ip并且重新连接)
  _switchMsIpHandler(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      //录制回放不做操作
      loger.warn('录制回放->不进行MS动态选点');
      return;
    }
    let _this = this;
    this._getFastestMsServer(function (_data) {
      loger.log("MS选点结束->", _data);
      //记录当前的IP地址,选点结束后需要判断一下是否是新的IP;
      let oldIp = GlobalConfig.MS_PUBLISH_IP;

      if (_data && _data.ip) {

        GlobalConfig.MS_PUBLISH_IP = _data.ip || "";
        GlobalConfig.MS_PUBLISH_PORT = _data.port || "";
      } else {
        //随机选择一个
        if (GlobalConfig.msListFinal && GlobalConfig.msListFinal.length > 0) {
          let index = parseInt(Math.random() * GlobalConfig.msListFinal.length);
          GlobalConfig.MS_PUBLISH_IP = GlobalConfig.msListFinal[index].ip || "";
          GlobalConfig.MS_PUBLISH_PORT = GlobalConfig.msListFinal[index].port || "";
        }
      }

      if (oldIp && oldIp != GlobalConfig.MS_PUBLISH_IP) {
        //选点完成需要更新用户数据
        if (_confer_ape) {
          _confer_ape.updateUserInfo();
        }
        loger.log('MS->最新地址->', GlobalConfig.MS_PUBLISH_IP, GlobalConfig.MS_PUBLISH_PORT);
      } else {
        //如果选点结束后获得的ip和当前的IP相同,不需要切换
        loger.log('MS不需要切换->IP', GlobalConfig.MS_PUBLISH_IP);
      }
    });
  }

  //切换MS -PULL地址
  _switchRtmpPullIpHandler(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      //录制回放不做操作
      loger.warn('录制回放->不进行MS-PULL动态选点');
      return;
    }
    if (!GlobalConfig.rtmpPullListFinal || GlobalConfig.rtmpPullListFinal.length < 1) {
      return;
    }
    let _this = this;
    this._getFastestRtmpPullServer(function (_data) {
      loger.log("MS->PULL->选点结束->", _data);
      if (_data && _data.ip) {
        GlobalConfig.MS_PLAY_RTMP_IP = _data.ip || "";
        GlobalConfig.MS_PLAY_RTMP_PORT = _data.port || "";
      } else {
        //随机选择一个
        if (GlobalConfig.rtmpPullListFinal && GlobalConfig.rtmpPullListFinal.length > 0) {
          let index = parseInt(Math.random() * GlobalConfig.rtmpPullListFinal.length);
          GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.rtmpPullListFinal[index].ip || "";
          GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.rtmpPullListFinal[index].port || "";
        }
      }
      //如果RTMP没有配置地址,那么还是使用推流的地址
      if (!GlobalConfig.MS_PLAY_RTMP_IP) {
        GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.MS_PUBLISH_IP;
        GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.MS_PUBLISH_PORT;
      }
    });
  }

  //切换MS HLS地址
  _switchHlsIpHandler(_param) {
    if (GlobalConfig.isRecordPlayBack) {
      //录制回放不做操作
      loger.warn('录制回放->不进行MS-HLS动态选点');
      return;
    }
    if (!GlobalConfig.hlsPullListFinal || GlobalConfig.hlsPullListFinal.length < 1) {
      return;
    }
    let _this = this;
    this._getFastestHlsServer(function (_data) {
      loger.log("HLS选点结束->", _data);
      if (_data && _data.ip) {
        GlobalConfig.MS_PLAY_HLS_IP = _data.ip || "";
        GlobalConfig.MS_PLAY_HLS_PORT = _data.port || "";
      } else {
        //随机选择一个
        if (GlobalConfig.hlsPullListFinal && GlobalConfig.hlsPullListFinal.length > 0) {
          let index = parseInt(Math.random() * GlobalConfig.hlsPullListFinal.length);
          GlobalConfig.MS_PLAY_HLS_IP = GlobalConfig.hlsPullListFinal[index].ip || "";
          GlobalConfig.MS_PLAY_HLS_PORT = GlobalConfig.hlsPullListFinal[index].port || "";
        }
      }
      //如果HLS没有配置地址,那么还是使用推流的地址
      if (!GlobalConfig.MS_PLAY_HLS_IP) {
        GlobalConfig.MS_PLAY_HLS_IP = GlobalConfig.RS_RECORD_PLAY_IP;
        GlobalConfig.MS_PLAY_HLS_PORT = GlobalConfig.RS_RECORD_PLAY_PORT;
      }
    });
  }

  //先通过Sass删除文档数据,删除成功之后才能删除MCU的
  _sassDeleteDocument(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }

    //判断传入的参数是否存在
    if (_param == null || EngineUtils.isEmptyObject(_param)) {
      loger.error('删除文档失败->参数错误', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_DOC_DELETE_FAILED_PARAM);
      return;
    }
    //判断必要的参数字段值
    if (_param.itemIdx == null || isNaN(_param.itemIdx) || _param.docId == null || _param.docId == "") {
      loger.error('删除文档失败->', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_DOC_DELETE_FAILED_PARAM);
      return;
    }
    loger.log('删除文档->', _param);

    if (_sass) {
      _sass.sassDeleteDocument(_param);
    }
  }

  _sendDocBroadcastMsg(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.sendDocBroadcastMsg(_param);
    }
  }

  //Sass删除文档成功之后,同步删除MCU数据
  _sassDeleteDocumentSuccess(_param) {
    loger.log('删除文档成功->', _param);
    this._sendDocumentDelete(_param);
  }

  _sassDeleteMediaShareSuccess(_param) {
    loger.log('删除媒体文件成功->', _param);
    this._sendMediaSharedDelete(_param);
  }

  _sassDeleteMusicShareSuccess(_param) {
    loger.log('删除Music文件成功->', _param);
    this._sendMusicSharedDelete(_param);
  }

  //ConferApe
  //开始上课
  _sendStartClass(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (GlobalConfig.isRecordPlayBack) {
      return;
    }
    if (_confer_ape) {
      //开始上课
      _confer_ape.startClass(_param);
      ////开始录制
    }
  }

  //ConferApe
  //全局禁言
  _silenceClass(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }

    if (_confer_ape) {
      _confer_ape.silenceClass(_param);
    }
  }

  //控制课堂全局是否可绘制的状态
  _changeDrawStatus(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_confer_ape) {
      _confer_ape.changeDrawStatus(_param);
    }
  }

  //控制课堂全局是否可送礼物的状态
  _changeGiftStatus(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_confer_ape) {
      _confer_ape.changeGiftStatus(_param);
    }
  }

  //暂停上课
  _sendPauseClass(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_confer_ape) {
      _confer_ape.pauseClass(_param);
    }
  }

  //更新设备信息
  _updateDeviceInfo(_param) {
    if (!_mcu.connected) {
      loger.warn('更新设备信息->失败', GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (!GlobalConfig.classJoinSuccess) {
      loger.warn('更新设备信息->失败->还没有加入课堂成功', GlobalConfig.getCurrentStatus());
    }
    if (_confer_ape) {
      _confer_ape.updateDeviceInfo(_param);
    }
  }

  //文档-媒体共享-屏幕共享模块切换
  _sceneTableChange(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.sceneTableChange(_param);
    }
  }

  //通过第三方消息通道发送消息
  _sendThridChannelMessage(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.onThirdReciveParentMessage(_param);
    }
  }

  //将指定nodeId的人踢出课堂
  _kickOutRosterFormNodeId(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.kickOutRosterFormNodeId(_param);
    }
  }

  // 禁言控制
  _controlSilenceStatus(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.controlSilenceStatus(_param);
    }
  }

  //举手状态控制
  _controlHandUpStatus(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.controlHandUpStatus(_param);
    }
  }

  //举手状态切换
  _changeHandUpStatus(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.changeHandUpStatus(_param);
    }
  }

  /*
   * 控制画笔使用状态
   * */
  _controlDrawStatus(_param) {
    //{nodeId:333333,isDisEnableDraw:true}
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.controlDrawStatus(_param);
    }
  }

  //停止上课
  _sendCloseClass(_param) {
    if (!_mcu || !_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if(GlobalConfig.classExit==true){
      console.warn("停止上课->已经离开课堂");
      return;
    }
    if (_confer_ape) {
      _confer_ape.closeClass(_param);
    }
    GlobalConfig.classExit=true;
    LogManager.IS_OPEN_SEND_LOG = false;//断开之后不再上报日志
  }

  // 离开课堂 {type:1}   type=1被踢出课堂;type=0自己主动离开
  _leaveClass(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if( GlobalConfig.classExit==true){
      console.log("已经离开课堂",_param);
      return;
    }
    //停止推流
    if (_video_ape) {
      _video_ape.stopPublishVideo();
      _video_ape.stopPublishScreenShare();
    }
    if (_audio_ape) {
      _audio_ape.stopPublishAudio();
    }
    //离开课堂
    if (_confer_ape) {
      //_confer_ape.stopRecord();//不主动调用停止录制
      _confer_ape.leaveClass();
    }

    let callBack = {};
    if (_param && _param.type) {
      callBack = _param;
    } else {
      callBack.type = 0;
    }
    loger.warn('离开课堂->', MessageTypes.CLASS_EXIT, callBack);
    LogManager.sendLogToServer();
    this._emit(MessageTypes.CLASS_EXIT, callBack);

    //断开MCU连接
    if (_mcu) {
      _mcu.leaveMCU();
      GlobalConfig.setCurrentStatus(GlobalConfig.statusCode_3);
    }
    //离开视频通话模块
    this._leaveChannel();
  }

  //获取课堂所有参数(20170727新规则) api/meeting/detail.do? flash中的接口文件是 getClassParam.do
  _sassGetClassParamSuccessHandler(_data) {
    loger.log('获取课堂课堂信息完成.', _data.appConfig);
    //包含整个课堂最全的信息,储存数据
    if (_data) {
      //老师\助教默认启用画笔功能,其他身份默认禁用画笔功能
      if (GlobalConfig.userRole == ApeConsts.host ||
        GlobalConfig.userRole == ApeConsts.assistant ||
        GlobalConfig.userRole == ApeConsts.presenter) {
        GlobalConfig.selfDisEnableDrawTime = 0;
      }

      GlobalConfig.mcuDelay = _data.h5Delay || 0; //mcu消息延迟的时间间隔,单位(秒),结合客户端传的messageDelay的值使用
      GlobalConfig.className = _data.meetingName || "";
      GlobalConfig.classBeginTime = _data.beginTime || "";
      GlobalConfig.classEndTime = _data.endTime || "";
      GlobalConfig.siteId = _data.siteID || "";//这个字段ID是大写的
      GlobalConfig.channelId = "" + GlobalConfig.siteId + "_" + GlobalConfig.classId;
      //sdk获取ip失败就使用saas返回的
      if (!GlobalConfig.userIp) {
        GlobalConfig.userIp = _data.userIp || "";
        loger.warn("使用从Sass返回的userIp", GlobalConfig.userIp);
      } else {
        loger.warn("使用SDK获取的userIp", GlobalConfig.userIp);
      }


      GlobalConfig.maxVideoChannels = _data.maxVideoChannels;
      GlobalConfig.maxAudioChannels = _data.maxAudioChannels;
      GlobalConfig.maxMediaChannels = Math.max(GlobalConfig.maxVideoChannels, GlobalConfig.maxAudioChannels);

      loger.warn("当前课堂允许最大推流数量-" + GlobalConfig.maxMediaChannels + " 视频频:" + GlobalConfig.maxVideoChannels + " 音频:" + GlobalConfig.maxAudioChannels);
      GlobalConfig.ssTunnelAppURL = _data.ssTunnelAppURL || ''; //屏幕共享插件的地址

      //视频质量相关设置,每次加入课堂都按最新的获取设置
      GlobalConfig.fps = _data.fps || 15;
      GlobalConfig.gop = _data.gop || 3;
      GlobalConfig.videoQuality = parseInt(_data.videoQuality);
      GlobalConfig.curVideoQuality = GlobalConfig.videoQuality;

      //是否自动开始(身份是host的时候才用到的)
      GlobalConfig.isAutoStartClass = _data.autoRecord || 0;
      GlobalConfig.logUrl = _data.logUrl || "";
      GlobalConfig.logUrl = GlobalConfig.logUrl.replace("https://", "");
      GlobalConfig.logUrl = GlobalConfig.logUrl.replace("http://", "");
      GlobalConfig.logUrl = GlobalConfig.locationProtocol + GlobalConfig.logUrl;

      GlobalConfig.serverTime = _data.serverTime || new Date().getTime(); //获取服务器时间戳
      GlobalConfig.serverAndLoacTimeDistanc = (new Date().getTime() - GlobalConfig.serverTime) / 1000; //当前系统时间和服务器时间的差值 (秒)
      loger.warn("服务器时间:" + GlobalConfig.serverTime + "  系统时间:" + new Date().getTime() + "  时间差:" + GlobalConfig.serverAndLoacTimeDistanc);

      GlobalConfig.setDocListPrepare(_data.docListPrepare); //提前上传的文档列表
      GlobalConfig.setRecordList(_data.recordList); //录制回放文件地址
      GlobalConfig.setDocList(_data.docList); //文档地址
      GlobalConfig.setMusicList(_data.musicList); //
      GlobalConfig.setMusicListPrepare(_data.musicListPrepare); //提前上传的声音文件列表
      GlobalConfig.setVideoCDNAddr(_data.videoCDNAddr); //cdn加速的拉流地址,直播的时候才使用
      GlobalConfig.setMediaShareList(_data.sharedMediaList); //提前上传的媒体共享文件列表

      /*
      //设置白板文档,固定ID
      let whiteBoradData = {
        itemIdx: GlobalConfig.whiteboardId,//指定的白板文档ID
        name: "白板.pdf",
        creatUserId: 0,
        md5: "b153313f6f390328a30db5389b6cee53",
        pageNum: 30,
        docId: "b153313f6f390328a30db5389b6cee53",
        url: "http://pclive.xuedianyun.com/DocSharing/data/whiteboard/default/whiteboard.pdf",
        dynamicTransferStatic: "0",
        relativeUrl: "/DocSharing/data/whiteboard/default/whiteboard.pdf",
        fileType: "pdf",
        type:"pdf"
      }
       GlobalConfig.docListPrepare.push(whiteBoradData);
      */
      if(GlobalConfig.whiteboardForSiteId&&Object.keys(GlobalConfig.whiteboardForSiteId).length>1){
        GlobalConfig.docListPrepare.push(GlobalConfig.whiteboardForSiteId);
      }else {
        loger.log("站点没有配置白板文档->使用默认白板");
        GlobalConfig.docListPrepare.push(GlobalConfig.whiteboardDefault);
      }

      let appConfigStr = _data.appConfig;
      appConfigStr = _base64.decode(appConfigStr);
      let appConfig = {};
      try {
        appConfig = JSON.parse(appConfigStr);
        //储存app相关信息
        this._setAppConfig(appConfig);
      } catch (err) {
        loger.warn("appConfig->解析失败", appConfigStr);
      }


      //文档服务器地址
      if (GlobalConfig.docList && GlobalConfig.docList.length > 0) {
        //doc上传地址,随机获取一个
        let index = parseInt(Math.random() * GlobalConfig.docList.length);
        GlobalConfig.DOCServerIP = GlobalConfig.docList[index].ip || "";
        GlobalConfig.DOCServerPort = GlobalConfig.docList[index].port || "";

        if (GlobalConfig.isHttps) {
          //https的时候替换所有80端口
          GlobalConfig.DOCServerPort = GlobalConfig.replacePort(GlobalConfig.DOCServerPort, "80", "");
        }

      }
      //录制回放文件的下载地址
      if (GlobalConfig.recordList && GlobalConfig.recordList.length > 0) {
        let index = parseInt(Math.random() * GlobalConfig.recordList.length);
        GlobalConfig.RecordServerIP = GlobalConfig.recordList[index].ip || "";
        GlobalConfig.RecordServerPort = GlobalConfig.recordList[index].port || "";

        if (GlobalConfig.isHttps) {
          //https的时候替换所有80端口
          GlobalConfig.RecordServerPort = GlobalConfig.replacePort(GlobalConfig.RecordServerPort, "80", "");
        }
      }
      loger.warn('默认->文档服务器地址->.', GlobalConfig.DOCServerIP, GlobalConfig.DOCServerPort);
      loger.warn('默认->录制回放文件下载地址->.', GlobalConfig.RecordServerIP, GlobalConfig.RecordServerPort);


      //存从Sass获取的MS和MCU服务列表
      let serverJsonStr = _data.serverJson;
      try {
        ServerConfig.sassServerJson = JSON.parse(serverJsonStr);///Sass返回的Server数据
      } catch (err) {
        loger.error("从SASS获取的SERVER数据解析失败", err.message);
      }
    }

    //存储Sass数据到本地
    if (_data.currentInfo) {
      //根据从Sass获取的数据信息,同步最后一次保存的课堂状态信息
      loger.log("从Saas返回的课堂状态信息数据", _data.currentInfo);
      try {
        let dataObj = JSON.parse(_data.currentInfo);
        dataObj.recordStatus = false;
        GlobalConfig.setClassStatusInfo(dataObj);
        this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
      } catch (err) {
        loger.warn("从Sass获取的课堂数据JSON转换失败->");
        GlobalConfig.setClassStatusInfo(_data.currentInfo);
      }
      loger.log(GlobalConfig.classStatusInfo);
      //课堂获取Sass数据完成
      this._emit(MessageTypes.CLASS_GET_INFO_SUCCESS, GlobalConfig.getClassInfo());

      //课堂数据获取完成->进入课堂或进入录制回放
      //录制回放不需要获取ip信息和选点
      if (GlobalConfig.isRecordPlayBack) {
        if (_recordPlayback) {
          //开启录制回放流程
          loger.warn("开启录制回放流程");
          //根据用户的userIp信息从sever.json和Sass中选择最终mcu和推流拉流数据列表
          ServerConfig.serverList = ServerConfig.sassServerJson;
          this._choiceMcuAndMsListFromSass();
          //获取MCU和MS 推流拉流、录制回放的默认地址
          this.getMcuAndMsDefaultServerIp();
          _recordPlayback.readyLoadRecordPlayData();
        } else {
          loger.warn("开启录制回放流程失败->还未创建模块");
        }
      } else {
        //初始化音视频通话sdk
        if (GlobalConfig.appId && !GlobalConfig.openFlash) {
          loger.log("使用webRtc通话模式");
          //加入webRtc
          this._initWebRtcSdk({
            appId: GlobalConfig.appId
          }, ()=> {
            //音视频通话SDK初始化完成之后,根据用户的userIp获取信息,获取服务列表选点,选点测速完成后才加入MCU
            this.loadServerJsonAndgetUserIpInfo();
          });
        } else {
          //加入flash
          loger.log("使用flash通话模式");
          this.loadServerJsonAndgetUserIpInfo();
        }
      }
    } else {
      loger.warn("从Sass获取的课堂数据currentInfo无效->再次获取");
      //如果获取的数据中没有课堂保存的状态数据,需要单独的接口获取一次
      _sass.getClassRecordInfo((_currentInfo)=> {
        if(_currentInfo){
          try {
            let dataObj = JSON.parse(_currentInfo);
            dataObj.recordStatus = false;
            GlobalConfig.setClassStatusInfo(dataObj);
            this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
            loger.log(dataObj);
          } catch (err) {
            loger.warn("getClassRecordInfo获取的课堂数据JSON转换失败->");
          }
        }
        //课堂获取Sass数据完成
        this._emit(MessageTypes.CLASS_GET_INFO_SUCCESS, GlobalConfig.getClassInfo());
        //课堂数据获取完成->进入课堂或进入录制回放
        //录制回放不需要获取ip信息和选点
        if (GlobalConfig.isRecordPlayBack) {
          if (_recordPlayback) {
            //开启录制回放流程
            loger.warn("开启录制回放流程");
            //根据用户的userIp信息从sever.json和Sass中选择最终mcu和推流拉流数据列表
            ServerConfig.serverList = ServerConfig.sassServerJson;
            this._choiceMcuAndMsListFromSass();
            //获取MCU和MS 推流拉流、录制回放的默认地址
            this.getMcuAndMsDefaultServerIp();
            _recordPlayback.readyLoadRecordPlayData();
          } else {
            loger.warn("开启录制回放流程失败->还未创建模块");
          }
        } else {
          //初始化音视频通话sdk
          if (GlobalConfig.appId && !GlobalConfig.openFlash) {
            loger.log("使用webRtc通话模式");
            //加入webRtc
            this._initWebRtcSdk({
              appId: GlobalConfig.appId
            }, ()=> {
              //音视频通话SDK初始化完成之后,根据用户的userIp获取信息,获取服务列表选点,选点测速完成后才加入MCU
              this.loadServerJsonAndgetUserIpInfo();
            });
          } else {
            //加入flash
            loger.log("使用flash通话模式");
            this.loadServerJsonAndgetUserIpInfo();
          }
        }
      })
    }

 /*   //课堂获取Sass数据完成
    this._emit(MessageTypes.CLASS_GET_INFO_SUCCESS, GlobalConfig.getClassInfo());

    //课堂数据获取完成->进入课堂或进入录制回放
    //录制回放不需要获取ip信息和选点
    if (GlobalConfig.isRecordPlayBack) {
      if (_recordPlayback) {
        //开启录制回放流程
        loger.warn("开启录制回放流程");
        //根据用户的userIp信息从sever.json和Sass中选择最终mcu和推流拉流数据列表
        ServerConfig.serverList = ServerConfig.sassServerJson;
        this._choiceMcuAndMsListFromSass();
        //获取MCU和MS 推流拉流、录制回放的默认地址
        this.getMcuAndMsDefaultServerIp();
        _recordPlayback.readyLoadRecordPlayData();
      } else {
        loger.warn("开启录制回放流程失败->还未创建模块");
      }
    } else {
      //初始化音视频通话sdk
      if (GlobalConfig.appId && !GlobalConfig.openFlash) {
        loger.log("使用webRtc通话模式");
        //加入webRtc
        this._initWebRtcSdk({
          appId: GlobalConfig.appId
        }, ()=> {
          //音视频通话SDK初始化完成之后,根据用户的userIp获取信息,获取服务列表选点,选点测速完成后才加入MCU
          this.loadServerJsonAndgetUserIpInfo();
        });
      } else {
        //加入flash
        loger.log("使用flash通话模式");
        this.loadServerJsonAndgetUserIpInfo();
      }
    }*/
  }

  //获取各个服务的默认ip,之后会进行测速选择更快的ip
  getMcuAndMsDefaultServerIp() {
    //MCU地址
    if (GlobalConfig.mcuListFinal && GlobalConfig.mcuListFinal.length > 0) {
      //还未开始选点之前随机选择一个
      let index = parseInt(Math.random() * GlobalConfig.mcuListFinal.length);
    /*  if (!GlobalConfig.MCUServerIP) {
        index = 0;
      }*/
      GlobalConfig.MCUServerIP = GlobalConfig.mcuListFinal[index].ip || "";
      GlobalConfig.MCUServerPort = GlobalConfig.mcuListFinal[index].port || "";
    }

    //录制回放时m3u8播流地址
    if (GlobalConfig.rsPullListFinal && GlobalConfig.rsPullListFinal.length > 0) {
      //还未开始选点之前随机选择一个
      let index = parseInt(Math.random() * GlobalConfig.rsPullListFinal.length);
      if (!GlobalConfig.RS_RECORD_PLAY_IP) {
        index = 0;
      }
      GlobalConfig.RS_RECORD_PLAY_IP = GlobalConfig.rsPullListFinal[index].ip || "";
      GlobalConfig.RS_RECORD_PLAY_PORT = GlobalConfig.rsPullListFinal[index].port || "";
    }

    //推流地址
    if (GlobalConfig.msListFinal && GlobalConfig.msListFinal.length > 0) {
      //还未开始选点之前随机选择一个
      let index = parseInt(Math.random() * GlobalConfig.msListFinal.length);
      if (!GlobalConfig.MS_PUBLISH_IP) {
        index = 0;
      }
      GlobalConfig.MS_PUBLISH_IP = GlobalConfig.msListFinal[index].ip || "";
      GlobalConfig.MS_PUBLISH_PORT = GlobalConfig.msListFinal[index].port || "";
    }

    //RTMP拉流
    if (GlobalConfig.rtmpPullListFinal && GlobalConfig.rtmpPullListFinal.length > 0) {
      // //还未开始选点之前随机选择一个
      let index = parseInt(Math.random() * GlobalConfig.rtmpPullListFinal.length);
      if (!GlobalConfig.MS_PLAY_RTMP_IP) {
        index = 0;
      }
      GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.rtmpPullListFinal[index].ip || "";
      GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.rtmpPullListFinal[index].port || "";
    } else {
      //如果没有单独的rtmp拉流地址,和推流地址一样即可
      GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.MS_PUBLISH_IP;
      GlobalConfig.MS_PLAY_RTMP_PORT = GlobalConfig.MS_PUBLISH_PORT;
    }

    //课堂中HLS拉流地址
    if (GlobalConfig.hlsPullListFinal && GlobalConfig.hlsPullListFinal.length > 0) {
      //有单独的hls拉流地址
      let index = parseInt(Math.random() * GlobalConfig.hlsPullListFinal.length);
      if (!GlobalConfig.MS_PLAY_HLS_IP) {
        index = 0;
      }
      GlobalConfig.MS_PLAY_HLS_IP = GlobalConfig.hlsPullListFinal[index].ip || "";
      GlobalConfig.MS_PLAY_HLS_PORT = GlobalConfig.hlsPullListFinal[index].port || "";
    } else {
      //没有单独的hls拉流地址,和录制回放地址一样即可
      GlobalConfig.MS_PLAY_HLS_IP = GlobalConfig.RS_RECORD_PLAY_IP;
      GlobalConfig.MS_PLAY_HLS_PORT = GlobalConfig.RS_RECORD_PLAY_PORT;
    }

    loger.warn('默认->MCU地址->.', GlobalConfig.MCUServerIP, GlobalConfig.MCUServerPort);
    loger.warn('默认->MS推流地址->.', GlobalConfig.MS_PUBLISH_IP, GlobalConfig.MS_PUBLISH_PORT);
    loger.warn('默认->HLS点播地址->.', GlobalConfig.RS_RECORD_PLAY_IP, GlobalConfig.RS_RECORD_PLAY_PORT);
    loger.warn('默认->HLS拉流地址->.', GlobalConfig.MS_PLAY_HLS_IP, GlobalConfig.MS_PLAY_HLS_PORT);
    loger.warn('默认->RTMP拉流地址->.', GlobalConfig.MS_PLAY_RTMP_IP, GlobalConfig.MS_PLAY_RTMP_PORT);

  }

  //ChatApe
  // 发送聊天消息
  _sendChatMsg(_messageInfo) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_messageInfo === null || EngineUtils.isEmptyObject(_messageInfo)) {
      loger.log('发送聊天消息失败->参数错误', _messageInfo);
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_chat_ape) {
      _chat_ape.sendChatMsg(_messageInfo);
    }
  }

  /*
  * 监听webRtc
  * */
  _webRtcRejoinChannel(_data){
    this._reJoinChannel(_data)
  }
  /*
   * 监听webRtc
   * */
  _mediaEnabledChange(_data){
    if (!_mcu.connected||GlobalConfig.isRecordPlayBack) {
      return;
    }
    if(_confer_ape){
      _confer_ape.sendMediaEnabledChange(_data);
    }
  }
  /*
   * 同步媒体的禁用状态
   * */
  _updateUserMediaMutedStatus(_data){
    if (!_mcu.connected||GlobalConfig.isRecordPlayBack) {
      return;
    }
    if(_confer_ape){
      _confer_ape.sendUpdateUserMediaMutedStatus(_data);
    }
  }

  //监听摄像头麦克风状态
  userDeviecStatusChange(_data) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_confer_ape) {
      _confer_ape.updaterUserDeviecStatusChange(_data);
    }
  }

  //webRtc推流状态发生改变
  mediaPublishStatusChange(_data) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_video_ape) {
      //if(_data.status==WebRtcApe.RECORD_STATUS_1&&!_data.publishUrl){
      loger.log("webRtc推流状态发生改变->发送同步消息", _data);
      if (_data.status == WebRtcApe.RECORD_STATUS_1) {
        let publishData = this._getVideoPublishPath();
        let publishUrl = "";
        //判断是否可以推流
        if (publishData && publishData.code == 0) {
          _data.publishUrl = publishData.publishUrl || "";
          _video_ape.mediaPublishStatusChange(_data);
        }else{
          loger.log("webRtc推流状态发生改变->推流失败", publishData);
          this._unpublishMedia();
        }
      }else {
        //停止推流
        _video_ape.mediaPublishStatusChange(_data);
      }

    }
  }

  //屏幕共享
  //开始屏幕共享
  _publishScreenShare(_param) {
    if (_video_ape) {
      _video_ape.publishScreenShare(_param);
    }
  }

  //停止屏幕共享
  _stopPublishScreenShare(_param) {
    if (_video_ape) {
      _video_ape.stopPublishScreenShare(_param);
    }
  }

  //添加外部流
  _addMediaExternalLink(_params){
    if(!_params||!_params.rtmpUrl){
      return;
    }
        let fileInfo = {};
        fileInfo.pageNum =1;// 文档的总页数
        fileInfo.fileName = _params.fileName||"视频"+EngineUtils.creatTimestamp()+".video";//文档名字
        fileInfo.fileType ="video";
        fileInfo.relativeUrl ="";   //文档相对地址
        fileInfo.url = _params.rtmpUrl||"unkown";   //文档绝对地址  默认值: null
        fileInfo.docId =""+EngineUtils.creatTimestamp();    //文档在数据库中的唯一id标识 默认值: null
        fileInfo.visible = false;    // 是否显示  默认值: false
        fileInfo.publishUrl = _params.rtmpUrl||"";
        fileInfo.rtmpUrl = _params.rtmpUrl||"";
        fileInfo.m3u8Url = _params.m3u8Url||"";
        fileInfo.replay = _params.replay||"";
        loger.log("添加外部流", fileInfo);
        this._sendDocumentUpload(fileInfo);
  }
  //删除外部流
  _deleteMediaExternalLink(_param){
    this._sendDocumentDelete(_param);
  }
  //推送外部流地址
  _publishExternalLink(_param) {
    if (!_mcu.connected) {
      loger.warn("推送外部流地址失败,mcu连接已经断开");
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    loger.log("推送外部流地址", _param);
    if (_video_ape) {
      _video_ape.publishExternalLink(_param);
    }
  }

  //停止推送外部流地址
  _stopPublishExternalLink(_param) {
    if (!_mcu.connected) {
      loger.warn("停止推送外部流地址失败,mcu连接已经断开");
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    loger.log("停止推送外部流地址", _param);
    if (_video_ape) {
      _video_ape.stopPublishExternalLink(_param);
    }
  }

  //VidoeApe
  videoUpdate(_data) {
    //视频同步的消息发送改变,需要通知ferApe模块中的用户更新状态
    if (_confer_ape) {
      _confer_ape.updaterRosterStatus(_data);
    }
  }

  _sendVideoBroadcastMsg(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_video_ape) {
      return _video_ape.sendVideoBroadcastMsg(_param);
    }
  }

  _getVideoPlayPath(_param) {
    if (_video_ape) {
      return _video_ape.getPlayVideoPath(_param);
    }
  }

  _getVideoPublishPath(_param) {
    if (_video_ape) {
      return _video_ape.getPublishVideoPath(_param);
    }
  }

  _getVideoAllChannelInfo(_param) {
    if (_video_ape) {
      return _video_ape.getAllChannelInfo(_param);
    }
  }

  _publishVideo(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_video_ape) {
      return _video_ape.publishVideo(_param);
    }
  }

  _stopPublishVideo(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_video_ape) {
      return _video_ape.stopPublishVideo(_param);
    }
  }

  //AudioApe
  audioUpdate(_data) {
    //音频同步的消息发送改变,需要通知ferApe模块中的用户更新状态
    if (_confer_ape) {
      _confer_ape.updaterRosterStatus(_data);
    }
  }

  sendAudioCommandMsg(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_audio_ape) {
      return _audio_ape.sendAudioBroadcastMsg(_param);
    }
  }

  _getPlayAudioPath(_param) {
    if (_audio_ape) {
      return _audio_ape.getAudioPlayPath(_param);
    }
  }

  _getPublishAudioPath(_param) {
    if (_audio_ape) {
      return _audio_ape.getAudioPublishPath(_param);
    }
  }

  _getAudioAllChannelInfo(_param) {
    if (_audio_ape) {
      return _audio_ape.getAllChannelInfo(_param);
    }
  }

  _publishAudio(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_audio_ape) {
      return _audio_ape.publishAudio(_param);
    }
  }

  _stopPublishAudio(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return {"code": ApeConsts.RETURN_FAILED, "data": ""};
    }
    if (_audio_ape) {
      return _audio_ape.stopPublishAudio(_param);
    }
  }

  //WhiteBoardApe
  // 添加标注,发送信息
  _sendInsertAnnotaion(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_whiteboard_ape) {
      _whiteboard_ape.sendInsetAnnotaion(_param);
    }
  }

  //CursorApe
  // 添加鼠标同步
  _sendInsertCursor(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_cursor_ape) {
      _cursor_ape.sendInsertCursor(_param);
    }
  }

  //删除当前页面上的所有标注
  _sendDeleteCurPageAnnotation(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_whiteboard_ape) {
      _whiteboard_ape.sendDeleteCurPageAnnotation(_param);
    }
  }

  //删除所有标注
  _sendDeleteAllAnnotation(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_whiteboard_ape) {
      _whiteboard_ape.sendDeleteAllAnnotation(_param);
    }
  }

  //返回上一步标注
  _sendGotoPrev(_param) {
    if (_whiteboard_ape) {
      _whiteboard_ape.sendGotoPrev(_param);
    }
  }

  //DocApe
  //获取文档的所有资源地址
  _getDocFullAddress(_param) {
    if (_doc_ape) {
      return _doc_ape.getDocFullAddress(_param);
    } else {
      loger.error("文档模块还没有创建无法获取");
      return {"code": ApeConsts.RETURN_FAILED, "data": "文档模块还没有创建无法获取"};
    }
  }

  //获取文档完整路径
  _getDocImageFullPath(_param) {
    if (_doc_ape) {
      return _doc_ape.getDocImageFullPath(_param);
    } else {
      loger.error("文档模块还没有创建无法获取");
      return [];
    }
  }

  _getDocPDFFullPath(_param) {
    if (_doc_ape) {
      return _doc_ape.getDocPDFFullPath(_param);
    } else {
      loger.error("文档模块还没有创建,无法获取");
      return [];
    }
  }

  //上传文档
  _sendDocumentUpload(_param) {
    if (!_mcu.connected) {
      console.warn("连接已经断开->上传文档");
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentUpload(_param);
    }
  }

  //切换文档
  _sendDocumentSwitchDoc(_param) {
    if (!_mcu.connected) {
      loger.warn("连接已经断开->不能切换文档");
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentSwitchDoc(_param);
    }
  }

  //切换到白板文档
  _switchToWhiteboard(_param) {
    if (!_mcu.connected) {
      loger.warn("连接已经断开->不能切换到白板文档");
      return;
    }
    //白板文档的数据
    let data = {
      itemIdx: GlobalConfig.whiteboardId,
      visible: true
    }
    if (_doc_ape) {
      loger.log("切换到白板文档");
      _doc_ape.documentSwitchDoc(data);
    }
  }

  //操作文档(翻页)
  _sendDocumentSwitchPage(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentSwitchPage(_param);
    }
  }

  //操作文档(页码上的动画步骤操作)
  _sendDocumentSwitchAnimation(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentSwitchAnimation(_param);
    }
  }

  //操作文档(缩放、滚动...)
  _sendDocumentCommand(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentCommand(_param);
    }
  }

  //删除文档
  _sendDocumentDelete(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentDelete(_param);
    }
  }

  //删除所有文档
  _documentDeleteAll(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_doc_ape) {
      _doc_ape.documentDeleteAll(_param);
    }
  }

  //// 文档变更,白板也需要做处理
  docUpdateHandler(_data) {
    if (!_mcu.connected && !GlobalConfig.isRecordPlayBack) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    //loger.log('Doc UpdateId ->');
    //loger.log(_data);
    if (_whiteboard_ape) {
      _whiteboard_ape.docUpdateHandler(_data);
    }
  }

  //文档删除,白板也需要做处理
  docDeleteHandler(_data) {
    if (_whiteboard_ape) {
      _whiteboard_ape.docDeleteHandler(_data);
    }
  }

  //隐藏当前显示的文档
  _hideCurrentDocument(_params) {
    if (_doc_ape) {
      _doc_ape.hideCurrentDocument(_params);
    }
  }

  //媒体共享模块的接口
  //上传
  _sendMediaSharedUpload(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_mediaShareApe) {
      _mediaShareApe.mediaSharedUpload(_param);
    }
  }

  //音乐共享模块的接口
  //上传
  _sendMusicSharedUpload(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_musicShareApe) {
      _musicShareApe.musicSharedUpload(_param);
    }
  }

  //Sass删除媒体文件数据
  _sassDeletMediaShare(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }

    //判断传入的参数是否存在
    if (_param == null || EngineUtils.isEmptyObject(_param)) {
      loger.error('删除媒体文件失败->参数错误', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_MEDIASHARE_DELETE_FAILED_PARAM);
      return;
    }
    //判断必要的参数字段值
    if (_param.itemIdx == null || isNaN(_param.itemIdx) || _param.fileId == null || _param.fileId == "") {
      loger.error('删除媒体文件失败->', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_MEDIASHARE_DELETE_FAILED_PARAM);
      return;
    }
    loger.log('删除媒体文件->', _param);

    if (_sass) {
      _sass.sassDeletMediaShare(_param);
    }
  }

  // 删除Music
  _sassDeletMusicShare(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }

    //判断传入的参数是否存在
    if (_param == null || EngineUtils.isEmptyObject(_param)) {
      loger.error('删除媒体文件失败->参数错误', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_MUSICSHARE_DELETE_FAILED_PARAM);
      return;
    }
    //判断必要的参数字段值
    if (_param.itemIdx == null || isNaN(_param.itemIdx) || _param.fileId == null || _param.fileId == "") {
      loger.error('删除媒体文件失败->', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_MUSICSHARE_DELETE_FAILED_PARAM);
      return;
    }
    loger.log('删除媒体文件->', _param);

    if (_sass) {
      _sass.sassDeletMusicShare(_param);
    }
  }

  //删除媒体
  _sendMediaSharedDelete(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_mediaShareApe) {
      _mediaShareApe.mediaSharedDelete(_param);
    }
  }

  //删除媒体
  _sendMusicSharedDelete(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_musicShareApe) {
      _musicShareApe.musicSharedDelete(_param);
    }
  }

  //音乐更新
  _sendMusicSharedUpdate(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_musicShareApe) {
      _musicShareApe.musicSharedUpdate(_param);
    }
  }

  //音乐播放
  _sendMusicSharedPlay(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_musicShareApe) {
      _musicShareApe.musicSharedPlay(_param);
    }
  }

  //音乐停止
  _sendMusicSharedStop(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_musicShareApe) {
      _musicShareApe.musicSharedStop(_param);
    }
  }

  //更新媒体文件的状态信息
  _sendMediaSharedUpdate(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_mediaShareApe) {
      _mediaShareApe.mediaSharedUpdate(_param);
    }
  }

  //播放
  _sendMediaSharedPlay(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_mediaShareApe) {
      _mediaShareApe.mediaSharedPlay(_param);
    }
  }

  //停止

  _sendMediaSharedStop(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_mediaShareApe) {
      _mediaShareApe.mediaSharedStop(_param);
    }
  }


  //文档加入频道成功,同步到MCU服务器上的数据
  docJoinChannelSuccess() {
    let interval=new Date().getTime()-parseInt(this.lastClassActiveTime);
    interval=interval/1000;
    //loger.log("最后一次记录的时间->"+this.lastClassActiveTime,"当前时间:"+new Date().getTime(),"间隔:"+interval+"秒");
    loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNum, "sassDoclength=", GlobalConfig.docListPrepare.length);
    //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
    if (GlobalConfig.docListPrepare && GlobalConfig.docListPrepare.length > 0) {
      //如果当前身份是老师或者当前课堂内只有一个人并且不是H5,有权限同步文档到MCU
      if (GlobalConfig.isHost || (GlobalConfig.rosterNum <= 1&&GlobalConfig.deviceType!=GlobalConfig.deviceH5)) {
        for (let i = 0; i < GlobalConfig.docListPrepare.length; i++) {
          let value = GlobalConfig.docListPrepare[i];
          if (value) {
            //loger.log("判断是否需要把提前上传的文档上传到mcu", value);
            let paramInfo = {
              "pageNum": value.pdfSize || value.pageNum,
              "fileName": value.name,
              "fileType": value.type||value.fileType,
              "relativeUrl": value.relativeLocation || value.relativeUrl,
              "url": value.absoluteLocation || value.url,
              "creatUserId": value.createUserID || 0,
              "docId": value.id || value.docId,
              "md5": value.MD5 || "",
              "visible": false,
              "itemIdx": value.itemIdx || 0
            };
            this._sendDocumentUpload(paramInfo);
          }
        }
      }
    }
  }

  //音乐共享模块加入频道成功,同步到MCU服务器上的数据
  musicShareApeJoinChannelSuccess() {
    //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
    if (GlobalConfig.musicListPrepare&& GlobalConfig.musicListPrepare.length > 0) {
      if (GlobalConfig.isHost || (GlobalConfig.rosterNum <= 1&&GlobalConfig.deviceType!=GlobalConfig.deviceH5)) {
        for (let i = 0; i < GlobalConfig.musicListPrepare.length; i++) {
          let value = GlobalConfig.musicListPrepare[i];
          if (value) {
            let paramInfo = {
              "status": 0,
              "creatUserId": value.creatUserId,
              "creatUserName": value.createUserName,
              "url": value.url || value.absoluteLocation,//伴音上传的接口有差异,课堂内上传返回的是url字段,后台带入的字段是absoluteLocation
              "fileType": value.type,
              "fileId": "" + value.id,
              "fileName": value.name,
              "seek": 0,
              "duration": parseInt(value.duration) || 0
            };
            //外部接口上传的伴音文件返回的地址有的不正确,需要特殊处理,检测是否有DocSharing目录
            if(paramInfo.url&&paramInfo.url.indexOf("/DocSharing/")<0){
              paramInfo.url=paramInfo.url.replace("/data/","/DocSharing/data/");
            }
            loger.log("MP3路径地址:"+paramInfo.url,"fileId:"+paramInfo.fileId);
            this._sendMusicSharedUpload(paramInfo);
          }
        }
      }
    }
  }

  //媒体共享模块加入频道成功,同步到MCU服务器上的数据
  mediaShareApeJoinChannelSuccess() {
    //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
    if (GlobalConfig.sharedMediaList&& GlobalConfig.sharedMediaList.length > 0) {
      if (GlobalConfig.isHost || (GlobalConfig.rosterNum <= 1&&GlobalConfig.deviceType!=GlobalConfig.deviceH5)) {
        for (let i = 0; i < GlobalConfig.sharedMediaList.length; i++) {
          let value = GlobalConfig.sharedMediaList[i];
          if (value) {
            let paramInfo = {
              "status": 0,
              "creatUserId": value.creatUserId,
              "creatUserName": value.createUserName,
              "url": value.url,
              "fileType": value.type,
              "fileId": "" + value.id,
              "fileName": value.name,
              "seek": 0,
              "duration": parseInt(value.duration) || 0
            };
            //外部接口上传的伴音文件返回的地址有的不正确,需要特殊处理,检测是否有DocSharing目录
            if(paramInfo.url&&paramInfo.url.indexOf("/DocSharing/")<0){
              paramInfo.url=paramInfo.url.replace("/data/","/DocSharing/data/");
            }
            loger.log("MP4路径地址:"+paramInfo.url,"fileId:"+paramInfo.fileId);
            this._sendMediaSharedUpload(paramInfo);
          }
        }
      }
    }

  }

  //录制回放相关的处理------------------------------------------------
  //录制回放初始化
  _initRecordPlayback(_param) {
    //{"classId":"1653304953","portal":"112.126.80.182:80","userRole":"normal","userId":0}
    if (_param == null) {
      loger.error('录制回放初始化失败->参数错误');
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_INIT_RECORD_PLAY_BACK_FAILED);
      return;
    }
    //判断必要的参数字段值
    if (_param.classId == null || isNaN(_param.classId) || _param.portal == null || _param.portal == "") {
      loger.error('录制回放初始化失败->', _param);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_INIT_RECORD_PLAY_BACK_FAILED);
      return;
    }
    loger.log('录制回放初始化->', _param);

    //保存参数
    GlobalConfig.isRecordPlayBack = true; //设置为录制回放状态
    GlobalConfig.classId = parseInt(_param.classId);
    GlobalConfig.portal = _param.portal || "";
    if (GlobalConfig.isHttps == true) {
      //https的时候替换所有80端口
      GlobalConfig.portal = GlobalConfig.replacePort(GlobalConfig.portal, ":80", "");
    }

    GlobalConfig.userRole = ApeConsts.normal; //*************很重要,录制回放的时候,身份模式是普通人********
    GlobalConfig.userId = _param.userId || "0";
    GlobalConfig.userName = _param.userName || "";

    //获取课堂最完整的数据,录制回放需要获取课堂数据
    if (_sass) {
      _sass.getClassParam();
    }
  }

  //开始录制回放
  _startRecordPlayback(_param) {
    if (_recordPlayback) {
      _recordPlayback.startRecordPlayback(_param);
    }
  }

  //停止录制回放
  _stopRecordPlayback(_param) {
    if (_recordPlayback) {
      _recordPlayback.stopRecordPlayback(_param);
    }
  }

  //暂停录制回放
  _pauseRecordPlayback(_param) {
    if (_recordPlayback) {
      _recordPlayback.pauseRecordPlayback(_param);
    }
  }

  //seek录制回放
  _seekRecordPlayback(_param) {
    if (_recordPlayback) {
      _recordPlayback.seekRecordPlayback(_param);
    }
  }

  //录制回放状态更新
  _recordPlaybackClearDataHandler(_param) {
    loger.log("录制回放状态更新->")
    if (_doc_ape) {
      _doc_ape.clearData();
    }
    if (_whiteboard_ape) {
      _whiteboard_ape.clearData();
    }
    if (_video_ape) {
      _video_ape.clearData();
    }
    if (_mediaShareApe) {
      _mediaShareApe.clearData();
    }
    if (_musicShareApe) {
      _musicShareApe.clearData();
    }
  }

  //录制回放加入 课堂成功
  _joinRecordPlaybackSuccessHandler(_data) {
    loger.log('加入录制回放成功.');
    LogManager.IS_OPEN_SEND_LOG = false;//录制回放不需要上报日志
    GlobalConfig.setCurrentStatus(GlobalConfig.statusCode_2);

    //返回给客户端初始化成功的数据
    let joinClassSuccessCallBackData = {};

    joinClassSuccessCallBackData.isRecordPlayBack = GlobalConfig.isRecordPlayBack;

    joinClassSuccessCallBackData.DOCServerIP = GlobalConfig.DOCServerIP;
    joinClassSuccessCallBackData.DOCServerPort = GlobalConfig.DOCServerPort;

    joinClassSuccessCallBackData.classStatus = GlobalConfig.classStatus;
    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
    joinClassSuccessCallBackData.className = GlobalConfig.className;
    joinClassSuccessCallBackData.h5Module = GlobalConfig.h5Module;
    joinClassSuccessCallBackData.isHost = GlobalConfig.isHost; //
    joinClassSuccessCallBackData.maxAudioChannels = GlobalConfig.maxAudioChannels;
    joinClassSuccessCallBackData.maxVideoChannels = GlobalConfig.maxVideoChannels;
    joinClassSuccessCallBackData.maxMediaChannels = GlobalConfig.maxMediaChannels;

    joinClassSuccessCallBackData.mcuDelay = GlobalConfig.mcuDelay;

    joinClassSuccessCallBackData.msType = GlobalConfig.msType;
    joinClassSuccessCallBackData.nodeId = GlobalConfig.nodeId;
    joinClassSuccessCallBackData.password = GlobalConfig.password;
    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired; //  老师的默认是true
    //GlobalConfig.passwordRequired  老师的默认是true
    //GlobalConfig.portal=_data.portal;
    joinClassSuccessCallBackData.role = GlobalConfig.role;
    joinClassSuccessCallBackData.topNodeID = GlobalConfig.topNodeID;
    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
    joinClassSuccessCallBackData.userName = GlobalConfig.userName;
    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
    joinClassSuccessCallBackData.userType = GlobalConfig.userType;

    joinClassSuccessCallBackData.siteId = GlobalConfig.siteId;
    joinClassSuccessCallBackData.classId = GlobalConfig.classId;
    joinClassSuccessCallBackData.userRole = GlobalConfig.userRole;
    joinClassSuccessCallBackData.userId = GlobalConfig.userId;
    joinClassSuccessCallBackData.passwordRequired = GlobalConfig.passwordRequired;
    joinClassSuccessCallBackData.classType = GlobalConfig.classType || ApeConsts.CLASS_TYPE_1v1;

    joinClassSuccessCallBackData.country = GlobalConfig.country; //国家
    joinClassSuccessCallBackData.city = GlobalConfig.city; //城市
    joinClassSuccessCallBackData.province = GlobalConfig.province; //服务商
    joinClassSuccessCallBackData.isp = GlobalConfig.isp; //服务商

    joinClassSuccessCallBackData.classTimestamp = GlobalConfig.classTimestamp; //课堂进行的累积时间
    joinClassSuccessCallBackData.recordTimestamp = GlobalConfig.recordTimestamp; //录制累积的总时间
    joinClassSuccessCallBackData.recordPlaybackMaxTime = GlobalConfig.recordPlaybackMaxTime; //录制回放的总时间

    joinClassSuccessCallBackData.fps = GlobalConfig.fps;
    joinClassSuccessCallBackData.gop = GlobalConfig.gop;
    joinClassSuccessCallBackData.videoQuality = GlobalConfig.videoQuality;

    joinClassSuccessCallBackData.ssTunnelAppURL = GlobalConfig.ssTunnelAppURL;
    joinClassSuccessCallBackData.currentSceneTableId = GlobalConfig.currentSceneTableId; //文档区域的模块显示
    joinClassSuccessCallBackData.serverAndLoacTimeDistanc = GlobalConfig.serverAndLoacTimeDistanc;

    joinClassSuccessCallBackData.deviceType = GlobalConfig.deviceType;
    joinClassSuccessCallBackData.language = GlobalConfig.language;
    joinClassSuccessCallBackData.explorer = GlobalConfig.explorer;
    joinClassSuccessCallBackData.explorerVersion = GlobalConfig.explorerVersion;
    joinClassSuccessCallBackData.os = GlobalConfig.os;

    loger.log(joinClassSuccessCallBackData);
    //和加入课堂成功使用同样的消息处理
    this._emit(MessageTypes.CLASS_JOIN_SUCCESS, joinClassSuccessCallBackData);
  }

  // //答题卡
  _creatQuestion(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_questionApe) {
      _questionApe.creatQuestion(_param);
    }
  }

  _getQuestion(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_questionApe) {
      _questionApe.getQuestion(_param);
    }
  }

  _getQuestionResult(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_questionApe) {
      _questionApe.getQuestionResult(_param);
    }
  }

  _stopQuestion(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_questionApe) {
      _questionApe.stopQuestion(_param);
    }
  }

  _sendAnswer(_param) {
    if (!_mcu.connected) {
      loger.warn(GlobalConfig.getCurrentStatus());
      return;
    }
    if (_questionApe) {
      _questionApe.sendAnswer(_param);
    }
  }

  //WEB RTC-------------------------------------------------------------------------------------------
  /*
   * 初始化webRtc
   * */
  _initWebRtcSdk(_params, _callback) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid || GlobalConfig.deviceType == GlobalConfig.deviceH5) {
      loger.warn("移动端不需要处理初始化webRtc");
      if (_callback) {
        _callback();
      }
      return;
    }
    if (_webRtc) {
      _webRtc.initApp(_params, (_callbackData)=> {
        //_callback({isSuccess:false,error:err});
        if (_callbackData && _callbackData.isSuccess == true) {
          this._emit(MessageTypes.WEB_RTC_INIT_SUCCESS, _callbackData);
        } else {
          this._emit(MessageTypes.WEB_RTC_INIT_FAILED, _callbackData);
        }
        if (_callback) {
          _callback();
        }
      });
    }
  }

  /*
   * 重新加入频道
   * */
  _reJoinChannel(_params) {
    //{publish:false}
    if (GlobalConfig.appId && !GlobalConfig.openFlash) {

      //1.获取当前用户的推流状态,重新加入频道之后如果之前正在推流,重连后需要自动重推
      let isPublish=false;
      if(_webRtc){
        isPublish=_webRtc.isPublish||false;
      }
      //2.检测是否调用接口的时候传入了开启推流的参数
      if(_params&&_params.publish==true){
        isPublish=true;
      }
      loger.log("离开视频通话频道时的推流状态->"+isPublish);
      //先离开频道
      this._leaveChannel();
      //主讲人和老师可以设置旁录

      //加入之前先设置旁录地址,只有直播支持旁路(1路流)
      if (_webRtc && GlobalConfig.isTeachOrAssistant) {
        let curTimestamp = new Date().getTime();
        let streamId = GlobalConfig.siteId + "_" + GlobalConfig.classId + "_" + GlobalConfig.userId + "_" + curTimestamp;
        //传入固定的流Id
        let publishData = this._getVideoPublishPath({streamId: streamId});
        loger.log("加入之前先设置旁录地址", publishData);
        if (publishData && publishData.code == 0) {
          _webRtc.setConfigPublisherUrl(publishData.publishUrl);
          let m3u8Stream = _video_ape.getPlayVideoPath({"type": "m3u8", "streamId": streamId});
          let rtmpStream = _video_ape.getPlayVideoPath({"type": "rtmp", "streamId": streamId});
          _webRtc.setRtmpM3u8Path({m3u8Url: m3u8Stream.playUrl, rtmpUrl: rtmpStream.playUrl});
        }else {
          //获取频道失败,不能自动推流
          isPublish=false;
        }
      }
      clearTimeout(this.joinChannelTimer);
      this.joinChannelTimer = setTimeout(()=> {
        //加入音视频通话模块,延迟一秒处理,因为视频需要根据用户列表信息来判断放的位置,太早的话用户列表没有数据
        this._joinChannel({
          channelId: GlobalConfig.channelId,
          channelKey: GlobalConfig.channelKey,
          uid: GlobalConfig.userUid,
          info: "" + GlobalConfig.userRole,
          immediatePublish:isPublish
        });
      }, 1600);
    }
  }

  /*
   * 加入视频通话
   * */
  _joinChannel(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      loger.warn("移动端不需要处理加入视频房间");
      return;
    }
    if (_webRtc) {
      _webRtc.joinChannel(_params);
    }
  }

  /*
   * 离开视频通话频道
   * */
  _leaveChannel(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.leaveChannel(_params);
    }
  }

  /*
   * 发布流
   * */
  _publishMedia(_params) {
    //判断是否能推流,当前课堂推流人数是有限制的
    let premission = GlobalConfig.getPublishPermission();
    loger.log("判断是否能推流->", premission);
    if (!premission && GlobalConfig.userRole != ApeConsts.invisible) {
      loger.warn("不能再打开更多设备");
      console.log("当前用户列表", GlobalConfig.rosters);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_WEBRTC_PUBLISH_FULL);
      return;
    }
    //ios和安卓的只需要更新数据即可
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      loger.log("调用webRtc推流");
      GlobalConfig.openCamera = EngineUtils.creatTimestamp();
      GlobalConfig.openMicrophones = GlobalConfig.openCamera;

      this.userDeviecStatusChange({
        nodeId: GlobalConfig.nodeId,
        userRole: GlobalConfig.userRole,
        userName: GlobalConfig.userName,
        userId: GlobalConfig.userId,
        openCamera: GlobalConfig.openCamera,
        openMicrophones: GlobalConfig.openMicrophones
      });
      this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_1});
      return;
    }

    //PC端的先推流再同步数据
    if (_webRtc) {
      _webRtc.publish(_params);
    }
  }

  /*
   * 停止发布流
   * */
  _unpublishMedia(_params) {
    //ios和安卓的只需要更新数据即可
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      loger.log("调用webRtc停止推流");
      GlobalConfig.openCamera = 0;
      GlobalConfig.openMicrophones = 0;
      this.userDeviecStatusChange({
        nodeId: GlobalConfig.nodeId,
        userRole: GlobalConfig.userRole,
        userName: GlobalConfig.userName,
        userId: GlobalConfig.userId,
        openCamera: GlobalConfig.openCamera,
        openMicrophones: GlobalConfig.openMicrophones
      });
      this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_0});
      return;
    }

    if (_webRtc) {
      this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_0});
      _webRtc.unpublish(_params);
    }
  }

  /*
   * 切换摄像头和麦克风设备
   * */
  _changeDevices(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.changeDevices(_params);
    }
  }

  /*
   * 设置旁路推流
   * */
  _setConfigPublisher(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.setConfigPublisher(_params);
    }
  }

  /*
   * 设置本地video视图
   * */
  _setLocalMediaView(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.setLoaclView(_params);
    }
  }

  /*
   * 设置房间内老师身份的视图
   * */
  _setHostRemoteMediaView(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.setHostRemoteMediaView(_params);
    }
  }

  /*
   * 设置房间内普通身份的视图
   * */
  _setNormalRemoteMediaView(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.setNormalRemoteMediaView(_params);
    }
  }

  /*
   * 设置RTC视频属性
   * */
  _changeRtcVideoConfig(_params) {
    loger.log("设置RTC视频属性", _params);
    if (!_params) {
      return;
    }
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.changeRtcVideoConfig(_params);

      //如果是老师和主讲人操作,需要同步给所有人
      if (GlobalConfig.isTeachOrAssistant) {
        if (_confer_ape) {
          let newVideoScale = _params.videoScale || 1;
          if (GlobalConfig.videoScale == newVideoScale) {
            loger.log("不需要设置视频视图大小,没有发生改变", newVideoScale);
            return;
          }
          loger.log("设置视频视图大小->", newVideoScale);
          GlobalConfig.videoScale = newVideoScale;
          _confer_ape.sendUpdaterClassStatusInfo({videoScale: _params.videoScale});
        }
      }
    }
  }

  /*
   * 设置监课和需要隐藏显示的用户视图
   * */
  _setInvisibleMediaView(_params) {
    if (GlobalConfig.deviceType == GlobalConfig.deviceIOS || GlobalConfig.deviceType == GlobalConfig.deviceAndroid) {
      return;
    }
    if (_webRtc) {
      _webRtc.setInvisibleMediaView(_params);
    }
  }

  //设置app相关数据
  _setAppConfig(_params) {
    if (!_params) {
      return;
    }
    loger.log("设置appConfig",_params);
    if (GlobalConfig.appId) {
      loger.log("本地已经设置appConfig,不需要再设置");
      return;
    }
    GlobalConfig.appId = _params.appId || "";
    GlobalConfig.appCertificate = _params.appCertificate || "";
    GlobalConfig.appRecordingKey = _params.appRecordingKey || "";
    GlobalConfig.recordInterfaces = _params.recordInterfaces || "";
    GlobalConfig.getChannelToken = _params.getChannelToken || ""
    GlobalConfig.getRecordInfoInterfaces = _params.getRecordInfoInterfaces || "";
    GlobalConfig.stopRecordingInterfaces = _params.stopRecordingInterfaces || "";
    GlobalConfig.getTxRecordInfoInterfaces = _params.getTxRecordInfoInterfaces || "";
    GlobalConfig.getRecordFileURLAgoInterfaces = _params.getRecordFileURLAgoInterfaces || "";
    GlobalConfig.recordFileSever = _params.recordFileSever || "";

    //去掉协议头
    try {
      if (GlobalConfig.recordInterfaces) {
        GlobalConfig.recordInterfaces = GlobalConfig.recordInterfaces.replace('http://', "");
        GlobalConfig.recordInterfaces = GlobalConfig.recordInterfaces.replace('https://', "");
      }
      if (GlobalConfig.getRecordInfoInterfaces) {
        GlobalConfig.getRecordInfoInterfaces = GlobalConfig.getRecordInfoInterfaces.replace('http://', "");
        GlobalConfig.getRecordInfoInterfaces = GlobalConfig.getRecordInfoInterfaces.replace('https://', "");
      }
      if (GlobalConfig.getTxRecordInfoInterfaces) {
        GlobalConfig.getTxRecordInfoInterfaces = GlobalConfig.getTxRecordInfoInterfaces.replace('http://', "");
        GlobalConfig.getTxRecordInfoInterfaces = GlobalConfig.getTxRecordInfoInterfaces.replace('https://', "");
      }
      if (GlobalConfig.getRecordFileURLAgoInterfaces) {
        GlobalConfig.getRecordFileURLAgoInterfaces = GlobalConfig.getRecordFileURLAgoInterfaces.replace('http://', "");
        GlobalConfig.getRecordFileURLAgoInterfaces = GlobalConfig.getRecordFileURLAgoInterfaces.replace('https://', "");
      }
      if (GlobalConfig.recordFileSever) {
        GlobalConfig.recordFileSever = GlobalConfig.recordFileSever.replace('http://', "");
        GlobalConfig.recordFileSever = GlobalConfig.recordFileSever.replace('https://', "");
      }
      if (GlobalConfig.stopRecordingInterfaces) {
        GlobalConfig.stopRecordingInterfaces = GlobalConfig.stopRecordingInterfaces.replace('http://', "");
        GlobalConfig.stopRecordingInterfaces = GlobalConfig.stopRecordingInterfaces.replace('https://', "");
      }
      if (GlobalConfig.getChannelToken) {
        GlobalConfig.getChannelToken = GlobalConfig.getChannelToken.replace('http://', "");
        GlobalConfig.getChannelToken = GlobalConfig.getChannelToken.replace('https://', "");
      }
    } catch (err) {

    }
  }

  //录制状态控制和推流状态控制
  _mediaRecordControl(_params) {
    if (!GlobalConfig.recordInterfaces || !_params) {
      loger.log("录制控制->失败->接口地址无效", _params);
      return;
    }
    if (_webRtc) {
      switch (_params.status) {
        case WebRtcApe.RECORD_STATUS_0:
        case WebRtcApe.RECORD_STATUS_1:
          //推流/停止推流/开启录制 统一使用一个接口
          _webRtc.changePublishStatusAndServerRecord(_params.status);
          break;
        case WebRtcApe.RECORD_STATUS_2:
          //停止录制
          loger.warn("调用停止音视频录制->");
          _webRtc.changePublishStatusAndServerRecord(WebRtcApe.RECORD_STATUS_2);
          break;
        default :
          break;
      }
    }
  }

  /*
  * 切换音视频的录制状态 1开启  2停止
  * */
  _changeMediaRecordStatus(_param){
    if(_webRtc){
      _webRtc.changeMediaRecordStatus(_param);
    }
  }
  //webRtc-----------------end --------------------------------
  //判断是否能推流,当前课堂推流人数是有限制的
  _hasFreePublishChannel() {
    let premission = GlobalConfig.getPublishPermission();
    loger.log("判断是否能推流->", premission);
    if (!premission && GlobalConfig.userRole != ApeConsts.invisible) {
      loger.warn("不能再打开更多设备");
      console.log("当前用户列表", GlobalConfig.rosters);
      this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_WEBRTC_PUBLISH_FULL);
      return premission;
    }
    return premission;
  }
}