WebRTC实现网页版多人视频聊天室
因为产品中要加入网页中网络会议的功能,这几天都在倒腾 WebRTC,现在分享下工作成果。
话说 WebRTC
Real Time Communication 简称 RTC,是谷歌若干年前收购的一项技术,后来把这项技术应用到浏览器中并开源出来,而且搞了一套标准提交给W3C,称为WebRTC,官方地址是:http://www.webrtc.org/。WebRTC要求浏览器内置实时传输音视频的功能,并提供一致的API供JS使用。目前实现这套标准的浏览器有:Chrome、FireFox、Opera。微软虽然也在对WebRTC标准的制定做贡献,但仍然没有在任何版本的IE中支持WebRTC,所以,对于IE浏览器,不得不安装Chrome Frame插件来支持WebRTC;对于Safari浏览器,可以使用WebRtc4all这个插件,地址是:https://code.google.com/p/webrtc4all/。
WebRTC基础
WebRTC提供了三个API:MediaStream、RTCPeerConnection、RTCDataChannel。 MediaStream 用于获取本地的 音视频流。不同的浏览器名称不一样,但参数一样,谷歌和Opera是navigator.webkitGetUserMedia,火狐是 navigator.mozGetUserMedia。 RTCPeerConnection:和 getUserMedia 一样 谷歌和火狐分别会有webkit、moz前缀。这个对象主要用于两个浏览器之间建立连接以及传输音视频流。 RTCDataChannel 用于两个浏览器之间传输自定义的数据,用这个对象可以实现互发消息,而不用经过服务端的中转。
WebRTC的实现是建立浏览器之间的直接连接,而不需要其他服务器的中转,即P2P,这就要求彼此之间需要知道对方的外网地址。但大多数计算机都位于NAT之后,只有少部分主机拥有外网地址,这就要求一种方式可以穿透NAT,STUN和TURN就是这样的技术。对于STUN和TURN的详细介绍,可以查看这里(http://www.h3c.com.cn/MiniSite/Technology_Circle/Net_Reptile/The_Five/Home/Catalog/201206/747038_97665_0.htm)。
WebRTC会使用默认的或程序指定的SUTN服务器,获取指向当前主机的外网地址和端口。谷歌浏览器默认的是谷歌域名下的一个STUN,国内可能不大稳定,于是我找到了这个 stunserver.org/ ,连接速度比较快,据说当年飞信就是使用的这个,应该比较可靠。如果信不过第三方的STUN服务,也可以自己搭建一台,搭建过程也挺简单。
P2P的建立过程需要依赖服务端中转外网IP及端口、音视频设备配置信息,所以服务端需要使用可以双工通讯的手段,比如WebSocket,来实现信令的中转,称之为信令服务器。
WebRTC会话的建立详解
会话的建立主要有两个过程:网络信息的交换、音视频设备信息的交换。以下以 lilei 要和 Lucy 开视频为例描述这两个过程。
网络信息的交换:
lilei首先创建了一个RTCPeerConnection对象,这个对象会自动的去向STUN服务器询问自己的外网IP和端口。然后lilei把自己的网络信息经过信令服务器中转后,发送给lucy。 lucy接收到lilei的网络信息之后,也创建了一个RTCPeerConnection对象,并把lilei发过来的信息通过addIceCandidate添加到对象中。 lucy把自己的网络信息经过信令服务器的中转后,发送给lilei。 lilei接收到信息后,通过RTCPeerConnection对象的addIceCandidate方法保存lucy的网络信息。
音视频设备信息的交换:
lilei通过RTCPeerConnection对象的createOffer方法,获取本地的音视频编码分辨率等信息,通过setLocalDescription添加到RTCPeerConnection中,并把这些信息经过信令服务器中转后发送给lucy。 lucy接收到lilei发过来的信息后,使用RTCPeerConnection对象的setRemoteDescription方法保存。然后通过createAnswer方法获取自己的音视频信息并以同样的手段发送给lilei。 lilei接收到lucy的信 息,调用setRemoteDescription方法保存。
以上两个过程可以是并发的,并无先后顺序,但必须得等到两个过程都完成后,P2P的连接才真正的建立。一旦连接建立,lilei和lucy就可以直接发送音视频流,而不需要中转。WebRTC在获取本地网络信息的时候,会先尝试STUN,如果失败,则会使用TURN。
WebRTC + Asp.net Web API 实现视频聊天室
首先使用WebSocket实现信令服务器部分,在此需要用到微软开发的用于实现WebSocket的dll (http://www.nuget.org/packages/Microsoft.WebSockets/),以及Json.net。
用于和客户端交互的会话类代码如下:
view sourceprint?01.
public
class
Session : WebSocketHandler
02.
{
03.
private
static
WebSocketCollection sessions =
new
WebSocketCollection();
04.
05.
public
String UserId { get; set; }
06.
07.
public
override
void
OnOpen()
08.
{
09.
this
.UserId = Guid.NewGuid().ToString(
'N'
);
10.
var message =
new
{ type = SignalMessageType.Conect, userId =
this
.UserId };
11.
sessions.Broadcast(Json.Encode(message));
12.
13.
sessions.Add(
this
);
14.
}
15.
16.
public
override
void
OnMessage(string msg)
17.
{
18.
var obj = Json.Decode(msg);
19.
var messageType = (SignalMessageType)obj.type;
20.
21.
switch
(messageType)
22.
{
23.
case
SignalMessageType.Offer:
24.
case
SignalMessageType.Answer:
25.
case
SignalMessageType.IceCandidate:
26.
var session = sessions.Cast<Session>().FirstOrDefault(n => n.UserId == obj.userId);
27.
var message =
new
{ type = messageType, userId =
this
.UserId, description = obj.description };
28.
session.Send(Json.Encode(message));
29.
break
;
30.
}
31.
}
32.
}
33.
34.
public
enum
SignalMessageType
35.
{
36.
Conect,
37.
DisConnect,
38.
Offer,
39.
Answer,
40.
IceCandidate
41.
}
WebAPI控制器需要引用命名空间“Microsoft.Web.WebSockets;”代码如下:
view sourceprint?01.
public
class
SignalServerController : ApiController
02.
{
03.
[HttpGet]
04.
public
HttpResponseMessage Connect()
05.
{
06.
var session =
new
WebRTCDemo.Session();
07.
HttpContext.Current.AcceptWebSocketRequest(session);
08.
09.
return
new
HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
10.
}
11.
}
JS脚本:
view sourceprint?001.
var RtcConnect = function (_userId, _webSocketHelper) {
002.
003.
var config = { iceServers: [{ url:
'stun:stunserver.org'
}] };
004.
var peerConnection =
null
;
005.
var userId = _userId;
006.
var webSocketHelper = _webSocketHelper;
007.
008.
var createVideo = function (stream) {
009.
var src = window.webkitURL.createObjectURL(stream);
010.
var video = $(
'<video />'
).attr(
'src'
, src);
011.
var container = $(
'<div />'
).addClass(
'videoContainer'
).append(video).appendTo($(
'body'
));
012.
013.
video[
0
].play();
014.
return
container;
015.
};
016.
017.
var init = function () {
018.
019.
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
020.
peerConnection = window.RTCPeerConnection(config);
021.
022.
peerConnection.addEventListener(
'addstream'
, function (event) {
023.
createVideo(event.stream);
024.
});
025.
peerConnection.addEventListener(
'icecandidate'
, function (event) {
026.
var description = JSON.stringify(event.candidate);
027.
var message = JSON.stringify({ type:
4
, userId: userId, description: description });
028.
webSocketHelper.send(message);
029.
});
030.
031.
navigator.getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
032.
var localStream = navigator.getMedia({ video:
true
, audio:
true
}, getUserMediaSuccess, getUserMediaFail);
033.
peerConnection.addStream(localStream);
034.
035.
};
036.
037.
this
.connect = function () {
038.
peerConnection.createOffer(function (offer) {
039.
peerConnection.setLocalDescription(offer);
040.
041.
var description = JSON.stringify(offer);
042.
var message = JSON.stringify({ type:
2
, userId: userId, description: description });
043.
webSocketHelper.send(message);
044.
});
045.
046.
};
047.
048.
this
.acceptOffer = function (offer) {
049.
peerConnection.setRemoteDescription(
new
RTCSessionDescription(offer));
050.
peerConnection.createAnswer(function (answer) {
051.
peerConnection.setLocalDescription(answer);
052.
var description = JSON.stringify(answer);
053.
054.
var message = JSON.stringify({ type:
3
, userId: userId, description: description });
055.
webSocketHelper.send(message);
056.
});
057.
};
058.
059.
this
.acceptAnswer = function (answer) {
060.
peerConnection.setRemoteDescription(
new
RTCSessionDescription(answer));
061.
062.
};
063.
064.
this
.addIceCandidate = function (candidate) {
065.
peerConnection.addIceCandidate(
new
RTCIceCandidate(candidate));
066.
};
067.
068.
init();
069.
070.
};
071.
072.
var WebSocketHelper = function (callback) {
073.
var ws =
null
;
074.
var url =
'ws://'
+ document.location.host +
'/api/Signal/Connect'
;
075.
076.
var init = function () {
077.
ws =
new
WebSocket(url);
078.
ws.onmessage = onmessage;
079.
ws.onerror = onerror;
080.
ws.onopen = onopen;
081.
};
082.
083.
var onmessage = function (message) {
084.
callback(JSON.parse(message.data));
085.
};
086.
087.
this
.send = function (data) {
088.
ws.send(data);
089.
};
090.
091.
init();
092.
};
093.
094.
$(function() {
095.
096.
var rtcConnects = {};
097.
var webSocketHelper =
new
WebSocketHelper(function (message) {
098.
var rtcConnect = getOrCreateRtcConnect(message.userId);
099.
switch
(message.type) {
100.
case
0
:
//Conect
101.
rtcConnect.connect();
102.
break
;
103.
case
2
:
//Offer
104.
rtcConnect.acceptOffer(JSON.parse(message.description));
105.
break
;
106.
case
3
:
//Answer
107.
rtcConnect.acceptAnswer(JSON.parse(message.description));
108.
break
;
109.
case
4
:
//IceCandidate
110.
rtcConnect.addIceCandidate(JSON.parse(message.description));
111.
break
;
112.
default
:
113.
break
;
114.
}
115.
});
116.
117.
var init = function() {
118.
navigator.getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
119.
var stream = navigator.getMedia({ video:
true
, audio:
true
}, function() {
120.
var src = window.webkitURL.createObjectURL(stream);
121.
var video = $(
'<video />'
).attr(
'src'
, src);
122.
$(
'<div />'
).addClass(
'videoContainer'
).append(video).appendTo($(
'body'
));
123.
124.
video[
0
].play();
125.
}, function (error) { console.error(error); });
126.
};
127.
128.
var getOrCreateRtcConnect = function (userId) {
129.
var rtcConnect = rtcConnects[userId];
130.
if
(typeof (rtcConnect) ==
'undefined'
) {
131.
rtcConnect =
new
rtcConnect(userId, webSocketHelper);
132.
rtcConnects[userId] = rtcConnect;
133.
}
134.
return
rtcConnect;
135.
};
136.
init();
137.
});
View代码:
view sourceprint?01.
<html>
02.
<head>
03.
<style>
04.
.videoContainer {
float
: left; padding: 10px
0
10px 10px; width: 210px; margin: 5px; }
05.
.videoContainer > video { width: 200px; height: 150px; margin-top: 5px; }
06.
</style>
07.
</head>
08.
<body>
09.
</body>
10.
</html>
编译后部署到IIS上,让同事都来试试,略有激动。
其他
如果想部署自己专用的STUN服务器,这里(http://www.stunprotocol.org/)有STUN服务器的完整开源实现,原生是运行在Linux上的,但也提供了cgwin下编译的windwos版本。如何编译、运行等在它的github主页上说的比较清楚:https://github.com/jselbie/stunserver。
如果觉得自己写那一坨js比较繁琐,这里(http://www.rtcmulticonnection.org/)有一个封装库,简单了解了一下,功能挺强大的。
WebRTC实现网页版多人视频聊天室相关推荐
- FlexAir 开源版-多人视频聊天室,网络远程多人视频会议系统((Flex,Fms3联合开发))视频聊天,会议开发实战
本篇是视频聊天,会议开发实例系列文章的第8篇,该系 列所有文章链接如下: http://www.cnblogs.com/aierong/archive/2008/12/30/Flex.html#sp ...
- Android 集成 Agora SDK 快速体验 RTC 版多人视频聊天|掘金技术征文
RTC (Real-Time Communication) 作为实时通讯领域的"新贵",在互动直播.远程控制.多人视频会议.屏幕共享等领域广受好评,如果你还不了解 RTC ,Tak ...
- vue仿微信网页版|vue+web端聊天室|仿微信客户端vue版
一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室- ...
- android 使用WebRTC搭建视频聊天室
使用WebRTC搭建前端视频聊天室--入门篇 https://www.jianshu.com/p/b54b27970534 android webrtc 两个手机 P2P 视频聊天 https://w ...
- 都秀多人视频聊天软件【常用软件】
都秀视频聊天软件是北京都秀网络科技有限公司基于宽带网络推出的"全新网络多人视频聊天工具".都秀聊天室整合了文本.语音.视频等多种互联网通信形式于一身,旨在为广大互联网用户提供融合数 ...
- 实现一个简单的视频聊天室(源码)
在 <实现一个简单的语音聊天室>一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头.视频的处理,逻辑会繁杂一些,本 ...
- 类似YY 9158网页版多人语音视频聊天室远程教学系统源码
仿六间房网页视频聊天室 网页视频直播系统源码 开发采用FMS加flash只需在网页上就可发布视频直播,无需安装插件儿 主要应用远程教育,培训,远程视频直播(类似6间房聊天室) 视频采用H264压缩一路 ...
- 实践:《从头到脚撸一个多人视频聊天 — 前端 WebRTC 实战(一)》
2019独角兽企业重金招聘Python工程师标准>>> 请先阅读原文,链接:从头到脚撸一个多人视频聊天 - 前端 WebRTC 实战(一),本文只涉及实践过程中的问题 1.video ...
- 网页版多人聊天室系统
网页版多人聊天室系统 SockJS+Spring+SpringMVC+Mybaties+EasyUI+Mysql+Tomcat7+Jdk7 兼容IE8 源代码下载:https://download.c ...
最新文章
- 【嵌入式】Libmodbus源码分析(二)-常用接口函数分析
- 域服务器可以修改ip,Windows Server 2016 域控制器修改IP
- LeetCode 421. 数组中两个数的最大异或值(Trie树)
- 支付宝二面微服务、分布式架构?太真实了!
- 《区块链》都火了两年多了,你还不知道它是什么?
- python读取txt第二行_使用python获取csv文本的某行或某列数据的实例
- 分布式操作系统与网络操作系统异同点
- android 中通过SimpleDateFormat自定义显示英文的时间格式
- Python的h5py模块
- 回归分析中f多少合适_spss回归分析F值很大,有100多,这样合理吗
- Excel常用数据分析技能(PowerBI)
- 小米平板4 Plus简单刷成开发版获取Root超级权限的流程
- JAVA 8 lambda 表达式实现按某个属性值查找对象集合中符合条件的对象
- SpringBoot整合Apollo配置中心快速使用
- 深入理解CSS margin折叠
- HTML语言的语法结构,语言的语法结构类型
- 平稳性检验(描述性)与纯随机性检验
- INA226使用之程序与模块测试
- 武林风云之数据库的基本操作
- 灵云工作室计算机,灵云全方位人工智能开放平台:AI赋能 共享产业未来