前言
最近写了一个js调用摄像头的代码,整体实现是非常简单,但是放到服务器上,通过外网访问后就会出现一个警告
我们需要通过js调用摄像头,但在此之前我们需要创建一个https服务器。所以,这篇博文就是帮助大家如何通过js 调用PC端的摄像头。
如何通过js调用摄像头
先看一下js调用摄像头的具体实现。
整个实现是基于Google提供的webRTC技术,它主要用来让浏览器实时获取和交换视频、音频和数据。
WebRTC共分三个API。
MediaStream(又称getUserMedia)
RTCPeerConnection
RTCDataChannel
getUserMedia主要用于获取视频和音频信息,后两个API用于浏览器之间的数据交换。
而我们主要了解MediaStream
下面我们直接看代码:
//获取用户设备媒体对象
navigator.getUserMedia({audio:true,video:true},stream => {},err => {});
1
2
我们直接通过navigator.getUserMedia方法就可以获取到用户的媒体对象,这个方法有三个参数。
①第一个参数为一个JSON对象,主要为了设置是否获取音频或视频流对象
②获取成功的回调函数,这个回调函数有一个stream参数为获取到的流对象
③出现错误的回调函数,这个回调函数有一个err错误对象
但是现在想让我们的摄像头拍摄到的画面显示出来,我们可以直接通过h5提供的video标签进行加载播放
于是我们可以直接在开头加上video标签,并给它一个id 为rtVideo
<video id="etVideo"></video>
1
我们不添加它的src属性,而是根据js进行动态赋值。
现在我们开始写获取媒体流成功的回调函数
let onSuccess = stream => {
//获取video的dom对象
let rtVideo = document.getElementById("rtVideo");
//为媒体流创建一个url指向
if(window.URL){
webcam.src = window.URL.createObjectURL(stream);
}
rtVideo.autoplay = true;
}
1
2
3
4
5
6
7
8
9
在上述代码中,由于getUserMedia是异步的,因此,通过getUserMedia获取到的媒体流对象后由事件循环取出,将媒体流对象放入获取成功的回调函数的参数,所以上述的stream参数实质就是我们通过getUserMedia获取到的媒体流对象。
然后我们通过getElementById();获取到了video的dom对象
let rtVideo = document.getElementById("rtVideo");
1
下面的代码我们为获取到的流对象创建了一个URL指向,让它可以被传入vedio的src属性。
window.URL.createObjectURL可以接受一个二进制的流对象作为参数。
随后我们给rtVideo设置上我们创建的url即可
if(window.URL){
rtVideo.src = window.URL.createObjectURL(stream);
}
1
2
3
最后我们设置此video为自动播放
rtVideo.autoplay = true;
1
现在在本地运行上述代码时,我们就可以看见摄像头能够正常调用了。但是注意这只是在本地,而被放置在服务器上,通过外网进行访问时,则会报出开头我贴出的那个警告,告知你调用摄像头必须是一个安全的环境比如https。
下面我们将开始用node.js创建一个https服务器,然后将上述代码通过https服务器返回给客户端。
通过node.js创建https服务器
首先我们得知道https可以看作是一个安全的http服务器,实际上它就是http + ssl/tls协议
创建https服务器必须持有CA机构给出的数字签名才行,这里我们使用openssl进行自签名
step 1:使用openssl创建服务器和客户端的私钥
$ openssl genrsa -out server.key 1024
$ openssl genrsa -out client.key 1024
1
2
step 2:使用openssl分别创建服务器和客户端的公钥
$ openssl rsa -in server.key -pubout -out server.pem
$ openssl rsa -in client.key -pubout -out client.pem
1
2
之后我们需要创建一个CA来自签名我们的数字证书
$ openssl genrsa -out ca.key 1024
$ openssl req -new -key ca.key -out ca.csr
$ openssl x509 req -in ca.csr -signkey ca.key -out ca.crt
1
2
3
随后我们生成服务器的csr数字证书请求文件
$ openssl req -new -key server.key -out server.csr
1
自签名
$ openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
1
到这里我们就已经创建完成一个自签名的服务器数字证书
需要注意的是,ssl/tls 协议是传输层协议,https基于ssl/tls协议,但是在建立起与客户端的链接时,https 还是会执行tcp的三次经典握手环节,这是因为https本质就是一个安全版的http,并且ssl/tls 提供的仅仅是安全加密这一方面的协议,因此,在建立https服务器的时候,其性能会比http慢半拍,因为在其传输的数据的时候,数据会在传输层加密,每一次传输都会有加密解密的环节,至于这半拍是多少,传送门:http://www.ruanyifeng.com/blog/2014/09/ssl-latency.html
经历了tcp握手,接下来就是https的四次握手
①客户端生成一个base64编码的随机数,然后将这个随机数随同支持的加密方式,ssl/tls协议的版本号一同发送到服务器
②服务器生成一个base64编码的随机数,将它和 确认的ssl/tls版本发送到客户端,还会发送一个服务器数字证书
③客户端回应一个编码改变通知和结束握手通知
④服务器回应一个编码改变通知和结束握手通知
至此,服务器和客户端握手完毕,值得注意的是,上述握手所传输的信息都是明文传输,但是传输的随机数却会根据RSA 来进行加密,此随机数我们称作 pre master secret ,这个随机数用于生成 session secret ,而session secret是用来利用对称加密来提高加密解密的效率。
至于为什么用三个master secret,这是因为我们的计算机是不会产生真随机数序列,它只会根据一定的算法产生伪随机数序列,一个master secret可能会被猜出来,那三个master secret 就会比较接近随机了。
下面我们利用https 模块来创建一个https服务器,在此之前我需要提一下tls模块,在node中,还封装了一个tls模块,这个模块基于传输层ssl/tls协议的,因此效率比https要高,如同http和tcp 模块。
首先我们引入https模块和fs模块,fs用于读取我们生成的数字证书,私钥,ca的证书等等
//引入模块
const fs = require("fs");
const https = require("https");
1
2
3
导入服务器的私钥和数字证书
其requestCert 的值为一个Boolean值,当其值为true时,会要求客户端给出相应的证书,默认值为false
//导入服务器的私钥和crt 证书文件
let options = {
key:fs.readFileSync("./key/server.key"),
cert:fs.readFileSync("./key/server.crt"),
requestCert:true,
ca:[fs.readFileSync("./key/ca.crt")]
};
1
2
3
4
5
6
7
当我们导入options的信息时,我们对请求响应对象的操作与普通http无较大差异
//创建https服务器
let httpsServer = https.createServer(options);
//监听用户连接
httpsServer.on("request",(req,resp) => {
console.log("有一用户连接");
resp.setHeader("Content-Type","text/html");
resp.writeHead(200);
resp.end(fs.readFileSync("./httpsTest1.html"));
});
httpsServer.listen(9999,"localhost",() => {
console.log("listening");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
至此,我们就创建了一个https服务器,上述代码的httpsTest1.html为文章开头的js代码,用于调用摄像头。
之后我们把代码部署到服务器上,就可以看见成功调用摄像头了,下面贴出完整代码:
node
//引入模块
const fs = require("fs");
const https = require("https");
//导入服务器的私钥和crt 证书文件
let options = {
key:fs.readFileSync("./key/server.key"),
cert:fs.readFileSync("./key/server.crt"),
requestCert:true,
ca:[fs.readFileSync("./key/ca.crt")]
};
//创建https服务器
let httpsServer = https.createServer(options);
//监听用户连接
httpsServer.on("request",(req,resp) => {
console.log("有一用户连接");
resp.setHeader("Content-Type","text/html");
resp.writeHead(200);
resp.end(fs.readFileSync("./httpsTest1.html"));
});
httpsServer.listen(9999,"localhost",() => {
console.log("listening");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
html/js :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<video id="rtVideo" src=""></video>
<script type="text/javascript">
navigator.getUserMedia({audio:true,video:true},stream => {
//获取video的dom对象
let rtVideo = document.getElementById("rtVideo");
//为媒体流创建一个url指向
if(window.URL){
rtVideo.src = window.URL.createObjectURL(stream);
}
rtVideo.autoplay = true;
},() => {});
</script>
</body>
</html>