大家好,我是若川。持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列

前言

最近项目遇到一个要在网页上录音的需求,在一波搜索后,发现了 react-media-recorder[1] 这个库。今天就跟大家一起研究一下这个库的源码吧,从 0 到 1 来实现一个 React 的录音、录像和录屏功能。

完整项目代码放在 Github[2]

需求与思路

首先要明确我们要完成的事:录音录像录屏

这种录制媒体流的原理其实很简单。

只需要记住:把输入 stream 存放在 blobList,最后转成预览 blobUrl

基础功能

有了上面的简单思路后,我们可以先做一个简单的录音与录像功能。

这里先把基础的 HTML 结构实现了:

const App = () => {const [audioUrl, setAudioUrl] = useState<string>('');const startRecord = async () => {}const stopRecord = async () => {}return (<div><h1>react 录音</h1><audio src={audioUrl} controls /><button onClick={startRecord}>开始</button><button>暂停</button><button>恢复</button><button onClick={stopRecord}>停止</button></div>);
}

上面有 开始暂停恢复 以及 停止 四个功能,还加加了一个 <audio> 来查看录音结果。

之后来实现 开始停止

const medisStream = useRef<MediaStream>();
const recorder = useRef<MediaRecorder>();
const mediaBlobs = useRef<Blob[]>([]);// 开始
const startRecord = async () => {// 读取输入流medisStream.current = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });// 生成 MediaRecorder 对象recorder.current = new MediaRecorder(medisStream.current);// 将 stream 转成 blob 来存放recorder.current.ondataavailable = (blobEvent) => {mediaBlobs.current.push(blobEvent.data);}// 停止时生成预览的 blob urlrecorder.current.onstop = () => {const blob = new Blob(mediaBlobs.current, { type: 'audio/wav' })const mediaUrl = URL.createObjectURL(blob);setAudioUrl(mediaUrl);}recorder.current?.start();
}// 结束,不仅让 MediaRecorder 停止,还要让所有音轨停止
const stopRecord = async () => {recorder.current?.stop()medisStream.current?.getTracks().forEach((track) => track.stop());
}

从上面可以看到,首先从 getUserMedia 获取输入流 mediaStream,以后还可以打开 video: true 来同步获取视频流。

然后将 mediaStream 传给 mediaRecorder,通过 ondataavailable 来存放当前流中的 blob 数据。

最后一步,调用 URL.createObjectURL 来生成预览链接,这个 API 在前端非常有用,比如上传图片时也可以调用它来实现图片预览,而不需要真的传到后端才展示预览图片。

在点击 开始 后,就可以看到当前网页正在录音啦:

现在把剩下的 暂停 以及 恢复 也实现了:

const pauseRecord = async () => {mediaRecorder.current?.pause();
}const resumeRecord = async () => {mediaRecorder.current?.resume()
}

Hooks

在实现简单功能之后,我们来尝试一下把上面的功能都封装成 React Hook,首先把这些逻辑都扔在一个函数中,然后返回 API:

const useMediaRecorder = () => {const [mediaUrl, setMediaUrl] = useState<string>('');const mediaStream = useRef<MediaStream>();const mediaRecorder = useRef<MediaRecorder>();const mediaBlobs = useRef<Blob[]>([]);const startRecord = async () => {mediaStream.current = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });mediaRecorder.current = new MediaRecorder(mediaStream.current);mediaRecorder.current.ondataavailable = (blobEvent) => {mediaBlobs.current.push(blobEvent.data);}mediaRecorder.current.onstop = () => {const blob = new Blob(mediaBlobs.current, { type: 'audio/wav' })const url = URL.createObjectURL(blob);setMediaUrl(url);}mediaRecorder.current?.start();}const pauseRecord = async () => {mediaRecorder.current?.pause();}const resumeRecord = async () => {mediaRecorder.current?.resume()}const stopRecord = async () => {mediaRecorder.current?.stop()mediaStream.current?.getTracks().forEach((track) => track.stop());mediaBlobs.current = [];}return {mediaUrl,startRecord,pauseRecord,resumeRecord,stopRecord,}
}

