UnityでTwilioを利用して固定電話に架電する

2018年12月16日UnityTwilio, Unity

本記事は、Unity Advent Calendar 2018 12月16日の記事です。

はじめに

本記事ではTwilioを利用しUnityから固定電話(携帯電話)網へ接続(発信)して任意のメッセージや音声ファイルを流す方法を紹介します。

注意点として固定電話網に接続する都合上、実行の度に通話料が請求されます。デバッグなどの際にはご注意ください。

ソフトウェアは下記バージョンを使用しています。UniRxはasync周りを楽に使うために入れているので必須ではないです。

ソフトウェア バージョン
Unity 2018.3.0f2
Twilio 5.24.0
Microsoft.IdentityModel.Logging 5.3.0
Microsoft.IdentityModel.Tokens 5.3.0
Microsoft.IdentityModel.JsonWebTokens 5.3.0
Newtonsoft.Json 12.0.1
System.IdentityModel.Tokens.Jwt 5.3.0
UniRx 6.2.2

Unityの設定で初期値から変更した項目

設定項目名 設定
Scripting Runtime Version .NET 4.x(18.3系だとデフォ値)
Api Compatibility Level .NET 4.x

Unityと固定電話網を連携させるメリットについて

そもそもUnityという最新技術の百貨店的なものと、18世紀の技術である固定電話を連携させるメリット、というか使い所の話です。

本段落はただのポエム的な要素もあるので読み飛ばして頂いても問題ありません。

そもそも電話というのは割り込み型の連絡手段でエンジニア界隈ではどちらかというとネガティブな印象があり、忌み嫌われている傾向にあると思います。

ですが、割り込み型故のメリットとしてどの作業をしていても確実に気付くような通知がきます。

ということで最重要事項は電話で通知を受けるべきでしょう。概ね人間が掛けてくる電話は重要でない事が多いのですが、Unityから電話を掛けさせる場合は本当に重要な事項だけを連絡させることができます。

例えば、VR系ゲームの常設展示を行っている状況で続行不能なExceptionが出た場合にエンジニアに電話を掛けさせてその旨を通知させます。こうすることで、現場の担当者が気付いて「なんかゲームが動かなくなりました!」という情報が送られてくる前に現場に向かい、さらにエラー原因の当たりをつけることが可能になります。

運用としては電話で「重大なエラー。○○Exception、渋谷○○、○号機」みたいな最小限の情報を通知し、同内容とExceptionの全文をSlackに投稿さるようにしておけば便利です。

Twilio登録

電話網を利用したサービスを提供している会社といえばTwilioが有名です。日本ではKDDIさんが代理店を務めています。

Twilioは簡単に電話番号を取得できますし、多数の言語SDKの用意がされています。他には一つの番号で複数の架電なども可能です。試してはいないですがエンジニアが複数いる場合に同時に電話を掛けることも可能だと思います。

今回は扱いませんが、SMS等も対応しているのでSMS認証を作成することも可能です。ユーザーの数字キーを押させて処理分岐などもできるのでコールセンター的な物も作成できますし、音声ファイルも対応しているのでこの電話番号に掛けるとvTuberが喋ってくれる!的なモノも作成できるでしょう。

それではアカウントを作成していきます。Twilioのページから無料サインアップでアカウントを作成できます。アカウント作成時にSMSでの認証が必要となります。このSMS認証した携帯電話番号が無料アカウントでデバッグ可能な電話番号として登録されるので、開発で使用する携帯電話などで認証するのが良いでしょう。このデバッグ可能な電話番号は後ほど追加登録することも可能です。

アカウントを登録すると下記、管理コンソールに入れます。アカウント作成時にテスト用として500円がアカウントにプレゼントされます。

まず、開発に使う電話番号を購入します。青線で囲った場所番号を取得するを選択します。

