WebRTC es muy popular para acceder a las cámaras y micrófonos de los dispositivos y transmitir los medios de video o audio en el navegador. Pero en muchos casos, es posible que necesitemos grabar la transmisión para uso futuro o para los usuarios (por ejemplo, si el usuario desea descargar la transmisión, etc.). En ese caso, podemos usar la API MediaRecorder para grabar la transmisión de medios.
En este artículo, crearemos un sitio web básico de Video and Audio Recorder utilizando JavaScript puro y su API MediaRecorder.
Descripción del proyecto: El sitio web que estamos construyendo tendrá-
- Una opción de selección para permitir que los usuarios elijan qué tipo de medio (audio o video con audio) grabar.
- Si el usuario elige grabar un video, el navegador le pedirá permiso para acceder a la cámara y al micrófono del dispositivo y, si el usuario lo permite, entonces:
- Un elemento de video mostrará el flujo de medios de la cámara
- El botón «Iniciar grabación» iniciará la grabación
- El botón «Detener grabación» detendrá la grabación.
- Cuando se completa la grabación, se muestra un nuevo elemento de video que contiene los medios grabados.
- Un enlace para que los usuarios descarguen el video grabado.
- Si el usuario elige grabar solo audio, el navegador le pedirá permiso para acceder al micrófono y, si el usuario lo permite, entonces:
- El botón «Iniciar grabación» iniciará la grabación
- El botón «Detener grabación» detendrá la grabación
- Cuando finaliza la grabación, se muestra un nuevo elemento de audio que contiene el audio grabado.
- Se proporciona un enlace para que los usuarios descarguen el audio grabado.
Entonces, primero configuremos nuestra página HTML simple:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content= "width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="index.css"> <title>Video & Audio Recorder</title> </head> <body> <h1> Video & Audio Recorder </h1> <label for="media"> Select what you want to record: </label> <select id="media"> <option value="choose-an-option"> Choose an option </option> <option value="vid">Video</option> <option value="aud">Audio</option> </select> <div class="display-none" id="vid-recorder"> <h3>Record Video </h3> <video autoplay id="web-cam-container" style="background-color: black;"> Your browser doesn't support the video tag </video> <div class="recording" id="vid-record-status"> Click the "Start video Recording" button to start recording </div> <!-- This button will start the video recording --> <button type="button" id="start-vid-recording" onclick="startRecording(this, document.getElementById('stop-vid-recording'))"> Start video recording </button> <!-- This button will stop the video recording --> <button type="button" id="stop-vid-recording" disabled onclick="stopRecording(this, document.getElementById('start-vid-recording'))"> Stop video recording </button> <!--The video element will be created using JavaScript and contains recorded video--> <!-- <video id="recorded-video" controls> Your browser doesn't support the video tag </video> --> <!-- The below link will let the users download the recorded video --> <!-- <a href="" > Download it! </a> --> </div> <div class="display-none" id="aud-recorder"> <h3> Record Audio</h3> <div class="recording" id="aud-record-status"> Click the "Start Recording" button to start recording </div> <button type="button" id="start-aud-recording" onclick="startRecording(this, document.getElementById('stop-aud-recording'))"> Start recording </button> <button type="button" id="stop-aud-recording" disabled onclick="stopRecording(this, document.getElementById('start-aud-recording'))"> Stop recording </button> <!-- The audio element will contain the recorded audio and will be created using Javascript --> <!-- <audio id="recorded-audio" controls></audio> --> <!-- The below link will let the users download the recorded audio --> <!-- <a href="" > Download it! </a> --> </div> <script src="index.js"></script> </body> </html>
Producción:
Si revisa el index.html cuidadosamente, verá que las etiquetas de video y audio no tienen ninguna fuente. Agregaremos las fuentes más tarde usando JavaScript. Por ahora, tenemos una opción de selección que le permite al usuario seleccionar el tipo de medio que desea grabar. El primer elemento de video dentro del elemento div «vid-recorder» contendrá la transmisión de la cámara web y el elemento de video en los comentarios contendrá el video grabado. Tenga en cuenta que solo el último elemento de video tiene el atributo «controles» , ya que el primer elemento de video contendrá la transmisión y no necesitará ningún control.
Dentro del div «aud-recorder» , tenemos dos botones para iniciar y detener la grabación, el elemento de audio comentado contendrá el audio grabado.
Ahora, agreguemos algo de CSS a la página HTML:
index.css
body { text-align: center; color: green; font-size: 1.2em; } .display-none { display: none; } .recording { color: red; background-color: rgb(241 211 211); padding: 5px; margin: 6px auto; width: fit-content; } video { background-color: black; display: block; margin: 6px auto; width: 420px; height: 240px; } audio { display: block; margin: 6px auto; } a { color: green; }
Producción:
Por ahora, hemos agregado la clase «display-none» a los divs «vid-recorder» y «aud-recorder» . Como queremos mostrar la grabadora correcta según la elección del usuario.
Ahora, implementemos la lógica para mostrar solo la grabadora seleccionada por el usuario mediante JavaScript:
index.js
const mediaSelector = document.getElementById("media"); let selectedMedia = null; // Handler function to handle the "change" event // when the user selects some option mediaSelector.addEventListener("change", (e) => { selectedMedia = e.target.value; document.getElementById( `${selectedMedia}-recorder`).style.display = "block"; document.getElementById( `${otherRecorder(selectedMedia)}-recorder`) .style.display = "none"; }); function otherRecorder(selectedMedia) { return selectedMedia === "vid" ? "aud" : "vid"; }
Salida: cuando el usuario selecciona «video», se muestra la siguiente grabadora de video:
De manera similar, cuando el usuario selecciona la opción «audio» , se muestra la grabadora de audio:
El código anterior muestra solo la grabadora seleccionada por el usuario, es decir, audio o video. Hemos agregado un detector de eventos de «cambio» al elemento mediaSelector, cuando el valor del elemento de selección cambia, emite un evento de «cambio» y el evento es manejado por la función de devolución de llamada dada. La función de devolución de llamada cambia la propiedad de «visualización» de CSS de la grabadora multimedia seleccionada a «bloquear» y otra grabadora multimedia a «ninguna» .
Acceso a la cámara web y al micrófono: la API getUserMedia de WebRTC le permite acceder a la cámara y al micrófono del dispositivo. El método getUserMedia() devuelve una Promesa que se resuelve en un MediaStream que contiene los contenidos multimedia (un flujo de pistas multimedia) según las especificaciones dadas. El método getUserMedia() toma un objeto MediaStreamConstraints como el parámetro que define todas las restricciones que debe cumplir el flujo de medios resultante.
const mediaStreamConstraints = { audio: true, video: true }; // The above MediaStreamConstraints object // specifies that the resulting media must have // both the video and audio media content or tracks. // The mediaStreamConstraints object is passed to // the getUserMedia method navigator.mediaDevices.getUserMedia( MediaStreamConstraints ) .then( resultingMediaStream => { // Code to use the received media stream });
Cuando se invoca el método getUserMedia, el navegador solicita al usuario permiso para usar la cámara y el micrófono del dispositivo. Si el usuario lo permite, la promesa devuelta por getUserMedia se resuelve en el flujo de medios resultante; de lo contrario, genera una excepción NotAllowedError . En el código anterior, el flujo de medios recibido contiene datos de medios de video y audio.
Por lo tanto, agregue las siguientes líneas de código al archivo index.js:
index.js
const mediaSelector = document.getElementById("media"); // Added code const webCamContainer = document .getElementById('web-cam-container'); let selectedMedia = null; /* Previous code ... Added code */ const audioMediaConstraints = { audio: true, video: false }; const videoMediaConstraints = { // or you can set audio to false // to record only video audio: true, video: true }; function startRecording(thisButton, otherButton) { navigator.mediaDevices.getUserMedia( selectedMedia === "vid" ? videoMediaConstraints : audioMediaConstraints) .then(mediaStream => { // Use the mediaStream in // your application // Make the mediaStream global window.mediaStream = mediaStream; if (selectedMedia === 'vid') { // Remember to use the "srcObject" // attribute since the "src" attribute // doesn't support media stream as a value webCamContainer.srcObject = mediaStream; } document.getElementById( `${selectedMedia}-record-status`) .innerText = "Recording"; thisButton.disabled = true; otherButton.disabled = false; }); } function stopRecording(thisButton, otherButton) { // Stop all the tracks in the received // media stream i.e. close the camera // and microphone window.mediaStream.getTracks().forEach(track => { track.stop(); }); document.getElementById( `${selectedMedia}-record-status`) .innerText = "Recording done!"; thisButton.disabled = true; otherButton.disabled = false; }
La función startRecording invoca el método navigator.mediaDevices.getUserMedia() para acceder a la cámara y el micrófono del dispositivo, desactiva el botón «iniciar grabación» y habilita el botón «detener grabación» . Mientras que la función stopRecording cierra la cámara y el micrófono llamando al método «stop()» de cada pista multimedia utilizada por el flujo multimedia, desactiva el botón «detener grabación» y habilita el botón «iniciar grabación» .
Implementación de la grabadora: hasta ahora, solo hemos accedido a la cámara web y al micrófono, pero no hemos hecho nada para grabar los medios.
Para grabar un flujo de medios, primero debemos crear una instancia de MediaRecorder (una interfaz para grabar flujos de medios) usando el constructor de MediaRecorder.
El constructor MediaRecorder toma dos parámetros:
- corriente: Una corriente es como un flujo de datos (datos de cualquier tipo). Aquí, en este artículo, usaremos MediaStream, que es básicamente un flujo de datos o contenido multimedia (video o audio o ambos).
- Opciones (opcional): un objeto que contiene algunas especificaciones sobre la grabación. Puede configurar el tipo MIME de los medios grabados, la tasa de bits de audio, la tasa de bits de video, etc. El tipo MIME es un estándar que representa el formato del archivo multimedia grabado (por ejemplo, los dos tipos MIME: «audio/ webm”, “video/mp4” indican un archivo de audio webm y un archivo de video mp4 respectivamente).
Sintaxis:
const mediaRecorder = new MediaRecorder( stream, { mimeType: "audio/webm" });
La línea de código anterior crea una nueva instancia de MediaRecorder que graba la transmisión dada y la almacena como un archivo de audio WebM.
Entonces, modifique su archivo index.js:
index.js file
/* Previous code ... */ function startRecording(thisButton, otherButton) { navigator.mediaDevices.getUserMedia( selectedMedia === "vid" ? videoMediaConstraints : audioMediaConstraints) .then(mediaStream => { /* New code */ // Create a new MediaRecorder // instance that records the // received mediaStream const mediaRecorder = new MediaRecorder(mediaStream); // Make the mediaStream global window.mediaStream = mediaStream; // Make the mediaRecorder global // New line of code window.mediaRecorder = mediaRecorder; if (selectedMedia === 'vid') { // Remember to use the srcObject // attribute since the src attribute // doesn't support media stream as a value webCamContainer.srcObject = mediaStream; } document.getElementById( `${selectedMedia}-record-status`) .innerText = "Recording"; thisButton.disabled = true; otherButton.disabled = false; }); } /* Remaining code ...*/
Cuando se invoca la función startRecording() , crea una instancia de MediaRecorder para grabar el mediaStream recibido. Ahora, necesitamos usar la instancia de MediaRecorder creada. MediaRecorder proporciona algunos métodos útiles que podemos usar aquí:
- start(): cuando se invoca este método, la instancia de MediaRecorder comienza a grabar el flujo de medios dado. Opcionalmente, toma «intervalo de tiempo» como un argumento que se especifica dará como resultado la grabación de los medios dados en pequeños fragmentos separados de la duración de ese intervalo de tiempo.
- pausa(): cuando se invoca, pausa la grabación
- resume(): cuando se invoca después de invocar el método de pausa() , reanuda la grabación.
- stop(): cuando se invoca, detiene la grabación y activa un evento de «datos disponibles» que contiene el blob final de los datos guardados.
- requestData() : cuando se invoca, solicita un Blob que contiene los datos guardados hasta ahora.
Del mismo modo, MediaRecorder también proporciona algunos controladores de eventos útiles:
- ondatadisponible: Controlador de eventos para el evento «datosdisponibles» . Cada vez que se graban los milisegundos del intervalo de tiempo (si se especifica) de los datos multimedia o cuando se realiza la grabación (si no se especifica el intervalo de tiempo), MediaRecorder emite un evento de «datos disponibles» con los datos de Blob grabados. Estos datos se pueden obtener de la propiedad «datos» del «evento» :
mediaRecorder.ondataavailable = ( event ) => { const recordedData = event.data; }
- onstop: Controlador de eventos para el evento «detener» emitido por MediaRecorder. Este evento se emite cuando se llama al método MediaRecorder.stop() o cuando se detiene el MediaStream correspondiente.
- onerror: Controlador para el evento de «error» que se emite cada vez que ocurre un error al usar MediaRecorder. La propiedad «error» del evento contiene los detalles del error:
mediaRecorder.onerror = ( event ) => { console.log(event.error); }
- onstart : Controlador para el evento de «inicio» que se emite cuando MediaRecorder comienza a grabar.
- onpause: Manejador para el evento de “pausa”. Este evento se emite cuando la grabación está en pausa.
- onresume : Manejador para el evento «resume». Este evento se emite cuando se reanuda la grabación después de haber estado en pausa.
Ahora, necesitamos usar algunos de estos métodos y controladores de eventos para que nuestro proyecto funcione.
index.js
const mediaSelector = document.getElementById("media"); const webCamContainer = document.getElementById("web-cam-container"); let selectedMedia = null; // This array stores the recorded media data let chunks = []; // Handler function to handle the "change" event // when the user selects some option mediaSelector.addEventListener("change", (e) => { // Takes the current value of the mediaSeletor selectedMedia = e.target.value; document.getElementById( `${selectedMedia}-recorder`) .style.display = "block"; document.getElementById( `${otherRecorderContainer( selectedMedia)}-recorder`) .style.display = "none"; }); function otherRecorderContainer( selectedMedia) { return selectedMedia === "vid" ? "aud" : "vid"; } // This constraints object tells // the browser to include only // the audio Media Track const audioMediaConstraints = { audio: true, video: false, }; // This constraints object tells // the browser to include // both the audio and video // Media Tracks const videoMediaConstraints = { // or you can set audio to // false to record // only video audio: true, video: true, }; // When the user clicks the "Start // Recording" button this function // gets invoked function startRecording( thisButton, otherButton) { // Access the camera and microphone navigator.mediaDevices.getUserMedia( selectedMedia === "vid" ? videoMediaConstraints : audioMediaConstraints) .then((mediaStream) => { // Create a new MediaRecorder instance const mediaRecorder = new MediaRecorder(mediaStream); //Make the mediaStream global window.mediaStream = mediaStream; //Make the mediaRecorder global window.mediaRecorder = mediaRecorder; mediaRecorder.start(); // Whenever (here when the recorder // stops recording) data is available // the MediaRecorder emits a "dataavailable" // event with the recorded media data. mediaRecorder.ondataavailable = (e) => { // Push the recorded media data to // the chunks array chunks.push(e.data); }; // When the MediaRecorder stops // recording, it emits "stop" // event mediaRecorder.onstop = () => { /* A Blob is a File like object. In fact, the File interface is based on Blob. File inherits the Blob interface and expands it to support the files on the user's systemThe Blob constructor takes the chunk of media data as the first parameter and constructs a Blob of the type given as the second parameter*/ const blob = new Blob( chunks, { type: selectedMedia === "vid" ? "video/mp4" : "audio/mpeg" }); chunks = []; // Create a video or audio element // that stores the recorded media const recordedMedia = document.createElement( selectedMedia === "vid" ? "video" : "audio"); recordedMedia.controls = true; // You can not directly set the blob as // the source of the video or audio element // Instead, you need to create a URL for blob // using URL.createObjectURL() method. const recordedMediaURL = URL.createObjectURL(blob); // Now you can use the created URL as the // source of the video or audio element recordedMedia.src = recordedMediaURL; // Create a download button that lets the // user download the recorded media const downloadButton = document.createElement("a"); // Set the download attribute to true so that // when the user clicks the link the recorded // media is automatically gets downloaded. downloadButton.download = "Recorded-Media"; downloadButton.href = recordedMediaURL; downloadButton.innerText = "Download it!"; downloadButton.onclick = () => { /* After download revoke the created URL using URL.revokeObjectURL() method to avoid possible memory leak. Though, the browser automatically revokes the created URL when the document is unloaded, but still it is good to revoke the created URLs */ URL.revokeObjectURL(recordedMedia); }; document.getElementById( `${selectedMedia}-recorder`).append( recordedMedia, downloadButton); }; if (selectedMedia === "vid") { // Remember to use the srcObject // attribute since the src attribute // doesn't support media stream as a value webCamContainer.srcObject = mediaStream; } document.getElementById( `${selectedMedia}-record-status`) .innerText = "Recording"; thisButton.disabled = true; otherButton.disabled = false; }); } function stopRecording(thisButton, otherButton) { // Stop the recording window.mediaRecorder.stop(); // Stop all the tracks in the // received media stream window.mediaStream.getTracks() .forEach((track) => { track.stop(); }); document.getElementById( `${selectedMedia}-record-status`) .innerText = "Recording done!"; thisButton.disabled = true; otherButton.disabled = false; }
Producción:
Supongamos que el usuario selecciona la grabadora de audio:
Ahora, si el usuario hace clic en el botón Iniciar grabación, entonces:
Y cuando se hace clic en el botón «detener grabación» :
Muestra el audio grabado y proporciona un enlace para descargar el audio grabado.
Entonces, ¿qué hace la función startRecording()?
- Invoca el método navigator.mediaDevices.getUserMedia() y accede a la cámara o al micrófono del dispositivo o a ambos. Este método devuelve una Promesa que se resuelve en un MediaStream.
- Después de recibir el MediaStream, crea una instancia de MediaRecorder que puede grabar el MediaStream dado, hace que tanto el MediaStream como el MediaRecorder sean globales para que podamos usarlos fuera de la función startRecording:
window.mediaStream = mediaStream; window.mediaRecorder = mediaRecorder;
- Inicia la grabación del MediaStream dado llamando al método MediaRecorder.start().
mediaRecorder.start();
- Define los controladores de eventos para el MediaRecorder creado. La función del controlador de eventos «datos disponibles» envía los datos grabados a la array de fragmentos (array de blob de datos de medios grabados). La función de controlador de eventos «detener» .
- Crea un nuevo Blob a partir de la array de fragmentos mediante el constructor Blob().
- Reinicializa la array de fragmentos
- Crea una URL para el Blob creado usando el método URL.createObjectURL().
- Establece la fuente del elemento «video» / «audio» recién creado en la URL creada.
- Crea un enlace para descargar los medios grabados y revoca la URL creada después de hacer clic en el enlace usando el método URL.revokeObjectURL().
- Deshabilita el botón “iniciar grabación” y habilita el botón “detener grabación” .
¿Qué hace la función stopRecording()?
- Detiene la grabación llamando al método MediaRecorder.stop() .
- Detiene todas las pistas multimedia de MediaStream, es decir, cierra la cámara y el micrófono.
- Deshabilita el botón “detener grabación” y habilita el botón “iniciar grabación ”.