Toggle navigation
Toggle navigation
此项目
正在载入...
Sign in
李勇
/
McuClient
转到一个项目
Toggle navigation
项目
群组
代码片段
帮助
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
李勇
2017-03-21 10:15:20 +0800
Browse Files
Options
Browse Files
Download
Plain Diff
Commit
17ddcb8de72219a0269b5b8de82104491c12ba77
17ddcb8d
2 parents
eab15268
f02ab8f1
Merge branch 'mcuClientBranch'
隐藏空白字符变更
内嵌
并排对比
正在显示
14 个修改的文件
包含
291 行增加
和
211 行删除
dist/McuClient.js
src/EngineEntrance.js
src/GlobalConfig.js
src/RecordPlayBackParse.js
src/Sass.js
src/ServerCheck.js
src/apes/Ape.js
src/apes/AudioApe.js
src/apes/ConferApe.js
src/apes/DocApe.js
src/apes/VideoApe.js
src/apes/WhiteBoardApe.js
src/mcu.js
src/pdus/pro.js
dist/McuClient.js
查看文件 @
17ddcb8
此 diff 太大无法显示。
src/EngineEntrance.js
查看文件 @
17ddcb8
...
...
@@ -141,7 +141,7 @@ export default class MessageEntrance extends Emiter {
this
.
getVideoPublishPath
=
this
.
_getVideoPublishPath
.
bind
(
this
);
this
.
getVideoAllChannelInfo
=
this
.
_getVideoAllChannelInfo
.
bind
(
this
);
this
.
publishVideo
=
this
.
_publishVideo
.
bind
(
this
);
this
.
stopPublishVideo
=
this
.
_stopPublishVideo
.
bind
(
this
);
this
.
stopPublishVideo
=
this
.
unPublishVideo
=
this
.
_stopPublishVideo
.
bind
(
this
);
this
.
sendVideoBroadcastMsg
=
this
.
_sendVideoBroadcastMsg
.
bind
(
this
);
...
...
@@ -150,7 +150,7 @@ export default class MessageEntrance extends Emiter {
this
.
getAudioPublishPath
=
this
.
_getPublishAudioPath
.
bind
(
this
);
this
.
getAudioAllChannelInfo
=
this
.
_getAudioAllChannelInfo
.
bind
(
this
);
this
.
publishAudio
=
this
.
_publishAudio
.
bind
(
this
);
this
.
stopPublishAudio
=
this
.
_stopPublishAudio
.
bind
(
this
);
this
.
stopPublishAudio
=
this
.
unPublishAudio
=
this
.
_stopPublishAudio
.
bind
(
this
);
this
.
sendAudioBroadcastMsg
=
this
.
sendAudioCommandMsg
.
bind
(
this
);
//whiteBoradApe
...
...
@@ -296,10 +296,22 @@ export default class MessageEntrance extends Emiter {
GlobalConfig
.
isRecordPlayBack
=
false
;
//设置为非录制回放状态
GlobalConfig
.
classId
=
parseInt
(
_param
.
classId
);
GlobalConfig
.
portal
=
_param
.
portal
;
GlobalConfig
.
userRole
=
_param
.
userRole
||
ApeConsts
.
normal
;
GlobalConfig
.
userId
=
_param
.
userId
||
"0"
;
GlobalConfig
.
userName
=
_param
.
userName
||
""
;
//设置角色身份
GlobalConfig
.
userRole
=
_param
.
userRole
||
ApeConsts
.
normal
;
if
(
GlobalConfig
.
userRole
!=
ApeConsts
.
host
&&
GlobalConfig
.
userRole
!=
ApeConsts
.
presenter
&&
GlobalConfig
.
userRole
!=
ApeConsts
.
assistant
&&
GlobalConfig
.
userRole
!=
ApeConsts
.
record
&&
GlobalConfig
.
userRole
!=
ApeConsts
.
invisible
){
GlobalConfig
.
userRole
=
ApeConsts
.
normal
;
}
//客户端决定是否延迟接收消息
GlobalConfig
.
messageDelay
=
_param
.
messageDelay
||
false
;
//最长允许录制的时间
if
(
_param
.
allowRecordMaxTime
){
GlobalConfig
.
allowRecordMaxTime
=
parseInt
(
_param
.
allowRecordMaxTime
);
...
...
@@ -412,7 +424,7 @@ export default class MessageEntrance extends Emiter {
// console.log(_data);
//包含整个课堂最全的信息,储存数据
if
(
_data
)
{
GlobalConfig
.
mcuDelay
=
_data
.
mcuDelay
||
60
;
//mcu消息延迟,用于文档模块
GlobalConfig
.
mcuDelay
=
_data
.
h5Delay
||
0
;
//mcu消息延迟的时间间隔,单位(秒),结合客户端传的messageDelay的值使用
GlobalConfig
.
className
=
_data
.
meetingName
||
""
;
GlobalConfig
.
classBeginTime
=
_data
.
beginTime
||
""
;
GlobalConfig
.
classEndTime
=
_data
.
endTime
||
""
;
...
...
@@ -421,6 +433,14 @@ export default class MessageEntrance extends Emiter {
GlobalConfig
.
maxVideoChannels
=
_data
.
maxVideoChannels
;
GlobalConfig
.
maxAudioChannels
=
_data
.
maxAudioChannels
;
//视频质量相关设置
GlobalConfig
.
fps
=
_data
.
fps
||
15
;
GlobalConfig
.
gop
=
_data
.
gop
||
3
;;
GlobalConfig
.
videoQuality
=
_data
.
videoQuality
||
2
;
//是否自动开始(身份是host的时候才用到的)
GlobalConfig
.
isAutoStartClass
=
_data
.
autoRecord
||
0
;
GlobalConfig
.
setDocListPrepare
(
_data
.
docListPrepare
);
//提前上传的文档列表
GlobalConfig
.
setRecordList
(
_data
.
recordList
);
//录制回放地址
GlobalConfig
.
setDocList
(
_data
.
docList
);
//文档地址
...
...
@@ -592,6 +612,11 @@ export default class MessageEntrance extends Emiter {
joinClassSuccessCallBackData
.
classTimestamp
=
GlobalConfig
.
classTimestamp
;
//课堂进行的累积时间
joinClassSuccessCallBackData
.
recordPlaybackMaxTime
=
GlobalConfig
.
recordPlaybackMaxTime
;
//录制回放的总时间
joinClassSuccessCallBackData
.
fps
=
GlobalConfig
.
fps
;
joinClassSuccessCallBackData
.
gop
=
GlobalConfig
.
gop
;
joinClassSuccessCallBackData
.
videoQuality
=
GlobalConfig
.
videoQuality
;
loger
.
log
(
'加入课堂成功'
);
loger
.
log
(
joinClassSuccessCallBackData
);
...
...
@@ -1141,6 +1166,10 @@ export default class MessageEntrance extends Emiter {
joinClassSuccessCallBackData
.
classTimestamp
=
GlobalConfig
.
classTimestamp
;
//课堂进行的累积时间
joinClassSuccessCallBackData
.
recordPlaybackMaxTime
=
GlobalConfig
.
recordPlaybackMaxTime
;
//录制回放的总时间
joinClassSuccessCallBackData
.
fps
=
GlobalConfig
.
fps
;
joinClassSuccessCallBackData
.
gop
=
GlobalConfig
.
gop
;
joinClassSuccessCallBackData
.
videoQuality
=
GlobalConfig
.
videoQuality
;
loger
.
log
(
joinClassSuccessCallBackData
);
//和加入课堂成功使用同样的消息处理
this
.
_emit
(
MessageTypes
.
CLASS_JOIN_SUCCESS
,
joinClassSuccessCallBackData
);
...
...
src/GlobalConfig.js
查看文件 @
17ddcb8
...
...
@@ -52,6 +52,9 @@ class GlobalConfig {
classInfo
.
MCUServerPort
=
this
.
MCUServerPort
;
classInfo
.
maxVideoChannels
=
this
.
maxVideoChannels
;
classInfo
.
maxAudioChannels
=
this
.
maxAudioChannels
;
classInfo
.
fps
=
this
.
fps
;
classInfo
.
gop
=
this
.
gop
;
classInfo
.
videoQuality
=
this
.
videoQuality
;
return
classInfo
;
...
...
@@ -242,8 +245,10 @@ GlobalConfig.statusCode_4={"code":4,message:"未知状态"};
GlobalConfig
.
md5
=
""
;
GlobalConfig
.
msType
=
1
;
//目前固定用这个
GlobalConfig
.
mcuDelay
=
60
;
//默认的延迟时间 flash中使用的是3000毫秒
GlobalConfig
.
docDelay
=
1600
;
//文档模块加入成功之后延迟发送送成功的消息给主模块
GlobalConfig
.
messageDelay
=
false
;
//是否启用消息延迟
GlobalConfig
.
mcuDelay
=
0
;
//默认的延迟时间(单位-秒)
GlobalConfig
.
docDelay
=
1600
;
//文档模块加入成功之后延迟发送送成功的消息给主模块(sdk内部使用)
GlobalConfig
.
portal
=
"112.126.80.182:80"
;
//Sass IP
//GlobalConfig.ip="112.126.80.182";
...
...
@@ -305,6 +310,8 @@ GlobalConfig.recordFileName="";//录制的文件名,如 果为空就创建一个
GlobalConfig
.
recordDownloadUrl
=
""
;
//下载地址
GlobalConfig
.
recordReplaytickValues
=
{};
// 滚动条关键点,用于快进快退
GlobalConfig
.
isAutoStartClass
=
0
;
//是否自动开始上课 0-否 ;1 是
GlobalConfig
.
updateClassInfoDelay
=
30
;
//(秒),每隔30秒同步一次课堂状态的并保存到Sass
//GlobalConfig.serverTimestamp=0;//当前的系统时间戳 用get set 获取
...
...
@@ -312,7 +319,10 @@ GlobalConfig.updateClassInfoDelay=30;//(秒),每隔30秒同步一次课堂状
GlobalConfig
.
activeDocId
=
0
;
//当前激活的文档ID
GlobalConfig
.
activeDocCurPage
=
1
;
//当前激活的文档的当前页
//视频质量相关设置
GlobalConfig
.
fps
=
15
;
//帧频
GlobalConfig
.
gop
=
3
;
//关键帧间隔(秒)
GlobalConfig
.
videoQuality
=
2
;
//画面质量 0-低;1-中;2-高;
GlobalConfig
.
classAllParam
=
{};
//Sass直接返回的所有课堂信息(最全)
...
...
src/RecordPlayBackParse.js
查看文件 @
17ddcb8
...
...
@@ -293,6 +293,8 @@ class RecordPlayBackParse extends Emiter {
position
+=
byteLen
;
this
.
_parseSaveSocketMsgReceivedHandler
(
byteData
,
timestamp
);
//记录最后一个数据的时间戳作为整个录制回放的总时间戳
this
.
_recordPlaybackMaxTime
=
timestamp
;
}
...
...
@@ -300,6 +302,12 @@ class RecordPlayBackParse extends Emiter {
this
.
_isReady
=
true
;
this
.
_stopTimerCounter
();
//录制回放的总时间长度按课堂最长时间计算,不能按最后一个消息的时间计算
if
(
this
.
_recordPlaybackMaxTime
<
GlobalConfig
.
classTimestamp
){
this
.
_recordPlaybackMaxTime
=
GlobalConfig
.
classTimestamp
;
}
GlobalConfig
.
recordPlaybackMaxTime
=
this
.
_recordPlaybackMaxTime
;
loger
.
log
(
"录制回放数据解析完成,录制回放的总时间长为->"
,
this
.
_recordPlaybackMaxTime
);
//console.log("_messages", this._messages);
...
...
@@ -382,13 +390,13 @@ class RecordPlayBackParse extends Emiter {
//查找关键帧,找到关键帧后再继续播放
this
.
_searchApeMessageKeyfram
(
this
.
_conferApeMssages
,
ApeConsts
.
CONFERENCE_SESSION_ID
);
this
.
_searchApeMessageKeyfram
(
this
.
_docApeMssages
,
ApeConsts
.
DOCSHARING_SESSION_ID
);
this
.
_searchApeMessageKeyfram
(
this
.
_whiteApeMssages
,
ApeConsts
.
WHITEBOARD_SESSION_ID
);
//
this._searchApeMessageKeyfram(this._whiteApeMssages, ApeConsts.WHITEBOARD_SESSION_ID);
this
.
_searchApeMessageKeyfram
(
this
.
_videoApeMssages
,
ApeConsts
.
VIDEO_SESSION_ID
);
this
.
_searchApeMessageKeyfram
(
this
.
_audioApeMssages
,
ApeConsts
.
AUDIO_SESSION_ID
);
//聊天模块的比较特殊,消息是累计的
this
.
_searchChatApeMessageKeyfram
(
this
.
_chatApeMssages
,
ApeConsts
.
CHAT_SESSION_ID
);
//聊天模块、白板标注模块的比较特殊,消息是累计的,默认最多30条
this
.
_searchApeHistoryMessageKeyfram
(
this
.
_chatApeMssages
,
ApeConsts
.
CHAT_SESSION_ID
);
this
.
_searchApeHistoryMessageKeyfram
(
this
.
_whiteApeMssages
,
ApeConsts
.
WHITEBOARD_SESSION_ID
);
//各个ape模块无论有没有找到关键帧数据,都继续播放
this
.
_startTimerCounter
();
...
...
@@ -420,7 +428,9 @@ class RecordPlayBackParse extends Emiter {
}
//查找聊天模块ape关键帧数据,聊天模块比较特殊,消息是累积的,当前时间戳之前的都需要显示
_searchChatApeMessageKeyfram
(
_apeMessages
)
{
_searchApeHistoryMessageKeyfram
(
_apeMessages
)
{
//最多30条数据
let
counter
=
0
;
let
messageItem
;
let
keyFrameSeek
=
0
;
for
(
let
i
=
this
.
_recordPlaybackTimestamp
;
i
>
0
;
i
--
)
{
...
...
@@ -429,9 +439,15 @@ class RecordPlayBackParse extends Emiter {
//把时间点对应的数据发送,同一秒内有存在多个数据的情况
for
(
let
i
=
0
;
i
<
messageItem
.
length
;
i
++
)
{
this
.
_everSocketMsgReceivedHandler
(
messageItem
[
i
].
byteData
,
0
);
counter
++
;
if
(
counter
>
30
){
loger
.
warn
(
"SEEK->最多处理历史消息30条"
);
return
;
}
}
}
}
}
}
...
...
src/Sass.js
查看文件 @
17ddcb8
...
...
@@ -201,7 +201,7 @@ class Sass extends Emiter {
// 获取课堂基本详情------------------------------------------------------------------------------------
getClassDetail
()
{
let
url
=
`
http
:
//${
confInfo.portal}/3m/meeting/getClassH5.do?classNumber=${confInfo
.classId}`;
let
url
=
`
http
:
//${
GlobalConfig.portal}/3m/meeting/getClassH5.do?classNumber=${GlobalConfig
.classId}`;
loger
.
log
(
'获取Class详情.'
,
url
);
fetch
(
url
,
{
timeout
:
5000
...
...
@@ -300,7 +300,7 @@ class Sass extends Emiter {
}
var
timestamp
=
new
Date
().
getTime
();
var
authId
=
MD5
(
_param
.
docId
+
""
+
_param
.
classId
+
""
+
timestamp
);
// docId+classId+timestamp的字符串,转成MD5
let
url
=
`
http
:
//${
confInfo.portal}/3m/api/document/deleteRelation.do?docId=${_param.docId}&classId=${confInfo
.classId}×tamp=${timestamp}&authId=${authId}`;
let
url
=
`
http
:
//${
GlobalConfig.portal}/3m/api/document/deleteRelation.do?docId=${_param.docId}&classId=${GlobalConfig
.classId}×tamp=${timestamp}&authId=${authId}`;
loger
.
log
(
'sassDeleteDocument'
,
url
);
fetch
(
url
,
{
...
...
@@ -350,16 +350,16 @@ class Sass extends Emiter {
}
//{"classStatusInfo":classStatusInfo}
var
timestamp
=
new
Date
().
getTime
();
var
authId
=
MD5
(
confInfo
.
classId
+
""
+
timestamp
);
// (classId+timestamp)的字符串,转成MD5
var
authId
=
MD5
(
GlobalConfig
.
classId
+
""
+
timestamp
);
// (classId+timestamp)的字符串,转成MD5
let
classStatusInfo
=
JSON
.
stringify
(
_param
.
classStatusInfo
);
let
url
=
`
http
:
//${
confInfo
.portal}/3m/api/meeting/saveInfo.do`;
let
url
=
`
http
:
//${
GlobalConfig
.portal}/3m/api/meeting/saveInfo.do`;
loger
.
log
(
'saveClassStatusInfo'
,
url
);
fetch
(
url
,
{
method
:
'POST'
,
headers
:
{
"Content-Type"
:
"application/x-www-form-urlencoded"
},
body
:
`
classId
=
$
{
confInfo
.
classId
}
&
info
=
$
{
classStatusInfo
}
&
timestamp
=
$
{
timestamp
}
&
authId
=
$
{
authId
}
`
,
body
:
`
classId
=
$
{
GlobalConfig
.
classId
}
&
info
=
$
{
classStatusInfo
}
&
timestamp
=
$
{
timestamp
}
&
authId
=
$
{
authId
}
`
,
timeout
:
5000
})
.
then
(
ret
=>
{
...
...
@@ -416,7 +416,7 @@ class Sass extends Emiter {
let
timestamp
=
new
Date
().
getTime
();
let
authId
=
MD5
(
key
+
siteID
+
meetingID
+
timestamp
);
let
url
=
`
http
:
//${
confInfo
.portal}/3m/recordingMeeting/insertRecordingMeeting.do?siteID=${siteID}&meetingID=${meetingID}&userID=${userID}&userName=${userName}&meetingName=${meetingName}&startTime=${startTime}&endTime=${endTime}&playUrl=${playUrl}&streamName=${streamName}&downloadUrl=${downloadUrl}&configFile=${confRecordFileName}×tamp=${timestamp}&recordTimestamp=${recordTimestamp}&authId=${authId}`;
let
url
=
`
http
:
//${
GlobalConfig
.portal}/3m/recordingMeeting/insertRecordingMeeting.do?siteID=${siteID}&meetingID=${meetingID}&userID=${userID}&userName=${userName}&meetingName=${meetingName}&startTime=${startTime}&endTime=${endTime}&playUrl=${playUrl}&streamName=${streamName}&downloadUrl=${downloadUrl}&configFile=${confRecordFileName}×tamp=${timestamp}&recordTimestamp=${recordTimestamp}&authId=${authId}`;
loger
.
log
(
'saveClassRecordContrlInfo'
,
url
);
fetch
(
url
,
{
...
...
src/ServerCheck.js
查看文件 @
17ddcb8
...
...
@@ -27,6 +27,8 @@ let speedTestPort = ':5555';//测速端口统一
let
checkMcuIpGroup
=
[];
//储存MCU需要查询的ip数组
let
checkMsIpGroup
=
[];
//储存MCU需要查询的ip数组
const
timeOutDelay
=
1000
;
//选点超时
class
ServerCheck
extends
Emiter
{
constructor
()
{
super
();
...
...
@@ -52,7 +54,7 @@ class ServerCheck extends Emiter {
loger
.
log
(
'获取IP信息 '
,
userIp
,
location
);
fetchJsonp
(
location
,
{
timeout
:
3000
,
timeout
:
timeOutDelay
,
}).
then
(
function
(
response
)
{
return
response
.
json
()
}).
then
(
function
(
json
)
{
...
...
@@ -168,7 +170,7 @@ class ServerCheck extends Emiter {
//loger.log('getBestMcuServer done -> ', fatest_ip_response);
this
.
_getBestMcuServerCallbackHandler
(
fatest_ip_response
)
}
}.
bind
(
this
),
3000
);
}.
bind
(
this
),
timeOutDelay
);
}
//获取最快的MS服务器地址,参数是一个ip数组
...
...
@@ -186,7 +188,7 @@ class ServerCheck extends Emiter {
//loger.log('getBestMsServer done -> ', fatest_ip_response);
this
.
_getBestMsServerCallbackHandler
(
fatest_ip_response
);
}
}.
bind
(
this
),
3000
);
}.
bind
(
this
),
timeOutDelay
);
}
_getBestMcuServerCallbackHandler
(
_data
)
{
...
...
src/apes/Ape.js
查看文件 @
17ddcb8
...
...
@@ -40,7 +40,7 @@ export default class Ape extends Emiter {
this
.
_adapter_pdu
=
new
pdu
[
'RCAdapterPdu'
];
this
.
_classInfo
=
null
;
this
.
_rCArrayBufferUtil
=
ArrayBufferUtil
;
this
.
_apeDelayed
=
fals
e
;
this
.
_apeDelayed
=
tru
e
;
this
.
_apeDelayedMsgs
=
[];
this
.
_apeDelayedTimer
=
0
;
...
...
@@ -75,15 +75,17 @@ export default class Ape extends Emiter {
// 消息处理
_pduMessageHandler
(
regBuffer
,
_seekTime
)
{
let
seekTime
=
_seekTime
||
0
;
//这个只有在录制回放的时候才有
//loger.log("RCPDU_REG_ADAPTER============seekTime",seekTime);
if
(
this
.
_apeDelayed
)
{
// this._apeDelayedMsgs.push(regBuffer);
// this._apeDelayedStart();
/* loger.warn('APE->收到消息处理->',GlobalConfig.mcuDelay,GlobalConfig.messageDelay);
//延迟处理消息(3个条件--->ape允许延迟&&客户端设置需要延迟&&Sass设置的延迟时间大于0)
if (this._apeDelayed&&GlobalConfig.messageDelay&&GlobalConfig.mcuDelay>0) {
loger.warn('延迟处理消息->',GlobalConfig.mcuDelay);
setTimeout(() => {
this._pduRegAdapterHandler(regBuffer,seekTime);
},
GlobalConfig
.
mcuDelay
||
2000
);
}, GlobalConfig.mcuDelay
*1000);//mcuDelay单位是秒,这里需要换算为毫秒
return;
}
*/
//不延迟,立即处理
this
.
_pduRegAdapterHandler
(
regBuffer
,
seekTime
);
}
...
...
@@ -164,7 +166,7 @@ export default class Ape extends Emiter {
let
tableUpdateItems
=
tableUpdateData
.
items
;
let
tableUpdateItemsLen
=
tableUpdateItems
.
length
;
//loger.log("RCRegistryTableUpdateItemPdu " + tableUpdateItemsLen);
loger
.
log
(
tableUpdateData
);
//
loger.log(tableUpdateData);
for
(
let
i
=
0
;
i
<
tableUpdateItemsLen
;
++
i
)
{
let
tableItem
=
tableUpdateItems
[
i
];
...
...
src/apes/AudioApe.js
查看文件 @
17ddcb8
...
...
@@ -316,6 +316,7 @@ class AudioApe extends Ape {
let
receiveChannelInfo
=
{};
receiveChannelInfo
.
mediaId
=
unpackChannelInfo
.
channelId
;
receiveChannelInfo
.
fromNodeId
=
unpackChannelInfo
.
fromNodeId
;
receiveChannelInfo
.
userName
=
unpackChannelInfo
.
userName
||
""
;
//消息不是自己同步的,需要处理
if
(
unpackChannelInfo
.
status
==
ApeConsts
.
CHANNEL_STATUS_OPENING
){
...
...
@@ -375,6 +376,7 @@ class AudioApe extends Ape {
packPduModel
.
mediaType
=
_param
.
mediaType
||
ApeConsts
.
MEDIA_TYPE_AUDIO
;
packPduModel
.
timestamp
=
_param
.
timestamp
||
EngineUtils
.
creatTimestamp
();
packPduModel
.
fromNodeId
=
GlobalConfig
.
nodeId
;
packPduModel
.
userName
=
GlobalConfig
.
userName
||
""
;
packPduModel
.
toNodeId
=
0
;
loger
.
log
(
"packPdu"
,
packPduModel
);
return
packPduModel
;
...
...
src/apes/ConferApe.js
查看文件 @
17ddcb8
...
...
@@ -377,6 +377,13 @@ class ConferApe extends Ape {
//如果是host ,开始录制
this
.
startRecord
();
}
else
if
(
GlobalConfig
.
classStatus
==
ApeConsts
.
CLASS_STATUS_WAIT
&&
GlobalConfig
.
isHost
&&
GlobalConfig
.
isAutoStartClass
&&
!
GlobalConfig
.
isRecordPlayBack
){
//自动开始上课的4个条件
//1.如果自己是host,2.Sass配置的是自动开始上课,3.并且当前是未开始状态,4.当前不是录制回放,开始自动上课
loger
.
log
(
'自动开始上课->classStatus:'
,
GlobalConfig
.
classStatus
,
" isHost:"
,
GlobalConfig
.
isHost
,
" isAutoStartClass:"
,
GlobalConfig
.
isAutoStartClass
,
" isRecordPlayBack:"
,
GlobalConfig
.
isRecordPlayBack
);
this
.
startClass
();
}
}
...
...
src/apes/DocApe.js
查看文件 @
17ddcb8
...
...
@@ -34,8 +34,6 @@ class DocApe extends Ape {
this
.
docList
=
{};
//记录文档的数组this.docList[itemIdx]=itemIdx的数据
//this.activeDocItemIdx =0;//当前激活的文档itemIdx
//this.activeDocCurPage=1;//当前激活的文档的当前页
// 延迟
this
.
_apeDelayed
=
false
;
// Ape Models
this
.
registerKey
(
this
.
_session_id
,
this
.
_session_name
,
this
.
_session_tag
,
new
ArrayBuffer
);
...
...
src/apes/VideoApe.js
查看文件 @
17ddcb8
...
...
@@ -243,7 +243,7 @@ class VideoApe extends Ape {
}
return
{
"code"
:
ApeConsts
.
RETURN_SUCCESS
,
"data"
:
""
};
}
//发送到mcu同步(更新数据)
sendTableUpdateHandler
(
_channelInfo
)
{
loger
.
log
(
"video===sendTableUpdateHandler "
);
let
updateModelPdu
=
this
.
packPdu
(
_channelInfo
,
_channelInfo
.
channelId
);
//let updateModelPdu=this.packPdu({},ApeConsts.VIDEO_OBJ_TABLE_ID+2);
...
...
@@ -321,6 +321,7 @@ class VideoApe extends Ape {
let
receiveChannelInfo
=
{};
receiveChannelInfo
.
mediaId
=
unpackChannelInfo
.
channelId
;
receiveChannelInfo
.
fromNodeId
=
unpackChannelInfo
.
fromNodeId
;
receiveChannelInfo
.
userName
=
unpackChannelInfo
.
userName
||
""
;
//消息不是自己同步的,需要处理
if
(
unpackChannelInfo
.
status
==
ApeConsts
.
CHANNEL_STATUS_OPENING
){
//正在推流
...
...
@@ -380,6 +381,7 @@ class VideoApe extends Ape {
packPduModel
.
mediaType
=
_param
.
mediaType
||
ApeConsts
.
MEDIA_TYPE_VIDEO
;
packPduModel
.
timestamp
=
_param
.
timestamp
||
0
;
packPduModel
.
fromNodeId
=
GlobalConfig
.
nodeId
;
packPduModel
.
userName
=
GlobalConfig
.
userName
||
""
;
packPduModel
.
toNodeId
=
0
;
loger
.
log
(
packPduModel
);
return
packPduModel
;
...
...
src/apes/WhiteBoardApe.js
查看文件 @
17ddcb8
...
...
@@ -43,8 +43,6 @@ class WhiteBoardApe extends Ape {
this
.
annoInfos
=
{};
//储存所有的标注数据
this
.
insertHistory
=
[];
//添加的白板记录,用于撤回操作
// 白板延迟
// this._apeDelayed = true;
//Ape Models
this
.
registerKey
(
this
.
_session_id
,
this
.
_session_name
,
this
.
_session_tag
,
new
ArrayBuffer
);
this
.
registerObj
(
pdu
.
RCPDU_REG_REGISTER_TABLE
,
ApeConsts
.
WHITEBOARD_OBJ_TABLE_ID
,
...
...
src/mcu.js
查看文件 @
17ddcb8
...
...
@@ -17,187 +17,199 @@ import EngineUtils from 'EngineUtils';
let
loger
=
Loger
.
getLoger
(
'MCU'
);
class
MCU
extends
Emiter
{
constructor
()
{
super
();
this
.
_apes
=
{};
this
.
_everSocket
=
everSocket
;
this
.
_everSocket
.
on
(
everSocket
.
OPEN
,
this
.
_everSocketOpenHandler
.
bind
(
this
));
this
.
_everSocket
.
on
(
everSocket
.
MESSAGE
,
this
.
_everSocketMsgReceivedHandler
.
bind
(
this
));
this
.
_everSocket
.
on
(
everSocket
.
CLOSED
,
this
.
_everSocketCloseHandler
.
bind
(
this
));
}
// 注册Ape
registerApe
(
ape
)
{
this
.
_apes
[
ape
.
_session_id
]
=
ape
;
}
// EverSocket建立通道完毕
_everSocketOpenHandler
()
{
this
.
_sendJoinClassRequest
();
}
// EverSocket连接断开
_everSocketCloseHandler
()
{
GlobalConfig
.
setCurrentStatus
(
GlobalConfig
.
statusCode_3
);
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_SOCKET_DISCONNECT
);
}
//MCU-发送加入课堂请求
_sendJoinClassRequest
(){
//const classInfo = this.classInfo;
loger
.
log
(
'MCU-发送加入课堂请求.'
);
loger
.
log
(
this
.
classInfo
);
var
descriptorPdu
=
new
pdu
[
'RCConferenceDescriptorPdu'
];
descriptorPdu
.
id
=
this
.
classInfo
.
classId
;
descriptorPdu
.
name
=
this
.
classInfo
.
className
||
""
;
descriptorPdu
.
mode
=
0
;
descriptorPdu
.
capacity
=
1
;
var
joinRequestPdu
=
new
pdu
[
'RCConferenceJoinRequestPdu'
];
joinRequestPdu
.
type
=
2
;
joinRequestPdu
.
initiator
=
this
.
classInfo
.
nodeId
;
joinRequestPdu
.
nodeType
=
PduConsts
.
NT_TERMINAL
;
//normal
joinRequestPdu
.
classDescription
=
descriptorPdu
;
// classDescription
let
pduMsg
=
pdu
.
create_join_class_request_pdu
(
joinRequestPdu
.
type
,
this
.
classInfo
.
nodeId
,
this
.
classInfo
.
classId
,
0
,
ApeConsts
.
BROADCAST_CHANNEL_ID
,
true
,
PduConsts
.
DP_TOP
,
this
.
classInfo
.
topNodeID
,
PduConsts
.
SEG_ONCE
);
pduMsg
.
set
(
"site"
,
this
.
classInfo
.
siteId
);
//课堂号对应的名称
pduMsg
.
set
(
"userId"
,
this
.
classInfo
.
userId
);
pduMsg
.
set
(
"userName"
,
Base64
.
fromByteArray
(
ArrayBufferUtil
.
strToUint8Array
(
this
.
classInfo
.
userName
)));
pduMsg
.
set
(
"userRole"
,
this
.
classInfo
.
userRole
);
pduMsg
.
set
(
"deviceType"
,
""
+
GlobalConfig
.
deviceType
);
pduMsg
.
set
(
"data"
,
joinRequestPdu
.
toArrayBuffer
());
this
.
_everSocket
.
send
(
pduMsg
.
toArrayBuffer
());
}
// EverSocket底层消息处理
_everSocketMsgReceivedHandler
(
data
)
{
let
pduMsg
=
pdu
.
decode_pdu
(
data
);
let
pduType
=
pduMsg
.
get
(
"type"
);
let
pduData
=
pduMsg
.
get
(
"data"
);
//loger.data('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
//loger.log('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
switch
(
pduType
)
{
case
PduType
.
RCPDU_CONNECT_PROVIDER_RESPONSE
:
//加入课堂请求返回数据处理
let
joinConfPdu
=
pdu
[
'RCConferenceJoinResponsePdu'
].
decode
(
pduData
);
let
pduResultCode
=
joinConfPdu
.
result
;
loger
.
warn
(
'RCPDU_CONNECT_PROVIDER_RESPONSE ->pduResultCode:'
+
pduResultCode
);
switch
(
pduResultCode
)
{
case
PduConsts
.
RET_SUCCESS
:
//加入成功
this
.
_updateMCUConfInfoDescription
(
joinConfPdu
.
classDescription
);
this
.
_emit
(
MessageTypes
.
CLASS_JOIN_MCU_SUCCESS
,
this
.
classInfo
);
break
;
case
PduConsts
.
RET_FULL_CAPACITY
:
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_CLASS_JOIN_FULL
);
//this._emit(MessageTypes.CLASS_JOIN_FAILED,MessageTypes.ERR_CLASS_JOIN_FULL);
//this._emit(MessageTypes.CLASS_JOIN_FULL);
break
;
default
:
loger
.
arn
(
'JoinConfPdu-未知类型-等待处理.'
,
pduResultCode
);
break
constructor
()
{
super
();
this
.
_apes
=
{};
this
.
_everSocket
=
everSocket
;
this
.
_everSocket
.
on
(
everSocket
.
OPEN
,
this
.
_everSocketOpenHandler
.
bind
(
this
));
this
.
_everSocket
.
on
(
everSocket
.
MESSAGE
,
this
.
_everSocketMsgReceivedHandler
.
bind
(
this
));
this
.
_everSocket
.
on
(
everSocket
.
CLOSED
,
this
.
_everSocketCloseHandler
.
bind
(
this
));
}
// 注册Ape
registerApe
(
ape
)
{
this
.
_apes
[
ape
.
_session_id
]
=
ape
;
}
// EverSocket建立通道完毕
_everSocketOpenHandler
()
{
this
.
_sendJoinClassRequest
();
}
// EverSocket连接断开
_everSocketCloseHandler
()
{
GlobalConfig
.
setCurrentStatus
(
GlobalConfig
.
statusCode_3
);
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_SOCKET_DISCONNECT
);
}
//MCU-发送加入课堂请求
_sendJoinClassRequest
()
{
//const classInfo = this.classInfo;
loger
.
log
(
'MCU-发送加入课堂请求.'
);
loger
.
log
(
this
.
classInfo
);
var
descriptorPdu
=
new
pdu
[
'RCConferenceDescriptorPdu'
];
descriptorPdu
.
id
=
this
.
classInfo
.
classId
;
descriptorPdu
.
name
=
this
.
classInfo
.
className
||
""
;
descriptorPdu
.
mode
=
0
;
descriptorPdu
.
capacity
=
1
;
var
joinRequestPdu
=
new
pdu
[
'RCConferenceJoinRequestPdu'
];
joinRequestPdu
.
type
=
2
;
joinRequestPdu
.
initiator
=
this
.
classInfo
.
nodeId
;
joinRequestPdu
.
nodeType
=
PduConsts
.
NT_TERMINAL
;
//normal
joinRequestPdu
.
classDescription
=
descriptorPdu
;
// classDescription
let
pduMsg
=
pdu
.
create_join_class_request_pdu
(
joinRequestPdu
.
type
,
this
.
classInfo
.
nodeId
,
this
.
classInfo
.
classId
,
0
,
ApeConsts
.
BROADCAST_CHANNEL_ID
,
true
,
PduConsts
.
DP_TOP
,
this
.
classInfo
.
topNodeID
,
PduConsts
.
SEG_ONCE
);
pduMsg
.
set
(
"site"
,
this
.
classInfo
.
siteId
);
//课堂号对应的名称
pduMsg
.
set
(
"userId"
,
this
.
classInfo
.
userId
);
pduMsg
.
set
(
"userName"
,
Base64
.
fromByteArray
(
ArrayBufferUtil
.
strToUint8Array
(
this
.
classInfo
.
userName
)));
pduMsg
.
set
(
"userRole"
,
this
.
classInfo
.
userRole
);
pduMsg
.
set
(
"deviceType"
,
""
+
GlobalConfig
.
deviceType
);
pduMsg
.
set
(
"data"
,
joinRequestPdu
.
toArrayBuffer
());
this
.
_everSocket
.
send
(
pduMsg
.
toArrayBuffer
());
}
// EverSocket底层消息处理
_everSocketMsgReceivedHandler
(
data
)
{
let
pduMsg
=
pdu
.
decode_pdu
(
data
);
let
pduType
=
pduMsg
.
get
(
"type"
);
let
pduData
=
pduMsg
.
get
(
"data"
);
//loger.data('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
//loger.log('MCU-FirstLayer封装消息', 'type', pdu.id2type(pduMsg.type), pduMsg.type, 'sessionId', ApeConsts(pduMsg.sessionId), pduMsg.sessionId);
switch
(
pduType
)
{
case
PduType
.
RCPDU_CONNECT_PROVIDER_RESPONSE
:
//加入课堂请求返回数据处理
let
joinConfPdu
=
pdu
[
'RCConferenceJoinResponsePdu'
].
decode
(
pduData
);
let
pduResultCode
=
joinConfPdu
.
result
;
loger
.
warn
(
'RCPDU_CONNECT_PROVIDER_RESPONSE ->pduResultCode:'
+
pduResultCode
);
switch
(
pduResultCode
)
{
case
PduConsts
.
RET_SUCCESS
:
//加入成功
this
.
_updateMCUConfInfoDescription
(
joinConfPdu
.
classDescription
);
this
.
_emit
(
MessageTypes
.
CLASS_JOIN_MCU_SUCCESS
,
this
.
classInfo
);
break
;
case
PduConsts
.
RET_FULL_CAPACITY
:
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_CLASS_JOIN_FULL
);
//this._emit(MessageTypes.CLASS_JOIN_FAILED,MessageTypes.ERR_CLASS_JOIN_FULL);
//this._emit(MessageTypes.CLASS_JOIN_FULL);
break
;
default
:
loger
.
arn
(
'JoinConfPdu-未知类型-等待处理.'
,
pduResultCode
);
break
}
break
;
case
PduType
.
RCPDU_SEND_DATA_REQUEST
:
//先判断当前消息属于哪个APE 根据 sessionId来判断
let
ape
=
this
.
_apes
[
pduMsg
.
sessionId
];
let
sessionLabel
=
ApeConsts
(
pduMsg
.
sessionId
);
if
(
ape
)
{
let
subTypeLabel
=
pdu
.
id2type
(
pduMsg
.
subType
);
//loger.log('MCU-SecondLayer封装消息', 'sessionId', sessionLabel, pduMsg.sessionId, 'subtype', subTypeLabel, pduMsg.subType);
loger
.
warn
(
'MCU->收到消息处理->'
,
GlobalConfig
.
mcuDelay
,
GlobalConfig
.
messageDelay
);
//延迟处理消息(3个条件--->ape允许延迟&&客户端设置需要延迟&&Sass设置的延迟时间大于0)
if
(
ape
.
_apeDelayed
&&
GlobalConfig
.
messageDelay
&&
GlobalConfig
.
mcuDelay
>
0
)
{
loger
.
warn
(
'延迟处理消息->'
,
GlobalConfig
.
mcuDelay
);
setTimeout
(()
=>
{
//this._pduRegAdapterHandler(regBuffer, seekTime);
ape
.
_emit
(
pduMsg
.
subType
,
pduMsg
.
data
);
},
GlobalConfig
.
mcuDelay
*
1000
);
//mcuDelay单位是秒,
// 这里需要换算为毫秒
return
;
}
ape
.
_emit
(
pduMsg
.
subType
,
pduMsg
.
data
);
}
else
{
loger
.
warn
(
sessionLabel
+
'尚未注册'
);
}
break
;
default
:
loger
.
warn
(
'PDU-未知类型-等待处理.'
,
pduType
);
}
break
;
case
PduType
.
RCPDU_SEND_DATA_REQUEST
:
//先判断当前消息属于哪个APE 根据 sessionId来判断
let
ape
=
this
.
_apes
[
pduMsg
.
sessionId
];
let
sessionLabel
=
ApeConsts
(
pduMsg
.
sessionId
);
if
(
ape
)
{
let
subTypeLabel
=
pdu
.
id2type
(
pduMsg
.
subType
);
//loger.log('MCU-SecondLayer封装消息', 'sessionId', sessionLabel, pduMsg.sessionId, 'subtype', subTypeLabel, pduMsg.subType);
//ape广播事件,只要ape中监听就能收到
ape
.
_emit
(
pduMsg
.
subType
,
pduMsg
.
data
);
}
else
{
loger
.
warn
(
sessionLabel
+
'尚未注册'
);
}
break
;
default
:
loger
.
warn
(
'PDU-未知类型-等待处理.'
,
pduType
);
}
}
_updateMCUConfInfoDescription
(
_data
)
{
// let _mcuConfDesc=new pdu['RCConferenceDescriptorPdu'].decode(mcuConfDesc);
loger
.
log
(
'_updateMCUConfInfoDescription.'
);
//let classDescription=new pdu['RCConferenceDescriptorPdu'].decode(_data);
loger
.
log
(
_data
);
//let info = this.mcuClassInfo.info;
//info._conference_name = ArrayBufferUtil.uint8ArrayToStr(mcuConfDesc.name, 0);
//info._capacity = mcuConfDesc.capacity;
//info._mode = mcuConfDesc.mode;
}
// MU服务是否连接
get
connected
()
{
if
(
this
.
_everSocket
&&
this
.
_everSocket
.
connected
)
return
true
;
return
false
;
}
// 课堂发送消息 -- 消息同意序列号
send
(
msg
)
{
if
(
this
.
connected
)
{
loger
.
log
(
'MCU-发送课堂数据....'
);
this
.
_everSocket
.
send
(
msg
.
toArrayBuffer
());
}
else
{
loger
.
log
(
'MCU-发送课堂数据失败,MCU底层通道不可用'
);
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_SOCKET_DISCONNECT
);
_updateMCUConfInfoDescription
(
_data
)
{
// let _mcuConfDesc=new pdu['RCConferenceDescriptorPdu'].decode(mcuConfDesc);
loger
.
log
(
'_updateMCUConfInfoDescription.'
);
//let classDescription=new pdu['RCConferenceDescriptorPdu'].decode(_data);
loger
.
log
(
_data
);
//let info = this.mcuClassInfo.info;
//info._conference_name = ArrayBufferUtil.uint8ArrayToStr(mcuConfDesc.name, 0);
//info._capacity = mcuConfDesc.capacity;
//info._mode = mcuConfDesc.mode;
}
// MU服务是否连接
get
connected
()
{
if
(
this
.
_everSocket
&&
this
.
_everSocket
.
connected
)
return
true
;
return
false
;
}
// 课堂发送消息 -- 消息同意序列号
send
(
msg
)
{
if
(
this
.
connected
)
{
loger
.
log
(
'MCU-发送课堂数据....'
);
this
.
_everSocket
.
send
(
msg
.
toArrayBuffer
());
}
else
{
loger
.
log
(
'MCU-发送课堂数据失败,MCU底层通道不可用'
);
this
.
_emit
(
MessageTypes
.
MCU_ERROR
,
MessageTypes
.
ERR_SOCKET_DISCONNECT
);
}
}
// 主动断开MCU连接
leaveMCU
()
{
for
(
let
ape
in
this
.
_apes
)
{
this
.
_apes
[
ape
].
stopApe
();
}
loger
.
log
(
'leaveMCU'
);
GlobalConfig
.
setCurrentStatus
(
GlobalConfig
.
statusCode_3
);
this
.
_everSocket
.
end
();
}
// 主动建立MCU连接
joinMCU
(
_classInfo
)
{
loger
.
log
(
'开始建立EverSocket通道.'
);
loger
.
log
(
_classInfo
);
_classInfo
.
classId
=
parseInt
(
_classInfo
.
classId
);
// classId 必须整形
this
.
classInfo
=
_classInfo
;
// 创建刷新nodeId
this
.
classInfo
.
nodeId
=
EngineUtils
.
creatSoleNumberFromTimestamp
();
GlobalConfig
.
nodeId
=
this
.
classInfo
.
nodeId
;
//这是标识自己身份的id
let
nodeInfoRecordPdu
=
new
pdu
[
'RCNodeInfoRecordPdu'
];
nodeInfoRecordPdu
.
name
=
this
.
classInfo
.
userName
;
nodeInfoRecordPdu
.
nodeId
=
this
.
classInfo
.
nodeId
;
nodeInfoRecordPdu
.
userId
=
this
.
classInfo
.
userId
;
nodeInfoRecordPdu
.
role
=
ApeConsts
.
userTypesToId
[
this
.
classInfo
.
userRole
]
||
1
;
//NR_NORMAL用户的身份,根据用户登录时的身份设置
nodeInfoRecordPdu
.
level
=
0
;
let
conferenceRecord
=
{};
//RCConferenceRecord_T
conferenceRecord
.
_conference_id
=
this
.
classInfo
.
classId
;
conferenceRecord
.
_top_node_id
=
this
.
classInfo
.
topNodeID
;
this
.
mcuClassInfo
=
{};
//RCMeetingInfo_T
this
.
mcuClassInfo
.
self
=
nodeInfoRecordPdu
;
this
.
mcuClassInfo
.
info
=
conferenceRecord
;
// 内部mcuConfInfo
this
.
classInfo
.
mcuClassInfo
=
this
.
mcuClassInfo
;
//开启EverSocket
this
.
_everSocket
.
begin
(
this
.
classInfo
.
MCUServerIP
,
this
.
classInfo
.
MCUServerPort
);
}
}
// 主动断开MCU连接
leaveMCU
()
{
for
(
let
ape
in
this
.
_apes
)
{
this
.
_apes
[
ape
].
stopApe
();
}
loger
.
log
(
'leaveMCU'
);
GlobalConfig
.
setCurrentStatus
(
GlobalConfig
.
statusCode_3
);
this
.
_everSocket
.
end
();
}
// 主动建立MCU连接
joinMCU
(
_classInfo
)
{
loger
.
log
(
'开始建立EverSocket通道.'
);
loger
.
log
(
_classInfo
);
_classInfo
.
classId
=
parseInt
(
_classInfo
.
classId
);
// classId 必须整形
this
.
classInfo
=
_classInfo
;
// 创建刷新nodeId
this
.
classInfo
.
nodeId
=
EngineUtils
.
creatSoleNumberFromTimestamp
();
GlobalConfig
.
nodeId
=
this
.
classInfo
.
nodeId
;
//这是标识自己身份的id
let
nodeInfoRecordPdu
=
new
pdu
[
'RCNodeInfoRecordPdu'
];
nodeInfoRecordPdu
.
name
=
this
.
classInfo
.
userName
;
nodeInfoRecordPdu
.
nodeId
=
this
.
classInfo
.
nodeId
;
nodeInfoRecordPdu
.
userId
=
this
.
classInfo
.
userId
;
nodeInfoRecordPdu
.
role
=
ApeConsts
.
userTypesToId
[
this
.
classInfo
.
userRole
]
||
1
;
//NR_NORMAL用户的身份,根据用户登录时的身份设置
nodeInfoRecordPdu
.
level
=
0
;
let
conferenceRecord
=
{};
//RCConferenceRecord_T
conferenceRecord
.
_conference_id
=
this
.
classInfo
.
classId
;
conferenceRecord
.
_top_node_id
=
this
.
classInfo
.
topNodeID
;
this
.
mcuClassInfo
=
{};
//RCMeetingInfo_T
this
.
mcuClassInfo
.
self
=
nodeInfoRecordPdu
;
this
.
mcuClassInfo
.
info
=
conferenceRecord
;
// 内部mcuConfInfo
this
.
classInfo
.
mcuClassInfo
=
this
.
mcuClassInfo
;
//开启EverSocket
this
.
_everSocket
.
begin
(
this
.
classInfo
.
MCUServerIP
,
this
.
classInfo
.
MCUServerPort
);
}
}
export
default
new
MCU
;
...
...
src/pdus/pro.js
查看文件 @
17ddcb8
...
...
@@ -777,6 +777,7 @@ message RCAudioChannelInfoPdu {
optional
string
site_id
=
8
;
//站点号
optional
string
user_id
=
9
;
//用户的userId
optional
string
stream_id
=
10
;
//流名称
optional
string
user_name
=
11
;
//用户的名字
}
message
RCVideoChannelInfoPdu
{
...
...
@@ -790,6 +791,7 @@ message RCVideoChannelInfoPdu {
optional
string
site_id
=
8
;
//站点号
optional
string
user_id
=
9
;
//用户的userId
optional
string
stream_id
=
10
;
//流名称
optional
string
user_name
=
11
;
//用户的名字
}
message
RCVideoChannelInfoRecordPdu
{
...
...
请
注册
或
登录
后发表评论