番号購入ページでは様々な国の電話番号購入ができます。今回は日本の電話番号を購入します。日本の電話番号ではSMSを扱うことができないようなのでSMSを使用したい場合は米国などを利用しましょう。Twilio international phone number availability and their capabilitiesで各国ごとに対応している機能が確認できます。

キャリアの設定や電話番号によっては国際電話を受けられない物もあるので米国電話番号で日本宛に発信させる場合は注意してください。

番号購入時にはどの番号にするか選ぶ事ができます。また、検索して任意の番号の取得もできますが、基本的には050始まりの番号になるので末尾検索で行うと良いでしょう。

検索を入れても入れなくても候補の電話番号が複数表示されるので、このリストから心に訴えかける電話番号を探します。気に入らなければ下部のRefresh resultsで別の候補を表示できます。

候補に+81 800~というぱっと見、携帯電話的な電話番号がありますが、これはフリーダイヤル用の番号です。0120から始まる物と同じです。通話料無料でサービスを提供したい場合はこちらを取得することでフリーダイヤルサービスを作る事ができます。

電話番号を購入すると、アクティブな電話番号のページで購入した番号が確認出来ます。
この電話番号はコードを書く際に引数として設定するのでメモるのがオススメです。

検証済電話番号も重要なので確認します。このページにはアカウント作成時にSMS認証した番号が書かれています。前述のとおり、無料アカウント状態では検証済電話番号以外に発信できないので注意してください。

SDKインストール

TwilioのドキュメントはTwilio Docsにあり、一部日本語化もされたドキュメントが完備されています。

そしてTwilioはありがたいことにC#のSDKが用意されています。C#ドキュメントは下記にあります。

Twilio SDK for C# and .NET

C#のSDKはNuGet上にあるので取得しにいきます。NuGetForUnityなどUnityでNuGetを使う方法はいくつかありますが、今回は泥臭く手動で取りに行きます。NuGetからデータをしょっちゅう取得するなら良い感じのフローを考えますが個人的にはあまりないので……

NuGetのTwilioページ右側にあるDownload packageから最新版のnupkgファイルがDLできます。

nupkgファイルは拡張子をzipにすることで解凍可能なので、解凍してdllファイルを取り出します。

解凍後、lib/net451/Twilio.dllをUnityのPluginsフォルダに追加します。

他にTwilioが依存しているパッケージを同じように追加していきます。(5個も手動で依存パッケージ入れたくないわ!って人は前述のNuGetForUnityとか使って頂ければ……)

Microsoft.IdentityModel.Logging

Microsoft.IdentityModel.Tokens

Microsoft.IdentityModel.JsonWebTokens

Newtonsoft.Json

System.IdentityModel.Tokens.Jwt

注意点としてNewtonsoft.Jsonはよく使われるため、AssetやSDKなどに含まれていることが多いです。(MixedRealityToolkitとか)

すでにプロジェクトフォルダに無いか確認してから入れる事をおすすめします。

電話で話す内容を設定する

Twilioでは話す内容をTwiMLで記述します。このTwiMLファイルをHTTPでアクセスできる場所に設置する必要があります。

事前に固定文言が決まっていれば適当に作って上げておけば良いのですが、Exceptionを流したいので動的生成を行いたいです。動的生成を行ったあと、FTPなどでサーバーに配置するか稼働機自信がHTTPサーバーになるかを行う必要があります。後者の手法を採れるシチュエーションはほぼ無いので基本的には前者になるでしょう。

ただ、前者の方法もサーバーを建てるなど面倒かつメンテナンスの必要な作業になります。そこで別の手段として、Twilio LABSにあるEchoという機能を使います。

これはhttp://twimlets.com/echo?Twiml={TwiMLの全文}でTwilioがアクセス出来るTwiMLを動的に生成してくれる機能です。これでクライアントだけでメッセージを送ることが可能になります。

TwiMLを作る

Twilioに日本語で「テストメッセージ」と喋って貰うTwiMLが以下になります。

