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/)有一个封装库,简单了解了一下,功能挺强大的。

时间: 2024-10-27 08:42:39

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

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

一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室——vueWebChat,实现了发送消息.表情(动图),图片.视频预览,右键菜单.截屏.截图可直接粘贴至文本框进行发送. 二.技术框架 MVVM框架:Vue2.5.6 状态管理:Vuex 页面路由:Vue-router iconfont图标:阿里巴巴字体图标库 自定义滚动条:vue-gemini-sc

开个多人视频聊天软件需要多少钱?

首先,不知道你开发的是哪一种产品,因为多人视频聊天软件很多种,像我们雅顾合作就起码有几十种产品,这边给你们推荐下我们最主要的三种: 一.人气娱乐视频聊天软件 雅顾推荐人气娱乐视频聊天软件.以房间为单位,有共同兴趣爱好的网友可以欢聚一室.房间设计可供多元化选择,如经典款竖二屏,经典款竖三屏:清新版横三屏:风尚版竖二屏等, 让您娱乐更加随心所欲!清晰的视频画面,丰富的视频特效,专业的音响效果,网友可通过音视频的方式尽情展示个性风采,观看精彩互动节目. 二.人气会员视频聊天软件 所谓人气会员就是和qq

C#实现多人视频聊天

在 <C#实现多人语音聊天>一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头.视频的处理,逻辑会繁杂一些,本文就实现一个简单的多人视频聊天系统,让多个人可以进入同一个房间进行语音视频沟通.先看看3个人进行视频聊天的运行效果截图:       上面两张截图分别是:登录界面.标注了各个控件的视频聊天室的主界面. 一. C/S结构 很明显,我这个语音聊天室采用的是C/S结构,整个项目结构相对比较简单,如下所示: 同语音聊天室

实现一个简单的视频聊天室(源码)

在 <实现一个简单的语音聊天室>一文发布后,很多朋友建议我也实现一个视频聊天室给他们参考一下,其实,视频聊天室与语音聊天室的原理是差不多的,由于加入了摄像头.视频的处理,逻辑会繁杂一些,本文就实现一个简单的多人视频聊天系统,让多个人可以进入同一个房间进行语音视频沟通.先看看3个人进行视频聊天的运行效果截图:       上面两张截图分别是:登录界面.标注了各个控件的视频聊天室的主界面. 一. C/S结构 很明显,我这个语音聊天室采用的是C/S结构,整个项目结构相对比较简单,如下所示: 同语音聊

3分钟实现网页版多人文本、视频聊天室 (含完整源码)

基于SimpleWebRTC快速实现网页版的多人文本.视频聊天室. 1 实现方法 复制下面的代码,保存为一个html文件 <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://simplewebrtc.com/latest.js"

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

http://segmentfault.com/a/1190000000436544 什么是WebRTC? 众所周知,浏览器本身不支持相互之间直接建立信道进行通信,都是通过服务器进行中转.比如现在有两个客户端,甲和乙,他们俩想要通信,首先需要甲和服务器.乙和服务器之间建立信道.甲给乙发送消息时,甲先将消息发送到服务器上,服务器对甲的消息进行中转,发送到乙处,反过来也是一样.这样甲与乙之间的一次消息要通过两段信道,通信的效率同时受制于这两段信道的带宽.同时这样的信道并不适合数据流的传输,如何建立浏

使用WebRTC搭建前端视频聊天室——数据通道篇

转自 使用WebRTC搭建前端视频聊天室——数据通道篇 在两个浏览器中,为聊天.游戏.或是文件传输等需求发送信息是十分复杂的.通常情况下,我们需要建立一台服务器来转发数据,当然规模比较大的情况下,会扩展成多个数据中心.这种情况下很容易出现很高的延迟,同时难以保证数据的私密性. 这些问题可以通过WebRTC提供的RTCDataChannel API来解决,他能直接在点对点之间传输数据.这篇文章将介绍如何创建并使用数据通道,并提供了一些网络上常见的用例 为了充分理解这篇文章,你可能需要去了解一些RT

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

博客原文地址 建议看这篇之前先看一下使用WebRTC搭建前端视频聊天室——入门篇 如果需要搭建实例的话可以参照SkyRTC-demo:github地址 其中使用了两个库:SkyRTC(github地址)和SkyRTC-client(github地址) 这两个库和demo都是我写的,如果有bug或是错误欢迎指出,我会尽力更正 前面的话 这篇文章讲述了WebRTC中所涉及的信令交换以及聊天室中的信令交换,主要内容来自WebRTC in the real world: STUN, TURN and s

Android多人视频聊天应用的开发(三)多人聊天

在上一篇<Android多人视频聊天应用的开发(二)一对一聊天>中我们学习了如何使用声网Agora SDK进行一对一的聊天,本篇主要讨论如何使用Agora SDK进行多人聊天.主要需要实现以下功能: 1.上一篇已经实现过的聊天功能 2.随着加入人数和他们的手机摄像头分辨率的变化,显示不同的UI,即所谓的"分屏" 3.点击分屏中的小窗,可以放大显示该聊天窗 分屏 根据前期技术调研,分屏显示最好的方式是采用瀑布流结合动态聊天窗实现,这样比较方便的能够适应UI的变化.所谓瀑布流,