Custom Extensionとは、CLOVAが基本的に提供している機能やサービスではなく、開発者が任意に拡張した機能や、外部のサービスを提供するExtensionです。例えば、ウェブ検索やニュースクリッピングなどのサービスだけでなく、ユーザーアカウント認証の必要な音楽、ショッピング、金融などの外部サービスを提供することができます。Custom Extensionは、解析されたユーザーの発話情報をCEKから受け取り、その内容を処理して、サービスの処理結果を返す必要があります。
このドキュメントでは、Custom Extensionを作成する際の準備事項と、Custom ExtensionがCEKとどのようなメッセージのやり取りをして、どのように動作する必要があるかについて、次の内容を説明します。
Custom Extensionの開発者は、次の項目を事前に準備する必要があります。
対話モデル
CLOVA Developer Centerに登録するCustom Extensionの対話モデルです。対話モデルは、ユーザーとCLOVAの対話シナリオのようなものです。ユーザーが発話しそうなフレーズを定義し、それぞれのフレーズがどんな意図を表し、どんな情報を持つかを設定します。
対話モデルを定義する方法の詳細については、対話モデルの定義を参照してください。
Extensionサーバー
CLOVA Developer Centerに登録するExtensionサーバーです。このサーバーは、CLOVAがユーザーの音声入力を解析した結果や、デフォルトで提供されるインテントを渡された際に、そのインテントを処理して適切な応答を返す必要があります。
認可サーバー
音楽、ショッピングなど、ユーザーアカウント認証の必要な外部のサービスを提供するCustom Extensionの場合、ユーザーアカウントを連携する必要があります。ユーザーアカウントを連携するには、必ず認可サーバーを構築する必要があります。詳細については、ユーザーアカウントを連携するを参照してください。
Custom ExtensionはCEKからCustom Extensionメッセージ形式のユーザーリクエストを受信します(HTTPSリクエスト)。Custom Extensionは通常、次のようにリクエストを処理し、レスポンスする必要があります。
このようなユーザーのリクエストは一度で終わるリクエストの場合もありますが、次のように文脈が維持される必要のあるマルチターン対話の場合もあります。
そのため、ユーザーのリクエストを3つのタイプに分類しています。Custom Extensionの開発者は、リクエストのタイプに応じて適切に処理する必要があります。 3つのリクエストタイプと、各リクエストタイプのユーザーの発話パターンは次のとおりです。
リクエストタイプ | ユーザーの発話パターン | サンプル発話 |
---|---|---|
LaunchRequest | [Extensionの呼び出し名] + 「を起動して/を開いて/に繋いで」 | 「ピザボットを起動して」 |
IntentRequest | (LaunchRequest タイプのリクエストを受け付けた状態で) [Extensionごとに登録したコマンド] |
(ピザボット起動状態で)「注文を確認して」 |
SessionEndedRequest | (LaunchRequest タイプのリクエストを受け付けた状態で)「終了して/終了/やめて」 |
「(ピザボットを)終了して」 |
メモ
EventRequest
は、ユーザーの発話の有無に関わらず、デバイスの状態が変化したときにExtensionに送信されるメッセージです。これらのイベントは、デバイスの状態を取得したり、状態変化したことを検知することに利用できます。また、Extensionがオーディオコンテンツを提供する際にも使用されます。ここでは、オーディオコンテンツ再生時のEventRequest
については説明しません。
LaunchRequest
タイプのリクエストは、ユーザーが特定のExtensionを使用すると宣言したことを示す際に使用されます。例えば、ユーザーが「ピザボットを起動して」や「ピザボットを開いて」のように指示した場合、CEKはピザの宅配サービスを提供するExtensionにLaunchRequest
タイプのリクエストを渡します。このタイプのリクエストを渡されたExtensionは、ユーザーの次のリクエストも受信できるように準備している必要があります。
LaunchRequestタイプのメッセージは、request.type
フィールドに"LaunchRequest"
の値を持ち、request
フィールドにユーザーの発話の解析情報を含めていません。Extensionの開発者は、このメッセージを受信した場合、事前の準備事項を処理するか、ユーザーにサービスを提供する準備ができたというレスポンスメッセージを返します。
このメッセージを受信してからSessionEndedRequest
タイプのリクエストメッセージを受信するまで、IntentRequest
タイプのリクエストメッセージを受信し、session.sessionId
フィールドは前のメッセージと同じ値を持ちます。
次はLaunchRequest
タイプのリクエストメッセージのサンプルです。
{
"version": "1.0",
"session": {
"new": true,
"sessionAttributes": {},
"sessionId": "a29cfead-c5ba-474d-8745-6c1a6625f0c5",
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
}
},
"context": {
"System": {
"application": {
"applicationId": "com.example.extension.pizzabot"
},
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
},
"device": {
"deviceId": "096e6b27-1717-33e9-b0a7-510a48658a9b",
"display": {
"size": "l100",
"orientation": "landscape",
"dpi": 96,
"contentLayer": {
"width": 640,
"height": 360
}
}
}
}
},
"request": {
"type": "LaunchRequest"
}
}
上記のサンプルで、各フィールドの意味は次のとおりです。
version
:使用しているCustom Extensionメッセージフォーマットのバージョンです。現在のバージョンはv1.0です。session
:新しいセッションです。新しいセッションで使用されるセッションのIDとユーザーの情報(ID、アクセストークン)が含まれています。context
:クライアントデバイスの情報です。デバイスのIDとデフォルトユーザーの情報が含まれています。request
:LaunchRequest
タイプのリクエストです。対象Extensionの使用を開始することを示します。ユーザーの発話の解析情報はありません。IntentRequest
タイプのリクエストは、あらかじめ定義した対話モデルに従って、CEKがExtensionにユーザー発話のリクエストを送信する際に使用されます。例えば、ユーザーが「ピザボットを起動して」と発話してサービスを開始した後に、「ピザを頼んで」と指示した時に、CEKはピザの宅配サービスを提供するExtensionにIntentRequest
タイプのリクエストを渡します。
IntentRequestタイプのリクエストは、request.type
フィールドに"IntentRequest"
の値を持ちます。呼び出されたインテントの名前と、解析されたユーザーの発話情報は、request.intent
フィールドから確認できます。このフィールドを分析してユーザーのリクエストを処理してから、レスポンスメッセージを返します。
次はIntentRequest
タイプのリクエストメッセージのサンプルです。
{
"version": "1.0",
"session": {
"new": false,
"sessionAttributes": {},
"sessionId": "a29cfead-c5ba-474d-8745-6c1a6625f0c5",
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
}
},
"context": {
"System": {
"application": {
"applicationId": "com.example.extension.pizzabot"
},
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
},
"device": {
"deviceId": "096e6b27-1717-33e9-b0a7-510a48658a9b",
"display": {
"size": "l100",
"orientation": "landscape",
"dpi": 96,
"contentLayer": {
"width": 640,
"height": 360
}
}
}
}
},
"request": {
"type": "IntentRequest",
"intent": {
"name": "OrderPizza",
"slots": {
"pizzaType": {
"name": "pizzaType",
"value": "ペパロニ"
}
}
}
}
}
上記のサンプルで、各フィールドの意味は次のとおりです。
version
:使用しているCustom Extensionメッセージフォーマットのバージョンです。現在のバージョンはv1.0です。session
: 既存のセッションに続くユーザーのリクエストです。既存セッションのIDとユーザーの情報(ID、アクセストークン)が含まれています。context
:クライアントデバイスの情報です。デバイスのIDとデフォルトユーザーの情報が含まれています。request
: IntentRequest
タイプのリクエストです。"OrderPizza"
という名前で登録されたインテントを呼び出しています。該当するインテントが必要とする情報として"pizzaType"
というスロットが一緒に渡されます。そのスロットは"ペパロニ"
という値を持っています。メモ
IntentRequest
タイプのリクエストは、LaunchRequest
タイプのリクエストと関係なく、新しいセッションを開始してリクエストを処理することができます。
SessionEndedRequest
タイプのリクエストは、ユーザーが特定のモードやCustom Extensionの使用を中断すると宣言したことを示す際に使用されます。ユーザーが「終了して」「やめて」などのように指示した場合、クライアントはExtensionの使用を中断し、CEKは対話サービスを提供するExtensionにSessionEndedRequest
タイプのリクエストを渡します。
SessionEndedRequest
タイプのメッセージはrequest.type
フィールドに"SessionEndedRequest"
の値を持ち、LaunchRequest
タイプと同じくrequest
フィールドにユーザーの発話の解析情報は含まれません。Extensionの開発者は、サービス終了時の後処理を実施してください。
次はSessionEndedRequest
タイプのリクエストメッセージのサンプルです。
{
"version": "1.0",
"session": {
"new": false,
"sessionAttributes": {},
"sessionId": "a29cfead-c5ba-474d-8745-6c1a6625f0c5",
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
}
},
"context": {
"System": {
"application": {
"applicationId": "com.example.extension.pizzabot"
},
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
},
"device": {
"deviceId": "096e6b27-1717-33e9-b0a7-510a48658a9b",
"display": {
"size": "l100",
"orientation": "landscape",
"dpi": 96,
"contentLayer": {
"width": 640,
"height": 360
}
}
}
}
},
"request": {
"type": "SessionEndedRequest"
}
}
上記のサンプルで、各フィールドの意味は次のとおりです。
version
:使用しているCustom Extensionメッセージフォーマットのバージョンです。現在のバージョンはv1.0です。session
: 既存のセッションに続くユーザーのリクエストです。既存セッションのIDとユーザーの情報(ID、アクセストークン)が含まれています。context
:クライアントデバイスの情報です。デバイスのIDとデフォルトユーザーの情報が含まれています。request
: SessionEndedRequest
タイプのリクエストです。対象Extensionの使用を中断することを示します。ユーザーの発話の解析情報はありません。注意
CEKがSessionEndedRequest
タイプのリクエストをExtensionに送信した瞬間から、CEKはそのExtensionからの応答をすべて無視します。
ExtensionがCEKからHTTPリクエストを受信するとき、そのリクエストが第三者ではなく、CLOVAから送信された信頼できるリクエストかどうかを検証する必要があります。HTTPヘッダーにあるSignatureCEK
フィールドとRSA公開鍵を使用して、以下のようにリクエストメッセージを検証してください。
`context.System.application.applicationId`が設定済みの`ExtensionId`と同一であることを確認してください
CLOVAの署名用RSA公開鍵を以下のURLからダウンロードしてください
https://clova-cek-requests.line.me/.well-known/signature-public-key.pem
SignatureCEK
ヘッダーの値を取得してください
SignatureCEK
ヘッダーの値は、Base64エンコードされた、HTTP bodyのRSA PKCS #1 v1.5署名値です。
SignatureCEK
ヘッダーを以下のように検証(verify)してくださいString signatureStr = req.getHeader("SignatureCEK");
byte[] body = getBody(req);
String publicKeyStr = downloadPublicKey();
publicKeyStr = publicKeyStr.replaceAll("\\n", "")
.replaceAll("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("-----END PUBLIC KEY-----", "");
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(body);
byte[] signature = Base64.getDecoder().decode(signatureStr);
boolean valid = sig.verify(signature);
メモ
検証に失敗した場合にはそのリクエストは破棄してください。
注意
HTTP headerのフィールドは大文字/小文字を区別しないcase-insensitiveです。Section 4.2, "Message Headers"
実装時にはフィールド名の大文字/小文字問わず、値が取得できるようにしてください。
リクエストメッセージを処理すると、CEKにレスポンスメッセージを返す必要があります(HTTPレスポンス)。リクエストメッセージのタイプによって異なる内容を返すこともありますが、レスポンスメッセージの構造に大差はありません。以下は、LaunchRequestタイプのリクエスト(「ピザボットを起動して」というユーザーリクエスト)を処理した後に返したレスポンスメッセージです。
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"outputSpeech": {
"type": "SimpleSpeech",
"values": {
"type": "PlainText",
"lang": "ja",
"value": "こんにちは。ピザボットです。どういったご用件ですか"
}
},
"card": {},
"directives": [],
"shouldEndSession": false
}
}
各フィールドは、次のような意味を持ちます。
version
:使用しているCustom Extensionメッセージフォーマットのバージョンです。現在のバージョンはv1.0です。response.outputSpeech
:ユーザーが日本語で「こんにちは。ピザボットです。どういったご用件ですか」の文章を話すように設定します。response.card
:クライアントの画面に表示するデータがありません。コンテンツテンプレート形式のデータで、クライアントの画面に表示するコンテンツをこのフィールドで渡すことができます。response.shouldEndSession
:セッションを終了せず、引き続きユーザーの入力を受け付けるかを管理します。このフィールドの値がtrueの場合、SessionEndedRequest
リクエストを受け取る前に、Extensionからセッションを終了できます。メモ
response.directives
フィールドはExtensionがCEKに渡すディレクティブです。response.directives
フィールドで使用するディレクティブは、現在、主にオーディオコンテンツを提供するために使用されます。次のように、場合によって複数の文章を出力するようにレスポンスメッセージを作成することができます。あるいは、インターネット上のオーディオファイルを再生するようにレスポンスメッセージを作成することもできます。
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"outputSpeech": {
"type": "SpeechList",
"values": [
{
"type": "PlainText",
"lang": "ja",
"value": "歌を歌ってみます。"
},
{
"type": "URL",
"lang": "" ,
"value": "https://example.com/song.mp3"
}
]
},
"card": {},
"directives": [],
"shouldEndSession": true
}
}
各response.outputSpeech
フィールドの説明は、次のとおりです。
response.outputSpeech.type
:複文タイプ(SpeechList)の音声情報です。response.outputSpeech.values[0]
:テキスト形式の音声情報です。日本語で「歌を歌ってみます」と発話するように設定されています。response.outputSpeech.values[1]
:URL形式の音声情報です。value
フィールドに入力されたURLのファイルを再生するように設定されています。メモ
単文や複文タイプの音声情報の他に、画面を持たないデバイスのような詳しい内容をGUIで表現できないクライアントデバイスのために、複合タイプ(SpeechSet)の音声情報もサポートしています。詳細については、Custom Extensionメッセージのレスポンスメッセージを参照してください。
音声出力だけでなく、クライアントデバイスの画面やクライアントアプリの画面にデータを出力する必要がある場合、次のようにresponse.card
フィールドにコンテンツテンプレートに合わせて表示するコンテンツを設定します。
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"outputSpeech": {
"type": "SimpleSpeech",
"values": {
"type": "PlainText",
"lang": "ja",
"value": "ブラウンの画像です"
}
},
"card": {
"type": "ImageText",
"imageUrl": {
"type": "url",
"value": ""
},
"mainText": {
"type": "string",
"value": "LINE Friendsのブラウンです"
},
"referenceText": {
"type": "string",
"value": "CLOVA検索結果"
},
"referenceUrl": {
"type": "url",
"value": "DUMMY_REFERENCE_URL"
},
"subTextList": [
{
"type": "string",
"value": "LINE Friends"
}
],
"thumbImageType": {
"type": "string",
"value": "キャラクター"
},
"thumbImageUrl": {
"type": "url",
"value": "DUMMY_IMAGE_URL"
}
},
"directives": [],
"shouldEndSession": true
}
}
Custom Extensionがサービスを提供したり、または動作するために必要な情報が、CEKから渡されたユーザーのリクエスト(IntentRequest
)にすべて含まれていないことがあります。また、シングルターンの対話では、1回の発話でユーザーのリクエストを受け付けることが難しい場合もあります。その場合、Custom Extensionはユーザーから足りない情報を聞き出すために、マルチターンの対話を行うことができます。
例えば、ユーザーが「ペパロニピザを頼んで」と発話したと仮定します。それを受け、CEKは次のようなリクエストメッセージを送信します。
{
"version": "1.0",
"session": {
"new": false,
"sessionAttributes": {},
"sessionId": "a29cfead-c5ba-474d-8745-6c1a6625f0c5",
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
}
},
"context": {
...
},
"request": {
"type": "IntentRequest",
"intent": {
"name": "OrderPizza",
"slots": {
"pizzaType": {
"name": "pizzaType",
"value": "ペパロニ"
}
}
}
}
}
Custom Extensionがピザの種類だけでなく、注文する数量に関する情報を必要とすることがあります。その際、レスポンスメッセージのresponse.shouldEndSession
フィールドをfalse
に設定すると、マルチターン対話で足りない情報を確認することができます。また、ユーザーが先に入力した情報をsessionAttributes
フィールドにキー(key)-値(value)の形で保存することもできます。
以下のようにレスポンスを返すことで、ユーザーがすでにリクエストしたintent
フィールドとpizzaType
の情報を保存するようにCLOVAにリクエストすることができます。また、ユーザーに数量に関する追加の情報を求めることができます。
{
"version": "1.0",
"sessionAttributes": {
"intent": "OrderPizza",
"pizzaType": "ペパロニ"
},
"response": {
"outputSpeech": {
"type": "SimpleSpeech",
"values": {
"type": "PlainText",
"lang": "ja",
"value": "何枚注文しますか?"
}
},
"card": {},
"directives": [],
"shouldEndSession": false
}
}
ユーザーの応答サンプルリクエストは次のようになります。マルチターン対話において追加で送信されたメッセージのsession.new
はfalse
になり、前のメッセージと同じsession.sessionId
値を持ちます。
また、解析した結果と共に、前回のレスポンスに含まれていたsessionAttributes
オブジェクトの情報をリクエストメッセージのsession.sessionAttributes
フィールドに含めて再送信します。Custom Extensionはこれらの受信した追加の情報を使用して次の動作を行うことができます。
{
"version": "1.0",
"session": {
"new": false,
"sessionAttributes": {
"intent": "OrderPizza",
"pizzaType": "ペパロニ"
},
"sessionId": "a29cfead-c5ba-474d-8745-6c1a6625f0c5",
"user": {
"userId": "U399a1e08a8d474521fc4bbd8c7b4148f",
"accessToken": "XHapQasdfsdfFsdfasdflQQ7"
}
},
"context": {
...
},
"request": {
"type": "IntentRequest",
"intent": {
"name": "AddInfo",
"slots": {
"pizzaAmount": {
"name": "pizzaAmount",
"value": "2"
}
}
}
}
}
注意
ExtensionがSessionEndedRequest
タイプのリクエストを受信すると、マルチターンの対話は終了します。SessionEndedRequest
タイプのリクエストを受信してからは、Extensionからどんな応答(使用終了のあいさつなど)が返されても、CEKで無視されます。
注意
cardは、Custom Extensionには対応しておりません。
Custom Extensionで、ユーザーに音楽やポッドキャストなどのオーディオコンテンツを提供することができます。そのためには、Custom ExtensionメッセージのEventRequest
タイプのリクエストメッセージとレスポンスメッセージの仕様のうち、オーディオコンテンツ再生関連のCIC APIを使用する必要があります。ユーザーにオーディオコンテンツを提供するには、次の内容をExtensionに実装する必要があります。
必須
任意
メモ
音声出力タイプ、オーディオコンテンツの再生タイプ共に、音源(.mp3)のリソースはHTTPSのみ許可されます。
ユーザーから音楽や、または音楽のような形でオーディオコンテンツの再生をリクエストされたとき、そのオーディオコンテンツの情報を渡す必要があります。ユーザーからのオーディオコンテンツ再生のリクエストがIntentRequest
タイプのリクエストでCustom Extensionに渡され、Custom ExtensionはそのIntentRequest
タイプのリクエストメッセージに対するレスポンスメッセージを返す必要があります。そのとき、そのメッセージにクライアントがオーディオコンテンツを再生するように指示するAudioPlayer.Play
ディレクティブを含めます。
注意
オーディオコンテンツを提供するCustom Extensionでは、コンテンツをコントロールする処理は必須の実装項目です。
以下は、AudioPlayer.Play
ディレクティブをCustom Extensionのレスポンスメッセージに含めたサンプルです。
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"card": {},
"directives": [
{
"header": {
"namespace": "AudioPlayer",
"name": "Play"
},
"payload": {
"audioItem": {
"audioItemId": "90b77646-93ab-444f-acd9-60f9f278ca38",
"episodeId": 22346122,
"stream": {
"beginAtInMilliseconds": 0,
"episodeId": 22346122,
"playType": "NONE",
"podcastId": 12548,
"progressReport": {
"progressReportDelayInMilliseconds": null,
"progressReportIntervalInMilliseconds": 60000,
"progressReportPositionInMilliseconds": null
},
"url": "https://streaming.example.com/1212334548/2231122",
"urlPlayable": true
},
"type": "podcast"
},
"source": {
"name": "Potbbang",
"logoUrl": "https://example.com/logo_180125.png"
},
"playBehavior": "REPLACE_ALL"
}
}
],
"outputSpeech": {},
"shouldEndSession": true
}
}
メモ
音楽を再生するレスポンスメッセージには、response.outputSpeech
フィールドを追加することもできます。例えば、ユーザーに対して、「リクエストしたオーディオコンテンツを再生します」という音声出力(TTS)を再生し、その後にオーディオコンテンツの再生を開始することができます。
注意
cardは、Custom Extensionには対応しておりません。
クライアントがオーディオを再生しているときに、ユーザーが「前」「次」などのように再生のコントロールに関連する発話を発した場合、ユーザーのリクエストはIntentRequest
タイプのリクエストメッセージでCustom Extensionに渡されます。現在、CEKはCustom Extensionで再生のコントロールに関連するユーザーのインテントを、以下のようなビルトインインテントとして渡すようになっています。
Clova.NextIntent
Clova.PreviousIntent
ユーザーが「前」「次」に該当する発話をして、Clova.NextIntent
またはClova.PreviousIntent
ビルトインインテントをIntentReqeust
タイプのリクエストメッセージで受け取ると、レスポンスメッセージでユーザーが前に聞いた、または次に聞くはずのオーディオコンテンツを再生するように指示する(AudioPlayer.Play
)必要があります。
メモ
前または次に該当するオーディオコンテンツがなかったり、有効ではない場合、「再生する前または次の曲がありません」などの音声出力をレスポンスメッセージで返す必要があります。
AudioPlayer.Play
ディレクティブでオーディオを再生するクライアントは、再生が開始、一時停止、再開、終了するタイミングで、AudioPlayer.PlayStarted
、AudioPlayer.PlayPaused
、AudioPlayer.PlayResumed
、AudioPlayer.PlayStopped
、AudioPlayer.PlayFinished
のようなイベントをCLOVAに送信します。そのとき、CLOVAはそのイベントの内容をEventRequest
タイプのリクエストメッセージでCustom Extensionに送信します。
また、クライアントはオーディオコンテンツを再生するように指示(AudioPlayer.Play
)を受けた後、AudioPlayer.Play
ディレクティブのprogressReport
フィールドに定義されている設定に従って再生の進行状況をレポートします。その内容もまた、EventRequest
タイプのリクエストメッセージでCustom Extensionに送信されます。クライアントは、進行状況をレポートするために、以下のイベントを送信します。
AudioPlayer.ProgressReportDelayPassed
イベント:再生が開始してから特定の時間が経過した後、再生の進行状況をレポートするAudioPlayer.ProgressReportPositionPassed
イベント:オーディオコンテンツの特定の位置(オフセット)を再生するときに、進行状況をレポートするAudioPlayer.ProgressReportIntervalPassed
イベント:再生中の場合、特定の間隔で繰り返し進行状況をレポートする以下は、EventRequest
タイプのリクエストメッセージで送信されたレポートのサンプルです。
{
"context": {
"AudioPlayer": {
"offsetInMilliseconds": 60000,
"playerActivity": "STOPPED",
"stream": {
"token": "TR-NM-17413540",
"url": "https://music.serviceprovider.net/content?id=17413540",
"urlPlayable": true
},
"totalInmillisecodns": 300000
},
"System": "{ ...}"
},
"request": {
"type": "EventRequest",
"requestId": "e5464288-50ff-4e99-928d-4a301e083d41",
"timestamp": "2017-09-05T05:41:21Z",
"event": {
"namespace": "AudioPlayer",
"name": "PlayStopped",
"payload": {}
}
},
"session": {
"new": true,
"sessionAttributes": {},
"sessionId": "69b20cc1-9166-41f3-a2dd-85b70f8e0bf5"
},
"version": "1.0"
}
上記のEventRequest
タイプのリクエストメッセージは、クライアントが全部で5分のオーディオコンテンツで、1分になるタイミングで再生を中断したことをレポートしています。Custom Extensionは、このようにして、クライアントの再生状態の変化を追跡することができます。例えば、AudioPlayer.PlayStopped
とAudioPlayer.PlayFinished
イベントが含まれたEventRequest
タイプのリクエストメッセージを収集して、オーディオを最後まで聞いたり、または最後まで聞かないユーザーを区別し、それを統計データにすることができます。
また、AudioPlayer.ProgressReportIntervalPassed
イベントが含まれたEventRequest
タイプのリクエストメッセージを使って、おおよそユーザーがオーディオコンテンツをどの位置まで聞いたかを把握することができます。ユーザーが次に同じオーディオコンテンツの再生をリクエストする場合、そのデータに基づいて、最後に聞いた位置から再生することができます。
注意
再生の進行状況のレポートに関するEventRequest
タイプのリクエストメッセージのうち、AudioPlayer.PlayFinished
イベントが含まれたメッセージを受け取った場合、Custom Extensionは、再生完了に対するクライアントの次のアクションをレスポンスメッセージで返す必要があります。そのアクションとして、次のオーディオコンテンツの再生を指示することもできますし、再生停止などの再生コントロールを指示することもできます。
ちなみに、このセクションで扱っているAudioPlayer
名前欄イベントには、AudioPlayer.PlaybackState
コンテキストが含まれます。その情報もまた、EventRequest
タイプのリクエストメッセージが送信されるときに含まれるので、Custom Extensionは含まれたAudioPlayer.PlaybackState
コンテキストからオーディオコンテンツのID、再生状態、オーディオコンテンツの再生位置などを把握できます。
以下は、AudioPlayer.PlaybackState
コンテキストが送信されたサンプルです。
{
"offsetInMilliseconds": 5077,
"playerActivity": "PLAYING",
"stream": {
"token": "TR-NM-17413540",
"url": "https://music.serviceprovider.net/content?id=17413540",
"urlPlayable": true
},
"totalInMilliseconds": 195265
}
Custom Extensionがクライアントにオーディオコンテンツの再生を指示するとき、レスポンスメッセージにAudioPlayer.Play
ディレクティブを含める必要があります。そのとき、AudioPlayer.Play
ディレクティブのaudioItem.stream.url
フィールドにオーディオコンテンツを再生できるURLを設定して送信します。
ただし、サービスの提供元によっては、セキュリティ上の問題により、永久に有効なURLを含めることができないことがあります。例えば、そのURLがさらされた場合、コンテンツを盗み取るための攻撃が発生する可能性がある場合などが考えられます。そのため、大抵の場合、比較的短い有効期限を持つインスタンスURLを使用します。また、クライアントがAudioPlayer.Play
ディレクティブを受信していても、より優先順位の高いタスクや、先に開始したタスク、またはネットワークの状況によって、オーディオコンテンツの再生開始が遅延することがあります。その場合、URLの有効期限が切れ、オーディオコンテンツを正常に再生できない可能性があります。
そのため、CLOVAはクライアントがオーディオコンテンツを再生できるURLを、再生の直前に取得する方法を提供しています。最初に、以下のようにAudioPlayer.Play
ディレクティブのurlPlayable
フィールドをfalse
に指定し、url
フィールドにURLではない、他の形式の値を設定します。
{
"audioItem": {
"audioItemId": "9CPWU-c82302b2-ea29-4f6c-ba6e-20fd268d8c3b-c1570067",
"title": "The theme song for LINE Friends",
"artist": "Unknown",
"stream": {
"beginAtInMilliseconds": 0,
"progressReport": {
"progressReportDelayInMilliseconds": null,
"progressReportIntervalInMilliseconds": null,
"progressReportPositionInMilliseconds": 60000
},
"token": "TR-NM-17413540",
"url": "clova:TR-NM-17413540",
"urlPlayable": false
}
},
"playBehavior": "REPLACE_ALL"
}
後にクライアントがAudioPlayer.Play
ディレクティブを処理するとき、urlPlayable
フィールドがfalse
に指定されていると、有効なオーディオコンテンツのURLを取得するためにAudioPlayer.StreamRequested
イベントをCLOVAに送信します。そのとき、イベントの内容はEventRequest
タイプのリクエストメッセージで、以下のように送信されます。
{
"context": {
...
},
"request": {
"type": "EventRequest",
"requestId": "e5464288-50ff-4e99-928d-4a301e083d41",
"timestamp": "2017-09-05T05:41:21Z",
"event": {
"namespace": "AudioPlayer",
"name": "StreamRequested",
"payload": {
"audioItemId": "9CPWU-c82302b2-ea29-4f6c-ba6e-20fd268d8c3b-c1570067",
"title": "The theme song for LINE Friends",
"artist": "Unknown",
"audioStream": {
"beginAtInMilliseconds": 0,
"progressReport": {
"progressReportDelayInMilliseconds": null,
"progressReportIntervalInMilliseconds": null,
"progressReportPositionInMilliseconds": 60000
},
"token": "TR-NM-17413540",
"url": "clova:TR-NM-17413540",
"urlPlayable": false
}
}
}
},
"session": {
"new": true,
"sessionAttributes": {},
"sessionId": "69b20cc1-9166-41f3-a2dd-85b70f8e0bf5"
},
"version": "1.0"
}
Custom Extensionは、そのタイミングで、再生できるオーディオコンテンツのURLをレスポンスメッセージで返す必要があります。そのために、AudioPlayer.StreamDeliver
ディレクティブをレスポンスメッセージに含める必要があります。クライアントは、以下のようなAudioPlayer.StreamDeliver
ディレクティブのボディを用いて、AudioPlayer.Play
ディレクティブを引き続き処理することができます。
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"card": {},
"directives": [
{
"header": {
"namespace": "AudioPlayer",
"name": "StreamDeliver"
},
"payload": {
"audioItemId": "5313c879-25bb-461c-93fc-f85d95edf2a0",
"audioStream": {
"token": "b767313e-6790-4c28-ac18-5d9f8e432248",
"url": "https://sample.musicservice.net/b767313e.mp3"
}
}
}
],
"outputSpeech": {},
"shouldEndSession": true
}
}
注意
cardは、Custom Extensionには対応しておりません。