https://d226lax1qjow5r.cloudfront.net/blog/blogposts/connecting-webrtc-and-pstn-with-opentok-and-nexmo-dr/opentok-sip.png

Connecter WebRTC et PSTN avec OpenTok et Nexmo

Publié le May 13, 2021

Temps de lecture : 5 minutes

Chez Nexmo, nous avons récemment annoncé SIP Connect qui permet de connecter des points d'extrémité WebRTC avec l'API Voice de Nexmo. Cette fonctionnalité permet aux utilisateurs du réseau téléphonique public commuté de se connecter à une session vidéo OpenTok.

Dans cet article, nous allons créer une application web de Video en temps réel à l'aide d'OpenTok et y connecter les utilisateurs du RTC à l'aide de SIP Connect et de la Voice API.

Conditions préalables

Avant de commencer, assurez-vous que vous disposez des éléments suivants :

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.

Pour commencer

Veuillez créer un projet TokBox API car vous aurez besoin d'un fichier apiKey et apiSecret pour ajouter la voix et la Video en temps réel à l'application web. En plus des informations d'identification TokBox, vous devrez créer une application Nexmo Voice et définir des webhooks d'événement et de réponse.

Ne vous préoccupez pas de leur nature pour l'instant, car nous les expliquerons au fur et à mesure que nous les utiliserons. Enfin, vous devez acheter un numéro virtuel Numbers et transférer tous les appels entrants vers ce numéro à l'application que vous avez créée.

Vue d'ensemble

architecture

Exemple de code

Pour commencer, clonez le fichier opentok-nexmo-sip et changer de répertoire dans le répertoire Dial-In-Conference dans le répertoire Dial-In-Conference.

Dans le répertoire du projet, vous trouverez un fichier config.example.js dans le répertoire du projet. Veuillez copier le contenu de ce fichier dans un nouveau fichier appelé config.js.

Assurez-vous d'ajouter les identifiants TokBox et Nexmo que vous avez générés précédemment au fichier config.js car nous les utiliserons pour l'application.

Code côté client

Dans ce cas, nous utilisons JavaScript pour le web, mais vous pouvez utiliser les mêmes concepts avec l'OpenTok. iOS, Androidet Windows SDKs.

Comme vous pouvez le voir ci-dessous, dans le fichier opentok.js situé dans le dossier public/js nous initialisons une session en appelant la méthode initSession sur l'objet OT sur l'objet Nous créons ensuite un objet objet Publisher à l'aide de la méthode initPublisher à l'aide de la méthode

Nous définissons ensuite les événements de session suivants :

  • streamCreated

  • streamDestroyed

  • sessionConnected

Ces événements sont déclenchés respectivement lorsqu'un flux est créé, lorsqu'un flux est détruit ou lorsque le client se connecte à la session.

Après avoir configuré les récepteurs d'événements, nous nous connectons à la session en transmettant le jeton et un gestionnaire d'erreurs. Le gestionnaire d'erreur est utilisé pour s'assurer qu'il n'y a pas eu d'erreur lors de la tentative de connexion à la session. Dans notre application, si une erreur survient, nous l'enregistrons dans la console, mais dans une application de production, nous devrions afficher un élément d'interface utilisateur et tenter de nous reconnecter.

const session = OT.initSession(apiKey, sessionId);
const publisher = OT.initPublisher('publisher');
session.on({
 streamCreated: (event) => {
   const subscriberClassName = `subscriber-${event.stream.streamId}`;
   const subscriber = document.createElement('div');
   subscriber.setAttribute('id', subscriberClassName);
   document.getElementById('subscribers').appendChild(subscriber);
   session.subscribe(event.stream, subscriberClassName);
 },
 streamDestroyed: (event) => {
   console.log(`Stream ${event.stream.name} ended because ${event.reason}.`);
 },
 sessionConnected: (event) => {
   session.publish(publisher);
 },
});

session.connect(token, (error) => {
 if (error) {
   console.log('error connecting to session');
 }
});

Ce fichier opentok.js est importé dans la vue située dans le fichier views/index.ejs fichier.

En plus de ce code, nous créons également quelques boutons qui déclenchent des requêtes API vers le serveur de l'application pour composer un numéro via SIP.

