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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容