- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章react-media-recorder轻松实现一个录音、录像、录屏工具库由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
哈喽,大家好,我是海怪.
最近项目遇到一个要在网页上录音的需求,在一波搜索后,发现了 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> ); }
上面有 开始,暂停,恢复 以及 停止 四个功能,还加加了一个 来查看录音结果.
之后来实现 开始 与 停止:
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 url recorder.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() }
在实现简单功能之后,我们来尝试一下把上面的功能都封装成 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,这里稍微做下总结吧:
这个小工具库的实现就给大家带到这里了,详情可以查看 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 。
原文地址:https://mp.weixin.qq.com/s/NSahiP_-sa0hDhR0r4YcVQ 。
最后此篇关于react-media-recorder轻松实现一个录音、录像、录屏工具库的文章就讲到这里了,如果你想了解更多关于react-media-recorder轻松实现一个录音、录像、录屏工具库的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我尝试将resteasy与自定义对象一起使用,当我创建jar文件时,它与intellij IDE一起工作正常,但失败并出现以下异常 org.jboss.resteasy.core.NoMessageB
我正在寻找一种在easy-close选项为TRUE时基于Shiny模态关闭触发事件的方法(因此,在模态外部单击将其关闭)。由于没有链接到模式的ID,因此我似乎无法捕获此事件。我尝试在“观察”事件中包装
假设我有一些定义如下的类: class Security { Boolean AuthenticateUser(String username, String password); B
正如标题所说,Coq 可以用作模型检查器吗?我可以将模型检查与 Coq 证明混合使用吗?这是常态吗?谷歌谈论“微积分”,有没有人有这方面的经验或类似的经验?是否建议以这种方式使用 Coq,或者我应该寻
是否有一种方法(设置或快捷方式)可以显示输出超过 500 行的查询的总行数 - 即,无需修改首选项中的“结果集页面大小”值?我本质上是在寻找 select count(*) from () t 的输出
我想这样做: System.out.println("안녕하세요!"); 但是当我尝试在 Eclipse 中进行编译时,出现“某些字符无法使用 MacRoman 字符编码进行编码”弹出式错误消息。我正
如果我有一个用这样的字符串初始化的框架 setter CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(at
所以这里没有什么新内容,我只是想得到一些澄清,但似乎在其他帖子中找不到任何澄清。 我正在安静地创建一个新资源,例如: /books (POST) 有一个 body : { title: 'The
我有很多预处理器宏定义,如下所示: #define FOO 1 #define BAR 2 #define BAZ 3 在实际应用中,每个定义对应一个解释器虚拟机中的一条指令。宏的编号也不是连续的,以
使用 SpriteKit 开发 iOS 游戏。我的背景由 map block 组成(本质上是无限 map ,程序生成)。 我们的系统旨在管理 map 的“ block ”,我们只加载玩家附近的 blo
我需要在 Ruby 中拆分一个具有以下格式的字符串: [{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:1
Linq 有一个名为 Take() 的便捷运算符方法,可以返回任何实现 IEnumerable 的元素中给定数量的元素。 jQuery 中是否有类似的东西可以处理数组? 或者,换个方式问:如何在 Ja
每当我使用以下代码在文档中插入图像时, var cursor = DocumentApp.getActiveDocument().getCursor(); var image = cursor
今天看到这样一段代码: if ( not defined($reply_address) or not defined($from_name) or not defined(
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
刚刚了解 mercurial 的 --style和 --template可用于 hg log 的选项和 hg tip我发现它们非常有帮助,但我不知道把我的“样式文件”放在哪里 我有一个“样式文件”,它
是否有一些应用程序可以自动 bundle (并缩小)包含 require('file.js') 调用的 JS 项目?这样它们就会合并并生成一个文件。 具体来说,我正在谈论when.js ,一个带有大量
如何(轻松)获取 Sublime Text 3 中的当前文件路径 我不经常使用 ST 控制台(我只使用它一次来安装包管理器),但我认为这可能是一个好方法: 像某种pwd命令一样获取当前文件路径。 但这
嗨,我正在使用resteasy api,我需要使用Map作为QueryParam。我可以使用列表作为 QueryParam,但是当我尝试传递 Map 时,我收到下面提到的错误。 这是我的服务代码 @G
假设我有一个 C++ 代码(请参阅下面的简单示例)。我想让期刊审稿人轻松安装/运行 所以我认为最简单的方法是将其变形为简化的类 R 包的 tar.gz 文件,以便裁判可以安装它通过简单地调用 inst
我是一名优秀的程序员,十分优秀!