运用 WebRTC 构建简朴的前端视频通信

迎接关注我的知乎专栏:https://zhuanlan.zhihu.com/starkwang

在传统的 Web 运用中,浏览器与浏览器之间是没法直接互相通讯的,必需借助服务器的辅佐,然则跟着 WebRTC 在各大浏览器中的提高,这一近况得到了转变。

WebRTC(Web Real-Time Communication,Web及时通讯),是一个支撑网页浏览器之间举行及时数据传输(包含音频、视频、数据流)的手艺,谷歌于2011年5月开放了工程的源代码,如今在各大浏览器的最新版本中都得到了差别水平的支撑。

这篇文章里我们采纳 WebRTC 来构建一个简朴的视频传输运用。

一、关于 WebRTC 的一些基本概念

传统的视频推流的手艺完成平常是如许的:客户端收集视频数据,推流到服务器上,服务器再根据具体状况将视频数据推送到其他客户端上。

然则 WebRTC 却判然差别,它能够在客户端之间直接搭建基于 UDP 的数据通道,经由简朴的握手流程以后,能够在差别装备的两个浏览器内直接传输恣意数据。

这个中的流程包含:

  1. 收集视频流数据,竖立一个 RTCPeerConnection

  2. 竖立一个 SDP offer 和响应的回应

  3. 为两边找到 ICE 候选途径

  4. 胜利竖立一个 WebRTC 衔接

下面我们引见这个中涉及到的一些关键词:

1、RTCPeerConnection 对象

RTCPeerConnection 对象是 WebRTC API 的进口,它担任竖立、保护一个 WebRTC 衔接,以及在这个衔接中的数据传输。如今新版本的浏览器多数支撑了这一对象,然则因为如今 API 还不稳固,所以须要到场各个浏览器内核的前缀,比方 Chrome 中我们运用 webkitRTCPeerConnection 来接见它。

2、会话形貌协定(SDP)

为了衔接到其他用户,我们必须要对其他用户的装备状况有所相识,比方音频视频的编码解码器、运用何种编码花样、运用何种收集、装备的数据处理才能,所以我们须要一张“手刺”来取得用户的一切信息,而 SDP 为我们供应了这些功用。

一个 SDP 的握手由一个 offer 和一个 answer 构成。

3、收集通讯引擎(ICE)

通讯的两侧能够会处于差别的收集环境中,有时会存在好几层的接见掌握、防火墙、路由跳转,所以我们须要一种要领在庞杂的收集环境中找到对方,而且衔接到响应的目的。WebRTC 运用了集成了 STUN、TURN 的 ICE 来举行两边的数据通讯。

二、竖立一个 RTCPeerConnection

起首我们的目的是在统一个页面中竖立两个及时视频,一个的数据直接来自你的摄像头,另一个的数据来自当地竖立的 WebRTC 衔接。看起来是如许的:

图图图。。。。。。。

起首我们竖立一个简朴的 HTML 页面,含有两个 video 标签:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        #theirs{
            position: absolute;
            top: 100px;
            left: 100px;
            width: 500px;
        }
        #yours{
            position: absolute;
            top: 120px;
            left: 480px;
            width: 100px;
            z-index: 9999;
            border:1px solid #ddd;
        }
    </style>
</head>
<body>
<video id="yours" autoplay></video>
<video id="theirs" autoplay></video>
</body>
<script type="text/javascript" src="./main.js"></script>
</html>

下面我们竖立一个 main.js 文件,先封装一下各浏览器的 userMediaRTCPeerConnection 对象:

function hasUserMedia() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.msGetUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    return !!navigator.getUserMedia;
}

function hasRTCPeerConnection() {
    window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;
    return !!window.RTCPeerConnection;
}

然后我们须要浏览器挪用体系的摄像头 API getUserMedia 取得媒体流,注重要翻开浏览器的摄像头限定。Chrome因为平安的题目,只能在 https 下或许 localhost 下翻开摄像头。

var yourVideo = document.getElementById("yours");
var theirVideo = document.getElementById("theirs");
var yourConnection, theirConnection;

if (hasUserMedia()) {
    navigator.getUserMedia({ video: true, audio: false },
        stream => {
            yourVideo.src = window.URL.createObjectURL(stream);
            if (hasRTCPeerConnection()) {
                // 稍后我们完成 startPeerConnection
                startPeerConnection(stream);
            } else {
                alert("没有RTCPeerConnection API");
            }
        },
        err => {
            console.log(err);
        }
    )
}else{
    alert("没有userMedia API")
}

没有不测的话,如今应当能在页面中看到一个视频了。

下一步是完成 startPeerConnection 要领,竖立传输视频数据所须要的 ICE 通讯途径,这里我们以 Chrome 为例:

