webRTC

webRTC的愿景是希望通过开发浏览器的程序,来实现音视频应用

https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API
谷歌webRTC demo:https://appr.tc/

一、安装web服务器

1. 安装nodejs

提供web服务

yum install nodejs -y
node -v

2. 安装npm

包管理器

yum install npm -y
npm -v

3. nodejs 实现最简单的http服务

  • require引入http模块;类似于java里的import
  • 创建http服务
  • 监听端口
mkdir -p /opt/aladdin/nodejs && cd  mkdir -p /opt/aladdin/nodejs
vim server.js
'use strict' #使用js最严格的语法,防止js的语法漏洞
var http = require('http'); //引入http模块
var app = http.createServer(function( req , res ){
  res.writeHead(200,{'Content-Type':'text/plain'});  //设置返回的请求头
  res.end('hello world\n'); //body
}).listen(8080,'0.0.0.0');  //用http模块创建一个http服务,并打开监听(0.0.0.0表示所有网卡都绑定8080端口并监听)

最后启动服务

#前台运行
node server.js
#后台运行一
nohub node app.js &
#后台运行二
npm install forever -g #-g forever 这个命令在整个环境中都生效;否则只是当前目录下生效
forever start server.js #forever stop server.js

浏览器访问

http://服务器ip:8080

4. https

chrome对个人隐私要求严格,不配置https不准使用麦克风/摄像头
http协议本身的内容是明文的,经过TLS/SSL加密,并经过第三方认证,最后 传输
HTTPS=HTTP+TLS/SSL

image.png

5. nodejs搭建https服务

生成证书

  • 私有证书: 我们自己生成的,浏览器不认可
  • 认证证书: 第三方机构认证颁发
mkdir -p /opt/aladdin/nodejs/cert && cd  mkdir -p /opt/aladdin/nodejs/cert

然后将证书拷贝到此目录下
*.key 是证书的key;
*.pem 是证书 ;

vim ../https_server.js
'use strict' #使用js最严格的语法,防止js的语法漏洞
var https = require('https'); //引入https模块
var filesystem= require('fs'); //引入文件模块,用于读取证书 
var options= { //类似于json格式
  key : fs.readFileSync('./cert/155467_www.xxx.com.key'),
  cert : fs.readFileSync('./cert/155467_www.xxx.com.pem')
}
var app = https.createServer(options,function( req , res )){ //options参数是证书
  res.writeHead(200,{'Content-Type':'text/plain'});  //设置返回的请求头
  res.end('hello world\n'); //body
}).listen(443,'0.0.0.0');  //用http模块创建一个http服务,并打开监听(0.0.0.0表示所有网卡都绑定443端口并监听)

域名访问

https://xxxx.com

6. 真正的web服务

  • 引入express模块【nodejs里专门处理web服务的,里面有很多功能】
  • serve-index模块【将整个目录里发布出来,这个目录里的文件都共享出来,可以直接通过浏览器浏览】
  • 指定发布目录
npm install express
npm install serve-index
mkdir -p /opt/aladdin/nodejs/webserver && cd  mkdir -p /opt/aladdin/nodejs/webserver
vim webserver.js
'use strict'
var http = require('http'); //既支持http

var https = require('https'); //也支持https

var filesystem= require('fs'); 

var express = require('express');

var serverIndex= require('serve-index');

var app= express(); #创建一个对象,实例化express模块
app.use(express.static('./public')); #发布静态资源的路径
app.usr(serverIndex('./public')); #浏览发布路径的文件

//http
var http_server = https.createServer(app);
http_server.listen(80, '0.0.0.0')

var options= { 
  key : fs.readFileSync('../cert/155467_www.xxx.com.key'),
  cert : fs.readFileSync('../cert/155467_www.xxx.com.pem')
}

//https
var https_server = https.createServer(options,app);
https_server.listen(443 , '0.0.0.0')

二、回顾JavaScript基础知识

image.png

1. 变量与类型

image.png

image.png

2. 基本运算

image.png

image.png

3. if else

image.png

image.png

image.png

4. 循环

image.png

image.png

5. 函数

image.png

image.png

三、 webRTC设备管理

1. enumerateDevices

通过这个api 能获取到电脑的音频/视频设备

Promise是js中特有的对象
Promise中有一个重要的结构体MediaDevicesInfo

image.png

背景:
JavaScript他是用单线程去处理整个逻辑,所以防止它被阻塞,大量使用了异步调用,Promise就是异步调用其中的一种方式
它的基本思想就是: 首先你在创建Promise的时候,要传给它一个handle函数,这个handle处理你的主要逻辑,那么处理完成之后,如果成功了,它就会调resolve这个函数,如果失败了它就调reject这个函数,这样就创建好了一个Promise;
Promise可以注册两个方法,一个是通过then,一个是通过catch;then就是当整个逻辑处理成功之后就会收到on_reolve事件,当收到事件的时候可以处理一些逻辑;catch就是当失败的时候收到on_reject处理一些失败的逻辑;
then成功的时候还可以返回一个Promise,你可以继续then。。。【链式】