<Response>
    <Saylanguage="ja-JP" voice="alice">テストメッセージ</Say>
</Response>

英語であればvoiceは男性、女性などが選べますが、日本語はaliceという女性ボイスのみ対応となります。Amazon Pollyと連携ができるようなので、どうしても男性ボイスが良いという場合はそちらで設定できると思います。(試したことはないです)

TwilioのSDKにあるVoiceResponse()で簡単にTwiMLを構築できます。

Echoに投げる時にはもちろんURLエンコードしないといけないので、エンコードも含めてよしなにしてくれるメソッド作っておきます。

using Twilio.TwiML;

    /// <summary>
    /// 任意のテキストをTwiML形式に加工する
    /// </summary>
    /// <param name="text">通話で流すテキスト</param>
    /// <returns>URLエンコード済みTwiML</returns>
    private string makeTwiML(string text)
    {      
        var response = new VoiceResponse();
        response.Say("テストメッセージ", voice: "alice", language: "ja-JP");

        return Uri.EscapeUriString(response.ToString());
    }

電話をかける

ではいよいよメインディッシュ、電話をかけてみます。必要な情報は下記4点です。

  • AccountSID
  • AuthToken
  • 取得した電話番号
  • 発信先の検証済電話番号

AccountSIDAuthTokenはTwilioのダッシュボードから取得可能です。

というわけで、先ほどのTwiMLを作成するメソッドと通話発信用のメソッドを組み合わせた物がこちらになります。

using System;
using Twilio;
using Twilio.TwiML;
using Twilio.Rest.Api.V2010.Account;
using UniRx;
using UniRx.Async;

public class TwilioTest
{
    /// <summary>
    /// TwilioSID
    /// </summary>
    private readonly string m_twilioAccountSid;

    /// <summary>
    /// Twilioトークン
    /// </summary>
    private readonly string m_twilioAuthToken;

    /// <summary>
    /// Twilio電話番号
    /// </summary>
    private readonly string m_twilioPhoneNo;

    /// <summary>
    /// 通話中のCallResource
    /// </summary>
    private CallResource m_callResource;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="twilioAccountSid">TwilioSID</param>
    /// <param name="twilioAuthToken">Twilioトークン</param>
    /// <param name="twilioPhoneNo">Twilio電話番号</param>
    public TwilioTest(string twilioAccountSid,string twilioAuthToken,string twilioPhoneNo)
    {
        m_twilioAccountSid = twilioAccountSid;
        m_twilioAuthToken = twilioAuthToken;
        m_twilioPhoneNo = twilioPhoneNo;
    }

    /// <summary>
    /// 発信
    /// </summary>
    /// <param name="text">通話時のテキスト</param>
    /// <param name="phoneNo">発信先</param>
    public void Call(string text,string phoneNo)
    {
        callAsync(text, phoneNo).Forget();
    }

    /// <summary>
    /// 発信
    /// </summary>
    /// <param name="text">通話時のテキスト</param>
    /// <param name="phoneNo">発信先</param>
    private async UniTask callAsync(string text,string phoneNo)
    {
        //Client初期化
        TwilioClient.Init(m_twilioAccountSid,m_twilioAuthToken);

        //発信
        m_callResource = await CallResource.CreateAsync(
            url: new Uri("http://twimlets.com/echo?Twiml=" + makeTwiML(text)),
            to: new Twilio.Types.PhoneNumber(phoneNo),
            from: new Twilio.Types.PhoneNumber(m_twilioPhoneNo)
        );
    }

    /// <summary>
    /// 任意のテキストをTwiML形式に加工する
    /// </summary>
    /// <param name="text">通話で流すテキスト</param>
    /// <returns>URLエンコード済みTwiML</returns>
    private string makeTwiML(string text)
    {      
        var response = new VoiceResponse();
        response.Say("テストメッセージ", voice: "alice", language: "ja-JP");

        return Uri.EscapeUriString(response.ToString());
    }

長々と書いていますが、通話を送っているメソッドは下記部分です。

