はじめに
以前投稿した以下の記事をベースに、本投稿ではオーディオコンテンツを再生するExtensionを作成します。
TypeScript を使って Clova Custom Extension を作ろう - Express
TypeScript を使って Clova Custom Extension を作ろう - AWS Lambda
公式ドキュメントはこちら
https://clova-developers.line.me/guide/#/CEK/Guides/Build_Custom_Extension.md#DirectClientToPlayAudio
https://clova-developers.line.me/guide/#/CEK/Guides/Build_Custom_Extension.md#UpdateAudioURLForSecurity
目次
- 実装する機能について
- 実装
- オーディオコンテンツの再生インテント(PlayIntent)の作成
- イベント要求(EventRequest)の作成
- オーディオコンテンツの再生インテント(DelayPlayIntent)の作成
-
AudioPlay.StreamRequested
イベントに対する応答(AudioPlayer.StreamDeliver)の作成 - イベント要求呼出の追加
実装する機能について
以下の機能を追加します。
- PlayIntent
- オーディオコンテンツの再生
- DelayPlayIntent
- StreamRequested`イベントによるオーディオコンテンツの再生を要求
- EventRequest
- オーディオコンテンツ再生終了時の制御
-
AudioPlay.StreamRequested
イベントに対する応答
EventRequestってなんぞやってところを公式ドキュメントから抜粋
EventRequestは、ユーザの発話の有無に関わらず、デバイスの状態が変化したときにExtensionに送信されるメッセージです。これらのイベントは、デバイスの状態を取得したり、状態変化したことを検知することに利用できます。また、Extensionがオーディオコンテンツを提供する際にも使用されます。
オーディオコンテンツでは、再生・停止・一時停止・再生終了・再生再開、再生状況レポートなどの情報がそれぞれのタイミングでクライアントから送られてきます。
他にもExtensionが有効・無効化されたかなども送られてきたりします。
実装
実装にはSDKを利用していますが、型定義が無いものもあるのでanyで回避している箇所があります。
オーディオコンテンツの再生インテント(PlayIntent)の作成
オーディオコンテンツを再生するには、レスポンスメッセージのディレクティブにAudioPlayer.Play
ディレクティブを含めて返す必要があります。
ここでは、以下のレスポンスを返します。
- 音声再生前に「サンプルを再生します。」と発話する。
- 発話後、すぐにオーディオコンテンツを再生する。
- shouldEndSessionは
false
にする。
switch (intent) {
case 'HelloWorldIntent':
// ~~~ 省略 ~~~
break;
case 'PlayIntent':
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('サンプルを再生します。')
);
// 型定義が無いのでanyで回避
responseHelper.responseObject.response.directives = <any>[
{
header: {
namespace: 'AudioPlayer',
name: 'Play',
dialogRequestId: (<any>responseHelper.requestObject.request).requestId,
messageId: uuid.v4()
},
payload: {
audioItem: {
audioItemId: uuid.v4(),
stream: {
beginAtInMilliseconds: 0,
url: process.env.AUDIO_URL,
urlPlayable: true
}
},
source: {
name: 'sample'
},
playBehavior: 'REPLACE_ALL'
}
}
];
break;
default:
// ~~~ 省略 ~~~
break;
}
- 音声再生前に「サンプルを再生します。」と発話する。
レスポンスに発話する内容を設定します。
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('サンプルを再生します。')
);
- 発話後、すぐにオーディオコンテンツを再生する。
AudioPlayer.Play
ディレクティブには、header
とpayload
を含める必要があります。
header
にはクライアントに何を指示したいのか、
payload
には指示に必要な情報を含めます。
payloadには含まれたコンテンツをすぐに再生する為に、urlPlayable: true
とplayBehavior: 'REPLACE_ALL'
を指定します。
urlPlayable
はドキュメントにもありますが、false
に設定すると次のオーディオストリームの再生直前にAudioPlayer.StreamRequested
イベントが発生し、有効なオーディオコンテンツのURLを返す制御ができます。
playBehavior: 'REPLACE_ALL'
は再生キューをすべてクリアして、送信されたオーディオストリームをすぐに再生するのに指定します。
payload: {
audioItem: {
audioItemId: uuid.v4(),
stream: {
beginAtInMilliseconds: 0,
url: process.env.AUDIO_URL,
urlPlayable: true
}
},
source: {
name: 'sample'
},
playBehavior: 'REPLACE_ALL'
}
- shouldEndSessionは
false
にする。
後述します。
オーディオコンテンツの再生インテント(DelayPlayIntent)の作成
StreamRequested
イベントによるオーディオコンテンツの再生を要求するには、前項と同様にレスポンスメッセージのディレクティブにAudioPlayer.Play
ディレクティブを含めて返す必要があります。
前項と違いここでは、以下のレスポンスを返します。
- 音声再生前に「遅れてサンプルを再生します。」と発話する。
- 発話後、すぐにオーディオコンテンツを再生せずに
StreamRequested
イベントを発生させるために、urlPlayable
をfalse
に設定する。
case 'DelayPlayIntent':
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('遅れてサンプルを再生します。')
);
// 型定義が無いのでanyで回避
responseHelper.responseObject.response.directives = <any>[
{
header: {
dialogRequestId: (<any>responseHelper.requestObject.request).requestId,
messageId: uuid.v4(),
name: 'Play',
namespace: 'AudioPlayer'
},
payload: {
audioItem: {
audioItemId: uuid.v4(),
stream: {
beginAtInMilliseconds: 0,
url: 'clova:Test',
urlPlayable: false
}
},
source: {
name: 'sample'
},
playBehavior: 'REPLACE_ALL'
}
}
];
responseHelper.responseObject.response.shouldEndSession = true;
break;
イベント要求(EventRequest)の作成
公式ドキュメントから抜粋
再生の進行状況のレポートに関するEventRequestタイプのリクエストメッセージのうち、AudioPlayer.PlayFinishedイベントが含まれたメッセージを受け取った場合、Custom Extensionは、再生完了に対するクライアントの次のアクションをレスポンスメッセージで返す必要があります。そのアクションとして、次のオーディオコンテンツの再生を指示することができます。
ここで先程後述すると書いたshouldEndSessionは false にする
ですが、
これをしておかないと、再度PlayIntentを呼び出すことが出来ませんでした。
ここではオーディオコンテンツ再生終了後に、AudioPlayer.PlayFinished
イベントをハンドリングしてもう一度PlayIntentを呼び出せるようにします。
公式ドキュメントのPlayFinished
メッセージサンプルを見ると以下のようになっています。
実際に送られてくるメッセージは以下のようになっています。
{
"context": [
...
],
"event": {
"namespace": "AudioPlayer",
"name": "PlayFinished",
"payload": {
"offsetInMilliseconds": 110045,
"token": ""
}
}
}
以下のようにAudioPlayer
, PlayFinished
を捕捉し、レスポンスメッセージに以下を実装します。
- 再生が終了したことを発話し、待受状態にする。
export const eventRequestHandler = async (responseHelper: Clova.Context) => {
// 型定義が無いのでanyで回避
const namespace = (<any>responseHelper.requestObject.request).event.namespace;
const name = (<any>responseHelper.requestObject.request).event.name;
switch (namespace) {
case 'AudioPlayer':
switch (name) {
case 'PlayFinished':
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('再生終了しました。')
);
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('再生して、と言ってください。'),
true
);
break;
default:
break;
}
break;
default:
break;
}
};
StreamRequested
イベントも同様に捕捉し、有効なオーディオコンテンツのURLを応答するStreamDeliver
ディレクティブを返します。
-
StreamDeliver
ディレクティブのpayload.audioStream
にAudioPlayer.StreamRequested
イベントのpayload.audioStream
を設定する。 -
StreamDeliver
ディレクティブのpayload.audioStream.url
に有効なオーディオコンテンツのURLを設定する。 -
StreamDeliver
ディレクティブのpayload.audioStream.urlPlayable
をtrue
に設定する。
export const eventRequestHandler = async (responseHelper: Clova.Context) => {
// 型定義が無いのでanyで回避
const namespace = (<any>responseHelper.requestObject.request).event.namespace;
const name = (<any>responseHelper.requestObject.request).event.name;
switch (namespace) {
case 'AudioPlayer':
switch (name) {
case 'StreamRequested':
responseHelper.setSimpleSpeech(
Clova.SpeechBuilder.createSpeechText('追加再生します。')
);
// StreamDeliverディレクティブ
responseHelper.responseObject.response.directives = <any>[
{
header: {
namespace: 'AudioPlayer',
name: 'StreamDeliver',
dialogRequestId: (<any>responseHelper.requestObject.request).requestId,
messageId: uuid.v4()
},
payload: {
audioItemId: uuid.v4(),
audioStream: (<any>responseHelper.requestObject.request).event.payload.audioStream
}
}
];
(<any>responseHelper.responseObject.response.directives[0].payload).audioStream.url = process.env.AUDIO_URL;
(<any>responseHelper.responseObject.response.directives[0].payload).audioStream.urlPlayable = true;
responseHelper.responseObject.response.shouldEndSession = true;
break;
default:
break;
}
break;
default:
break;
}
};
イベント要求呼出の追加
以下のように作成したeventRequestHandler
を呼び出しを追加します。
ここの処理はSDKを無視した独自実装になっているのでご自身の環境に合わせて呼び出してください。
switch (<any>context.requestObject.request.type) {
case 'LaunchRequest':
requestHandler = Handlers.launchRequestHandler;
break;
case 'IntentRequest':
requestHandler = Handlers.intentHandler;
break;
case 'EventRequest':
requestHandler = Handlers.eventRequestHandler;
break;
case 'SessionEndedRequest':
default:
requestHandler = Handlers.sessionEndedRequestHandler;
break;
}
まとめ
本投稿では、AudioPlayer.Play
, AudioPlayer.StreamDeliver
ディレクティブを利用しオーディオコンテンツを再生するExtensionを作成しました。
これから作成される方の参考になれば幸いです。
今回のソースは以下にあります。
こちらのソースは以下の投稿の実装も兼ねてます。