prefacio
Hola a todos, soy un monstruo marino.
Recientemente, el proyecto encontró la necesidad de grabar en una página web y después de una ola de búsquedas, se encontró la biblioteca react-media-recorder [1] . Hoy, estudiemos el código fuente de esta biblioteca con usted, de 0 a 1 para realizar una función de grabación React, grabación de video y grabación de pantalla.
El código completo del proyecto se encuentra en Github [2]
necesidades e ideas
En primer lugar, debemos aclarar lo que queremos lograr: grabación de audio, grabación de video y grabación de pantalla .
El principio de este flujo de medios de grabación es realmente muy simple.
Solo recuerda: stream
almacena blobList
y conviértela en una vista previa al final blobUrl
.
funciones básicas
Con las ideas simples anteriores, primero podemos hacer una función simple de grabación y grabación de video.
Aquí primero implementamos la estructura HTML básica:
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>
);
}
Hay 开始
, 暂停
y cuatro恢复
funciones arriba, además de una para ver los resultados de la grabación.停止
<audio>
Después de lograr 开始
con 停止
:
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());
}
Como puede ver en lo anterior, primero getUserMedia
obtenga el flujo de entrada de mediaStream
, y luego puede abrir video: true
para obtener el flujo de video sincrónicamente.
Luego mediaStream
pase a mediaRecorder
, a través ondataavailable
de para almacenar los blob
datos en el flujo actual.
El último paso es llamar URL.createObjectURL
para generar un enlace de vista previa. Esta API es muy útil en el front-end. Por ejemplo, al cargar una imagen, también se puede llamar para lograr una vista previa de la imagen, sin realmente transmitirla al back-end para mostrar la imagen de vista previa.
开始
Después de hacer clic en , puede ver que la página web actual está grabando:
Ahora implemente el resto de 暂停
y 恢复
también:
const pauseRecord = async () => {
mediaRecorder.current?.pause();
}
const resumeRecord = async () => {
mediaRecorder.current?.resume()
}
Manos
Después de implementar funciones simples, intentemos encapsular las funciones anteriores en React Hooks, primero agregue todas estas lógicas en una función y luego regrese a la 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
Simplemente obtenga el valor de retorno en:
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>
);
}
Una vez empaquetado, es hora de agregar más funciones a este Hook.
borrar datos
Al generar la URL del blob, llamamos a la URL.createObjectURL
API para lograr esto. La URL generada se ve así:
blob:http://localhost:3000/e571f5b7-13bd-4c93-bc53-0c84049deb0a
URL.createObjectURL
Se generará una referencia después de cada vez url -> blob
, y dicha referencia también ocupará la memoria de recursos, por lo que podemos proporcionar un método para destruir esta referencia.
const useMediaRecorder = () => {
const [mediaUrl, setMediaUrl] = useState<string>('');
...
return {
...
clearBlobUrl: () => {
if (mediaUrl) {
URL.revokeObjectURL(mediaUrl);
}
setMediaUrl('');
}
}
}
grabación de pantalla
Las grabaciones de audio y video anteriores getUserMedia
se , y la grabación de pantalla debe llamar a getDisplayMedia
esta interfaz para implementarla.
Para distinguir mejor entre estas dos situaciones, podemos proporcionar a los desarrolladores audio
y tresvideo
parámetros que nos digan a qué interfaz se debe llamar para obtener los datos de flujo de entrada correspondientes: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();
}
...
}
Dado que hemos permitido a los usuarios grabar video y sonido, al generar URL, también debemos configurar el correspondiente blobProperty
para generar el tipo de medio correspondiente blobUrl
.
Finalmente, cuando se llama al gancho screen: true
, se puede activar la función de grabación de pantalla:
Nota: Ya sea que se trate de una grabación de video, una grabación de audio o una grabación de pantalla, es la capacidad de llamar al sistema y la página web solo le solicita esta capacidad al navegador, pero la premisa es que el navegador ya tiene el permiso del sistema, por lo que se debe permitir navegar en la configuración del sistema.El dispositivo tiene estos permisos para grabar la pantalla.
El método anterior de incluir la lógica de obtener el flujo de medios en la getMediaStream
función puede usarse fácilmente para obtener permisos de usuario.Si queremos obtener los permisos de cámara, micrófono y grabación de pantalla del usuario cuando el componente se acaba de cargar, podemos usarlo en useEffect
. llámalo :
useEffect(() => {
if (askPermissionOnMount) {
getMediaStream().then();
}
}, [audio, screen, video, getMediaStream, askPermissionOnMount])
avance
La grabación de video solo necesita configurarse en getUserMedia
el momento { video: true }
de realizar la grabación de video. Para que sea más conveniente para los usuarios ver el efecto mientras graban, también podemos devolver la transmisión de video al usuario:
return {
...
getMediaStream: () => mediaStream.current,
getAudioStream: () => audioStream.current
}
Después de obtener estos mediaStream
, puede asignarlo directamente srcObject
para obtener una vista previa:
<button onClick={() => previewVideo.current!.srcObject = getMediaStream() || null}>
预览
</button>
silencio
Finalmente, implementemos la función de silencio, y el principio es igualmente simple. Consiga el audioStream
interior audioTrack
y luego enabled = false
configúrelos .
const toggleMute = (isMute: boolean) => {
mediaStream.current?.getAudioTracks().forEach(track => track.enabled = !isMute);
audioStream.current?.getAudioTracks().forEach(track => track.enabled = !isMute)
setIsMuted(isMute);
}
Se puede usar para deshabilitar y habilitar canales cuando está en uso:
<button onClick={() => toggleMute(!isMuted)}>{isMuted ? '打开声音' : '禁音'}</button>
Resumir
Lo anterior utiliza la API de WebRTC para implementar simplemente una herramienta de grabación, video y grabación de pantalla Hook. Aquí hay un breve resumen:
getUserMedia
Se puede usar para obtener la transmisión desde el micrófono y desde la cámaragetDisplayMedia
se utiliza para obtener los flujos de video y audio de la pantallaLa esencia de registrar cosas es
stream -> blobList -> blob url
queMediaRecorder
pueden ser monitoreadasstream
para obtenerblob
datos.MediaRecorder
También proporciona múltiples interfaces relacionadas con el registro, como inicio, finalización, pausa y reanudación.createObjectURL
yrevokeObjectURL
son antónimos, uno crea una referencia y el otro destruyeEl silenciamiento se puede lograr
track.enabled = false
cerrando pista de audio
La implementación de esta pequeña biblioteca de herramientas se presenta aquí para usted. Para obtener detalles, puede consultar el código fuente de react-media-recorder [3]. Esta biblioteca es muy simple y fácil de entender. Es muy adecuada para estudiantes que están empezando a leer el código fuente!
Referencias
[1]
React Media Recorder: https://github.com/0x006F/react-media-recorder
[2]Código del proyecto: https://github.com/haixiangyan/react-media-recorder
[3]React Media Recorder: https://github.com/0x006F/react-media-recorder