https://s3.amazonaws.com/a.storyblok.com/f/270183/130643/14eada5a9e/opentok-sip.png

OpenTokとNexmoによるWebRTCとPSTNの接続

最終更新日 May 13, 2021

所要時間:1 分

ネクスモでは最近 SIP Connectを発表しました。この機能により、WebRTCエンドポイントをNexmo Voice APIに接続することができます。この機能により、PSTNユーザーがOpenTokビデオセッションにダイヤルすることが可能になります。

この投稿では、OpenTokを使用してリアルタイムのビデオWebアプリケーションを構築し、SIP ConnectとVoice APIを使用してPSTNユーザを接続します。

前提条件

作業を始める前に、以下のことをご確認ください:

はじめに

TokBoxAPIプロジェクトを作成してください。 apiKeyapiSecretが必要なので、TokBox APIプロジェクトを作成してください。TokBoxの認証情報に加えて、Nexmo Voiceアプリケーションを作成し、イベントと応答のWebhookを設定する必要があります。

その内容については、仕事をしながら説明していくので、今は心配しないでほしい。最後に Nexmoバーチャル番号を購入し、その番号にかかってきた電話をすべて作成したアプリケーションに転送する必要があります。

概要

architecture

サンプルコード

まずは をクローンしてください。リポジトリをクローンし Dial-In-Conferenceディレクトリに移動してください。

プロジェクト・ディレクトリに config.example.jsファイルがあります。このファイルの内容をコピーして config.js.

アプリで使用するため、先ほど生成したTokBoxとNexmoの認証情報を必ず config.jsファイルに追加してください。

クライアント側コード

今回はウェブ用のJavaScriptを使っていますが、OpenTokでも同じコンセプトが使えます。 iOS, Androidおよび WindowsSDK。

以下のように opentok.jsフォルダにある public/jsフォルダにあるファイルで initSessionメソッドを呼び出すことでセッションを初期化しています。 OTオブジェクトのメソッドを呼び出すことで、セッションを初期化しています。次に オブジェクトを作成します。オブジェクトを作成します。 initPublisherメソッドを使用してPublisherオブジェクトを作成します。

次に、以下のセッションイベントを設定する:

  • streamCreated

  • streamDestroyed

  • sessionConnected

これらのイベントは、ストリームが作成されたとき、ストリームが破棄されたとき、クライアントがセッションに接続したときに、それぞれトリガーされる。

イベント・リスナーを設定した後、トークンとエラー・ハンドラを渡してセッションに接続する。エラー・ハンドラは、セッションに接続しようとしてエラーが発生しなかったことを確認するために使用される。私たちのアプリでは、エラーがあればコンソールにログを記録しますが、本番アプリケーションでは、UI要素を表示して再接続を試みます。

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');
 }
});

この opentok.jsファイルは views/index.ejsファイルにインポートされます。

このコードに加えて、SIP経由でダイヤル発信するためにアプリサーバーへのAPIリクエストをトリガーするボタンもいくつか作成する。

このアプリでは、このビューは私たちのサーバーによってレンダリングされていますが、好きなようにレンダリングすることができます。このビューがレンダリングされているコードを見るには、以下をご覧ください。 リンク.

サーバー・サイド・コード

クライアントのセットアップができたので、次は サーバーコード.

をインポートしていることに気づくだろう。 express, opentokそして body-parserパッケージをインポートしていることに気づくだろう。私たちは を使っている。を使用しています。 OpenTok Node SDKそして ボディ・パーサーライブラリがあります。

で設定した configファイルで設定した config.jsファイルで設定した

次に、以下のエンドポイントを作成する:

  • /room/:roomId

  • /dial-out

  • /hang-up

  • /nexmo-answer

  • /nexmo-dtmf

  • /nexmo-events

パス /room/:roomIdパスはアプリのメインパスだ。これは index.ejsビューをレンダリングします。このとき、アプリサーバはOpenTokにセッションを作成するようリクエストし、OpenTokはセッションオブジェクトに sessionId.

この sessionIdを使用してOpenTokトークンを生成します。次に4桁のピンコードを生成し、それを sessionIdと部屋名に対応付けます。これは重要です。 sessionIdを調べるのに必要だからです。また、ユーザが同じ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);
    });
  }
});

ダイヤルアウト

SIPエンドポイントにダイヤル発信するには、ブラウザがエンドポイントにリクエストを行い、サーバーがトークンを生成し、Nexmoの認証情報(APIキーとAPIシークレット)をSIP uri ( /dial-outサーバはトークンを生成し、Nexmo の認証情報 (API Key と API Secret) と SIP uri (sip:lvn@sip.nexmo.com) を使って OpenTok にリクエストし、セッションをダイヤルアウトします。これが成功すると、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);
    }
  });
});

SIP経由でNexmo Voice APIにダイヤルすると、次のイベントがトリガーされます。 イベントウェブフック, /nexmo-eventsこの場合、ステータスの変化、つまり started, ringingなど。

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

イベントURLに加え アンサーウェブフックが呼び出され、アプリケーションサーバーは Voice API にその呼び出しに対して何をすべきかを伝えることができます。

Voice APIはこれを NCCOJSONオブジェクトで、アプリサーバーがアクションを指定します。

この場合、アプリサーバーは actionconversationとして指定し sessionIdの名前として conversation.を決定します。 sessionIdを決定するには、ダイヤル発信時に OpenTok が追加する SIP ヘッダを使うか、ユーザーが 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);
});

上のコードでは、ヘッダーをチェックする条件文で sessionId.この場合、SIPヘッダーがないときは、PSTNユーザーがダイヤルしてきたと仮定して、ピンの入力を促します。このためには actiontalktext.また、アプリサーバーはVoice APIに、. dtmfコードを /nexmo-dtmfウェブフック。

暗証番号の入力

ウェブフック /nexmo-dtmfウェブフックが呼び出されると、リクエストボディにある dtmfコードをチェックし、マッピングに基づいて sessionIdを検索します。これは、WebRTCとPSTNのユーザーを同じ会話で接続していることを確認するために行われます。これにより、同じ仮想電話番号を複数のピンコードで再利用し、同時会議を促進することもできます。

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);
});

我々は /hang-upエンドポイントを作成しました。 forceDisconnectオブジェクトの OpenTokオブジェクトのメソッドを使用できるエンドポイントを作成しました。

/**
* 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');
  }
});

最後に、ポートを指定してエクスプレス・サーバーを実行する:

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

結論

この投稿では、SIP ConnectとNexmo Voice APIを使って、OpenTokセッションとPSTNユーザとのブリッジングについて説明しました。他のSIPサンプルを含む完全なコードを見るには opentok-nexmo-sipリポジトリをご覧ください。

シェア:

https://a.storyblok.com/f/270183/384x384/63f654d765/manik.png
Manik Sachdevaヴォネージの卒業生

シニア・ソフトウェア・エンジニア。開発者と協力してAPIを作るのが好き。APIやSDKを構築していないときは、カンファレンスやミートアップで講演している。