https://d226lax1qjow5r.cloudfront.net/blog/blogposts/adding-voice-functionality-to-an-existing-chat-application-dr/Elevate_Enable-Audio-1.png

Añadir funciones de voz a una aplicación de chat existente

Publicado el May 13, 2021

Tiempo de lectura: 14 minutos

¿Alguna vez te has quedado a medio escribir un largo mensaje a alguien en un chat y has pensado: no sería mucho más fácil si pudiera hablar con esa persona? Por supuesto que sí. Utilizando el Client SDK de Nexmo en tu aplicación de chat, podrás hacerlo.

La aplicación de demostración aplicación de demostración y un ejemplo terminado ya están disponibles en GitHub.

Requisitos previos

Nodo y NPM

Para empezar vas a necesitar Node y NPM instalados. Esta guía utiliza Node 8 y NPM 6. Comprueba que están instalados y actualizados.

node --version npm --version

Tanto Node como NPM necesitan estar instalados y en la versión correcta. Vaya a nodejs.org e instale la versión correcta si no la tiene.

Nexmo CLI

Para configurar tu aplicación, necesitarás instalar Nexmo CLI. Instálalo usando NPM en la terminal.

npm install -g nexmo-cli@beta

Configurar el Nexmo CLI con la clave de la API y el secreto que se puede encontrar en el tablero de instrumentos.

nexmo setup

Git (opcional)

Puede utilizar git para clonar nuestra aplicación de demostración desde GitHub.

Para aquellos que no se sientan cómodos con los comandos git, no os preocupéis, os tengo cubiertos. Esta guía contiene instrucciones para descargar el proyecto como archivo ZIP.

Siga esta guía para instalar git

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

La aplicación de demostración

La aplicación es, ante todo, un simple cliente de chat. Permite que dos usuarios (o más, si así lo configuras) se conecten y empiecen a chatear.

Instalación básica

Para que esta guía sea fácil de seguir, clona la aplicación de demostración directamente desde GitHub.

git clone https://github.com/nexmo-community/enable-audio-in-chat.git

Para quienes no se sientan cómodos con los comandos git, pueden descargar la aplicación de demostración como archivo zip y descomprimirlo localmente.

Una vez clonado o descomprimido, cambie al nuevo directorio de aplicaciones de demostración.

cd enable-audio-in-chat

Instale las dependencias de npm.

npm install

Ahora, inicia la aplicación.

npm start

Ahora puedes probarlo en tu navegador favorito y ver la aplicación, que debería estar ejecutándose en la dirección predeterminada: http://127.0.0.1:8080.

Login boxLogin box

Sin configurar, verás un cuadro de inicio de sesión. No puedes iniciar sesión porque aún no sabes quién puede hacerlo.

Tenga en cuenta, sólo estás simulando la autenticación aquí, y debes estar preparado para configurar algo real y seguro para las aplicaciones de producción.

User not found - Login BoxUser not found - Login Box

Detener la aplicación: En terminal o bash, puedes usar CTRL+C para detener el proceso en ejecución. A medida que realices cambios, no deberías necesitar iniciar o detener la aplicación, ya que todo se solicita de nuevo al servidor cada vez que recargas una página.

Instalación muy sencilla

En la demo (la que estás ejecutando ahora,) hay un script para hacer los siguientes pasos mucho más fáciles.

Cómo funciona el script: Te pide algunos datos y luego crea la aplicación, la conversación y los usuarios necesarios para esta guía ejecutando todos los comandos de configuración que de otro modo tendrías que hacer manualmente. A continuación, genera la configuración de la aplicación de demostración. Puedes ver el código aquí para asegurarte de que no hace nada malo.

Woah¿No quieres ejecutar mi script? ¿Prefieres generar el archivo de configuración manualmente? Te cubro las espaldas - pasos para crear el archivo de configuración manualmente.

Ejecutar el script de instalación

Por lo tanto, para configurar la aplicación para los próximos pasos, ejecute el script de configuración.

npm run setup-script

El guión plantea algunas preguntas.

Script QuestionsScript Questions

Al final, actualiza el config.js para que se parezca más a esto.

const USERS = {
  luke: 'eyJhbGciOiJIkpXVCJ9.eyJpYXQiOnt9fX19.EDHi1R61yh01oeZ9DYQ',
  alex: 'eyJhbGciOi234JXVCJ9.eyJpyXQiOjt9fX19.VqLdU97Fdb2ZiOfqmoQ',
}

