AudioPlayerの再生キューの活用
渡辺です。
AudioPlayerでハローワールドでは、AudioPlayerを利用するスキルの基本を解説しました。 AudioPlayerスキルのセッションとイベントでは、AudioPlayerスキルでハンドリングしなければならないイベントと、実装時の注意事項を解説しました。 簡単な音楽再生スキルは充分に作成できると思います。
今回はAudioPlayerスキルの仕上げとして、複数の音楽ファイルを再生するスキルを作成してみました。
再生キューとストリーミング
AudioPlayerを有効にしたスキルを起動した場合、クライアント(Echo端末など)は再生キューを持ちます。
スキルからPlayディレクティブを送信すると、ストリーミング再生がはじまります。 次の曲を続けて再生したい場合、再生が終了する前に、次のオーディオを追加できます。 この時、オーディオはクライアントの再生キューにキューイングされます。 クライアントでは再生中のオーディオの再生が終わり次第、キューイングされたオーディオを順番に再生します。 キューイングされることにより、曲と曲の間のタイムラグを最小にできます。
再生キューは、PlayディレクティブやClearQueueディレクティブで制御できます。
Playディレクティブ
Playディレクティブでは、playBehavior
パラメーターで、どのようにオーディオを再生するか指定します。
playBehavior
パラメーターに設定できる値は以下の3種類です。
- REPLACE_ALL
- ENQUEUE
- REPLACE_ENQUEUED
REPLACE_ALL
を指定した場合、そのオーディオを即時に再生します。
キューイングされたオーディオが再生されていても上書き(REPLACE)するので、単一のオーディオを再生する場合は、REPLACE_ALL
が適します。
ENQUEUE
を指定した場合、キューの最後にオーディオを追加します。
再生中のオーディオに影響を与えないため、次の曲を指定するようなケースで有効です。
クライアントでは、現在の曲が終わり次第、連続的に再生されます。
連続して曲を再生するスキルでは、PlaybackNearlyFinished
イベントの処理で、ENQUEUE
を指定して次の曲をクライアントに指定すると良いでしょう。
REPLACE_ENQUEUED
を指定した場合、キューをクリアした上で、次の曲としてオーディオを追加します。
再生中のオーディオには影響ありません。
ClearQueueディレクティブ
キューのクリアだけを行いたい場合、ClearQueueディレクティブを使用します。
Playディレクティブと同様に、clearBehavior
をヒントとして与えます。
- CLEAR_ENQUEUED
- CLEAR_ALL
CLEAR_ENQUEUED
は、キューイングされたオーディオをクリアしますが、再生中のオーディオの再生は継続します。
CLEAR_ALL
では、キューイングされたオーディオをクリアし、再生中のオーディオも停止します。
ジュークボックス系スキルを作ってみる
スキルで再生リストを管理し、各イベントをハンドリングすることで、ジュークボックス系スキルが作れます。 この時、キューイングを上手く活用し、クライアントから曲がスムーズに流れるようにすると良いでしょう。
再生リストの利用と再生開始
はじめに初期化処理を行います。
オーディオリスト(audioList)は、タイトルと曲のURLで構成されます。 今回はLambdaにハードコーディングしましたが、ここはDyanamoDBなどから取得しても良いでしょう。
playList
関数は、曲のインデックスを配列で返します。
ランダム再生に対応するため、シャッフルオプションを用意しておきました。
const audioList = [ { title: "001", url: "https://s3-ap-northeast-1.amazonaws.com/[BucketName]/001.mp3" }, { title: "002", url: "https://s3-ap-northeast-1.amazonaws.com/[BucketName]/002.mp3" }, { title: "003", url: "https://s3-ap-northeast-1.amazonaws.com/[BucketName]/003.mp3" } ]; const playList = function(length, shuffled) { var array = Array.apply(null, {length: length}).map(Number.call, Number); if (shuffled) { for (var i = 0; i < length; i++) { var j = Math.floor(Math.random() * length); var temp = array[i]; array[i] = array[j]; array[j] = temp; } } return array; }; const findIndexInPlayList = function(title) { for (var index = 0 ; index < audioList.length; index++) { if (audioList[index]['title'] === title) return index; } return -1; };
'Start': function () { // ex [0, 1, 2, 3] this.attributes['playOrder'] = playList(audioList.length, false); this.attributes['index'] = 0; this.attributes['token'] = null; this.attributes['loop'] = false; const playOrder = this.attributes['playOrder']; const index = this.attributes['index']; const target = playlist[playOrder[index]]; const url = target['url']; const token = target['title']; const expectedPreviousToken = null; const offset = 0; this.response.audioPlayerPlay('REPLACE_ALL', url, token, expectedPreviousToken, offset); this.emit(':responseReady'); },
PlaybackStartedイベントの処理
クライアントで曲が再生されるとPlaybackStarted
イベントが発生します。
この時、再生された曲の情報をセッション(DyanmoDB)に保存しておきましょう。
'PlaybackStarted': function () { const token = this.event.request.token; this.attributes['index'] = findIndexInPlayList(token); this.attributes['token'] = token; this.emit(':responseReady'); },
イベントのrequest.token
から再生した曲のToken
が取れるため、そこから曲を逆引きしています。
PlaybackStarted
イベントは、停止後の再生などでも発生するので注意してください。
PlaybackNearlyFinishedイベントの処理
PlaybackNearlyFinished
は、曲の再生が終わる前に呼ばれるイベントです。
ここで、次の曲をキューに追加し、クライアントでスムーズな再生をさせるとよいでしょう。
この時、セッションから再生中の曲のインデックスを取り、インクリメントすることで次曲を装填しています。
なお、キューに追加する場合は、前曲のToken
をexpectedPreviousToken
に指定しなければなりません。
'PlaybackNearlyFinished': function () { var index = this.attributes['index']; if (this.attributes['loop']) { // loop } else { index++; if (index === audioList.length) index = 0; } const playOrder = this.attributes['playOrder']; const target = playlist[playOrder[index]]; const url = target['url']; const token = target['title']; const expectedPreviousToken = this.attributes['token']; const offset = 0; this.response.audioPlayerPlay('ENQUEUE', url, token, expectedPreviousToken, offset); this.emit(':responseReady'); },
NextIntent/PreviousIntentの処理
「Alexa, 次の曲」などAudioPlayerの標準機能を実装します。
曲を指定する流れは変わりませんが、即時に再生をはじめるためREPLACE_ALL
を設定します。
'AMAZON.NextIntent': function () { var index = this.attributes['index'] + 1; if (index === audioList.length) index = 0; const playOrder = this.attributes['playOrder']; const target = audioList[playOrder[index]]; const url = target['url']; const token = target['title']; const expectedPreviousToken = null; const offset = 0; this.response.audioPlayerPlay('REPLACE_ALL', url, token, expectedPreviousToken, offset); this.emit(':responseReady'); }, 'AMAZON.PreviousIntent': function () { var index = this.attributes['index'] - 1; if (index === -1) index = (audioList.length - 1); const playOrder = this.attributes['playOrder']; const target = audioList[playOrder[index]]; const url = target['url']; const token = target['title']; const expectedPreviousToken = null; const offset = 0; this.response.audioPlayerPlay('REPLACE_ALL', url, token, expectedPreviousToken, offset); this.emit(':responseReady'); },
ShuffleOnIntent/ShuffleOffIntentの処理
シャッフル再生も対応しましょう。 シャッフルが有効になった場合の処理と無効になった場合の処理を実装します。
今回は、シャッフルしても現在の曲は流れたままという仕様にしました。
'AMAZON.ShuffleOnIntent': function () { this.attributes['playOrder'] = playList(audioList.length, true); const output = this.t('SHUFFLE_ON'); this.response.speak(output); this.emit(':responseReady'); }, 'AMAZON.ShuffleOffIntent': function () { this.attributes['playOrder'] = playList(audioList.length, false); const output = this.t('SHUFFLE_OFF'); this.response.speak(output); this.emit(':responseReady'); },
LoopOffIntent/LoopOnIntentの処理
ループ再生機能のオン/オフを処理します。
ループ再生は再生リスト全体をループするかどうかという仕様にしました。
ループ再生がオフの場合、PlaybackNearlyFinished
イベントで最後の曲の場合、キューに曲を追加しません。
'AMAZON.LoopOffIntent': function () { this.attributes['loop'] = false; const output = this.t('LOOP_OFF'); this.response.speak(output); this.emit(':responseReady'); }, 'AMAZON.LoopOnIntent': function () { this.attributes['loop'] = true; const output = this.t('LOOP_ON'); this.response.speak(output); this.emit(':responseReady'); },
1曲のみのループ再生は別のインテントで実装可能です。
RepeatIntentの処理
RepeatIntentは「Alexa, もう1回」といった発話に対応します。 ここでは曲を最初から再生しなおす実装としました。
'AMAZON.RepeatIntent': function () { const token = this.event.context.AudioPlayer.token; const index = findIndexInPlayList(token); const playOrder = this.attributes['playOrder']; const target = audioList[playOrder[index]]; const url = target['url']; const expectedPreviousToken = null; const offset = 0; this.response.audioPlayerPlay('REPLACE_ALL', url, token, expectedPreviousToken, offset); this.emit(':responseReady'); },
まとめ
こんな感じに実装すれば、ジュークボックス系スキルが実装できます。 キューを活用してクライアントに曲を先行で送ることがポイントでしょう。
なお、デフォルトで実装すべきインテントが多くあります。 どんな発話に対応してインテントが起動するかに注意してください。 意図した発話でない場合、期待しない挙動をとってしまいます。 必要に応じてカスタムインテントを追加しましょう。