image.png

回到

var ePromise= navigator.mediaDevices.enumerateDevices();

在enumerateDevices()这个函数里它就new了一个Promise,中间给它注册了一个handle, 所以当这个函数执行的时候,它就返回一个Promise,在我们用的时候,拿到这个Promise,我们就给它注册两个函数,一个是then的方法,一个是catch的方法,如果成功调用then的方法做成功的逻辑,如果失败了调catch

2. 获取用户音频/视频设备实战

#首先进入上面发布的目录,在目录下写我们的程序,这样当我们写完它就发布出来了,我们通过浏览器就能看到相应的结果
cd  /opt/aladdin/nodejs/webserver/public 
#建一个子目录
mkdir device && cd device
vim index.html
<html>
  <head>
    <title>WebRTC get autio and video device</title>
  </head>

  <body>
    <script src="./js/client.js"></script> <!--引入我们js代码,这样当我们打开页面的时候js代码就会执行,chrome浏览器会把它交到底层的v8引擎解析渲染-->
  </body>
</html>
mkdir js && cd js
vim client.js
'use strict'

#首先看浏览器是否支持我们的方法(方法是否存在),支持则调用,不支持报错 
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices()) {
  console.log('enumerateDevices is not supported!');
}else{
  navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
}

function gotDevices(devicesInfos){
  devicesInfos.forEach(function(devicesInfo){
    console.log(devicesInfo.kind+":"+devicesInfo.label+":"+ devicesInfo.deviceId+":"+devicesInfo.groupId);
  });
}

function handleError(err){
  console.log(err.name+":"+err.message);
}

如果结果lable为空,则浏览器应该用https打开,http是不显示设备名称的

3. 在页面上显示设备

vim index.html
<html>
  <head>
    <title>WebRTC get autio and video device</title>
  </head>

  <body>
    <div>
      <div>
        <lable>audio input device</lable>
        <select id="audioSource"></select>
      </div>  
      <div>
        <lable>audio output device</lable>
        <select id="audioOutput"></select>
      </div>  
      <div>
        <lable>video input device</lable>
        <select id="videoSource"></select>
      </div>  
    </div>  

    <script src="./js/client.js"></script> 
  </body>
</html>
vim ./js/client.js
'use strict'

#首先获取到音频输入设备(select中的id为audioSource)
var audioSource= document.querySelect("select#audioSource");
var audioOutput= document.querySelect("select#audioOutput");
var videoSource= document.querySelect("select#videoSource");

if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices()) {
  console.log('enumerateDevices is not supported!');
}else{
  navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);
}

function gotDevices(devicesInfos){
  devicesInfos.forEach(function(devicesInfo){
    console.log(devicesInfo.kind+":"+devicesInfo.label+":"+ devicesInfo.deviceId+":"+devicesInfo.groupId);
    
    var option = document.createElement('option');
    option.text= devicesInfo.label;
    option.value= devicesInfo.deviceId;

    if(devicesInfo.kind === 'audioinput'){
      audioSource.appendChild(option);
    }else if(devicesInfo.kind === 'audiooutput'){
      audioOutput.appendChild(option);
    }else if(devicesInfo.kind === 'videoinput'){
      videoSource.appendChild(option);    
    }
  });
}

function handleError(err){
  console.log(err.name+":"+err.message);
}

4. 不同浏览器运行之间的差别

image.png

四、 音视频采集API

image.png
mkdir -p /opt/aladdin/nodejs/webserver/public && cd  mkdir -p /opt/aladdin/nodejs/webserver/public 
mkdir mediastream && cd mediastream

一个流(stream)可以包括多个轨(track)
每一条媒体轨就是一种媒体数据(音频/视频),可以有多个音频/视频组成一个stream

vim index.html
<html>
  <head>
    <title>WebRTC capture video and audio</title>
  </head>

  <body>
    <video autoplay playsinline id="player"></video> <!--html5的标签,可以显示我们捕获的音频数据 ;autoplay属性为playsinline 表示在页面中播放-->
    <script src="./js/client.js"></script> <!-- js代码用于捕获音频数据-->
  </body>
</html>
vim ./js/client.js
'use strict'

if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia()) {
  console.log('getUserMedia is not supported!');
}else{
  var constrants:{
    #同时采集音频和视频数据
    video =true,
    audio=true
  }
  navigator.mediaDevices.getUserMedia(constrants).then(gotMediaStream).catch(handleError);
}

var videoplay=document.querySelector('video#player')

function gotMediaStream(stream){
   #指定数据源
  videoplay.srcObject=stream;
}

function handleError(err){
  console.log(err.name+":"+err.message);
}

1. getUserMedia适配

各个浏览器厂商对getUserMedia起的名字是不一样的


image.png

image.png
cd /opt/aladdin/nodejs/webserver/public/mediastream
vim index.html
<html>
  <head>
    <title>WebRTC capture video and audio</title>
  </head>

  <body>
    <video autoplay playsinline id="player"></video> 

    <script src="https://webrtc. github.io/adapter/adapter-latest.js"></script> <!--适配 -->
    <script src="./js/client.js"></script> 
  </body>
