整体

在浏览其中输入https://dst_host_domain:13004后, 请求了index.html,该文件在licode\extras\basic_example\public\index.html开始, 引入了erizo.js和script.js, testConnection()

//licode\extras\basic_example\public\index.html

<html>
<head>
<title>Licode Basic Example</title>
<script type="text/javascript" src="erizo.js"></script>
<script type="text/javascript" src="script.js"></script>//定义了windows.load
</head> <body>
<button id="startButton" onclick="startBasicExample()" disabled>Start</button>
<button id="testConnection" onclick="testConnection()">Test Connection</button>
<button id="recordButton" onclick="startRecording()" disabled>Toggle Recording</button>
<button id="slideShowMode" onclick="toggleSlideShowMode()" disabled>Toggle Slide Show Mode</button>
<h1 id="startWarning">Press the start buttong to start receiving streams</h1>
<div id="videoContainer"></div>
</body>
</html>

由于scripts中设置了window.onload()的回调,所以加载的时候就直接运行了如下回调,去调用例子

//licode\extras\basic_example\public\script.js

window.onload = () => {
const onlySubscribe = getParameterByName('onlySubscribe');
const bypassStartButton = getParameterByName('noStart');
if (!onlySubscribe || bypassStartButton) {
startBasicExample();//启动例子
} else {
document.getElementById('startButton').disabled = false;
}
};

startBasicExample()首先会读取配置,创建本地stream

//licode\extras\basic_example\public\script.js

  localStream = Erizo.Stream(config); //创建本地stream

Erizo是erizo.js定义导出的全局对象, Stream是其函数对象,函数定义Stream.js中, 其提供了一些操作流的接口,包括播放,控制等等,并且还创建了事件分发器,用来处理publisher或者room的事件

//licode\erizo_controller\erizoClient\src\Erizo.js

const Erizo = {
Room: Room.bind(null, undefined, undefined, undefined),
LicodeEvent,
RoomEvent,
StreamEvent,
//Stream 使用Stream.bind创建返回一个函数对象赋值
//创建的时候该对象函数中的this指针置NULL, 调用时第一个参数默认为undefined
Stream: Stream.bind(null, undefined),
Logger,
};
export default Erizo;

客户端完成本地媒体的初始化之后,将生成的Roomdata当作参数发送createtoken请求给服务端, 响应后调用callback进行回调

//licode\extras\basic_example\public\script.js

  const createToken = (roomData, callback) => {
const req = new XMLHttpRequest();
const url = `${serverUrl}createToken/`; req.onreadystatechange = () => { //设置响应回调
if (req.readyState === 4) {
callback(req.responseText);
}
}; req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/json');
req.send(JSON.stringify(roomData));
}; createToken(roomData, (response) => {.....});

创建token请求会被发送到服务器,服务器的express http框架会进行处理,将请求通过匹配到对应函数,对请求进行处理,此处为创建完token并同时创建room,将tokenroomid返回发送回去

//licode\extras\basic_example\basicServer.js

basicServer.js

app.post('/createToken/', (req, res) => {
console.log('Creating token. Request body: ', req.body); const username = req.body.username;
const role = req.body.role; let room = defaultRoomName;
let type;
let roomId;
let mediaConfiguration; if (req.body.room) room = req.body.room;
if (req.body.type) type = req.body.type;
if (req.body.roomId) roomId = req.body.roomId;
if (req.body.mediaConfiguration) mediaConfiguration = req.body.mediaConfiguration; const createToken = (tokenRoomId) => {
N.API.createToken(tokenRoomId, username, role, (token) => {
console.log('Token created', token);
res.send(token);//将token发送回去
}, (error) => {
console.log('Error creating token', error);
res.status(401).send('No Erizo Controller found');
});
}; if (roomId) {
createToken(roomId);
} else {
getOrCreateRoom(room, type, mediaConfiguration, createToken);
}
});

发送了createroken请求,客户端收到token之后,根据返回的token(其中包含了服务端创建的room的一些信息)去初始化Room对象,并为一些事件绑定回调,比如房间连接成功了,流订阅等等, 然后调用localStream.init() 初始化本地媒体

