[日本語Alexa] Alexa-SDK Ve2 (その9) Audio の連続再生

2018.10.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

Alexa SDK V2を使用した、AudioPlayerの基本的な使い方については、下記で紹介させて頂きました。

今回は、上記の応用として、オーディオの連続再生の要領を確認したいと思います。

2 一度だけ再生するAudioスキル

最初に、ベースとなるサンプルとして、Audioを1回だけ再生するスキルを作成してみます。

ユーザーからの呼び出して、PlayIntentHandlerが起動され、audio001.mp3の再生が始まります。そして、それが終わった時点でオーディオ再生は終了します。

PauseIntentHandler「中断して」及び、ResumeIntentHandler「再開して」にも対応しており、途中で止めることも出来ますが、やはり再生は1回だけです。

const Alexa = require('ask-sdk');
const mp3 = "https://exsample.com/audio001.mp3";

let skill;
exports.handler = async function (event, context) {
    if (!skill) {
      skill = Alexa.SkillBuilders.standard()
        .addRequestHandlers(
            PlayIntentHandler,
            PauseIntentHandler,
            ResumeIntentHandler,
            PlaybackHandler)
        .create();
    }
    return skill.invoke(event);
}

const PlayIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'LaunchRequest','PlayIntent');
    },
    async handle(h) {
        const url = mp3;
        const token = mp3;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('REPLACE_ALL', url, token, 0, null)
            .speak('サンプルを再生します')
            .getResponse();
    }
};

// 中断
const PauseIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'AMAZON.PauseIntent');
    },
    async handle(h) {
        return h.responseBuilder 
        .addAudioPlayerStopDirective()
        .getResponse();
    }
};

// 再開
const ResumeIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'AMAZON.ResumeIntent');
    },
    async handle(h) {
        const url = mp3;
        const AudioPlayer = h.requestEnvelope.context.AudioPlayer;
        const token = AudioPlayer.token;
        const offset = AudioPlayer.offsetInMilliseconds;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('REPLACE_ALL', url, token, offset, null)
            .getResponse();
  }
};

// 以下のイベントは処理しない
const PlaybackHandler = {
    canHandle(h) {
        return isMatch(h,'AudioPlayer.PlaybackStarted',
                          'AudioPlayer.PlaybackFinished',
                          'AudioPlayer.PlaybackStopped',
                          'AudioPlayer.PlaybackNearlyFinished',
                           'AudioPlayer.PlaybackFailed');
    },
    async handle(h) {
        return h.responseBuilder.getResponse();
    }
};

// インテント名・タイプのマッチング用のヘルパー
isMatch = function(h, ...intents) {
    return intents.some( intent => {
        if (intent == 'SessionEndedRequest' || intent == 'LaunchRequest') {
            return h.requestEnvelope.request.type == intent;
        }
        if (h.requestEnvelope.request.type == 'IntentRequest') {
            return h.requestEnvelope.request.intent.name === intent
        }
        if (h.requestEnvelope.request.type.indexOf('AudioPlayer.') === 0) {
          return h.requestEnvelope.request.type === intent
        }
        return false;
    })
}

3 再生を繰り返すAudioスキル

AudioPlayerを有効にすると、各種のAudioPlayerのイベントがスキルに送られてきます。

タイプ 詳細
AudioPlayer.PlaybackStarted 再生開始
AudioPlayer.PlaybackFinished 再生終了
AudioPlayer.PlaybackStopped 再生停止
AudioPlayer.PlaybackNearlyFinished もうすぐ再生終了
AudioPlayer.PlaybackFailed 再生失敗

先のサンプルでは、これらをまったく処理しなかったので、最初に再生したオーディオが終わった時点で、キューは空となり、オーディオの再生は終了していました。

連続再生の一例としては、AudioPlayer.PlaybackNearlyFinishedが送られてきたタイミングで、addAudioPlayerPlayDirective() で、ENQUEUEを指定して、次のオーディオをキューに入れる方法があります。

  • AudioPlayer.PlaybackNearlyFinished : もうすぐ再生終了のイベント
  • ENQUEUE : 現在のキューの最後に追加、再生中のストリームには影響なし

AudioPlayer.PlaybackNearlyFinishedを処理する一例は下記のとおりです。

const PlaybackNearlyFinishedHandler = {
    canHandle(h) {
        return isMatch(h, 'AudioPlayer.PlaybackNearlyFinished');
    },
    async handle(h) {
        const url = mp3;
        const token = mp3;
        const AudioPlayer = h.requestEnvelope.context.AudioPlayer;
        const expectedPreviousToken = AudioPlayer.token;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('ENQUEUE', url, token, 0, expectedPreviousToken)
            .getResponse();
    }
};

ENQUEUEを使用するときは、前のストリームのTokenをexpectedPreviousTokenに指定する必要があることにご注意下さい。

4 違うオーディオを順次再生するAudioスキル

続いて連続再生の応用として、違ったAudioを順次再生するスキルを作成してみます。

(1) コンテンツ

最初にコンテンツです。扱いやすいように配列で準備してみました。

const mp3_001 = "https://exsample.com/audio001.mp3";
const mp3_002 = "https://exsample.com/audio002.mp3";
const mp3_003 = "https://exsample.com/audio003.mp3";
const mp3_004 = "https://exsample.com/audio004.mp3";
const audioList = [mp3_001, mp3_002, mp3_003, mp3_004];

(2) インデックス

順次再生するため、リストのインデックスが必要ですが、これは、セッションをまたぐ情報となりますので、DynamoDBを使用した永続化情報で準備しました。

