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);
},...})
}
Related
I'm writing a listener service. Data is streaming to me from a specific port. But I cannot read the incoming data and write to consola.
I have a listener service written in GoLang. I tried to extract the listener logic from these codes and adapt it for node.js. But I couldn't.
My service can listen to the port, there is no problem in this regard. Because the place where I get the data produces output because your service is running on its side.
Message returning to me from the place that sent data
These are my proto codes:
syntax = "proto3";
import "rpc_base_msg.proto";
//----------Remote Process Control Models BEGIN----------//
message RPCKeyboardActivityRequest {
RequestModel request_model = 1;
}
message RPCKeyboardActivityResponse {
ResponseModel response_model =1;
}
//----------Remote Process Control Models END-----------//
//---------------Data Tranfer Models BEGIN-------------//
message DTKeyboardActivityModel {
uint64 dt = 1; // Date-Time Stamp
enum Activity {
APPLICATION = 0;
WEB_BROWSER = 1;
}
Activity activity = 2; // Activity: Can be application or a web browser.
string activity_name = 3; //Activity is the name of the application or web page name
string key = 4; // This is the user's keystroke sequence. Changes for every different application or same application in every 60 seconds!!!
bool islive_data=5;
uint64 delete_id=6;
}
message DTKeyboardActivityRequest {
DTKeyboardActivityModel dt_keyboard_activity_model = 1;
}
message DTKeyboardActivityResponse {
bool ret_complete = 1; // Return True/False
}
//---------------Data Tranfer Models END-------------//
//----------------Service BEGIN------------------//
service ActivityService {
rpc SSPCKeyboardActivity (RPCKeyboardActivityRequest) returns (stream RPCKeyboardActivityResponse);
rpc UDTKeyboardActivity (DTKeyboardActivityRequest) returns (DTKeyboardActivityResponse);
rpc BDSDTKeyboardActivity (stream DTKeyboardActivityRequest) returns (stream DTKeyboardActivityResponse);
}
//----------------Service END------------------//
These are the proto import codes:
syntax = "proto3";
message RequestModel {
bool is_process_completed = 1;
}
message ResponseModel {
bool is_monitoring = 1;
}
These are my listen service codes:
const grpc = require('#grpc/grpc-js');
const protoLoader = require('#grpc/proto-loader');
const path = "./protos/test.proto";
const path2= "./protos/activity.proto";
const packageDefination = protoLoader.loadSync(path,{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const packageDef = protoLoader.loadSync(path2,{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const test_proto = grpc.loadPackageDefinition(packageDefination);
const activity_proto = grpc.loadPackageDefinition(packageDef);
function sayHello(call,callback) {
callback(null,{message:'Hello '+ call.request.name});
}
function sayHelloAgain(call,callback) {
callback(null,{message:'Hello Again ' + call.request.name});
}
function SSPCKeyboardActivity(call){
let rpcKeyboardActivityRequest = call.request.RPCKeyboardActivityRequest;
console.log("rpcKeyboardActivityRequest:" + rpcKeyboardActivityRequest);
let monitorStatusChange = true;
let whileControl = true;
console.log("SSPCKeyboardActivity client recv :" + GetRequestModel());
while (whileControl === true){
call.write({IsMonitoring: monitorStatusChange})
monitorStatusChange = !monitorStatusChange;
if (!monitorStatusChange){
setTimeout(function () {},30000)
}else {
setTimeout(function () {},10000)
}
}
}
function UDTKeyboardActivity(call,callback) {
let context = call.metadata.get('context.Context');
console.log("Context" + context);
if (context){
let udtKeyboardActivity = call.request.DTKeyboardActivityRequest;
console.log("udtKeyboardActivity" + udtKeyboardActivity);
let dtKeyboardActivityResponse = call.request.DTKeyboardActivityResponse;
console.log("dtKeyboardActivityResponse" + dtKeyboardActivityResponse);
callback(null , dtKeyboardActivityResponse);
console.log("CallBack" + callback);
}else {
console.log("Contex Error");
}
}
function BDSDTKeyboardActivity(call) {
call.on('data', function () {
let dtKeyboardActivityRequest = call.request.DTKeyboardActivityRequest;
console.log("dtKeyboardActivityRequest" + dtKeyboardActivityRequest);
})
call.on('end',function () {
call.write(DTKeyboardActivityResponse);
console.log("Write DTKeyboardActivityResponse"+DTKeyboardActivityResponse);
})
}
function main(){
const server = new grpc.Server();
server.bindAsync('0.0.0.0:5801',grpc.ServerCredentials.createInsecure(),()=>{
server.start();
console.log("Server listening at :5801");
});
server.addService(test_proto.Greeter.service,{
sayHello: sayHello,
sayAgainHello: sayHelloAgain
});
server.addService(activity_proto.ActivityService.service, {
SSPCKeyboardActivity : SSPCKeyboardActivity,
UDTKeyboardActivity: UDTKeyboardActivity,
BDSDTKeyboardActivity: BDSDTKeyboardActivity,
})
}
main();
"path" and testproto are not a problem for me. I ran it. I am having a problem with activity_proto.
I came across after the service was running:
➜ service node server/server.js
Server listening at :5801
As an example, I also share the functions in the service code block written in go.
package service
import (
"context"
"github.com/******/pb"
"log"
"time"
)
type ActivityServer struct {
}
type ActivityRpcUpdateModel struct {
IsMonitor bool
}
func NewActivityServer() *ActivityServer {
return &ActivityServer{}
}
func (a *ActivityServer) SSPCKeyboardActivity(request *pb.RPCKeyboardActivityRequest, server pb.ActivityService_SSPCKeyboardActivityServer)
error {
monitorStatusChange := true
log.Printf("SSPCKeyboardActivity client recv : %v", request.GetRequestModel())
for {
err := server.Send(&pb.RPCKeyboardActivityResponse{ResponseModel: &pb.ResponseModel{IsMonitoring: monitorStatusChange}})
if err != nil {
log.Printf("SSPCForegroundApp err : %v", err)
}
monitorStatusChange = !monitorStatusChange
if !monitorStatusChange {
time.Sleep(time.Second * 30)
} else {
time.Sleep(time.Second * 10)
}
}
}
func (a *ActivityServer) UDTKeyboardActivity(
ctx context.Context,
request *pb.DTKeyboardActivityRequest,
) (*pb.DTKeyboardActivityResponse, error) {
log.Println(request.GetDtKeyboardActivityModel())
return &pb.DTKeyboardActivityResponse{
RetComplete: true,
}, nil
}
func (a *ActivityServer) BDSDTKeyboardActivity(
server pb.ActivityService_BDSDTKeyboardActivityServer,
) error {
panic("implement me")
}
I cannot catch the incoming data. And I can't see in the consol. What kind of changes should I make to my functions on the node.js side of me?
I've moveMarker methods with setInterval to making my Marker in mapbox-gl is moving along my Routes Line and I've play button to trigger this function . My question is , what should I do if I want to create pause button with clearInterval?
I've tried create function to clearInterval in moveMarker but doesn't work
this is my function to move the marker :
moveMarker () {
const moveCoordinate = []
const loop = setInterval(() => {
if (this.index + 1 === (this.coordinates.length - 1)) {
clearInterval(loop)
}
for (let index = 0; index < moveCoordinate.length; index++) {
moveCoordinate[index].remove()
}
this.map.panTo(this.coordinates[this.index])
const lat = this.coordinates[this.index][0]
const lng = this.coordinates[this.index][1]
const newMarker = new mapboxgl.LngLat(lat, lng)
console.log('new', newMarker)
const markerMapbox = new mapboxgl.Marker()
.setLngLat(newMarker)
.addTo(this.map)
moveCoordinate.push(markerMapbox)
this.index++
}, 1000)
},
and this is stop function :
stop () {
clearInterval(this.moveMarker)
},
First off you should store you interval in data property, to have access to it in the stop method. Then in stop method just call clearInterval with stored interval:
export default {
...
data() {
return {
interval: null
}
},
methods: {
moveMarker () {
const moveCoordinate = []
this.interval = setInterval(() => {
if (this.index + 1 === (this.coordinates.length - 1)) {
clearInterval(this.interval)
}
for (let index = 0; index < moveCoordinate.length; index++) {
moveCoordinate[index].remove()
}
this.map.panTo(this.coordinates[this.index])
const lat = this.coordinates[this.index][0]
const lng = this.coordinates[this.index][1]
const newMarker = new mapboxgl.LngLat(lat, lng)
console.log('new', newMarker)
const markerMapbox = new mapboxgl.Marker()
.setLngLat(newMarker)
.addTo(this.map)
moveCoordinate.push(markerMapbox)
this.index++
}, 1000)
},
stop() {
clearInterval(this.interval)
},
},
...
}
Calling clearInterval on the moveMarker method isn't going to do anything. You need to save the interval id somewhere that stop can access it.
e.g. Inside moveMarker:
this.intervalId = loop
Then:
stop () {
clearInterval(this.intervalId)
}
There's no need to add intervalId to your data as you don't need it to be reactive.
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 am using RecordRTC from recording webrtc meeting. After implementing recording, when I test this application if both client are on the same system then its working fine. When I test this application on different system it isn't working fine and meeting is not recorded.
Here this is my code from stop recording client side.
recordRTC.stopRecording(function (videoURL) {
console.log('recordRTC.stopRecording Function inside');
SelectedFile = recordRTC.getBlob();
$('#uploadForm').append('#attachmentFileId', recordRTC.getBlob());
StartUpload();
});
var FReader;
var Name = "Meeting" + "_" + Date.now() + ".webm";
function StartUpload()
{
FReader = new FileReader();
FReader.onload = function (evnt)
{
socket.emit('Upload', { 'Name': Name, Data: evnt.target.result });
}
socket.emit('Start', { 'Name': Name, 'Size': SelectedFile.size });
}
socket.on('MoreData', function (data)
{
var Place = data['Place'] * 524288; //The Next Blocks Starting Position
var NewFile; //The Variable that will hold the new Block of Data
if (SelectedFile.webkitSlice)
NewFile = SelectedFile.webkitSlice(Place, Place + Math.min(524288, (SelectedFile.size - Place)));
else
NewFile = SelectedFile.slice(Place, Place + Math.min(524288, (SelectedFile.size - Place)));
FReader.readAsBinaryString(NewFile);
});
Server Side Code
I get this from here.
socket.on('Start', function (data) { //data contains the variables that we passed through in the html file
var Name = data['Name'];
Files[Name] = { //Create a new Entry in The Files Variable
FileSize : data['Size'],
Data : "",
Downloaded : 0
}
var Place = 0;
try{
var Stat = fs.statSync('Temp/' + Name);
if(Stat.isFile())
{
Files[Name]['Downloaded'] = Stat.size;
Place = Stat.size / 524288;
}
}
catch(er){} //It's a New File
fs.open("Temp/" + Name, 'a', 0755, function(err, fd){
if(err)
{
console.log(err);
}
else
{
Files[Name]['Handler'] = fd; //We store the file handler so we can write to it later
socket.emit('MoreData', { 'Place' : Place, Percent : 0 });
}
});
});
socket.on('Upload', function (data){
var Name = data['Name'];
Files[Name]['Downloaded'] += data['Data'].length;
Files[Name]['Data'] += data['Data'];
if(Files[Name]['Downloaded'] == Files[Name]['FileSize']) //If File is Fully Uploaded
{
fs.write(Files[Name]['Handler'], Files[Name]['Data'], null, 'Binary', function(err, Writen){
var input = fs.createReadStream("Temp/" + Name);
var output = fs.createWriteStream("Video/" + Name);
//util.pump(readableStream, writableStream, [callback])
//Deprecated: Use readableStream.pipe(writableStream)
input.pipe(output);
input.on("end", function() {
console.log("end");
fs.unlink("Temp/" + Name, function ()
{ //This Deletes The Temporary File
console.log("unlink this file:",Name );
//socket.emit('Done', {'Image' : 'Video/' + Name + '.jpg'});
});
});
});
}
else if(Files[Name]['Data'].length > 10485760){ //If the Data Buffer reaches 10MB
fs.write(Files[Name]['Handler'], Files[Name]['Data'], null, 'Binary', function(err, Writen){
Files[Name]['Data'] = ""; //Reset The Buffer
var Place = Files[Name]['Downloaded'] / 524288;
var Percent = (Files[Name]['Downloaded'] / Files[Name]['FileSize']) * 100;
socket.emit('MoreData', { 'Place' : Place, 'Percent' : Percent});
});
}
else
{
var Place = Files[Name]['Downloaded'] / 524288;
var Percent = (Files[Name]['Downloaded'] / Files[Name]['FileSize']) * 100;
socket.emit('MoreData', { 'Place' : Place, 'Percent' : Percent});
}
});
If both clients are on same machine/system its working fine, but if both clients are on different system then meeting is not recorded.
jsfiddle link: http://jsfiddle.net/T8ee7/
When I call Knockout's subscribe method is there a way I can get both the previous and new value? Right now, I can only call get these values separately.
I want to trigger some code if the old and new value are different.
I suppose I could do the following, but it can get messy...
(http://jsfiddle.net/MV3fN/)
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
this.pageNumber = ko.observable(pageNumber || 1);
this.numberOfPages = ko.observable(1);
this.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
};
var _pagedRequest = new sv.PagedRequest();
var oldValue;
_pagedRequest.pageNumber.subscribe(function (previousValue) {
console.log("old: " + previousValue);
oldValue = previousValue;
}, _pagedRequest, "beforeChange");
_pagedRequest.pageNumber.subscribe(function (newValue) {
console.log("new: " + newValue);
if (oldValue != newValue) {
console.log("value changed!");
}
});
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
I prefer using an observable extender.
http://jsfiddle.net/neonms92/xybGG/
Extender:
ko.extenders.withPrevious = function (target) {
// Define new properties for previous value and whether it's changed
target.previous = ko.observable();
target.changed = ko.computed(function () { return target() !== target.previous(); });
// Subscribe to observable to update previous, before change.
target.subscribe(function (v) {
target.previous(v);
}, null, 'beforeChange');
// Return modified observable
return target;
}
Example Usage:
// Define observable using 'withPrevious' extension
self.hours = ko.observable().extend({ withPrevious: 1 });
// Subscribe to observable like normal
self.hours.subscribe(function () {
if (!self.hours.changed()) return; // Cancel if value hasn't changed
print('Hours changed from ' + self.hours.previous() + ' to ' + self.hours());
});
This seems to work for me
ko.observable.fn.beforeAndAfterSubscribe = function (callback, target) {
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
callback.call(target, _oldValue, newValue);
});
};
See more at: http://ideone.com/NPpNcB#sthash.wJn57567.dpuf
http://jsfiddle.net/MV3fN/3/
var sv = sv || {};
sv.PagedRequest = function (pageNumber, pageSize) {
var self = this;
self.pageNumber = ko.observable(pageNumber || 1);
self.numberOfPages = ko.observable(1);
self.pageSize = ko.observable(pageSize || sv.DefaultPageSize);
self.pageNumber.subscribe(function (previousValue) {
console.log(previousValue);
console.log(self.pageNumber.arguments[0]);
if (previousValue != _pagedRequest.pageNumber.arguments[0]) {
console.log('value changed');
}
else {
//This won't get executed because KO doesn't
//call the function if the value doesn't change
console.log('not changed');
}
}, _pagedRequest, "beforeChange");
};
var _pagedRequest = new sv.PagedRequest();
_pagedRequest.pageNumber(10);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(20);
_pagedRequest.pageNumber(5);
I don't know if you're really supposed to use arguments[0], but it seems to work.
You could also set up your own method to accomplish this in a much cleaner way:
http://jsfiddle.net/PXKgr/2/
...
self.setPageNumber = function(page) {
console.log(self.pageNumber());
console.log(page);
if (self.pageNumber() != page) {
console.log('value changed');
}
else {
console.log('not changed');
}
self.pageNumber(page);
};
...
_pagedRequest.setPageNumber(10);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(20);
_pagedRequest.setPageNumber(5);