このセクションでは、APLコマンドとそれをスキルに追加する方法を学びます。すべてのAPL画面でTextコンポーネントと画像をアニメーション化し、Videoコンポーネントを追加します。
APLコマンドは、ユーザーに表示するコンテンツの視覚および音声の表現を変更するメッセージです。コマンドはさまざまな方法で使用できます。まずはAlexa.Presentation.APL.ExecuteCommands. ディレクティブを使用する方法が挙げられます。一部のコンポーネントにもAPLドキュメントにも、さまざまなハンドラープロパティがあります。たとえば、TouchWrapperレスポンシブ対応コンポーネントには、コマンドのリストを受け取るonPressプロパティがあります。今回は、同様のメカニズムであるAPLドキュメントの onMountプロパティを使用します。これは、ドキュメントのインフレート直後にコマンドを実行します。導入で使用するアニメーションコマンドには、これが便利です。
では、Textオブジェクトをいくつかアニメーション化しましょう。以下のようなものを作ります。
 
                        まず、コンポーネントにidプロパティを追加してAPLドキュメントを更新し、コマンドを使用できるようにする必要があります。APL画面のロード直後にコマンドを実行したいので、コマンドを起動ドキュメント(launchDocument.jsonファイル)のonMountプロパティに挿入します。コマンドでTextコンポーネントを対象とするには、それらの論理idを追加する必要があります。でもちょっと待ってください。 これらはAPLパッケージで定義されています。元のバージョンを見れば、既にコンポーネントidがデフォルトで追加されているのがわかります。 それは、top、middle、bottomの各Textコンポーネントに対応するtextTop, textMiddle, textBottomです。したがって、特に何かを追加する必要はありません。ただし、別のidを追加したい場合は、各TextコンポーネントidがAPLパッケージでパラメーターとして明示されています。
A. launchDocument.jsonファイルのAlexaImageコンポーネントに、"id": "image"を追加します。
B. 誕生日の視覚要素(birthdayDocument.jsonファイル)のVideoコンポーネントに、"id": "birthdayVideo"を追加します。
使用するコマンドは、AnimateItemコマンドです。このコマンドはAPL 1.1で追加されたものなので、APLドキュメントで、version: '1.1'のようにバージョン1.1を宣言していることを確認します。AnimateItemコマンドは、単一コンポーネントの1つ以上のプロパティについて、固定の時間のアニメーションシークエンスを実行します。ここではtransformプロパティを変更していきます。 transformプロパティを変更していきます。
AnimateItemコマンドの基本構造は次のとおりです。
{
     "type": "AnimateItem",
     "easing": "ease-in-out",
     "duration": 2000,
     "componentId": "textTop",
     "value": [
           {
                 "property": "transform",
                 "from": [
                       {
                             "translateX": 1200
                       }
                 ],
                 "to": [
                       {
                             "translateX": 0
                       }
                 ]
           }
     ]
}
transformプロパティでは、コンポーネントの移動先と移動元を指定する必要があります。「to」配列と「from」配列のすべての変換で、型が一致しなければなりません。この場合、型はtranslationXです。有効な値の詳細については、AnimateItemコマンドの値の技術資料を参照してください。すべてのTextアニメーションを並列実行するには、Parallelコマンドを使用します。このコマンドにはコマンドのリストがあり、呼び出した子コマンドを実行します。すべての子コマンドは同時に実行されます。
{
     "type": "Parallel",
     "delay": 500,
     "commands": [
         <コマンドのリスト>
     ]
 }
