非真实网络的视频传输实战(一)

04
六月
2021

本光头在N久之前的一门教学中说到,WEBRTC的原理,不知道同学们有没有看过那一篇,如果没有的话那就先去看看那篇课程,或者大家可以搜索一下webrtc的相关通信原理再来看本篇文章。

本篇会介绍端对端连接的基本流程,也就是peer 2 peer,这次为演示方便,就不准备使用真实的服务器进行介绍(毕竟服务器带宽也不便宜呀)。也就是说本篇不涉及到跨网络的应用,而是在同一个页面里面,在其中一个video标签里头展示我们采集到的音频,视频流,之后创建两个peerConnection,然后将这个媒体流数据加入到其中一个的peerConnection里面,然后再让他们连接,连接之后,再通过本机底层的peerConnection连接到另一端的peerConnection,当另一个端的peerConnection收到数据之后他就回调onAddStream事件,那么当另一个端收到onAddStream事件之后,将这个视频流数据转给video标签,那视频就被渲染起来了。

虽然这个流程没有经过实际的跨网络的调用,没有信令服务器,但是其流程与真实的网络流程是一样的。我们先从这一个简单的例子中,了解一下webrtc的基本传输流程,在后续的介绍中,本光头将会把真实的网络加入到代码中,让大家从浅入深,逐步了解webrtc的传输原理以及如何搭建自己的webrtc服务器。

我们的代码分为展示部分与控制部分,展示部分为html,而控制部分则是js调用webrtc的api。

建立一个文件夹

mkdir webrtc

cd webrtc

mkdir js

vim index.html

输入以下内容:

<html>

    <head>

        <title>非真实网络应用视频传输</title>

        <link rel="stylesheet" href="css/main.css"/>

    </head>

    <body>

        <div>

            <!-- 收到数据之后要自动播放 -->

            <video id="localVideo" autoplay playsinline></video>

            <!-- 展示远端的视频 -->

            <video id="remoteVideo" autoplay playsinline></video>

            <div>

                <!-- 开始采集,将数据设置到localVideo -->

                <button id="start">start</button>

                <!-- 当start,采集到数据之后,调用call之后,创建双方的RtcPeerConnection,当两个peerConnection创建之后,他们就要

                协商,协商处理之后就要进行双方的cadidate采集,也就是双方的有效地址采集,采集完之后进行交换 ,然后cadidate pair

                要进行检测,筛选,最终找到最有效的传输链路,之后就再将localVideo的数据,展示到另一端,另一端收到数据之后会触发

                onAddStream事件或onTrack事件,说明我收到数据了,当收到这事件之后,再将它设置到remoteVideo的标签中,这样remoteV

               ideo就能展示出来了-->

                <button id="call">call</button>

                <!-- 挂起 -->

                <button id="hangup">stop</button>

            </div>

        </div>




        <!-- 用于各浏览器间的适配 -->

        <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

        <!-- 控制部分JS,调用webrtc相关的api -->

        <script src="./js/main.js"></script>




    </body>

</html>

这样就写完了html部分的代码,接下来写JS控制部分的代码

vim main.js


