zoukankan      html  css  js  c++  java
  • 非真实网络的视频传输实战(一)

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

    '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哦。敬请期待。

  • 相关阅读:
    springboot 之JPA
    Oracle数据库安装
    Pytho之Django
    springboot之docker化
    opencv之dlib库人脸识别
    opencv之调用摄像头
    springboot之多模块化项目打包
    python学习笔记2--list
    ETL测试
    Mockserver -MOCO的使用
  • 原文地址:https://www.cnblogs.com/eflypro/p/14848732.html
Copyright © 2011-2022 走看看