李勇

1.增加视频模块AudioApe

  1 +// //////////////////////////////////////////////////////////////////////////////
  2 +//音频模块
  3 +// //////////////////////////////////////////////////////////////////////////////
  4 +
  5 +import Ape from './Ape';
  6 +import ApeConsts from './ApeConsts';
  7 +import pdu from 'pdus';
  8 +import Loger from 'Loger';
  9 +import MessageTypes from 'MessageTypes';
  10 +import GlobalConfig from 'GlobalConfig';
  11 +import EngineUtils from 'EngineUtils';
  12 +import MediaModule from "./MediaModule";
  13 +
  14 +let loger = Loger.getLoger('AudioApe');
  15 +
  16 +class AudioApe extends Ape {
  17 + constructor() {
  18 + super(
  19 + ApeConsts.AUDIO_SESSION_ID,
  20 + ApeConsts.AUDIO_SESSION_NAME,
  21 + ApeConsts.AUDIO_SESSION_TAG
  22 + );
  23 +
  24 + this.mediaModule=new MediaModule();
  25 + this.mediaModule.MEDIA_OBJ_TABLE_ID=ApeConsts.AUDIO_OBJ_TABLE_ID;
  26 + this.mediaModule.mediaChannels={};
  27 + // Ape Models
  28 + this.registerKey(this._session_id, this._session_name, this._session_tag, new ArrayBuffer);
  29 + this.registerObj(pdu.RCPDU_REG_REGISTER_TABLE, ApeConsts.AUDIO_OBJ_TABLE_ID, ApeConsts.AUDIO_OBJ_TABLE_NAME, ApeConsts.AUDIO_OBJ_TABLE_TAG, 0, new ArrayBuffer);
  30 +
  31 + // 广播消息,用户之间的消息传递
  32 + this.on(pdu.RCPDU_AUDIO_SEND_DATA_REQUEST, this.receiveAudiooCommandHandler.bind(this));
  33 + }
  34 + //ape加入成功
  35 + onJoinChannelHandlerSuccess(){
  36 + //这个设置很重要,因为只有Sass流程完成之后,APE才能取得GlobalConfig中的数据
  37 + this.mediaModule.maxMediaChannel=GlobalConfig.maxAudioChannels;
  38 + }
  39 +
  40 + /////////////发送数据操作////////////////////////////////////////////
  41 + //获取播流地址
  42 + getAudioPlayPath(_param) {
  43 + loger.log('getAudioPlayPath');
  44 + return this.mediaModule.getMediaPlayPath(_param);
  45 + }
  46 +
  47 + //获取推流地址
  48 + getAudioPublishPath(_param) {
  49 + loger.log('getAudioPublishPath');
  50 + return this.mediaModule.getMediaPublishPath(_param);
  51 + }
  52 +
  53 + //推流
  54 + publishAudio(_param) {
  55 + if (_param == null||_param.channelId == null||
  56 + _param.classId == null||_param.userId == null||
  57 + _param.siteId == null|| _param.timestamp==null)
  58 + {
  59 + loger.warn('publishAudio,参数错误', _param);
  60 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
  61 + return {"code": 1, "data": ""};
  62 + }
  63 +
  64 + //同一个nodeId只允许推一个流,如果已经推了就不能再推
  65 + if(this.mediaModule.getOpeningMediaChannel(GlobalConfig.nodeId)!=0){
  66 + loger.warn("publishVideo,已经存在一个流,不能再推");
  67 + return;
  68 + }
  69 +
  70 + //判断当前是否还有空闲的channle
  71 + let freeChannel = this.mediaModule.getFreeMediaChannel();
  72 + if (freeChannel == 0) {
  73 + loger.warn("publishAudio,没有空闲的channel ");
  74 + return {"code": 1, "data": "不能再打开更多的设备"};
  75 + }
  76 +
  77 + //判断当前的频道是否已经占用
  78 + if(this.mediaModule.checkChannelIsOpening(_param.channelId)){
  79 + loger.warn(_param.channelId,"频道已经被占用");
  80 + return {"code":1,"data":"频道已经被占用!"};
  81 + }
  82 +
  83 + let channelInfo={};
  84 + channelInfo.status=ApeConsts.CHANNEL_STATUS_OPENING;
  85 + channelInfo.fromNodeId=GlobalConfig.nodeId;
  86 + channelInfo.channelId=_param.channelId;//freeChannel
  87 + channelInfo.timestamp=_param.timestamp;//EngineUtils.creatTimestamp();
  88 + channelInfo.classId=_param.classId;//GlobalConfig.classId;
  89 + channelInfo.siteId=_param.siteId;//GlobalConfig.siteId;
  90 + channelInfo.toNodeId=0;
  91 + channelInfo.mediaType=ApeConsts.MEDIA_TYPE_AUDIO;
  92 + this.sendTableUpdateHandler(channelInfo);
  93 + return {"code":0,"data":"推流成功!"}
  94 + }
  95 +
  96 + //停止推流,
  97 + stopPublishAudio(_param) {
  98 + loger.log('stopPublishAudio');
  99 + //_param如果为空,那么默认就是当前自己的nodeId,否则用_param
  100 + let nodeId;
  101 + if(_param&&parseInt(_param.nodeId)>=0){
  102 + nodeId=parseInt(_param.nodeId);
  103 + }else {
  104 + nodeId=GlobalConfig.nodeId;
  105 + }
  106 +
  107 + let openingChannel = this.mediaModule.getOpeningMediaChannel(nodeId);
  108 + if (openingChannel == 0) {
  109 + loger.warn(nodeId,"stopPublishAudio,没有打开的channel,不需要关闭");
  110 + return {"code": 1, "data": "没有打开的channel,不需要关闭"};
  111 + }
  112 +
  113 + let channelInfo={};
  114 + channelInfo.status=ApeConsts.CHANNEL_STATUS_RELEASED;
  115 + channelInfo.fromNodeId=0;
  116 + channelInfo.channelId=openingChannel;
  117 + channelInfo.timestamp=0;
  118 + channelInfo.classId=GlobalConfig.classId;
  119 + channelInfo.toNodeId=0;
  120 + channelInfo.mediaType=ApeConsts.MEDIA_TYPE_DEFAULT;
  121 + this.sendTableUpdateHandler(channelInfo);
  122 + }
  123 +
  124 + sendAudioBroadcastMsg(_param) {
  125 + if (this._classInfo === null || EngineUtils.isEmptyObject(this._classInfo)) {
  126 + loger.log('sendAudioBroadcastMsg.McuClient还未初始化数据!');
  127 + if (GlobalConfig.getCurrentStatus().code == 0 || GlobalConfig.getCurrentStatus().code == 1) {
  128 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_SEND_FAILED_NO_JOIN);
  129 + return {"code": 1, "data": "sendAudioBroadcastMsg.McuClient还未初始化数据"};
  130 + }
  131 + return {"code": 1, "data": "sendAudioBroadcastMsg.McuClient还未初始化数据"};
  132 + }
  133 + if (_param == null) {
  134 + loger.warn('sendAudioBroadcastMsg,参数错误', _param);
  135 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
  136 + return {"code": 1, "data": "sendAudioBroadcastMsg,参数错误"};
  137 + ;
  138 + }
  139 + // to, message
  140 + loger.log('发送Audio消息.', _param);
  141 +
  142 + if (_param.actionType != null && _param.actionType == ApeConsts.MEDIA_ACTION_OPEN_CAMERA) {
  143 + //判断当前开启的视频数量是否已经是最大值,如果已经是最大值,不能再开启
  144 + let freeChannel = this.mediaModule.getFreeMediaChannel();
  145 + if (freeChannel == 0) {
  146 + loger.warn('sendAudioBroadcastMsg,不能再打开更多的设备', _param);
  147 + return {"code": 1, "data": "不能再打开更多的设备"};
  148 + }
  149 + }
  150 + /* message RCVideoSendDataRequestPdu {
  151 + required uint32 from_node_id = 1;//发起人
  152 + optional uint32 to_node_id = 2;//接收人,如果是0就是所有人都接收
  153 + optional uint32 actionType = 3;//消息指令类型;
  154 + optional bytes data = 4;//其他数据,这个根据actionType来确定数据的结构
  155 + }*/
  156 +
  157 + let audioSendPdu = new pdu['RCAudioSendDataRequestPdu'];
  158 + audioSendPdu.type = pdu.RCPDU_AUDIO_SEND_DATA_REQUEST;
  159 + audioSendPdu.isPublic = true;
  160 +
  161 + audioSendPdu.fromNodeId = GlobalConfig.nodeId;//发起人
  162 + audioSendPdu.toNodeId = parseInt(_param.toNodeID) || 0;//接收者,0就是所有人
  163 + audioSendPdu.actionType = parseInt(_param.actionType) || ApeConsts.MEDIA_ACTION_DEFAULT;
  164 +
  165 + audioSendPdu.data = this._rCArrayBufferUtil.strToUint8Array("h5" + _param.data);//开头两个字会乱码
  166 +
  167 + if (!audioSendPdu.isPublic && 0 != audioSendPdu.toNodeId) {
  168 + //发送给制定的人
  169 + loger.log('发送私聊消息.');
  170 + this.send(audioSendPdu);
  171 + } else {
  172 + //发送给所有人
  173 + loger.log('发送公聊消息.');
  174 + this.sendChatUniform(audioSendPdu);
  175 + }
  176 + return {"code": 0, "data": ""};
  177 + }
  178 +
  179 + sendTableUpdateHandler(_channelInfo) {
  180 + loger.log("audio,sendTableUpdateHandler ");
  181 + let updateModelPdu = this.packPdu(_channelInfo, _channelInfo.channelId);//let updateModelPdu=this.packPdu({},ApeConsts.VIDEO_OBJ_TABLE_ID+2);
  182 + if(updateModelPdu==null){
  183 + loger.warn("sendTableUpdateHandler error,updateModelPdu=null");
  184 + return;
  185 + }
  186 +
  187 + let tableItemPdu = new pdu['RCRegistryTableItemPdu'];
  188 + tableItemPdu.itemIdx = _channelInfo.channelId;//tableItemPdu.itemIdx=ApeConsts.VIDEO_OBJ_TABLE_ID+2;
  189 + tableItemPdu.owner = 0;//收到flash的是这个值,不清楚先写固定
  190 + tableItemPdu.itemData = updateModelPdu.toArrayBuffer();
  191 +
  192 + //insert
  193 + let tableInsertItemPdu = new pdu['RCRegistryTableUpdateItemPdu'];
  194 + //optional RCPduType_E type = 1 [default = RCPDU_REG_TABLE_UPDATE_PDU];
  195 + //repeated RCRegistryTableItemPdu items = 2;
  196 + tableInsertItemPdu.type = pdu.RCPDU_REG_TABLE_UPDATE_PDU;//
  197 + tableInsertItemPdu.items.push(tableItemPdu);
  198 +
  199 + let updateObjPdu = new pdu['RCRegistryUpdateObjPdu'];
  200 + updateObjPdu.objId = ApeConsts.AUDIO_OBJ_TABLE_ID;//
  201 + updateObjPdu.subType = tableInsertItemPdu.type;
  202 + updateObjPdu.userData = tableInsertItemPdu.toArrayBuffer();
  203 +
  204 + //同步
  205 + let adapterItemPdu = new pdu['RCAdapterItemPdu'];
  206 + adapterItemPdu.type = pdu.RCPDU_REG_UPDATE_OBJ;
  207 + adapterItemPdu.itemData = updateObjPdu.toArrayBuffer();
  208 +
  209 + let adapterPdu = new pdu['RCAdapterPdu'];
  210 + adapterPdu.type = pdu.RCPDU_REG_ADAPTER;
  211 + adapterPdu.item.push(adapterItemPdu);
  212 +
  213 + loger.log("发送更新AUDIO.itemIdx=" + tableItemPdu.itemIdx);
  214 + this.sendUniform(adapterPdu, true);
  215 + }
  216 +
  217 + /////收到消息处理//////////////////////////////////////////////////
  218 +
  219 + // 消息处理,内部处理,不需要告诉应用层
  220 + receiveAudiooCommandHandler(_data) {
  221 + let audioReceivePdu = pdu['RCAudioSendDataRequestPdu'].decode(_data);
  222 + if (audioReceivePdu == null) {
  223 + loger.warn("音频消息处理,收到的消息为null,不做处理");
  224 + return;
  225 + }
  226 + audioReceivePdu.data = this._rCArrayBufferUtil.uint8ArrayToStr(audioReceivePdu.data, 2);//开头两个字会乱码
  227 + loger.log('音频消息处理 receiveAudiooCommandHandler.', audioReceivePdu);
  228 +
  229 + //判断接收者的id,如果不是0,并且也不是自己的nodeId,那么消息不做处理
  230 + if (audioReceivePdu.toNodeId != 0 && audioReceivePdu.toNodeId != GlobalConfig.nodeId) {
  231 + loger.log('音频消息不处理 toNodeId=', audioReceivePdu.toNodeId, "my nodeId=", GlobalConfig.nodeId);
  232 + } else {
  233 + this._emit(MessageTypes.AUDIO_COMMAND, audioReceivePdu);
  234 + }
  235 + }
  236 +
  237 + tableUpdateHandler(owner, itemIdx, itemData) {
  238 + // debugger;
  239 + let videoChannelInfo = this.unPackPdu(owner, itemIdx, itemData);
  240 + //videoChannelInfo.owner = owner;
  241 + //videoChannelInfo.channelId = itemIdx;
  242 + //videoChannelInfo.status = owner === 0 ? ApeConsts.CHANNEL_STATUS_RELEASED : videoChannelInfo.status;
  243 + //loger.log('视频消息处理 tableUpdateHandler.',videoChannelInfo);
  244 + this.mediaModule.mediaChannels[itemIdx] = videoChannelInfo;
  245 +
  246 + this._emit(MessageTypes.AUDIO_UPDATE, videoChannelInfo);
  247 + /* switch (videoChannelInfo.status) {
  248 + case ApeConsts.CHANNEL_STATUS_RELEASED:
  249 + // 只能关闭自己的流
  250 + if (this.activeChannelId === videoChannelInfo.channelId) {
  251 + this.activeChannelId = 0;
  252 + this.activeURL = '';
  253 + this.emitVideoChange();
  254 + }
  255 + break;
  256 + case ApeConsts.CHANNEL_STATUS_OPENING:
  257 + //_playUrl = "rtmfp://" + Config.mediaServerAddr + ":" + Config.mediaServerPort + "/message/" + _streamName;
  258 + //_cdnUrl = "rtmp://" + Config.mediaCDNServerAddr + ":" + Config.mediaCDNServerPort + "/message/" + _streamName;
  259 + //this.activeChannelId = videoChannelInfo.channelId;
  260 + //// AMS/FMS
  261 + //if (this._classInfo.msType ==ApeConsts.MS_TYPE_FMS) {
  262 + // this.activeURL = `http://dazhi.3mang.com/live/${this._classInfo.classId}/${this._classInfo.classId}_${videoChannelInfo.channelId}_flash_cam_mic_aac/playlist.m3u8`;
  263 + //}else {
  264 + // this.activeURL = `http://hls.3mang.com/live/${this._classInfo.classId}_${videoChannelInfo.channelId}_flash_cam_mic_aac/playlist.m3u8`;
  265 + //}
  266 + // 任何人都可以打开流
  267 + this.emitVideoChange();
  268 + break;
  269 + default:
  270 + break;
  271 + }*/
  272 + }
  273 +
  274 + ///////数据的封包和解包/////////////////////////////////////////
  275 + packPdu(_param, _itemIdx) {
  276 + loger.log("packPdu ");
  277 + //验证坐标点集合数组是否合法
  278 + if (_param == null || _itemIdx == null) {
  279 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
  280 + return null;
  281 + }
  282 +
  283 + /* message RCVideoChannelInfoPdu {
  284 + optional uint32 status = 1;//开启的状态
  285 + optional uint32 channel_id = 2;//唯一的频道id
  286 + optional uint32 timestamp = 3;//更新的时间戳
  287 + optional uint32 from_node_id = 4;//发起者的id
  288 + optional uint32 to_node_id = 5;//接收者的id,(如果是0,所有人都接收)
  289 + }*/
  290 +
  291 + //判断type类型,根据type设置不同的参数
  292 + let packPduModel = new pdu['RCAudioChannelInfoPdu'];
  293 + packPduModel.status = _param.status||ApeConsts.CHANNEL_STATUS_RELEASED;
  294 + packPduModel.channelId = _itemIdx;
  295 + packPduModel.siteId=_param.siteId||GlobalConfig.siteId;//GlobalConfig.siteId;
  296 + packPduModel.classId =parseInt(_param.classId)||parseInt(GlobalConfig.classId);
  297 + packPduModel.userId =_param.userId||"0";
  298 + packPduModel.mediaType =_param.mediaType|| ApeConsts.MEDIA_TYPE_AUDIO;
  299 + packPduModel.timestamp =_param.timestamp||EngineUtils.creatTimestamp();
  300 + packPduModel.fromNodeId = GlobalConfig.nodeId;
  301 + packPduModel.toNodeId = 0;
  302 + console.log("packPdu",packPduModel);
  303 + return packPduModel;
  304 + }
  305 +
  306 + unPackPdu(owner, itemIdx, itemData) {
  307 + loger.log("unPackPdu ");
  308 + if (owner == null || itemIdx == null || itemData == null) {
  309 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
  310 + return null;
  311 + }
  312 + try {
  313 + let videoChannelInfo = pdu['RCAudioChannelInfoPdu'].decode(itemData);
  314 + loger.log("unPackPdu",videoChannelInfo);
  315 + return videoChannelInfo;
  316 + } catch (err) {
  317 + loger.log("unPackPdu error,itemIdx=" + itemIdx + " err:" + err.message);
  318 + }
  319 + return null;
  320 + }
  321 +
  322 +}
  323 +
  324 +export default AudioApe;
  325 +
  1 +// //////////////////////////////////////////////////////////////////////////////
  2 +// VideoApe、AudioApe 共用的方法单独提取处理
  3 +// //////////////////////////////////////////////////////////////////////////////
  4 +
  5 +import ApeConsts from './ApeConsts';
  6 +import Loger from 'Loger';
  7 +import MessageTypes from 'MessageTypes';
  8 +import GlobalConfig from 'GlobalConfig';
  9 +import EngineUtils from 'EngineUtils';
  10 +
  11 +let loger = Loger.getLoger('MediaModule');
  12 +
  13 +class MediaModule {
  14 + constructor() {
  15 + this.mediaChannels = {};
  16 + this.maxMediaChannel=0;
  17 + this.MEDIA_OBJ_TABLE_ID=0;
  18 + }
  19 +
  20 + //获取播流地址
  21 + getMediaPlayPath(_param) {
  22 + loger.log('getMediaPlayPath');
  23 + if (_param == null||_param.siteId == null||
  24 + _param.classId == null||_param.userId == null||
  25 + _param.channelId == null|| _param.timestamp==null)
  26 + {
  27 + loger.warn('getMediaPlayPath,参数错误', _param);
  28 + this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
  29 + return {"code": 1, "data": ""};
  30 + }
  31 +
  32 + let path = "";
  33 + let port="";
  34 + if (_param.type == "m3u8") {
  35 + //M3U8
  36 + //http://123.56.73.119:6001/hls/h5dev_403074980_0_983041_1487663265/index.m3u8
  37 + port = (GlobalConfig.RSServerPort == "" || GlobalConfig.RSServerPort == null) ? "":":" + GlobalConfig.RSServerPort;
  38 + path = "http://" + GlobalConfig.RSServerIP
  39 + + port + "/live/"
  40 + + _param.siteId
  41 + + "_" + _param.classId
  42 + + "_" + _param.userId
  43 + + "_" + _param.channelId
  44 + + "_" + _param.timestamp
  45 + + "/index.m3u8";
  46 + } else {
  47 + port = (GlobalConfig.MSServerPort == "" || GlobalConfig.MSServerPort == null) ? "":":" + GlobalConfig.MSServerPort;
  48 + path = "rtmp://" + GlobalConfig.MSServerIP
  49 + + port + "/live/"
  50 + + _param.siteId
  51 + + "_" + _param.classId
  52 + + "_" + _param.userId
  53 + + "_" + _param.channelId
  54 + + "_" + _param.timestamp;
  55 + }
  56 + return {"code": 0, "data": path};
  57 + }
  58 +
  59 + //获取推流地址
  60 + getMediaPublishPath(_param) {
  61 + loger.log('getMediaPublishPath');
  62 + //判断当前开启的视频数量是否已经是最大值,如果已经是最大值,不能再开启
  63 + let freeChannel = this.getFreeMediaChannel();
  64 + if (freeChannel == 0) {
  65 + return {"code": 1, "data": "不能再打开更多的设备"};
  66 + }
  67 +
  68 + //默认方式推流
  69 + let pubType="live";
  70 + //flash推流
  71 + if(_param&&_param.type=="flash"){
  72 + pubType ="flash";
  73 + }
  74 +
  75 + //端口,有端口就显示 ":xxx",没有端口就是""
  76 + let port = (GlobalConfig.MSServerPort == "" || GlobalConfig.MSServerPort == null) ? "":":" + GlobalConfig.MSServerPort;
  77 + //时间戳
  78 + let timestamp = EngineUtils.creatTimestamp();
  79 +
  80 + //生成推流地址和推流数据(同步数据的时候用)
  81 + let publishUrl = "rtmp://" + GlobalConfig.MSServerIP
  82 + + port + "/"+pubType+"/" +GlobalConfig.siteId+"_"
  83 + + GlobalConfig.classId + "_"+GlobalConfig.userId
  84 + +"_" + freeChannel + "_" + timestamp;
  85 + return {"code": 0,
  86 + "data":
  87 + { "siteId":GlobalConfig.siteId,
  88 + "classId":GlobalConfig.classId,
  89 + "userId":GlobalConfig.userId,
  90 + "channelId": freeChannel,
  91 + "timestamp": timestamp,
  92 + "publishUrl": publishUrl
  93 + }
  94 + };
  95 + }
  96 +
  97 + //获取当前空闲的channel,返回值为0代表没有空闲的,否则返回的就是空闲的channelId
  98 + getFreeMediaChannel() {
  99 + let counter = 0;
  100 + for (let key in this.mediaChannels) {
  101 + let item = this.mediaChannels[key];
  102 + if (item && item.status == ApeConsts.CHANNEL_STATUS_RELEASED) {
  103 + return item.channelId;
  104 + }
  105 + counter++;
  106 + }
  107 + loger.log("getFreeMediaChannel","maxMediaChannel",this.maxMediaChannel,"counter:",counter);
  108 + console.log(this.mediaChannels);
  109 + if (counter < this.maxMediaChannel) {
  110 + return this.MEDIA_OBJ_TABLE_ID + (counter);
  111 + }
  112 + return 0;//没有空闲的
  113 + }
  114 +
  115 + //获取当前属于nodeId的已经打开的的channel,返回值为0代表没有打开的,否则返回的就是打开的channelId
  116 + getOpeningMediaChannel(_nodeId){
  117 + if(_nodeId==null||_nodeId==0){
  118 + return 0;
  119 + }
  120 + for (let key in this.mediaChannels) {
  121 + let item = this.mediaChannels[key];
  122 + if (item && item.status == ApeConsts.CHANNEL_STATUS_OPENING&&item.fromNodeId==_nodeId) {
  123 + return item.channelId;
  124 + }
  125 + }
  126 + return 0;
  127 + }
  128 +
  129 + //检查频道是否已经被占用
  130 + checkChannelIsOpening(_channelId){
  131 + if(_channelId==null){
  132 + loger.warn("checkChannelIsOpening error,channel=",_channelId);
  133 + return true;
  134 + }
  135 + let channelInfo=this.mediaChannels[_channelId];
  136 + if(channelInfo==null||channelInfo.status==ApeConsts.CHANNEL_STATUS_RELEASED){
  137 + return false;
  138 + }
  139 + return true;
  140 + }
  141 +}
  142 +
  143 +export default MediaModule;
  144 +