页面中实现录音需要使用浏览器提供的 Media​Recorder API,所以前提是需要浏览器支持 MediaStream Recording 相关的功能。

以下代码默认工作在 Chrome 环境中。

准备页面

首先准备一个页面,其中内容很简单,一个录音按钮,一个用于播放的 <audio> 标签。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>audio record</title>
</head>
<body>
<div class="app">
<button class="record-btn">record</button>
<audio controls class="audio-player"></audio>
</div>
<script src="./recorder.js"></script>
</body>
</html>

获取录音权限

因为录音需要使用设备的话筒,所以第一步应该是向用户索要录音的权限。这是通过 Media​Devices​.get​User​Media() 来完成的,其用法为:

var promise = navigator.mediaDevices.getUserMedia(constraints);

其中 constraints 为需要获取的权限列表,这里只需要指定音频 audio 即可。

其返回是个 Promise,因为用户何时进行授权是不确定的。通过在 Promise 的回调中进行授权成功或失败的处理。

在使用前需要判断浏览器是否已经支持相应的 API,此时得到如下的代码:

if (navigator.mediaDevices.getUserMedia) {
const constraints = { audio: true };
navigator.mediaDevices.getUserMedia(constraints).then(
stream => {
console.log("授权成功!");
},
() => {
console.error("授权失败!");
}
);
} else {
console.error("浏览器不支持 getUserMedia");
}

其中成功回调里得到的入参 streamMediaStream 对象。

此时运行后可看到浏览器展示出了让用户授权使用麦克风的提示。

向用户索要麦克风使用权限

创建录音实例

将上一步获取到的 MediaStream 传入 Media​Recorder 的构造器创建一个录音器的实例。

var mediaRecorder = new MediaRecorder(stream);

启动录音

通过监听页面中录音按钮的点击来启动录音。

const recordBtn = document.querySelector(".record-btn");
const mediaRecorder = new MediaRecorder(stream);
recordBtn.onclick = () => {
mediaRecorder.start();
console.log("录音中...");
};

MediaRecorder 实例上有个 state 状态,可用来判断录音器当前的活动状态,总共有三种值:

  • inactive:处于休息状态,要么是没开始,要么是开始后已经停止。
  • recording:录音中
  • paused:已经开始,但被暂停了,不是停止也没有被恢复。

所以通过这个状态,我们可以实现再次点击按钮时,结束录音。

recordBtn.onclick = () => {
if (mediaRecorder.state === "recording") {
mediaRecorder.stop();
recordBtn.textContent = "record";
console.log("录音结束");
} else {
mediaRecorder.start();
console.log("录音中...");
recordBtn.textContent = "stop";
}
console.log("录音器状态:", mediaRecorder.state);
};

音频数据的获取

上面按钮处理来自用户的交互,只负责启动或停止录音。音频的数据还是从 Media​Recorder 实例上通过监听其相应的事件来完成的。

当录音开始时,会触发其 MediaRecorder.ondataavailable 事件,从该事件回调的入参为 BlobEvent,从它身上取到 event.data 便是我们需要的音频数据。因为数据是一段一段产生的,所以需要暂存到一个数组中。

const chunks = [];
mediaRecorder.ondataavailable = function(e) {
chunks.push(e.data);
};

目前为止完成的代码应该是这样的:

recorder.js
const recordBtn = document.querySelector(".record-btn");
const playBtn = document.querySelector(".play-btn"); if (navigator.mediaDevices.getUserMedia) {

var chunks = [];

const constraints = { audio: true };

navigator.mediaDevices.getUserMedia(constraints).then(

stream => {

console.log("授权成功!");
  <span class="pl-k">const</span> <span class="pl-c1">mediaRecorder</span> <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">MediaRecorder</span>(stream);

  <span class="pl-smi">recordBtn</span>.<span class="pl-en">onclick</span> <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {
<span class="pl-k">if</span> (<span class="pl-smi">mediaRecorder</span>.<span class="pl-smi">state</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>recording<span class="pl-pds">"</span></span>) {
<span class="pl-smi">mediaRecorder</span>.<span class="pl-c1">stop</span>();
<span class="pl-smi">recordBtn</span>.<span class="pl-smi">textContent</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>record<span class="pl-pds">"</span></span>;
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音结束<span class="pl-pds">"</span></span>);
} <span class="pl-k">else</span> {
<span class="pl-smi">mediaRecorder</span>.<span class="pl-c1">start</span>();
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音中...<span class="pl-pds">"</span></span>);
<span class="pl-smi">recordBtn</span>.<span class="pl-smi">textContent</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>stop<span class="pl-pds">"</span></span>;
}
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音器状态:<span class="pl-pds">"</span></span>, <span class="pl-smi">mediaRecorder</span>.<span class="pl-smi">state</span>);
}; <span class="pl-smi">mediaRecorder</span>.<span class="pl-en">ondataavailable</span> <span class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-smi">e</span>) {
<span class="pl-smi">chunks</span>.<span class="pl-c1">push</span>(<span class="pl-smi">e</span>.<span class="pl-c1">data</span>);
};
},
() <span class="pl-k">=&gt;</span> {
<span class="pl-en">console</span>.<span class="pl-c1">error</span>(<span class="pl-s"><span class="pl-pds">"</span>授权失败!<span class="pl-pds">"</span></span>);
}

);

} else {

console.error("浏览器不支持 getUserMedia");

}