    /// <summary>
    /// 発信
    /// </summary>
    /// <param name="text">通話時のテキスト</param>
    /// <param name="phoneNo">発信先</param>
    private async UniTask callAsync(string text,string phoneNo)
    {
        //Client初期化
        TwilioClient.Init(m_twilioAccountSid,m_twilioAuthToken);

        //発信
        m_callResource = await CallResource.CreateAsync(
            url: new Uri("http://twimlets.com/echo?Twiml=" + makeTwiML(text)),
            to: new Twilio.Types.PhoneNumber(phoneNo),
            from: new Twilio.Types.PhoneNumber(m_twilioPhoneNo)
        );
    }

TwilioClient.InitにAccountSIDとAuthTokenを渡すとTwilioの利用準備が完了します。

CallResource.CreateAsyncで通話発信です。第一引数がTwiML、第二引数が発信先電話番号、第三引数が取得した電話番号となります。

電話番号の引数は+81を含めた国際電話番号表記にする必要があります。

例えば、090-0000-1234という電話番号の場合は+819000001234という電話番号になります。

使われていない電話番号や発信が出来なかった場合はCallResource.CreateAsyncでExceptionが出るのでCatchしてあげて下さい。

なお、冒頭に書いたトラブル発生時のException通知用途で使う場合はここでもExceptionが出たらどうしようも無いのでもう諦めて良いと思います。

予め用意した音源を再生させる

Twilioには先ほど解説したテキスト読み上げ以外にmp3ファイルなどを再生する機能も有ります。

淡々とExceptionの事実を告げられるのは辛い、という方は結月ゆかりさんとかに予め読み上げていただいたmp3とかを用意して流すと幾分心が軽くなります。

TwiMLを以下のように書くと指定したURL場所のmp3が再生されます。

<Response>
    <Play loop="1">https://hogehoge.com/hogehoge.mp3</Play>
</Response>

Play loopの数だけループ再生されます。0で無制限ループになります。

C#上で音声再生のTwiMLを生成する場合は下記で生成できます。

      var response = new VoiceResponse();
        response.Play(new Uri("https://hogehoge.com/hogehoge.mp3"), loop: 1);

なお、電話なので音質は期待しないで下さい。

他、音声再生については下記ドキュメントを参照してください。

TwiML™ Voice:

通話を途中で切断させる

Twilioは発信してしまえば、あとはTwiMLを全て再生するか受話側が切らない限りコールし続けます。

基本的にはそれで問題無いと思いますが、明示的に切断したいという場合は下記で切断ができます。

    /// <summary>
    /// 通話中のCallResource
    /// </summary>
    private CallResource m_callResource;

        /// <summary>
        /// 通話の終了
        /// </summary>
        private async UniTask disconnectAsync()
        {
              //現在通話中のID
                string sid = m_callResource.Sid;

                //通話切断
                CallResource callUpdate = await CallResource.UpdateAsync(
                    status: CallResource.UpdateStatusEnum.Completed,
                    pathSid: sid
                );
            }

先ほど、発信時にメンバ変数へ保存しておいたCallResourceを使用します。この中に通話中のIDが入っているので、そのIDのStatusをCompletedにすることで通話を終了させます。

あとがき

以上で、Unityから電話網への発信ができるようになりました。

これで現場稼働中の端末異常管理もより良く行う事ができます。電話が鳴らないと良いですね。

Twilio自体はユーザーのプッシュ操作などで処理分岐などコールセンターなどを構築するのに十分な機能を沢山持っています。昔、電話でアニメキャラと話せるみたいな物もありましたが、今風にVtuberと話せるみたいな電話番号があっても面白いかなと思ったり思わなかったり。(生配信でコミュニケーション取れるので需要は無いと思う)


最後に、

「え?設置現場にネットワークが無い?」

うん、多いよねそんな環境。