Dans cette application, cette vue est rendue par notre serveur, mais vous pouvez choisir de la rendre comme vous le souhaitez. Pour voir le code dans lequel cette vue est rendue, veuillez consulter le lien suivant lien.

Code côté serveur

Maintenant que notre client est en place, vérifions le code du serveur code du serveur.

Vous remarquerez que nous importons express, opentok, et body-parser les paquets. Nous utilisons Express.js pour notre serveur, le OpenTok Node SDKet l'analyseur de body parser qui sera utilisée pour analyser le corps des requêtes entrantes.

Notez que nous importons également l'élément config que nous avons défini dans le fichier config.js dans le fichier

Nous créons ensuite les points d'extrémité suivants :

  • /room/:roomId

  • /dial-out

  • /hang-up

  • /nexmo-answer

  • /nexmo-dtmf

  • /nexmo-events

Le chemin /room/:roomId est le chemin principal de notre application. Il rend la vue index.ejs avec les informations d'identification OpenTok appropriées. Lorsque cela se produit, le serveur de l'application demande à OpenTok de créer une session, qui répond ensuite avec un objet de session contenant l'identifiant sessionId.

Les sessionId est ensuite utilisé pour générer un jeton OpenTok. Nous générons ensuite un code pin à 4 chiffres et l'associons au nom de l'utilisateur et au nom de la chambre. sessionId et au nom de la chambre. Ceci est important car nous en aurons besoin pour rechercher le nom de la salle lorsque l'utilisateur du RTC se connectera. sessionId lorsque l'utilisateur du RTPC compose le numéro. Nous avons également ajouté une certaine logique pour placer les utilisateurs dans la même session lorsqu'ils font une demande avec le même roomId.

/**
* When the room/:roomId request is made, either a template is rendered is served with the
* sessionid, token, pinCode, roomId, and apiKey.
*/

app.get('/room/:roomId', (req, res) => {
  const { roomId } = req.params;
  let pinCode;
  if (app.get(roomId)) {
    const sessionId = app.get(roomId);
    const token = generateToken(sessionId);
    pinCode = app.get(sessionId);
    renderRoom(res, sessionId, token, roomId, pinCode);
  } else {
    pinCode = generatePin();
    OT.createSession({
      mediaMode: 'routed',
    }, (error, session) => {
      if (error) {
        return res.send('There was an error').status(500);
      }
      const { sessionId } = session;
      const token = generateToken(sessionId);
      app.set(roomId, sessionId);
      app.set(pinCode, sessionId);
      renderRoom(res, sessionId, token, roomId, pinCode);
    });
  }
});

Composition d'un numéro de téléphone

Pour se connecter au point d'extrémité SIP, le navigateur envoie une requête au point d'extrémité /dial-out et le serveur générera un jeton et utilisera nos informations d'identification Nexmo (clé et secret API) ainsi que l'uri SIP (sip:lvn@sip.nexmo.com) pour demander à OpenTok de se connecter à la session. Si cela réussit, nous obtenons des informations de connexion via le rappel pour les participants SIP.

/**
* When the dial-out get request is made, the dial method of the OpenTok Dial API is invoked
*/

app.get('/dial-out', (req, res) => {
  const { roomId } = req.query;
  const { conferenceNumber } = config;
  const sipTokenData = `{"sip":true, "role":"client", "name":"'${conferenceNumber}'"}`;
  const sessionId = app.get(roomId); // grabbing the sessionId from the mapping we created earlier
  const token = generateToken(sessionId, sipTokenData);
  const options = setSipOptions();
  const sipUri = `sip:${conferenceNumber}@sip.nexmo.com;transport=tls`;
  OT.dial(sessionId, token, sipUri, options, (error, sipCall) => {
    if (error) {
      res.status(500).send('There was an error dialing out');
    } else {
      app.set(conferenceNumber + roomId, sipCall.connectionId);
      res.json(sipCall);
    }
  });
});

La composition d'un numéro dans l'API Voice de Nexmo via SIP déclenchera le événement webhook, /nexmo-events dans notre cas, pour tout changement d'état, c'est-à-dire started, ringingetc.

app.get('/nexmo-events', (req, res) => {
  console.log('call event', req.query);
  res.status(200).send();
});

