WebRTC基于浏览器的开发

WebRTC简介


WebRTC通信原理

WebRTC需要通过长链接查找到通信双方,然后通过 peer to peer 的方式传输音频数据。

PeerConnection

WebRTC中最主要的就是一个叫做PeerConnection的对象,这个是WebRTC中已经封装好的对象。每一路的音视频会话都会有唯一的一个PeerConnection对象,WebRTC通过这个PeerConnection对象进行视频的发起、传输、接收和挂断等操作。
PeerConnection中包含的属性如下:

  • localDescription:本地描述信息,类型:RTCSessionDescription
  • remoteDescription:远端描述信息,类型:RTCSessionDescription
  • onicecandidate:传入一个回调方法,该回调方法有一个返回参数,返回参数类型为:RTCIceCandidateEvent
  • onaddstream:传入一个回调方法,该回调方法有一个返回参数,返回参数类型为:``,如果检测到有远程媒体流传输到本地之后便会调用该方法。
  • ondatachannel:(暂未用到)
  • oniceconnectionstatechange:(暂未用到)
  • onnegotiationneeded:(暂未用到)
  • onremovestream:(暂未用到)
  • onsignalingstatechange:(暂未用到)

PeerConnection中还包含了一些方法:

  • setLocalDescription:设置本地offer,将自己的描述信息加入到PeerConnection中,参数类型:RTCSessionDescription
  • setRemoteDescription:设置远端的answer,将对方的描述信息加入到PeerConnection中,参数类型:RTCSessionDescription
  • createOffer:创建一个offer,需要传入两个参数,第一个参数是创建offer成功的回调方法,会返回创建好的offer,可以在这里将这个offer发送出去。第二个参数是创建失败的回调方法,会返回错误信息。
  • createAnswer:创建一个answer,需要传入两个参数,第一个参数是创建answer成功的回调方法,会返回创建好的answer,可以在这里将这个answer发送出去。第二个参数是创建失败的回调方法,会返回错误信息。
  • addIceCandidate:将打洞服务器加入到配置信息中,参数类型:RTCIceCandidate
  • addStream:向PeerConnection中加入需要发送的数据流,参数类型:MediaStream
  • close:
  • createDTMFSender:
  • createDataChannel:
  • getLocalStreams:
  • getRemoteStreams:
  • getStats:
  • getStreamById:
  • removeStream:
  • updateIce:
RTCSessionDescription

RTCSessionDescription类型中包含了两个属性:

  • sdp:这个包含了所有的音视频的配置信息。
  • type:这个指明了是视频的接收方还是发起方,这个将在之后进行讨论。
通信过程:

A向B发起通信请求

  1. A链接socket;
  2. A获取音频数据;
  3. A创建一个Ice Candidate
  4. A通过创建好的Ice Candidate创建一个PeerConnection
  5. A创建一个offeroffer中包含了视频设置sdp,将创建好的offer设置为PeerConnectionlocalDescription
  6. A同时将创建的offerIce Candidate通过socket发送给B;
  7. 将A获取到的音频数据存入PeerConnection
  8. 如果B先接收到A发过来的offer,那么先将offer存起来,等到接收到A发过来的Ice Candidate后通过Ice Candidate创建一个PeerConnection,再将保存好的offer设置为PeerConnectionremoteDescription
    如果B先接收到A发过来的Ice Candidate,那么通过A发过来的Ice Candidate创建一个PeerConnection,然后等待接收到A发过来的offer,再将A发过来的offer设置为PeerConnectionremoteDescription
  9. B接收到A发过来的offer后要创建一个answer,将answer设置为PeerConnectionlocalDescription。并且将创建的answer通过socket返回给A。
  10. B开始获取音频数据,将音频数据存入PeerConnection中,WebRTC便会自动将音频数据发送给A。
  11. A接收到B返回的answer,将B返回的answer设置为PeerConnectionremoteDescription
  12. 这个时候WebRTC会将音频数据自动发送给B,A和B就建立起了实时音频通信。

WebRTC实现

1.信令服务器

首先WebRTC需要一个信令服务器,也就是一个socket链接用来发起视频通信,发送WebRTC中的offer和回复answer
如何搭建一个简单的socket服务器,可以找我的这篇文章《》,也可以是用webSocket搭建信令服务器。

2.打洞服务器

WebRTC需要打洞服务器(一个stun,一个turn)来穿透防火墙等,我们需要配置打洞服务器:

var iceServer = {
    "iceServers": [{
        "urls" : ["stun:stun.l.google.com:19302"]
    }, {
        "urls" : ["turn:numb.viagenie.ca"],
        "username" : "webrtc@live.com",
        "credential" : "muazkh"
    }]
};
3.创建PeerConnection

WebRTC由于是未来的一种即时通信的标准,所以目前在Chrome、Firefox和Opera浏览器中有内置插件,均提供一个全局的PeerConnection类。

  • Chrome浏览器中为webkitRTCPeerConnection
  • FireFox浏览器中为mozRTCPeerConnection
  • Opera浏览器中暂时没有特殊名称

创建PeerConnection的对象:

var peerConnection = new webkitRTCPeerConnection(iceServer) 

创建时需要传入打洞服务器的配置信息,如果不穿入打洞服务器的配置信息,则只可以在内网中使用实时音频通讯。

由于PeerConnection是全局的,所以我们可以通过另外的一种方式进行创建:

window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var peerConnection = new RTCPeerConnection(iceServer) 

如何判断浏览器是否支持WebRTC:

if (RTCPeerConnection) (function () {
    console.log("浏览器支持实时音频通讯");
    // 这里面可以做其他操作
})();else {
    console.log("您使用的浏览器暂不支持实时音频通讯。");
}
4.获取本地音视频数据

