APL(Alexa Presentation Language)で音声同期アニメーションを作る

#こんにちは
作ろう、こたつスキル！

最近は黙々とスマートスピーカーで「こたつスキル構想」を進めています。
「こたつスキル構想」とは…ざっくり言うと、

日本は家が狭いので（偏見）こたつに潜ってこたつみかんを食べながら目の前の画面を楽しむスキルがあってもいいではないか！でも音声デバイスであることは忘れるなよ！

というコンセプトになります。

日本で画面つき端末と言うとamazonのecho showなどを思い付きますが、LINEはLIFFがあるし、GoogleAssistantもカード表示が自由度あるので、作りたいプラットホームはたくさんあるのです。

と言っても、うち、こたつ無いんですけどね！ヨホホホ！（ブルック風に）

#きっかけ
最近はことさら、画面付きスマートスピーカーデバイス（amazon echo spotなど）のAPL(Alexa Presentation Language)でできることの可能性を探っていますが、そのなかで今回特に注目しているのはスライダーやカルーセルなどとも言われるような、複数のページを横に配置してスライド表示できる
Pager
という機能です。

APLには表示したものの挙動を制御する仕組み(ExecuteCommand)があり、その中でもPagerで表示するページを変更できる
SetPage
というコマンドがあるのですが、このSetPageのページ指定には絶対指定と相対指定の２種類がありまして。

相対指定をすると(+1や-1)、
「あぁ、右や左にスライドするんだろうなぁ」
という想像がつくのですが、ここで絶対指定で遠いページを指定したときの挙動はどうなるんだろう？と瞬間的に思いを巡らせるわけです。

どうなるんだろう？：
1. 遠い分だけ移動速度が増す
2. 瞬間的に切り替わる
3. ほわっとクロスフェードする

ここで私は割と何でも「本来とは違う面白い使い道はないか」を考えてしまうひねくれ者なので、上記例だとそれぞれ

1. ⇒スクロールの速さに強弱つけれたら動体視力ゲームとか作れそう！
2. ⇒パラパラアニメーションとかできるかな？
3. ⇒つまんね…

という妄想に至るわけです。（ここまで0.5秒）

# ためしてみる
早速簡単なスキルを作って試してみたわけですが、結果は

2.瞬間的に切り替わる

でした。
これ実は一番期待していた動作です。
というのも、echoが画面付きになったもののクライアントJavaScriptやHTML5はおろかアニメPNGも許されず、動画は制御が不自由で…ということで「カスタムスキルで扱いやすい、動きのある視覚レスポンス」に長いこと飢えていたわけです。
待っていたぜAPL！

