diff --git a/src/index.js b/src/index.js index dd5b8e8..13de279 100644 --- a/src/index.js +++ b/src/index.js @@ -12,21 +12,29 @@ var response_messages = require("./util/responses.js"); // Create Alexa skill application var app = new alexa.app("youtube"); -// Set Heroku URL -var heroku = process.env.HEROKU_APP_URL || "https://dmhacker-youtube.herokuapp.com"; +// Process environment variables +const heroku = process.env.HEROKU_APP_URL || "https://dmhacker-youtube.herokuapp.com"; +const interactive_wait = !(process.env.DISABLE_INTERACTIVE_WAIT === "true" || + process.env.DISABLE_INTERACTIVE_WAIT === true || + process.env.DISABLE_INTERACTIVE_WAIT === 1); +const cache_polling_interval = parseInt(process.env.CACHE_POLLING_INTERVAL || "5000", 10); +const ask_interval = parseInt(process.env.ASK_INTERVAL || "45000", 10); -// Variables relating to videos waiting for user input -var buffer_search = {}; +// Variables relating to videos waiting for user input +var buffer_search = {}; // Variables relating to the last video searched var last_search = {}; var last_token = {}; var last_playback = {}; -// Variables for repetition of current song +// Variables for repetition of current song var repeat_infinitely = {}; var repeat_once = {}; +// To track whether downloading is already in progress +var downloading = false; + /** * Generates a random UUID. Used for creating an audio stream token. * @@ -103,7 +111,7 @@ function search_video(req, res, lang) { return new Promise((resolve, reject) => { var search = heroku + "/alexa/v3/search/" + new Buffer(query).toString("base64"); - // Populate URL with correct language + // Populate URL with correct language if (lang === "de-DE") { search += "?language=de"; } else if (lang === "fr-FR") { @@ -120,7 +128,7 @@ function search_video(req, res, lang) { } else { // Convert body text in response to JSON object var body_json = JSON.parse(body); - if (body_json.status === "error" && body_json.message === "No results found") { + if (body_json.state === "error" && body_json.message === "No results found") { // Query did not return any video resolve({ message: response_messages[lang]["NO_RESULTS_FOUND"].formatUnicorn(query), @@ -155,6 +163,7 @@ function search_video(req, res, lang) { // Set most recently searched for video buffer_search[userId] = metadata; + downloading = false; res.reprompt().shouldEndSession(false); } @@ -167,8 +176,110 @@ function search_video(req, res, lang) { }); } +function make_download_video_request(id) { + return new Promise((resolve, reject) => { + request(heroku + "/alexa/v3/download/" + id, function(err, res, body) { + if (err) { + console.error(err.message); + reject(err.message); + } else { + var body_json = JSON.parse(body); + var url = heroku + body_json.link; + console.log("Requested download for ... " + url); + resolve(url); + } + }); + }); +} + +function check_cache_ready(id, timeout) { + return new Promise((resolve, reject) => { + request(heroku + "/alexa/v3/cache/" + id, function(err, res, body) { + if (!err) { + var body_json = JSON.parse(body); + if (body_json.hasOwnProperty('downloaded') && body_json['downloaded'] != null) { + if (body_json.downloaded) { + downloading = false; + + console.log(id + " has been cached. Ready to play!"); + + resolve(); + } + else { + downloading = true; + + console.log(id + " is being cached."); + if (timeout <= 0) { + resolve(); + return; + } + + var interval = Math.min(cache_polling_interval, timeout); + console.log("Checking again in " + interval + "ms (delay: " + timeout + "ms)."); + + resolve(new Promise((_resolve, _reject) => { + setTimeout(() => { + _resolve(check_cache_ready(id, timeout - cache_polling_interval).catch(_reject)); + }, interval); + }).catch(reject)); + } + } + else { + console.log(id + " will not be cached."); + reject("Video unavailable."); + } + } + else { + console.error(err.message); + reject(err.message); + } + }); + }); +} + +function respond_play(req, res) { + var userId = req.userId; + + // Final response to the user, indicating that their video will be playing + var speech = new ssml(); + var title = buffer_search[userId].title; + var message = response_messages[req.data.request.locale]["NOW_PLAYING"].formatUnicorn(title); + speech.say(message); + res.say(speech.ssml(true)); + + console.log("Starting to play ... " + title); + + // Start playing the video! + restart_video(req, res, 0); +} + +function interactively_wait_for_video(req, res) { + var userId = req.userId; + var id = buffer_search[userId].id; + return check_cache_ready(id, ask_interval).then(() => { + if (!downloading) { + // Download finished ... notify user + respond_play(req, res); + } + else { + console.log("Asking whether to continue waiting." ); + + // Download still in progress ... ask if the user wants to keep tracking + var message = response_messages[req.data.request.locale]["ASK_TO_CONTINUE"]; + var speech = new ssml(); + speech.say(message); + res.say(speech.ssml(true)); + res.reprompt(message).shouldEndSession(false); + } + return res.send(); + }).catch(reason => { + console.error(reason); + return res.fail(reason); + }); +} + /** - * Downloads the mostly recent video the user requested. + * Downloads the mostly recent video the user requested. * * @param {Object} req A request from an Alexa device * @param {Object} res A response that will be sent to the device @@ -181,7 +292,7 @@ function download_video(req, res) { console.log("Requesting download ... " + id); return new Promise((resolve, reject) => { - var download = heroku + "/alexa/v3/download/" + id; + var download = heroku + "/alexa/v3/download/" + id; // Make download request to server request(download, function(err, res, body) { @@ -209,12 +320,7 @@ function download_video(req, res) { }); }).then(function() { // Have Alexa tell the user that the video is finished downloading - var speech = new ssml(); - speech.say(response_messages[req.data.request.locale]["NOW_PLAYING"].formatUnicorn(buffer_search[userId].title)); - res.say(speech.ssml(true)); - - // Start playing the video! - restart_video(req, res, 0); + respond_play(req, res); // Send response to Alexa device res.send(); @@ -231,19 +337,17 @@ function download_video(req, res) { * @param {Function} callback The function to execute about load completion */ function wait_for_video(id, callback) { - setTimeout(function() { - request(heroku + "/alexa/v3/cache/" + id, function(err, res, body) { - if (!err) { - var body_json = JSON.parse(body); - if (body_json.downloaded) { - callback(); - } - else { - wait_for_video(id, callback); - } + request(heroku + "/alexa/v3/cache/" + id, function(err, res, body) { + if (!err) { + var body_json = JSON.parse(body); + if (body_json.downloaded) { + callback(); } - }); - }, 2000); + else { + setTimeout(wait_for_video, cache_polling_interval, id, callback); + } + } + }); } // Filter out bad requests (the client's ID is not the same as the server's) @@ -260,6 +364,10 @@ app.pre = function(req, res, type) { } }; +app.error = function(exc, req, res) { + res.say("An error occured: " + exc); +}; + // Looking up a video in English app.intent("GetVideoIntent", { "slots": { @@ -339,9 +447,24 @@ app.intent("AMAZON.YesIntent", function(req, res) { if (!buffer_search.hasOwnProperty(userId) || buffer_search[userId] == null) { res.send(); } - else { + else if (!interactive_wait) { return download_video(req, res); } + else { + var id = buffer_search[userId].id; + if (!downloading) { + return make_download_video_request(id) + .then(url => { + downloading = true; + last_search[userId] = url; + return interactively_wait_for_video(req, res); + }) + .catch(reason => { + return res.fail(reason); + }); + } + return interactively_wait_for_video(req, res); + } }); app.intent("AMAZON.NoIntent", function(req, res) { @@ -362,9 +485,9 @@ app.audioPlayer("PlaybackNearlyFinished", function(req, res) { var userId = req.userId; // Repeat is enabled, so begin next playback - if (has_video(userId) && - ((repeat_infinitely.hasOwnProperty(userId) && repeat_infinitely[userId]) || - (repeat_once.hasOwnProperty(userId) && repeat_once[userId]))) + if (has_video(userId) && + ((repeat_infinitely.hasOwnProperty(userId) && repeat_infinitely[userId]) || + (repeat_once.hasOwnProperty(userId) && repeat_once[userId]))) { // Generate new token for the stream var new_token = uuidv4(); @@ -473,7 +596,7 @@ app.intent("AMAZON.PauseIntent", {}, function(req, res) { res.send(); }); -// User told Alexa to repeat audio once +// User told Alexa to repeat audio once app.intent("AMAZON.RepeatIntent", {}, function(req, res) { var userId = req.userId; diff --git a/src/package-lock.json b/src/package-lock.json index a85d807..f7f1dd5 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,6 +1,6 @@ { "name": "alexa-youtube-skill", - "version": "3.0.0", + "version": "3.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,7 +14,7 @@ "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", "requires": { - "@types/chai": "4.1.7" + "@types/chai": "*" } }, "ajv": { @@ -22,10 +22,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "alexa-app": { @@ -33,13 +33,13 @@ "resolved": "https://registry.npmjs.org/alexa-app/-/alexa-app-4.2.3.tgz", "integrity": "sha512-PhUbFEqUtleN8yy0x7ltD6mAMgzUlkuW2oQKuv8WSlXEPkl9xzkAugd38XdhorkCyclCxaGgDBGAdyJk33odSQ==", "requires": { - "@types/chai-as-promised": "7.1.0", - "alexa-utterances": "0.2.1", - "alexa-verifier-middleware": "1.0.1", - "bluebird": "2.11.0", - "body-parser": "1.18.3", - "lodash.defaults": "4.2.0", - "numbered": "1.1.0" + "@types/chai-as-promised": "^7.1.0", + "alexa-utterances": "^0.2.0", + "alexa-verifier-middleware": "^1.0.0", + "bluebird": "^2.10.2", + "body-parser": "^1.15.2", + "lodash.defaults": "^4.2.0", + "numbered": "^1.0.0" } }, "alexa-utterances": { @@ -47,8 +47,8 @@ "resolved": "https://registry.npmjs.org/alexa-utterances/-/alexa-utterances-0.2.1.tgz", "integrity": "sha1-8rcLav062IZxHaj5lOfklUnmC/E=", "requires": { - "js-combinatorics": "0.5.4", - "numbered": "1.1.0" + "js-combinatorics": "^0.5.0", + "numbered": "^1.0.0" } }, "alexa-verifier": { @@ -56,8 +56,8 @@ "resolved": "https://registry.npmjs.org/alexa-verifier/-/alexa-verifier-1.0.0.tgz", "integrity": "sha512-1XE/40ajf4sESuvAdacxVrxy06tkewC2sJ8qC5T/zQtGiYRsuoQjj6UkSpw7WTqG8wkmESANz/w5NnfIruyCjQ==", "requires": { - "node-forge": "0.7.6", - "validator": "9.4.1" + "node-forge": "^0.7.0", + "validator": "^9.0.0" } }, "alexa-verifier-middleware": { @@ -65,7 +65,7 @@ "resolved": "https://registry.npmjs.org/alexa-verifier-middleware/-/alexa-verifier-middleware-1.0.1.tgz", "integrity": "sha512-dBWmXmtK4HHdibu18csh7f9uOzMvsu55X7PoROfRntLRRSJI5faPnNp/K5oAKnA9A8SzOpZDTQ92FSXDX0RZQQ==", "requires": { - "alexa-verifier": "1.0.0" + "alexa-verifier": "^1.0.0" } }, "asn1": { @@ -73,7 +73,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -101,7 +101,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bluebird": { @@ -115,15 +115,15 @@ "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", + "depd": "~1.1.2", + "http-errors": "~1.6.3", "iconv-lite": "0.4.23", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.2", "raw-body": "2.3.3", - "type-is": "1.6.16" + "type-is": "~1.6.16" } }, "bytes": { @@ -141,7 +141,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "content-type": { @@ -159,7 +159,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { @@ -185,8 +185,8 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ee-first": { @@ -224,9 +224,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.7", - "mime-types": "2.1.21" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, "getpass": { @@ -234,7 +234,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "har-schema": { @@ -247,8 +247,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "6.6.1", - "har-schema": "2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, "http-errors": { @@ -256,10 +256,10 @@ "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "depd": "1.1.2", + "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": "1.5.0" + "statuses": ">= 1.4.0 < 2" } }, "http-signature": { @@ -267,9 +267,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.15.2" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -277,7 +277,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "inherits": { @@ -351,7 +351,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.37.0" + "mime-db": "~1.37.0" } }, "ms": { @@ -418,26 +418,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.8.0", - "caseless": "0.12.0", - "combined-stream": "1.0.7", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.3", - "har-validator": "5.1.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.21", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.4.3", - "tunnel-agent": "0.6.0", - "uuid": "3.3.2" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" } }, "safe-buffer": { @@ -460,15 +460,15 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "ssml-builder": { @@ -486,8 +486,8 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "psl": "1.1.29", - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -502,7 +502,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -516,7 +516,7 @@ "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.21" + "mime-types": "~2.1.18" } }, "unpipe": { @@ -529,7 +529,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" } }, "uuid": { @@ -547,9 +547,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } } } diff --git a/src/package.json b/src/package.json index f707164..c95e50a 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "alexa-youtube-skill", - "version": "3.0.0", + "version": "3.0.1", "description": "Use Alexa to search YouTube for your favorite videos", "engines": { "node": "6.1.0" diff --git a/src/util/responses.js b/src/util/responses.js index 40a21e7..2fe2b74 100644 --- a/src/util/responses.js +++ b/src/util/responses.js @@ -1,18 +1,20 @@ -module.exports = { +let messages = { "en-US": { "NO_RESULTS_FOUND": "{0} did not return any results on YouTube.", - "ASK_TO_PLAY": "I found a video called {0}. Would you like me to play it?", + "ASK_TO_PLAY": "I found a video called {0}. Would you like me to download it now?", + "ASK_TO_CONTINUE": "Download still in progress. Would you like to keep waiting?", "NOW_PLAYING": "I am now playing {0}.", "NOTHING_TO_RESUME": "You are not playing anything currently.", "NOTHING_TO_REPEAT": "You have not selected a video to play.", "LOOP_ON_TRIGGERED": "I will repeat your {0} selection infinitely.", "LOOP_OFF_TRIGGERED": "I will no longer repeat your {0} selection.", "REPEAT_TRIGGERED": "I will repeat your {0} selection once.", - "HELP_TRIGGERED": "To use the YouTube skill, tell the skill to search for the video you want. Additionally, once the video is playing, you can tell Alexa to pause, restart, or loop it." + "HELP_TRIGGERED": "To use the YouTube skill, tell the skill to search for the video you want. Additionally, once the video is playing, you can tell Alexa to pause, restart, or loop it." }, "de-DE": { "NO_RESULTS_FOUND": "Keine Ergebnisse auf Youtube gefunden.", "ASK_TO_PLAY": "Ich habe ein Video mit dem Namen {0} gefunden. Möchten Sie, dass ich es spiele?", + "ASK_TO_CONTINUE": "Download läuft noch. Möchten Sie gerne warten?", "NOW_PLAYING": "Ich spiele jetzt {0}.", "NOTHING_TO_RESUME": "Sie spielen derzeit nichts.", "NOTHING_TO_REPEAT": "Sie haben kein Video zum Abspielen ausgewählt.", @@ -24,6 +26,7 @@ module.exports = { "fr-FR": { "NO_RESULTS_FOUND": "{0} n'a pas été trouvé sur YouTube.", "ASK_TO_PLAY": "J'ai trouvé une vidéo appelée {0}. Voulez-vous que je joue?", + "ASK_TO_CONTINUE": "Téléchargement toujours en cours. Voulez-vous continuer à attendre?", "NOW_PLAYING": "Je suis en train de jouer {0}.", "NOTHING_TO_RESUME": "Il n'y a aucun élément en cours de lecture.", "NOTHING_TO_REPEAT": "Vous n'avez pas sélectionné de vidéo à écouter.", @@ -35,6 +38,7 @@ module.exports = { "it-IT": { "NO_RESULTS_FOUND": "{0} non lo trovo su YouTube.", "ASK_TO_PLAY": "Ho trovato un video chiamato {0}. Vorresti che lo suonassi?", + "ASK_TO_CONTINUE": "Download ancora in corso. Ti piacerebbe continuare ad aspettare?", "NOW_PLAYING": "Sto suonando {0}.", "NOTHING_TO_RESUME": "Non stai suonando nulla ora.", "NOTHING_TO_REPEAT": "Non hai selezionato nessun video da riprodurre.", @@ -44,3 +48,7 @@ module.exports = { "HELP_TRIGGERED": "Per usare la skill di YouTube, chiedi alla skill di cercare il video che vuoi. In aggiunta, una volta che il video è in riproduzione, puoi chiedere ad Alexa di metterlo in pausa, riavviarlo, o di metterlo in loop." } } + +messages['en-GB'] = messages['en-US']; + +module.exports = messages;