zoukankan      html  css  js  c++  java
  • 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>
     
    转自:https://www.jianshu.com/p/57fd3b5d2f80 作者:Shmily落墨
  • 相关阅读:
    SpringBoot--日期格式化
    SpringBoot--使用redis实现分布式限流
    SpringBoot--集成Shiro
    xxl-job搭建、部署、SpringBoot集成xxl-job
    SpringBoot--使用socket搭建聊天室
    SpringBoot--数据库管理与迁移(LiquiBase)
    SpringBoot--防止重复提交(锁机制---本地锁、分布式锁)
    Springboot--元注解及自定义注解(表单验证)
    java类对象的初始化顺序
    java23种设计模式(三)单例模式
  • 原文地址:https://www.cnblogs.com/javalinux/p/14445874.html
Copyright © 2011-2022 走看看