-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstateHandlers.js
356 lines (338 loc) · 15.9 KB
/
stateHandlers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
'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'] = false; //do not loop on the list of podcast
this.attributes['shuffle'] = false;
this.attributes['playbackIndexChanged'] = true;
// Change state to START_MODE
this.handler.state = constants.states.START_MODE;
var message = 'Welcome to Focus Game, where your mind will become sharper than a sword and stronger than an elephant. Say go or start to begin';
var reprompt = 'You can say, go or start, 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'] = false; //do not loop on the list of podcast
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 Focus Game. You can say begin or start to begin playing';
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, go or begin to start.';
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 Focus Game. You can say go or start to begin.';
reprompt = 'You can say, go or start, to begin.';
} else {
this.handler.state = constants.states.RESUME_DECISION_MODE;
message = 'You were on ' + audioData[this.attributes['playOrder'][this.attributes['index']]].title +
' Would you like to resume?';
reprompt = 'You can say yes to resume or no to start over';
}
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 playing Focus Game. You can say, Next or Previous to navigate through different levels. ' +
'At any time, you can say Pause to pause 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 each level.';
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 on ' + audioData[this.attributes['playOrder'][this.attributes['index']]].title +
' Would you like to resume?';
var reprompt = 'You can say yes to resume or no to start over.';
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 on ' + audioData[this.attributes['index']].title +
' Would you like to resume?';
var reprompt = 'You can say yes to resume or no to start over.';
this.response.speak(message).listen(reprompt);
this.emit(':responseReady');
},
'AMAZON.StopIntent' : function () {
var message = 'Namaste.';
this.response.speak(message);
this.emit(':responseReady');
},
'AMAZON.CancelIntent' : function () {
var message = 'Namaste.';
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);
}
var message = "This is " + audioData[this.attributes['index']].title;
this.response.speak(message).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 = 'Congratulations young Jedi, you have completed Focus Game. See you tomorrow.';
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 the beginning';
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);
}