因为产品中要加入网页中网络会议的功能,这几天都在倒腾 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 开视频为例描述这两个过程。

网络信息的交换:

  1. lilei首先创建了一个RTCPeerConnection对象,这个对象会自动的去向STUN服务器询问自己的外网IP和端口。然后lilei把自己的网络信息经过信令服务器中转后,发送给lucy。
  2. lucy接收到lilei的网络信息之后,也创建了一个RTCPeerConnection对象,并把lilei发过来的信息通过addIceCandidate添加到对象中。
  3. lucy把自己的网络信息经过信令服务器的中转后,发送给lilei。
  4. lilei接收到信息后,通过RTCPeerConnection对象的addIceCandidate方法保存lucy的网络信息。

音视频设备信息的交换:

  1. lilei通过RTCPeerConnection对象的createOffer方法,获取本地的音视频编码分辨率等信息,通过setLocalDescription添加到RTCPeerConnection中,并把这些信息经过信令服务器中转后发送给lucy。
  2. lucy接收到lilei发过来的信息后,使用RTCPeerConnection对象的setRemoteDescription方法保存。然后通过createAnswer方法获取自己的音视频信息并以同样的手段发送给lilei。
  3. 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。

用于和客户端交互的会话类代码如下:

    public class Session : WebSocketHandler
{
private static WebSocketCollection sessions = new WebSocketCollection(); public String UserId { get; set; } public override void OnOpen()
{
this.UserId = Guid.NewGuid().ToString("N");
var message = new { type = SignalMessageType.Conect, userId = this.UserId };
sessions.Broadcast(Json.Encode(message)); sessions.Add(this);
} public override void OnMessage(string msg)
{
var obj = Json.Decode(msg);
var messageType = (SignalMessageType)obj.type; switch (messageType)
{
case SignalMessageType.Offer:
case SignalMessageType.Answer:
case SignalMessageType.IceCandidate:
var session = sessions.Cast<Session>().FirstOrDefault(n => n.UserId == obj.userId);
var message = new { type = messageType, userId = this.UserId, description = obj.description };
session.Send(Json.Encode(message));
break;
}
}
} public enum SignalMessageType
{
Conect,
DisConnect,
Offer,
Answer,
IceCandidate
}