コマンドの構造を理解できたので、launchDocument.jsonファイルのonMountプロパティのコマンドを表すJSONを作成する必要があります。コマンドを追加していきましょう。はじめに、gifの表示のとおり、Textコンポーネントが異なる方向からスライドしてくるようなアニメーションにします。コマンドをすべて同時に実行したいので、3つのTextのAnimateItemコマンドをparallelコマンドに含めます。各変換の値を適切に設定するには、まずviewport座標系を理解する必要があります。
viewportは、左上隅を座標(0,0)として描画されます。また、すべての「to」プロパティは、viewportの座標ではなく、APLドキュメントのオブジェクトからの相対位置になっています。1つ目のオブジェクトは、X軸の1600から0に移動します。middle textは左から来るので、X軸の-400(これでアニメーション開始前にはTextを表示できません)から0に移動します。最後のアイテムは下から来るので、「translateY」を使います。Yは正で下に進むので、Y軸の1200から0に移動します。
A. コマンドセットをlaunchDocument.jsonドキュメントのonMountプロパティに追加します。上のコマンドをparallelコマンドに挿入すると、次のようになります。
[
       {
               "type": "Parallel",
               "commands": [
                       {
                               "type": "AnimateItem",
                               "easing": "ease-in-out",
                               "duration": 2000,
                               "componentId": "textTop",
                               "value": [
                                       {
                                               "property": "transform",
                                               "from": [
                                                       {
                                                               "translateX": 1200
                                                       }
                                               ],
                                               "to": [
                                                           {
                                                                   "translateX": 0
                                                           }
                                                   ]
                                           }
                               ]
                       },
                       {
                               "type": "AnimateItem",
                               "easing": "ease-in-out",
                               "duration": 2000,
                               "componentId": "textMiddle",
                               "value": [
                                       {
                                               "property": "transform",
                                               "from": [
                                                       {
                                                               "translateX": -400
                                                       }
                                               ],
                                               "to": [
                                                       {
                                                               "translateX": 0
                                                       }
                                               ]
                                       }
                               ]
                       },
                       {
                               "type": "AnimateItem",
                               "easing": "ease-in-out",
                               "duration": 2000,
                               "componentId": "textBottom",
                               "value": [
                                       {
                                               "property": "transform",
                                               "from": [
                                                       {
                                                               "translateY": 1200
                                                       }
                                               ],
                                               "to": [
                                                       {
                                                               "translateX": 0
                                                       }
                                               ]
                                       }
                               ]
                       }
               ]
       }
]
うまく機能したら、Imageコンポーネント用にもう少し複雑なアニメーションを作成しましょう。先ほど再生したアニメーションを見ると、画像をかなり小さいサイズから1(フルサイズ)に拡大しています。また、約2秒間かけて0度から360度に回転させています。軌道はそれほど直線的ではなく、ほかのアニメーションとは異なっていることにもお気付きでしょう。これはカスタムで定義されているためです。下の表の事前定義されたプロパティにこだわる必要はなく、3次ベジェ曲線による独自の曲線、または線形の軌道を定義できます。実際のところ、名前付きの曲線は、すべて下の表にリストされているように数学的に定義されています。座標は(0,0)から始まり(1,1)に移動します。X軸を時間、Y軸を変化の程度と考えてください。ここでは曲線を"easing": "path(0.25, 0.2, 0.5, 0.5, 0.75, 0.8)"と定義していますが、独自の曲線に変更しても構いません。
 
                        B. 画像のコマンドをすべてまとめると、次のようになります。
{
       "type": "AnimateItem",
       "easing": "path(0.25, 0.2, 0.5, 0.5, 0.75, 0.8)",
       "duration": 3000,
       "componentId": "image",
       "value": [
               {
                       "property": "transform",
                       "from": [
                               {
                                       "scale": 0.01
                               },
                               {
                                       "rotate": 0
                               }
                       ],
                       "to": [
                               {
                                       "scale": 1
                               },
                               {
                                       "rotate": 360
                               }
                       ]
               }
       ]
}
これをlaunchDocument.jsonファイルのonMountコマンドリストに追加します。
C. テストしてみましょう。
D. うまく機能したら、誕生日を入力して、今日が自分の誕生日ではないという想定でlaunchHandlerをテストしましょう。これにも同様にコマンドが適用されて表示されるはずです。これはまだ完成ではありません。誕生日がきた場合のアニメーションはどうしましょう? その場合のエクスペリエンスはbirthdayDocument.jsonファイルに定義されていますが、こちらにはまだコマンドを追加していません。これを修正していきましょう。
上のgifには、ほかにも違いがありますね? birthdayDocument.jsonドキュメントに、AlexaTransportsControlsレスポンシブ対応コンポーネントという新しいコンポーネントが追加されています。ビデオには常に画面上のコントロールが必要です。これがなければ認定に合格できません。さっそく追加しましょう。このコンポーネントもalexa-layoutsパッケージに含まれています。
A. これも中央に配置したいので、AlexaTransportControlsコンポーネントを、birthdayDocument.jsonのVideoコンポーネントがあるContainerに追加します。itemsリストでVideoコンポーネントの後に追加します。
{
     "type": "Container",
     "alignItems": "center",
     "items": [
         ...<Videoコンポーネント>...
         {
             "primaryControlSize": 50,
             "secondaryControlSize": 0,
             "mediaComponentId": "birthdayVideo",
             "type": "AlexaTransportControls"
         }
     ]
 }