永続化の要領等については、下記の「4 Persistent attributes(セッションを跨いで永続化する DynamoDB等が必要)」をご参照下さい。
参考:[日本語Alexa] Alexa-SDK Ver2(その4) スキル属性

async function getIndex(h) {
    let attributes = await h.attributesManager.getPersistentAttributes();
    if(!attributes.index){ 
        attributes.index = 0; 
    }
    return attributes.index;
}

async function setIndex(h, index) {
    let attributes = await h.attributesManager.getPersistentAttributes();
    attributes.index = index;
    h.attributesManager.setPersistentAttributes(attributes);
    await h.attributesManager.savePersistentAttributes();
}

(3) PlayIntent

配列となったAudioリソースの利用したPlayIntentは、次のようになりました。

const PlayIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'LaunchRequest', 'PlayIntent');
    },
    async handle(h) {
        const index = await getIndex(h);
        const url = audioList[index];
        const token = index;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('REPLACE_ALL', url, token, 0, null)
            .speak('サンプルを再生します')
            .getResponse();
    }
};

(4) PlaybackNearlyFinishedHandler

PlaybackNearlyFinishedHandlerでは、インデックスをインクリメントして、次のAudioをキューにセットしています。

<br />const PlaybackNearlyFinishedHandler = {
    canHandle(h) {
        return isMatch(h, 'AudioPlayer.PlaybackNearlyFinished');
    },
    async handle(h) {
        let index = await getIndex(h);
        index++;
        if(audioList.length <= index) {
            index = 0;
        }
        await setIndex(h, index);
        const url = audioList[index];
        const token = index;
        const AudioPlayer = h.requestEnvelope.context.AudioPlayer;
        const expectedPreviousToken = AudioPlayer.token;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('ENQUEUE', url, token, 0, expectedPreviousToken)
            .getResponse();
    }
};

(5) PauseとResume

AMAZON.ResumeIntent(再開)については、あまり変化はないですが、AMAZON.PauseIntent(中断)時は、その時点で再生中であったインデックスを記憶する必要があります。

これは、PlaybackNearlyFinishedHandlerで、インデックスをインクリメントするので、「中断」したタイミングによっては、DB上のインデックスが、中断時のインデックスとづれてしまう可能性があるためです。

const PauseIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'AMAZON.PauseIntent');
    },
    async handle(h) {
        const AudioPlayer = h.requestEnvelope.context.AudioPlayer;
        const index = AudioPlayer.token;
        await setIndex(h, index);
        return h.responseBuilder 
        .addAudioPlayerStopDirective()
        .getResponse();
    }
};

const ResumeIntentHandler = {
    canHandle(h) {
        return isMatch(h, 'AMAZON.ResumeIntent');
    },
    async handle(h) {
        const index = await getIndex(h);
        const url = audioList[index];
        const AudioPlayer = h.requestEnvelope.context.AudioPlayer;
        const token = AudioPlayer.token;
        const offset = AudioPlayer.offsetInMilliseconds;
        return h.responseBuilder
            .addAudioPlayerPlayDirective('REPLACE_ALL', url, token, offset, null)
            .getResponse();
  }
};

5 その他の標準ビルトインインテント

Audioインターフェースを使用したスキルでは、AMAZON.PauseIntent及び、AMAZON.ResumeIntentの実装が必須となることは紹介しましたが、この2つ以外にも、下記の標準ビルトインインテントが、スキルに送信されます。(インテントスキーマに定義していなくても送信されます)

インテント 一般的な発話
AMAZON.LoopOffIntent ループ再生オフ ループ停止 ループ取り消し
AMAZON.LoopOnIntent ループ再生して
AMAZON.NextIntent 次 次にスキップして 次のに飛んで
AMAZON.PreviousIntent 前に戻って 前へ 直前のへ
AMAZON.RepeatIntent もう一度 もう一度言って 繰り返して
AMAZON.ShuffleOffIntent シャッフル再生をオフにして シャッフル止めて シャッフル再生オフ
AMAZON.ShuffleOnIntent シャッフル再生して
AMAZON.StartOverIntent 最初から始めて 最初に戻って もう一度初めから

先のサンプルでは、インデックスは、順次インクリメントするだけの処理でしたが、上記のインテントの意味に応じたインデックス操作を実装すれば、色々変化のある再生が可能でしょう。

下記の記事では、「ジュークボックス系スキルを作ってみる」という事で、これらのインテントを処理する例が紹介されています。

参考:AudioPlayerの再生キューの活用

6 最後に

今回は、Audioインターフェースを使用したスキルで連続再生する要領について確認してみました。

通常のスキルと違って、スキルが終了してからのイベントを処理することになるので、ちょっと、イメージが湧きにくい部分もありますが、まずは、再生中の各タイミングで送られてくるAudioPlayerタイプのイベントを把握すると理解が簡単だと思いました。

7 参考リンク


AudioPlayer の概要
[Alexaスキル] AudioPlayerでハローワールド
[Alexaスキル] AudioPlayerスキルのセッションとイベント
AudioPlayerの再生キューの活用
AudioPlayerインターフェースのリファレンス
AudioPlayer インターフェース(v1.0)
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その1)はじめの一歩
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その2)ハンドラの登録
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その3)レスポンスの作成
[日本語Alexa] Alexa-SDK Ver2(その4)スキル属性
[日本語Alexa] Alexa-SDK Ver2(その5)ダイアログモード
[日本語Alexa] Alexa-SDK Ver2(その6) 所在地情報
[日本語Alexa] Alexa-SDK Ver2(その7) ディスプレイ表示
[日本語Alexa] Alexa-SDK Ver2(その8) AudioPlayer