//licode\extras\basic_example\public\script.js

  createToken(roomData, (response) => {
const token = response;
console.log(token);
//创建房间
room = Erizo.Room({ token }); //创建订阅流接口
const subscribeToStreams = (streams) => {...... }; //添加一些事件处理回调,
room.addEventListener('room-connected', (roomEvent) => {......}); room.addEventListener('stream-subscribed', (streamEvent) => {......}); room.addEventListener('stream-added', (streamEvent) => {......}); room.addEventListener('stream-removed', (streamEvent) => {......}); room.addEventListener('stream-failed', () => {......}); if (onlySubscribe) {
room.connect({ singlePC });
} else {
//默认执行的地方
const div = document.createElement('div');
div.setAttribute('style', 'width: 320px; height: 240px; float:left');
div.setAttribute('id', 'myVideo');
document.getElementById('videoContainer').appendChild(div);
//为'access-accepted'事件设置回调,该回调渲染视频画面
localStream.addEventListener('access-accepted', () => {
room.connect({ singlePC });
localStream.show('myVideo');
});
//初始化
localStream.init();
}
});

其中,在room.connect()时候,会对得到的token进行解析获得erizocontroller,也就是licode的媒体服务器入口的ip和port,建立ws连接,建立完成后,通过事件管理器(EventDispatcher)向上层抛出room-connected事件, room-connected事件的处理回调中,调用了room.publishroom.autoSubscribe进行推拉流

事件处理

无论是Erizo.Room还是Erizo.Stream,都可以分别在Room.js和Stream.js中找到其对应的对象生成方式,在生成对象的过程中都可以看到是先从生成一个EventDispatcher,然后在其上面作派生的

  const that = EventDispatcher(spec);

EventDispatcher是一个事件处理器,在Event.js中可以找到,其维护了一个对象数组eventListeners,将事件和回调做了key-value的绑定,当事件发生的时候,外部调用dispatchEvent 遍历搜索,执行其回调

//licode\erizo_controller\erizoClient\src\Events.js

const EventDispatcher = () => {
const that = {};
// Private vars
const dispatcher = {
eventListeners: {},
}; // Public functions //将事件和回调放到对象数组中去
that.addEventListener = (eventType, listener) => {
if (dispatcher.eventListeners[eventType] === undefined) {
dispatcher.eventListeners[eventType] = [];
}
dispatcher.eventListeners[eventType].push(listener);
}; // It removes an available event listener.
that.removeEventListener = (eventType, listener) => {
if (!dispatcher.eventListeners[eventType]) {
return;
}
const index = dispatcher.eventListeners[eventType].indexOf(listener);
if (index !== -1) {
dispatcher.eventListeners[eventType].splice(index, 1);
}
}; // It removes all listeners
that.removeAllListeners = () => {
dispatcher.eventListeners = {};
}; that.dispatchEvent = (event) => {
//遍历,找到该event的回调,并执行
let listeners = dispatcher.eventListeners[event.type] || [];
listeners = listeners.slice(0);
for (let i = 0; i < listeners.length; i += 1) {
listeners[i](event);
}
}; that.on = that.addEventListener;
that.off = that.removeEventListener;
that.emit = that.dispatchEvent; return that;
};

在使用Erizo.Room({ token });创建Room对象的过程中,可以看到其是先生成一个EventDispatcher对象然后在其上面进行扩展。

媒体

publish

publishroom-connected之后发生

//licode\extras\basic_example\public\script.js

      if (!onlySubscribe) {
room.publish(localStream, options);//将本地媒体publish
}

该函数实际如下,根据config对流进行一些设置之后开始推流

