Related
I am working with azure blob storage, i have done that with PHP language, now i want to upload files on azure blob storage with jquery, so i used one plugin for that, when i try to upload file on that it is giving me error in console
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at (Reason: CORS header
‘Access-Control-Allow-Origin’ missing).
I read about the CORS configuration, and i enable that configuration in Azure Blob Storage, here is my screenshot for that
Here is my jquery code
$(function () {
var ju = {
sasUrl: $('#txtSAS'),
list: $('#tbFileList tbody'),
btnUpload: $('#btnUpload'),
btnClear: $('#btnClear'),
btnAddFile: $('#btnAddFile'),
sasSearchName: 'sas',
btnLink: $('#btnLink'),
linkPopover: $('#linkPopover'),
init: function () {
this.root = location.href;
if (this.root.indexOf('?') > 0) {
this.root = this.root.substr(0, this.root.indexOf('?'));
this.root = this.root.replace(/#/g, '');
}
this.btnClear.hide();
var sas = this.queryString(this.sasSearchName);
if (sas) {
this.sasUrl.val(sas);
}
this.list.blobuploader(
{
url: ju.sasUrl.val(),
beforeSend: function (blob) {
},
progress: function (blob) {
ju.progress(blob.element.closest('tr'), blob.loaded, blob.size);
},
success: function (blob, data, status) {
var st = blob.speed(true);
var msg = 'total time: ' + ((st.end - st.start) / 1000).toFixed(2) + 'S<br/>'
+ 'max speed: ' + st.max + '<br/>'
+ 'min speed: ' + st.min + '<br/>'
+ 'average speed: ' + st.average;
ju.status(blob.element, msg);
var download = '<a target="_blank" role="button" class="btn btn-link" href="'
+ blob.blobUrl
+ '" >' + blob.name + '</a>';
ju.log(blob.element.closest('tr').find('td:first'), download);
},
error: function (blob, block, xhr, desc, err) {
var msg = $('<span></span>');
msg.append('upload ' + blob.name + ' error.');
var btn = $('<button type="button" id="btnUpload" class="btn btn-sm btn-primary pull-right" role="button">Retry</button>');
btn.click(function () {
ju.retry($(this).closest('tr'));
});
msg.append(btn)
ju.status(blob.element, msg, 'danger');
}
});
this.btnClear.click(function () {
ju.clear();
});
this.btnAddFile.find('input').on('change', function () {
ju.add();
});
this.btnUpload.click(function () {
ju.upload();
});
this.btnLink.popover({
html: true,
content: this.linkPopover,
container: 'body'
});
this.btnLink.on('shown.bs.popover', function () {
var panel = $('#linkPopover');
panel.find('#txtShareUrl').val(ju.getLongUrl());
panel.find('#ckShortUrl').click(function () {
if ($(this).is(':checked')) {
ju.generateShortUrl();
} else {
panel.find('#txtShareUrl').val(ju.getLongUrl());
}
})
panel.find('.close').click(function () {
ju.btnLink.popover('toggle');
});
panel.find('#ckShortUrl').attr('checked', false);
panel.find('.loading').hide();
});
this.sasUrl.on('change', function () {
ju.linkPopover.find('#ckShortUrl').attr('ckecked', false);
ju.linkPopover.find('.loading').hide();
});
var code = $('.prettyprint');
code.text(code.text().replace('site-domain', location.origin));
},
progress: function (tr, loaded, total) {
var percent = (loaded / total * 100).toFixed(2);
var span = tr.find('td:last .percent');
if (span.length == 0) {
span = $('<span class="percent"/>').appendTo(tr.find('td:last').empty());
}
span.text(percent + '%');
},
log: function (td, message, type) {
var div = td.empty();
if (type) {
div = $('<div class="alert alert-' + type + '"/>').appendTo(td);
}
if (message instanceof jQuery) {
div.append(message);
} else {
div.html(message);
}
},
information: function (element, info, type) {
var td = element.closest('tr').find('td:eq(1)');
if (info) {
ju.log(td, info, type);
} else {
return td.html();
}
},
status: function (element, message, type) {
var td = element.closest('tr').find('td:last');
if (message) {
ju.log(td, message, type);
} else {
return td.html();
}
},
add: function () {
var tr = $('<tr/>'), td = $('<td/>');
var file = this.btnAddFile.find('input');
this.btnAddFile.append(file.clone(true));
var f = file.get(0).files[0];
td.append(file)
.append(f.name)
.appendTo(tr);
td = $('<td/>')
.append(f.type, f.type ? '<br/>' : '', (f.size / 1000).toFixed(2) + 'KB')
.appendTo(tr);
$('<td><span class="percent"></span></td>').appendTo(tr);
tr.appendTo(this.list);
this.btnClear.show();
},
setProperties: function () {
if (!this.sasUrl.val()) {
alert('Please typedin the Container SAS');
return;
}
var blockSize = parseInt($('#txtBlockSize').val());
var maxThread = parseInt($('#txtMaxThread').val());
if (isNaN(blockSize) || isNaN(maxThread)) {
alert("Block Size and Max Thread can only be number.");
return;
}
if (blockSize > 4096) {
alert('The block size should be less than 4096kb');
return;
}
if (blockSize < 1) {
alert('The block size should be greater than 1kb');
return;
}
if (maxThread < 0) {
maxThread = 0;
}
this.list.blobuploader('option', { maxThread: maxThread, blockSizeKB: blockSize, url: this.sasUrl.val() });
return true;
},
upload: function () {
if (this.setProperties()) {
this.list.blobuploader('upload');
}
},
retry: function (tr) {
if (this.setProperties()) {
if (tr) {
var element = tr.find('input[type="file"]');
var blob = this.list.blobuploader('blob', element);
this.list.blobuploader('retry', blob);
} else {
this.list.blobuploader('retry');
}
}
},
clear: function () {
this.list.empty();
this.btnClear.hide();
},
queryString: function (name, value) {
if (!value) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results == null ? "" : atob(decodeURIComponent(results[1].replace(/\+/g, " ")));
} else {
return name + '=' + encodeURIComponent(btoa(value));
}
},
getLongUrl: function () {
return this.root + '?' + this.queryString('sas', this.sasUrl.val());
},
generateShortUrl: function () {
var request = gapi.client.urlshortener.url.insert({
'resource': {
'longUrl': this.getLongUrl()
}
});
request.execute(function (response) {
if (response.id != null) {
ju.linkPopover.find('.loading').hide();
ju.linkPopover.find('#txtShareUrl').val(response.id);
}
else {
ju.linkPopover.find('.loading').text('error.');
}
});
}
}
ju.init();
prettyPrint();
})
function gapiload() {
gapi.client.setApiKey('AIzaSyDzeVB4WDi6azVvIu6uc8hIhWxf99dB6c8');
gapi.client.load('urlshortener', 'v1', function () { });
}
In the input we need to add "Input Your Container SAS Here" i am adding there
https://*****.blob.core.windows.net/?sv=2017-04-17&ss=bfqt&srt=sco&sp=rwdlacup&se=2017-09-10T01:51:20Z&st=2017-09-09T17:51:20Z&spr=https&sig=****** this SAS url, it will get this SAS url and after then we need to select file and upload it.
Can anyone please tell me what is exact issue ?
Thanks
I also download and test the library, it worked fine with following setting on my blob storage service. The MaxAgeInSeconds setting will cache the preflight OPTIONS request. I suggest you reset it to 0 and run your code again(Please use different browsers to test it).
In addition, there are multi CORS setting under Azure Storage panel. Please mark sure that you were setting the right one for Azure Blob Storage.
I've been trying to make an alexa skill that involves audio. I found a great guide here.
Here is their example code:
var stateByUser = {};
var podcastURL = "https://feeds.soundcloud.com/stream/309340878-user-652822799-episode-010-building-an-alexa-skill-with-flask-ask-with-john-wheeler.mp3";
// Entry-point for the Lambda
exports.handler = function(event, context) {
var player = new SimplePlayer(event, context);
player.handle();
};
// The SimplePlayer has helpful routines for interacting with Alexa, within minimal overhead
var SimplePlayer = function (event, context) {
this.event = event;
this.context = context;
};
// Handles an incoming Alexa request
SimplePlayer.prototype.handle = function () {
var requestType = this.event.request.type;
var userId = this.event.context ? this.event.context.System.user.userId : this.event.session.user.userId;
var response = null;
// On launch, we tell the user what they can do (Play audio :-))
if (requestType === "LaunchRequest") {
this.say("Welcome to the Simple Audio Player. Say Play to play some audio!", "You can say Play");
// Handle Intents here - Play, Pause and Resume is all for now
} else if (requestType === "IntentRequest") {
var intent = this.event.request.intent;
if (intent.name === "Play") {
this.play(podcastURL, 0);
} else if (intent.name === "AMAZON.PauseIntent") {
// When we receive a Pause Intent, we need to issue a stop directive
// Otherwise, it will resume playing - essentially, we are confirming the user's action
this.stop();
} else if (intent.name === "AMAZON.ResumeIntent") {
var lastPlayed = this.load(userId);
var offsetInMilliseconds = 0;
if (lastPlayed !== null) {
offsetInMilliseconds = lastPlayed.request.offsetInMilliseconds;
}
this.play(podcastURL, offsetInMilliseconds);
}
} else if (requestType === "AudioPlayer.PlaybackStopped") {
// We save off the PlaybackStopped Intent, so we know what was last playing
this.save(userId, this.event);
}
};
/**
* Creates a proper Alexa response using Text-To-Speech
* #param message
* #param repromptMessage
*/
SimplePlayer.prototype.say = function (message, repromptMessage) {
var response = {
version: "1.0",
response: {
shouldEndSession: false,
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
},
reprompt: {
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
}
}
}
}
this.context.succeed(response);
};
/**
* Plays a particular track, from specific offset
* #param audioURL The URL to play
* #param offsetInMilliseconds The point from which to play - we set this to something other than zero when resuming
*/
SimplePlayer.prototype.play = function (audioURL, offsetInMilliseconds) {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Play",
playBehavior: "REPLACE_ALL", // Setting to REPLACE_ALL means that this track will start playing immediately
audioItem: {
stream: {
url: audioURL,
token: "0", // Unique token for the track - needed when queueing multiple tracks
expectedPreviousToken: null, // The expected previous token - when using queues, ensures safety
offsetInMilliseconds: offsetInMilliseconds
}
}
}
]
}
}
this.context.succeed(response);
};
// Stops the playback of Audio
SimplePlayer.prototype.stop = function () {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Stop"
}
]
}
}
this.context.succeed(response);
};
// Saves information into our super simple, not-production-grade cache
SimplePlayer.prototype.save = function (userId, state) {
console.log("Save: " + userId);
stateByUser[userId] = state;
};
// Load information from our super simple, not-production-grade cache
SimplePlayer.prototype.load = function (userId) {
console.log("Load: " + userId);
var state = null;
if (userId in stateByUser) {
state = stateByUser[userId];
console.log("Loaded " + userId + " State: " + state);
}
return state;
};
I am trying to refactor this code so that it follows a similar format to the trivia skills example that amazon provides. However, when I run my refactored code I get an error saying
TypeError: Cannot set property 'say' of undefined
at Object.<anonymous> (/Users/Rob/Desktop/super-simple-audio-player/index.js:47:28)
Here is my attempt at refactoring
"use strict";
var stateByUser = {};
var podcastURL = "https://p.scdn.co/mp3-preview/2385471a5d35709ad90e368dacabe4082af4541a?cid=null";
var Alexa = require("alexa-sdk");
// Entry-point for the Lambda
exports.handler = function(event, context) {
var alexa = Alexa.handler(event, context);
alexa.registerHandlers(SimplePlayer);
alexa.execute();
};
// The SimplePlayer has helpful routines for interacting with Alexa, within minimal overhead
var SimplePlayer = {
"LaunchRequest": function () {
this.emit(":tell","Welcome to the Simple Audio Player. Say play to begin.");
},
"Play": function() {
this.play(podcastURL, 0);
},
"AMAZON.PauseIntent": function() {
this.stop();
},
"AMAZON.ResumeIntent": function () {
var lastPlayed = this.load(userId);
var offsetInMilliseconds = 0;
if (lastPlayed !== null) {
offsetInMilliseconds = lastPlayed.request.offsetInMilliseconds;
}
this.play(podcastURL, offsetInMilliseconds);
},
"AudioPlayer.PlaybackStopped": function () {
this.save(userId, this.event);
}
};
// Handles an incoming Alexa request
SimplePlayer.prototype.say = function (message, repromptMessage) {
var response = {
version: "1.0",
response: {
shouldEndSession: false,
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
},
reprompt: {
outputSpeech: {
type: "SSML",
ssml: "<speak> " + message + " </speak>"
}
}
}
}
this.context.succeed(response);
};
/**
* Plays a particular track, from specific offset
* #param audioURL The URL to play
* #param offsetInMilliseconds The point from which to play - we set this to something other than zero when resuming
*/
SimplePlayer.prototype.play = function (audioURL, offsetInMilliseconds) {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Play",
playBehavior: "REPLACE_ALL", // Setting to REPLACE_ALL means that this track will start playing immediately
audioItem: {
stream: {
url: audioURL,
token: "0", // Unique token for the track - needed when queueing multiple tracks
expectedPreviousToken: null, // The expected previous token - when using queues, ensures safety
offsetInMilliseconds: offsetInMilliseconds
}
}
}
]
}
}
this.context.succeed(response);
};
// Stops the playback of Audio
SimplePlayer.prototype.stop = function () {
var response = {
version: "1.0",
response: {
shouldEndSession: true,
directives: [
{
type: "AudioPlayer.Stop"
}
]
}
}
this.context.succeed(response);
};
// Saves information into our super simple, not-production-grade cache
SimplePlayer.prototype.save = function (userId, state) {
console.log("Save: " + userId);
stateByUser[userId] = state;
};
// Load information from our super simple, not-production-grade cache
SimplePlayer.prototype.load = function (userId) {
console.log("Load: " + userId);
var state = null;
if (userId in stateByUser) {
state = stateByUser[userId];
console.log("Loaded " + userId + " State: " + state);
}
return state;
};
I've added alexa-sdk and changed the exports.handler and the simplePlayer.prototype.handler(). Any thoughts as to why it is not working?
Thanks in advance
I actually created the project you reference. Glad you are finding it useful.
In re-factoring the project, you changed it from prototype-style JS object to an object literal. Both are viable approaches, but the object literal becomes a problem when holding the state for a particular request (the event and context fields in particular).
It also means that the prototype methods defined in the project are not available from the object literal definition. You need to instantiate SimplePlayer (by calling new SimplePlayer(event, context)) before you will get those.
If you want to understand the trade-off between these approaches better, you can read here:
Object literal vs constructor+prototype
Here is an example of working with the Alexa SDK consistent with my project. It defines the "LaunchRequest" function as a prototype function rather than simply a property:
SimplePlayer.prototype.LaunchRequest = function () {
this.emit(":tell", "Welcome to the Simple Audio Player. Say play to begin.");
};
You also need to make sure to instantiate the SimplePlayer (not just reference it). When registering it, it should look like this:
alexa.registerHandlers(new SimplePlayer(event, context));
Hope that makes sense, and good luck with it! Let me know how it goes (I can always be reached at https://gitter.im/bespoken/bst)
I'm new to Alexa, and have followed the airportinfo tutorial and I have copied the code from github https://github.com/bignerdranch/alexa-airportinfo and when i test it using npm and input an airport code e.g. SFO, Theres no "outputSpeech:" and i tried making a similar skill with the same issue, I'm not sure what I'm doing wrong. I have both index.js and FAADataInfo.js Thanks in advance for your help.
This is the index.js file
'use strict';
module.change_code = 1;
var _ = require('lodash');
var Alexa = require('alexa-app');
var skill = new Alexa.app('airportinfo');
var FAADataHelper = require('./faa_data_helper');
skill.launch(function(req, res) {
var prompt = 'For delay information, tell me an Airport code.';
res.say(prompt).reprompt(prompt).shouldEndSession(false);
});
skill.intent('airportInfoIntent', {
'slots': {
'AIRPORTCODE': 'FAACODES'
},
'utterances': [
'{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}'
]
},
function(req, res) {
var airportCode = req.slot('AIRPORTCODE');
var reprompt = 'Tell me an airport code to get delay information.';
if (_.isEmpty(airportCode)) {
var prompt = 'I didn\'t hear an airport code. Tell me an airport code.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
var faaHelper = new FAADataHelper();
console.log(airportCode);
faaHelper.getAirportStatus(airportCode).then(function(airportStatus) {
console.log(airportStatus);
res.say(faaHelper.formatAirportStatus(airportStatus)).send();
}).catch(function(err) {
console.log(err.statusCode);
var prompt = 'I didn\'t have data for an airport code of ' +
airportCode;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
});
return false;
}
}
);
module.exports = skill;
and heres FAADataInfo.js
'use strict';
var _ = require('lodash');
var requestPromise = require('request-promise');
var ENDPOINT = 'http://services.faa.gov/airport/status/';
function FAADataHelper() {
}
FAADataHelper.prototype.getAirportStatus = function(airportCode) {
var options = {
method: 'GET',
uri: ENDPOINT + airportCode,
json: true
};
return requestPromise(options);
};
FAADataHelper.prototype.formatAirportStatus = function(aiportStatusObject) {
if (aiportStatusObject.delay === 'true') {
var template = _.template('There is currently a delay for ${airport}. ' +
'The average delay time is ${delay_time}.');
return template({
airport: aiportStatusObject.name,
delay_time: aiportStatusObject.status.avgDelay
});
} else {
//no delay
var template =_.template('There is currently no delay at ${airport}.');
return template({
airport: aiportStatusObject.name
});
}
};
module.exports = FAADataHelper;
This is the response that I get
{
"version": "1.0",
"response": {
"directives": [],
"shouldEndSession": true
},
"sessionAttributes": {},
"dummy": "text"
}
The alexa-app version that the tutorial is using is out of date. When using the latest alexa-app npm version (4.0.0), the return value for the .intent() function should be a Promise and not a boolean if you are running asynchronous functions.
In your index.js, add:
return faaHelper.getAirportStatus(....) {}.catch(){}
and remove the return false; after the catch.
Here's the full skill.intent() code
skill.intent('airportInfoIntent', {
'slots': {
'AIRPORTCODE': 'FAACODES'
},
'utterances': [
'{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}'
]
},
function(req, res) {
var airportCode = req.slot('AIRPORTCODE');
var reprompt = 'Tell me an airport code to get delay information.';
if (_.isEmpty(airportCode)) {
var prompt = 'I didn\'t hear an airport code. Tell me an airport code.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
var faaHelper = new FAADataHelper();
console.log(airportCode);
return faaHelper.getAirportStatus(airportCode).then(function(airportStatus) {
console.log(airportStatus);
res.say(faaHelper.formatAirportStatus(airportStatus)).send();
}).catch(function(err) {
console.log(err.statusCode);
var prompt = 'I didn\'t have data for an airport code of ' +
airportCode;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
});
//return false;
}
}
);
I've followed the sample code from https://github.com/alexa/skill-sample-nodejs-audio-player and gotten the example podcast player to play via my Amazon Echo.
How would one modify this code in order to tell Alexa to "Ask MySkill to Play $trackname". Instead of just playing from the top?
I'm very new to creating skills but I've read all the documentation and understand that this involves Audio Directives. However, I can't figure out what goes where.
In the sample code, the audioAssets.js contains a list of titles and urls. So for example, if I wanted to say "Play Episode 138" (one of the titles) - which files would I need to modify in order to do this?
'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;
I'm assuming the code would go into stateHandlers.js but honestly I'm not sure.
'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);
}
You need to create a custom intent and custom slot to your skill.
Go to your interaction model
add this
{
"intent": "PodcastIntent",
"slots": [
{
"name": "Podcast",
"type": "AMAZON.NUMBER"
}
]
}
In your sample Utterances add
PodcastIntent play episode {Podcast}
This will let user to able to say play episode 140 etc...
Then in your var stateHandlers = {
startModeIntentHandlers create a new function for PodcastIntent
That part is up to you , the code I write wont work but should give you some idea, maybe something like
'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);
check the https://github.com/alexa for the AMAZON.NUMBER intents...
I was able to set specific indexes when I modified the intents within "playModeIntentHandlers." In the example, they were only setting this.attributes['index'] within "startModeIntentHandlers" - which in my case were never being called.
There are several intents that go in each handler, but I'm only showing 1 (PlaySongIntent) as an example.
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);
},...})
}
I have to pin secondary tile in my windows phone 8.1 application.
I followed the msdn tutorial : http://code.msdn.microsoft.com/windowsapps/secondary-tiles-sample-edf2a178/
It does work with internal image (ms-appx://.. ) but not with web url (http://)
working sample:
var logo = new Windows.Foundation.Uri("ms-appx:///Images/square30x30Tile-sdk.png");
var currentTime = new Date();
var TileActivationArguments = data.ad_id + " WasPinnedAt=" + currentTime;
var tile = new Windows.UI.StartScreen.SecondaryTile(data.ad_id,
data.subject,
TileActivationArguments,
logo,
Windows.UI.StartScreen.TileSize.square150x150);
tile.visualElements.foregroundText = Windows.UI.StartScreen.ForegroundText.light;
tile.visualElements.square30x30Logo = logo;
tile.visualElements.showNameOnSquare150x150Logo = true;
var selectionRect = this.element.getBoundingClientRect();
// Now let's try to pin the tile.
// We'll make the same fundamental call as we did in pinByElement, but this time we'll return a promise.
return new WinJS.Promise(function (complete, error, progress) {
tile.requestCreateForSelectionAsync({ x: selectionRect.left, y: selectionRect.top, width: selectionRect.width, height: selectionRect.height }, Windows.UI.Popups.Placement.above).done(function (isCreated) {
if (isCreated) {
complete(true);
} else {
complete(false);
}
});
});
And if I use
var logo = new Windows.Foundation.Uri(data.images[0]);
I got an invalid parameter exception.
You can take a look at the documentation for the SecondaryTile.Logo property. In it you'll see this:
The location of the image. This can be expressed as one of these schemes:
ms-appx:///
ms-appdata:///local/
You can download the image first and then set it using the ms-appdata:///local/ scheme. I'm not sure that changing the logo with something from the Internet is a good idea, though. This should be the app's logo, so it should be in the package.
I found the solution
fileExists: function (fileName) {
var applicationData = Windows.Storage.ApplicationData.current;
var folder = applicationData.localFolder;
return folder.getFileAsync(fileName).then(function (file) {
return file;
}, function (err) {
return null;
});
},
download: function (imgUrl, imgName) {
return WinJS.xhr({ url: imgUrl, responseType: "blob" }).then(function (result) {
var blob = result.response;
var applicationData = Windows.Storage.ApplicationData.current;
var folder = applicationData.localFolder;
return folder.createFileAsync(imgName, Windows.Storage.
CreationCollisionOption.replaceExisting).then(function (file) {
// Open the returned file in order to copy the data
return file.openAsync(Windows.Storage.FileAccessMode.readWrite).
then(function (stream) {
return Windows.Storage.Streams.RandomAccessStream.copyAsync
(blob.msDetachStream(), stream).then(function () {
// Copy the stream from the blob to the File stream
return stream.flushAsync().then(function () {
stream.close();
});
});
});
});
}, function (e) {
//var msg = new Windows.UI.Popups.MessageDialog(e.message);
//msg.showAsync();
});
},
var self = this;
this.download(data.images[0], data.ad_id).then(function () {
self.fileExists(data.ad_id).then(function (file) {
var logo = new Windows.Foundation.Uri("ms-appdata:///Local/" + data.ad_id);
....
I need to download the image, store it and then I can use ms-appdata:///Local