function startPeerConnection(stream) {
    //这里运用了几个大众的stun协定服务器
    var config = {
        'iceServers': [{ 'url': 'stun:stun.services.mozilla.com' }, { 'url': 'stun:stunserver.org' }, { 'url': 'stun:stun.l.google.com:19302' }]
    };
    yourConnection = new RTCPeerConnection(config);
    theirConnection = new RTCPeerConnection(config);
    yourConnection.onicecandidate = function(e) {
        if (e.candidate) {
            theirConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }
    theirConnection.onicecandidate = function(e) {
        if (e.candidate) {
            yourConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }
}

我们运用这个函数竖立了两个衔接对象,在 config 里,你能够恣意指定 ICE 服务器,虽然有些浏览器内置了默许的 ICE 服务器,能够不必设置,但照样发起加上这些设置。下面,我们举行 SDP 的握手。

因为是在统一页面中举行的通讯,所以我们能够直接交流两边的 candidate 对象,但在差别页面中,能够须要一个分外的服务器辅佐这个交流流程。

三、竖立 SDP Offer 和 SDP Answer

浏览器为我们封装好了响应的 Offer 和 Answer 要领,我们能够直接运用。

function startPeerConnection(stream) {
    var config = {
        'iceServers': [{ 'url': 'stun:stun.services.mozilla.com' }, { 'url': 'stun:stunserver.org' }, { 'url': 'stun:stun.l.google.com:19302' }]
    };
    yourConnection = new RTCPeerConnection(config);
    theirConnection = new RTCPeerConnection(config);
    yourConnection.onicecandidate = function(e) {
        if (e.candidate) {
            theirConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }
    theirConnection.onicecandidate = function(e) {
        if (e.candidate) {
            yourConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }

    //本方发生了一个offer
    yourConnection.createOffer().then(offer => {
        yourConnection.setLocalDescription(offer);
        //对方接收到这个offer
        theirConnection.setRemoteDescription(offer);
        //对方发生一个answer
        theirConnection.createAnswer().then(answer => {
            theirConnection.setLocalDescription(answer);
            //本方接收到一个answer
            yourConnection.setRemoteDescription(answer);
        })
    });

}

和 ICE 的衔接一样,因为我们是在统一个页面中举行 SDP 的握手,所以不须要借助任何其他的通讯手腕来交流 offer 和 answer,直接赋值即可。假如须要在两个差别的页面中举行交流,则须要借助一个分外的服务器来辅佐,能够采纳 websocket 或许别的手腕举行这个交流历程。

四、到场视频流

如今我们已经有了一个牢靠的视频数据传输通道了,下一步只须要向这个通道到场数据流即可。WebRTC 直接为我们封装好了到场视频流的接口,当视频流增加时,另一方的浏览器会经由过程 onaddstream 来示知用户,通道中有视频流到场。

yourConnection.addStream(stream);
theirConnection.onaddstream = function(e) {
    theirVideo.src = window.URL.createObjectURL(e.stream);
}

以下是完全的 main.js 代码:

function hasUserMedia() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.msGetUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
    window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;
    return !!window.RTCPeerConnection;
}

var yourVideo = document.getElementById("yours");
var theirVideo = document.getElementById("theirs");
var yourConnection, theirConnection;

if (hasUserMedia()) {
    navigator.getUserMedia({ video: true, audio: false },
        stream => {
            yourVideo.src = window.URL.createObjectURL(stream);
            if (hasRTCPeerConnection()) {
                startPeerConnection(stream);
            } else {
                alert("没有RTCPeerConnection API");
            }
        },
        err => {
            console.log(err);
        })
} else {
    alert("没有userMedia API")
}

function startPeerConnection(stream) {
    var config = {
        'iceServers': [{ 'url': 'stun:stun.services.mozilla.com' }, { 'url': 'stun:stunserver.org' }, { 'url': 'stun:stun.l.google.com:19302' }]
    };
    yourConnection = new RTCPeerConnection(config);
    theirConnection = new RTCPeerConnection(config);

    yourConnection.onicecandidate = function(e) {
        if (e.candidate) {
            theirConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }
    theirConnection.onicecandidate = function(e) {
        if (e.candidate) {
            yourConnection.addIceCandidate(new RTCIceCandidate(e.candidate));
        }
    }
    
    theirConnection.onaddstream = function(e) {
        theirVideo.src = window.URL.createObjectURL(e.stream);
    }
    yourConnection.addStream(stream);
    
    yourConnection.createOffer().then(offer => {
        yourConnection.setLocalDescription(offer);
        theirConnection.setRemoteDescription(offer);
        theirConnection.createAnswer().then(answer => {
            theirConnection.setLocalDescription(answer);
            yourConnection.setRemoteDescription(answer);
        })
    });
}
    原文作者:王伟嘉
    原文地址: https://segmentfault.com/a/1190000005864228
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