ClovaとLINE Botの一味違う２つの連携手法～タイミング同期＆無限セッション～

## はじめに
本記事は [LINEBot&Clova Advent Calendar 2018](https://qiita.com/advent-calendar/2018/linebot) の23日目の記事です。

Clovaの特徴であり最大のメリットは、LINE Botとの連携ですね。
ClovaとLINE Botでは、それぞれ同一のLINEアカウントであれば同一のユーザーIDを使うので、簡単に連携したサービスを作ることができます。
しかし、ClovaのAPIの使い方を工夫することで、ClovaとLINE Botを一味違ったレベルで連携させることができます。
この記事では、単純なデータのやりとりにとどまらない、Clovaの音声とBotの連動方法を２つ、実際の活用例をもとにご紹介します。

## 本記事で紹介すること
ClovaとLINE Botを「音声」の観点で連動させる、以下の２つの手法です。

### ① Clova音声とLINEメッセージ送信の「タイミング同期」
これは、 **Clovaから連続して再生される複数の音声に合わせて、LINEに対してメッセージを送信する** というものです。
Clovaから再生される音声や音楽に合わせて、タイミングよくLINEにメッセージを送ることができます。

### ② Clovaスキルを一時停止させLINEからの操作待ち状態を継続させる「無限セッション」
スマートスピーカースキルとしては斬新かつ裏技的な手法なのではないかと思いますが、これは **Clovaスキル起動中に一時停止を行い、LINEからClovaスキルに対してさまざまな入力・指示を行う機能を提供する** ということができるというものです。
これを使うと、「ユーザーとの対話の中で音声アシスタントはいつまでもユーザーの発話を待ってくれない」というVUIの常識を覆す、スキルの **無限セッション** を得ることができます。


## 実装例と前提
「Clova＆LINEで絵本読み聞かせ」のスキルを実装例とします。
こちらはLINE BOOT AWARDS 2018にエントリーしファイナルまで進んだものです。
この作品づくりの中で使った手法なので、これをそのまま例に解説させてください。

参考：[作品紹介](https://www.line-community.me/awards/workdetail/5bb572e04016908c8d8ea020) ＆ [技術解説](https://qiita.com/himarin269/private/e707cf68bd07452610b9)

今回紹介する手法では、ClovaのAPIのうち、mp3のオーディオ再生を指示する機能である「[AudioPlayer](https://clova-developers.line.biz/guide/CEK/Guides/Build_Custom_Extension.md#ProvideAudioContent)」を使います。
この機能は音楽や録音済みの音声などを再生させるほか、各種text-to-speechのAPIを使ってmp3ファイルを合成することで、テキストの読み上げを行うこともできます。
※上記スキルでは[AITalk WebAPI](https://www.ai-j.jp/cloud/webapi/)を使いました

## 詳細解説①：Clova音声とLINEメッセージ送信の「タイミング同期」
### 実装例
まずは実装例です。
※動画です。クリックするとYouTubeに飛びます
[![実装例①](http://img.youtube.com/vi/m7r-yhAgOAc/0.jpg)](https://www.youtube.com/watch?v=m7r-yhAgOAc)

### 解説
Clovaから読み上げられる音声に合わせ、タイミングよくLINE Botへメッセージ（ここではイメージマップ）が送られてきます。

音声は、AudioPlayerの機能を使っているのですが（テキストをmp3に合成したものを再生している）、Clovaではオーディオ再生時に様々なイベントを通知してくれます。

* [AudioPlayer.PlayStartedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#PlayStarted)
* [AudioPlayer.PlayPausedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#PlayPaused)
* [AudioPlayer.PlayResumedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#PlayResumed)
* [AudioPlayer.PlayStoppedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#PlayStopped)
* [AudioPlayer.PlayFinishedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#PlayFinished)

このうち、 `AudioPlayer.PlayFinished` イベントが鍵になります。

仕組みはこうです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/194664/9419818d-0d52-76d7-32f9-35bf15e4f956.png)

オーディオファイルの再生がClova側で終了すると、 `AudioPlayer.PlayFinished` イベントが `EventRequest` としてサーバーに送信されるので、そのタイミングで次のオーディオ再生指示をClovaへのレスポンスとして返し、それと同時に連携しているLINE BotでClovaスキルを使用中のユーザー宛にPushメッセージを送ります。


### 応用例
また、終了時以外にも、

* [AudioPlayer.ProgressReportDelayPassedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#ProgressReportDelayPassed)：再生が開始してから特定の時間が経過した後、再生の進行状況をレポートする
* [AudioPlayer.ProgressReportPositionPassedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#ProgressReportPositionPassed)：オーディオコンテンツの特定の位置(オフセット)を再生するときに、進行状況をレポートする
* [AudioPlayer.ProgressReportIntervalPassedイベント](https://clova-developers.line.biz/guide/CEK/References/CEK_API.md#ProgressReportIntervalPassed)：再生中の場合、特定の間隔で繰り返し進行状況をレポートする

といったイベントが用意されているので、これらを利用し、長めのオーディオファイルの任意のタイミングでPushメッセージを出すといった応用も可能なので、いろいろな活用例が考えられます。

特に `AudioPlayer.ProgressReportIntervalPassed` を利用すると、短い間隔でイベントを受け取るようにすれば、その中に含まれる現在の再生位置情報（`offsetInMilliseconds`）をもとにかなり細かい精度でオーディオ再生位置に合わせたPushメッセージを送ることができるので、たとえばアイディアの一例として

* 演奏に合わせて歌詞が送られるカラオケスキル
* 英語スピーチに合わせて字幕や単語訳が送られる英語学習スキル

などが実現できるかと思います。

ただし、この手法はLINE Bot（Messaging API）の `Push API` を前提とするので、友だち50人限定・お試し用の「Developer Trial」プランか月32,400円の「プロ(API)」でしか使えない点で注意が必要です。

## 詳細解説②：Clovaスキルを一時停止させLINEからの操作待ち状態を継続させる「無限セッション」

続いて２つめです。こちらもまずは実装例の動画から。
[![実装例②](http://img.youtube.com/vi/Q_kaIyEdBW8/0.jpg)](https://www.youtube.com/watch?v=Q_kaIyEdBW8)

## 解説
こちらも、さきほどの手法と同様に `AudioPlayer.PlayFinished` イベントを利用しています。
無限セッションを得るためにごく短い長さの“無音のmp3ファイル”を利用しています。
無限ループさせるかどうかの値をサーバー（DBなど）に持たせておき、それがtrueである間、「PlayFinishedのEventRequest」「無音mp3の再生」を連続で行わせます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/194664/98882f3e-96a5-3832-231c-6e57632ed62a.png)

これにより、Clovaスキルを待機状態にし、理論上無限のセッションを得ることができます。

無音無限ループによる「無限セッション」を作り出すことで、「絵本読み聞かせ」で行ったように一時停止・再開の機能を提供したりすることができるほか（再開指示でフラグをfalseに変えてあげればいいだけ）、ユーザーにLINE Botから複雑で時間のかかる入力を行わせることができるようにもなります（フラグをfalseにしつつ入力データの登録処理も実行）。
もちろん、無限セッションといえど音声でClovaに指示をすることもできるので、音声のみでスキルを終了させることもできます（逆にいえば、実際にこれを使ったスキルを作成する場合は無限ループ中にスキルを終了された際の考慮も必要ということになります）。

### 応用例
たとえば「絵本読み聞かせ」スキルへの応用で、たとえば、「ウォーリーをさがせ！」のようなこともできます。
「ウォーリーを探してね」という音声とともにイメージマップでLINEに画像を送り、無音無限ループの無限セッションに入ります。

ユーザーはイメージマップの中から探すべきウォーリーの部分を探すための時間が与えられ、見つけたら該当箇所をタップ、あらかじめその位置に設定しておいたタップイベントで正誤判定を行います。
また、無限ループとせず無音mp3の最大再生回数を決めておけば、解答までの制限時間を設けることもできます。
ちなみに「絵本読み聞かせ」スキルですが、現在開発中の公開版ではこんな感じの「仕掛け絵本」が楽しめるような機能をつけていく予定です（アワードに出したものは一時停止＆再開のためにしかにこれは使ってません）。

## サンプルコード
手法に関する説明は以上です。
Azure Functions＋C#での実装となりますが、「Clova＆LINEで絵本読み聞かせ」のLINE BOOT AWARDS 2018バージョンのコードはGitHubですべて公開しています。
https://github.com/himanago/ClovaLinePictureBookLineBootAwards2018ver

本記事に関係あるのは主に [ClovaExtensionFunction.cs](https://github.com/himanago/ClovaLinePictureBookLineBootAwards2018ver/blob/master/ClovaLinePictureBookLineBootAwards2018ver/ClovaExtensionFunction.cs) です。
`AudioPlayer.PlayFinished` イベントの処理で、一時停止フラグ（DBの値）をもとに ①次の再生＋Push / ②無音無限ループ を分岐させています。
なお、[ClovaのC# SDK（コミュニティ製）](https://github.com/kenakamu/clova-cek-sdk-csharp)はオーディオ関連の機能に対応していなかったため、この部分は直接JSONを解析して処理しています。

```cs:ClovaExtensionFunction.cs（抜粋）
// オーディオイベントの制御
// Clovaでのオーディオ再生が終わった際に呼び出される
if (reqObj["event"]["namespace"].Value<string>() == "AudioPlayer")
{
    var userId = reqJson["session"]["user"]["userId"].Value<string>();
    var beforeId = reqObj["event"]["payload"]["token"].Value<string>();
    var eventName = reqObj["event"]["name"].Value<string>();
    var cache = await PlayStatusService.FindCacheAsync(userId);
    switch (eventName)
    {
        case "PlayFinished":
            // LINE Botのメニューから停止要求がされていれば呼び出さない
            if (cache?.StopRequest != null && cache.StopRequest.IsStopped)
            {
                // DBに存在する停止レコードに、直近の再生済みオーディオのIDを更新する
                cache.StopRequest.BeforId = beforeId;
                cache.StopRequest.IsPaused = false;
                await PlayStatusService.SaveAsync(cache);
            }
            else if (cache?.StopRequest != null && cache.StopRequest.IsPaused)
            {
                // 一時停止状態の場合は無音を流す（無限ループ）
                response.Response.Directives.Add(GetAudioResponseDirective(beforeId, "一時停止中", Consts.SilentAudioFileUrl));
                response.ShouldEndSession = true;
            }
            else
            {
                // キャッシュ削除
                await PlayStatusService.DeleteAsync(cache);

                // 次のオーディオ再生（画像送付があればそれも）を実施
                await AddAudioPlayAndPushPictureAsync(response, userId, null, beforeId, log);
            }
            break;

        case "PlayPaused":
            // 再生中断された場合
            cache.StopRequest.IsStopped = true;
            await PlayStatusService.SaveAsync(cache);
            break;

        case "PlayStarted":
            // 再生開始OK：DBに記録
            // 再生開始時はStopped=falseで登録
            if (cache?.StopRequest == null)
            {
                await PlayStatusService.AddCacheAsync(userId, beforeId);
            }
            else
            {
                cache.StopRequest.BeforId = beforeId;
                await PlayStatusService.SaveAsync(cache);
            }
            break;
    }
}
```

## おわりに
今回は、LINE BOOT AWARDS 2018での開発を通して確立したAPI応用方法を紹介させていただきました。
「絵本読み聞かせ」も、アワード向けのものから大幅リニューアルしてストア公開を目指すことにしました。
アワード向けに開発した「絵本読み聞かせ」では、自動で再生させる音声にあわせてメッセージを送る＋好きなタイミングで一時停止・再開というかたちで、自分のペースで絵本の読み聞かせを楽しむことができる機能を提供しましたが、Push APIを前提とする実装方法であったため、個人開発して一般公開するにはハードルが高くなってしまいました。

そこで無音無限ループの無限セッションを利用し、絵本のページ送りをBotから手動で操作するようにすることで、Reply APIのみでほぼ同様の機能を実現できるよう考えています（LINE Botからの次ページ要求操作に対する返信として絵を送るようにすればフリープラン内で実装可能）。

とはいえ、そこまでいくと一見「音声で操作する」というスマートスピーカーの特徴・存在意義を放棄しているかのようにも思えてしまいます。
「VUI」ではなく「GUI」での操作であり、それならば同様のゲームや読み上げをアプリとして提供し、わざわざスマートスピーカーを使わせずスマートフォン/タブレットのみで完結すべきと考えることもできるでしょう。
しかし、普通のスマートスピーカーではなく、顔のついたスマートスピーカーである「Clova Friends」や「Clova Friends mini」を持つClovaなら、こういったスキルの提供も意味を持つのではないか？と考えています。
あえて音声による案内を流すのをスマートフォン/タブレットから切り離し、ブラウンやサリーなどのかわいらしいキャラクターをかたどったデバイスが話してくれることで、まるでそのキャラクターとともに遊んでいるような体験を得られるはずです。
先に述べた仕掛け絵本のような機能とともに、Clova/スマートスピーカーでしか実現できないようなスキルといえるようなものを目指して設計・開発を進めていきたいと思っています。
VUIやスマートスピーカーはまだ新しい技術なので、もしその中でまた新しい手法が見つけられたら、どんどん共有していきたいと思います。