App.tsx 里拿到返回值就可以了:

const App = () => {const { mediaUrl, startRecord, resumeRecord, pauseRecord, stopRecord } = useMediaRecorder();return (<div><h1>react 录音</h1><audio src={mediaUrl} controls /><button onClick={startRecord}>开始</button><button onClick={pauseRecord}>暂停</button><button onClick={resumeRecord}>恢复</button><button onClick={stopRecord}>停止</button></div>);
}

封装好之后,现在就可以在这个 Hook 里添加更多的功能了。

清除数据

在生成 blob url 的时候我们调用了 URL.createObjectURL API 来实现,生成后的 url 长这样:

blob:http://localhost:3000/e571f5b7-13bd-4c93-bc53-0c84049deb0a

每次 URL.createObjectURL 后都会生成一个 url -> blob 的引用,这样的引用也是会占用资源内存的,所以我们可以提供一个方法来销毁这个引用。

const useMediaRecorder = () => {const [mediaUrl, setMediaUrl] = useState<string>('');...return {...clearBlobUrl: () => {if (mediaUrl) {URL.revokeObjectURL(mediaUrl);}setMediaUrl('');}}
}

录屏

上面录音和录像使用 getUserMedia 来实现,而 录屏则需要调用 getDisplayMedia 这个接口来实现。

为了能更好地区分这两种情况,可以给开发者提供 audio, video 以及 screen 三个参数,告诉我们应该调哪个接口去获取对应的输入流数据:

