音视频采集API
getusrMedia:https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
MediaDevices.getUserMedia()
会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream
,里面包含了请求的媒体类型的轨道。此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。
它返回一个 Promise
对象,成功后会resolve
回调一个 MediaStream
对象。若用户拒绝了使用权限,或者需要的媒体源不可用,promise
会reject
回调一个 PermissionDeniedError
或者 NotFoundError
。
返回的promise对象可能既不会resolve也不会reject,因为用户不是必须选择允许或拒绝。
通常你可以使用 navigator.mediaDevices
来获取 MediaDevices
,例如:
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* 使用这个stream stream */
})
.catch(function(err) {
/* 处理error */
});
语法
var promise = navigator.mediaDevices.getUserMedia(constraints);
参数
constraints
作为一个MediaStreamConstraints
对象,指定了请求的媒体类型和相对应的参数。
constraints 参数是一个包含了video
和 audio
两个成员的MediaStreamConstraints
对象,用于说明请求的媒体类型。必须至少一个类型或者两个同时可以被指定。如果浏览器无法找到指定的媒体类型或者无法满足相对应的参数要求,那么返回的Promise对象就会处于rejected[失败]状态,NotFoundError
作为rejected[失败]回调的参数。
以下同时请求不带任何参数的音频和视频:
{ audio: true, video: true }
如果为某种媒体类型设置了 true
,得到的结果的流中就需要有此种类型的轨道。如果其中一个由于某种原因无法获得,getUserMedia()
将会产生一个错误。
当由于隐私保护的原因,无法访问用户的摄像头和麦克风信息时,应用可以使用额外的constraints参数请求它所需要或者想要的摄像头和麦克风能力。下面演示了应用想要使用1280x720的摄像头分辨率:
{
audio: true,
video: { width: 1280, height: 720 }
}
浏览器会试着满足这个请求参数,但是如果无法准确满足此请求中参数要求或者用户选择覆盖了请求中的参数时,有可能返回其它的分辨率。
强制要求获取特定的尺寸时,可以使用关键字min
, max
, 或者 exact
(就是 min == max). 以下参数表示要求获取最低为1280x720的分辨率。
{
audio: true,
video: {
width: { min: 1280 },
height: { min: 720 }
}
}
如果摄像头不支持请求的或者更高的分辨率,返回的Promise会处于rejected状态,NotFoundError作为
rejected回调的参数,而且用户将不会得到要求授权的提示。
造成不同表现的原因是,相对于简单的请求值和ideal
关键字而言,关键字min
, max
, 和 exact
有着内在关联的强制性,请看一个更详细的例子:
{
audio: true,
video: {
width: { min: 1024, ideal: 1280, max: 1920 },
height: { min: 776, ideal: 720, max: 1080 }
}
}
当请求包含一个ideal(应用最理想的)值时,这个值有着更高的权重,意味着浏览器会先尝试找到最接近指定的理想值的设定或者摄像头(如果设备拥有不止一个摄像头)。
简单的请求值也可以理解为是应用理想的值,因此我们的第一个指定分辨率的请求也可以写成如下:
{
audio: true,
video: {
width: { ideal: 1280 },
height: { ideal: 720 }
}
}
返回值
返回一个 Promise
, 这个Promise成功后的回调函数带一个 MediaStream
对象作为其参数。
异常
返回一个失败状态的Promise,这个Promise失败后的回调函数带一个DOMException
对象作为其参数。 可能的异常有:
AbortError
[中止错误]
尽管用户和操作系统都授予了访问设备硬件的权利,而且未出现可能抛出NotReadableError
异常的硬件问题,但仍然有一些问题的出现导致了设备无法被使用。
NotAllowedError
[拒绝错误]
用户拒绝了当前的浏览器实例的访问请求;或者用户拒绝了当前会话的访问;或者用户在全局范围内拒绝了所有媒体访问请求。
SecurityError
,但在新版本当中SecurityError
被赋予了新的意义。
NotFoundError
[找不到错误]
找不到满足请求参数的媒体类型。
NotReadableError
[无法读取错误]
尽管用户已经授权使用相应的设备,操作系统上某个硬件、浏览器或者网页层面发生的错误导致设备无法被访问。
OverConstrainedError
[无法满足要求错误]
指定的要求无法被设备满足,此异常是一个类型为OverconstrainedError
的对象,拥有一个constraint
属性,这个属性包含了当前无法被满足的constraint
对象,还拥有一个message
属性,包含了阅读友好的字符串用来说明情况。
SecurityError
[安全错误]
在getUserMedia()
被调用的 Document
上面,使用设备媒体被禁止。这个机制是否开启或者关闭取决于单个用户的偏好设置。
TypeError
[类型错误]
constraints对象未设置[空],或者都被设置为false
。
使用getusermedia获取音视频
目录结构:
client.js:
'use strict'
var videoplay = document.querySelector('video#player');
function gotMediaStream(stream){
window.stream = stream;
videoplay.srcObject = stream;
}
function handleError(err){
console.log('getUserMedia error:', err);
}
function start() {
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia){
console.log('getUserMedia is not supported!');
return;
}else{
var constraints = {
video : true,
audio : true
}
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.catch(handleError);
}
}
start()
index.html
<html>
<head>
<title>WebRTC capture video and audio</title>
</head>
<body>
<video autoplay playsinline id="player"></video>
<script src="./js/client.js"></script>
</body>
</html>
WebRTC_API_适配(浏览器适配方法)
而如果我们自己实现的话,就需要分别判断是否有合适的浏览器api,但是这种方式比较麻烦,可以用google的开源库适配不同的浏览器,为其定义统一的接口。
添加adapter后的index.html代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC caoture video and audio</title>
</head>
<body>
<!--video元素里可以显示我们捕获的音视频数据-->
<!--autoplay属性表示我们拿到视频源的时候直接将它播放出来,playsinline 表示在浏览器的页面中播放-->
<video autoplay playsinline id="player"></video>
<!--引入adapter.js-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!--加入一段javascript源码,通过调用 getusermedia这个api来捕获音视频数据,-->
<script src = './js/client.js' ></script>
</body>
</html>
查看加载的js
获取音视频设备的访问权限
在前面获取到音视频设备信息的章节中,我们在chrome浏览器中可以获取到设备信息,而换到safari浏览器中却获取失败,这是因为没有获取到相应的权限。
解决这个问题,可以通过上小节我们提到的adapter.js 获取到音视频设备的权限。
代码如下:
client.js
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
videoplay.srcObject = stream;
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
//如果这个方法不存在,则打印
if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia){
console.log('getUserMedia is not supported!');
}else {
//设置参数,同时采集音频和视频数组
var constraints = {
video:true,
audio:true
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
index.html
<html>
<head>
<title>WebRTC capture video and audio</title>
</head>
<body>
<!--音频源,音频是输出,视频源-->
<div>
<label>audio Source:</label>
<select id = "audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id = "audioOutput"></select>
</div>
<div>
<label>video Source</label>
<select id = "videoSource"></select>
</div>
<video autoplay playsinline id="player"></video>
<script src="./js/client.js"></script>
</body>
</html>
音视频采集约束
视频约束 ( 视频参数调整 )
一般宽和高有两种比例,4:3, 16:9
frameRate 为帧率,我们可以通过帧率的多少来控制我们的码流,如果帧率低的话,会看到画面不太平滑,帧率高的话,画面就会很平滑,目前一般的高清电影都有60帧,另外一点,如果帧率过高的话,码流肯定会过大,相当于一秒钟采集的数据多
facingMode 用于控制摄像头翻转,主要用于手机摄像头。
resizeMode 为采集的画面是否裁剪。
这些参数的设置方法如下:
//设置参数,只采集视频,并设置视频参数
var constraints = {
video:{
width:320,
height:240,
frameRate:30,
facingMode:'enviroment'
},
audio:false
}
音频约束(音频参数设置)
volunme:声音大小,从0 到 1.0
sampleRate:采样率
sampleSize: 采样大小,一般情况下为16位,也就是2个字节
echoCancellation:回音,即采集数据之后,是否要开启回音消除
autoGainControl:自动增益,即在原有声音的基础上,是否增加音量
noiseSuppression :降噪,即采集数据的时候,是否要开启降噪功能
latency:延迟大小,设置延迟小,而网络状况不好,就会出现卡顿,花屏等问题,好处是双方可以实时通信,一般是低于500毫秒, 最好是200毫秒内
channelCount:单声道还是双声道,一般情况下使用单声道就够了,如果是乐器,一般为双声道
deviceID:当有多个摄像头或者多个输入输出设备的时候,可以进行设备的切换
groupID:音频的输入与输出
实战:修改后的clinet.js 代码如下
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
videoplay.srcObject = stream;
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
function start() {
//如果这个方法不存在,则打印
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia is not supported!')
return;
} else {
var deviceId = videoSource.value;
//设置参数,采集音视频,并设置视频参数和音频参数
var constraints = {
video: {
width: 320,
height: 240,
frameRate: 30,
facingMode: 'enviroment',
//判断deviceId的值是否为空,如果不为空就设置它的值为deviceId,如果为空就设置为undefined
deviceId: deviceId ? deviceId: undefined
},
audio: {
noiseSuppression: true,
echoCancellation: true
},
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
//增加事件,选择摄像头的时候重新调用start函数,实现设备切换
videoSource.onchange = start;
视频特效
在浏览器中做特效使用的是 CSS filter,在不同浏览器中使用的filter还不一样。我们在视频渲染的时候,实际上在底层最终调用的还是OpenGL或者是Metal基础的图形绘制库,通过GPU进行绘制。
实战:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC caoture video and audio</title>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
<!--音频源,音频是输出,视频源-->
<div>
<label>audio Source:</label>
<select id = "audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id = "audioOutput"></select>
</div>
<div>
<label>video Source</label>
<select id = "videoSource"></select>
</div>
<div>
<label>Filter: </label>
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
</div>
<!--video元素里可以显示我们捕获的音视频数据-->
<!--autoplay属性表示我们拿到视频源的时候直接将它播放出来,playsinline 表示在浏览器的页面中播放-->
<video autoplay playsinline id="player"></video>
<!--引入adapter.js-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!--加入一段javascript源码,通过调用 getusermedia这个api来捕获音视频数据,-->
<script src = './js/client.js' ></script>
</body>
</html>
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
var filtersSelect = document.querySelector("select#filter");
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
videoplay.srcObject = stream;
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
function start() {
//如果这个方法不存在,则打印
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia is not supported!')
return;
} else {
var deviceId = videoSource.value;
//设置参数,采集音视频,并设置视频参数和音频参数
var constraints = {
video: {
width: 320,
height: 240,
frameRate: 30,
facingMode: 'enviroment',
//判断deviceId的值是否为空,如果不为空就设置它的值为deviceId,如果为空就设置为undefined
deviceId: deviceId ? deviceId: undefined
},
audio: {
noiseSuppression: true,
echoCancellation: true
},
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
//增加事件,选择摄像头的时候重新调用start函数,实现设备切换
videoSource.onchange = start;
//增加事件处理,当我们选择其中的某一项变化的时候,进行处理
//onchange 事件会在域的内容改变时发生
filtersSelect.onchange = function () {
//获取视频的模式的名字
videoplay.className = filtersSelect.value;
从视频中获取图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC caoture video and audio</title>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
<!--音频源,音频是输出,视频源-->
<div>
<label>audio Source:</label>
<select id = "audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id = "audioOutput"></select>
</div>
<div>
<label>video Source</label>
<select id = "videoSource"></select>
</div>
<div>
<label>Filter: </label>
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
</div>
<!--video元素里可以显示我们捕获的音视频数据-->
<!--autoplay属性表示我们拿到视频源的时候直接将它播放出来,playsinline 表示在浏览器的页面中播放-->
<video autoplay playsinline id="player"></video>
<div>
<button id="snapshot">Take snapshot</button>
</div>
<div>
<canvas id="picture"></canvas>
</div>
<!--引入adapter.js-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!--加入一段javascript源码,通过调用 getusermedia这个api来捕获音视频数据,-->
<script src = './js/client.js' ></script>
</body>
</html>
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
//filter
var filtersSelect = document.querySelector("select#filter");
//picture
var snapshot = document.querySelector('button#snapshot');
var picture = document.querySelector('canvas#picture');
picture.width = 320;
picture.height = 240;
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
videoplay.srcObject = stream;
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
function start() {
//如果这个方法不存在,则打印
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia is not supported!')
return;
} else {
var deviceId = videoSource.value;
//设置参数,采集音视频,并设置视频参数和音频参数
var constraints = {
video: {
width: 320,
height: 240,
frameRate: 30,
facingMode: 'enviroment',
//判断deviceId的值是否为空,如果不为空就设置它的值为deviceId,如果为空就设置为undefined
deviceId: deviceId ? deviceId: undefined
},
audio: {
noiseSuppression: true,
echoCancellation: true
},
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
//增加事件,选择摄像头的时候重新调用start函数,实现设备切换
videoSource.onchange = start;
//增加事件处理,当我们选择其中的某一项变化的时候,进行处理
//onchange 事件会在域的内容改变时发生
filtersSelect.onchange = function () {
//获取视频的模式的名字
videoplay.className = filtersSelect.value;
}
//给snapshot添加onclick事件,当我们点击button的时候,触发这个事件
snapshot.onclick = function () {
//设置滤镜
picture.className = filtersSelect.value;
//得到picture的上下文,将videoplay当成源,从这里截取一帧数据,写成一张图片
picture.getContext('2d').drawImage(videoplay,
0,0,
picture.width,
picture.height);
}
采集音频数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC caoture video and audio</title>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
<!--音频源,音频是输出,视频源-->
<div>
<label>audio Source:</label>
<select id = "audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id = "audioOutput"></select>
</div>
<div>
<label>video Source</label>
<select id = "videoSource"></select>
</div>
<div>
<label>Filter: </label>
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
</div>
<!--增加audio标签,只采集音频数据, controls会将暂停和播放按钮显示出来-->
<audio autoplay controls id="audioplayer"></audio>
<div>
<button id="snapshot">Take snapshot</button>
</div>
<div>
<canvas id="picture"></canvas>
</div>
<!--引入adapter.js-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!--加入一段javascript源码,通过调用 getusermedia这个api来捕获音视频数据,-->
<script src = './js/client.js' ></script>
</body>
</html>
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
//filter
var filtersSelect = document.querySelector("select#filter");
//picture
var snapshot = document.querySelector('button#snapshot');
var picture = document.querySelector('canvas#picture');
picture.width = 320;
picture.height = 240;
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取音频
var audioplay = document.querySelector('audio#audioplayer')
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
//videoplay.srcObject = stream;
audioplay.srcObject = stream;
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
function start() {
//如果这个方法不存在,则打印
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia is not supported!')
return;
} else {
var deviceId = videoSource.value;
//设置参数,采集音视频,并设置视频参数和音频参数
var constraints = {
video: false,
audio: {
noiseSuppression: true,
echoCancellation: true
},
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
//增加事件,选择摄像头的时候重新调用start函数,实现设备切换
videoSource.onchange = start;
//增加事件处理,当我们选择其中的某一项变化的时候,进行处理
//onchange 事件会在域的内容改变时发生
filtersSelect.onchange = function () {
//获取视频的模式的名字
videoplay.className = filtersSelect.value;
}
//给snapshot添加onclick事件,当我们点击button的时候,触发这个事件
snapshot.onclick = function () {
//设置滤镜
picture.className = filtersSelect.value;
//得到picture的上下文,将videoplay当成源,从这里截取一帧数据,写成一张图片
picture.getContext('2d').drawImage(videoplay,
0,0,
picture.width,
picture.height);
}
MediaStreamAPI及获取视频约束
MediaStream.addTrack:向媒体流中加入不同的轨
MediaStream.removeTrack:从媒体流中将不想要的轨移除
MediaStream.getVedeoTracks:拿到所有视频的轨
MediaSream.getAudioTracks:拿到媒体流中所有音频的track
MediaStream.onaddtrack:添加一个媒体轨到媒体流的时候会触发这个事件
MediaStream.onremovetrack:移除一个媒体轨到媒体流的时候会触发这个事件
MediaStream.onended:当一个流结束的时候,会收到流结束的事件
实战:
'use strict'
//获取select这个元素中id为audioSource的元素
var audioSource = document.querySelector("select#audioSource");
var audioOutput = document.querySelector("select#audioOutput");
var videoSource = document.querySelector("select#videoSource");
//filter
var filtersSelect = document.querySelector("select#filter");
//picture
var snapshot = document.querySelector('button#snapshot');
var picture = document.querySelector('canvas#picture');
picture.width = 320;
picture.height = 240;
//首先获取到在html中定义的vedio标签
var videoplay = document.querySelector('video#player');
//获取音频
var audioplay = document.querySelector('audio#audioplayer')
//获取到 divConstraints
var divConstraints = document.querySelector('div#constraints')
//获取设备信息,参数是设备信息的数组
function gotDevices(deviceInfos) {
//遍历设备信息数组
deviceInfos.forEach(
//拿到每一项的deviceInfo作为参数
function (deviceinfo) {
//select 中的每一项是一个option,根据不同的种类添加到不同的select中去
var option = document.createElement('option');
option.text = deviceinfo.label; //设备名称
option.value = deviceinfo.deviceId //值是deviceid
console.log("deviceinfo.kind:",deviceinfo.kind);
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
}
)
}
//实现获取流之后的方法,将获取到的流赋值给我们在html中定义的vedio标签
function gotMediaStream(stream) {
//指定标签获取视频流的数据源
videoplay.srcObject = stream;
//通过media stream 拿到视频的track,视频轨,只取第一个
var videoTrack = stream.getVideoTracks()[0];
//拿到vedio的所有约束
var videoConstraints = videoTrack.getSettings();
//将其转成json格式,作为内容赋值给divConstraints,在页面展示出来
divConstraints.textContent = JSON.stringify(videoConstraints,null,2);
//拿到流之后,说明用户已经同意访问音视频设备了,此时可以返回一个promise,获取
//所有的音视频设备
return navigator.mediaDevices.enumerateDevices();
}
//获取失败 打印出错信息
function handleError(err) {
console.log('getUserMedia error:', err);
}
function start() {
//如果这个方法不存在,则打印
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia is not supported!')
return;
} else {
var deviceId = videoSource.value;
//设置参数,采集音视频,并设置视频参数和音频参数
var constraints = {
video: {
width: 320,
height: 240,
frameRate: 30,
facingMode: 'enviroment',
//判断deviceId的值是否为空,如果不为空就设置它的值为deviceId,如果为空就设置为undefined
deviceId: deviceId ? deviceId: undefined
},
audio: {
noiseSuppression: true,
echoCancellation: true
},
}
//如果存在,就调用这个方法,成功进入成功方法,失败进入失败方法
//成功之后,由于promise是可以串联的,then之后可以继续then
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
//增加事件,选择摄像头的时候重新调用start函数,实现设备切换
videoSource.onchange = start;
//增加事件处理,当我们选择其中的某一项变化的时候,进行处理
//onchange 事件会在域的内容改变时发生
filtersSelect.onchange = function () {
//获取视频的模式的名字
videoplay.className = filtersSelect.value;
}
//给snapshot添加onclick事件,当我们点击button的时候,触发这个事件
snapshot.onclick = function () {
//设置滤镜
picture.className = filtersSelect.value;
//得到picture的上下文,将videoplay当成源,从这里截取一帧数据,写成一张图片
picture.getContext('2d').drawImage(videoplay,
0,0,
picture.width,
picture.height);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebRTC caoture video and audio</title>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
<!--音频源,音频是输出,视频源-->
<div>
<label>audio Source:</label>
<select id = "audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id = "audioOutput"></select>
</div>
<div>
<label>video Source</label>
<select id = "videoSource"></select>
</div>
<div>
<label>Filter: </label>
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
</div>
<!--增加audio标签,只采集音频数据, controls会将暂停和播放按钮显示出来-->
<!-- <audio autoplay controls id="audioplayer"></audio>-->
<section>
<table>
<tr>
<!--video元素里可以显示我们捕获的音视频数据-->
<!--autoplay属性表示我们拿到视频源的时候直接将它播放出来,playsinline 表示在浏览器的页面中播放-->
<!-- 一行两列,第一列显示捕获的视频,第二列显示视频约束-->
<td> <video autoplay playsinline id="player"></video></td>
<td><div id="constraints" class="output"></div></td>
</tr>
</table>
</section>
<div>
<button id="snapshot">Take snapshot</button>
<canvas id="picture"></canvas>
</div>
<!--引入adapter.js-->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!--加入一段javascript源码,通过调用 getusermedia这个api来捕获音视频数据,-->
<script src = './js/client.js' ></script>
</body>
</html>
显示的constrant:
webrtc录制媒体流
MediaRecoder
基本格式
var mediaRecorder = new MediaRecorder(stream[,options]);
参数说明:
参数 | 说明 |
---|---|
stream | 媒体流,可从getUserMedia、<video>、<audio>或<canvas>获取 |
options | 限制选项 |
选项 | 说明 |
---|---|
mimeType | video/webm audio/webm video/webm; codecs=vp8 video/webm;codecs=h264 audio/webm;codecs=opus |
audioBitsPerSecond | 音频码率 |
videooBitsPerSecond | 视频码率 |
bitsPerSecond | 整体码率 |
MediaRecorder API(一)
- MediaRecorder.start(timeslice)
开始录制媒体,timeslice是可选的,如果设置了会按时间切片存储数据 - MediaRecorder.stop()
停止录制,此时会触发包括最终Blob数据的dataavailable时间 - MediaRecorder.pause()
- MediaRecorder.resume()
- MediaRecorder.isTypeSupported()
MediaRecorder事件
- MediaRecorder.ondataavailable
每次记录一定时间的数据时(如果没有指定时间片,则记录整个数据时)会定时触发。
MediaRecorder.onerror
录制错误时候会停止录制
JavaScript几种存储数据的方式
- 字符串
- Blob
- ArrayBuffer
- ArrayBufferView
实际操作:录制音视频
创建标签
创建开始录制/开始播放/开始下载/存放录制视频的标签:
<table>
<tr>
<td>
<video autoplay playsinline id="player"></video>
</td>
<td>
<!--播放录制视频标签-->
<video autoplay playsinline id="recplayer"></video>
</td>
<td>
<div id="constraints" class="output">
</div>
</td>
</tr>
<tr>
<!--开始录制视频按钮-->
<td><button id="record">Start Record</button></td>
<!--播放录制视频按钮-->
<td><button id="recplay" disabled>Play</button></td>
<!--下载录制视频按钮-->
<td><button id="download" disabled>Download</button></td>
</tr>
</table>
在gotMediaStream函数中,把流放到全局的window中
window.stream = stream;
再获取响应的按钮,添加相应的按钮事件:
// 录制视频的四个按钮
var recvideo = document.querySelector('video#recplayer');
var btnRecord = document.querySelector('button#record');
var btnPlay = document.querySelector('button#recplay');
var btnDownload = document.querySelector('button#download');
//录制视频的数据
var buffer;
var mediaRecorder;
function handleDataAvilable(e) {
if (e && e.data && e.data.size > 0) {
buffer.push(e.data);
}
}
function startRecord() {
buffer = [];
var options = {
memeType: 'video/webm;codecs=vp8'
}
if (!MediaRecorder.isTypeSupported(options.memeType)) {
console.error('${options.memeType} is not suttported');
return;
}
try {
mediaRecorder = new MediaRecorder(stream, options);
}catch (e) {
console.error('Failed to create MediaRecorder:', e);
}
mediaRecorder.ondataavailable = handleDataAvilable;
mediaRecorder.start(10);
}
function stopRecord() {
mediaRecorder.stop();
}
//录制视频按钮点击事件
btnRecord.onclick = ()=> {
if (btnRecord.textContent === 'Start Record') {
startRecord();
btnRecord.textContent = 'Stop Record';
btnPlay.disabled = false;
btnDownload.disabled = false;
}else {
stopRecord();
btnRecord.textContent = 'Start Record';
// btnPlay.disabled = true;
// btnDownload.disabled = true;
}
};
btnPlay.onclick = ()=> {
var blob = new Blob(buffer, {type: 'video/webm'});
recvideo.src = window.URL.createObjectURL(blob);
recvideo.srcObject = null;
recvideo.controls = true;
recvideo.play();
}
// 下载按钮
btnDownload.onclick = ()=> {
var blob = new Blob(buffer, {type: 'video/webm'});
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.style.display = 'none';
a.download = 'aaa.webm';
a.click();
}
整个文件:
'use strict'
//devices
var audioSource = document.querySelector('select#audioSource');
var audioOutput = document.querySelector('select#audioOutput');
var videoSource = document.querySelector('select#videoSource');
//filter
var filtersSelect = document.querySelector('select#filter');
//picture
var snapshot = document.querySelector('button#snapshot');
var picture = document.querySelector('canvas#picture');
picture.width = 640;
picture.height = 480;
var videoplay = document.querySelector('video#player');
//var audioplay = document.querySelector('audio#audioplayer');
//div
var divConstraints = document.querySelector('div#constraints');
//record
var recvideo = document.querySelector('video#recplayer');
var btnRecord = document.querySelector('button#record');
var btnPlay = document.querySelector('button#recplay');
var btnDownload = document.querySelector('button#download');
var buffer;
var mediaRecorder;
//socket.io
var btnConnect = document.querySelector('button#connect');
var btnLeave = document.querySelector('button#leave');
var inputRoom = document.querySelector('input#room');
var socket;
var room;
function gotDevices(deviceInfos){
deviceInfos.forEach(function(deviceinfo){
var option = document.createElement('option');
option.text = deviceinfo.label;
option.value = deviceinfo.deviceId;
if(deviceinfo.kind === 'audioinput'){
audioSource.appendChild(option);
}else if(deviceinfo.kind === 'audiooutput'){
audioOutput.appendChild(option);
}else if(deviceinfo.kind === 'videoinput'){
videoSource.appendChild(option);
}
})
}
function gotMediaStream(stream){
var videoTrack = stream.getVideoTracks()[0];
var videoConstraints = videoTrack.getSettings();
divConstraints.textContent = JSON.stringify(videoConstraints, null, 2);
window.stream = stream;
videoplay.srcObject = stream;
//audioplay.srcObject = stream;
return navigator.mediaDevices.enumerateDevices();
}
function handleError(err){
console.log('getUserMedia error:', err);
}
function start() {
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getDisplayMedia){
console.log('getUserMedia is not supported!');
return;
}else{
var deviceId = videoSource.value;
var constraints = {
video : {
width: 640,
height: 480,
frameRate:15,
facingMode: 'enviroment',
deviceId : deviceId ? {exact:deviceId} : undefined
},
audio : false
}
navigator.mediaDevices.getDisplayMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError);
}
}
start();
videoSource.onchange = start;
filtersSelect.onchange = function(){
videoplay.className = filtersSelect.value;
}
snapshot.onclick = function() {
picture.className = filtersSelect.value;
picture.getContext('2d').drawImage(videoplay, 0, 0, picture.width, picture.height);
}
function handleDataAvailable(e){
if(e && e.data && e.data.size > 0){
buffer.push(e.data);
}
}
function startRecord(){
buffer = [];
var options = {
mimeType: 'video/webm;codecs=vp8'
}
if(!MediaRecorder.isTypeSupported(options.mimeType)){
console.error(`${options.mimeType} is not supported!`);
return;
}
try{
mediaRecorder = new MediaRecorder(window.stream, options);
}catch(e){
console.error('Failed to create MediaRecorder:', e);
return;
}
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10);
}
function stopRecord(){
mediaRecorder.stop();
}
btnRecord.onclick = ()=>{
if(btnRecord.textContent === 'Start Record'){
startRecord();
btnRecord.textContent = 'Stop Record';
btnPlay.disabled = true;
btnDownload.disabled = true;
}else{
stopRecord();
btnRecord.textContent = 'Start Record';
btnPlay.disabled = false;
btnDownload.disabled = false;
}
}
btnPlay.onclick = ()=> {
var blob = new Blob(buffer, {type: 'video/webm'});
recvideo.src = window.URL.createObjectURL(blob);
recvideo.srcObject = null;
recvideo.controls = true;
recvideo.play();
}
btnDownload.onclick = ()=> {
var blob = new Blob(buffer, {type: 'video/webm'});
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.style.display = 'none';
a.download = 'aaa.webm';
a.click();
}
function connectServer(){
socket = io.connect();
btnLeave.disabled = false;
btnConnect.disabled = true;
///
socket.on('joined', function(room, id){
console.log('The User(' + id + ') have joined into ' + room);
});
socket.on('leaved', function(room, id){
console.log('The User(' + id + ') have leaved from ' + room);
});
//join room
room = inputRoom.value;
if(room !== ''){
socket.emit('join', room);
}
}
btnConnect.onclick = ()=> {
connectServer();
}
btnLeave.onclick = ()=> {
if(room !== ''){
socket.emit('leave', room);
btnLeave.disabled = true;
btnConnect.disabled = false;
socket.disconnect();
}
}
<html>
<head>
<title>WebRTC capture video and audio</title>
<link rel="stylesheet" href="css/main.css"/>
<style>
.none {
-webkit-filter: none;
}
.blur {
-webkit-filter: blur(3px);
}
.grayscale {
-webkit-filter: grayscale(1);
}
.invert {
-webkit-filter: invert(1);
}
.sepia {
-webkit-filter: sepia(1);
}
</style>
</head>
<body>
<section>
<div>
<label>audio Source:</label>
<select id="audioSource"></select>
</div>
<div>
<label>audio Output:</label>
<select id="audioOutput"></select>
</div>
<div>
<label>video Source:</label>
<select id="videoSource"></select>
</div>
<div>
<label>Connect Server:</label>
<button id="connect">Connect</button>
<label>Room Name: </label>
<input type="text" id="room" value="aaa"></input>
</div>
<div>
<button id="leave" disabled>Leave</button>
</div>
</section>
<section>
<div>
<label>Filter:</label>
<select id="filter">
<option value="none">None</option>
<option value="blur">blur</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="sepia">sepia</option>
</select>
</div>
<!--<audio autoplay controls id='audioplayer'></audio>-->
<table>
<tr>
<td><video autoplay playsinline id="player"></video></td>
<td><video playsinline id="recplayer"></video></td>
<td><div id='constraints' class='output'></div></td>
</tr>
<tr>
<td><button id="record">Start Record</button></td>
<td><button id="recplay" disabled>Play</button></td>
<td><button id="download" disabled>Download</button></td>
</tr>
</table>
</section>
<section>
<div>
<button id="snapshot">Take snapshot</button>
</div>
<div>
<canvas id="picture"></canvas>
</div>
</section>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
<script src="./js/client.js"></script>
</body>
</html>
实际操作:录制桌面
getDisplayMedia
基本格式
var promise = navigator.mediaDevices.getDisplayMedia(constraints);
- constraints可选
constraints约束与getUserMedia函数中的一致
打开chrome设置
chrome://flags/#enable-experimental-web-platform-features设置为enable。
使用时只需要将上面的getUsrMedia换成getDisplayMedia即可。