Alexa Presentation Language (APL) で Twitterタイムライン読み上げスキル を改良　（「SpeakListコマンド」）

#概要
- [先日作った「タイムライン読み上げ」スキル](https://qiita.com/nemui1999/items/7c34565062ef394ce0e7) の画面表示があんまりなので何とかしたかった
- APLに、読み上げながらスクロールさせる方法がいくつかあったが、「SpeakListコマンド」を採用
- APLと「SpeakListコマンド」の実装を雰囲気だけ紹介
- スキルの余談

#Twitterタイムライン読み上げスキルの画面表示をどうにかしたい！
先日作ったスキルでは画面付きデバイスのことをあまり考えていなかったので、
画面付きデバイスで使用すると、カード情報そのままで、こんな画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277692/cba35f1c-953d-9d4d-7ea1-2954fea7ce13.png)
実機ではなくてシミュレータでの表示です。
ツイートは人様のものなのでぼかしです。

どうにも文字が大きくて見られる情報量が少ない上、スクロールもしません。（実機で手で動かせば動くかも。）
これではあんまりなので、最低限レイアウトを作って、読み上げに合わせてスクロールさせたいと思っていました。

#画面レイアウトとスクロールを実現する方法は？
画面付きデバイス用にAPLが用意されてるのでこれは使いますが、
喋らせるだけのときのお手軽スキル開発と比べるとできることが多くて、
使い方を覚えるのと、どれが使えるのか選ぶのが一苦労です。

