修改Alexa Audio Player示例项目以播放特定曲目

时间:2017-01-04 03:20:50

标签: node.js audio alexa

我已经关注了来自https://github.com/alexa/skill-sample-nodejs-audio-player的示例代码,并通过我的Amazon Echo获取了示例播客播放器。

如何修改此代码以告知Alexa“让MySkill播放$ trackname”。而不只是从顶部玩?

我是创新技能的新手,但我已阅读所有文档,并了解这涉及音频指令。但是,我无法弄清楚究竟发生了什么。

在示例代码中,audioAssets.js包含标题和网址列表。例如,如果我想说“Play Episode 138”(其中一个标题) - 为了做到这一点,我需要修改哪些文件?

 'use strict';


var audioData = [
{
    'title' : 'Episode 140',
    'url' : 'https://feeds.soundcloud.com/stream/275202399-amazon-web-  services-306355661-amazon-web-services.mp3'
},
{
    'title' : 'Episode 139',
    'url' : 'https://feeds.soundcloud.com/stream/274166909-amazon-web-services-306355661-aws-podcast-episode-139.mp3'
},
{
    'title' : 'Episode 138',
    'url' : 'https://feeds.soundcloud.com/stream/273105224-amazon-web-services-306355661-aws-podcast-episode-138.mp3'
},
{
    'title' : 'Episode 137',
    'url' : 'https://feeds.soundcloud.com/stream/272089501-amazon-web-services-306355661-aws-podcast-episode-137.mp3'
}

 ];

 module.exports = audioData;