录音状态的查看

录音结束及音频的播放

通过监听 MediaRecorder.onstop 事件,将收集好的音频数据创建成 Blob 对象,然后 通过 URL.createObjectURL 创建成 HTML 中 <audio> 标签可使用的资源链接。

mediaRecorder.onstop = e => {
var blob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });
chunks = [];
var audioURL = window.URL.createObjectURL(blob);
audio.src = audioURL;
};

其中,在使用完收到到的音频数据后,将 chunks 置空方便下次录音时使用。

目前为止完成的代码应该是这样的:

recorder.js
const recordBtn = document.querySelector(".record-btn");
const player = document.querySelector(".audio-player"); if (navigator.mediaDevices.getUserMedia) {

var chunks = [];

const constraints = { audio: true };

navigator.mediaDevices.getUserMedia(constraints).then(

stream => {

console.log("授权成功!");
  <span class="pl-k">const</span> <span class="pl-c1">mediaRecorder</span> <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">MediaRecorder</span>(stream);

  <span class="pl-smi">recordBtn</span>.<span class="pl-en">onclick</span> <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> {
<span class="pl-k">if</span> (<span class="pl-smi">mediaRecorder</span>.<span class="pl-smi">state</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>recording<span class="pl-pds">"</span></span>) {
<span class="pl-smi">mediaRecorder</span>.<span class="pl-c1">stop</span>();
<span class="pl-smi">recordBtn</span>.<span class="pl-smi">textContent</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>record<span class="pl-pds">"</span></span>;
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音结束<span class="pl-pds">"</span></span>);
} <span class="pl-k">else</span> {
<span class="pl-smi">mediaRecorder</span>.<span class="pl-c1">start</span>();
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音中...<span class="pl-pds">"</span></span>);
<span class="pl-smi">recordBtn</span>.<span class="pl-smi">textContent</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>stop<span class="pl-pds">"</span></span>;
}
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>录音器状态:<span class="pl-pds">"</span></span>, <span class="pl-smi">mediaRecorder</span>.<span class="pl-smi">state</span>);
}; <span class="pl-smi">mediaRecorder</span>.<span class="pl-en">ondataavailable</span> <span class="pl-k">=</span> <span class="pl-smi">e</span> <span class="pl-k">=&gt;</span> {
<span class="pl-smi">chunks</span>.<span class="pl-c1">push</span>(<span class="pl-smi">e</span>.<span class="pl-c1">data</span>);
}; <span class="pl-smi">mediaRecorder</span>.<span class="pl-en">onstop</span> <span class="pl-k">=</span> <span class="pl-smi">e</span> <span class="pl-k">=&gt;</span> {
<span class="pl-k">var</span> blob <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">Blob</span>(chunks, { type<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>audio/ogg; codecs=opus<span class="pl-pds">"</span></span> });
chunks <span class="pl-k">=</span> [];
<span class="pl-k">var</span> audioURL <span class="pl-k">=</span> <span class="pl-c1">window</span>.<span class="pl-c1">URL</span>.<span class="pl-en">createObjectURL</span>(blob);
<span class="pl-smi">player</span>.<span class="pl-smi">src</span> <span class="pl-k">=</span> audioURL;
};
},
() <span class="pl-k">=&gt;</span> {
<span class="pl-en">console</span>.<span class="pl-c1">error</span>(<span class="pl-s"><span class="pl-pds">"</span>授权失败!<span class="pl-pds">"</span></span>);
}

);

} else {

console.error("浏览器不支持 getUserMedia");

}

运行

完成上面步骤后,实现了一个简单的录音功能,可通过此地址在线体验。完整的代码可在仓库wayou/audio-recorder 中获取到。

相关资源