const CONVERSATION_ID = 'CON-da9c1a6b-c2dc-4bdd-ac03-cc041ef03502'

¿Y qué hizo ese guión?

Así, entre bastidores, el script lleva a cabo los siguientes pasos.

  • Crea una aplicación Nexmo utilizando el comando nexmo app:create y anota el ID.

  • Crea una conversación Nexmo utilizando el comando nexmo conversation:create y anota el ID.

  • Crea ambos usuarios utilizando el comando nexmo user:create y toma nota de los ID.

  • Añade ambos usuarios a la conversación Nexmo con nexmo member:add.

  • Genera JWTs para que ambos usuarios accedan a la aplicación y toma nota de los JWTs.

  • Escribe la configuración en config.js usando los IDs y JWTs que ha guardado.

Chitty Chitty Chat Chat

Ahora que ya has configurado nuestra aplicación de demostración básica, ¡ya puedes charlar un poco! Aquí estoy yo probándolo con uno de mis colegas.

Chat textChat text

Activar audio

Ahora, ya estás en marcha. Tienes una aplicación de demostración que puedes utilizar para chatear con otras personas. A continuación, añade un botón para habilitar el audio para que también podáis hablar entre vosotros.

El HTML

Encuentre el siguiente código dentro del archivo index.html archivo.

<section id="messages">
    <!-- /audio-toggle -->
    <h1>Messages</h1>
    <div id="messageFeed"></div>
    
    <textarea id="messageTextarea"></textarea>
    <br>
    <button id="send">Send</button>
  </section>

Sustituya la línea <!-- /audio-toggle --> por el siguiente HTML.

<div>
      <audio id="audio">
        
      </audio>
      <div class="btn-group" data-toggle="buttons">
        <label class="btn btn-secondary">
           <span id="audioToggleText">Enable Audio</span>
        </label>
      </div>
    </div>

Ahora, permítanme explicar el código anterior.

<audio> se añadieron a la especificación HTML para permitir la incrustación de archivos de audio (o secuencias) en páginas web. La etiqueta se utiliza para indicar la fuente (ruta/url) y la versión del audio, lo que permite incrustar/codificar varias versiones del audio para distintos contextos (o navegadores, sistemas operativos, etc.).

Además, está añadiendo un botón que será nuestra palanca.

El archivo index.html debería contener una sección parecida a ésta.

<section id="messages">
    <div>
      <audio id="audio">
        
      </audio>
      <div class="btn-group" data-toggle="buttons">
        <label class="btn btn-secondary">
           <span id="audioToggleText">Enable Audio</span>
        </label>
      </div>
    </div>
    <h1>Messages</h1>
    <div id="messageFeed"></div>
    
    <textarea id="messageTextarea"></textarea>
    <br>
    <button id="send">Send</button>
  </section>

Estos son todos los cambios de HTML esta vez. ¿Y ahora qué?

El JavaScript

A continuación, editaremos el JavaScript de nuestra aplicación de demostración.

Encuentre el siguiente código dentro del archivo chat.js archivo.

constructor() {
    this.messageTextarea = document.getElementById('messageTextarea')
    this.sendButton = document.getElementById('send')
    this.loginForm = document.getElementById('login')
    this.loginButton = document.getElementById('loginButton')
    this.messages = document.getElementById('messages')
    this.messageFeed = document.getElementById('messageFeed')
    // audio-elements
    this.setupUserEvents()
  }

Sustituya la línea // audio-elements por el siguiente código JavaScript.

this.audio = document.getElementById('audio')
    this.audioToggle = document.getElementById('audioToggle')
    this.audioToggleText = document.getElementById('audioToggleText')

Este código 'registra' 3 nuevos elementos para que puedas utilizar más fácilmente los elementos on-page en todo el archivo JavaScript.

Ahora, encuentra este código dentro del mismo archivo chat.js archivo.

// audio-toggle-event

    this.showConversationHistory(conversation)

Sustituya la línea // audio-toggle-event por el siguiente código JavaScript.

conversation.on("member:media", (member, event) => {
      console.log(`*** Member changed media state`, member, event)
      const text = `${member.user.name} <b>${event.body.audio ? 'enabled' : 'disabled'} audio in the conversation</b><br>`
      this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
    })

Este código también 'registra' un oyente para el evento member:media evento. Cuando ve ese evento, entonces envía un console.log al navegador; como, miembro y datos del evento. Especialmente útil para la depuración. También va a crear un poco de texto con formato y actualizar el messageFeed anteponiendo el texto a la alimentación existente.

