云服务器

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

2021-06-04 09:18:39 6

本光头在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>

&lt;head&gt;

    &lt;title&gt;非真实网络应用视频传输&lt;/title&gt;

    &lt;link rel="stylesheet" href="css/main.css"/&gt;

&lt;/head&gt;

&lt;body&gt;

    &lt;div&gt;

        &lt;!-- 收到数据之后要自动播放 --&gt;

        &lt;video id="localVideo" autoplay playsinline&gt;&lt;/video&gt;

        &lt;!-- 展示远端的视频 --&gt;

        &lt;video id="remoteVideo" autoplay playsinline&gt;&lt;/video&gt;

        &lt;div&gt;

            &lt;!-- 开始采集,将数据设置到localVideo --&gt;

            &lt;button id="start"&gt;start&lt;/button&gt;

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

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

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

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

           ideo就能展示出来了--&gt;

            &lt;button id="call"&gt;call&lt;/button&gt;

            &lt;!-- 挂起 --&gt;

            &lt;button id="hangup"&gt;stop&lt;/button&gt;

        &lt;/div&gt;

    &lt;/div&gt;




    &lt;!-- 用于各浏览器间的适配 --&gt;

    &lt;script src="https://webrtc.github.io/adapter/adapter-latest.js"&gt;&lt;/script&gt;

    &lt;!-- 控制部分JS,调用webrtc相关的api --&gt;

    &lt;script src="./js/main.js"&gt;&lt;/script&gt;




&lt;/body&gt;

</html>

 

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

 

 

vim main.js

'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) =&gt; {



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




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

    pc2.addIceCandidate(e.candidate)

        .catch(handleError);

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

}




pc1.iceconnectionstatechange = (e) =&gt; {

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

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

}




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

pc2 = new RTCPeerConnection();




pc2.onicecandidate = (e)=&gt; {



    // send candidate to peer

    // receive candidate from peer




    pc1.addIceCandidate(e.candidate)

        .catch(handleError);

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

}




pc2.iceconnectionstatechange = (e) =&gt; {

    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)=&gt;{

    // 对每条轨道进行循环,每次循环都拿到一个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哦。敬请期待。

上一篇: 无

微信关注

获取更多技术咨询