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
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基础知识
1. 变量与类型
2. 基本运算
3. if else
4. 循环
5. 函数
三、 webRTC设备管理
1. enumerateDevices
通过这个api 能获取到电脑的音频/视频设备
Promise
是js中特有的对象
Promise中有一个重要的结构体MediaDevicesInfo
背景:
JavaScript他是用单线程去处理整个逻辑,所以防止它被阻塞,大量使用了异步调用,Promise就是异步调用其中的一种方式
它的基本思想就是: 首先你在创建Promise的时候,要传给它一个handle函数,这个handle处理你的主要逻辑,那么处理完成之后,如果成功了,它就会调resolve这个函数,如果失败了它就调reject这个函数,这样就创建好了一个Promise;
Promise可以注册两个方法,一个是通过then,一个是通过catch;then就是当整个逻辑处理成功之后就会收到on_reolve事件,当收到事件的时候可以处理一些逻辑;catch就是当失败的时候收到on_reject处理一些失败的逻辑;
then成功的时候还可以返回一个Promise,你可以继续then。。。【链式】
回到
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
WebRTC get autio and video device
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
WebRTC get autio and video device
audio input device
audio output device
video input device
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. 不同浏览器运行之间的差别
四、 音视频采集API
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
WebRTC capture video and audio
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起的名字是不一样的
cd /opt/aladdin/nodejs/webserver/public/mediastream
vim index.html
WebRTC capture video and audio
2. 获取访问音频/适配设备的权限
为解决之前获取设备不同浏览器之间有差异问题:不同浏览器对权限获取的实现不同,导致有的能获取到设备,有的不能;
cd /opt/aladdin/nodejs/webserver/public/mediastream
vim index.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
}
#因为下面 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. 音频/视频采集约束
-
视频
-
音频
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. 浏览器视频特效
6. 从视频中获取图片
7. 只采集音频
五、 MediaStream
六、 WebRTC录制媒体流
就是录制上面navigator.mediaDevices.getUserMedia()采集的数据
1. WebRTC捕获桌面
vim ./js/client.js
将所有getUserMedia换为getDisplayMedia就ok 了
七、socket.io
1. 使用socket.io发送消息
2. WebRTC信令服务器
3. 通过socket.io实现信令服务器
vim server.js
日志