文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-02 06:07

关注

最近项目遇到一个要在网页上录音的需求,在一波搜索后,发现了 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()
}

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,这里稍微做下总结吧:

这个小工具库的实现就给大家带到这里了,详情可以查看 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

来源:写代码的海怪内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