En plus de l'url de l'événement, un webhook de réponse est appelé afin que le serveur d'application puisse indiquer à Voice API ce qu'il doit faire de cet appel.

L'API Voice s'attend à ce que cette information soit présentée sous la forme d'un fichier NCCOun objet JSON dans lequel le serveur d'application spécifie la ou les action(s).

Dans notre cas, le serveur d'application spécifiera l'élément action en tant que conversation et utilisera le sessionId comme nom du fichier conversation. Nous déterminons le sessionId soit en utilisant les en-têtes SIP qui sont ajoutés par OpenTok lors de la numérotation, soit en utilisant le code pin à quatre chiffres que l'utilisateur saisit via la fonction DTMF.

app.get('/nexmo-answer', (req, res) => {
  const { serverUrl } = config;
  const ncco = [];
  if (req.query['SipHeader_X-OpenTok-SessionId']) {
    ncco.push({
      action: 'conversation',
      name: req.query['SipHeader_X-OpenTok-SessionId'],
    });
  } else {
    ncco.push(
      {
        action: 'talk',
        text: 'Please enter a a pin code to join the session'
      },
      {
        action: 'input',
        eventUrl: [`${serverUrl}/nexmo-dtmf`]
      }
    )
  }

  res.json(ncco);
});

Dans le code ci-dessus, nous avons une instruction conditionnelle qui vérifie les en-têtes pour récupérer le fichier sessionId. Dans ce cas, s'il n'y a pas d'en-tête SIP, nous pouvons supposer qu'il s'agit d'un utilisateur du réseau téléphonique public commuté (RTPC), ce qui nous permet de lui demander un code PIN. Pour ce faire, nous définissons notre action à talk avec quelques text. Le serveur d'application indique également à l'API Voice que nous voulons recevoir le code dtmf au niveau du /nexmo-dtmf webhook.

Demande de code PIN

Lorsque le /nexmo-dtmf est appelé, nous vérifions la présence du code dtmf dans le corps de la requête et recherchons le code sessionId en fonction du mappage. Cela permet de s'assurer que nous connectons les utilisateurs WebRTC et PSTN dans la même conversation. Cela permet également de réutiliser le même numéro de téléphone virtuel avec plusieurs codes pin pour faciliter les conférences simultanées.

app.post('/nexmo-dtmf', (req, res) => {
  const { dtmf } = req.body;
  let sessionId;

  if (app.get(dtmf)) {
    sessionId = app.get(dtmf);
  }

  const ncco = [
  {
    action: 'conversation',
    name: sessionId,
  }];

  res.json(ncco);
});

Nous avons créé un /hang-up où nous pouvons utiliser la méthode forceDisconnect sur l'objet OpenTok pour déconnecter le participant SIP.

/**
* When the hang-up get request is made, the forceDisconnect method of the OpenTok API is invoked
*/
app.get('/hang-up', (req, res) => {
  const { roomId } = req.query;
  const { conferenceNumber } = config;
  if (app.get(roomId) + app.get(conferenceNumber + roomId)) {
    const sessionId = app.get(roomId);
    const connectionId = app.get(conferenceNumber + roomId); 
    OT.forceDisconnect(sessionId, connectionId, (error) => {
      if (error) {
        res.status(500).send('There was an error hanging up');
      } else {
        res.status(200).send('Ok');
      }
    });
  } else {
    res.status(400).send('There was an error hanging up');
  }
});

Enfin, nous spécifions un port et lançons le serveur express :

const port = process.env.PORT || '3000';
app.listen(port, () => console.log(`listening on port ${port}`));

Conclusion

Dans ce billet, nous avons couvert la connexion d'une session OpenTok avec des utilisateurs PSTN à l'aide de SIP Connect et de l'API Voice de Nexmo. Pour voir le code complet avec d'autres échantillons SIP, veuillez consulter le site web opentok-nexmo-sip repo.

Partager:

https://a.storyblok.com/f/270183/384x384/63f654d765/manik.png
Manik SachdevaAnciens de Vonage

Manik est ingénieur logiciel senior. Il aime travailler avec les développeurs et créer des API. Lorsqu'il ne construit pas d'API ou de SDK, vous pouvez le trouver en train de parler lors de conférences et de rencontres.