A continuación, busque este código dentro del archivo chat.js archivo.

})

    // audio-click-event
  }

Sustituya la línea // audio-click-event por el siguiente código JavaScript.

    this.audioToggle.addEventListener('click', () => {
      const buttonContainer = this.audioToggle.parentNode
      if (this.audioToggle.checked) {
        this.audioToggleText.innerHTML = 'Disable Audio'
        buttonContainer.classList.add('btn-danger')
        buttonContainer.classList.add('active')
        buttonContainer.classList.remove('btn-secondary')
        this.conversation.media.enable().then(stream => {
          // Older browsers may not have srcObject
          if ("srcObject" in this.audio) {
            this.audio.srcObject = stream
          } else {
            // Avoid using this in new browsers, as it is going away.
            this.audio.src = window.URL.createObjectURL(stream)
          }

          this.audio.onloadedmetadata = () => {
            this.audio.play()
          }

          this.eventLogger('member:media')()
        }).catch(this.errorLogger)
      } else {
        this.audioToggleText.innerHTML = 'Enable Audio'
        buttonContainer.classList.remove('btn-danger')
        buttonContainer.classList.remove('active')
        buttonContainer.classList.add('btn-secondary')
        this.conversation.media.disable().then(this.eventLogger('member:media')).catch(this.errorLogger)
      }
    })

Este código es un biggy. Y, esto también registra un oyente. Esta vez, está escuchando cuando el usuario hace clic en nuestro audioToggle botón que acaba de agregar.

Si un usuario pulsa el botón y ya estaba activado, se desactiva. Si estaba desactivado, se activa.

Cuando está activada, activa el audio añadiendo la URL del flujo de audio a la etiqueta y actualiza el estilo del botón. Por lo tanto, cuando está desactivado, desactiva el audio eliminando la URL del flujo de audio de la etiqueta y actualiza el estilo del botón.

El archivo chat.js debería ser algo (largo) parecido a esto.

class ChatApp {
  constructor() {
    this.messageTextarea = document.getElementById('messageTextarea')
    this.sendButton = document.getElementById('send')
    this.loginForm = document.getElementById('login')
    this.loginButton = document.getElementById('loginButton')
    this.messages = document.getElementById('messages')
    this.messageFeed = document.getElementById('messageFeed')
    this.audio = document.getElementById('audio')
    this.audioToggle = document.getElementById('audioToggle')
    this.audioToggleText = document.getElementById('audioToggleText')
    this.setupUserEvents()
  }

  joinConversation(userToken) {
    new NexmoClient({ debug: false })
      .createSession(userToken)
      .then(app => {
        console.log('*** Logged into app', app)
        return app.getConversation(CONVERSATION_ID)
      })
      .then((conversation) => {
        console.log('*** Joined conversation', conversation)
        this.setupConversationEvents(conversation)
      })
      .catch(this.errorLogger)
  }

