Cree una grabadora de video y audio con JavaScript MediaRecorder API

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 ”.

Publicación traducida automáticamente

Artículo escrito por samufo y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *