这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

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

完整项目代码放在 Github

需求与思路

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

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

只需要记住:把输入 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 关闭音轨来实现

本文转载于:

https://juejin.cn/post/7071101341396893732

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

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

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

    大家好,我是若川.持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列& ...

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

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

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

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

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

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

  5. 录屏手机html5插件,越狱插件:首款能在iOS10上轻松录屏的工具!

    原标题:越狱插件:首款能在iOS10上轻松录屏的工具! 说实话,今天这款插件是应一些朋友的要求才找来的,因为这些天有不少果粉在后台说能不能介绍一款越狱后能用的录屏工具,小智也不能确定这个插件之前有没有 ...

  6. captura录屏发生了一个错误_录屏教程的方法有哪些?学会这两种轻松搞定

    录屏教程的方法有哪些?在日常的工作以及生活当作,录屏教程的需求是经常需要做的.比方说:工作的时候,需要录屏新产品操作教程.生活中需要录屏学习基础教程等等.所以,对于录屏教程能够找到一个好用的方法是非常 ...

  7. camtasia studio2022电脑屏幕录像录屏剪辑

    如今是短视频的时代,很多人将一些影视类的视频剪辑工作当做主业或是副业,运营于各个短视频平台或者公众号上面,比如电影解说类,因此掌握熟练的电影录屏剪辑操作也成为了必备技能之一,那么电影怎么录屏剪辑,电影 ...

  8. Driver.js - 开源无依赖的 web 新手交互引导工具库,功能强大、高度可定制

    可以快速实现新手引导效果,自带动画,体验优秀,而且还能高度定制. 关于 Driver.js Driver.js 是一个可以轻松实现新手指引交互的 javascript 工具库,主要的作用是为刚接触应用 ...

  9. C# 用鼠标框选屏幕一个范围进行录屏

    实现方案基于OMCS+MFile构建,网上有很多的案例,但是很少有用鼠标框选范围的来进行录制的, 主要的录屏方法如下图 , 传入的参数是录制屏幕的矩形大小 接下来主要的就是获取在屏幕中鼠标框选的范围, ...

最新文章

  1. mycat mysql 存储过程_MyCat 学习笔记 第十三篇.数据分片 之 通过HINT执行存储过程...
  2. andorid service 本地服务
  3. python安装requests库pip不是_python pip安装requests时报错,怎么解决?
  4. 什么是软件开发中的 green field 和 brown field 模式 - 绿地开发和棕地开发
  5. java每日小算法(10)
  6. linux man 手册翻译,close (linux man) 翻译
  7. ClassPathResource详解
  8. Spring源码分析
  9. CentOS7.0离线安装RHadoop
  10. 游戏 编程 语言 服务端 客户端 就业
  11. ssr使用mysql数据库_MySQL数据库安装与配置详解
  12. vscode创建工作区及生成html模板
  13. mysql 小于转义_MyBatis中大于和小于号的转义写法
  14. 父爱如山,催泪微电影感动全网
  15. 关于uefi启动的笔记本安装win7,win10,Ubuntu三系统
  16. Android 抛弃原生MediaPlayer 使用音频框架 StarrySky
  17. 一键反编译安卓apk文件
  18. 中民协元宇宙工委承办|2022年世界互联网大会乌镇峰会元宇宙主题咖荟
  19. table数据刷新;v-if刷新el-table表格
  20. Tensorflow五种花卉分类

热门文章

  1. 【69. Sqrt(x)】
  2. zynq 7000 AMP模式 双裸核CPU同时运行
  3. SAP WORKFLOW 相关TCODE
  4. Physics Engine - Car Games [ 物理引擎 - 赛车游戏 ]
  5. 什么是编译器里的前端和后端
  6. 建立适合自己的团队的规范流程
  7. ftp服务器/客户端程序
  8. Windows ISR介绍
  9. PTA 一 水果忍者
  10. xp_CAPTCHA V4.1(瞎跑-白嫖版)安装