alexaスキルにリマインダー機能を追加したら、なぜか音声のループができなくなった

alexaスキルにリマインダー機能を追加したら、なぜか音声のループができなくなった

AudioPlayer.PlaybackNearlyFinished が動かなくなった

どうも、先日既にリリース済みの「瞑想タイマー」スキルに新たにリマインダー機能を追加してバージョンアップしようとしたところ、思いもよらぬところで躓いたので、備忘録です。

躓いた内容は、

今回のバージョンアップで追加した機能は、リマインダーAPIを使用しユーザーの瞑想を習慣化するために、指定された日時に「瞑想する時間ですよー」と通知する機能です。

ですが、なぜかAmazonに申請しようと実機確認をしていたら、これまで問題なく動いていた、音源のループ機能(AudioPlayer.PlaybackNearlyFinished)が上手く利用できなくなりました。(AudioPlayer.PlaybackNearlyFinishedについては、詳しくは公式ドキュメントをご覧下さい。)

cloud Watchに残されたエラーを確認したところ、

Cannot get SessionAttributes from out of session request

とのこと。

リマインダーAPIを追加して、動かなくなったので、エラーをよく見ずにAPI周りの処理を確認ばかりに気を取られ無駄に時間を溶かしました。

AudioPlayer.PlaybackNearlyFinished リクエストも通常のIntentリクエストと同様の処理をされる

冷静にエラーを読めばすぐに気がつくのですが、通常のAmazonEchoから

  • LaunchRequest
  • IntentRequest

では、JSONの中にSessionAttributes という形で、ユーザーとの一時的な対話の履歴を保持するオブジェクトが存在します。しかし、AudioPlayer.PlaybackNearlyFinished リクエストは通常のリクエストとは違うため、SessionAttributesが存在しないため、SessionAttributesを使用しようとすると上記のようなエラーになります。

エラーの原因となったコード

const SetRemindHandler = {
  async canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    const dataController = new DataControlloer(handlerInput)
    const setRemindMode = dataController.tmpDataCheckExist(WHEN,TIME,SETREMINDER);
    // console.log(`リマインダーモード${setRemindMode}`);
    return setRemindMode
      && request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.YesIntent';
  },

const dataController = new DataControlloer(handlerInput)

const setRemindMode = dataController.tmpDataCheckExist(WHEN,TIME,SETREMINDER);

↑こいつが原因でした、DataControllerという一時保存・永続保存のデータを管理するクラスを作成しており、この中に、リマインダーをセットしようとしているのかの判定をする真偽を確認しているのですが、一時保存データつまりSessionAttributesを探しに行きます。

LaunchRequest・IntentRequestなどのリクエストであれば問題は無いのですが、AudioPlayer.PlaybackNearlyFinishedは、SessionAttributesを持ち合わせていないのでエラーで落ちてしまいます。

雑な対処法

const SetRemindHandler = {
  async canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    if (request.type !== 'IntentRequest'){
      return false
    }
    const dataController = new DataControlloer(handlerInput)
    const setRemindMode = dataController.tmpDataCheckExist(WHEN,TIME,SETREMINDER);
    // console.log(`リマインダーモード${setRemindMode}`);
    return setRemindMode
      && request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.YesIntent';
  },

上記のような雑なコードで一旦対応しました。(Amazon様午前中に申請しなと審査してくれるの翌日になっちゃうので)

そもそも、なぜこんなことになったか

リマインダーAPIは、Amazon様のガイドラインによれば必ずリマインダーを作詞する前に、

スキル「今からリマインダーを作成しますがいいですか?」

ユーザー「はい、良いですよ!」

という、やり取りを毎回する必要があります。ですので、リクエストをまたぐ必要が有るので、SessionAttributesに「リマインダーをセットしようとしてますよー」的なことを一時保存して、ユーザーからのYesIntentで実際にリマインダーAPIを叩いています。

コード書きながら、「これ絶対ベストプラクティスではないなー。」と思ってますので、もしもっとまともな方法ご存知の方は教えて頂ければ幸いです。

もしくは、発見したら、ブログ書き直します。

今日も最後までご覧いただきありがとうございました。