我假设代码会进入stateHandlers.js但老实说我不确定。

 'use strict';

 var Alexa = require('alexa-sdk');
 var audioData = require('./audioAssets');
 var constants = require('./constants');

 var stateHandlers = {
startModeIntentHandlers : Alexa.CreateStateHandler(constants.states.START_MODE, {
    /*
     *  All Intent Handlers for state : START_MODE
     */
    'LaunchRequest' : function () {
        // Initialize Attributes
        this.attributes['playOrder'] = Array.apply(null, {length: audioData.length}).map(Number.call, Number);
        this.attributes['index'] = 0;
        this.attributes['offsetInMilliseconds'] = 0;
        this.attributes['loop'] = true;
        this.attributes['shuffle'] = false;
        this.attributes['playbackIndexChanged'] = true;
        //  Change state to START_MODE
        this.handler.state = constants.states.START_MODE;

        var message = 'Welcome to the AWS Podcast. You can say, play the audio to begin the podcast.';
        var reprompt = 'You can say, play the audio, to begin.';

        this.response.speak(message).listen(reprompt);
        this.emit(':responseReady');
    },
    'PlayAudio' : function () {
        if (!this.attributes['playOrder']) {
            // Initialize Attributes if undefined.
            this.attributes['playOrder'] = Array.apply(null, {length: audioData.length}).map(Number.call, Number);
            this.attributes['index'] = 0;
            this.attributes['offsetInMilliseconds'] = 0;
            this.attributes['loop'] = true;
            this.attributes['shuffle'] = false;
            this.attributes['playbackIndexChanged'] = true;
            //  Change state to START_MODE
            this.handler.state = constants.states.START_MODE;
        }
        controller.play.call(this);
    },
    'AMAZON.HelpIntent' : function () {
        var message = 'Welcome to the AWS Podcast. You can say, play the audio, to begin the podcast.';
        this.response.speak(message).listen(message);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent' : function () {
        var message = 'Good bye.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent' : function () {
        var message = 'Good bye.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    'SessionEndedRequest' : function () {
        // No session ended logic
    },
    'Unhandled' : function () {
        var message = 'Sorry, I could not understand. Please say, play the audio, to begin the audio.';
        this.response.speak(message).listen(message);
        this.emit(':responseReady');
    }
}),
playModeIntentHandlers : Alexa.CreateStateHandler(constants.states.PLAY_MODE, {
    /*
     *  All Intent Handlers for state : PLAY_MODE
     */
    'LaunchRequest' : function () {
        /*
         *  Session resumed in PLAY_MODE STATE.
         *  If playback had finished during last session :
         *      Give welcome message.
         *      Change state to START_STATE to restrict user inputs.
         *  Else :
         *      Ask user if he/she wants to resume from last position.
         *      Change state to RESUME_DECISION_MODE
         */
        var message;
        var reprompt;
        if (this.attributes['playbackFinished']) {
            this.handler.state = constants.states.START_MODE;
            message = 'Welcome to the AWS Podcast. You can say, play the audio to begin the podcast.';
            reprompt = 'You can say, play the audio, to begin.';
        } else {
            this.handler.state = constants.states.RESUME_DECISION_MODE;
            message = 'You were listening to ' + audioData[this.attributes['playOrder'][this.attributes['index']]].title +
                ' Would you like to resume?';
            reprompt = 'You can say yes to resume or no to play from the top.';
        }

        this.response.speak(message).listen(reprompt);
        this.emit(':responseReady');
    },
    'PlayAudio' : function () { controller.play.call(this) },
    'AMAZON.NextIntent' : function () { controller.playNext.call(this) },
    'AMAZON.PreviousIntent' : function () { controller.playPrevious.call(this) },
    'AMAZON.PauseIntent' : function () { controller.stop.call(this) },
    'AMAZON.StopIntent' : function () { controller.stop.call(this) },
    'AMAZON.CancelIntent' : function () { controller.stop.call(this) },
    'AMAZON.ResumeIntent' : function () { controller.play.call(this) },
    'AMAZON.LoopOnIntent' : function () { controller.loopOn.call(this) },
    'AMAZON.LoopOffIntent' : function () { controller.loopOff.call(this) },
    'AMAZON.ShuffleOnIntent' : function () { controller.shuffleOn.call(this) },
    'AMAZON.ShuffleOffIntent' : function () { controller.shuffleOff.call(this) },
    'AMAZON.StartOverIntent' : function () { controller.startOver.call(this) },
    'AMAZON.HelpIntent' : function () {
        // This will called while audio is playing and a user says "ask <invocation_name> for help"
        var message = 'You are listening to the AWS Podcast. You can say, Next or Previous to navigate through the playlist. ' +
            'At any time, you can say Pause to pause the audio and Resume to resume.';
        this.response.speak(message).listen(message);
        this.emit(':responseReady');
    },
    'SessionEndedRequest' : function () {
        // No session ended logic
    },
    'Unhandled' : function () {
        var message = 'Sorry, I could not understand. You can say, Next or Previous to navigate through the playlist.';
        this.response.speak(message).listen(message);
        this.emit(':responseReady');
    }
}),
remoteControllerHandlers : Alexa.CreateStateHandler(constants.states.PLAY_MODE, {
    /*
     *  All Requests are received using a Remote Control. Calling corresponding handlers for each of them.
     */
    'PlayCommandIssued' : function () { controller.play.call(this) },
    'PauseCommandIssued' : function () { controller.stop.call(this) },
    'NextCommandIssued' : function () { controller.playNext.call(this) },
    'PreviousCommandIssued' : function () { controller.playPrevious.call(this) }
}),
resumeDecisionModeIntentHandlers : Alexa.CreateStateHandler(constants.states.RESUME_DECISION_MODE, {
    /*
     *  All Intent Handlers for state : RESUME_DECISION_MODE
     */
    'LaunchRequest' : function () {
        var message = 'You were listening to ' + audioData[this.attributes['playOrder'][this.attributes['index']]].title +
            ' Would you like to resume?';
        var reprompt = 'You can say yes to resume or no to play from the top.';
        this.response.speak(message).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.YesIntent' : function () { controller.play.call(this) },
    'AMAZON.NoIntent' : function () { controller.reset.call(this) },
    'AMAZON.HelpIntent' : function () {
        var message = 'You were listening to ' + audioData[this.attributes['index']].title +
            ' Would you like to resume?';
        var reprompt = 'You can say yes to resume or no to play from the top.';
        this.response.speak(message).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent' : function () {
        var message = 'Good bye.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent' : function () {
        var message = 'Good bye.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    'SessionEndedRequest' : function () {
        // No session ended logic
    },
    'Unhandled' : function () {
        var message = 'Sorry, this is not a valid command. Please say help to hear what you can say.';
        this.response.speak(message).listen(message);
        this.emit(':responseReady');
      }
   })
};

 module.exports = stateHandlers;

 var controller = function () {
     return {
         play: function () {
         /*
         *  Using the function to begin playing audio when:
         *      Play Audio intent invoked.
         *      Resuming audio when stopped/paused.
         *      Next/Previous commands issued.
         */
        this.handler.state = constants.states.PLAY_MODE;

        if (this.attributes['playbackFinished']) {
            // Reset to top of the playlist when reached end.
            this.attributes['index'] = 0;
            this.attributes['offsetInMilliseconds'] = 0;
            this.attributes['playbackIndexChanged'] = true;
            this.attributes['playbackFinished'] = false;
        }

        var token = String(this.attributes['playOrder'][this.attributes['index']]);
        var playBehavior = 'REPLACE_ALL';
        var podcast = audioData[this.attributes['playOrder'][this.attributes['index']]];
        var offsetInMilliseconds = this.attributes['offsetInMilliseconds'];
        // Since play behavior is REPLACE_ALL, enqueuedToken attribute need to be set to null.
        this.attributes['enqueuedToken'] = null;

        if (canThrowCard.call(this)) {
            var cardTitle = 'Playing ' + podcast.title;
            var cardContent = 'Playing ' + podcast.title;
            this.response.cardRenderer(cardTitle, cardContent, null);
        }

        this.response.audioPlayerPlay(playBehavior, podcast.url, token, null, offsetInMilliseconds);
        this.emit(':responseReady');
    },
    stop: function () {
        /*
         *  Issuing AudioPlayer.Stop directive to stop the audio.
         *  Attributes already stored when AudioPlayer.Stopped request received.
         */
        this.response.audioPlayerStop();
        this.emit(':responseReady');
    },
    playNext: function () {
        /*
         *  Called when AMAZON.NextIntent or PlaybackController.NextCommandIssued is invoked.
         *  Index is computed using token stored when AudioPlayer.PlaybackStopped command is received.
         *  If reached at the end of the playlist, choose behavior based on "loop" flag.
         */
        var index = this.attributes['index'];
        index += 1;
        // Check for last audio file.
        if (index === audioData.length) {
            if (this.attributes['loop']) {
                index = 0;
            } else {
                // Reached at the end. Thus reset state to start mode and stop playing.
                this.handler.state = constants.states.START_MODE;

                var message = 'You have reached at the end of the playlist.';
                this.response.speak(message).audioPlayerStop();
                return this.emit(':responseReady');
            }
        }
        // Set values to attributes.
        this.attributes['index'] = index;
        this.attributes['offsetInMilliseconds'] = 0;
        this.attributes['playbackIndexChanged'] = true;

        controller.play.call(this);
    },
    playPrevious: function () {
        /*
         *  Called when AMAZON.PreviousIntent or PlaybackController.PreviousCommandIssued is invoked.
         *  Index is computed using token stored when AudioPlayer.PlaybackStopped command is received.
         *  If reached at the end of the playlist, choose behavior based on "loop" flag.
         */
        var index = this.attributes['index'];
        index -= 1;
        // Check for last audio file.
        if (index === -1) {
            if (this.attributes['loop']) {
                index = audioData.length - 1;
            } else {
                // Reached at the end. Thus reset state to start mode and stop playing.
                this.handler.state = constants.states.START_MODE;

                var message = 'You have reached at the start of the playlist.';
                this.response.speak(message).audioPlayerStop();
                return this.emit(':responseReady');
            }
        }
        // Set values to attributes.
        this.attributes['index'] = index;
        this.attributes['offsetInMilliseconds'] = 0;
        this.attributes['playbackIndexChanged'] = true;

        controller.play.call(this);
    },
    loopOn: function () {
        // Turn on loop play.
        this.attributes['loop'] = true;
        var message = 'Loop turned on.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    loopOff: function () {
        // Turn off looping
        this.attributes['loop'] = false;
        var message = 'Loop turned off.';
        this.response.speak(message);
        this.emit(':responseReady');
    },
    shuffleOn: function () {
        // Turn on shuffle play.
        this.attributes['shuffle'] = true;
        shuffleOrder((newOrder) => {
            // Play order have been shuffled. Re-initializing indices and playing first song in shuffled order.
            this.attributes['playOrder'] = newOrder;
            this.attributes['index'] = 0;
            this.attributes['offsetInMilliseconds'] = 0;
            this.attributes['playbackIndexChanged'] = true;
            controller.play.call(this);
        });
    },
    shuffleOff: function () {
        // Turn off shuffle play. 
        if (this.attributes['shuffle']) {
            this.attributes['shuffle'] = false;
            // Although changing index, no change in audio file being played as the change is to account for reordering playOrder
            this.attributes['index'] = this.attributes['playOrder'][this.attributes['index']];
            this.attributes['playOrder'] = Array.apply(null, {length: audioData.length}).map(Number.call, Number);
        }
        controller.play.call(this);
    },
    startOver: function () {
        // Start over the current audio file.
        this.attributes['offsetInMilliseconds'] = 0;
        controller.play.call(this);
    },
    reset: function () {
        // Reset to top of the playlist.
        this.attributes['index'] = 0;
        this.attributes['offsetInMilliseconds'] = 0;
        this.attributes['playbackIndexChanged'] = true;
        controller.play.call(this);
         }
     }
 }();

 function canThrowCard() {
/*
 * To determine when can a card should be inserted in the response.
 * In response to a PlaybackController Request (remote control events) we cannot issue a card,
 * Thus adding restriction of request type being "IntentRequest".
 */
if (this.event.request.type === 'IntentRequest' && this.attributes['playbackIndexChanged']) {
    this.attributes['playbackIndexChanged'] = false;
    return true;
   } else {
       return false;
   }
 }

 function shuffleOrder(callback) {
// Algorithm : Fisher-Yates shuffle
var array = Array.apply(null, {length: audioData.length}).map(Number.call, Number);
var currentIndex = array.length;
var temp, randomIndex;

while (currentIndex >= 1) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    temp = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temp;
  }
  callback(array);
 }

2 个答案:

答案 0 :(得分:1)

您需要为自己的技能创建自定义意图和自定义插槽。

转到您的互动模式

添加此

{
  "intent": "PodcastIntent",
  "slots": [
    {
      "name": "Podcast",
      "type": "AMAZON.NUMBER"
    }
  ]
}

在你的样本中,话语添加

PodcastIntent play episode {Podcast}

这将让用户能够说play episode 140等...

然后在var stateHandlers = { startModeIntentHandlersPodcastIntent

创建新功能

这部分取决于你,我写的代码不会工作,但应该给你一些想法,也许像

'PodcastIntent' : function () { var podname = this.handlerContext.event.request.intent.slots.Podcast.value;

//this should get the value from alexa if user say play episode 140 , podname sould be 140

//then in your audiodata dictionary you need to find episode 140
//again this part is your work

//when you find the url for episode 140 

//you can set your state to _PLAY_MODE
//then pass the url to audio player
response().audioPlayerPlay('REPLACE_ALL', podcast.audioURL, token, previousToken, 0);

检查https://github.com/alexa是否有AMAZON.NUMBER意图......

答案 1 :(得分:0)

当我修改“playModeIntentHandlers”中的意图时,我能够设置特定的索引。在这个例子中,他们只是在“startModeIntentHandlers”中设置this.attributes ['index'] - 在我的例子中从未被调用过。

每个处理程序都有几个意图,但我只是以1(PlaySongIntent)为例。

var stateHandlers = {
startModeIntentHandlers : Alexa.CreateStateHandler(constants.states.START_MODE, {
'PlaySongIntent' : function () {
        if (!this.attributes['playOrder']) {
            // Initialize Attributes if undefined.
            this.attributes['playOrder'] = Array.apply(null, {length: audioData.length}).map(Number.call, Number);
            this.attributes['index'] = 1; //CHANGING THIS NUMBER NEVER WORKED FOR ME. 
            this.attributes['offsetInMilliseconds'] = 0;
            this.attributes['loop'] = false;
            this.attributes['shuffle'] = false;
            this.attributes['playbackIndexChanged'] = true;
            //  Change state to START_MODE
            this.handler.state = constants.states.START_MODE;
        }
        controller.play.call(this);
    },...}),
playModeIntentHandlers : Alexa.CreateStateHandler(constants.states.PLAY_MODE, {
'PlaySongIntent' : function () {
        this.attributes['index'] = 1; //HERE IS WHERE THE INDEX STICKS.
        this.attributes['offsetInMilliseconds'] = 0;
        this.attributes['playbackIndexChanged'] = true;
        controller.play.call(this);
    },...})
}