Closed jratliff681 closed 7 years ago
I've got this working, and it's really great. The main limitation of course, is that you need to use an "Alexa, ask sonos ..." command before you can use the short, native form - i.e. "Alexa next | previous | shuffle", etc. At some point, perhaps Amazon will let users specify a select skill as their default audio player...
It's a bit of a hack and I haven't cleaned it up much. But the basic idea is this:
You'll need to make edits to AlexaSkills.js
and index.js
. Let me know how it goes for you.
AlexaSkill.js
Response.prototype = (function () {
var buildSpeechletResponse = function (options) {
var alexaResponse = {
outputSpeech: createSpeechObject(options.output),
shouldEndSession: options.shouldEndSession
};
if (options.reprompt) {
alexaResponse.reprompt = {
outputSpeech: createSpeechObject(options.reprompt)
};
}
if (options.cardTitle && options.cardContent) {
alexaResponse.card = {
type: "Simple",
title: options.cardTitle,
content: options.cardContent
};
}
// START INSERT
// Add directive to play a dummy audio file and thereby assume responsibility for native Amazon AudioPlay intents
alexaResponse.directives = [ {
type: "AudioPlayer.Play",
playBehavior: "REPLACE_ALL",
audioItem: {
stream: {
token: "0123456789", // Dummy token
url: "<<<INSERT LINK TO MP3 FILE HERE>>>", // Five seconds of silence
offsetInMilliseconds: 0
}
}
}];
// END INSERT
var returnResult = {
version: '1.0',
response: alexaResponse
};
if (options.session && options.session.attributes) {
returnResult.sessionAttributes = options.session.attributes;
}
return returnResult;
};
return {
tell: function (speechOutput) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
shouldEndSession: true
}));
},
tellWithCard: function (speechOutput, cardTitle, cardContent) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
cardTitle: cardTitle,
cardContent: cardContent,
shouldEndSession: true
}));
},
ask: function (speechOutput, repromptSpeech) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
reprompt: repromptSpeech,
shouldEndSession: false
}));
},
askWithCard: function (speechOutput, repromptSpeech, cardTitle, cardContent) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
reprompt: repromptSpeech,
cardTitle: cardTitle,
cardContent: cardContent,
shouldEndSession: false
}));
}
};
})();
index.js
intent.slots.Room.value
is empty. Make it null
as you see below, and it will succeed without needing to modify loadCurrentRoomAndService
. 'AMAZON.PauseIntent': function (intent, session, response) {
console.log("Amazon PauseIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
options.path = '/' + encodeURIComponent(room) + '/pause';
httpreq(options, function(error) {
genericResponse(error, response);
});
});
},
'AMAZON.StopIntent': function (intent, session, response) {
console.log("Amazon PauseIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
options.path = '/' + encodeURIComponent(room) + '/pause';
httpreq(options, function(error) {
genericResponse(error, response);
});
});
},
'AMAZON.CancelIntent': function (intent, session, response) {
console.log("Amazon PauseIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
options.path = '/' + encodeURIComponent(room) + '/pause';
httpreq(options, function(error) {
genericResponse(error, response);
});
});
},
'AMAZON.ResumeIntent': function (intent, session, response) {
console.log("Amazon ResumeIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
options.path = '/' + encodeURIComponent(room) + '/play';
httpreq(options, function(error) {
genericResponse(error, response);
});
});
},
'AMAZON.NextIntent': function (intent, session, response) {
console.log("NextTrackIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
actOnCoordinator(options, '/next', room, function (error, responseBodyJson) {
genericResponse(error, response);
});
});
},
'AMAZON.PreviousIntent': function (intent, session, response) {
console.log("PreviousTrackIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
actOnCoordinator(options, '/previous', room, function (error, responseBodyJson) {
genericResponse(error, response);
});
});
},
'AMAZON.LoopOnIntent': function (intent, session, response) {
console.log("RepeatIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
toggleHandler(room, 'on', "repeat", response);
});
},
'AMAZON.LoopOffIntent': function (intent, session, response) {
console.log("RepeatIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
toggleHandler(room, 'off', "repeat", response);
});
},
'AMAZON.ShuffleOnIntent': function (intent, session, response) {
console.log("ShuffleIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
toggleHandler(room, 'on', "shuffle", response);
});
},
'AMAZON.ShuffleOffIntent': function (intent, session, response) {
console.log("ShuffleIntent received");
loadCurrentRoomAndService('DefaultEcho', null, function(room, service) {
toggleHandler(room, 'off', "shuffle", response);
});
}
Thank you so much! I modified my index.js with all the intents as soon as I posted this a while ago. I just couldn't figure out what to add to invoke the new Alexa Skill commands to get it to trigger the audio player part. I'll add those lines while I'm at work and test it when I get home this weekend, looking forward to trying it!
My idea I was going to try was to send the AudioPlayer.Stop command even if nothing was playing and see if that triggered the echo to mark the skill as the last audio directing skill. The silence is a good idea too. I usually host my audio files in an Amazon S3 bucket.
It works great, thanks mdcampbell!
I have 5 separate skills with invocation names of each of my speaker room names. I noticed each echo is independent of each other when saving the last used audio player skill. So if I say "Alexa, tell the living room to play" on the living room echo, and "Alexa, tell the kitchen to play" on the kitchen dot, and so on in each room, then everytime I can just come back to any room and say "play/pause/next/previous" and it always controls the correct speaker! (As long as I didn't start a room from a different room).
I still keep a base "Sonos" skill that isn't designated as an audio player skill so that I can control music in another room without changing which skill is currently controlling the audio commands on that echo.
Sounds like a sweet setup. Glad it's working for you!
This sounds like a great upgrade. @mdcampbell, any reason not to submit this as a PR and change the way invocation works for the skill?
So I noticed today the new "Audio Player" option under skill information on developer.amazon.com.
Here's the link to the amazon information about it. https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-audioplayer-interface-reference
It seems it is looking to stream audio to the echo, but could we also use this to just control our Sonos commands? The part of it that looks useful is where it says if your skill was the last skill marked to be playing audio that you could then use any of the built in amazon commands next / previous / shuffle / stop / etc WITHOUT invoking the skill name! This would be nice to have if it's possible. I'm trying to read over it but wanted to share with those of you who may have a little better understanding than me.