zoukankan      html  css  js  c++  java
  • WebRTC学习(六)端对端传输

    一:媒体能力协商

    (一)RTCPeerConnection回顾

    WebRTC学习(一)WebRTC了解

    RTCPeerConnection类是整个WebRTC的一个核心类,它是上层的一个统一的接口,但是在底层做了非常多的复杂逻辑,包括了整个媒体的协商,流和轨道的处理,接收与发送,统计数据,都是由这一个类处理的。

    所以对上层来说,你可能简单的调用了这个类或者里面的几个简单的API,但是实际在底层做了大量的工作,所以这个类是整个WebRTC的一个核心。

    RTCPeerConnection基本格式:

    注意:configuration配置参数,是可选的

    (二)RTCPeerConnection方法分类

    1.媒体协商

    第一类,就是媒体协商相关的,比较简单,包括四个方法。

    通过这几个方法之后,就可以拿到整个双方之间的媒体信息,然后双方会进行交换信息,去协商双方用的编码器,音频格式,协商一致后,他才真正地进行数据传输与编解码。

    2.Stream/Track

    第二大类,就是流与轨道,在整个WebRTC当中,每一路它是一路流,这个流里面有很多轨,有音频轨和视频轨,多路音频轨多路视频轨,在此前MediaStream中已经做过介绍,那在这里是同样的理念,在传输中我们传输的就是Stream和轨,在轨道中就包含了音频数据和视频数据。

    3.传输的相关方法

    第三类,是与传输相关的,就是通过RTP协议去传输,通过RTCP反馈这个链路的质量好坏。再通过数据的统计分析,查看链路的质量,是不是已经发生拥塞,还是说链路就是不太好的,都可以通过这个传输相关的方法来获取到去计算。

    4.统计相关方法

    最后一类的就是统计相关的,包括编解码器,音频格式,视频格式,整个传输相关的数据都可以通过这个统计相关的方法汇报。

    以上就是RTCPeerConnection方法的分类。在后面,我们也会学习到如何通过这些方法来获取到这些数据,来为我们真正的产品的质量提供帮助。

    (三)媒体协商过程(其中SDP信息和Candidate数据的处理,详细参考二:端对端连接)

    对于这两个端来说,一个假设是A,第二个是B。

    1.对于A,首先是要创建一个offer,这个offer就是SDP的一种描述格式去描述的,实际就形成了一个SDP。SDP是包含了一些媒体信息和编解码信息,包括这个传输的相关的信息。

    2.创建完成SDP之后,A通过云端的信令Channel将SDP传给B,但是传之前的那他还要调一个方法setLocalDescription,触发一个非常重要的动作,就是setLocalDescription会触发一个非常重要动作就是去收集candidate,就是收集候选者。最后通过信令服务器发送SDP信息,在收到candidate后(由TURN/STUN服务器响应),也通过信令服务器转发给对端!!!

    补充:收集候选者

    p2p学习过程中了解到,当我们去创建这个连接之前,首先要拿到所有的那种候选者,去收集候选者,是像这个stun或者是turn发送一个请求,在这个请求过程中,会拿到本地主机的IP地址,还有通过NAT之后反射的这个地址以及TURN服务的中继地址。

    3.B端收到这个offer之后,他首先调用setRemoteDescription,将这个offer所形成的SDP的这个数据放到自己远端的描述信息的槽里。

    4.之后B端要回一个answer,通过这个PeerConnection连接,调用方法去创建answer,也就是说创建一个包含B端的媒体信息(和A端offer中的信息一样)。

    5.在传递answer到云端信令服务之前,B端也会去调用setLocalDescription方法,去触发收集候选者,我的这个网络有多少个候选者都要收集起来形成一个列表,调用这个函数之后它将请求获取候选者,之后通过这个信令服务就将answer转给了A,同时处理候选者列表。

    6.A收到这个answer之后它又把它存到这个setRemoteDescription,存到它的远程槽里,这样在每一端实际都有两个SDP

    7.那么第一个SDP是我自己的这个媒体信息,第二个SDP是描述对方的媒体信息。那个拿着这两个媒体信息之后,他在内部就进行一个协商,协商双方所支持的音视频、编解码格式,取出这个交集之后,整个协商过程就算建立完成。

    8.协商完成之后才能进行后面的操作,进行真正的编解码。传输数据到对方进行解码,去渲染音频,渲染视频。这就是完整的一个协商过程。

    总之,每一端是有四个步骤:

    调用方创建offer,设置LocalDescription,然后是接收answer,设置RemoteDescription;
    
    被调用方,他就是先接收offer,然后设置setRemoteDescription,然后是创建answer上设置setLocalDescription。

    (四)协商状态变化

    那么下面呢,我们再看看协商的状态的变化:stable、have-local-offer、have-remote-offer、have-local-PRanswer、have-remote-PRanswer

    1.当我们一开始创建这个RTCPeerConnection的时候,处于stable稳定状态,那么这个时候实际connection就可以使用了,但用的时候它是不能进行编解码的,为什么呢

    因为他没有进行数据协商,虽然我这个connection类是可以用,但是并没有进行数据协商,所以他没法儿进行数据的传输与编解码。 

    2.对于调用者来说,首先创建了connection之后,需要创建这个offer,创建offer之后通过调用那个setLocalDescription将这个offer传参进去后,状态变化,变成什么呢?

    变成have-local-offer,当调用者设完这个之后,如果对方没有给我回他的answer的时候,那实际我的状态就一直处于have-local-offer状态,无论我再接受多少次这个setLocalDescription方法仍在处理这个状态,所以这个状态是不会变的。 

    3.那什么时候调用者才会进行编解码呢?只有在远端的answer回来的时候,如前面所讲的远端的answer创建好,然后通过消息传给这个调用者的时候,那它会调用这个setRemoteDescription,那么将answer设进去之后,他又回到了stable状态,这个时候RTCpeerConnection又可以使用了,并且是已经协商过的了,这时候调用端可以进行编解码,进行传输,这是对于调用者来说。

    4.那么对于这个被调用者来说,同样,那当他收到这个offer之后呢,它要调用setRemote offer,这个时候呢,他从那个stable状态就变成了have-remote-offer,那同样的,当他自己创建了一个answer之后,并且调用了setLocalDescription这个方法将answer设置进去之后,他又从这个remote-offer变成了stable状态,那这个时候他也可以工作了

    以上状态变化过程如下:

     
    对于调用者来说,首先在创建offer之后呢,会调用setLocalDescription将这个offer设置进去,调用者的状态,就变成了have-local-offer,那当他收到对端的这个answer之后呢,它会调用setRemoteDescription将这个offer设置进去,这样就完成了一个协商,就从这个have-local-offer变为了stable状态,那他就可以继续下面的工作了。
    
    而对于被调用者,他首先是从信令服务器收到一个offer,那他首先调用setRemoteDescription获取这个offer,那它就变成了have-remote-offer状态,这个时候再调用自己的这个create answer方法, 创建完自己的这个answer之后调用setLocalDescription(answer)那就从这个have-remote-offer变为了stable状态,这样的被调用者他也就完成了自己的协商工作,可以继续下面的操作。 

    但是还是两种情况,会有一种中间的这个状态叫做PRanswer,就是提前应答,这个状态是什么时候会产生呢,就是在双方通讯的时候其中被调用者还没有准备好数据的时候,那可以先创建一个临时的这个answer

    那这个临时的answer有一个特点,就是它没有媒体数据也就是说没有音频流和视频流,并且将这个发送的方向设置成send  only,

    什么意思呢, 对于B来说,他回的这个answer是一个什么样的answer呢 ?

    就是说,我的媒体流还没有准备好,所以就没有媒体流,但是我呢,只能发送,不能接受,当他发给对方A的时候,A收到这样一个send  only,他就知道,对方还不能进入数据,所以这时候他们的通讯虽然是做了的协商,但是他们之间还不能进行通讯。

    因为第一个是对方没有媒体流,第二个是对方不接受我的数据。

    处于这样一个状态有什么好处呢?

    那就是可以提前建立这个链路的连接,也就是说包括ICE,包括这个DLS这些跟链路相关的这个协商其实都已经创建好了,对刚才我们已经介绍了,就是对于B来说,他已经提前准备好了一个answer,但这个answer里有没有媒体数据,但是呢实际是有网络数据的,我收集的各种各种候选者实际都已经有了

    那么就可以提前交给这个A,那AB之间,实际就是链路层已经协商好了,包括这个DLS还要进行这个握手,因为是安全加密,加密所以要进行握手,握手的时间其实还是蛮长的,那在B准备好这个自己的流之前,将所有的链路都准备好,
    
    那一旦这个B向那个用户申请说想开启音频和视频,当用户授权说可以,这个时候呢,他们拿到数据之后,只要将数据传进去,就可以进行这个通讯了。

    以上状态变化过程如下:

    B没有准备好之前,他可以使用一个PRanswer,就是提前预定好的一个answer给这个A发过去,发过去之后,它就变成了这个have-remote-offer这个状态,这是一个中间状态在这个状态下,双方的这个链路是可以协商好的,只是没有这个媒体数据。
    当B设置好他自己的媒体流之后,就是一切都准备好之后,然后再给他回一个最终的answer当调用者收到它这个最终的answer之后呢,他又变成了stable状态,那双方就可以就真正协商好了。
    这时候呢,实际是减少了底层的这个网络流的这个握手,以及一些其他的逻辑处理工作,这样就节省了时间。

    对于对端A也是类似的,所以在他回这个真正的answer之前,他是处于这have
    -local-PRanswer的,当真正的这个最终的Answer,准备好之后,再重新设一下setLocalAnswer,他又变成了stable状态。

    这就是一个整个协商完整的一个状态变化,只有在整个协商完成之后,才能进行我们后边的真正的音视频数据的传输以及编解码,这就是协商状态的变化。

    (五)媒体协商方法

    createOffer,创建一个本地的这个媒体信息,音频编解码视频编解码等等。

    对于这个对端呢,就是收到offer之后呢,它会创建一个createAnswer,这是第二个方法,也就是说我本地的一个信息最终要传给这个调用者。

    那第三个就是setLocalDescription,我把我自己本地的这个SDP的描述信息设置好之后我就可以触发去采集这个收集候选者了 。

    那第四个就是setRemoteDescription,当收到到对端的这个描述信息之后,将它设的setRemoteDescription这个槽儿里去,在内部做真正的协商,就是媒体协商的方法。

    createOffer:

    PeerConnection类中有一个方法就是createOffer,它有一个可选的option,有几个选项,每个选项都有其特殊的意义。返回的是一个promise,创建成功之后有一个逻辑处理,失败会做另外一个逻辑处理。

    createAnswer:

    那这个createAnswer这个格式其实跟他是类似的,就变成了createAnswer,还有一个option,这个option其实就是作用不大,主要是createOffer是这个option有很多作用。

    setLocalDescription:

    setLocalDescription的格式就是将createOffer或者createAnswer的结果,包括参数设置到这里就设置好了,

    setRemoteDescription:

    同样道理,setRemoteDescription。那这个格式也比较简单,也是刚那个对端的这个sessionDescription设置进来就好了,这是四个协商相关的方法。

    (六)Track方法

    要在RTCPeerConnection里就有两个重要的这个Track的方法,一个是添加,一个是移除。

    这个比较简单,添加的格式就是:

    第一个是要添加的这个Track的是音频的Track还是视频的Track。

    那么第二个是stream,那么这个stream从哪儿来呢?实际就是我们之前介绍的getUserMedia那里我们会拿到一个流,这个流里面可能有音频Track有视频Track,那就要遍历一下,让他们一个个都加入到这个PeerConnection里去,这样PeerConnection就可以控制这每一路轨了,从轨中获取到数据进行发送,这是添加。

    还有其他一些参数。

    removeTrack这个比较简单,就是将Addtrack里头这个send放进去,然后他就可以这个移除掉。

    (七)重要事件

    就是PeerConnection还有几个比较重要的事件,现在呢,我们首先介绍两个:

     

    那么第一个是这个协商事件,当进行媒体协商的时候,就会触发这个事件,onnegotiationneeded就是需要协商,只要协商的时候会触发这个事件。

    那么第二个是onicecandidate,就是当我们收到一个ICE的候选者的时候,也会从底层触发这个事件,告诉我们现在有一个候选者来了,那么我们要拿到这个候选者,将它添加到我们的这个ICE里去。

    二:端到端连接的基本流程

    端到端连接的一个基本流程,下图非常清楚的表达了A与B这两个端首先进行媒体协商、最终进行链路的连接、最后进行媒体数据的传输,下面就来进行分析:

    (一)流程图成员

    首先在这张图里面有4个实体:

    第一个是A,也就是端到端连接的A端。
    
    然后是B,是端到端连接的B端。
    
    然后是信令服务器Signal。
    
    最后是stun/turn,这个stun和turn服务用的是同一台服务器,既具有stun功能又具有turn功能。

    (二)通信流程

    1.首先A是发起端也就是呼叫端,呼叫端要与信令服务器建立连接,被呼叫端B端也要与信令服务器建立连接,这样他们就可以经过信令服务器对信令消息进行中转。

    2.接下来A如果想要发起呼叫,首先它要创建一个PeerConnect,对端的连接对象,创建一个这样的实例,之后通过getUserMedia拿到本地的音视频流,将这个流添加到连接里去,这是第一步。

    在进行媒体协商之前,我们需要先将流(本地采集的数据)添加到peerConnection连接中去。这样在媒体协商之前,我们才知道有哪些媒体数据。
    如果先做媒体协商的话,知道这是连接中没有数据媒体流,就不会设置相关底层的接收器、发送器,即使后面设置了媒体流,传递给了peerConnection,他也不会进行媒体传输,所以我们要先添加流

    3.接下来第二步它就可以调用PeerConnect的CreateOffer的方法去创建一个Offer的SDP,创建好SDP之后再调用setLocalDescription,把它设置到LocalDescription这个槽里去,那调用完这个方法之后实际在底层会发送一个bind请求给stun和turn服务,那这个时候它就开始收集所有与对方连接的候选者了。(还没收集完成,因为stun和turn服务还没有进行响应)

    4.那与此同时调用完setLocalDescription之后,那之前CreateOffer方法拿到这个SDP那也要发送给信令服务器,那通过信令服务器的中转,最终转给B,这个时候B就拿到了offer,也就是说A这端的媒体相关的描述信息。

    5.我们再来看B端 ,B端收到这个SDP之后呢,首先要创建一个PeerConnetion,创建一个连接对象,创建好这个对象之后它会调用setRemoteDescription将这个收到的SDP设置进去,那设置完成之后它要给一个应答,它要调用Create Answer,这时候它就产生了本机相关的媒体的信息也就是Answer SDP,创建好之后它也要调用setLocalDescription,讲这个本地的Answer SDP设置进去,这样对B来说它的协商就OK了。也就是说它有远端的SDP同时它自己这端的SDP也获取到了,这时候在底层就会进行协商。

    6.对于B端,在setLocalDescription的时候它也要向stun和turn服务发送一个bind请求,也就是收集它能够与A进行通信的所有的候选者,在调用完setLocalDescription之后,它将它这个Answer SDP发送给信令服务器 ,通过信令服务器又转给了A,那A这个时候就拿到了B 这一端的媒体描述信息,然后它再设置setRemoteDescription,那这个时候A也可以进行媒体协商了,这个时候A和B进行媒体协商的动作就算完成了。这是媒体协商这一部分。

    7.那接下来stun和turn服务将这个信息回给A,这个时候就会触发A端的这个onIceCandidate事件,因为我们上面是有一个请求(3中出现的),所有这个时候我们就能收到很多不同的onIceCandidate,A收到这个候选者之后它将这个候选者发送给这个信令服务器,通过信令服务器转给对端,也就是让对端知道我都有哪些通路(让B端知道本机A有哪些通路),那对端B收到这个Candidate之后要调用AddIceCandidate这个方法将它添加到对端的这个连接通路的候选者列表中去。

    8.那同样的道理,当B收到这个Candidate之后,它也发给信令,通过信令转发给A,那这个时候A也拿到B的所有的候选者,并将它添加到这个候选者列表中去,也就是AddIceCandidate,那这个时候双方就拿到了所有的对方的可以互通的候选者,这个时候它底层就会做连接检测,看看哪些,首先他会做一个个的Candidate pair也就是候选者对,然后进行排序,排序完了之后进行连接检测等等等一系列的连接检测。

    9.在我们之前都做过这个方面的介绍,当它找到一个最优的线路之后呢,A与B就进行通讯了,那首先是A将数据流发送给B,那B在收到这个数据流之后,因为它们前面已经做了绑定了,就知道是谁来的数据给我了,给我之后就与它的这个Connection进行对连,收到这个数据之后它是不能显示的,B虽然收到数据但是还是显示不出来,那它要将这个数据进行onAddStream,要添加进来,添加进行之后才能把这个视频数据和音频数据向上抛,那才能走到上一层进行视频的渲染和音频的渲染。

    那以上就是一个基本的端对端连接的基本流程。那经过这样一个分析大家就会清楚,整个A要与B进行通讯,要走几步:

    第一大块就是整个媒体的协商,看A端有什么媒体能力看B端有什么媒体能力,他们直接所有的媒体取一个交集,取大家都能够识别的支持的能力,包括音频编解码视频编解码,这个采样率是多少,帧率是多少,以及网络的一些信息;
    第二大部分就是通过ICE对整个可连通的链路进行这个链路地址的收集,它收集完了之后进行排序和连接检测,找出双方可以连接的最优的这条线路;
    那么最后拿到线路之后就可以进行媒体数据的传输了,当从一端传输到另一端之后呢,另一端会收到一个事件,就是onAddStream,当收到这个事件之后就可以将这个媒体流添加到自己的video标签和audio标签中进行音频的播放和视频的渲染,这个就是整个端对端连接的基本流程。

    也就是分成这个三大块,熟悉了这个基本的流程之后,我们再编写这个程序的时候,就非常的简单了。

    我们只要按照这个步骤一步步的 操作那么就能实现两个端之间的通讯。

    三:实战音视频通信

    为了尽量的简化,并没有使用真实的跨网络(从一台电脑的网络到另一台电脑的网络进行真实的音视频的传输),而是在我们一个页面里面其中一个video取展示我们本地采集的音频和视频,

    那之后创建两个PeerConnection,然后将这个媒体流加入到其中一个PeerConnection之后让他们进行连接,连接之后进行本机底层的网络传输,传到另一端的PeerConnection,

    当另一端PeerConnection收到这个音视频数据之后取回调这个事件,也就是onAddStream,当另一端收到这个onAddStream之后,将这个收到的数据转给视频标签,视频就被渲染出来了,

    虽然没有经过真实的网络,但是他们整体的流程和真实网络的流程是一摸一样的,虽然不用信令传输了,但是我们还是要走这样一个逻辑。

    那么在后面我们就会将真实的网络加入进去,那么大家就会看到实际整个流程并没有变,只是把这个信令通过信令服务器进行中转,网络连接也不是在自己本地中转 ,而是通过真实的网络进行传输。

    (一)代码实现

    'use strict'
    
    var http = require("http");
    var https = require("https");
    var fs = require("fs");
    
    var express = require("express");
    var serveIndex = require("serve-index");
    
    var socketIo = require("socket.io");    //引入socket.io
    
    var log4js = require('log4js');            //开启日志
    var logger = log4js.getLogger();
    logger.level = 'info';
    
    var app = express();                    //实例化express
    app.use(serveIndex("./"));        //设置首路径,url会直接去访问该目录下的文件
    app.use(express.static("./"));    //可以访问目录下的所有文件
    
    //https server
    var options = {
        key : fs.readFileSync("./ca/learn.webrtc.com-key.pem"),            //同步读取文件key
        cert: fs.readFileSync("./ca/learn.webrtc.com.pem"),                //同步读取文件证书
    };
    
    var https_server = https.createServer(options,app);
    //绑定socket.io与https服务端
    var io = socketIo.listen(https_server);    //io是一个节点(站点),内部有多个房间
    https_server.listen(443,"0.0.0.0");
    //---------实现了两个服务,socket.io与https server;都是绑定在443,复用端口
    
    //-----处理事件
    io.sockets.on("connection",(socket)=>{    //处理客户端到达的socket
        //监听客户端加入、离开房间消息
        socket.on("join",(room)=>{
            socket.join(room);                //客户端加入房间
            //io.sockets指io下面的所有客户端
            //如果是第一个客户端加入房间(原本房间不存在),则会创建一个新的房间
            var myRoom = io.sockets.adapter.rooms[room];    //从socket.io中获取房间
            var users = Object.keys(myRoom.sockets).length;    //获取所有用户数量
    
            logger.info("the number of user in room is:"+users);
    
            //开始回复消息,包含两个数据房间和socket.id信息
            //socket.emit("joined",room,socket.id);    //给本人
            //socket.to(room).emit("joined",room,socket.id);    //给房间内其他所有人发消息
            //io.in(room).emit("joined",room,socket.id);        //给房间中所有人(包括自己)发送消息
            socket.broadcast.emit("joined",room,socket.id);    //给节点内其他所有人发消息
        });
        
        socket.on("leave",(room)=>{
            var myRoom = io.sockets.adapter.rooms[room];    //从socket.io中获取房间
            var users = Object.keys(myRoom.sockets).length;    //获取所有用户数量
    
            logger.info("the number of user in room is:"+(users-1));
    
            socket.leave(room);    //离开房间
            //开始回复消息,包含两个数据房间和socket.id信息
            socket.broadcast.emit("leaved",room,socket.id);    //给节点内其他所有人发消息
        });
    
        socket.on("message",(room,msg)=>{
            var myRoom = io.sockets.adapter.rooms[room];    //从socket.io中获取房间
            logger.info("send data is:"+msg);
    
            socket.broadcast.emit("message",room,socket.id,msg);    //给节点内其他所有人发消息
        });
    });
    服务器实现
    <html>
        <head>
            <title>    WebRTC PeerConnection </title>
        </head>
        <body>
            <h1>Index.html</h1>
            <div>
                <video autoplay playsinline id="localvideo"></video>
                <video autoplay playsinline id="remotevideo"></video>
            </div>
    
            <div>
                <button id="start">Start</button> <!--采集音视频数据-->
                <button id="call">Call</button> <!--创建双方的peerconnection,开始通信-->
                <button id="hangup">HangUp</button> <!--挂断-->
            </div>
        </body>
        <script type="text/javascript" src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
        <script type="text/javascript" src="./js/main.js"></script>
        
    </html>
    index.html

    main.js主要javascript文件:

    'use strict'
    
    var localVideo = document.querySelector("video#localvideo");
    var remoteVideo = document.querySelector("video#remotevideo");
    
    var btnStart = document.querySelector("button#start");
    var btnCall = document.querySelector("button#call");
    var btnHangup = document.querySelector("button#hangup");
    
    var localStream;                    //设置全局流,用来addStream发送给对端时使用
    var pc1;                            //处理pc1与pc2时候,只需要站在一个角度就可以了,因为对端也是一样的
    var pc2;
    
    function handleError(err){
        console.err(err.name+":"+err.message);
    }
    
    function getMediaStream(stream){
        localVideo.srcObject = stream;    //显示到网页视频控件
        localStream = stream;            //保存到全局流中
    }
    
    //采集本机音视频数据
    function start(){
        if(!navigator.mediaDevices ||
            !navigator.mediaDevices.getUserMedia){
            console.error("the getUserMedia is not support!");
            return;
        }else{
            var constraints = {
                audio:false,
                video:true
            };
            navigator.mediaDevices.getUserMedia(constraints)
                                    .then(getMediaStream)    //获取数据流
                                    .catch(handleError);
        }
    }
    
    function getRemoteStream(e){            //会有多个流
        remoteVideo.srcObject = e.streams[0];        //只取其中一个就可以了,就将远端的音视频流传给了remoteVideo
    }
    
    function getAnswer(desc){
        pc2.setLocalDescription(desc);    //7.远端设置本地描述信息
        //发送描述信息SDP到signal信令服务端,与pc1进行交换
        //8.pc1设置远端描述信息
        pc1.setRemoteDescription(desc);    //-----这里开始获取了所有对端的SDP信息,双端信息协商完成!!!!----
    
    }
    
    function getOffer(desc){            //获取了描述信息,开始设置到peerConnection中去
        //4.设置本地的描述信息,添加到peerconnection
        pc1.setLocalDescription(desc);
    
        //发送描述信息SDP到signal信令服务端,与pc2进行交换
        //5.对端接收设置SDP信息
        pc2.setRemoteDescription(desc);
    
        //6.创建Answer信息
        pc2.createAnswer()
                .then(getAnswer)        //7.远端设置本地描述信息
                .catch(handleError);
    }
    
    function call(){
        //1.创建peerConnect,pc1与pc2同时连接到signal服务器(这里是一起到本机)
        /*
        在这个Connection里面实际上是有一个可选参数的,
        这个可选参数就涉及到网络传输的一些配置 
        我们整个ICE的一个配置,但是由于是我们在本机内进行传输,所以在这里我们就不设置参数了,因为它也是可选的 
        所以它这里就会使用本机host类型的candidate 
        */
        pc1 = new RTCPeerConnection();            //调用方
        pc2 = new RTCPeerConnection();            //被调用方
    
        //当收到candidate后,会触发事件,获取候选者列表,之后调用send candidate发送给signal服务器,从而发送给对端。双方获取之后进行连通性检测
        pc1.onicecandidate = (e) => {    
            pc2.addIceCandidate(e.candidate);    //开始添加给对端
        };
        pc2.onicecandidate = (e) => {    
            pc1.addIceCandidate(e.candidate);    //开始添加给对端
        };
    
        //pc2是相对特殊的,因为是被调用者,用于接受数据
        pc2.ontrack = getRemoteStream;            //被调用方,接收数据,有数据经过的时候调用ontrack事件
    
        //下面要先添加媒体流,然后才进行媒体协商
        //2.添加媒体流
        localStream.getTracks().forEach((track)=>{    //获取所有的轨
            pc1.addTrack(track,localStream);        //将本地产生的音视频流添加到pc1的peerConnection
        });
    
        //3.创建offer
        var offerOptions = {
            offerToRecieveAudio:0,    //不处理音频
            offerToRecieveVideo:1
        };
    
        pc1.createOffer(offerOptions)
                .then(getOffer)        //4.设置本地的描述信息,添加到peerconnection
                .catch(handleError);
    }
    
    function hangup(){
        pc1.close();
        pc2.close();
        pc1 = null;
        pc2 = null;
    }
    
    btnStart.onclick = start;
    btnCall.onclick = call;
    btnHangup.onclick = hangup;

    (二)测试结果

  • 相关阅读:
    不能选中EXCEL单元格直接复制内容到数据库
    trim c# .net
    Postion and AlignmentPoint
    format详解
    range()函数详解
    蓝桥杯,查找整数,python
    蓝桥杯,杨辉三角形,Python
    蓝桥杯,回文数,Python
    微信小程序页面间的数据传递和数据共享
    蓝桥杯,特殊回文数,Python
  • 原文地址:https://www.cnblogs.com/ssyfj/p/14805040.html
Copyright © 2011-2022 走看看