Janus源码分析(5)——echotest分析
1、运行效果图
Echo测试旨在验证通过传输音频和视频至服务器网关后是否会收到相应的回复,并如图所示展示了实验结果。

2、代码分析
2.1 代码结构

2.2 源码分析
2.2.1 创建线程
说明

通过调用createSession方法发送请求并完成了一次httpAPICall操作后返回了创建的处理编号:174710721773778
Janus.httpAPICall(server, {
verb: 'POST',
withCredentials: withCredentials,
body: request,
success: function(json) {
Janus.debug(json);
if(json["janus"] !== "success") {
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
callbacks.error(json["error"].reason);
return;
}
Janus.sessions[sessionId] = that;
eventHandler();
callbacks.success();
},
成功回调eventHandler以及echoest的sucesss
2.2.2 创建插件
echoest的sucesss函数如下:
定义了插值对象及对应的回调函数。
janus.attach(
{
......
}
this.attach = function(callbacks) { createHandle(callbacks)}
实际调用createHandle,并把对应的回调plugin,succecss等传入,如下图。

在该createHandle function中实现了多个插件功能模块的具体实现,并通过调用echotest.js中的janus.attach success callback完成状态反馈机制的设计。该模块不仅输出"Plugin attached!"(janus.plugin.echotest, id=1747107217737787),还详细定义了一个完整的响应机制框架:包括接收并处理相应事件、处理逻辑设计以及与其他系统的交互实现等关键功能点。
pluginHandles[handleId] = pluginHandle;
callbacks.success(pluginHandle);
echotest = pluginHandle;定义为插件,其中定义许多函数。包括:
var pluginHandle =
{
session : that,
plugin : plugin,
id : handleId,
token : handleToken,
detached : false,
webrtcStuff : {}
getId : function() { return handleId; },
getPlugin : function() { return plugin; },
getVolume : function() { return getVolume(handleId); },
isAudioMuted : function() { return isMuted(handleId, false); },
muteAudio : function() { return mute(handleId, false, true); },
unmuteAudio : function() { return mute(handleId, false, false); },
isVideoMuted : function() { return isMuted(handleId, true); },
muteVideo : function() { return mute(handleId, true, true); },
unmuteVideo : function() { return mute(handleId, true, false); },
getBitrate : function() { return getBitrate(handleId); },
send : function(callbacks) { sendMessage(handleId, callbacks); },
data : function(callbacks) { sendData(handleId, callbacks); },
dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
consentDialog : callbacks.consentDialog,
iceState : callbacks.iceState,
mediaState : callbacks.mediaState,
webrtcState : callbacks.webrtcState,
slowLink : callbacks.slowLink,
onmessage : callbacks.onmessage,
createOffer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
createAnswer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
onlocalstream : callbacks.onlocalstream,
onremotestream : callbacks.onremotestream,
ondata : callbacks.ondata,
ondataopen : callbacks.ondataopen,
oncleanup : callbacks.oncleanup,
ondetached : callbacks.ondetached,
hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
detach : function(callbacks) { destroyHandle(handleId, callbacks); }
}
并且随后向服务端发送一个请求,在收到成功响应之前不会触发echotest.js中的janus.attach的成功回调函数即可
success: function(pluginHandle) {
$('#details').remove();
echotest = pluginHandle;
Janus.log("Plugin attached! (" + echotest.getPlugin() + ", id=" + echotest.getId() + ")");
// Negotiate WebRTC
var body = { "audio": true, "video": true };
Janus.debug("Sending message (" + JSON.stringify(body) + ")");
echotest.send({"message": body});
Janus.debug("Trying a createOffer too (audio/video sendrecv)");
echotest.createOffer(
{
// No media provided: by default, it's sendrecv for audio and video
media: { data: true }, // Let's negotiate data channels as well
// If you want to test simulcasting (Chrome and Firefox only), then
// pass a ?simulcast=true when opening this demo page: it will turn
// the following 'simulcast' property to pass to janus.js to true
simulcast: doSimulcast,
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
echotest.send({"message": body, "jsep": jsep});
},
error: function(error) {
Janus.error("WebRTC error:", error);
bootbox.alert("WebRTC error... " + JSON.stringify(error));
}
});
$('#start').removeAttr('disabled').html("Stop")
.click(function() {
$(this).attr('disabled', true);
clearInterval(bitrateTimer);
janus.destroy();
});
},
在这个函数准备向服务器发送相应的信息连接请求。
发送音视频信息
var body = { "audio": true, "video": true };
echotest.send({"message": body});
echotest.send 实际调用sendMessage,发送的请求为:
var request = { "janus": "message", "body": message, "transaction": transaction };
Sending message to plugin (handle=3650608414117457),响应:

接着执行success函数。随后使用handleEvent获取数据,并继续执行Onmessage函数。信息如下:

The echotest.createOffer function is invoked by prepareWebrtc. Attempting a createOffer attempt ( audio / video sendrecv. Log the aforementioned message and invoke the HTTP API call to establish communication.
function eventHandler()
负责发送httpAPICall请求,并在请求成功后调用handleEvent
当handleEvent接收到信息时,将调用Onmessage函数

2.2.3 创建offer信息
通过触发echotest.createOffe函数的调用过程来实现功能目标,在该过程中将getUserMedia操作用于获取本地流数据,并随后触发streamsDone任务以完成整体流程。代码如下所示:
navigator.mediaDevices.getUserMedia(gumConstraints)
.then(function(stream) {
pluginHandle.consentDialog(false);
streamsDone(handleId, jsep, media, callbacks, stream); })
注
// 根据当前条件,选择执行下面功能
config.pc = new RTCPeerConnection(pc_config, pc_constraints);
pluginHandle.onlocalstream(config.myStream);//显示本地流
createOffer(handleId, media, callbacks);//调用函数 createOffe
config.pc.setRemoteDescription//如果已经有sdp信息,远程sdp,调用此处
在函数function createOffer(handleId, media, callbacks)中:
config.pc.createOffer 调用系统的函数,并且成功的话回调。
当 callbacks.success(jsep) 被触发(即调用 echotest.createOffer)时:
- 准备向服务器发送 sdp 信息。
- 当收到本地 sdp 信息(success when):
- 准备向服务器发送 sdp 信息。
success: function(jsep) {
Janus.debug("Got SDP!");
Janus.debug(jsep);
}

调用send函数用于发送消息。若成功发送,则返回ack包。

2.2.4 收到SDP信息
当服务器完成处理后会主动将sdp信息发送给客户端,并由客户端通过handleEvent事件进行处理
else if(json["janus"] === "event") {
Janus.debug("Got a plugin event on session " + sessionId);
var callback = pluginHandle.onmessage;
if(callback !== null && callback !== undefined) {
Janus.debug("Notifying application...");
callback(data, jsep);//实际调用echotest.js中函数onmessage
}
收到消息,成功回调函数onmessage。收到远程的SDP信息。

同时调用echotest.handleRemoteJsep({jsep: jsep});其作用是配置远程会话描述信息, 用于建立与远程端点之间的P2P连接, 并通过config.pc.setRemoteDescription完成相关设置.
在该回调streamsDone内部用来获取远程流配置信息config.pc.ontrack, 其中这个函数是属于webrtc自定义的一个特定于webrtc的回调函数
config.pc.ontrack = function(event) {
pluginHandle.onremotestream(config.remoteStream);
// ...
}

并且回调onremotestream用来显示远程流。
3、通用插件交互时序图

4、参考资料
Webrtc server: Introduction to Janus EchoTest learning.
