修改Alexaaudio播放器示例项目以播放特定音轨

我已经从https://github.com/alexa/skill-sample-nodejs-audio-player上看到了示例代码,并通过我的Amazon Echo获得了示例播客播放器。

如何修改这段代码,以告诉Alexa“询问MySkill播放$ trackname”。 而不是从顶部玩?

我对创build技能非常陌生,但是我已经阅读了所有的文档,并且明白这涉及audio指令。 但是,我无法弄清楚究竟在哪里。

在示例代码中,audioAssets.js包含标题和url的列表。 例如,如果我想说“播放第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); } 

您需要为您的技能创build一个自定义意图和自定义插槽。

去你的交互模式

添加这个

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

在您的示例话语中添加

 PodcastIntent play episode {Podcast} 

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

然后在你的var stateHandlers = { startModeIntentHandlersPodcastIntent创build一个新的函数

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

 '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意&#x56FE;…

当我修改“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); },...}) }