WebRTC也提供了一个全局单例来获取本地的音视频信息:

  • Chrome浏览器中为webkitGetUserMedia
  • Firefox浏览器中为mozGetUserMedia
  • Opera浏览器中为msGetUserMedia

GetUserMedia需要传入三个参数,第一个参数为配置信息,第二个参数为获取成功的回调,第三个参数为获取失败的回调。
获取到视频流之后

navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia
navigator.getMedia({
    audio: true, // 是否开启麦克风
    video: true // 是否开启摄像头,这里还可以进行更多的配置
}, function(stream){
    // 获取到视频流stream
    // 绑定本地媒体流到video标签用于输出
    document.getElementById('localVideo').src = URL.createObjectURL(stream);
    // 向PeerConnection中加入需要发送的流
    peerConnection.addStream(stream);
}, function(error){
    // 获取本地视频流失败
})
5.发起音频通话请求

创建一个offer并发送给指定的对象:

peerConnection.createOffer(function(desc){
    console.log("创建offer成功");
    // 将创建好的offer设置为本地offer
    peerConnection.setLocalDescription(desc);
    // 通过socket发送offer
}, function(error){
    // 创建offer失败
    console.log("创建offer失败");
})

创建offer时要同时发送打洞服务器配置信息,WebRTC给了一个监听:

peerConnection.onicecandidate = function (event) {
    console.log("发送打洞服务器配置信息");
}

返回的参数中有一个candidate属性,便是打洞服务器的配置信息。

6.收到音频通话请求

音频通话请求是通过socket发来的,需要通过socket去监听。

如果收到了offer,那么需要将offer存到自己的peerConnection中,并且创建一个answer发送回对方。
将offer存入peerConnection中:

peerConnection.setRemoteDescription(new RTCSessionDescription(json.data.sdp));

创建一个answer并返回给对方:

peerConnection.createAnswer(function(desc){
    console.log("创建answer成功");
    // 将创建好的answer设置为本地offer
    peerConnection.setLocalDescription(desc);
    // 通过socket发送answer
}, function(error){
    // 创建answer失败
    console.log("创建answer失败");
})

如果收到了打洞服务器的配置信息,那么需要将打洞服务器的配置信息存入到peerConnection中:

peerConnection.addIceCandidate(new RTCIceCandidate(json.data.candidate));

发送给对方answer后便可以等待接受对方的数据流了:

peerConnection.onaddstream = function(event){
    console.log("检测到媒体流连接到本地");
    // 绑定远程媒体流到video标签用于输出
    document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream);
};

至此,整个简单的WebRTC的流程就完成了

WebRTC例子

<html>
<body>
    Local: <br>
<video id="localVideo" autoplay></video><br>
    Remote: <br>
<video id="remoteVideo" autoplay></video>

<script>
    console.log("开始");
    // 仅仅用于控制哪一端的浏览器发起offer,#号后面有值的一方发起
    // #号后面加true的为发起者
    var isCaller = window.location.href.split('#')[1];

    // 与信令服务器的WebSocket连接
    var socket = new WebSocket("ws://127.0.0.1:3000");

    // stun和turn服务器,打洞服务器设置
    var iceServer = {
        "iceServers": [{
            "url": "stun:stun.l.google.com:19302"
        }, {
            "url": "turn:numb.viagenie.ca",
            "username": "webrtc@live.com",
            "credential": "muazkh"
        }]
    };

    // 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯)
    window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
    var peerConnection = new RTCPeerConnection(iceServer) 

    // 发送offer的函数
    var sendOfferFn = function (desc) {
        // 设置本地Offer
        peerConnection.setLocalDescription(desc); 
        // 发送offer
        socket.send(JSON.stringify({
            "event": "_offer",
                "data": {
                    "sdp": desc
                }
            }));
    };
    // 发送answer的函数,发送本地session描述
    var sendAnswerFn = function(desc){ // 发送answer
        peerConnection.setLocalDescription(desc); // 设置本地Offer
        socket.send(JSON.stringify({ // 发送answer
            "event": "_answer",
            "data": {
                "sdp": desc
            }
        }));
    };

    // 获取本地音频数据
    navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia
    navigator.getMedia({
        audio: true, // 是否开启麦克风
        video: true // 是否开启摄像头,这里还可以进行更多的配置
    }, function(stream){ 
        // 获取到视频流stream 
        // 绑定本地媒体流到video标签用于输出 
        document.getElementById('localVideo').src = URL.createObjectURL(stream); 
        // 向PeerConnection中加入需要发送的流 
        peerConnection.addStream(stream);
        // 如果是发起方则发送一个offer信令
        if(isCaller){
            peerConnection.createOffer(sendOfferFn, function (error) {
                console.log('Failure callback: ' + error);
            });
        }
    }, function(error){ 
        // 获取本地视频流失败
        console.log("获取本地视频流失败");
    })

    // 发送ICE候选到其他客户端
    peerConnection.onicecandidate = function(event){
        if (event.candidate !== null) {
            socket.send(JSON.stringify({
                "event": "_ice_candidate",
                "data": {
                    "candidate": event.candidate
                }
            }));
        }
    };

    // 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出
    peerConnection.onaddstream = function(event){
        document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream);
    };

    //处理到来的信令
    socket.onmessage = function(event){
        var json = JSON.parse(event.data);
        //如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
        if( json.event === "_ice_candidate" ){
            peerConnection.addIceCandidate(new RTCIceCandidate(json.data.candidate));
        } else {
            peerConnection.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
            // 如果是一个offer,那么需要回复一个answer
            if(json.event === "_offer") {
                peerConnection.createAnswer(sendAnswerFn, function (error) {
                    console.log('Failure callback: ' + error);
                });
            }
        }
    };
</script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容