UNPKG

zby-live-sdk

Version:

This is a live SDK for weclassroom.

1,569 lines (1,391 loc) 50.6 kB
import CHANNEL from '../index'; import zbysdk from '../../zby-live-sdk.js'; import ENTRY from './interactWithChannelEntry.js'; import io from 'socket.io-client'; import dataReport from '../../network/dataReport'; import dealReceiveStreamMsg from '../stream-msg'; // import {getApiCloudBaseUrl} from '../../config/config'; let w = window; let CONTROL = { socket: null, //连接保持相关 enterInit: false, //是否发起过登录,leaveRoom后置为false enterSuccessed: false, //是否进入房间成功过,leaveRoom或主动disconnect后置为false isConnect: false, //连接服务器成功的标志 isInRoom: false, //当前是否在房间中,掉线后为false isReconnect: false, //是否是自动重连,控制“进入教室”的显示 isOffline: false, //是否网络断线 joindelaytimer: null, //连接建立后,延迟发起登录请求 jointimeouttimer: null, //登录超时time connecttimeouttimer: null, connecttimeCount: 0, //初次连接时已尝试连接服务器的次数(用于connect超时检查) connecttimeout: 20000, //连接服务器超时,第一次设置20秒 之后变为30秒 authorizefailedcount: 0, //授权失败的次数 //设置相关 address: '', //以下为客户端传入的变量 token: '', code: '', //口令课时的口令,此时token应为空 role: '1', //'1=teacher'/'2=student'/'3=assistant'/'4=parent' username: '', //显示用户相关信息时使用username userid: '', //消息后台识别用户使用userid avatar: '', conn_origin: '', language: 0, //0=中文,1=英文 classmode: 1, ////1=1对1模式;2=1对多模式;3=大班课模式;5=互动大班课 playmode: 1, ////1=正常上课;2=回放 roomid: '', institutionid: '', //确保消息发送成功相关的 streamIndex: 0, normalMsgIndex: 0, //不需要保序的消息的id checkInterval: 3000, //检查“消息发送超时”的时间间隔(超时时间在检查函数中) maxAckDelay: 8000, //消息回复的最大时间延迟,超过最大延迟还没收到回复,认为没发送成功 checkSendSucessTimer: null, sendCheckList: null, //发送确认队列 sendCacheList: null, //发送缓存队列 recvmsgKeys: null, //接收消息的缓存队列 recvmsgTimes: null, //接收消息的缓存队列每条消息对应的时间 cacheReqSend: false, //请求服务器端缓存的命令是否发送了 recvCacheList: null, //断线重连后,请求缓存队列后,保存缓存的消息 recvTmpList: null, //断线重连后,在获取服务器到服务器端的缓存队列前,先缓存收到的消息,不上抛 timedeviation: 0, //本地时间与服务器时间的差值,毫秒:服务器时间-本地时间 orderCheck: null, //保序验证 lastRecvMsgId: null, //保序验证 //测试添加 testrecvcount: null, //保序验证,统计接收消息数 //测试添加 delaycheckList: null, //保序、丢消息、延迟验证 //测试添加 delayList: null, //延迟列表 lastcheckindex: 0, //上一个收到的echo回复的 dealChannelMsg: null, // 业务层传入的消息处理器,作为回调使用 pingPongArr: [], //public 进入教室 enterRoom: function (dealChannelMsg) { CONTROL.dealChannelMsg = dealChannelMsg; if (!this.enterInInitparas()) { return; } ENTRY.recordLogToFile('info', 'enter room info'); //通知宿主,页面开始连接了 if (w.controlObj && w.controlObj.onStartConnect) { w.controlObj.onStartConnect(); } this.startEnter(); }, //public 退出,断开连接 leaveRoom: function () { CONTROL.otherChannelleaveRoom(); ENTRY.recordLogToFile('info', 'control: leaveRoom'); if (!this.enterInit) { return; } CONTROL.enterInit = false; this.sendleaveRoomMsg(); if (this.socket) { this.socket.disconnect(); } this.leaveRoomReset(); }, //其它通道离开房间 otherChannelleaveRoom: function () { try { //其他通道 if (ENTRY.channeltype == 0) { if (w.CHAT) { w.CHAT.leaveRoom(); } } } catch (e) { ENTRY.recordException(e); } }, //开始登录过程 连接-验证身份-进入教室 startEnter: function () { CONTROL.enterInit = true; ENTRY.recordLogToFile('info', 'enter channel room start'); var logmsg = 'control:connect '; logmsg += this.address; CONTROL.displayLogMsg(logmsg); ENTRY.recordLogToFile('info', logmsg); //连接 this.socketConnect(); //超时检查 this.startCheckConnectTimer(); }, //socket 开始连接 socketConnect: function () { //开始连接时,如果存在已有socket,先断开连接 if (CONTROL.socket) { CONTROL.socket.disconnect(); } var opt = new Object(); opt.transports = ['websocket']; ENTRY.recordLogToFile('info', 'channel start connect ' + this.address); CONTROL.socket = io.connect(this.address, opt); this.setEventListeners(); }, //注册监听事件 setEventListeners: function () { console.log('websocket: 设置监听'); //监听连接成功 01步 this.socket.on('connect', function (o) { ENTRY.recordLogToFile('info', 'channel connect success'); CONTROL.dealConnectSuccess(); try { dataReport.joinRoomResult({ code: '0', // cloud_api_response: JSON.stringify(window.zby_sdk_cloud_data), cloud_api_response: window.zby_sdk_cloud_data, cloud_api_url: `${getApiCloudBaseUrl()}/rtccloud/class/init`, chat_url: window.zby_sdk_cloud_data.chatUrl }); } catch (e) {}; }); //授权成功 02步 this.socket.on('authenticated', function () { console.log('websocket: 授权成功'); CONTROL.displayLogMsg('control:authenticated!'); ENTRY.recordLogToFile('info', 'control:authenticated succeessful'); CONTROL.stopCheckConnectTimer(); CONTROL.joindelaytimer = window.setTimeout(CONTROL.sendJoinInMsg, 100); //延迟100毫秒登录 }); //授权不成功 this.socket.on('unauthenticated', function () { ENTRY.recordLogToFile('info', 'control:authenticated failed'); CONTROL.dealAuthorizeFailed(); try { dataReport.joinRoomResult({ code: '4', cloud_api_response: window.zby_sdk_cloud_data, cloud_api_url: `${getApiCloudBaseUrl()}/rtccloud/class/init`, chat_url: window.zby_sdk_cloud_data.chatUrl }); } catch (e) {}; }); //监听连接断开,禁用网卡/主动disconnect,会回调这个 this.socket.on('disconnect', function () { CONTROL.isOffline = true; CONTROL.dealDisconnect(); }); this.socket.on('pong', function (data) { // console.log(data + ' - pong'); CONTROL.pingPongArr.push(data); }); this.OnUserDefineMsg(); }, //发送验证权限消息 sendAuthenMsg: function () { try { if (CONTROL.token != '') { CONTROL.socket.emit('authenticate', { token: CONTROL.token, room: CONTROL.roomid, instid: CONTROL.institutionid, userid: CONTROL.userid }); } else if (CONTROL.code != '') { CONTROL.socket.emit('authenticate', { code: CONTROL.code, room: CONTROL.roomid, instid: CONTROL.institutionid, userid: CONTROL.userid }); var logmsg = 'control sendAuthenMsg:with code:' + CONTROL.code; ENTRY.recordLogToFile('info', logmsg); } else { ENTRY.recordLogToFile('error', 'control auth para wrong'); } } catch (e) { ENTRY.recordException(e); } }, //连接授权成功后发送:进入房间消息发送 sendJoinInMsg: function () { // 销毁延迟登录timer CONTROL.stopDelayJoinTimer(); try { var obj = {}; obj.username = CONTROL.username; obj.room = CONTROL.roomid; obj.avatar = CONTROL.avatar; obj.userid = CONTROL.userid; obj.role = CONTROL.role; obj.playmode = CONTROL.playmode; obj.device = 1; //0 - 默认;1 - PC客户端;2 - PC web;3 - 移动 web;4 - 移动 APP; obj.instid = CONTROL.institutionid; obj.conn_origin = CONTROL.conn_origin; obj.classmode = ENTRY.getClassTypeInDb(); ENTRY.recordLogToFile('info', 'control:emit join_room'); CONTROL.socket.emit('join_room', obj, function (param) { CONTROL.dealJoinMsgAck(param); var logmsg = 'control:join_room ack' + JSON.stringify(param); ENTRY.recordLogToFile('info', logmsg); }); CONTROL.isOffline = false; } catch (e) { ENTRY.recordException(e); } // 超时后检查进入房间是否成功 CONTROL.startCheckJoinInTimer(); }, //退出登录消息发送 sendleaveRoomMsg: function () { if (CONTROL.socket == null) { return; } try { ENTRY.recordLogToFile('info', 'control:emit leave room'); CONTROL.socket.emit('leave_room', {}, function (param) {}); } catch (e) { ENTRY.recordException(e); } }, //进入房间是否成功的检查,在进入房间尚未成功时,重新启动进入过程 checkAndReEnter: function () { CONTROL.stopAllTimeoutTimer(); // 离开教室 / 进入房间成功后,不用再检查了 // 掉线重连,不走这儿,这儿是尚未连接成功过,或主动disconnect后的重连;掉线后,socket.io库会自动重连,并回调connect if (!CONTROL.enterInit || CONTROL.enterSuccessed) { var logmsg = 'control:checkAndReEnter not needed:' + CONTROL.enterInit + ',' + CONTROL.enterSuccessed; ENTRY.recordLogToFile('info', logmsg); return; } ENTRY.recordLogToFile('info', 'control:checkAndReEnter'); if (CONTROL.connecttimeCount < ENTRY.enterMaxCount) { CONTROL.reEnterRoom(); } else { //上报客户端 if (w.controlObj && w.controlObj.loginFailed) { w.controlObj.loginFailed(); } // channel_join 信道连接 进入房间失败 ENTRY.recordLogToFile('info', 'channel reenter failed'); } }, //重新连接并进入房间 reEnterRoom: function () { CONTROL.reenterReset(); if (CONTROL.enterSuccessed) { CONTROL.isReconnect = true; } CONTROL.connecttimeCount++; CONTROL.startEnter(); }, //连接超时检查 checkConnectTimeout: function () { CONTROL.stopCheckConnectTimer(); if (!CONTROL.isConnect) { ENTRY.recordLogToFile('info', 'control:check connect failed,reenter'); CONTROL.checkAndReEnter(); } }, //处理连接成功的事件 dealConnectSuccess: function () { ENTRY.recordLogToFile('info', 'control:connect success'); //没有执行过登录操作 if (!this.enterInit) { ENTRY.recordLogToFile('error', 'control:recve connect,not init yet!'); return; } this.isConnect = true; //发送验证 this.sendAuthenMsg(); //回调 authenticated //上报客户端 if (w.controlObj && controlObj.connectSuccess) { controlObj.connectSuccess(); } }, //处理断开连接事件 dealDisconnect: function () { ENTRY.recordLogToFile('info', 'control:channel disconnect'); if (!CONTROL.enterInit) { ENTRY.recordLogToFile('info', 'control:recve disconnect,already logout'); return; } try { CONTROL.displayLogMsg('control: disconnect'); CONTROL.isConnect = false; CONTROL.isInRoom = false; if (CONTROL.enterSuccessed) { //自己掉线通知 if (w.controlObj && controlObj.disconnect) { controlObj.disconnect(); } CONTROL.dealChannelMsg({ type: 'channel_disconnect', data: { message: 'Channel disconnect' } }); } } catch (e) {} }, //处理授权失败 dealAuthorizeFailed: function () { try { if (CONTROL.enterSuccessed) { //进入房间成功过,但掉线重连时,验证授权失败了 ENTRY.recordLogToFile('info', 'control:authenticated failed when reconect'); if (CONTROL.authorizefailedcount < 8) { ENTRY.recordLogToFile('error', 'control:authenticated failed socketConnect'); CONTROL.socketConnect(); } } else { //尚未进入房间成功,且授权失败了 ENTRY.recordLogToFile('error', 'control:authenticated failed when connect first time'); CONTROL.checkAndReEnter(); } ++CONTROL.authorizefailedcount; } catch (e) { ENTRY.recordException(e); } }, //处理登录消息 dealJoinMsgAck: function (msgobj) { //{'code':0,'message':'join ok'} if (!msgobj) { return; } if (msgobj.code == 0) { if (!this.enterSuccessed) { CONTROL.otherChannelEnter(); //其它通道进入房间,免身份验证 } this.stopCheckJoinInTimer(); if (!this.isInRoom) { if (this.enterSuccessed) { CONTROL.dealReconnectSuccess(); this.displayLogMsg('control: reenter room success'); //重新登录成功 ENTRY.recordLogToFile('info', 'reenter room success'); CONTROL.dealChannelMsg({ type: 'channel_reconnect', data: { message: 'Channel reconnect' } }); //如果之前登入过,且现在没在房间内,代表当次连接为断线重连,重连成功须重新发送音视频 join 消息 CHANNEL.reJoin(); } else { //请求历史消息 this.getHistoryList(); this.displayLogMsg('control: enter room success'); //已进入房间的话就不要提示了 ENTRY.recordLogToFile('info', 'control: enter room success'); } } this.isInRoom = true; this.enterSuccessed = true; //设置进房间已经成功过了 if (w.controlObj && w.controlObj.loginResponse) { w.controlObj.loginResponse(); } //测试添加 //this.getOnlineList(); //在线人数 this.notifyMembernumChanged(msgobj.data); CONTROL.timedeviation = msgobj.data.unixtime - (new Date().getTime()); } else { this.checkAndReEnter(); } }, //其它通道进行连接 otherChannelEnter: function () { try { if (ENTRY.channeltype == 0) { if (w.CHAT) { w.CHAT.enterRoom(); } } } catch (e) { ENTRY.recordException(e); } }, //登录前,初始化参数 enterInInitparas: function () { try { var config = ENTRY.getConfigInfo(); var roominfo = ENTRY.getRoomInfo(); if (!config || !roominfo) { ENTRY.recordLogToFile('info', 'enter room:room info is empty or user info is empty'); return false; } this.address = config.address + 'control'; //兼容一下老的客户端,老的客户端使用的是url编码 // if(config.hasOwnProperty('encodetype') && config.encodetype=='base64'){ // var b64decoder = new w.Base64(); // this.username = b64decoder.decode(config.user.username); // this.avatar = b64decoder.decode(config.user.avatar); // ENTRY.recordLogToFile('i','control enterInInitparas:use base64 decode:'+this.username); // } // else{ this.username = decodeURIComponent(config.user.username); this.avatar = decodeURIComponent(config.user.avatar); ENTRY.recordLogToFile('info', 'control enterInInitparas:use url decode:' + this.username); // } this.userid = config.user.userid; if (config.user.hasOwnProperty('token')) { this.token = config.user.token; } if (config.user.hasOwnProperty('code')) { this.code = config.user.code; } this.role = config.user.role; this.language = config.language; this.roomid = roominfo.roomid; this.classmode = roominfo.classmode; this.playmode = roominfo.playmode; this.institutionid = roominfo.institutionid; this.conn_origin = roominfo.conn_origin; if (this.roomid == '' || this.userid == '') { ENTRY.recordLogToFile('error', 'enter room:roomid is empty or userid is empty'); return false; } this.isReconnect = false; //重连标志,重连一次就算重连了 this.connecttimeCount = 0; this.initCacheList(); } catch (e) { ENTRY.recordException(e); return false; } return true; }, //重启进入教室过程前,对某些变量进行重置 reenterReset: function () { this.isInRoom = false; this.isConnect = false; if (this.userstate == null) { this.userstate = new Array(); } else { this.userstate.length = 0; } if (this.socket) { this.socket.disconnect(); } this.stopAllTimeoutTimer(); }, leaveRoomReset: function () { this.reenterReset(); //停一下timer this.stopAllTimeoutTimer(); this.unInitCacheList(); this.connecttimeCount = 0; this.enterInit = false; this.enterSuccessed = false; this.socket = null; }, //启动 连接超时检查 startCheckConnectTimer: function () { this.connecttimeouttimer = window.setTimeout(CONTROL.checkConnectTimeout, CONTROL.connecttimeout); CONTROL.connecttimeout = 30000; }, //关闭 连接超时检查 stopCheckConnectTimer: function () { if (this.connecttimeouttimer) { w.clearTimeout(this.connecttimeouttimer); this.connecttimeouttimer = null; } }, //关闭 延迟join的timer stopDelayJoinTimer: function () { if (CONTROL.joindelaytimer != null) { window.clearTimeout(CONTROL.joindelaytimer); CONTROL.joindelaytimer = null; } }, //启动 进入房间超时的检查 startCheckJoinInTimer: function () { this.jointimeouttimer = window.setTimeout(CONTROL.checkAndReEnter, CONTROL.connecttimeout); }, //关闭 进入房间超时的检查 stopCheckJoinInTimer: function () { if (this.jointimeouttimer) { w.clearTimeout(this.jointimeouttimer); this.jointimeouttimer = null; } }, //停止所有计时器 stopAllTimeoutTimer: function () { this.stopCheckConnectTimer(); this.stopCheckJoinInTimer(); this.stopDelayJoinTimer(); }, displayLogMsg: function (msg) { if (w.CHATDIS) { w.CHATDIS.displayLogMsg(msg); } }, //////////////////////以上为进入房间/离开房间 过程的通用处理///////////////////////////////////// //发送流控制消息 string string string string sendStreamMsg: function (subtype, content, targetid, save) { //subtype : stream / class(save) var obj = {}; obj.index = this.streamIndex++; //int obj.uuid = ENTRY.getMsgKey(obj.index); //string obj.mtype = subtype; //消息子类型 obj.targetid = targetid; obj.trace_id = `sdk_1.0_${(new Date().getTime()).toString().padStart(16, '123456')}`; obj.message = content; obj.save = save; if (this.isInRoom) { this.sendStreamMsgInner(obj); } else { this.cachedSendMsg(obj); } }, //发送可以丢失的消息 sendUnimportantStreamMsg: function (subtype, content, targetid, save) { //subtype : stream / class(save) if (!this.isInRoom || !this.socket) { return; } var obj = {}; obj.index = this.normalMsgIndex++; //int 不关心 obj.uuid = ENTRY.getMsgKey(obj.index); //string obj.mtype = 'lowquality'; //消息子类型 obj.targetid = targetid; obj.message = content; obj.save = save; this.socket.emit('stream', obj, function (status) {}); }, sendStreamMsgInner: function (obj) { try { var logmsg = 'send:' + obj.index; logmsg += ',msg:'; logmsg += obj.message; logmsg += ',uuid:'; logmsg += obj.uuid; ENTRY.recordLogToFile('info', logmsg); this.addToCheckList(obj); this.socket.emit('stream', obj, function (status) { if (status.code == 0) { CONTROL.setSendSuccess(obj); //提示开始上课/结束上课 var curtime = new Date(); var timeseconds = curtime.getTime() + CONTROL.timedeviation; var timestr = ''; timestr += timeseconds; obj.unixtime = timestr; CONTROL.displayClassOperate(obj); } var logmsg = 'control:send ack:' + obj.index; ENTRY.recordLogToFile('info', logmsg); }); } catch (e) { ENTRY.recordException(e); } //CONTROL.displayLogMsg(logmsg1);//删掉 }, //发送普通控制消息 string string string 暂时没有使用此事件 sendCommonMsg: function (subtype, content, targetid) { //subtype:netstate var obj = {}; obj.index = ENTRY.getMsgIndex(); //int obj.mtype = subtype; //消息子类型 obj.targetid = targetid; obj.message = content; //obj.save = save;//暂时没有save字段 this.socket.emit('common', obj, function (status) {}); }, //发送可以丢失的消息 sendLowQualityMsg: function (subtype, content, targetid) { //subtype : stream / class(save) if (!this.isInRoom || !this.socket) { return; } var obj = {}; obj.index = this.normalMsgIndex++; //int 不关心 obj.uuid = ENTRY.getMsgKey(obj.index); //string obj.mtype = subtype; //消息子类型 obj.targetid = targetid; obj.message = content; this.socket.emit('netstate', obj, function (status) {}); }, //获取当前在线列表 老师身份的才有效 getOnlineList: function () { try { this.socket.emit('listeners', {}, function (status) { if (status.code != 0) { //if(w.onlinemeber && w.onlinemeber.onRecvMemberDataFinished){ // w.onlinemeber.onRecvMemberDataFinished(); //} ENTRY.recordLogToFile('error', 'get online list failed!'); } }); } catch (e) { ENTRY.recordException(e); } }, //请求stream历史消息 getHistoryList: function () { try { ENTRY.recordLogToFile('info', 'emit history_message!'); this.socket.emit('history_message', {}, function (para) { ENTRY.recordLogToFile('info', 'history_message ret!'); if (para.code == 0) { try { //只处理开始上课、结束上课消息 var length = para.data.history_list.length; for (var i = 0; i < length; ++i) { var msgobj = para.data.history_list[i]; CONTROL.displayClassOperate(msgobj); } //end of for //通知学生端 最后一条课程状态消息 if (length > 0 && CONTROL.role == '2' && w.controlObj && w.controlObj.onReciveStream) { for (var j = length - 1; j >= 0; j--) { var msgobjclass = para.data.history_list[j]; if (msgobjclass.mtype == 'class') { w.controlObj.onReciveStream(msgobjclass.mtype, msgobjclass.message); break; } } } //通知老师端,开始上课和结束上课有没有 var startclassExist = 'false'; var endclassExist = 'false'; if (w.controlObj && w.controlObj.onKeyMessageSent) { for (var i = 0; i < length; i++) { var msgobjclass = para.data.history_list[i]; if (msgobjclass.mtype == 'class') { var cmdobj = JSON.parse(msgobjclass.message); if (cmdobj && cmdobj.cmdtype == 'start') { startclassExist = 'true'; } if (cmdobj && cmdobj.cmdtype == 'end') { endclassExist = 'true'; } } } w.controlObj.onKeyMessageSent('startClass', startclassExist); w.controlObj.onKeyMessageSent('endClass', endclassExist); } //end of if } catch (e) { ENTRY.recordException(e); } } //end of if }); } catch (e) { ENTRY.recordException(e); } }, //监听消息 OnUserDefineMsg: function (msg) { //进入教室成功 this.socket.on('join_room', function (obj) { CONTROL.dealJoinMsg(obj); }); //离开教室 this.socket.on('leave_room', function (obj) { CONTROL.dealleaveRoomMsg(obj); }); //监测网络状态 this.socket.on('pong', function (time) { if (w.controlObj && controlObj.onPong) { controlObj.onPong('' + time); } }); //接收流控制消息 this.socket.on('stream', function (obj) { CONTROL.dealStreamMsg(obj, false); }); this.socket.on('netstate', function (obj) { CONTROL.dealNetStateMsg(obj); }); this.socket.on('award', function (obj) { CONTROL.dealAwardMsg(obj); //奖励通知 }); this.socket.on('streamservicetype', function (obj) { CONTROL.dealStreamServiceTypeMsg(obj); //切换SDK通知 }); this.socket.on('force_exit', function (obj) { //被踢通知 CONTROL.displayLogMsg('force_exit'); //删掉 CONTROL.dealForceExit(obj); }); this.socket.on('room_expired', function (obj) { //房间过期 CONTROL.dealRoomExpired(obj); }); //接收监课状态消息 this.socket.on('supervisor', function (obj) { CONTROL.dealSupervisorMsg(obj); }); //收到在线列表消息 this.socket.on('listeners', function (obj) { if (w.onlinemeber && w.onlinemeber.onRecvOnlinMemberData) { var objstr = JSON.stringify(obj); w.onlinemeber.onRecvOnlinMemberData(objstr); } }); //掉线通知 this.socket.on('offline', function (obj) { CONTROL.dealOfflineMsg(obj); CONTROL.isOffline = true; //channel_disconnect 信道离线,退出房间 CONTROL.dealChannelMsg({ type: 'user_disconnect', data: { userId: +obj.userid, userName: obj.username, role: obj.role } }); ENTRY.recordLogToFile('info', `sdk notice user_disconnect role: ${obj.role} userId : ${obj.userid} userName: ${obj.username}`); ENTRY.recordLogToFile('info', `offline ${JSON.stringify(obj)}`); }); //收到异常状态消息 this.socket.on('unusual', function (obj) { if (obj && w.controlObj && w.controlObj.onRecvUnusualNOtify) { w.controlObj.onRecvUnusualNOtify(JSON.stringify(obj)); } }); //供参考,实测并不完全符合:客户端socket发起连接时的顺序。当第一次连接时,事件触发顺序为:connecting->connect; // 当失去连接时,事件触发顺序为:disconnect->reconnecting(可能进行多次)->connecting->reconnect->connect。 this.socket.on('reconnecting', function () { CONTROL.displayLogMsg('control: reconnecting!'); }); //并不会回调这个 this.socket.on('connecting', function () { CONTROL.displayLogMsg('control: connecting!'); }); //监听连接失败 this.socket.on('connect_failed', function (o) { CONTROL.displayLogMsg('control:connectfailed'); try { dataReport.joinRoomResult({ code: '3', cloud_api_response: window.zby_sdk_cloud_data, cloud_api_url: `${getApiCloudBaseUrl()}/rtccloud/class/init`, chat_url: window.zby_sdk_cloud_data.chatUrl }); } catch (e) {}; }); //目前没发现,什么情况下会回调这个 this.socket.on('error', function () { CONTROL.displayLogMsg('control:soceket错误!'); }); //并不会回调这个:自动重连成功后,会调用connect this.socket.on('reconnect', function () { CONTROL.displayLogMsg('control:reconnect!'); }); //并不会回调这个 this.socket.on('reconnect_failed', function () { CONTROL.displayLogMsg('control:reconnect_failed!'); }); }, //处理登录消息 dealJoinMsg: function (msgobj) { if (w.onlinemeber && w.onlinemeber.onRecvMemberStateChange) { try { var objuser = {}; objuser.online = 'login'; objuser.username = msgobj.username; objuser.userid = msgobj.userid; objuser.role = ENTRY.translateRoleBack(msgobj.role); if (msgobj.hasOwnProperty('avatar')) { objuser.avatar = msgobj.avatar; } if (msgobj.hasOwnProperty('device')) { objuser.device = msgobj.device; } var jsonstr = JSON.stringify(objuser); w.onlinemeber.onRecvMemberStateChange(jsonstr); } catch (e) { ENTRY.recordException(e); } } var logmsg = 'control:收到join_room:' + msgobj.userid; ENTRY.recordLogToFile('info', logmsg); this.displayLogMsg(logmsg); CONTROL.notifyMembernumChanged(msgobj); }, //处理退出登录消息 dealleaveRoomMsg: function (msgobj) { try { //{'unixtime':'32435435','userid':'username','username':'Falcon','role':'1'} var logmsg = 'control:收到leaveRoom_message:'; logmsg += JSON.stringify(msgobj); CONTROL.displayLogMsg(logmsg); ENTRY.recordLogToFile('info', logmsg); var isme = ENTRY.isMyMsg(msgobj); if (isme) { //这个应该不会发生 ENTRY.recordLogToFile('error', 'receive self leaveroom ack'); } else { if (w.onlinemeber && w.onlinemeber.onRecvMemberStateChange) { var objuser = {}; objuser.online = 'logout'; objuser.userid = msgobj.userid; objuser.role = ENTRY.translateRoleBack(msgobj.role); var jsonstr = JSON.stringify(objuser); w.onlinemeber.onRecvMemberStateChange(jsonstr); } } } catch (e) { ENTRY.recordException(e); } CONTROL.notifyMembernumChanged(msgobj); }, notifyMembernumChanged: function (obj) { try { //非一对一模式下,需要通知应用层成员数量 if (obj.hasOwnProperty('onlinecounts')) { if (w.controlObj && controlObj.onMemberChanged && ENTRY.isLargeClass()) { controlObj.onMemberChanged(obj.onlinecounts); } var logmsg = 'control:online count:'; logmsg += obj.onlinecounts; this.displayLogMsg(logmsg); } } catch (e) { ENTRY.recordException(e); } }, //处理断开连接消息 dealOfflineMsg: function (msgobj) { try { var logmsg = 'control:收到disconnect_message'; logmsg += JSON.stringify(msgobj); CONTROL.displayLogMsg(logmsg); ENTRY.recordLogToFile('info', logmsg); var isme = ENTRY.isMyMsg(msgobj); if (!isme) { if (w.onlinemeber && w.onlinemeber.onRecvMemberStateChange) { var objuser = {}; objuser.online = 'offline'; objuser.userid = msgobj.userid; objuser.role = ENTRY.translateRoleBack(msgobj.role); var jsonstr = JSON.stringify(objuser); w.onlinemeber.onRecvMemberStateChange(jsonstr); } CONTROL.notifyMembernumChanged(msgobj); } } catch (e) { ENTRY.recordException(e); } }, //处理流控制消息 dealStreamMsg: function (obj, history) { if (!obj) { return; } if (CONTROL.judgeAlreadyRecv(obj, history)) { var logmsg = 'recv repeat msg:' + JSON.stringify(obj); ENTRY.recordLogToFile('info', logmsg); CONTROL.displayLogMsg(logmsg); //删除 return; } //如果现在正在请求缓存队列,先缓存收到的正常消息 if (this.cacheReqSend) { obj.history = history; if (obj.hasOwnProperty('cache') && obj.cache == 1) { this.recvCacheList.push(obj); var logmsg1 = 'recv:cache msg' + JSON.stringify(obj); ENTRY.recordLogToFile('info', logmsg1); CONTROL.displayLogMsg(logmsg1); //删除 } else { this.recvTmpList.push(obj); var logmsg2 = 'recv:cache normal msg' + JSON.stringify(obj); ENTRY.recordLogToFile('info', logmsg2); CONTROL.displayLogMsg(logmsg2); //删除 } return; } CONTROL.dealStreamMsgInner(obj, history); this.checkToIntException(obj); }, //消息去重处理 judgeAlreadyRecv: function (obj, history) { //缓存队列 if (!history && obj.hasOwnProperty('uuid') && obj.uuid != '') { if (this.recvmsgKeys.indexOf(obj.uuid) != -1) { return true; } else { this.recvmsgKeys.push(obj.uuid); var timenow = new Date(); this.recvmsgTimes.push(timenow); //var logmsg = 'recv msg,time now:'+timenow.getTime(); //CONTROL.displayLogMsg(logmsg); } CONTROL.reduceRecvMsgList(); } return false; }, //缩减接收消息列表的长度,防止超长 reduceRecvMsgList: function () { try { if (this.recvmsgTimes.length < 1000) { return; } var timenow = new Date(); var timeex = 3 * 60 * 1000; var index = 0; for (index = 0; index < this.recvmsgTimes.length; ++index) { if ((timenow.getTime() - this.recvmsgTimes[index].getTime()) < timeex) { break; } } if (index >= 1) { this.recvmsgTimes.splice(0, index); this.recvmsgKeys.splice(0, index); //var logmsg = 'reduce key list: index'; //logmsg += index; //logmsg += ',left:'; //logmsg += this.recvmsgKeys.length; //CONTROL.displayLogMsg(logmsg);//删除 } } catch (e) { ENTRY.recordException(e); } }, //处理流控制消息 dealStreamMsgInner: function (obj, history) { try { ////测试丢消息、保序和时延 //if(!history){ // this.checkechobacktest(obj); //} //var logmsg1 = 'deal stream:'+obj.index + ' '+obj.userid; if (obj.mtype != 'lowquality') { var logmsg1 = 'recv:' + obj.index; logmsg1 += ',msg:'; logmsg1 += obj.message; logmsg1 += ',uuid:'; logmsg1 += obj.uuid; logmsg1 += ',utime:'; logmsg1 += obj.unixtime; ENTRY.recordLogToFile('info', logmsg1); } /*******专用处理信道中流信息相关功能 start ********/ if (!history) { const data = JSON.parse(obj.message); // window.zby_sdk_init_params为用户初始化sdk传递的基础参数 const baseInfo = { roomId: window.zby_sdk_init_params.roomId, userId: CONTROL.userid, userName: CONTROL.username, institutionId: window.zby_sdk_init_params.institutionId, targetId: data.actorId, role: CONTROL.role === '1' ? 'teacher' : 'student' }; dealReceiveStreamMsg(data, baseInfo); } /*******专用处理信道中流信息相关功能 end ********/ if (!history && w.controlObj && controlObj.onReciveStream) { controlObj.onReciveStream(obj.mtype, obj.message); } //提示上下课 CONTROL.displayClassOperate(obj); if (w.CHATEST && w.CHATEST.checkorder == 1) { CONTROL.checkOrder(obj); } } catch (e) { ENTRY.recordException(e); } }, //处理监课消息 dealSupervisorMsg: function (obj) { if (!obj) { return; } try { if (obj.mtype == 'help_state') { //var message = obj.message; var helpobj = obj.message; if (this.roomid != helpobj.lessonid || this.userid != helpobj.userid) { return; } //求助,机构后台处理后,推送此消息,收到后上报客户端 if (w.controlObj && w.controlObj.onSupervisorState) { var objformat = {}; objformat.mtype = 'help_state'; objformat.status = helpobj.supervisorStatus; objformat.resolve = helpobj.supervisorResolve; w.controlObj.onSupervisorState(JSON.stringify(objformat)); } } } catch (e) { ENTRY.recordException(e); } }, //处理网络状态消息 dealNetStateMsg: function (obj) { if (obj && w.controlObj && w.controlObj.onReciveLowQualityMsg) { w.controlObj.onReciveLowQualityMsg(obj.mtype, obj.message); } }, //处理奖励消息 dealAwardMsg: function (obj) { if (obj && w.controlObj && w.controlObj.onServerPushMessage) { w.controlObj.onServerPushMessage('award', JSON.stringify(obj.message)); } }, //处理切换SDK消息 dealStreamServiceTypeMsg: function (obj) { console.log('%c接收到信道广播的切换SDK消息,切换到:' + obj.message.data, 'color:blue'); ENTRY.recordLogToFile('info', 'channel msg: streamservicetype, change sdk to ' + obj.message.data); const data = obj.message; if (window.current_sdk_type !== data.data) { zbysdk.changeSDK(); } else { console.log('%c接收到信道广播目标SDK和本地一致,不切换:' + obj.message.data, 'color:blue'); ENTRY.recordLogToFile('info', 'channel target sdk type is the same as the local sdk ,will not change sdk to :' + obj.message.data); } }, //处理被踢消息 dealForceExit: function (obj) { if (ENTRY.istestFromhtml == 1) { CONTROL.displayLogMsg('您已被踢'); ENTRY.recordLogToFile('info', 'deal with force exit'); CONTROL.leaveRoom(); if (w.CHAT && ENTRY.channeltype == 0) { w.CHAT.leaveRoom(); } } if (w.controlObj && w.controlObj.onServerPushMessage) { w.controlObj.onServerPushMessage('force_exit', ''); } }, //处理房间过期消息 dealRoomExpired: function (obj) { if (w.controlObj && w.controlObj.onServerPushMessage) { w.controlObj.onServerPushMessage('room_expired', ''); } }, //处理断线后重连 dealReconnectSuccess: function () { ENTRY.recordLogToFile('info', 'deal reconnect success'); CONTROL.displayLogMsg('deal reconnect success'); //删掉 CONTROL.resendMessages(); CONTROL.requestCachedMsg(); }, //断线重连后,请求缓存队列 requestCachedMsg: function () { var uuid = ''; if (this.recvmsgKeys.length > 0) { uuid = this.recvmsgKeys[this.recvmsgKeys.length - 1]; } this.cacheReqSend = true; var obj = {}; obj.uuid = uuid; var logmsg = 'requestCachedMsg:' + obj.uuid; CONTROL.displayLogMsg(logmsg); //删除 ENTRY.recordLogToFile('info', logmsg); this.socket.emit('cache_message', obj, function (para) { logmsg = 'requestCachedMsg: ack ' + para.code; CONTROL.displayLogMsg(logmsg); //删除 ENTRY.recordLogToFile('info', logmsg); CONTROL.cacheReqSend = false; if (para.code == 0) { CONTROL.dealRecvCache(); } else { ENTRY.recordLogToFile('error', 'request cache msg ret failed!'); } }); }, //处理缓存的收到但未处理的消息 dealRecvCache: function () { var reportobj = {}; reportobj.count = this.recvCacheList.length; for (var i = 0; i < this.recvCacheList.length; ++i) { CONTROL.dealStreamMsgInner(this.recvCacheList[i], this.recvCacheList[i].history); } ENTRY.recordLogToFile('info', 'dealRecvCache:server cached num:' + this.recvCacheList.length); this.recvCacheList.length = 0; for (var j = 0; j < this.recvTmpList.length; ++j) { CONTROL.dealStreamMsgInner(this.recvTmpList[j], false); } ENTRY.recordLogToFile('info', 'dealRecvCache:local cached num:' + this.recvTmpList.length); this.recvTmpList.length = 0; //上报积压消息情况 if (w.controlObj && w.controlObj.onSelfHealthReport) { w.controlObj.onSelfHealthReport('servercachedcount', JSON.stringify(reportobj)); } }, //保序验证 checkOrder: function (obj) { //CONTROL.displayLogMsg('recv:'+obj.index); if (CONTROL.lastRecvMsgId == null) { CONTROL.lastRecvMsgId = new Array(); } ++CONTROL.testrecvcount; var curtime = new Date(); CONTROL.displayLogMsg('recv:' + curtime.toTimeString() + ',' + CONTROL.testrecvcount); var userFind = false; for (var i = 0; i < CONTROL.lastRecvMsgId.length; ++i) { if (CONTROL.lastRecvMsgId[i].userid == obj.userid) { userFind = true; if (CONTROL.lastRecvMsgId[i].index > obj.index) { CONTROL.displayLogMsg('control msg disorder,userid:' + obj.userid + ',' + obj.index); if (w.CHATEST) { w.CHATEST.setOrderCheckResult('message disorder'); } } if (CONTROL.lastRecvMsgId[i].index + 1 != obj.index) { CONTROL.displayLogMsg('control msg lost,userid:' + obj.userid + ',' + CONTROL.lastRecvMsgId[i].index + ' to ' + obj.index); if (w.CHATEST) { w.CHATEST.setOrderCheckResult('message lost'); } } if (obj.index % 30 == 0) { CONTROL.displayLogMsg('recv,useri:' + obj.userid + ',' + obj.index); } CONTROL.lastRecvMsgId[i].index = obj.index; break; } } if (!userFind) { var tmpobj = {}; tmpobj.index = obj.index; tmpobj.userid = obj.userid; CONTROL.lastRecvMsgId.push(tmpobj); } }, //初始化缓存队列相关 initCacheList: function () { try { this.streamIndex = 0; this.normalMsgIndex = 0; if (!this.sendCheckList) { this.sendCheckList = new Array(); } else { this.sendCheckList.length = 0; } if (!this.sendCacheList) { this.sendCacheList = new Array(); } else { this.sendCacheList.length = 0; } this.cacheReqSend = false; if (!this.recvCacheList) { this.recvCacheList = new Array(); } else { this.recvCacheList.length = 0; } if (!this.recvTmpList) { this.recvTmpList = new Array(); } else { this.recvTmpList.length = 0; } if (!this.recvmsgKeys) { this.recvmsgKeys = new Array(); } else { this.recvmsgKeys.length = 0; } if (!this.recvmsgTimes) { this.recvmsgTimes = new Array(); } else { this.recvmsgTimes.length = 0; } if (!this.orderCheck) { this.orderCheck = new Array(); } else { this.orderCheck.length = 0; } if (!this.delaycheckList) { this.delaycheckList = new Array(); } else { this.delaycheckList.length = 0; } if (!this.delayList) { this.delayList = new Array(); } else { this.delayList.length = 0; } this.checkSendSucessTimer = w.setInterval(this.checkStreamMsgSendSuccess, this.checkInterval); } catch (e) { ENTRY.recordException(e); } }, //反初始化缓存队列相关 unInitCacheList: function () { this.streamIndex = 0; this.normalMsgIndex = 0; this.sendCacheList = null; this.cacheReqSend = false; this.recvCacheList = null; this.recvTmpList = null; this.recvmsgKeys = null; this.recvmsgTimes = null; this.orderCheck = null; this.delaycheckList = null; this.delayList = null; if (this.checkSendSucessTimer) { w.clearInterval(this.checkSendSucessTimer); this.checkSendSucessTimer = null; } this.sendCheckList = null; }, //缓存要发送的消息 cachedSendMsg: function (obj) { try { this.sendCacheList.push(obj); } catch (e) { ENTRY.recordException(e); } }, //添加到确认队列 addToCheckList: function (obj) { try { var checkobj = {}; checkobj.data = obj; checkobj.time = new Date(); checkobj.success = false; this.sendCheckList.push(checkobj); } catch (e) { ENTRY.recordException(e); } }, //收到确认 setSendSuccess: function (obj) { try { for (var i = 0; i < CONTROL.sendCheckList.length; ++i) { if (CONTROL.sendCheckList[i].data.index == obj.index) { CONTROL.sendCheckList[i].success = true; break; } } //缓存队列:清理 var length = CONTROL.sendCheckList.length; while (length > 0 && CONTROL.sendCheckList[0].success) { CONTROL.sendCheckList.splice(0, 1); length = CONTROL.sendCheckList.length; } } catch (e) { ENTRY.recordException(e); } }, //检查消息是否发送成功 checkStreamMsgSendSuccess: function () { try { var length = CONTROL.sendCheckList.length; if (length > 0 && this.isInRoom) { var curtime = new Date(); if (curtime.getTime() - CONTROL.sendCheckList[length - 1].time.getTime() > CONTROL.maxAckDelay) { CONTROL.resendMessages(); ////todo 缓存队列过长的话,断开重新连接 if (length > 200) { var logmsg01 = 'check message to much:' + length; ENTRY.recordLogToFile('error', logmsg01); } var logmsg = 'resend messages withou ack:' + length; ENTRY.recordLogToFile('info', logmsg); CONTROL.displayLogMsg(logmsg); //删掉 ENTRY.reportHintInfo(logmsg); } } } catch (e) { ENTRY.recordException(e); } }, //重新发送缓存的消息 resendMessages: function () { try { var reportobj = {}; var length = CONTROL.sendCheckList.length; reportobj.unconfirmcount = length; if (length > 0) { var sendlist = CONTROL.sendCheckList.slice(0); CONTROL.sendCheckList.length = 0; for (var i = 0; i < sendlist.length; ++i) { this.sendStreamMsgInner(sendlist[i].data); } } var cachedlenth = this.sendCacheList.length; reportobj.count = length + this.sendCacheList.length; for (var i = 0; i < this.sendCacheList.length; ++i) { this.sendStreamMsgInner(this.sendCacheList[i]); } this.sendCacheList.length = 0; //上报积压消息情况 if (w.controlObj && w.controlObj.onSelfHealthReport) { w.controlObj.onSelfHealthReport('resendcount', JSON.stringify(reportobj)); } var logmsg = 'resent message,checklist:'; logmsg += length; logmsg += ',cached:'; logmsg += cachedlenth; ENTRY.recordLogToFile('info', logmsg); CONTROL.displayLogMsg(logmsg); //删掉 } catch (e) { ENTRY.recordException(e); } }, //显示开始上课/结束上课消息 displayClassOperate: function (msgobj) { try { if (msgobj.mtype == 'class') { var classobj = JSON.parse(msgobj.message); if (!msgobj.hasOwnProperty('unixtime')) { return; } //只用control通道的话,需要通知chat通道去显示,否则直接在聊天页面显示 if (ENTRY.channeltype == 1) { if (w.controlObj && w.controlObj.onDisplayClassOperate) { w.controlObj.onDisplayClassOperate(classobj.cmdtype, msgobj.unixtime); } } else { if (w.CHATDIS) { w.CHATDIS.disClassStatusHint(classobj.cmdtype, msgobj.unixtime); } } } //end of if } catch (e) { ENTRY.recordException(e); } }, //检查异常 checkToIntException: function (obj) { try { var uttime = obj.unixtime.substr(0, obj.unixtime.length - 3); var timeint = parseInt(uttime); //todo 检查到底怎么回事 var timeint = ENTRY.parseIntMySimple(uttime); if (!(timeint.toString() == uttime)) { ENTRY.recordLogToFile('error', 'parseint error'); CONTROL.displayLogMsg('parseint error'); //删掉 } } catch (e) {} }, //发送流控制消息 string string string string sendStreamMsgTest: function (subtype, content, targetid, save) { //subtype : stream / class(save) var curtime = new Date(); var obj = {}; obj.index = this.streamIndex++; //int obj.uuid = ENTRY.getMsgKey(obj.index); //string obj.mtype = subtype; //消息子类型 obj.targetid = targetid; obj.message = content; obj.save = save; obj.echotest = 1; obj.sendtime = curtime.getTime(); obj.checkindex = obj.index; var objtest = {}; objtest.checkindex = obj.index; objtest.sendtime = obj.sendtime; this.delaycheckList.push(objtest); if (this.isInRoom) { this.sendStreamMsgInner(obj); } else { this.cachedSendMsg(obj); } }, checkechobacktest: function (obj) { //subtype : stream / class(save) var curtime = new Date(); var index = 0; if (!obj.hasOwnProperty('echotest')) { return; } for (index = 0; index < this.delaycheckList.length; ++index) { if (obj.checkindex > this.delaycheckList[index].checkindex) { break; } if (obj.checkindex == this.delaycheckList[index].checkindex) { ; } } }, getPingPongArr: function () { setTimeout(function () { CONTROL.pingPongArr = []; }, 1); return CONTROL.pingPongArr; } }; export default CONTROL; // })();