WebAPI控制器需要引用命名空间“Microsoft.Web.WebSockets;”代码如下:

    public class SignalServerController : ApiController
{
[HttpGet]
public HttpResponseMessage Connect()
{
var session = new WebRTCDemo.Session();
HttpContext.Current.AcceptWebSocketRequest(session); return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
}
JS脚本:
var RtcConnect = function (_userId, _webSocketHelper) {

    var config = { iceServers: [{ url: 'stun:stunserver.org' }] };
var peerConnection = null;
var userId = _userId;
var webSocketHelper = _webSocketHelper; var createVideo = function (stream) {
var src = window.webkitURL.createObjectURL(stream);
var video = $("<video />").attr("src", src);
var container = $("<div />").addClass("videoContainer").append(video).appendTo($("body")); video[0].play();
return container;
}; var init = function () { window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
peerConnection = window.RTCPeerConnection(config); peerConnection.addEventListener('addstream', function (event) {
createVideo(event.stream);
});
peerConnection.addEventListener('icecandidate', function (event) {
var description = JSON.stringify(event.candidate);
var message = JSON.stringify({ type: 4, userId: userId, description: description });
webSocketHelper.send(message);
}); navigator.getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var localStream = navigator.getMedia({ video: true, audio: true }, getUserMediaSuccess, getUserMediaFail);
peerConnection.addStream(localStream); }; this.connect = function () {
peerConnection.createOffer(function (offer) {
peerConnection.setLocalDescription(offer); var description = JSON.stringify(offer);
var message = JSON.stringify({ type: 2, userId: userId, description: description });
webSocketHelper.send(message);
}); }; this.acceptOffer = function (offer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
peerConnection.createAnswer(function (answer) {
peerConnection.setLocalDescription(answer);
var description = JSON.stringify(answer); var message = JSON.stringify({ type: 3, userId: userId, description: description });
webSocketHelper.send(message);
});
}; this.acceptAnswer = function (answer) {
peerConnection.setRemoteDescription(new RTCSessionDescription(answer)); }; this.addIceCandidate = function (candidate) {
peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
}; init(); }; var WebSocketHelper = function (callback) {
var ws = null;
var url = "ws://" + document.location.host + "/api/Signal/Connect"; var init = function () {
ws = new WebSocket(url);
ws.onmessage = onmessage;
ws.onerror = onerror;
ws.onopen = onopen;
}; var onmessage = function (message) {
callback(JSON.parse(message.data));
}; this.send = function (data) {
ws.send(data);
}; init();
}; $(function() { var rtcConnects = {};
var webSocketHelper = new WebSocketHelper(function (message) {
var rtcConnect = getOrCreateRtcConnect(message.userId);
switch (message.type) {
case 0: //Conect
rtcConnect.connect();
break;
case 2: //Offer
rtcConnect.acceptOffer(JSON.parse(message.description));
break;
case 3: //Answer
rtcConnect.acceptAnswer(JSON.parse(message.description));
break;
case 4: //IceCandidate
rtcConnect.addIceCandidate(JSON.parse(message.description));
break;
default:
break;
}
}); var init = function() {
navigator.getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var stream = navigator.getMedia({ video: true, audio: true }, function() {
var src = window.webkitURL.createObjectURL(stream);
var video = $("<video />").attr("src", src);
$("<div />").addClass("videoContainer").append(video).appendTo($("body")); video[0].play();
}, function (error) { console.error(error); });
}; var getOrCreateRtcConnect = function (userId) {
var rtcConnect = rtcConnects[userId];
if (typeof (rtcConnect) == 'undefined') {
rtcConnect = new rtcConnect(userId, webSocketHelper);
rtcConnects[userId] = rtcConnect;
}
return rtcConnect;
};
init();
});
View代码:
<html>
<head>
<style>
.videoContainer { float: left; padding: 10px 0 10px 10px; width: 210px; margin: 5px; }
.videoContainer > video { width: 200px; height: 150px; margin-top: 5px; }
</style>
</head>
<body>
</body>
</html>
编译后部署到IIS上,让同事都来试试,略有激动。

其他

如果想部署自己专用的STUN服务器,这里(http://www.stunprotocol.org/)有STUN服务器的完整开源实现,原生是运行在Linux上的,但也提供了cgwin下编译的windwos版本。如何编译、运行等在它的github主页上说的比较清楚:https://github.com/jselbie/stunserver

如果觉得自己写那一坨js比较繁琐,这里(http://www.rtcmulticonnection.org/)有一个封装库,简单了解了一下,功能挺强大的。

WebRTC实现网页版多人视频聊天室的更多相关文章

  1. vue仿微信网页版|vue+web端聊天室|仿微信客户端vue版

    一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室— ...

  2. 使用WebRTC搭建前端视频聊天室——点对点通信篇

    WebRTC给我们带来了浏览器中的视频.音频聊天体验.但个人认为,它最实用的特性莫过于DataChannel——在浏览器之间建立一个点对点的数据通道.在DataChannel之前,浏览器到浏览器的数据 ...

  3. 使用WebRTC搭建前端视频聊天室——入门篇

    http://segmentfault.com/a/1190000000436544 什么是WebRTC? 众所周知,浏览器本身不支持相互之间直接建立信道进行通信,都是通过服务器进行中转.比如现在有两 ...

  4. 使用WebRTC搭建前端视频聊天室——信令篇

    博客原文地址 建议看这篇之前先看一下使用WebRTC搭建前端视频聊天室——入门篇 如果需要搭建实例的话可以参照SkyRTC-demo:github地址 其中使用了两个库:SkyRTC(github地址 ...

  5. 开源Flex Air版免费激情美女视频聊天室,免费网络远程视频会议系统((Flex,Fms3联合打造))

    开源Flex Air版免费激情美女视频聊天室,免费网络远程视频会议系统((Flex,Fms3联合打造))   Flex,Fms3系列文章导航 Flex,Fms3相关文章索引 本篇是视频聊天,会议开发实 ...

  6. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  7. 视频聊天室可以用php制作吗?

    首先,告诉你单纯用php制作视频聊天室是实现不了的,需要配合其他技术手段一起操作,例如和FLASH配合,使用FLASH获取语音(FLASH可以获取访问端的设备,例如摄像头). PHP运行在服务器端,是 ...

  8. 基于JQuery+JSP的无数据库无刷新多人在线聊天室

    JQuery是一款非常强大的javascript插件,本文就针对Ajax前台和JSP后台来实现一个无刷新的多人在线聊天室,该实现的数据全部存储在服务端内存里,没有用到数据库,本文会提供所有源程序,需要 ...

  9. 在Ubuntu上部署一个基于webrtc的多人视频聊天服务

    最近研究webrtc视频直播技术,网上找了些教程最终都不太能顺利跑起来的,可能是文章写的比较老,使用的一些开源组件已经更新了,有些配置已经不太一样了,所以按照以前的步骤会有问题.折腾了一阵终于跑起来了 ...

随机推荐

  1. bc:linux下命令行计算器

    在linux下,存在一个命令行的计算器:bc.该程序一般随发行版发布. bc计算器能够执行一些基本的计算,包括+,-,×,\,%. 这些计算不经针对十进制,还可以使用二进制,八进制,十六进制,并且可以 ...

  2. sublimeText3安装emmet(For Mac)

    每次重装st,安装emmet都困难重重,对上一次依照网上查的资料一步步做好了,这次又忘了如何操作,结果又是网上搜索打开一箩筐的网页. 终于决定,把这些惨痛的经历记录下来,要用的话自己看,也可能可以帮助 ...

  3. 有趣的代码: fixTypeof

    typeof 可以匹配对象的类型,但是他的能力很弱,比如 typeof new String('123')会显示的object这是我们不想看到的结果很久以前JQ的作者通过Object.prototyp ...

  4. python smtplib发送邮件遇到的认证问题

    python的smtplib模块主要是用来发送邮件的,使用起来比较方便. 使用程序发送邮件只需要写以下几行代码就OK了: #!/usr/bin/env python import smtplib s ...

  5. About memories in ASIC FPGA

    1. Write first | Read First | No Change区别在于:en & wr的时候,dout是什么,三种case对应于: dout = din; dout = mem ...

  6. 结构化查询语言(SQL)数据类型

    简要描述一下结构化查询语言中的五种数据类型:字符型,文本型,数值型,逻辑型和日期型. 字符型 VARCHARVS CHAR VARCHAR型和CHAR型数据的这个差别是细微的,但是非常重要.他们都是用 ...

  7. MySQL数据库安装与配置详解

    转载提示:在原文http://www.cnblogs.com/sshoub/p/4321640.html基础上修改. 目录 一.概述 二.MySQL安装 三.安装成功验证 四.NavicatforMy ...

  8. ZOJ3795_Grouping

    告诉你某些人的年龄大小关系,问你把所有的人分成若干个组,最少需要多少组,使得组内任意两个人的年龄不可比. 首先考虑特殊情况,如果所有年龄关系构成了一个环,那么这个环中所有人的年龄都是相等,也就是可比的 ...

  9. 在DW 5.5+PhoneGap+Jquery Mobile下搭建移动开发环境

    移动设备应用开发有多难,只要学会HTML5+Javascript就可以.用Dreamweaver5.5+PhoneGap+Jquery Mobile搭建移动开发环境,轻轻松松开发你自己的应用.让你用普 ...

  10. SSE and Websocket

    http://www.w3school.com.cn/html5/html_5_serversentevents.asp http://javascript.ruanyifeng.com/htmlap ...