実際に試作したのが下記動画になります。（スキルは非公開）
[Alexaで音声同期をとったパラパラアニメーション(YouTube)](https://youtu.be/WdG-3-hGQ8o)

ここでの確認目標は、

1. ちらつきなくブレなくアニメーションできるか
2. 各コマ(ページ)ごとに同期して音声出力できるか
3. アニメーション後、会話を続けられるか

といったあたりですが、結果としては

1. ⇒ちらつき無し・ブレ無し（※ちらつきは環境や指示のしかた次第で発生するのですが、この時点では気づかず）
2. ⇒コマごとの音声を設定する方法なら同期可能（ぴったり同時ではないですが許容範囲かな）
3. ⇒（上記動画には入れませんでしたが）アニメーションのあとそのまま会話継続可能(発話待ちに出来る）

上記のように概ね（自己）満足のいく結果となりました。

# 実用化に向けて
今のところ同じような事やってるスキル見かけないし、早速入れたれ！と以前公開した拙作のスキル「[強欲なうさぎの迷路](https://www.amazon.co.jp/dp/B07GT2CWDG)」をAPL対応のついでにアニメーション演出を組み入れ、スキル公開しました。うさぎはとっても強欲なのです…。

が！

公開スキルとしてアニメーションを行うには実際にはいくつかの壁（や、迷路の壁じゃなくて困難の壁！）がありました。

1. APLのSpeakItemコマンドではmp3は再生できない。小一時間悩んだあとに、Amazon Developer Forumsに「そのうち対応する」的なことが書いてあった（すいませんURL忘失）  
(2019/5/27追記)APLでmp3再生する方法ありました。後でざっくり書きます。  
(2019/5/28追記)書きました。 [APL(Alexa Presentation Language)のSpeakItemでmp3を鳴らす](https://qiita.com/kyukkyu81/items/30537335f38940cb170f) 
2. ちらつく。読み込みが間に合わないと、画面がクロスフェードやフェードアウト状態になってしまい、ちらつくので格好悪い。
3. アニメさせちゃうとリプロンプト（無回答時の聞き直し）できない。
4. APLの指定が冗長になり、読みにくく書きにくい。
5. APLの指示が約24KByteを超えるとエラーになる。

こちらについては、地道な試行錯誤の果て、おおよそ次のような方法で改善しました。

1. ⇒「通常の発話でmp3鳴らす」⇒「APLでアニメしながら同期音声」という流れにする。
2. ⇒0ページ目に「これから表示する全ての画像を重ねたページ」を最初に表示することでデバイスのキャッシュに頼る。それでも300msec以下のアニメーションは厳しいかな…。  
(2019/5/27追記)Pagerの外でWidth=0,Height=0で表示しておく方法もありますね、その方が確実かも。
3. ⇒画面タッチをするスキルではないのでリプロンプトしたいところだが…reprompt指定しておいても機能しないみたい？。このまま15秒くらい放置すると終了してしまうが、時計表示に戻る前に「アレクサ、はい」などと喋るとスキルが拾って継続可能。
4. ⇒APLのRenderDocument/ExecuteCommandsのJSONをパターン化して外部ファイルに追い出す。CSVで画像リスト・音声リスト・シナリオを指定できるような形式に汎用化。
5. ⇒凝りすぎない。ほどほどにする。

# ディレクティブ解説
図表のセンスが無くて恐縮です。好きですパステルカラー。
ディレクティブで送るJSONを図にしたものです。
JSONのままだと冗長で理解しづらいと思い、図にしてみたのですがまぁ色々言われそうな仕上がりに。色々だけに。

![APLのディレクティブ構成図.png](https://qiita-image-store.s3.amazonaws.com/0/309295/e4873603-665e-d8e7-e84e-e3aeccea45ab.png)

RenderDocumentのディレクティブで画面＋音声の「設置」を、
ExecuteCommandsのディレクティブで「動作」を指示しています。
※ 個々のプロパティについては割愛します。

### RenderDocument
とりあえず「盛り付け」「具材」に分かれていると考えると分かりやすいでしょう。

### RenderDocument/datasources
具材です。画像や発話内容が入ります。
datasources配下は好きに命名してOKで、盛り付けの時に具材の場所を示すときに使います。
（propertiesの中にimageも入れたほうがいいのかな…？とりあえず今稼働している状態で正直に書いておきます）
transformersだけは名前が決まっており、SSMLを表示用テキストに変換してくれたりしますが、特に変換の必要が無ければ使わなくて良いと思います。

### RenderDocument/document/mainTemplate
盛り付けです。まぁテンプレートです。
今回全画面アニメーションを作ったので、ページレイアウト自体は
- 幅0で発話内容を隠しておく
- 幅100vwでPagerを設置
というシンプルなモノです。
Pagerの各ページには、アニメーションの１コマ１画像で指定します。先ほど述べた通り、最初のページはちらつき防止のために「先読みLoading...」のつもりで全画像を詰め込んでいます。
Containerのwhenパラメータで、echo spotとecho show、その他デバイスを出し分け可能です。私はとりあえずデフォルトデザインに倣ってecho spotとそれ以外の２つコンテナ用意しましたが、spotとshowで素材を分けるのが面倒でしたので下記の指定をしつつ全く一緒のレイアウト指定にしています。
あくまで一例として参考程度に。
- 画像は1024 x 600
- best-fillで表示
- 中央の600x600だけを使う。両脇は余白（グレー）

### ExecuteCommands

まず、全画像を詰め込んだ0ページ目を表示します(SetPage)。この瞬間、あとで使用する画像が先読みされてる…はず…
エビデンス無いけど、これするとしないとではだいぶちらつき違いました。
でも20枚くらい読みこませたら流石に限界があった(ちらつきが改善しなくなった)ので、ほどほどに。

1ページ目からは、コマ（画像）と音声を組み合わせてペアにしていきます。
Parallelでくるむことで同時実行されます。（パラレルワールドのパラレルやね。綴りが覚えられない。）

SetPage：表示したい画像を置いたページを絶対指定。
SpeakItem：発話したい内容を指定（さっきの具材です）。ここで大事なのはDelayパラメータ。「そのページを表示するまでの遅延時間」をミリ秒で指定します。「表示してから待つ」のではなく、「待ってから表示」なので注意。このタイミングでリソース読み込みするようで、初めて画像登場のタイミングであまり短い時間にしちゃうと画像出てこず、ちらつきます。単純な繰り返しなら1/60secでも表示はされます。

もし、「１つの発話してる間に複数の画像を切り替えるような状態」を作りたい場合は、
「Sequencialで囲ったSetPageたち」と「SpeakItem」をParallelで囲えばOK！

最後に上記数多くのペアを順番につなげたいので、全体をSeqencial（順番実行）で囲って完了です。
# 実装サンプル
実際に公開したスキルの動画がこちらになります。若干ちらついてる？

[APLでアニメーション(YouTube)](https://youtu.be/4c0clBubNZ0)

# これから
スキルが公開できたので、きっとAPLによるアニメーション表現はamazonに受け入れられたのでしょう！(笑)

実は一番苦労したのは汎用化かもしれません。

今後は更にレイヤーや込み入ったレイアウトと組み合わせて新たな表現を試してみようと、次のスキルを模索中です。VoiceでRogue作りたい。＼(^o^)／

これで今後サクッと仕様がひっくり返ったら泣きますな…( ´∀｀ )

でも本当はいま一番気になるのはIn-Skill Purchaseです！（￥▽￥）／