ありがたい情報はたくさんありました。一部だけ紹介。
[Alexa Presentation Language（APL）の概要（公式ドキュメント）](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/understand-apl.html)
[APLトレーニングシリーズ第1回: 初めての Alexa Presentation Language (APL) (AlexaBlogs)](https://developer.amazon.com/ja/blogs/alexa/post/a3712152-cd02-4fda-9d33-3b64ef3a13af/jp-alexa-presentation-language-part1)
[Alexa ハローAPL、Alexaスキルの画面への対応](https://qiita.com/Mount/items/72d9928ff2c0ae5de737)

何から手を付ければ良いやらな多機能ぶりですが、
スクロールさせるみたいなことするには「コマンド」が必要になり、
コマンドを選ぶとそれを使用できる「コンポーネント」も限られてくるので、
コマンドから選ぶといい感じでした。

[APLコマンド （公式ドキュメント）](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-commands.html)

コマンドもたくさんありまして、スクロールしそうなのを見ていくと・・・

###[AutoPageコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#autopage-command)
「Pagerコンポーネントの連続するページを自動的に進めて表示させます。」とのこと。
良さげなのですが、Pagerは横方向にしか並べられないとのこと。
ツイートが横に並ぶのも何なので見送り。

###[Scrollコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#scroll-command)
名前はまんまスクロール。
「ScrollViewまたはSequenceを一定のページ数だけ前または後にスクロールします。」とのこと。
ScrollViewは単一コンポーネントを持つ。Sequenceは縦にも横にも並べられます。
良さそうなのですが、1回のスクロールを指示するだけなので、順々に送っていくのは大変そうです。
順にコマンドを処理するSequentialコマンドなんかもあるので頑張ればできるかもしれませんが…。
あと、送る量の指定がページ単位。あまり細かくできないようです。

###[ScrollToComponentコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#scroll_to_component_command)
###[ScrollToIndexコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#scrolltoindex-command)
スクロール位置を指定できるコマンド。Scrollコマンドより細かく制御できそうです。
ただ、順送りが面倒なのは変わらず。

###[SetFocusコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#set_focus_command)
フォーカス位置を動かせるとのこと。
これでも細かい制御はできそうですが、同じく順送りが。

###[SetPageコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#setpage-command)
フォーカスするPagerを制御できるとのこと。
Pagerを細かく制御するよう。
今回は、横方向限定のPager使わないし、やっぱり順送りが。
AutoPageコマンドの縦版が欲しいのに・・・。

どれも今一つだなーと思っていたところ、スクロールっぽくない名前のコマンドがスクロール機能を持っていることに気づきました。

###[SpeakItemコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#speakitem-command)
「画面の1つの項目のコンテンツを読み込みます。見えない場合、項目はスクロールされて表示されます。」とのこと。
なが～いテキストなんかをスクロールしながら表示できるわけですね。
行ごとにスクロールする指定もできて、良さそうです。

SpeakItemコマンドは、サンプルスキルで使われていました。
[skill-sample-python-pager-karaoke](https://github.com/alexa-labs/skill-sample-python-pager-karaoke)

なお、サンプルはカラオケの歌詞表示に使うみたいな感じなんですが、
カラオケなら音声との同期を別に考えないといけないような。
行毎に音声用意したりするのかしら。それでうまく繋がるのかしら。

###[SpeakListコマンド](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-standard-commands.html#speaklist-command)
「共通Container内にある項目のコンテンツを読み取ります。各項目がスクロール表示され、その後に発話されます。」とのこと。
こちらは1つの項目ではなく、複数項目が連なったSequenceコンポーネントでもOKです。


SpeakItemコマンド か SpeakListコマンド でいけそうですが、
~~SpeakItemコマンドはサンプルそのままになってつまらない~~
~~長いテキストの改行がタグ挿入位置となぜかずれて解決が面倒~~
複数ツイートを送るんで複数項目表示できるレイアウトを使うのが自然だろうということで、
SpeakListコマンドを使用することにしました。

#実装する！
詳しくは上述のドキュメント類を見ればいいのですが、雰囲気だけザクっと。

##画面レイアウトをデザイン
公式開発ページにオーサリングツールがあって、こんな画面でデザインできます。~~VisualStudioみたい。~~
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277692/9073e0ee-3b37-4d9a-05e1-cdfbb835b98c.png)
今回は、シンプルに、ヘッダと、名前とツイートの繰り返し、があるだけのデザイン。
頑張れば、画像を入れたり、文字を修飾したりすることもできます。

すると、デザインを表すjsonが生成されます。これをAlexaへのレスポンスに含めます。
（一部のみ。空項目とか文字修飾とかカット。）

```json
{
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "Container",
                "direction": "column",
                "items": [
                    {
                        "type": "Text",
                        "text": "タイムライン読み上げ"                   },
                    {
                        "type": "ScrollView",
                        "item": [
                            {
                                "type": "Sequence",
                                "id": "timelineSequence",
                                "data": "${payload.data.properties.tweets}",
                                "item": [
                                    {
                                        "type": "Container",
                                        "direction": "column",
                                        "speech": "${data.speech}",
                                        "items": [
                                            {
                                                "type": "Text",
                                                "color": "#FFFFFF",
                                                "text": "${data.name}"                                            },
                                            {
                                                "type": "Text",
                                                "fontSize": "40dp",
                                                "text": "${data.text}"
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
}
```
コマンドをSequenceに発行するので、ScrollViewは挟む必要無かったかもしれません。
SpeakListコマンドはコマンドを実行するコンポーネント直下のコンポーネントのspeech要素を読み上げるので、
Sequence直下のContainerのspeechにデータをバインドするようにします。
表示用のユーザ名とツイートはさらに一段下にTextコンポーネントを並べてそこにバインドします。

##データのセットとコマンド実行指示
スキルのバックエンドのサービス(Lambda)でデータのセットとコマンド実行指示をします。
（コマンド実行指示は先のJSONに追加される形なので最初から入れてしまっても良さそう。）

ツイートを取得した後データをセットしておいて（ここではtweetsという配列にツイートごとに追加する）、

``` Python
tweets.append({
    'name': （表示用のユーザ名）, 
    'text': （表示用のツイート）,
    'speechSsml': （読み上げる文をSSMLで）
})
```

Alexaへのレスポンスに追加します。

``` Python
from ask_sdk_model.interfaces.alexa.presentation.apl import RenderDocumentDirective, ExecuteCommandsDirective, SpeakListCommand

def _load_apl_document(file_path):
    with open(file_path) as f:
        return json.load(f)

handler_input.response_builder
    .add_directive(
        RenderDocumentDirective(
            token="getTimelineToken",
            document=_load_apl_document("timeline.json"),
            datasources={
                'data': {
                    "properties": {
                        'tweets': tweets
                    },
                    'transformers': [
                        {
                            'inputPath': 'tweets[*].speechSsml',
                            'outputName': 'speech',
                            'transformer': 'ssmlToSpeech'
                        },
                    ]
                }
            }
        ))
        .add_directive(
            ExecuteCommandsDirective(
                token="getTimelineToken",
                commands=[
                    SpeakListCommand(
                        component_id="timelineSequence",
                        start=0,
                        count=10,
                        align="first"
                    )
                ]
            )
        )
```
speech要素には音声データをバインドしないといけないので、トランスフォーマーなるものを用いて変換させます。
ワイルドカードを使って返還対象を指定できます。

[トランスフォーマー](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-data-source.html#transformers)

##結果
こんな風になりました。
読み上げに合わせてツイート単位でスクロールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277692/c6be3260-52be-de39-5ecf-85ada873890f.png)
またシミュレータ画面、人様のツイートなのでぼかしです。

#その他スキル余談
##まだ公開できていません
改修後のスキルは現在審査中です。
スキルの審査は結構厳しい印象があるので、公開までには時間がかかるかもしれません。

##改修検討中のこと
###「もっと読む」機能追加
現状10ツイート限定ですが、もっと読ませるか悩み中。
技術的には何も難しくないと思うのですが、そんなに聞くことあるかが微妙。
###140文字を超えるツイートに対応
現状140文字しか取得していません。
TwitterAPIがデフォルトで140文字だけ取得するものそのままです。
もっと取得する方法は分かっているのですが、
Alexaへのレスポンスにも文字数制限があるようで、
どこまで返せるのか検証&レスポンスのダイエットをしないと開放できないかなと思っています。
確かな情報が見当たらないんですよねー。