webRTC的愿景是希望通過開發(fā)瀏覽器的程序,來實現(xiàn)音視頻應(yīng)用
https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API
谷歌webRTC demo:https://appr.tc/
一柏腻、安裝web服務(wù)器
1. 安裝nodejs
提供web服務(wù)
yum install nodejs -y
node -v
2. 安裝npm
包管理器
yum install npm -y
npm -v
3. nodejs 實現(xiàn)最簡單的http服務(wù)
require
引入http模塊;類似于java里的import
- 創(chuàng)建http服務(wù)
- 監(jiān)聽端口
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'}); //設(shè)置返回的請求頭
res.end('hello world\n'); //body
}).listen(8080,'0.0.0.0'); //用http模塊創(chuàng)建一個http服務(wù)抬吟,并打開監(jiān)聽(0.0.0.0表示所有網(wǎng)卡都綁定8080端口并監(jiān)聽)
最后啟動服務(wù)
#前臺運行
node server.js
#后臺運行一
nohub node app.js &
#后臺運行二
npm install forever -g #-g forever 這個命令在整個環(huán)境中都生效弛针;否則只是當(dāng)前目錄下生效
forever start server.js #forever stop server.js
瀏覽器訪問
http://服務(wù)器ip:8080
4. https
chrome對個人隱私要求嚴格,不配置https不準使用麥克風(fēng)/攝像頭
http協(xié)議本身的內(nèi)容是明文的铸董,經(jīng)過TLS/SSL加密祟印,并經(jīng)過第三方認證,最后 傳輸
HTTPS=HTTP+TLS/SSL
5. nodejs搭建https服務(wù)
生成證書
- 私有證書: 我們自己生成的粟害,瀏覽器不認可
- 認證證書: 第三方機構(gòu)認證頒發(fā)
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參數(shù)是證書
res.writeHead(200,{'Content-Type':'text/plain'}); //設(shè)置返回的請求頭
res.end('hello world\n'); //body
}).listen(443,'0.0.0.0'); //用http模塊創(chuàng)建一個http服務(wù)套鹅,并打開監(jiān)聽(0.0.0.0表示所有網(wǎng)卡都綁定443端口并監(jiān)聽)
域名訪問
https://xxxx.com
6. 真正的web服務(wù)
- 引入express模塊【nodejs里專門處理web服務(wù)的,里面有很多功能】
- serve-index模塊【將整個目錄里發(fā)布出來汰具,這個目錄里的文件都共享出來卓鹿,可以直接通過瀏覽器瀏覽】
- 指定發(fā)布目錄
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(); #創(chuàng)建一個對象,實例化express模塊
app.use(express.static('./public')); #發(fā)布靜態(tài)資源的路徑
app.usr(serverIndex('./public')); #瀏覽發(fā)布路徑的文件
//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基礎(chǔ)知識
1. 變量與類型
2. 基本運算
3. if else
4. 循環(huán)
5. 函數(shù)
三吟孙、 webRTC設(shè)備管理
1. enumerateDevices
通過這個api 能獲取到電腦的音頻/視頻設(shè)備
Promise
是js中特有的對象
Promise中有一個重要的結(jié)構(gòu)體MediaDevicesInfo
背景:
JavaScript他是用單線程去處理整個邏輯,所以防止它被阻塞,大量使用了異步調(diào)用杰妓,Promise就是異步調(diào)用其中的一種方式
它的基本思想就是: 首先你在創(chuàng)建Promise的時候藻治,要傳給它一個handle函數(shù),這個handle處理你的主要邏輯巷挥,那么處理完成之后桩卵,如果成功了,它就會調(diào)resolve這個函數(shù)倍宾,如果失敗了它就調(diào)reject這個函數(shù)吸占,這樣就創(chuàng)建好了一個Promise;
Promise可以注冊兩個方法凿宾,一個是通過then矾屯,一個是通過catch;then就是當(dāng)整個邏輯處理成功之后就會收到on_reolve事件初厚,當(dāng)收到事件的時候可以處理一些邏輯件蚕;catch就是當(dāng)失敗的時候收到on_reject處理一些失敗的邏輯;
then成功的時候還可以返回一個Promise产禾,你可以繼續(xù)then排作。。亚情⊥荆【鏈式】
回到
var ePromise= navigator.mediaDevices.enumerateDevices();
在enumerateDevices()這個函數(shù)里它就new了一個Promise,中間給它注冊了一個handle楞件, 所以當(dāng)這個函數(shù)執(zhí)行的時候衫生,它就返回一個Promise,在我們用的時候土浸,拿到這個Promise罪针,我們就給它注冊兩個函數(shù),一個是then的方法黄伊,一個是catch的方法泪酱,如果成功調(diào)用then的方法做成功的邏輯,如果失敗了調(diào)catch
2. 獲取用戶音頻/視頻設(shè)備實戰(zhàn)
#首先進入上面發(fā)布的目錄还最,在目錄下寫我們的程序墓阀,這樣當(dāng)我們寫完它就發(fā)布出來了寒屯,我們通過瀏覽器就能看到相應(yīng)的結(jié)果
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代碼乱陡,這樣當(dāng)我們打開頁面的時候js代碼就會執(zhí)行,chrome瀏覽器會把它交到底層的v8引擎解析渲染-->
</body>
</html>
mkdir js && cd js
vim client.js
'use strict'
#首先看瀏覽器是否支持我們的方法(方法是否存在)芬首,支持則調(diào)用悦即,不支持報錯
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);
}
如果結(jié)果lable為空吮成,則瀏覽器應(yīng)該用https打開橱乱,http是不顯示設(shè)備名稱的
3. 在頁面上顯示設(shè)備
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'
#首先獲取到音頻輸入設(shè)備(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)
每一條媒體軌就是一種媒體數(shù)據(jù)(音頻/視頻)粱甫,可以有多個音頻/視頻組成一個stream
vim index.html
<html>
<head>
<title>WebRTC capture video and audio</title>
</head>
<body>
<video autoplay playsinline id="player"></video> <!--html5的標簽泳叠,可以顯示我們捕獲的音頻數(shù)據(jù) ;autoplay屬性為playsinline 表示在頁面中播放-->
<script src="./js/client.js"></script> <!-- js代碼用于捕獲音頻數(shù)據(jù)-->
</body>
</html>
vim ./js/client.js
'use strict'
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia()) {
console.log('getUserMedia is not supported!');
}else{
var constrants:{
#同時采集音頻和視頻數(shù)據(jù)
video =true,
audio=true
}
navigator.mediaDevices.getUserMedia(constrants).then(gotMediaStream).catch(handleError);
}
var videoplay=document.querySelector('video#player')
function gotMediaStream(stream){
#指定數(shù)據(jù)源
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
<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. 獲取訪問音頻/適配設(shè)備的權(quán)限
為解決之前獲取設(shè)備不同瀏覽器之間有差異問題:不同瀏覽器對權(quán)限獲取的實現(xiàn)不同茶宵,導(dǎo)致有的能獲取到設(shè)備危纫,有的不能;
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乌庶,所以我們還可以繼續(xù)進行then操作种蝶,去獲取設(shè)備
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');
#當(dāng)我拿到這個流,說明用戶已經(jīng)同意拿到音頻/視頻設(shè)備了
function gotMediaStream(stream){
videoplay.srcObject=stream;
#相當(dāng)于將Promise返回回去瞒大。用于繼續(xù)使用Promise的鏈式調(diào)用螃征,繼續(xù)then()
return avigator.mediaDevices.enumerateDevices();
}
function handleError(err){
console.log(err.name+":"+err.message);
}
#實現(xiàn)一下獲取設(shè)備后的操作
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. 切換采集設(shè)備
vim ./js/client.js
'use strict'
function(){ #將這一段設(shè)為一個函數(shù)
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia()) {
console.log('getUserMedia is not supported!');
return; #出錯直接返回
}else{
var deviceId=videoSource.value; #拿到設(shè)備的id
var constrants:{
video =true,
audio=true
}
deviceId: deviceId?deviceId:undefind #deviceId為空,則為undefind透敌;不為空則賦值
navigator.mediaDevices.getUserMedia(constrants).then(gotDevices).then(gotMediaStream).catch(handleError);
}
}
#當(dāng)頁面進來就調(diào)用這個函數(shù)
start();
#增加一個事件:當(dāng)我們選擇攝像頭(標簽)的時候盯滚,可以觸發(fā)onchange事件,調(diào)用start()函數(shù)酗电,重新進行初始化
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()采集的數(shù)據(jù)
1. WebRTC捕獲桌面
vim ./js/client.js
將所有g(shù)etUserMedia換為getDisplayMedia就ok 了
七撵术、socket.io
1. 使用socket.io發(fā)送消息
2. WebRTC信令服務(wù)器
3. 通過socket.io實現(xiàn)信令服務(wù)器
vim server.js
日志