diff --git a/src/EngineEntrance.js b/src/EngineEntrance.js
index 048d473..d5d5830 100644
--- a/src/EngineEntrance.js
+++ b/src/EngineEntrance.js
@@ -62,7 +62,7 @@ export default class MessageEntrance extends Emiter {
   constructor() {
     super();
     //sdk 信息
-    GlobalConfig.sdkVersion = "v2.9.3.20170919";
+    GlobalConfig.sdkVersion = "v2.10.4.20170920";
     loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
 
     //设置
@@ -147,6 +147,7 @@ export default class MessageEntrance extends Emiter {
     //_confer_ape.on(MessageTypes.SWITCH_RTMP_PULL_IP, this._switchRtmpPullIpHandler.bind(this)); //MS 拉流地址动态选点
     //_confer_ape.on(MessageTypes.SWITCH_HLS_IP, this._switchHlsIpHandler.bind(this)); //MS HLS动态选点
     _confer_ape.on(MessageTypes.STOP_ALL_MEDIA_PUBLISH, this._stopAllMediaPublishHandler.bind(this)); //课堂状态发生改变,需要停止当前的所有推流
+    _confer_ape.on(MessageTypes.CLASS_UPDATE_ROSTER,this._onRosterUpdateHandler.bind(this));
 
     _chat_ape = new ChatApe();
     _chat_ape.on('*', (type, data) => this._emit(type, data));
@@ -1359,6 +1360,22 @@ export default class MessageEntrance extends Emiter {
     loger.log('课堂状态发生改变,需要停止当前的所有推流');
     this._emit(MessageTypes.MEDIA_STOP_PUBLISH);
   }
+  //用更状态数据发送变更
+  _onRosterUpdateHandler(_data){
+    //数据无效/ios/android  不处理数据
+    if(!_data||GlobalConfig.deviceType==1||GlobalConfig.deviceType==2){
+      return;
+    }
+    let nodeData=_data.nodeData;
+    //数据用户是pc或H5不处理
+    if(!nodeData||nodeData.deviceType==0||nodeData.deviceType==3){
+      return;
+    }
+    if(nodeData.openCamera>0&&_webRtc){
+        loger.log("收到移动端用户数据更新,当前是开启摄像头状态,需要尝试添加一个远程视频");
+        _webRtc.tryAddMobileStream(_data.nodeId);
+    }
+  }
 
   //手动切换MS -> {ip;"xxx.xx.xx","port":"xxxx"}
   _switchMediaServer(_param) {
@@ -1742,7 +1759,7 @@ export default class MessageEntrance extends Emiter {
       GlobalConfig.className = _data.meetingName || "";
       GlobalConfig.classBeginTime = _data.beginTime || "";
       GlobalConfig.classEndTime = _data.endTime || "";
-
+      GlobalConfig.channelId = ""+GlobalConfig.siteId+"_"+GlobalConfig.classId;
       //sdk获取ip失败就使用saas返回的
       if(!GlobalConfig.userIp){
         GlobalConfig.userIp = _data.userIp || "";
diff --git a/src/RecordPlayBackParse.js b/src/RecordPlayBackParse.js
index 1019afa..e5a7e5c 100644
--- a/src/RecordPlayBackParse.js
+++ b/src/RecordPlayBackParse.js
@@ -33,6 +33,7 @@ class RecordPlayBackParse extends Emiter {
         this._recordPlaybackMaxTime = 0;//录制回放的总时间
         this._isReady = false;//录制回放是否已经准备完成
         this._apes = {};
+        this._videoApeBroadcastMssages={};//视频模块的广播消息
         this.mediaChannleList={};
         this._conferApeMssages = {};//会议数据
         this._chatApeMssages = {};//聊天数据
@@ -134,7 +135,7 @@ class RecordPlayBackParse extends Emiter {
             pduMsg.type = PduType.RCPDU_SEND_DATA_REQUEST;
             pduType = PduType.RCPDU_SEND_DATA_REQUEST;
         }
-        //loger.log('pduType', pduType);
+        loger.log('解析和储存->pduType', pduType);
         switch (pduType) {
             case PduType.RCPDU_CONNECT_PROVIDER_RESPONSE:
                 //加入课堂请求返回数据处理
@@ -160,7 +161,7 @@ class RecordPlayBackParse extends Emiter {
                 let ape = this._apes[pduMsg.sessionId];
                 let sessionLabel = ApeConsts(pduMsg.sessionId);
                 //只做解析存储,不对外发送
-                //loger.log('解析数据-timestamp->', timestamp, 'sessionId->', pduMsg.sessionId, 'sessionLabel->', sessionLabel);
+                loger.log('解析数据-timestamp->', timestamp, 'sessionId->', pduMsg.sessionId, 'sessionLabel->', sessionLabel,"subType:"+pduMsg.subType);
                 switch (pduMsg.sessionId) {
                     case ApeConsts.CONFERENCE_SESSION_ID:
                         this.saveParseData(data, timestamp, this._conferApeMssages);
@@ -375,7 +376,7 @@ class RecordPlayBackParse extends Emiter {
         console.log('文档数据',this._docApeMssages);
         console.log('白板数据',this._whiteApeMssages);
         console.log('聊天数据',this._chatApeMssages);
-
+        console.log('视频模块广播消息',this._videoApeBroadcastMssages);
         loger.log("录制回放数据解析完成,录制回放的总时间长为->", this._recordPlaybackMaxTime,"recordTimestamp:"+GlobalConfig.recordTimestamp);
         this._emit(RecordPlayBackParse.CLASS_JOIN_RECORD_PLAYBACK_SUCCESS, {"recordPlaybackMaxTime": this._recordPlaybackMaxTime});
     }
@@ -638,9 +639,16 @@ class RecordPlayBackParse extends Emiter {
              regItemSize = regItems.length;
 
         }catch (err){
-            console.warn('RCAdapterPdu->unpack-error->type类型不对')
+            console.warn('RCAdapterPdu->unpack-error->type类型不对');
+            try {
+                let sendDataPdu = pdu['RCVideoSendDataRequestPdu'].decode(regBuffer);
+                console.log("RCVideoSendDataRequestPdu",sendDataPdu);
+            }catch (err){
+
+            }
             return;
         }
+
         for (var i = 0; i < regItemSize; ++i) {
             let regItem = regItems[i];
             let regItemType = regItem.type;
@@ -707,6 +715,28 @@ class RecordPlayBackParse extends Emiter {
                     let tableDeleteData = pdu['RCRegistryTableDeleteItemPdu'].decode(user_data);
                     //console.log("tableDeleteData",object_id,tableDeleteData);
                     break;
+                case  pdu.RCPDU_SEND_VIDEO_DATA_REQUEST:
+                    //视频模块的控制消息
+                    try{
+                      let videoReceivePdu = pdu['RCVideoSendDataRequestPdu'].decode(user_data);
+                      if (videoReceivePdu == null) {
+                        loger.warn("视频控制消息处理,收到的消息为null,不做处理");
+                        return;
+                      }
+                      videoReceivePdu.data = this._rCArrayBufferUtil.uint8ArrayToStr(videoReceivePdu.data, 2);//开头两个字会乱码
+                      let dataObj = {};
+                      try {
+                        dataObj = JSON.parse(videoReceivePdu.data);
+                      } catch (err) {
+                        loger.warn('控制消息->JSON转换失败');
+                        dataObj = videoReceivePdu.data;
+                      }
+                      videoReceivePdu.data = dataObj;
+                      this._videoApeBroadcastMssages[timestamp]={parseData:videoReceivePdu,byteData:data,timestamp: timestamp};
+                    }catch (err){
+                      loger.warn("RCPDU_SEND_VIDEO_DATA_REQUEST->err",err);
+                    }
+                    break;
                 case pdu.RCPDU_REG_TABLE_UPDATE_PDU:
                     let tableUpdateData = pdu['RCRegistryTableUpdateItemPdu'].decode(user_data);
                     let tableUpdateItems = tableUpdateData.items;
diff --git a/src/SystemConfig.js b/src/SystemConfig.js
index 2f69a91..ecc3035 100644
--- a/src/SystemConfig.js
+++ b/src/SystemConfig.js
@@ -30,7 +30,7 @@ class SystemConfig {
     }
     if (mdetect.isAndroid()) {
       GlobalConfig.platform = "android"; //"android";
-      GlobalConfig.deviceType = 2; //"ios";
+      GlobalConfig.deviceType = 2; //"android";
     }
 
     //语言
diff --git a/src/apes/WebRtcApe.js b/src/apes/WebRtcApe.js
index cd8b1f6..1f54c3d 100644
--- a/src/apes/WebRtcApe.js
+++ b/src/apes/WebRtcApe.js
@@ -35,6 +35,8 @@ class WebRtcApe extends Emiter {
     this.curCameraId = "";
     this.curMicrophoneId = "";
 
+    this.remoteVideoList={};//记录远程视频流
+
     this.videoResolution = "240P";
     this.isOpenVideo = true;
 
@@ -138,6 +140,10 @@ class WebRtcApe extends Emiter {
     });
     this.client.on('stream-subscribed', (evt)=> {
       let stream = evt.stream;
+      this.addRemoetStreamView(stream);
+    });
+  /*    this.client.on('stream-subscribed', (evt)=> {
+      let stream = evt.stream;
       if(stream){
         //let viewDiv=`<div id="${this.xdyRemote + stream.getId()}" style="width:${this.hostRemoteVideoWidth}px;height:${this.hostRemoteVideoHeight}px;"></div>`;
         let uid=stream.getId();
@@ -148,6 +154,7 @@ class WebRtcApe extends Emiter {
           userName=user.name||"";
           userRole=user.userRole;
         }
+
         let nameDiv=`<div style="width:98%;height:20px; position: absolute; z-index: 1;left: 4px;overflow:hidden;font-size: 14px; color: #cccccc;display:${this.nameDisplay}">${userName}</div>`;
 
         if(userRole==ApeConsts.invisible){
@@ -172,14 +179,21 @@ class WebRtcApe extends Emiter {
           stream.play(this.xdyRemote + stream.getId());
         }catch (err){
         }
+        if(user.deviceType==1||user.deviceType==2){
+          this.remoteVideoList[user.nodeId]=stream;
+        }
+        console.log("移动端远程视频流集合->",this.remoteVideoList);
       }
-    });
+    });*/
 
     this.client.on('stream-removed', (evt)=> {
       let stream = evt.stream;
-      stream.stop();
-      $('#' + this.xdyRemote + stream.getId()).remove();
-      loger.log("远程视频流已经断开:" + stream.getId());
+      if(stream){
+        stream.stop();
+        $('#' + this.xdyRemote + stream.getId()).remove();
+        loger.log("远程视频流已经断开:" + stream.getId());
+      }
+
     });
 
     this.client.on('peer-leave', (evt)=> {
@@ -196,6 +210,48 @@ class WebRtcApe extends Emiter {
     });
 
   }
+  addRemoetStreamView(stream){
+    if(stream){
+      //let viewDiv=`<div id="${this.xdyRemote + stream.getId()}" style="width:${this.hostRemoteVideoWidth}px;height:${this.hostRemoteVideoHeight}px;"></div>`;
+      let uid=stream.getId();
+      let user=GlobalConfig.getUserInfoFromeNodeId(uid);
+      let userName="";
+      let userRole=""
+      if(user){
+        userName=user.name||"";
+        userRole=user.userRole;
+      }
+      let nameDiv=`<div style="width:98%;height:20px; position: absolute; z-index: 1;left: 4px;overflow:hidden;font-size: 14px; color: #cccccc;display:${this.nameDisplay}">${userName}</div>`;
+
+      if(userRole==ApeConsts.invisible){
+        //把远程视频添加到监课列表
+        loger.log("获取远程视频流成功->监课:"+userName+"->" + uid,new Date().getTime());
+        let viewDiv=`<div id="${this.xdyRemote + uid}" style="width:${this.invisibleVideoWidth}px;height:${this.invisibleVideoHeight}px;float: left;margin-right: 1px;pointer-events: none;">${nameDiv}</div>`;
+        $(this.invisibleViewId).append(viewDiv);
+      }else if(userRole==ApeConsts.host){
+        //把远程视图添加到老师列表
+        loger.log("获取远程视频流成功->老师:"+userName+"->" + uid,new Date().getTime());
+        let viewDiv=`<div id="${this.xdyRemote + uid}" style="width:${this.hostRemoteVideoWidth}px;height:${this.hostRemoteVideoHeight}px;float: left;margin-right: 1px;pointer-events: none;">${nameDiv}</div>`;
+        $(this.hostRemoteViewId).append(viewDiv);
+      }else {
+        //把视图添加到学生列表
+        loger.log("获取远程视频流成功->学生:"+userName+"->" +uid,new Date().getTime());
+        let viewDiv=`<div id="${this.xdyRemote + uid}" style="width:${this.normalRemoteVideoWidth}px;height:${this.normalRemoteVideoHeight}px;float: left;margin-right: 1px;pointer-events: none;">${nameDiv}</div>`;
+        $(this.normalRemoteViewId).append(viewDiv);
+      }
+      //播放视频,隐藏控制条
+      try{
+        $("bar_"+stream.getId()).hide();
+        stream.play(this.xdyRemote + stream.getId());
+      }catch (err){
+      }
+      if(user.deviceType==1||user.deviceType==2){
+        this.remoteVideoList[user.nodeId]=stream;
+      }
+      console.log("移动端远程视频流集合->",this.remoteVideoList);
+    }
+  }
+  
   //重新添加远程视频
   reAddRemoteStream(_stream){
     if(!_stream){
@@ -215,7 +271,6 @@ class WebRtcApe extends Emiter {
         return;
       }
       if(err=="PEERCONNECTION_FAILED"){
-        //this.reAddRemoteStreamDelay=setTimeout(()=>{
           setTimeout(()=>{
           loger.warn("连接远程的流失败->尝试重新连接",err);
           this.reAddRemoteStream(_stream);
@@ -224,6 +279,23 @@ class WebRtcApe extends Emiter {
         loger.warn("添加一个远程视频流->失败", err);      }
     });
   }
+  //尝试添加远程的移动设备视频流
+  tryAddMobileStream(nodeId){
+    let stream=this.remoteVideoList[nodeId];
+    if(!stream){
+      return;
+    }
+    let remoteView=document.getElementById(this.xdyRemote +nodeId)
+    console.log("remoteView->",remoteView)
+    if(remoteView){
+      loger.log(nodeId+" 流已经添加显示,不需要再处理");
+      return;
+    }
+    if(stream) {
+      loger.log("收到移动端推流的消息,主动添加一个远程视频流");
+      this.addRemoetStreamView(stream);
+    }
+  }
 
   joinChannel(_params) {
     this.channelId = _params.channelId||"";