  showConversationHistory(conversation) {
    conversation
      .getEvents({ page_size: 20 })
      .then((events_page) => {
        var eventsHistory = ""

        events_page.items.forEach((value, key) => {
          if (conversation.members.get(value.from)) {
            const date = new Date(Date.parse(value.timestamp))
            switch (value.type) {
              case 'text':
                eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date.toLocaleString('en-GB')}: <b>${value.body.text}</b><br>` + eventsHistory
                break;
              case 'member:joined':
                eventsHistory = `${conversation.members.get(value.from).user.name} @ ${date.toLocaleString('en-GB')}: <b>joined the conversation</b><br>` + eventsHistory
                break;
            }
          }
        })

        this.messageFeed.innerHTML = eventsHistory + this.messageFeed.innerHTML
      })
      .catch(this.errorLogger)
  }

  setupConversationEvents(conversation) {
    this.conversation = conversation
    this.messages.style.display = "block"

    // Bind to events on the conversation
    conversation.on('text', (sender, message) => {
      const date = new Date(Date.parse(message.timestamp))
      console.log('*** Message received', sender, message)
      const text = `${sender.user.name} @ ${date.toLocaleString('en-GB')}: <b>${message.body.text}</b><br>`
      this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
    })

    conversation.on("member:joined", (member, event) => {
      const date = new Date(Date.parse(event.timestamp))
      console.log(`*** ${member.user.name} joined the conversation`)
      const text = `${member.user.name} @ ${date.toLocaleString('en-GB')}: <b>joined the conversation</b><br>`
      this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
    })

    conversation.on("member:media", (member, event) => {
      console.log(`*** Member changed media state`, member, event)
      const text = `${member.user.name} <b>${event.body.audio ? 'enabled' : 'disabled'} audio in the conversation</b><br>`
      this.messageFeed.innerHTML = text + this.messageFeed.innerHTML
    })

    this.showConversationHistory(conversation)
  }

  errorLogger(error) {
    console.log(error)
  }

  eventLogger(event) {
    return () => {
      console.log("'%s' event was sent", event)
    }
  }

  setupUserEvents() {
    this.sendButton.addEventListener('click', () => {
      this.conversation.sendText(this.messageTextarea.value)
        .then(() => {
            this.eventLogger('text')()
            this.messageTextarea.value = ''
        })
        .catch(this.errorLogger)
    })
  
    this.loginForm.addEventListener('submit', (event) => {
      event.preventDefault()
      const userName = this.loginForm.children.username.value
      const userToken = this.authenticate(userName)
      this.loginForm.children.username.value = ''
      if (userToken) {
        this.joinConversation(userToken)
        this.loginForm.style.display = 'none'
      } else {
        alert('user not found')
      }
    })

    this.audioToggle.addEventListener('click', () => {
      const buttonContainer = this.audioToggle.parentNode
      if (this.audioToggle.checked) {
        this.audioToggleText.innerHTML = 'Disable Audio'
        buttonContainer.classList.add('btn-danger')
        buttonContainer.classList.add('active')
        buttonContainer.classList.remove('btn-secondary')
        this.conversation.media.enable().then(stream => {
          // Older browsers may not have srcObject
          if ("srcObject" in this.audio) {
            this.audio.srcObject = stream
          } else {
            // Avoid using this in new browsers, as it is going away.
            this.audio.src = window.URL.createObjectURL(stream)
          }

          this.audio.onloadedmetadata = () => {
            this.audio.play()
          }

          this.eventLogger('member:media')()
        }).catch(this.errorLogger)
      } else {
        this.audioToggleText.innerHTML = 'Enable Audio'
        buttonContainer.classList.remove('btn-danger')
        buttonContainer.classList.remove('active')
        buttonContainer.classList.add('btn-secondary')
        this.conversation.media.disable().then(this.eventLogger('member:media')).catch(this.errorLogger)
      }
    })
  }

  authenticate(username) {
    return USERS[username] || null
  }
}
new ChatApp()

Suponiendo que lo has hecho todo bien, ejecuta npm start de nuevo y abra la aplicación en http://127.0.0.1:8080. Si ya se estaba ejecutando, deberías poder actualizar la página para obtener la última versión.

Login boxLogin box

Si crees que el JavaScript no está funcionando y reiniciar NPM no ayuda, usa CTRL+F5 para refrescar la página web, lo que solicita todo el JS y CSS de la página fresco.

Inicie sesión con las credenciales de prueba que ha configurado.

Login using credentialsLogin using credentials

Ahora que has iniciado sesión, puedes ver el feed de mensajes como antes, y el botón para Activar Audio. Adelante, haz clic en Activar audio.

Permite que la aplicación utilice tu micrófono. Esto es en Chrome para macOS, otros navegadores y sistemas operativos pueden variar.

Allow microphoneAllow microphone

Con ambos usuarios conectados y el audio activado, puedes mantener una conversación entre los dos usuarios.

Two user convoTwo user convo

En este punto, si estás en una cafetería y lo pruebas en dos ventanas del navegador como yo, prepárate para que tus compañeros frunzan el ceño cuando generes un bucle de retroalimentación ruidoso.

Ahora puedes hacer clic en Desactivar audio para volver a desactivar el micrófono. Los demás usuarios recibirán un aviso de que has desactivado el audio.

Disable AudioDisable Audio

Resultados

Siempre va a existir la necesidad de permitir la comunicación de audio entre usuarios web y nuestro Client SDK es una solución perfecta para ello.

Pruébalo y cuéntame lo que piensas en nuestra Comunidad Slack o en la sección de comentarios.

Compartir:

https://a.storyblok.com/f/270183/250x250/451101b4f0/lukeoliff.png
Luke OliffAntiguos alumnos de Vonage

Amable educador tecnológico, padre de familia, defensor de la diversidad, probablemente discuta demasiado. Anteriormente ingeniero de backend. Háblame de JavaScript (frontend o backend), el increíble Vue.js, DevOps, DevSecOps, cualquier cosa JamStack. Escritor en DEV.to