こんにちは。データ&サイエンスソリューション統括本部 スマートデバイス本部で、ヤフーのAlexaスキルや、Actions on Googleのアクションを開発している大島翼です。本日12月12日に、Amazon Echo Showが日本でも発売され、画面付きのスマートスピーカーがより一層注目を集めております。今回はヤフーのAlexaスキルである「Yahoo!天気・災害」と「Yahoo!路線」をAlexa Presentation Language(APL)を用いて画面付きデバイスに対応した話をご紹介したいと思います。
Alexa Presentation Language(APL)とは?
簡単に言うと、Alexaの動作を制御するjsonの中にCSSやHTMLのような要素を埋め込み、Alexaの画面のレイアウトを自由にカスタマイズできるようにAmazonが開発した新しい言語です。これにより音声だけではなく画像や映像を用いて視覚にも訴えるマルチモーダルなスキルの開発が可能となりました。Amazon公式のテクニカルドキュメントはこちら
Yahoo! JAPANの画面対応スキル
Yahoo!天気・災害
指定された地域・日付の気象情報を伝えた後に、スライドショーで週間天気も表示しています。警報がある場合は警報の画面も間に表示されます。週間天気の画面はEcho Spotでは二枚に分割して表示しています。APLでは画面タイプがroundかrectangleかを判定して、画面の形・大きさに最適化したレイアウトを表示することができます。Yahoo!天気・災害スキルはこちらから利用可能です。
また、「中央区の天気」のように複数地域の候補が存在する場合は、声だけではなく場面をタップして地域を絞り込めるようになっています。
Yahoo!路線
指定された路線の運行情報を状況に応じて背景色を切り替えて表示しています。声だけでなく視覚にも訴えることで一目で遅延があるかわかるようになっています。Yahoo!路線スキルはこちらから利用可能です。
また、「Yahoo!路線を開いて」と発話された時はWebやアプリのYahoo!路線で登録している路線の運行情報を答えるようになっており、複数の路線に運行情報がある場合は、各画面で一路線の運行情報を読み上げて自動で次のページに遷移し、また次の運行情報を読み上げます。
APLの実装方法
まず、Alexaの動作を制御するjsonの構造は以下のようになっています。
{
"body": {
"version": "1.0",
"response": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>Alexaが発話する内容</speak>"
},
"directives": [
{
"type": "Alexa.Presentation.APL.RenderDocument",
"document": {
// 画面のレイアウト
},
"datasources": {
// レイアウトを構成するパラメーター
}
},
{
"type": "Alexa.Presentation.APL.ExecuteCommands",
"commands": [
// 画面を自動遷移させるコマンドなど
]
}
],
"shouldEndSession": true // 会話を終了するか否か
},
"sessionAttributes": {} // 会話の中で引き継ぐパラメータ
}
}
APLはAlexaの応答を制御するjsonの中のdirectives
プロパティの中でレイアウトを定義するdocument
プロパティとデータソースを定義するdatasources
で分けて定義します。レイアウトとデータソースを部分を分けて定義することができるので、document
部分を実装するエンジニアとパラメーターを動的に生成してdatasources
のjsonを生成するエンジニアで分担して開発をすることができます。「Yahoo!路線」の運行情報の画面を構成しているdocument
およびdatasources
は以下のようになっています(レイアウトの細かい数値は省略してあります。)
document.json(Yahoo!路線)
{
"type": "APL",
"version": "1.0",
"theme": "dark",
"import": [
{
"name": "alexa-layouts",
"version": "1.0.0"
}
],
"mainTemplate": {
"parameters": [
"payload"
],
"item": {
"type": "Container",
"direction": "column",
"alignItems": "center",
"items": [
{
"type": "Image",
"source": "${payload.pagerData.properties.backgroundImage}",
"position": "absolute",
"width": "100vw",
"height": "100vh"
},
{
"type": "Container",
"direction": "column",
"alignItems": "center",
"paddingTop": 50,
"items": [
{
"type": "Image",
"source": "${payload.pagerData.properties.trainIcon}",
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${payload.pagerData.properties.railName}",,
// width, height, positionなど定義
},
{
"type": "Image",
"source": "${payload.pagerData.properties.circleIcon}",,
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${payload.pagerData.properties.status}",
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${payload.pagerData.properties.time}",
// width, height, positionなど定義
}
]
}
]
}
}
}
datasources.json(Yahoo!路線)
{
"datasources": {
"pagerData": {
"type": "object",
"properties": {
"backgroundImage": "<imagePath>",
"trainIcon": "<imagePath>",
"circleIcon": "<imagePath>",
"railName": "山手線",
"status": "列車遅延",
"time": "9:06 現在"
}
}
}
}
APLで発話と同期したスライドショーを実現する
ここでは、「Yahoo!天気・災害」と「Yahoo!路線」のAPLの開発で最も重要かつ実装に苦戦した、各画面で発話をした後に自動で次のページに遷移する実装をご紹介したいと思います。Pager
コンポーネントを用いることで、スライドショーのような動作を実現させることができますが、その場合outputSpeech
に入れたテキストを全て読み終えた後に、画面のスクロールが始まります。つまり、Yahoo!路線で複数運行情報があったときに、全ての運行情報の読み上げが完了してから画面が動きはじめるという動作になってしまいます。各ページで一つの路線の運行情報を読み上げて次の画面へ遷移することを実現するために以下のような実装をしました。
- datasourcesにセットするデータの中で、各画面で話す内容のデータをSSMLで用意する。
- datasourcesの中でssmlToSpeech transformerを利用してSSMLを実際のスピーチデータに変換する。
- APLドキュメントの中で、2で変換したスピーチデータをTextコンポーネントのspeechプロパティにセットする。
- SpeakItemコマンドでspeechをセットしたTextコンポーネントのIDを指定してしゃべらせる。
- SpeakItemと画面を遷移させるコマンドであるSetPageを交互にcommandsプロパティに入れることで、発話と画面の遷移を交互に行うようにする。
「Yahoo!路線」の山手線と半蔵門線の二つの路線の運行情報を伝える時のjsonは以下のようになっています。
datasources.json(Yahoo!路線)
{
"pagerData": {
"type": "object",
"delayInfos": [
{
"backgroundImage": "<imagePath>",
"trainIcon": "<imagePath>",
"circleIcon": "<imagePath>",
"railName": "山手線",
"status": "列車遅延",
"time": "07:40 現在"
},
{
"backgroundImage": "<imagePath>",
"trainIcon": "<imagePath>",
"circleIcon": "<imagePath>",
"railName": "半蔵門線",
"status": "運転見合わせ",
"time": "07:40 現在"
}
],
"properties": {
"Ssml_0": "<speak>山手線は列車遅延中...</speak>", // 1. 画面ごとに話す発話テキスト
"Ssml_1": "<speak>半蔵門線の列車遅延中...</speak>"
},
"transformers": [
{
"inputPath": "Ssml_0",
"outputName": "Speech_0",
"transformer": "ssmlToSpeech" // 2. SSMLを実際のスピーチデータに変換
},
{
"inputPath": "Ssml_1",
"outputName": "Speech_1",
"transformer": "ssmlToSpeech"
}
]
}
}
document.json(Yahoo!路線)
{
"type": "APL",
"version": "1.0",
"theme": "dark",
"import": [
{
"name": "alexa-layouts",
"version": "1.0.0"
}
],
"mainTemplate": {
"parameters": [
"payload"
],
"item": [
{
"type": "Container",
"width": "100vw",
"height": "100vh",
"items": [
{
"type": "Text",
"id": "SpeechComponent_0",
"speech": "${payload.pagerData.properties.Speech_0}", // 3. 2で変換したスピーチデータをTextコンポーネントのspeechプロパティにセット
"position": "absolute",
"width": 0,
"height": 0
},
{
"type": "Text",
"id": "SpeechComponent_1",
"speech": "${payload.pagerData.properties.Speech_1}",
"position": "absolute",
"width": 0,
"height": 0
},
{
"type": "Pager",
"id": "trainInfoPager",
"numbered": true,
"width": "100vw",
"height": "100vh",
"data": "${payload.pagerData.delayInfos}",
"item": [
{
"type": "Container",
"direction": "column",
"alignItems": "center",
"items": [
{
"type": "Image",
"source": "${data.backgroundImage}",
"scale": "best-fill",
"position": "absolute",
"width": "100vw",
"height": "100vh"
},
{
"type": "Container",
"direction": "column",
"alignItems": "center",
"paddingTop": 50,
"items": [
{
"type": "Image",
"source": "${data.trainIcon}",
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${data.railName}",,
// width, height, positionなど定義
},
{
"type": "Image",
"source": "${data.circleIcon}",,
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${data.status}",
// width, height, positionなど定義
},
{
"type": "Text",
"text": "${data.time}",
// width, height, positionなど定義
}
]
}
]
}
]
}
]
}
]
}
}
command.json(Yahoo!路線)
{
"type": "Alexa.Presentation.APL.ExecuteCommands",
"token": "trainInfoToken",
"commands": [
{
"type": "SpeakItem",
"componentId": "SpeechComponent_0" // 4. コンポーネントidを指定して、SpeakItemで発話させる
},
{
"type": "SetPage", // 5.ページをめくるコマンド
"componentId": "trainInfoPager",
"position": "relative",
"value": 1
},
{
"type": "SpeakItem",
"componentId": "SpeechComponent_1"
}
]
}
APLの画面開発の手順
「Yahoo!天気・災害」と「Yahoo!路線」の画面なしのスキルがリリース済みの状態から、画面対応した手順をご紹介します。
1.画面対応のフローチャートを作成
すでにある音声のみのフローチャートの中でどのタイミングで、どんな情報を盛り込んだ画面を表示するのが適切か考えて、画面対応のフローチャートを作成します。音声のみのフローではなかった画面対応ならではのことも考える必要があります。例えばYahoo!路線では運行情報がある場合は一画面に一路線の情報を表示していますが、全て平常運行の場合には画面はわけずに一画面に全ての路線を表示するようにするなどの分岐が加わりました。
2.画面デザイン
画面を表示させるタイミングと表示する情報が決まったらデザイナーによる画面デザインに入ります。Echo SpotとEcho Showそれぞれのデザインを考える必要があります。Echo Spotの画面解像度は480x480ピクセルで、画面が円形にトリミングされます。Echo Showの画面解像度は1024x600です。
3.画面のレイアウトの実装
デザインに沿って画面レイアウトを実装します。主にdocument.json
を実装することになります。マルチモーダルディスプレイの画面表示確認コンソールを用いることで、ホームページビルダーのように、画面のレイアウトを確認しながらdocument.json
を実装することができます。しかし、このコンソールで正常に表示されても、実機ではレイアウトが崩れるケースが多々あります。ある程度レイアウトをコンソールの表示をベースを作った後は実機で表示を確認しながら開発を進めて行くことをおすすめします。
4.動的な値の埋め込みを実装
APIから取得したデータを元に動的にdatasources.json
を生成するメソッドを新規に実装します。そして、Alexaからのリクエスト中のcontext.Viewport.shape
から画面タイプがroundかrectangleかを判定して、それぞれの画面のレイアウトを生成するメソッドを呼び出し、directives
にjsonをセットします。
5.リリース前の動作テスト
動作テストは画面なしのデバイスと、Echo Spot、Echo Showの3つで動作確認を行う必要があるため、時間は音声のみの場合よりもかかります。狭い空間で動作テストを行うと隣の人のデバイスがWake Upしてしまうので、Alexaの設定からWakeUpワードを変更して動作テストを行いました。
最後に
これまでは画面をデザインするためにはAmazonが用意したテンプレートを使うしかありませんでしたが、APLが使えるようになったことで大幅に画面デザインの自由度が高まりました。それに伴いVUIの対話デザインだけではなく、画面のビジュアルデザインも必要となり、さらには画面付きデバイスと、画面なしのデバイスで別々に動作テストをする必要があったりと現場のリソースはより必要になりますが、その分ユーザーに届けられる体験は格段によくなります。また、開発されてからまだ間もない言語であり、ナレッジも少ないため技術者同士が積極的に情報共有する必要があると感じております。本稿がこれからAPLで画面付きデバイスに対応する開発者の手助けになると幸いです。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました