https://d226lax1qjow5r.cloudfront.net/blog/blogposts/record-a-call-in-ruby-with-vonage-voice-api-websockets/Blog_Ruby_Record-Voice-Call_1200x600.png

Enregistrer un appel en Ruby avec les WebSockets de l'API Voice de Vonage

Publié le November 5, 2020

Temps de lecture : 10 minutes

L'API vocale de Vonage WebSockets de l'API Voice de Vonage a récemment quitté le statut bêta et est devenue généralement disponible. WebSockets vous permet de créer une communication bidirectionnelle sur une seule connexion TCP persistante. Avec WebSockets, vous n'avez pas besoin de gérer de multiples requêtes et réponses HTTP. Une seule connexion WebSocket peut permettre la communication de données textuelles et binaires en continu, avec une seule connexion ouverte.

Bien que les WebSockets puissent simplifier le cycle de réponse et de demande HTTP, il s'agit d'un paradigme différent pour construire une application. Heureusement, la plupart des langages de programmation couramment utilisés disposent d'outils WebSocket qui peuvent aider à simplifier le processus.

Dans ce tutoriel, nous allons construire un petit serveur web pour travailler avec les WebSockets en Ruby. Le serveur gérera les appels vocaux entrants, les connexions WebSocket, et rendra du HTML. Nous utiliserons Rack comme interface web et Thin comme serveur web. Ce tutoriel ne nécessite pas de connaissances préalables sur le travail avec les WebSockets, mais il suppose une légère expérience du travail avec des serveurs web en Ruby.

tl;dr Si vous souhaitez passer directement à l'exécution de l'application, vous pouvez trouver une version entièrement fonctionnelle sur GitHub.

Conditions préalables

Ce tutoriel nécessite l'installation de Ruby v2.7 ou plus sur votre machine. De plus, plusieurs gems sont utilisés dans l'application. Chacune d'entre elles est listée dans le fichier Gemfile que nous créerons plus tard et sera installé en exécutant bundle install à partir de la ligne de commande :

Nous pouvons maintenant passer à la mise en œuvre de notre application.

Compte API Vonage

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.

Ce tutoriel utilise également un numéro de téléphone virtuel. Pour en acheter un, allez à Numbers > Acheter des Numbers et recherchez celui qui répond à vos besoins.

La dernière étape de la configuration de notre Account API consiste à créer une Voice API Application. Nous allons lier le numéro de téléphone virtuel que nous avons provisionné à cette application et définir les URL de webhook.

À partir du tableau de bord de l'API de Vonage, naviguez vers Vos Applications et cliquez sur Créer une nouvelle application. La page suivante s'affichera :

Dashboard Create ApplicationDashboard Create Application

Les domaines clés sur lesquels vous devez vous concentrer pour créer votre application sont surlignés en violet :

  • Nom : Vous pouvez donner à votre application le nom de votre choix.

  • Clé publique et privée : Cette opération génère une paire de clés publique et privée pour l'authentification. Un fichier de clé privée sera téléchargé sur votre machine. Notre application ne traite que les appels vocaux entrants, nous n'avons donc pas besoin d'en faire quoi que ce soit.

  • Capacités : Chaque application peut gérer plusieurs fonctionnalités. Dans le cas qui nous occupe, il suffit d'activer les fonctions suivantes Voice.

Une fois que vous avez terminé les options, vous pouvez appuyer sur le bouton Générer une nouvelle application pour terminer.

Maintenant que votre application est créée, nous allons la lier à votre numéro de téléphone nouvellement provisionné et définir les URLs du webhook.

Comme précédemment, naviguez vers Vos Applications dans le tableau de bord, cliquez sur les ellipses à côté du nom de votre application, et cliquez sur le bouton Modifier lien.

Dans le cadre des Capacités de la page, vous verrez les options suivantes :

Application Webhook URL settingsApplication Webhook URL settings

Nous devons remplir le champ URL de l'événement de l'événement et l URL de la réponse. C'est à la première que Vonage enverra toutes les données relatives au cycle de vie de l'appel vocal. La seconde est l'endroit où Vonage enverra chaque nouvel appel vocal lorsqu'il est initié. Les URL fournies ici doivent être accessibles de l'extérieur pour que Vonage puisse les atteindre. En d'autres termes, l'utilisation de localhost ne fonctionne pas. Une option de développement populaire est ngrok, et vous pouvez suivre notre tutoriel pour l'utiliser.

Assurez-vous que l'URL de l'événement et l'URL de la réponse se terminent par /webhooks/event et /webhooks/answerrespectivement.

Nous devons également connecter notre numéro de téléphone Vonage à cette application. Pour ce faire, naviguez vers Numbers>Vos Numbers et cliquez sur l'icône en forme de crayon à côté de votre numéro. Vous pouvez ensuite sélectionner votre nouvelle application dans les options pour y lier le numéro de téléphone. Une fois que vous aurez appuyé sur Sauvegardercela signifie que tous les appels entrants vers ce numéro seront transférés vers votre application.

Création de la structure des dossiers

Maintenant que notre compte API Vonage et nos paramètres sont prêts, passons à la création de la structure des dossiers pour notre application. Elle ressemblera à ce qui suit à la fin :

.
+-- recordings/
+-- views/
|   +-- index.html.erb
+-- app.rb
+-- Gemfile

Le dossier racine de notre application contiendra app.rbqui sera notre serveur web qui traitera tous les appels vocaux entrants et les connexions WebSocket. Il contiendra également le fichier Gemfilequi est l'endroit où nous définirons nos dépendances. Il y aura deux autres dossiers : recordings/ et views/. Le dossier recordings/ sera l'endroit où l'enregistrement de l'appel téléphonique provenant de la connexion WebSockets de Voice API sera sauvegardé. Le dossier views/ est l'endroit où nous conserverons la vue unique de l'application.

Définition des dépendances

Dans le fichier Gemfile ajoutez les gemmes suivantes que nous utiliserons dans l'application :

source 'https://rubygems.org'

gem 'wavefile'
gem 'faye-websocket'
gem 'json'
gem 'rack'
gem 'thin'

Chaque pierre précieuse remplit une fonction spécifique :

  • Wavefile : Nous utiliserons cette pierre précieuse pour convertir les données audio brutes en un fichier WAV.

  • Faye : Nous allons utiliser cette gem pour gérer la connexion WebSockets.

  • JSON : Cette gemme sera utilisée pour convertir les instructions d'appel que nous envoyons à l'API Voice de Vonage en JSON.

  • Rack : Nous utiliserons Rack comme cadre web.

  • Mince : Cette gemme nous fournit notre serveur web au dessus de Rack

Vous pouvez maintenant lancer bundle install à partir de la ligne de commande pour mettre toutes ces pierres précieuses à la disposition de votre application.

Construction du serveur

Dépendances et définition des variables

Nous sommes maintenant prêts à construire notre serveur web. La première chose que nous ferons en construisant le serveur est d'ajouter plusieurs fichiers require et include afin d'ajouter les fonctionnalités des gemmes mentionnées ci-dessus à notre application :

require 'rack'
require 'erb'
require 'faye/websocket'
require 'json'
require "wavefile"
include Rack
include WaveFile

À ce stade, nous allons également définir une variable constante qui correspondra à l'URL accessible de l'extérieur pour nos demandes de connexion WebSockets. Nous utiliserons cette URL dans les instructions que nous enverrons à l'API Voice de Vonage lorsque nous recevrons un nouvel appel :

EXTERNAL_WS_URL = 'ws://example.com/cable'

Remplacez le example.com dans l'extrait ci-dessus par votre URL accessible de l'extérieur.

Méthodes d'assistance

Notre application tirera parti de deux méthodes d'aide. Nous pouvons les créer maintenant et les ajouter après la déclaration de la variable constante.

La première méthode, #create_wav_filepermet de transformer les données audio binaires reçues par la WebSocket en un fichier WAV. Elle utilisera la fonctionnalité de la gemme Wavefile pour créer le fichier WAV et renverra le nom du fichier à utiliser ultérieurement dans l'application :

def create_wav_file(data, file_name)
  buffer = Buffer.new(data, Format.new(:mono, :pcm_16, 16000)) 
  puts "Audio Buffer Created..."
  writer = Writer.new(file_name, Format.new(:mono, :pcm_16, 16000))
  puts "New Audio File Created..."
  puts "Writing to the Buffer..."
  writer.write(buffer)
  puts "Closing Buffer Writing..."
  writer.close
  puts "WAV File Created..."
  file_name
