EchoShowでAPLを使って告白される

ごきげんよう

この記事は
**「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」**
の13日目の記事です。

ついにきましたね！
**Echo Show!**

というわけで、さっそく試してみるわけです。

#でかい

比較のために500mlの空き缶を添えてますがでかいです。

![DSC_1541.JPG](https://qiita-image-store.s3.amazonaws.com/0/67398/21f6c7cc-e6a5-91ca-9c7c-fc00a2afebf5.jpeg)


#動画とかすごい

映画を観ると特に感じますが、ブラウン管かよってレベルの大きさであるだけ音がすごい良いです。
これはもう今後Echo ShowでPrime VideoやYoutubeを観ます。


#スキルたちがより見やすくなる

■キャプテン九九
![DSC_1547.JPG](https://qiita-image-store.s3.amazonaws.com/0/67398/fc3bd6f5-3003-e0ee-f000-1352e52d6f18.jpeg)


■乙葉の時間
![DSC_1568.JPG](https://qiita-image-store.s3.amazonaws.com/0/67398/07311bf9-8589-8562-e54b-3c4a6ef96997.jpeg)


■ヒロインの告白
![DSC_1574.JPG](https://qiita-image-store.s3.amazonaws.com/0/67398/b37db43b-02d2-aad8-396a-2adb33f807e3.jpeg)


#ヒロインの告白をAPL対応にしてみる

結果、テストバージョンでこうなってます。

![DSC_1575.JPG](https://qiita-image-store.s3.amazonaws.com/0/67398/731f097e-cf60-c8b7-59d6-5b27342046b9.jpeg)


環境構築から実装まで、偉大なる @zono_0 さんが既にわかりやすい記事を掲載しています。
いつもありがとうございます。


[Alexa Presentation Language（APL）スキル 開発チュートリアル](https://qiita.com/zono_0/items/8710951e5f6d1c01dd35)


上記、記事ではask cliが前提ですが、そうでなくてもAPLはもちろん使えます。
同じところは省いて、そうでないところを今回は補完していきます。

##1.Amazon Developer Portal

僕は今回開いた際にに右下にデバイス検出エラーがでました。

![デバイス検出エラー.png](https://qiita-image-store.s3.amazonaws.com/0/67398/011d5961-04be-ed87-eef6-f4deea17317c.png)

が、今回は気にしないで大丈夫です。
JSONのフォーマットが欲しいので。


ちなみに、ここで対象のデバイスの大きさを以下のように選べます。


![小型ハブと中型ハブ.png](https://qiita-image-store.s3.amazonaws.com/0/67398/bfdd5997-b667-28ce-d9fa-e778a3cd3a0b.png)


![大型ハブと超大型TV.png](https://qiita-image-store.s3.amazonaws.com/0/67398/56768527-12d1-8074-2522-3a498dd495eb.png)


##2.Lambdaでインラインエディタでも組める

ask cliは僕も使います。
というより、慣れるとそちらの方が開発は大変良いです。
コード管理とかもできますし。

かといって、ask cliの導入に躓く方もいらっしゃるとも思いますので、インラインでがんばってみました。

@zono_0 さんのように、APLのJSONは分割した方が良いと思います。

![lambda.png](https://qiita-image-store.s3.amazonaws.com/0/67398/cf34a236-913c-143d-2a3f-6360d6182799.png)

##3.ソースコード


とりあえず起動して動いた段階のソースコードです。

```javascript:APLでのヒロインの告白のLaunchRequest

'use strict';

const Alexa = require('ask-sdk');


//起動時
const kotoha_smartmacchiato = '<audio src=\"https://s3XXXXXXXXXXXXXX.mp3\" />';

const kotoha_voice = '<audio src=\"https://s3-XXXXXXXXXXXXXX.mp3\" />';


const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {

    // ディスプレイ有り（APL対応）の場合
    if (supportsApl(handlerInput)) {
      // APL対応（documentに設定したテンプレートレイアウトを利用し、datasourcesの内容をディスプレイに表示します。）
      handlerInput.responseBuilder
        .addDirective({
          type : 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          document: require('./homepage.json'),
          datasources: require('./data.json')
        });
    }

    const speechText = kotoha_smartmacchiato + kotoha_voice;
    
    return handlerInput.responseBuilder
      .speak('<speak>' + speechText + '</speak>')
      .reprompt('<speak>' + speechText + '</speak>')
      .withShouldEndSession(false)
      .getResponse();

  }
};


/**
 * ディスプレイサポート（APL対応）判定値
 * @author zono_0    いつもありがとうございます！
 */ 
const supportsApl = (handlerInput) => {
  const hasDisplay =
    handlerInput.requestEnvelope.context &&
    handlerInput.requestEnvelope.context.System &&
    handlerInput.requestEnvelope.context.System.device &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces['Alexa.Presentation.APL'];

  return hasDisplay;
};

/**
 * Echo Spotで使っていたディスプレイかどうかを判定するfunction
   Echo Showでも引き続き使えますが、今回はAPLを使います
*/
function supportsDisplay(handlerInput) {
  var hasDisplay =
    handlerInput.requestEnvelope.context &&
    handlerInput.requestEnvelope.context.System &&
    handlerInput.requestEnvelope.context.System.device &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display;

  console.log("Supported Interfaces are" + JSON.stringify(handlerInput.requestEnvelope.context.System.device.supportedInterfaces));
  return hasDisplay;
}

//動かす
exports.handler = Alexa.SkillBuilders.standard()
  .addRequestHandlers(LaunchRequestHandler)
  .lambda();

```


```json:homepage.json

{
        "type": "APL",
        "version": "1.0",
        "theme": "dark",
        "import": [
            {
                "name": "alexa-layouts",
                "version": "1.0.0"
            }
        ],
        "resources": [
            {
                "description": "Stock color for the light theme",
                "colors": {
                    "colorTextPrimary": "#151920"
                }
            },
            {
                "description": "Stock color for the dark theme",
                "when": "${viewport.theme == 'dark'}",
                "colors": {
                    "colorTextPrimary": "#f0f1ef"
                }
            },
            {
                "description": "Standard font sizes",
                "dimensions": {
                    "textSizeBody": 48,
                    "textSizePrimary": 27,
                    "textSizeSecondary": 23,
                    "textSizeSecondaryHint": 25
                }
            },
            {
                "description": "Common spacing values",
                "dimensions": {
                    "spacingThin": 6,
                    "spacingSmall": 12,
                    "spacingMedium": 24,
                    "spacingLarge": 48,
                    "spacingExtraLarge": 72
                }
            },
            {
                "description": "Common margins and padding",
                "dimensions": {
                    "marginTop": 40,
                    "marginLeft": 60,
                    "marginRight": 60,
                    "marginBottom": 40
                }
            }
        ],
        "styles": {
            "textStyleBase": {
                "description": "Base font description; set color and core font family",
                "values": [
                    {
                        "color": "@colorTextPrimary",
                        "fontFamily": "Amazon Ember"
                    }
                ]
            },
            "textStyleBase0": {
                "description": "Thin version of basic font",
                "extend": "textStyleBase",
                "values": {
                    "fontWeight": "100"
                }
            },
            "textStyleBase1": {
                "description": "Light version of basic font",
                "extend": "textStyleBase",
                "values": {
                    "fontWeight": "300"
                }
            },
            "mixinBody": {
                "values": {
                    "fontSize": "@textSizeBody"
                }
            },
            "mixinPrimary": {
                "values": {
                    "fontSize": "@textSizePrimary"
                }
            },
            "mixinSecondary": {
                "values": {
                    "fontSize": "@textSizeSecondary"
                }
            },
            "textStylePrimary": {
                "extend": [
                    "textStyleBase1",
                    "mixinPrimary"
                ]
            },
            "textStyleSecondary": {
                "extend": [
                    "textStyleBase0",
                    "mixinSecondary"
                ]
            },
            "textStyleBody": {
                "extend": [
                    "textStyleBase1",
                    "mixinBody"
                ]
            },
            "textStyleSecondaryHint": {
                "values": {
                    "fontFamily": "Bookerly",
                    "fontStyle": "italic",
                    "fontSize": "@textSizeSecondaryHint",
                    "color": "@colorTextPrimary"
                }
            }
        },
        "layouts": {},
        "mainTemplate": {
            "parameters": [
                "payload"
            ],
            "items": [
                {
                    "when": "${viewport.shape == 'round'}",
                    "type": "Container",
                    "direction": "column",
                    "width": "100vw",
                    "height": "100vh",
                    "items": [
                        {
                            "type": "Image",
                            "source": "${payload.bodyTemplate3Data.image.sources[0].url}",
                            "scale": "best-fill",
                            "width": "100vw",
                            "height": "100vh",
                            "position": "absolute",
                            "overlayColor": "rgba(0, 0, 0, 0.6)"
                        },
                        {
                            "type": "ScrollView",
                            "width": "100vw",
                            "height": "100vh",
                            "item": [
                                {
                                    "type": "Container",
                                    "direction": "column",
                                    "alignItems": "center",
                                    "paddingLeft": 30,
                                    "paddingRight": 30,
                                    "paddingBottom": 200,
                                    "items": [
                                        {
                                            "type": "AlexaHeader",
                                            "headerAttributionImage": "${payload.bodyTemplate3Data.logoUrl}",
                                            "headerTitle": "${payload.bodyTemplate3Data.title}"
                                        },
                                        {
                                            "type": "Text",
                                            "text": "<b>告白と言えば</b> | <b>やはり学校ですよね</b>",
                                            "style": "textStylePrimary",
                                            "color": "#4dd2ff",
                                            "width": "90vw",
                                            "textAlign": "center"
                                        },
                                        {
                                            "type": "Text",
                                            "text": "<b>${payload.bodyTemplate3Data.textContent.title.text}</b>",
                                            "style": "textStyleBody",
                                            "width": "90vw",
                                            "textAlign": "center"
                                        },
                                        {
                                            "type": "Text",
                                            "text": "${payload.bodyTemplate3Data.textContent.subtitle.text}",
                                            "style": "textStylePrimary",
                                            "width": "90vw",
                                            "textAlign": "center"
                                        },
                                        {
                                            "type": "Text",
                                            "text": "${payload.bodyTemplate3Data.textContent.primaryText.text}",
                                            "paddingTop": 40,
                                            "style": "textStylePrimary",
                                            "width": "90vw",
                                            "textAlign": "center"
                                        },
                                        {
                                            "type": "Text",
                                            "text": "${payload.bodyTemplate3Data.textContent.bulletPoint.text}",
                                            "paddingTop": 50,
                                            "style": "textStylePrimary",
                                            "width": "90vw",
                                            "textAlign": "center"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                },
                {
                    "type": "Container",
                    "width": "100vw",
                    "height": "100vh",
                    "items": [
                        {
                            "type": "Image",
                            "source": "${payload.bodyTemplate3Data.backgroundImage.sources[0].url}",
                            "scale": "best-fill",
                            "width": "100vw",
                            "height": "100vh",
                            "position": "absolute"
                        },
                        {
                            "type": "AlexaHeader",
                            "headerTitle": "${payload.bodyTemplate3Data.title}",
                            "headerAttributionImage": "${payload.bodyTemplate3Data.logoUrl}"
                        },
                        {
                            "type": "Container",
                            "direction": "row",
                            "paddingLeft": 40,
                            "paddingRight": 72,
                            "grow": 1,
                            "items": [
                                {
                                    "type": "Image",
                                    "source": "${payload.bodyTemplate3Data.image.sources[0].url}",
                                    "width": 340,
                                    "height": 360,
                                    "scale": "best-fit",
                                    "align": "center"
                                },
                                {
                                    "type": "ScrollView",
                                    "height": "60vh",
                                    "shrink": 1,
                                    "item": [
                                        {
                                            "type": "Container",
                                            "items": [
                                                {
                                                    "type": "Text",
                                                    "text": "<b>やはり告白と言えば学校ですよね</b>",
                                                    "style": "textStylePrimary",
                                                    "color": "#4dd2ff"
                                                },
                                                {
                                                    "type": "Text",
                                                    "text": "<b>${payload.bodyTemplate3Data.textContent.title.text}</b>",
                                                    "style": "textStyleBody"
                                                },
                                                {
                                                    "type": "Text",
                                                    "text": "${payload.bodyTemplate3Data.textContent.subtitle.text}",
                                                    "style": "textStylePrimary"
                                                },
                                                {
                                                    "type": "Text",
                                                    "text": "${payload.bodyTemplate3Data.textContent.primaryText.text}",
                                                    "paddingTop": 40,
                                                    "style": "textStylePrimary"
                                                },
                                                {
                                                    "type": "Text",
                                                    "text": "${payload.bodyTemplate3Data.textContent.bulletPoint.text}",
                                                    "paddingTop": 50,
                                                    "style": "textStylePrimary"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
}

```

```json:data.json

{
        "bodyTemplate3Data": {
            "type": "object",
            "objectId": "bt3Sample",
            "backgroundImage": {
                "contentDescription": null,
                "smallSourceUrl": null,
                "largeSourceUrl": null,
                "sources": [
                    {
                        "url": "https://s3-学校画像.png",
                        "size": "small",
                        "widthPixels": 0,
                        "heightPixels": 0
                    },
                    {
                        "url": "https://s3-学校画像.png",
                        "size": "large",
                        "widthPixels": 0,
                        "heightPixels": 0
                    }
                ]
            },
            "title": "APLを使って Echo Show で学校で（？）告白されよう！",
            "image": {
                "contentDescription": null,
                "smallSourceUrl": null,
                "largeSourceUrl": null,
                "sources": [
                    {
                        "url": "https://s3-結城琴葉画像.png",
                        "size": "small",
                        "widthPixels": 0,
                        "heightPixels": 0
                    },
                    {
                        "url": "https://s3-結城琴葉画像.png",
                        "size": "large",
                        "widthPixels": 0,
                        "heightPixels": 0
                    }
                ]
            },
            "textContent": {
                "title": {
                    "type": "PlainText",
                    "text": " ヒロイン（結城琴葉）の告白"
                },
                "subtitle": {
                    "type": "PlainText",
                    "text": " 告白メッセージ"
                },
                "primaryText": {
                    "type": "PlainText",
                    "text": " 私、あなたが好きです。世界中の誰よりも・・あなたのことが、本当に好きなんです！私と・・付き合ってください. "
                },
                "bulletPoint": {
                    "type": "PlainText",
                    "text": " 結城琴葉ちゃんから告白されてみよう！ "
                }
            },
            "logoUrl": "https://s3-スキルのロゴ.png",
            "hintText": "アレクサ、「ヒロインの告白」を開いて"
        }
    }

```


#まとめ

ask cliで躓いても、なんとかなります。
現時点では僕はこれが実機で動いたレベルですが、APLを使いこなせるようになるとそれだけで職業になる気がします。


以上