</html>

2. 获取访问音频/适配设备的权限

为解决之前获取设备不同浏览器之间有差异问题:不同浏览器对权限获取的实现不同,导致有的能获取到设备,有的不能;

cd /opt/aladdin/nodejs/webserver/public/mediastream
vim index.html
image.png
vim ./js/client.js
'use strict'

if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia()) {
  console.log('getUserMedia is not supported!');
}else{
  var constrants:{
    video =true,
    audio=true
  }

#因为下面  gotMediaStream()方法返回了一个Promise,所以我们还可以继续进行then操作,去获取设备
navigator.mediaDevices.getUserMedia(constrants).then(gotDevices).then(gotMediaStream).catch(handleError);
}

#获取标签
var audioSource=document.querySelector('select#audioSource');
var audioOutput=document.querySelector('select#audioOutput');
var videSource=document.querySelector('select#videoSource');

var videoplay=document.querySelector('video#player');

#当我拿到这个流,说明用户已经同意拿到音频/视频设备了
function gotMediaStream(stream){
  videoplay.srcObject=stream;
  #相当于将Promise返回回去。用于继续使用Promise的链式调用,继续then()
  return avigator.mediaDevices.enumerateDevices();
}

function handleError(err){
  console.log(err.name+":"+err.message);
}

#实现一下获取设备后的操作
function gotDevices(devicesInfos){
  devicesInfos.forEach(function(devicesInfo){
    console.log(devicesInfo.kind+":"+devicesInfo.label+":"+ devicesInfo.deviceId+":"+devicesInfo.groupId);
    
    var option = document.createElement('option');
    option.text= devicesInfo.label;
    option.value= devicesInfo.deviceId;

    if(devicesInfo.kind === 'audioinput'){
      audioSource.appendChild(option);
    }else if(devicesInfo.kind === 'audiooutput'){
      audioOutput.appendChild(option);
    }else if(devicesInfo.kind === 'videoinput'){
      videoSource.appendChild(option);    
    }
  });
}

3. 音频/视频采集约束

  • 视频

    设置具体值

    设置范围

  • 音频

    image.png

4. 切换采集设备

vim ./js/client.js
'use strict'

function(){ #将这一段设为一个函数
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia()) {
  console.log('getUserMedia is not supported!');
  return; #出错直接返回
}else{
  var deviceId=videoSource.value; #拿到设备的id
  var constrants:{
    video =true,
    audio=true
  }
  deviceId: deviceId?deviceId:undefind #deviceId为空,则为undefind;不为空则赋值

navigator.mediaDevices.getUserMedia(constrants).then(gotDevices).then(gotMediaStream).catch(handleError);
}
}

#当页面进来就调用这个函数
start();

#增加一个事件:当我们选择摄像头(标签)的时候,可以触发onchange事件,调用start()函数,重新进行初始化 
videSource.onchange=start();

var audioSource=document.querySelector('select#audioSource');
var audioOutput=document.querySelector('select#audioOutput');
var videSource=document.querySelector('select#videoSource');

var videoplay=document.querySelector('video#player');

function gotMediaStream(stream){
  videoplay.srcObject=stream;
  return avigator.mediaDevices.enumerateDevices();
}

function handleError(err){
  console.log(err.name+":"+err.message);
}

function gotDevices(devicesInfos){
  devicesInfos.forEach(function(devicesInfo){
    console.log(devicesInfo.kind+":"+devicesInfo.label+":"+ devicesInfo.deviceId+":"+devicesInfo.groupId);
    
    var option = document.createElement('option');
    option.text= devicesInfo.label;
    option.value= devicesInfo.deviceId;

    if(devicesInfo.kind === 'audioinput'){
      audioSource.appendChild(option);
    }else if(devicesInfo.kind === 'audiooutput'){
      audioOutput.appendChild(option);
    }else if(devicesInfo.kind === 'videoinput'){
      videoSource.appendChild(option);    
    }
  });
}

5. 浏览器视频特效


image.png
image.png
image.png

image.png

image.png

6. 从视频中获取图片

image.png

image.png

image.png

7. 只采集音频

image.png

image.png

image.png

image.png

五、 MediaStream

image.png

image.png

image.png

image.png

image.png

六、 WebRTC录制媒体流

就是录制上面navigator.mediaDevices.getUserMedia()采集的数据


image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png
image.png

image.png

image.png

image.png

image.png

1. WebRTC捕获桌面

image.png

image.png
vim ./js/client.js

将所有getUserMedia换为getDisplayMedia就ok 了


七、socket.io

1. 使用socket.io发送消息

image.png

image.png

image.png

image.png

2. WebRTC信令服务器

image.png

image.png

image.png

3. 通过socket.io实现信令服务器

image.png
vim server.js
image.png
image.png

image.png

日志


image.png

image.png

image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容