//licode\erizo_controller\erizoClient\src\Room.js

  that.publish = (streamInput, optionsInput = {}, callback = () => {}) => {
const stream = streamInput;
const options = optionsInput; //设置流的一些属性以及会调
省略......
if (stream && stream.local && !stream.failed && !localStreams.has(stream.getID())) {
if (stream.hasMedia()) {
if (stream.isExternal()) {
publishExternal(stream, options, callback);
} else if (that.p2p) {
publishP2P(stream, options, callback);
} else {
publishErizo(stream, options, callback);//推流
}
} else if (stream.hasData()) {
publishData(stream, options, callback);
}
} else {
Logger.error('Trying to publish invalid stream, stream:', stream);
callback(undefined, 'Invalid Stream');
}
};

publishErizo中发送了SDP,将流填充到本地数组进行管理, 创建流连接

//licode\erizo_controller\erizoClient\src\Room.js

  const publishErizo = (streamInput, options, callback = () => {}) => {
const stream = streamInput;
Logger.info('Publishing to Erizo Normally, is createOffer', options.createOffer);
const constraints = createSdpConstraints('erizo', stream, options);
constraints.minVideoBW = options.minVideoBW;
constraints.maxVideoBW = options.maxVideoBW;
constraints.scheme = options.scheme; //发送publish信令到媒体服务器和SDP
socket.sendSDP('publish', constraints, undefined, (id, erizoId, connectionId, error) => {
if (id === null) {
Logger.error('Error publishing stream', error);
callback(undefined, error);
return;
}
//填充流
populateStreamFunctions(id, stream, error, undefined);
//创建流连接
createLocalStreamErizoConnection(stream, connectionId, erizoId, options);
callback(id);
});
};

创建流连接中添加了icestatechanged的失败回调,以及调用了pc连接中的addstream接口

//licode\erizo_controller\erizoClient\src\Room.js

 const createLocalStreamErizoConnection = (streamInput, connectionId, erizoId, options) => {
const stream = streamInput;
const connectionOpts = getErizoConnectionOptions(stream, connectionId, erizoId, options);
stream.addPC(
that.erizoConnectionManager
.getOrBuildErizoConnection(connectionOpts, erizoId, spec.singlePC)); //绑定icestatechanged到failed的回调
stream.on('icestatechanged', (evt) => {
Logger.info(`${stream.getID()} - iceConnectionState: ${evt.msg.state}`);
if (evt.msg.state === 'failed') {
const message = 'ICE Connection Failed';
onStreamFailed(stream, message, 'ice-client');
if (spec.singlePC) {
connectionOpts.callback({ type: 'failed' });
}
}
});
//调用pcconnect连接中的添加流
stream.pc.addStream(stream);
};

其中pc连接是定义licode\erizo_controller\erizoClient\src\ErizoConnectionManager.js中的class ErizoConnection,其对浏览器原生的webrtc的js接口包了一层,并继承了事件发生器,将有关连接以及媒体的事件抛到上层的事件处理器中进行处理, 此处调用了原生的接口addStream之后,通过后续的发送offer协商完成之后就会自动开始推流。