const useMediaRecorder = (params: Params) => {const {audio = true,video = false,screen = false,askPermissionOnMount = false,} = params;const [mediaUrl, setMediaUrl] = useState<string>('');const mediaStream = useRef<MediaStream>();const audioStream = useRef<MediaStream>();const mediaRecorder = useRef<MediaRecorder>();const mediaBlobs = useRef<Blob[]>([]);const getMediaStream = useCallback(async () => {if (screen) {// 录屏接口mediaStream.current = await navigator.mediaDevices.getDisplayMedia({ video: true });mediaStream.current?.getTracks()[0].addEventListener('ended', () => {stopRecord()})if (audio) {// 添加音频输入流audioStream.current = await navigator.mediaDevices.getUserMedia({ audio: true })audioStream.current?.getAudioTracks().forEach(audioTrack => mediaStream.current?.addTrack(audioTrack));}} else {// 普通的录像、录音流mediaStream.current = await navigator.mediaDevices.getUserMedia(({ video, audio }))}}, [screen, video, audio])// 开始录const startRecord = async () => {// 获取流await getMediaStream();mediaRecorder.current = new MediaRecorder(mediaStream.current!);mediaRecorder.current.ondataavailable = (blobEvent) => {mediaBlobs.current.push(blobEvent.data);}mediaRecorder.current.onstop = () => {const [chunk] = mediaBlobs.current;const blobProperty: BlobPropertyBag = Object.assign({ type: chunk.type },video ? { type: 'video/mp4' } : { type: 'audio/wav' });const blob = new Blob(mediaBlobs.current, blobProperty)const url = URL.createObjectURL(blob);setMediaUrl(url);onStop(url, mediaBlobs.current);}mediaRecorder.current?.start();}...
}

由于我们已经允许用户来录视频以及声音,所以在生成 URL 时,也要设置对应的 blobProperty 来生成对应媒体类型的 blobUrl

最后在调用 hook 时传入 screen: true,可以开启录屏功能:

注意:无论是录像、录音、录屏都是要调用系统的能力,而网页只是问浏览器要这个能力,但这样的前提是浏览器已经拥有了系统权限了,所以必须在系统设置里允许浏览器有这些权限才能录屏。

上面把获取媒体流的逻辑都扔在 getMediaStream 函数里的做法,能很方便地用它来获取用户权限,假如我们想在刚加载这个组件时就获取用户摄像头、麦克风、录屏权限,就可以在 useEffect 里调用它

useEffect(() => {if (askPermissionOnMount) {getMediaStream().then();}
}, [audio, screen, video, getMediaStream, askPermissionOnMount])

预览

录像只需要在 getUserMedia 的时候设置 { video: true } 就可以实现录像了。为了能更方便用户在使用时能边录边看效果,我们可以把视频流也返回给用户:

return {...getMediaStream: () => mediaStream.current,getAudioStream: () => audioStream.current}

用户在拿到这些 mediaStream 之后就可以直接赋值到 srcObject 上来进行预览了:

<button onClick={() => previewVideo.current!.srcObject = getMediaStream() || null}>预览
</button>

禁音

最后,我们来实现禁音功能,原理也同样简单。拿到 audioStream 里面的 audioTrack,再将它们设置 enabled = false 就可以了。

const toggleMute = (isMute: boolean) => {mediaStream.current?.getAudioTracks().forEach(track => track.enabled = !isMute);audioStream.current?.getAudioTracks().forEach(track => track.enabled = !isMute)setIsMuted(isMute);
}

使用时可以用它来禁用和开启声道:

<button onClick={() => toggleMute(!isMuted)}>{isMuted ? '打开声音' : '禁音'}</button>

总结

上面用 WebRTC 的 API 简单地实现了一个录音、录像、录屏工具 Hook,这里稍微做下总结吧:

  • getUserMedia 可用于获取麦克风以及摄像头的流

  • getDisplayMedia 则用于获取屏幕的视频、音频流

  • 录东西的本质是 stream -> blobList -> blob url,其中 MediaRecorder 可监听 stream 从而获取 blob 数据

  • MediaRecorder 还提供了开始、结束、暂停、恢复等多个与 Record 相关的接口

  • createObjectURLrevokeObjectURL 是反义词,一个是创建引用,另一个是销毁

  • 禁音可通过 track.enabled = false 关闭音轨来实现

这个小工具库的实现就给大家带到这里了,详情可以查看 react-media-recorder[3] 这个库的源码,非常简洁易懂,很适合入门看源码的同学!

如果你也喜欢我的文章,可以点一波关注,或者一键三连再走,比心 ❤️

参考资料

[1]

react-media-recorder: https://github.com/0x006F/react-media-recorder

[2]

项目代码: https://github.com/haixiangyan/react-media-recorder

[3]

react-media-recorder: https://github.com/0x006F/react-media-recorder

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

用JS轻松实现一个录音、录像、录屏工具库相关推荐

  1. 记录--用JS轻松实现一个录音、录像、录屏的工具库

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 最近项目遇到一个要在网页上录音的需求,在一波搜索后,发现了 react-media-recorder 这个库.今天就跟大家一起研究一 ...

  2. 用JS轻松实现一个录音、录像、录屏的工具库

    前言 哈喽,大家好,我是海怪. 最近项目遇到一个要在网页上录音的需求,在一波搜索后,发现了 react-media-recorder 这个库.今天就跟大家一起研究一下这个库的源码吧,从 0 到 1 来 ...

  3. 意外发现ppt中隐藏了一个免费的录屏工具!!!

    有时候需要录制一些教学视频需要用到录屏软件,网上找很多都是要钱的,破解了的又会自带各种各样的广告,什么一刀999啥的广告你下了就每天都弹出来. 终于我在ppt中发现了一个免费可以使用的录屏软件,还可以 ...

  4. 6种电脑录屏工具,免费在线,桌面端Windows和Mac均适用

    如果你想做一个教程类博主,不管是游戏类的,科技类的,还是语言类的,你首先需要一个不错的电脑录屏工具.自自媒体大流行开始以来,对电脑屏幕录制的需求有不断增长.来自不同领域的专业人士,例如教育领域的专业人 ...

  5. h5打开麦克风权限录音_MAC录屏没有声音?如何在苹果电脑MACBOOK上录音录屏

    文章目录 1 MAC录屏没有声音? 如何在苹果电脑Macbook上录音录屏 2 Mac录屏软件 3 Mac录制系统声音 3.1 1- 下载SoundFlower插件 3.2 2- 安装插件 3.3 3 ...

  6. android4.2屏幕录像,android——使用自带录屏工具进行屏幕录像

    在做开源项目的时候,想传一个gif效果图上去.但是,要有连贯的动画效果.所以,就想到先录制视频,然后视频转gif.但是,用第三录屏软件总是不完美. 那么,怎么办呢? android4.4 提供了自带录 ...

  7. windows录屏html文件,录音、录屏、共享屏幕怎么玩?

    为什么要录屏.录音? 有的时候产品.开发同学不在现场,用研需要一些证据给他们看,还有就是帮助我们自己回忆整理.但很多时候产品和设计都在现场,我们当时就会把问题记录下来,也可以不录屏,直接录音就好了. ...

  8. 屏幕录像工具哪个好?这3款录屏工具就很不错!

    案例:有没有好用的录屏工具分享? [每次对电脑屏幕进行录制都要花费我很多时间,录制的内容质量还差,真是事倍功半.听说使用好的录屏工具可以提高录屏的效率,那录屏软件哪款好用?在线蹲一款!] 在现代科技时 ...

  9. Python番外篇:教你如何编写一个GIF录屏工具

    hello,大家好,我是wangzirui32,今天我们来学习如何编写一个GIF录屏工具,开始学习吧! 1. 项目准备 我们需要PIL库对屏幕进行截取,使用imageio对截取的图像进行拼接,合成为G ...

最新文章

  1. 推荐两款简单好用的图片放大jquery插件
  2. 云网融合 — 基于 SD-DCI 的云间网络融合
  3. how mang libraries do we have: 139
  4. unity3d 随机生成地形之随机山脉
  5. 统一建模语言UML轻松入门(3)――静态建模:类和对象
  6. 使用validate.js实现表单数据验证
  7. android sdk是灰的,Android Studio 2.3 sdk管理器标签灰显
  8. VLfeat win10 vs2015 编译
  9. VirtualBox 安装 Ubuntu 14.10 花屏 解决方案
  10. backup archivelog delete input 与delete all input 区别
  11. [Erlang 0127] Term sharing in Erlang/OTP 上篇
  12. 感谢宝贝: 带给我别样人生
  13. 西南科技大学城市学院计算机专业录取分数线,西南科技大学城市学院2020年录取分数线(附2017-2020年分数线)...
  14. “那个人样子好怪。”“我也看到了,他好像一条狗。”
  15. android win7 共享网络打印机,win7设置局域网共享打印机
  16. 基础选择器之id选择器
  17. 怎么获取淘宝商品详情
  18. 哄睡宝宝的有限状态机
  19. 关于uboot的简介——uboot常用的命令
  20. 20行Python代码爬取2W多条音频文件素材【内附源码+详细解析】新媒体创作必备

热门文章

  1. python 元组和列表区别_Python干货整理:一分钟了解元组与列表使用与区别
  2. ibm服务器和微软,微软与IBM不得不说的事情
  3. bean.xml配置数据源和读取配置文件配置数据源
  4. 微信支付配置参数:支付授权目录、回调支付URL
  5. continue 的用户及实例
  6. Unity GeometryShader(从一个线框渲染的例子开始)
  7. ReflectionClass与Closure
  8. 浅析SQL Server 2005中的主动式通知机制
  9. 再读新疆系列(六)——吹拂“卡拉库里湖”的风
  10. VSCode从下载到配置Ubuntu系统