Implemente fácilmente una biblioteca de herramientas de grabación de sonido, grabación de video y grabación de pantalla con JS

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.

69addac35fb7004985392a0ffc038a91.png

Solo recuerda: streamalmacena blobListy conviértela en una vista previa al final blobUrl.

82fb3279d1a768856828c0a05279156f.png

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>

50abdc951b79799ab90bc67ba7927ed0.png

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 getUserMediaobtenga el flujo de entrada de mediaStream, y luego puede abrir video: truepara obtener el flujo de video sincrónicamente.

Luego mediaStreampase a mediaRecorder, a través ondataavailablede para almacenar los blobdatos en el flujo actual.

El último paso es llamar URL.createObjectURLpara 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:

d339d601cbaab156a2fbba240d97284f.png

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.tsxSimplemente 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.createObjectURLAPI para lograr esto. La URL generada se ve así:

blob:http://localhost:3000/e571f5b7-13bd-4c93-bc53-0c84049deb0a

URL.createObjectURLSe 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 getUserMediase , y la grabación de pantalla debe llamar a getDisplayMediaesta interfaz para implementarla.

Para distinguir mejor entre estas dos situaciones, podemos proporcionar a los desarrolladores audioy 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 blobPropertypara 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:

e71296e5fbde7802a173af858feb67d0.png

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.

9e81659871adaac3c47335aa9946b30d.png

El método anterior de incluir la lógica de obtener el flujo de medios en la getMediaStreamfunció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 getUserMediael 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 srcObjectpara obtener una vista previa:

<button onClick={() => previewVideo.current!.srcObject = getMediaStream() || null}>
    预览
</button>
5af9dd6a900af8adf5150b25e1fced08.png

silencio

Finalmente, implementemos la función de silencio, y el principio es igualmente simple. Consiga el audioStreaminterior audioTracky luego enabled = falseconfigú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:

  • getUserMediaSe puede usar para obtener la transmisión desde el micrófono y desde la cámara

  • getDisplayMediase utiliza para obtener los flujos de video y audio de la pantalla

  • La esencia de registrar cosas es stream -> blobList -> blob urlque MediaRecorderpueden ser monitoreadas streampara obtener blobdatos.

  • MediaRecorderTambién proporciona múltiples interfaces relacionadas con el registro, como inicio, finalización, pausa y reanudación.

  • createObjectURLy revokeObjectURLson antónimos, uno crea una referencia y el otro destruye

  • El silenciamiento se puede lograr track.enabled = falsecerrando 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

Supongo que te gusta

Origin blog.csdn.net/qq_36538012/article/details/123887655
Recomendado
Clasificación