コンポーネントでは、第2コントロールに0を指定していますが、これは第2コントロールのボタンを表示しないようにするためです。これらは、ビデオシリーズなどを再生する場合の早送りボタンと早戻しボタンです。第1コントロールのサイズは、再生ボタンのサイズです。ドキュメントでは、これより前にmediaComponentIdでVideoComponentを参照する必要があります。
B. これらの変更を保存してデプロイし、誕生日のシナリオをテストします。ボタンが機能し、切り替えたときにビデオが停止および再生されることを確認します。
Alexaからの音声応答が途切れたことにお気付きでしょうか? 今日が誕生日ではない場合は気付かないかもしれませんが、Alexaの音声応答は、ビデオの再生が始まると中断されます。これを修正するには、ExecuteCommandsディレクティブを使用する必要があります。
Alexaはビデオの再生が始まると音声を中断します。Alexaが話し終わってからビデオの再生を自動的に開始するようにしましょう。これを修正するには、まずbirthdayDocument.jsonのVideoコンポーネントのプロパティで自動再生をオフ(false)にします。
{
"type": "Video",
"height": "85vh",
"width":"90vw",
"source": "${payload.assets.video}",
"autoplay": false,
"id":"birthdayVideo"
},
しかし、ビデオの開始をユーザーに委ねるのもおかしな話です。コマンドを使ってこれを解決します。
音声を修正するには、ExecuteCommandsディレクティブをバックエンドとそのペイロードに追加します。ExecuteCommandsディレクティブは、Alexaの話が終わってから、提供されたコマンドのリストを実行します。次のような構造になっています。
{
     "type" : "Alexa.Presentation.APL.ExecuteCommands",
     "token": "[SkillProvidedToken]",
     "commands": [
         <コマンドのリスト>
     ]
 }
今回の使い方では、ターゲットへのExecuteCommandディレクティブのトークンをスキルが提供する必要があります。これを"birthdayToken"とします。これがないと、コマンドはどのドキュメントをターゲットとして実行すべきかがわかりません。
A. APL RenderDocumentディレクティブに、値をbirthdayTokenとした新しいトークンフィールドを追加します。すると、addDirective(…)は次のようになります。
// Renderディレクティブを作成します
 handlerInput.responseBuilder.addDirective({
     type: 'Alexa.Presentation.APL.RenderDocument',
     token: 'birthdayToken',
     document: birthdayDocument,
     datasources: {
         ... <省略>...
     }
 });
B. HasBirthdayLaunchRequestHandlerのelseブロックに、別のディレクティブを追加する必要があります。これは、現在のRenderディレクティブと連鎖させることができます。下のコードをhandlerInput.responseBuilderに追加します。
.addDirective({
     type: "Alexa.Presentation.APL.ExecuteCommands",
     token: "birthdayToken",
     commands: [
         <コマンドのリスト>
     ]
 });
C. <コマンドのリスト>を、作成したコマンドリストに置き換えます。ここでは、ビデオを開始するコマンド1つだけです。これはAlexaが話し終わると実行されるので、想定どおりに動作するはずです。 コマンドは次のようになります。
{
       type: "ControlMedia",
       componentId: "birthdayVideo",
       command: "play"
}
最終的なAPLディレクティブコードは、以下のとおりです。
// Renderディレクティブを作成します handlerInput.responseBuilder.addDirective({
     type: 'Alexa.Presentation.APL.RenderDocument',
     token: 'birthdayToken',
     document: birthdayDocument,
     datasources: {
         text: {
             type: 'object',
             start: "Happy Birthday!",
             middle: "From,",
             end: "Alexa <3"
         },
         assets: {
             video: "https://public-pics-muoio.s3.amazonaws.com/video/Amazon_Cake.mp4",
             backgroundURL: getBackgroundURL(handlerInput, "confetti")
         }
     }
 }).addDirective({
     type: "Alexa.Presentation.APL.ExecuteCommands",
     token: "birthdayToken",
     commands: [{
         type: "ControlMedia",
         componentId: "birthdayVideo",
         command: "play"
     }]
 });
D. 保存してデプロイし、テストします。
クールなアニメーションが完成しました。いかがですか? 以上で、画像、テキスト、ビデオ、そしてアニメーションを使用したCake Timeができました。おめでとうございます。