end

La méthode ci-dessus spécifie que l'audio entrant provient d'une source mono au lieu d'une source stereo source. La différence est qu'il y a une seule piste audio, définie par un tableau plat de données binaires, et non un tableau de tableaux de données. L'audio est défini comme pcm_16ce qui signifie que la source est PCM linéaire 16 bits. Enfin, la source est définie comme 16000ce qui signifie qu'il s'agit d'une fréquence d'échantillonnage de 16 kHz. Le nouveau fichier WAV est créé avec les mêmes paramètres audio que les données audio binaires de la source.

La deuxième méthode, #erbest une méthode courte que nous utiliserons pour rendre les fichiers modèles ERB à l'utilisateur :

def erb(template)
  path = File.expand_path("#{template}")
  ERB.new(File.read(path)).result(binding)
end

Le reste de notre code sera constitué d'une série d'instructions map connectant les routes URL à des actions spécifiques enveloppées dans un Rack::Handler middleware.

Définir les itinéraires

Les routes pour notre application doivent être définies à l'intérieur de l'intergiciel Rack qui relie Rack à Thin. Nous utiliserons également l'intergiciel Rack::Static pour servir le fichier audio statique dans la vue. Nous initialiserons également le gestionnaire WebSocket de Faye ici :

Rack::Handler::Thin.run(Rack::Builder.new {
  Faye::WebSocket.load_adapter('thin')
  use(Rack::Static, urls: ["/recording"], root: 'recording')

Il y a quatre itinéraires pour lesquels nous devons créer des déclarations map pour : /cable, /, /webhooks/answeret /webhooks/answer. C'est ce que nous allons faire maintenant.

La première route gère la connexion WebSockets :

map('/cable') do
  run(->env{
    if Faye::WebSocket.websocket?(env)
      puts "WebSockets connection opened..."
      @call_data = []
      ws = Faye::WebSocket.new(env)
  
      ws.on :message do |event|
        if event.data.is_a?(Array)
          @call_data.append(event.data.pack('c*').unpack('s*'))
        else
          puts event.data
        end
      end
  
      ws.on :close do |event|
        puts 'WebSocket connection closed...'
        create_wav_file(@call_data.flatten, 'recording/recording.wav')
      end
  
      ws.rack_response
    end
  })
end

Dans la route ci-dessus, nous vérifions si la demande de connexion est une demande WebSocket. Si c'est le cas, nous créons une nouvelle instance de Faye::WebSocket. Il existe deux types de données possibles envoyées à une WebSocket : du texte ou des données binaires. Ces dernières sont toujours envoyées sous la forme d'entiers de la taille d'un octet dans un tableau.

Ainsi, nous pouvons vérifier si l'objet event.data est un objet de type tableau ou non. S'il s'agit d'un tableau, nous savons qu'il s'agit des données audio binaires que nous utiliserons pour créer notre fichier WAV. Si ce n'est pas le cas, il s'agit de mises à jour d'état provenant de l'API Voice de Vonage. Dans ce cas, nous pouvons les enregistrer dans la console.

Une remarque importante à prendre en compte : La gemme Faye WebSockets convertit les données binaires en entiers de la taille d'un octet, comme nous l'avons vu plus haut. Cela signifie toutefois qu'elle les convertit en exactement 1 octet ou 8 entiers de la taille d'un bit. L'API Voice de Vonage envoie les données binaires audio en nombres entiers de 16 bits, une norme commune pour la parole humaine. Cela signifie que notre application doit convertir les 8 bits de données binaires en 16 bits. Nous utilisons les méthodes de la bibliothèque standard Ruby #pack et #unpack que nous ajoutons à notre variable d'instance @call_data à notre variable d'instance. Cette étape est nécessaire pour produire un son compréhensible.

L'itinéraire suivant desservira la index . Elle vérifiera s'il y a un fichier. Elle vérifiera s'il existe un fichier et, si c'est le cas, le transmettra à la vue avec des variables d'instance :

map('/') do
  if File.exist?('recording.wav')
    @call_status = 'Audio Loaded!'
    @file = 'recording.wav'
  end
  run(->env{
    [200, { 'Content-Type' => 'text/html'}, [erb("views/index.html.erb")]]})
end

L'itinéraire /webhooks/answer retournera aux instructions spécialisées de l'API Voice de Vonage, appelées NCCO (Nexmo Call Control Object) informant l'API de ce qu'elle doit faire avec l'appel qu'elle vient de recevoir. L'instruction que nous renvoyons sous forme de JSON indique à Voice API que nous voulons ouvrir une connexion WebSocket et lui fournit l'URL WebSocket pour commencer la connexion. Nous indiquons également à Voice API que nous voulons qu'elle transmette à l'appelant un court message l'informant que son appel sera diffusé dans un instant :

map('/webhooks/answer') do
  run(->env{
    ncco = [
      {
        "action": "talk",
        "text": "You will be streaming momentarily."
      },
      {
        "action": "connect",
        "endpoint": [
          {
            "type": "websocket",
            "uri": "#{EXTERNAL_WS_URL}",
            "content-type": "audio/l16;rate=16000",
          }
        ]
      }
    ].to_json

    [200, { 'Content-Type' => 'application/json' }, [ncco]]
  })
end

La dernière route que nous allons créer traitera les données relatives au cycle de vie des événements de l'appel que l'API Voice envoie à notre application. Nous ne voulons rien faire avec ces données, si ce n'est accuser réception à l'API avec un 200 code d'état :

  map('/webhooks/event') do
  run(->env{
    [200, { 'Content-Type' => 'text/html'}, ['']]
  })
end

Enfin, nous fermons le bloc Rack::Builder que nous avons ouvert au tout début en spécifiant un port sur lequel exécuter notre application :

}, Port: 9292)

