这里给大家分享我在网上总结出来的一些知识 ,希望对大家有所帮助
从上面可以看到 ,首先从 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 相关的接口
createObjectURL 与 revokeObjectURL 是反义词 ,一个是创建引用 ,另一个是销毁
禁音可通过 track.enabled = false 关闭音轨来实现
本文转载于:
https://juejin.cn/post/7071101341396893732
如果对您有所帮助 ,欢迎您点个关注 ,我会定时更新技术文档 ,大家一起讨论学习 ,一起进步 。
声明:本站所有文章 ,如无特殊说明或标注 ,均为本站原创发布。任何个人或组织 ,在未征得本站同意时 ,禁止复制 、盗用、采集 、发布本站内容到任何网站 、书籍等各类媒体平台 。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 。