
Compartilhar:
Educador de tecnologia com um jeito amigável, pai de família, defensor da diversidade, provavelmente discuto um pouco demais. Ex-engenheiro de backend. Fale comigo sobre JavaScript (front-end ou back-end), o incrível Vue.js, DevOps, DevSecOps e tudo relacionado ao JamStack. Escritor no DEV.to
Como adicionar funcionalidade de Voice a um aplicativo de bate-papo já existente
Tempo de leitura: 14 minutos
Você já chegou na metade de uma mensagem longa para alguém em um chat e pensou: “não seria muito mais fácil se eu pudesse simplesmente falar com essa pessoa?” Claro que já! Ao usar o Nexmo Client SDK no seu aplicativo de chat, você poderá fazer exatamente isso.
O aplicativo de demonstração e um exemplo finalizado já estão disponíveis no GitHub.
Pré-requisitos
Node e NPM
Para começar, você precisará ter o Node e o NPM instalados. Este guia utiliza o Node 8 e o NPM 6. Verifique se eles estão instalados e atualizados.
Tanto o Node quanto o NPM precisam estar instalados e na versão correta. Acesse o site nodejs.org e instale a versão correta, caso ainda não a tenha.
Nexmo CLI
Para configurar seu aplicativo, você precisará instalar a CLI do Nexmo. Instale-a usando o NPM no terminal.
Configure a CLI do Nexmo com a chave e o segredo da API, que podem ser encontrados no painel de controle.
Git (opcional)
Você pode usar o Git para clonar nosso aplicativo de demonstração do GitHub.
Para quem não se sente à vontade com os comandos do Git, não se preocupe, eu cuido disso para você. Este guia traz instruções sobre como baixar o projeto como um arquivo ZIP.
Siga este guia para instalar o git
O aplicativo de demonstração
O aplicativo é, antes de tudo, um cliente de bate-papo simples. Ele permite que dois usuários (ou mais, se você configurá-lo para isso) façam login e comecem a conversar.
Instalação básica
Para facilitar o acompanhamento deste guia, clone o aplicativo de demonstração diretamente do GitHub.
Para quem não se sente à vontade com os comandos do Git, é possível baixar o aplicativo de demonstração como um arquivo zip e descompactá-lo localmente.
Depois de clonada ou descompactada, acesse o novo diretório do aplicativo de demonstração.
Instale as dependências do npm.
Agora, inicie o aplicativo.
Agora você pode testá-lo no seu navegador favorito e acessar o aplicativo, que deve estar em execução no endereço padrão: http://127.0.0.1:8080.
Login box
Se não estiver configurado, você verá uma caixa de login. Você não pode fazer login porque ainda não sabe quem pode fazer login!
Lembre-se de que que aqui você está apenas simulando a autenticação e deve estar preparado para configurar algo real e seguro para aplicativos de produção.
User not found - Login Box
Interrompa o aplicativo: No terminal ou no bash, você pode usar CTRL+C para interromper o processo em execução. À medida que você faz alterações, não deve ser necessário iniciar ou interromper o aplicativo, pois tudo é solicitado novamente do servidor sempre que você recarrega uma página.
Configuração super simples
Na demonstração (aquela que você está executando agora), há um script que facilita bastante as próximas etapas.
Como o script funciona: Ele solicita algumas informações e, em seguida, cria o aplicativo, a conversa e os usuários necessários para este guia, executando todos os comandos de configuração que você teria que fazer manualmente. Em seguida, ele gera a configuração para o aplicativo de demonstração. Você pode conferir o código aqui para ter certeza de que ele não faz nada de errado.
Uau, você não quer rodar meu script? Prefere gerar o arquivo de configuração manualmente? Não se preocupe — veja aqui os passos para criar o arquivo de configuração manualmente.
Execute o script de configuração
Portanto, para configurar o aplicativo para as próximas etapas, execute o script de configuração.
O roteiro faz algumas perguntas.
Script Questions
No final, ele atualiza o config.js para ficar mais parecido com isto.
const USERS = {
luke: 'eyJhbGciOiJIkpXVCJ9.eyJpYXQiOnt9fX19.EDHi1R61yh01oeZ9DYQ',
alex: 'eyJhbGciOi234JXVCJ9.eyJpyXQiOjt9fX19.VqLdU97Fdb2ZiOfqmoQ',
}
const CONVERSATION_ID = 'CON-da9c1a6b-c2dc-4bdd-ac03-cc041ef03502' Então, o que esse script fez?!?
Portanto, nos bastidores, o script executa as seguintes etapas.
Crie um aplicativo Nexmo usando o
nexmo app:createe anote o ID.Crie uma conversa no Nexmo usando o
nexmo conversation:createe anote o ID.Crie os dois usuários usando o
nexmo user:createe anote os IDs.Adicione os dois usuários à conversa do Nexmo com
nexmo member:add.Gere JWTs para que ambos os usuários possam acessar o aplicativo e anote os JWTs.
Grava a configuração no arquivo config.js usando os IDs e os JWTs que foram armazenados.
Chitty Chitty Chat Chat
Pronto, você já configurou nosso aplicativo de demonstração básico; agora pode dar uma conversinha! Aqui estou eu testando com um dos meus colegas.
Chat text
Ativar áudio
Agora, tudo já está funcionando. Você tem um aplicativo de demonstração que pode usar para conversar com outras pessoas. Em seguida, adicione um botão para ativar o áudio, para que vocês também possam conversar entre si.
O HTML
Encontre o código a seguir dentro do index.html arquivo.
<section id="messages">
<!-- /audio-toggle -->
<h1>Messages</h1>
<div id="messageFeed"></div>
<textarea id="messageTextarea"></textarea>
<br>
<button id="send">Send</button>
</section>Substitua a linha <!-- /audio-toggle --> pelo seguinte código 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>Agora, vou explicar o código acima.
<audio> As tags foram adicionadas à especificação HTML para permitir a incorporação de arquivos de áudio (ou fluxos) em páginas da web. A tag é usada para indicar uma fonte (caminho/URL) e uma versão do áudio, permitindo que várias versões do áudio sejam incorporadas/codificadas para diferentes contextos (ou navegadores, sistemas operacionais etc.).
Além disso, você vai adicionar um botão que servirá como nosso botão de alternância.
O index.html arquivo deve conter agora uma seção parecida com esta.
<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>Essas são todas as alterações no HTML desta vez. E agora, o que vem a seguir?
O JavaScript
A seguir, você vai editar o código JavaScript do nosso aplicativo de demonstração.
Encontre o código a seguir dentro do chat.js arquivo.
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()
}Substitua a linha // audio-elements pelo seguinte código JavaScript.
this.audio = document.getElementById('audio')
this.audioToggle = document.getElementById('audioToggle')
this.audioToggleText = document.getElementById('audioToggleText')Esse código “registra” três novos elementos para que você possa usar com mais facilidade os elementos da página em todo o arquivo JavaScript.
Agora, encontre este código dentro do mesmo chat.js arquivo.
// audio-toggle-event
this.showConversationHistory(conversation)Substitua a linha // audio-toggle-event pelo seguinte 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
})Esse código também “registra” um ouvinte para o member:media evento. Ao detectar esse evento, ele envia um console.log ao navegador; por exemplo, dados de membros e eventos. É especialmente útil para depuração. Ele também vai criar um texto formatado e atualizar o messageFeed , acrescentando o texto no início do feed existente.
Em seguida, localize este código dentro do chat.js arquivo.
})
// audio-click-event
}Substitua a linha // audio-click-event pelo seguinte 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)
}
})
Esse código é bem importante. E ele também registra um ouvinte. Desta vez, ele fica atento ao momento em que o usuário clicar no nosso audioToggle botão que você acabou de adicionar.
Se um usuário clicar no botão e ele já estiver ativado, ele será desativado. Se estiver desativado, ele será ativado.
Quando ativado, ele habilita o áudio adicionando a URL do fluxo de áudio à tag e atualiza o estilo do botão. Portanto, quando desativado, ele desabilita o áudio removendo a URL do fluxo de áudio da tag e atualiza o estilo do botão.
O arquivo inteiro chat.js arquivo deve agora ter uma aparência (longa) mais ou menos assim.
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()Supondo que você tenha feito tudo certo, execute npm start novamente e abra o aplicativo em http://127.0.0.1:8080. Se ele já estivesse em execução, basta atualizar a página para obter a versão mais recente.
Login box
Se você achar que o JavaScript não está funcionando e reiniciar o NPM não resolver, use CTRL+F5 para atualizar a página da web, o que faz com que todos os arquivos JS e CSS da página sejam recarregados.
Faça login usando as credenciais de teste que você configurou.
Login using credentials
Agora que você está conectado, pode ver o feed de mensagens como antes e o botão para ativar o áudio. Vá em frente e clique em “Ativar áudio”.
Permita que o aplicativo use seu microfone. Isso se aplica ao Chrome para macOS; em outros navegadores e sistemas operacionais, o procedimento pode variar.
Allow microphone
Com os dois usuários conectados e com o áudio ativado, é possível manter uma conversa entre eles.
Two user convo
Nesse momento, se você estiver em uma cafeteria e estiver testando isso em duas janelas do navegador, como eu estava, prepare-se para receber olhares de reprovação dos outros frequentadores do local quando você gerar um loop de feedback barulhento.
Agora você pode clicar em “Desativar áudio” para desligar o microfone novamente. Os outros usuários serão avisados de que você desativou o áudio.
Disable Audio
Os resultados
Sempre haverá a necessidade de possibilitar a comunicação por áudio entre os usuários da web, e nosso Client SDK é a solução perfeita para isso.
Experimente e me diga o que achou na nossa Slack da Comunidade ou na seção de comentários abaixo.
Compartilhar:
Educador de tecnologia com um jeito amigável, pai de família, defensor da diversidade, provavelmente discuto um pouco demais. Ex-engenheiro de backend. Fale comigo sobre JavaScript (front-end ou back-end), o incrível Vue.js, DevOps, DevSecOps e tudo relacionado ao JamStack. Escritor no DEV.to