Le dernier élément que nous devons créer avant d'exécuter notre application est notre vue.

Création de la vue

L'application n'a qu'une seule vue. Cette vue est accessible en allant à l'URL racine de l'application, c'est-à-dire localhost:9292 ou 127.0.0.1:9292. La vue présentera l'audio à jouer avec un élément <audio> élément HTML :

<html>
  <head>
    <title>Ruby Vonage WebSockets Demo</title>
  </head>
  <body>
    <h1>Vonage WebSockets + Ruby == ♥</h1>
    <p>Welcome to the Vonage WebSockets demo in Ruby</p>  
    <h2>Your Audio To Playback</h2>
    <p>Once you have finished your call, your audio will be available to playback from here.</p>
    <div id="audio-status">
      <%= @call_status %>
      <br />
      <% if @file %>
        <audio
          controls
          src="recording/<%= @file %>">
            Your browser does not support the
            <code>audio</code> element.
        </audio>
      <% end %>
    </div>
  </body>
</html>

La vue utilise les variables d'instance que nous avons créées dans la route pour déterminer s'il faut ou non afficher l'élément <audio> l'élément.

Nous sommes maintenant prêts à exécuter l'application !

Exécution de l'application

L'application est maintenant prête à être exécutée. Pour la lancer, exécutez la commande suivante à partir de la ligne de commande dans le dossier racine de l'application :

bundle exec rackup app.rb

N'oubliez pas non plus de vérifier que votre serveur web est accessible de l'extérieur à l'aide de ngrok ou d'un autre outil similaire.

À ce stade, appelez votre application en composant votre numéro de téléphone virtuel Vonage. Lorsque vous avez fini de parler, vous pouvez raccrocher. Si vous visitez votre application à l'aide de votre navigateur Web, vous verrez maintenant un lecteur audio et vous pourrez écouter votre enregistrement. Félicitations !

Pour en savoir plus

Ce tutoriel a démontré les fonctionnalités de base de la fonction WebSockets de l'API Voice de Vonage en Ruby. Il y a beaucoup d'autres choses que vous pouvez faire avec cette fonctionnalité. Pour en savoir plus sur les sockets Web de l'API Voice de Vonage, consultez les sites suivants :

Partager:

https://a.storyblok.com/f/270183/384x384/e5480d2945/ben-greenberg.png
Ben GreenbergAnciens de Vonage

Ben est un développeur en seconde carrière qui a auparavant passé une décennie dans les domaines de la formation pour adultes, de l'organisation communautaire et de la gestion d'organisations à but non lucratif. Il a travaillé comme défenseur des développeurs pour Vonage. Il écrit régulièrement sur l'intersection du développement communautaire et de la technologie. Originaire de Californie du Sud et ayant longtemps vécu à New York, Ben réside aujourd'hui près de Tel Aviv, en Israël.