```cpp
'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 btnStop= document.querySelector('button#stop');




// 定义全局变量

var localStream;

//  模拟A端PC

var pc1;

//  模拟B端PC

var pc2;




// 将视频流放到localVideo标签中

function gotMediaStream(stream){

    localVideo.srcObject = stream;

    // 将stream存储到全局变量中,方便日后调用

    localStream = stream;

}




// 异常处理

function handleError(err){

    console.log("浏览器不支持getUserMeida", err);

}




// 点击开始按钮 #start,调用webrtc api

function start(){

    // 判断浏览器是否支持该api

    if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia){

        return;

    }else {

        navigator.mediaDevices.getUserMedia({

            video: true,

            audio: false 

        }).then(gotMediaStream)

          .catch(handleError);

    }




}




// 创建应答,B端要设置本地的localDescription,A端要设置远端的RemoteDescription

function gotAnswerDescription(desc){

    pc2.setLocalDescription(desc); // 设置之后他也要收集candidate,发送desc到信令服务器

    pc1.setRemoteDescription(desc);




}




// desc:描述信息

function gotLocalDescription(desc){ 

    // 协商逻辑:对于A来说,拿到desc就要设置setLocalDescription ,会触发底层收集candidate这个动作

    // 正常来说,这步成功了就会发送这个desc到信令服务器,到了信令服务器就会转发到第二个人,第二个

    // 人收到就要接收这个DESC,在这里就是B端的PC2,,B端要设置setLocalDescription

    pc1.setLocalDescription(desc);

    pc2.setRemoteDescription(desc); 




    // B端设置成功这个DESC之后就会创建一个应答 

    pc2.createAnswer().then(gotAnswerDescription)

             .catch(handleError);

}




// 实际上e里面有多个流,取第一个即可

// 做完这一步之后,接下来我们将本地采集的数据添加到A端的peerConnection中去

// 这样我们在做媒体协商的时候,对方才知道我们有哪些媒体数据

// 这里的顺序是这样的:必须先添加媒体数据再做媒体协商

function gotRemoteStream(e){

    // 当发生ontrack事件的时候,就将远端的端传输过来

    if(remoteVideo.srcObject !== e.streams[0]){

        remoteVideo.srcObject = e.streams[0];

    }

}




// 回调,代码顺序不能乱,这是一个执行的过程,整个webrtc api调用的过程

function call(){

    var offerOptions = {

        offerToReceiveAudio: 0,  // 是否接收音频

        offerToReceiveVideo: 1   // 是否接收视频

    }




    // A端PC创建一个RTCPeerConnection链接

    pc1 = new RTCPeerConnection();




    // 监听candiate

    pc1.onicecandidate = (e) => {

    

        // 收到candidate之后,交给信令(但是本次例子没有信令,就直接交给B端)




        // 调用远端电脑,A端将自己的candidate交给B端,反之B端也是如此

        pc2.addIceCandidate(e.candidate)

            .catch(handleError);

        console.log('pc1 ICE candidate:', e.candidate);

    }




    pc1.iceconnectionstatechange = (e) => {

        console.log(`pc1 ICE state: ${pc.iceConnectionState}`);

        console.log('ICE state change event: ', e);

    }




    // B端PC创建一个RTCPeerConnection链接

    pc2 = new RTCPeerConnection();




    pc2.onicecandidate = (e)=> {

    

        // send candidate to peer

        // receive candidate from peer




        pc1.addIceCandidate(e.candidate)

            .catch(handleError);

        console.log('pc2 ICE candidate:', e.candidate);

    }




    pc2.iceconnectionstatechange = (e) => {

        console.log(`pc2 ICE state: ${pc.iceConnectionState}`);

        console.log('ICE state change event: ', e);

    }

       

    // B端属于被调用方,所以有一个ontrack事件

    pc2.ontrack = gotRemoteStream;




    // 先添加媒体流数据再进行媒体协商,因为如果没有媒体流数据,不会

    // 调用底层的api接口,底层认为没有数据的话就不会启用candidate

    // 媒体协商机制,也就是说无法进行通信。

    // 添加流,localStrea,.getTracks() 拿到全部数据流

    localStream.getTracks().forEach((track)=>{

        // 对每条轨道进行循环,每次循环都拿到一个track,直接添加到addTrack中

        pc1.addTrack(track, localStream);  //将本地采集的音视频流添加到pc1那里

    });




    //  媒体协商的第一步就是创建offer,这个就是创建A端电脑的PC1的媒体信息

    //  他也是一个promise的信息

    pc1.createOffer(offerOptions)

        .then(gotLocalDescription)

        .catch(handleError);




}




// 停止

function stop(){

    pc1.close();

    pc2.close();

    pc1 = null;

    pc2 = null;




}




//响应按钮

btnStart.onclick = start;

btnCall.onclick = call;

btnstop.onclick = stop;

至此本篇内容结束,下章本光头将承着本篇的内容,继续介绍offeranswer里面的内容,其中还有sdp哦。敬请期待。

TAG

网友评论

共有访客发表了评论
请登录后再发布评论,和谐社会,请文明发言,谢谢合作! 立即登录 注册会员