zby-live-sdk
Version:
This is a live SDK for weclassroom.
1,569 lines (1,391 loc) • 50.6 kB
JavaScript
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;
// })();