licode(1) Basic Example 客户端解析的更多相关文章

  1. HTTP Basic 验证客户端 C#实现笔记

    HTTP Basic 验证客户端的原理:把HTTP头重的ContentType设置为:application/x-www-form-urlencoded如果HTTP头没有Authorization,那 ...

  2. LINUX DNS客户端 解析域名慢的问题。

    Linux系统下域名解析的配置文件是/etc/resolv.conf cat /etc/resolv.conf # Generated by NetworkManager options single ...

  3. Springboot 构建http服务,返回的http行是'HTTP/1.1 200' 无状态码描述 客户端解析错误

    ————————————————————————————————————————— *** 响应的数据格式  HTTP/1.1 200 OK  Server: Apache-Coyote/1.1  A ...

  4. OpenVPN客户端解析

    windows版本的VPN客户端,实际上就是一个外壳,创建了图形界面,托盘,和 右键菜单, 在connect的动作里,实际上是通过cmd调用 openvpn.exe openvpn --config ...

  5. Mysql异常问题排查与处理——mysql的DNS反向解析和客户端网卡重启

    中午刚想趴一会,不料锅从天降!!!Mysql连不上了....... 现象如下: 现象1:登录mysql所在服务器,连接MySQL 成功: 现象2:通过客户端远程连接MySQL,返回失败,如下: Ent ...

  6. Ace教你一步一步做Android新闻客户端(三) JSON数据解析

    对于服务器端来说,返回给客户端的数据格式一般分为html.xml和json这三种格式,现在给大家讲解一下json这个知识点, 1 如何通过json-lib和gson这两个json解析库来对解析我们的j ...

  7. Arcgis瓦片--js客户端加载

    接上篇博客,下载好arcgis格式的瓦片数据以后,需要用js客户端在前端加载出来.这里介绍两种方案: 1.使用超图iServer将瓦片发布成rest地图服务,或者arcgis地图服务,客户端直接加载 ...

  8. javascript客户端与服务器端通信

    高性能的网络通信包括以下方面:选择正确的数据格式和与之匹配的传输技术. 一.数据格式 用于传输的数据格式有: 1)html,仅适用于特定场合,传输数据量大,不过它可以节省客户端的CPU周期, 2)XM ...

  9. Delphi用QJSON解析JSON格式的数据

    本来用superobject来解析JSON已经够用了,可惜这个东东不能在移动端使用,于是找到QJSON来处理. 这是一个国内高手写开源免费的东西,赞一个. 假入数据如下: {"message ...

随机推荐

  1. C#深入浅出之操作符和控制流程

    操作符 操作符简单举例就是生活中的+-*/等等运算符号,下面会详细讨论运算符内容. 一元正负操作符 有时候需要改变数值的正负号.一元操作符(-)可以使得数字的正负号改变. 例如:int a = -11 ...

  2. Java生鲜电商平台-系统异常状态的设计与架构(APP应用或者生鲜小程序)

    Java生鲜电商平台-系统异常状态的设计与架构 说明:在实际开发Java生鲜电商平台的时候,异常状态的设计关系着整体系统的性能问题,架构设计,以及稳定性方面,对此,我根据实际的业务场景,进行了系统设计 ...

  3. Java学习 1.1——(JVM介绍)Java为什么能够跨平台?

    首先介绍一下Java的各个层级,先放一张图: 硬件,操作系统和操作系统接口:这三级不说大家都知道,操作系统有很多种,比如Windows,Linux.Windows又分为win7,win10,win x ...

  4. feign使用hystrix熔断的配置

    熔断器hystrix 在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很正常的. Hystrix 可以让我们在分布式系统中对服务间的调用 ...

  5. spark shell操作

    RDD有两种类型的操作 ,分别是Transformation(返回一个新的RDD)和Action(返回values). 1.Transformation:根据已有RDD创建新的RDD数据集build ...

  6. Python Exception处理

    Python中的错误处理分为两类:语法错误和异常处理.语法错误一般是指由于python语句.表达式.函数等存在书写格式活语法规则上的错误抛出的异常,如python常见的缩进控制,若同层次的执行语句存在 ...

  7. centos7设置静态ip-修改配置文件方式

    修改IP地址为静态地址需要修改配置文件,首先打开配置文件,在控制台输入cd /etc/sysconfig/network-scripts 输入ifconfig,这样就可以看到你的ip地址等信息了. v ...

  8. 信号处理函数陷阱:调用malloc导致死锁[转]

    概览 因malloc是加锁的,上网了解的相关信息,额外了解到信号处理规范使用,mark 正文 在执行malloc的过程中,跳转到了信号处理函数中.而信号处理函数在调用某个系统api时,内部又调用了ma ...

  9. 小程序-picker组件选择数量

    <!-- detail.wxml --> <view class="picker"> <picker range="{{range}}&qu ...

  10. 各种数和各种反演(所谓FFT的前置知识?)

    每次问NC做多项式的题需要什么知识点. 各种数. 各种反演. 多项式全家桶. 然后我就一个一个地学知识点.然而还差好多,学到后面的前面的已经忘了(可能是我太菜吧不是谁都是NC啊) 然后发现每个知识点基 ...