JavaScript 实现页面中录音功能的更多相关文章

  1. JavaScript在页面中的执行顺序(理解声明式函数与赋值式函数) 转载

    JavaScript在页面中的执行顺序 https://blog.csdn.net/superhoy/article/details/52946277 2016年10月27日 15:38:52 阅读数 ...

  2. JavaScript在页面中的引用方法

    现在前端开发越来越流行,框架也越来越多,像ExtJs.JQuery.Bootstrap等.虽然入行这么多年,但是感觉自己在前端方面还是存在基础不牢的地方,特别是CSS和JS.因此最近打算重新阅读这方面 ...

  3. javascript dom页面中的location属性

    属性名称 host:设置或返回主机名和当前URL的端口号 hostname:设置或 返回当前的URL的主机名 href:设置或返回完整的url,可用于设置跳转 hash:设置或返回从#号开始的URL锚 ...

  4. javascript html页面中的内容替换

    <script language="javascript"> function ffRed(){  var xsxf = document.getElementById ...

  5. JavaScript中让Html页面中循环播放文字

    JavaScript中让Html页面中循环播放文字 <html> <head> <meta http-equiv="Content-Type" con ...

  6. 使用JavaScript设置、获取父子页面中的值

    一:获取父页面中的值 有二种方法windows.open()和windows.showModalDialog() 1.windos.open(URL,name,reatures,replace) 再父 ...

  7. jQuery基础学习5——JavaScript方法获取页面中的元素

    给网页中的所有<p>元素添加onclick事件 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN& ...

  8. 一行JavaScript代码获取页面中的所有超链接地址

    因为我喜欢收集Web开发类的网址,平时对网址就很敏感. 我总结了一下我收集网址的几个阶段: 1.纯手工阶段,傻傻的阶段. 在这个阶段,主要是收集一些在页面中展现出来的网址,就是说,如果网址出现在HTM ...

  9. HTML页面中JavaScript能获取到的各种屏幕大小信息

    在HTML页面中,通过JavaScript代码访问 window 对象,能够获取到很多表征屏幕大小的信息,下面列举并加以区分. window 对象中的屏幕信息 window.innerheight. ...

随机推荐

  1. J2SE基础:11.异常处理

    1:异常的概念: 异常是程序在执行时发生的事件(异常发生在执行期间). 程序出现错误.打断原本的运行流程. 2:Java中处理异常. 在Java中.异常被封装成一个对象.(属性和方法) 3:异常产生 ...

  2. HTML DOM Table 对象

    Table 对象 Table 对象代表一个 HTML 表格. 在 HTML 文档中 <table> 标签每出现一次,一个 Table 对象就会被创建. Table 对象集合 集合 描述 c ...

  3. 彻底弄懂px,em和rem的区别

    国内的设计大师都喜欢用px,而国外的网站大都喜欢用em和rem,那么三者有什么区别,又各自有什么优劣呢? px特点: 1.IE无法调整那些使用px作为单位的字体大小: 2.国外大部分网站能够调整的原因 ...

  4. 词典(一) 跳转表(Skip table)

    词典,顾名思义,就是通过关键码来查询的结构.二叉搜索树也可以作为词典,不过各种BST,如AVL树.B-树.红黑树.伸展树,结构和操作比较复杂,而且理论上插入和删除都需要O(logn)的复杂度. 在词典 ...

  5. Java多线程系列二——Thread类的方法

    Thread实现Runnable接口并实现了大量实用的方法 public static native void yield(); 此方法释放CPU,但并不释放已获得的锁,其它就绪的线程将可能得到执行机 ...

  6. golang——strconv包常用函数

    1.func ParseBool(str string) (value bool, err error) 返回字符串表示的bool值.它接受1.0.t.f.T.F.true.false.True.Fa ...

  7. Rabbitmq笔记一

    几个基本概念 Producer 生产者,发送消息的一方,图中左侧的client. Consumer 消费者,接收消息的一方,图中后侧的client. Broker 消息中间件的服务节点,一般一个Rab ...

  8. Nginx开启http2访问和gzip网页压缩功能

    准备工作   如果Nginx要开启http2需要满足以下2个条件: nginx >=1.9.5 openSSL >= 1.0.2 所以这里我们首先要检查Nginx的版本如果没有安装要先安装 ...

  9. daily_journal_3 the game of thrones

    昨晚追完了最爱的美剧(the game of thrones),哇,看到结局有点崩溃.果然还是美帝淫民开放,各种乱伦,在七夕收到的万点暴击就祝天下有情人就像剧中一样终是血亲. 昨天算是完成了git的复 ...

  10. 贪心 Codeforces Round #191 (Div. 2) A. Flipping Game

    题目传送门 /* 贪心:暴力贪心水水 */ #include <cstdio> #include <algorithm> #include <cstring> us ...