するめごはんのIT日記

主にITネタを書いていくのさ

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

ごきげんよう

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

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

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

でかい

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

f:id:surumegohan:20181213154055j:plain
開封の儀

動画とかすごい

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

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

■キャプテン九九

f:id:surumegohan:20181213154132j:plain
キャプテン九九

乙葉の時間

f:id:surumegohan:20181213154222j:plain
乙葉の時間

■ヒロインの告白

f:id:surumegohan:20181213154254j:plain
ヒロインの告白

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

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

f:id:surumegohan:20181213154333j:plain
ヒロインの告白をAPLにしてみる

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

qiita.com

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

1.Amazon Developer Portal

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

f:id:surumegohan:20181213154445p:plain
バイス検出エラー

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

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

f:id:surumegohan:20181213154519p:plain
小型ハブと中型ハブ

f:id:surumegohan:20181213154612p:plain
大型ハブと超大型TC

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

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

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

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

f:id:surumegohan:20181213154649p:plain
Lambdaのインラインエディタでがんばる

3.ソースコード

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

■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();

■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"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
}

■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を使いこなせるようになるとそれだけで職業になる気がします。

以上