非真实网络的视频传输实战(一)
本光头在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哦。敬请期待。