LoginSignup
5
2

More than 5 years have passed since last update.

TypeScript を使って Clova Custom Extension を作ろう - オーディオコンテンツの再生

Last updated at Posted at 2018-08-17

はじめに

以前投稿した以下の記事をベースに、本投稿ではオーディオコンテンツを再生する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ディレクティブには、headerpayloadを含める必要があります。

headerにはクライアントに何を指示したいのか、
payloadには指示に必要な情報を含めます。

payloadには含まれたコンテンツをすぐに再生する為に、urlPlayable: trueplayBehavior: '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イベントを発生させるために、urlPlayablefalseに設定する。
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.audioStreamAudioPlayer.StreamRequestedイベントのpayload.audioStreamを設定する。
  • StreamDeliverディレクティブのpayload.audioStream.urlに有効なオーディオコンテンツのURLを設定する。
  • StreamDeliverディレクティブのpayload.audioStream.urlPlayabletrueに設定する。
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を無視した独自実装になっているのでご自身の環境に合わせて呼び出してください。

src/extension/clova-extension-lambda.ts
    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を作成しました。
これから作成される方の参考になれば幸いです。

今回のソースは以下にあります。
こちらのソースは以下の投稿の実装も兼ねてます。

Clova Custom Extention の使用状況を QuickSight で! - Qiita

daisukeArk/clova-extension-sample-audio-player - GitHub

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2