diff --git a/dashboard-ui/voice/Readme.md b/dashboard-ui/voice/Readme.md new file mode 100644 index 0000000000..ffa75e2c06 --- /dev/null +++ b/dashboard-ui/voice/Readme.md @@ -0,0 +1,177 @@ +#Emby Voice commands + +Emby voice commands use json and regular expression to find corresponding commands. + +With this solution the translation to other languages will be simplified and is fully compatible with regular expression for many programming languages. + +###Json template : +```json +[ + { + "group": "general", + "name": "General commands", + "defaultValues": { + "sourceid": "", + "deviceid": "", + "itemName": "", + "itemType": "", + "shuffle": false, + "filters": [], + "sortBy": "", + "sortOrder": "", + "limit": 100, + "category": "" + }, + "items": [ + { + "actionid": "show", + "sourceid": "movies", + "menuid" : "home", + "groupid" : "movie", + "deviceid": "displaymirroring", + "command": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "itemName": "", + "itemType": "movie", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending", + "limit": 100, + "category": "", + "commandtemplates": [ + "Show Movie based commands", + "Show Music based commands", + "Show Picture based commands", + "Show TV series based commands", + "Show general commands" + ] + } + ] + } +] +``` + +###Json hierarchy + +>+ Group + - defaultValues + - Items + + commandtemplates + +###Json Description : + +**Group** +>**groupid** : (mandatory) id of the group commands +>**name** : (mandatory) name of the group + +**Items and defaultValues (mandatory)** + +**actionid** : (mandatory) Liste of actions available +>- show +- play +- shuffle +- search +- control +- enable +- disable +- toggle + +**sourceid** : (optional) source commands available +>- music +- movies +- tvseries +- livetv +- recordings +- latestepisodes +- home +- group + +**menuid** : (optional) menu commands available +>- Commands for live TV + - livetv + - guide + - channels + - recordings + - scheduled + - series + - group +>- Commands for home menus + - home + - nextup + - favorites + - upcoming + - nowplaying + +**groupid** : (optional) name of the group +You can define a group name specified in the json file + +**deviceid** : (optional) devices commands available +>- displaymirroring + + +**Emby filters** : (optional) +>- itemName +- itemType +- shuffle : default value = false +- filters +- sortBy +- sortOrder +- limit : default value = 100 +- category + +**commandtemplates** (mandatory) +array : list of text commands + + +**command** : (mandatory) +regular expression used to filter commands +**Exemple :** +command: "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", +altcommand: "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + +####Regular expression description +```json +(? or ?, etc) - are for defining watts is captured +my|me - indicate each of those words/phrases can be used +\\s? - is for spaces +(?.*)? - the ? at the end of the closing brackets represent an optional value + +``` + +In the example below theses phrases can the match for an action +- Show my movies +- Show movies +- Display movies +- Go to movies +- etc. + +**altcommand** : (optional) +alternate regular expression used to filter commands if the property **command** does not match + + +####Regular expression description +```json +? or ?, etc - are for defining watts is captured +my|me - indicate each of those words/phrases can be used +\s? - is for spaces +(?.*)? - the ? at the end of the closing brackets represent an optional value + +``` + +####Additional properties used by regular expression +>**action** : Linked to actionid +>**source** : Linked to sourceid +>**menu** : Linked to menuid +>**group** : Linked to groupid +>**device** : Linked to deviceid +>**determiner1 or determiner2 etc** : used just to capture words +>**moviename** +>**devicename** +>**songname** +>**artistname** +>**albumname** +>**seriename** +>**seasonname** +>**picturename** +>**authorname** \ No newline at end of file diff --git a/dashboard-ui/voice/commands/controlcommands.js b/dashboard-ui/voice/commands/controlcommands.js new file mode 100644 index 0000000000..46d27418f0 --- /dev/null +++ b/dashboard-ui/voice/commands/controlcommands.js @@ -0,0 +1,11 @@ + +define([], function () { + + return function (result) { + result.success = true; + if (result.properties.devicename) + MediaController.trySetActiveDeviceName(result.properties.devicename); + + return; + } +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/disablecommands.js b/dashboard-ui/voice/commands/disablecommands.js new file mode 100644 index 0000000000..6338fecd5f --- /dev/null +++ b/dashboard-ui/voice/commands/disablecommands.js @@ -0,0 +1,16 @@ + +define([], function () { + + return function (result) { + result.success = true; + switch (result.item.deviceid) { + case 'displaymirroring': + MediaController.enableDisplayMirroring(false); + break; + default: + result.success = false; + return; + } + } + +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/enablecommands.js b/dashboard-ui/voice/commands/enablecommands.js new file mode 100644 index 0000000000..e790731f48 --- /dev/null +++ b/dashboard-ui/voice/commands/enablecommands.js @@ -0,0 +1,16 @@ + +define([], function () { + + return function (result) { + result.success = true; + switch (result.item.deviceid) { + case 'displaymirroring': + MediaController.enableDisplayMirroring(true); + break; + default: + result.success = false; + return; + } + } + +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/playcommands.js b/dashboard-ui/voice/commands/playcommands.js new file mode 100644 index 0000000000..1e1c3fc81b --- /dev/null +++ b/dashboard-ui/voice/commands/playcommands.js @@ -0,0 +1,106 @@ + +define([], function () { + + /// Play items. + /// The items. + /// The shuffle. + /// . + function playItems(items, shuffle) { + + if (shuffle) { + items = shuffleArray(items); + } + + items = items.map(function (i) { + return i.Id; + }); + + if (items.length) { + MediaController.play({ + ids: items + }); + } + else { + Dashboard.alert({ + message: Globalize.translate('MessageNoItemsFound') + }); + } + } + + /// Shuffle array. + /// The array. + /// . + function shuffleArray(array) { + var currentIndex = array.length, temporaryValue, randomIndex; + + // While there remain elements to shuffle... + while (0 !== currentIndex) { + + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; + } + + return function (result) { + result.success = false; + + var query = { + + Limit: result.item.limit || 100, + UserId: result.userId, + ExcludeLocationTypes: "Virtual" + }; + + if (result.item.itemType) { + query.IncludeItemTypes = result.item.itemType; + } + + if (result.item.sourceid === 'nextup') { + + ApiClient.getNextUpEpisodes(query).then(function (queryResult) { + + playItems(queryResult.Items, result.item.shuffle); + + }); + result.success = true; + return; + } + + if (result.item.shuffle) { + result.item.sortBy = result.sortBy ? 'Random,' + result.item.sortBy : 'Random'; + } + + query.SortBy = result.item.sortBy; + query.SortOrder = result.item.sortOrder; + query.Recursive = true; + + if (result.item.filters.indexOf('unplayed') !== -1) { + query.IsPlayed = false; + } + if (result.item.filters.indexOf('played') !== -1) { + query.IsPlayed = true; + } + if (result.item.filters.indexOf('favorite') !== -1) { + query.Filters = 'IsFavorite'; + } + + + ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (queryResult) { + + playItems(queryResult.Items, result.item.shuffle); + }); + + result.success = true; + + return; + + } +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/searchcommands.js b/dashboard-ui/voice/commands/searchcommands.js new file mode 100644 index 0000000000..6338fecd5f --- /dev/null +++ b/dashboard-ui/voice/commands/searchcommands.js @@ -0,0 +1,16 @@ + +define([], function () { + + return function (result) { + result.success = true; + switch (result.item.deviceid) { + case 'displaymirroring': + MediaController.enableDisplayMirroring(false); + break; + default: + result.success = false; + return; + } + } + +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/showcommands.js b/dashboard-ui/voice/commands/showcommands.js new file mode 100644 index 0000000000..81850a657d --- /dev/null +++ b/dashboard-ui/voice/commands/showcommands.js @@ -0,0 +1,93 @@ + +define([], function () { + + return function (result) { + result.success = true; + switch (result.item.sourceid) { + case 'music': + Dashboard.navigate('music.html'); + break; + case 'movies': + if (result.properties.movieName) { + //TODO: Find a way to display movie + var query = { + + Limit: 1, + UserId: result.userId, + ExcludeLocationTypes: "Virtual" + }; + + + if (result.item.itemType) { + query.IncludeItemTypes = result.item.itemType; + } + + query.SearchTerm = result.properties.movieName; + + ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (queryResult) { + + var s = queryResult[0]; + + }); + + } + else + Dashboard.navigate('movies.html'); + + break; + case 'tvseries': + Dashboard.navigate('tv.html'); + break; + case 'livetv': + var act = result.item.menuid; + if (act) { + if (act.indexOf('livetv') != -1) + Dashboard.navigate('livetv.html?tab=0'); + else if (act.indexOf('guide') != -1) + Dashboard.navigate('livetv.html?tab=1'); + else if (act.indexOf('channels') != -1) + Dashboard.navigate('livetv.html?tab=2'); + else if (act.indexOf('recordings') != -1) + Dashboard.navigate('livetv.html?tab=3'); + else if (act.indexOf('scheduled') != -1) + Dashboard.navigate('livetv.html?tab=4'); + else if (act.indexOf('series') != -1) + Dashboard.navigate('livetv.html?tab=5'); + else + Dashboard.navigate('livetv.html?tab=0'); + } + else + Dashboard.navigate('livetv.html?tab=0'); + break; + case 'recordings': + Dashboard.navigate('livetv.html?tab=3'); + break; + case 'latestepisodes': + Dashboard.navigate('tv.html?tab=1'); + case 'home': + var act = result.item.menuid; + if (act) { + if (act.indexOf('home') != -1) + Dashboard.navigate('index.html'); + else if (act.indexOf('nextup') != -1) + Dashboard.navigate('index.html?tab=2'); + else if (act.indexOf('favorites') != -1) + Dashboard.navigate('index.html?tab=2'); + else if (act.indexOf('upcoming') != -1) + Dashboard.navigate('index.html?tab=3'); + else if (act.indexOf('nowplaying') != -1) + Dashboard.navigate('nowplaying.html'); + else + Dashboard.navigate('index.html'); + } + else + Dashboard.navigate('index.html'); + case 'group': + break; + default: + result.success = false; + return; + } + + } +}); \ No newline at end of file diff --git a/dashboard-ui/voice/commands/togglecommands.js b/dashboard-ui/voice/commands/togglecommands.js new file mode 100644 index 0000000000..526668229c --- /dev/null +++ b/dashboard-ui/voice/commands/togglecommands.js @@ -0,0 +1,16 @@ + +define([], function () { + + return function (result) { + result.success = true; + switch (result.item.deviceid) { + case 'displaymirroring': + MediaController.toggleDisplayMirroring(); + break; + default: + result.success = false; + return; + } + } + +}); \ No newline at end of file diff --git a/dashboard-ui/voice/grammar/en-US.json b/dashboard-ui/voice/grammar/en-US.json new file mode 100644 index 0000000000..0a24a4bc78 --- /dev/null +++ b/dashboard-ui/voice/grammar/en-US.json @@ -0,0 +1,330 @@ +[ + { + "groupid": "general", + "name": "General commands", + "items": [ + { + "actionid": "show", + "sourceid": "group", + "groupid": "movie", + "command": "(?show|display|view)\\s?(?Movie)?\\s?(?based commands)", + "commandtemplates": [ "Show Movie based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "music", + "command": "(?show|display|view)\\s?(?Music)?\\s?(?based commands)", + "commandtemplates": [ "Show Music based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "picture", + "command": "(?show|display|view)\\s?(?Picture)?\\s?(?based commands)", + "commandtemplates": [ "Show Picture based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "tvseries", + "command": "(?show|display|view)\\s?(?TV series)?\\s?(?based commands)", + "commandtemplates": [ "Show TV series based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "home", + "command": "(?show|display|view)\\s?(?home page)?\\s?(?based commands)", + "commandtemplates": [ "Show home page based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "general", + "command": "(?show|display|view)\\s?(?general)?\\s?(?group commands)", + "commandtemplates": [ "Show general group commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "devices", + "command": "(?show|display|view)\\s?(?devices)?\\s?(?based commands)", + "commandtemplates": [ "Show devices based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "livetv", + "command": "(?show|display|view)\\s?(?live tv)?\\s?(?based commands)", + "commandtemplates": [ "Show Live TV based commands" ] + } + ] + }, + { + "groupid": "home", + "name": "Home commands", + "items": [ + { + "actionid": "show", + "sourceid": "home", + "menuid": "home", + "command": "(? show|display|view)\\s?(?home)", + "commandtemplates": [ "Show home" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "nextup", + "command": "(? show|display|view)\\s?(?next up)", + "commandtemplates": [ "Show next up" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "favorites", + "command": "(? show|display|view)\\s?(?favorites)", + "commandtemplates": [ "Show favorites" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "upcoming", + "command": "(? show|display|view)\\s?(?upcoming)", + "commandtemplates": [ "Show upcoming" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "nowplaying", + "command": "(? show|display|view)\\s?(?now playing)", + "commandtemplates": [ "Show now playing" ] + } + ] + }, + { + "groupid": "movie", + "name": "Movie based commands", + "defaultValues": { + "sourceid": "movies", + "itemType": "Movie", + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "movies", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?movies)\\s?(?.*)?", + "altcommand": "(?show|display|go to|view)\\s?(?the)?\\s?(?movie)\\s?(?.*)?", + "itemName": "", + "commandtemplates": [ + "Show movies", + "Show my movies", + "Show the movie [movie name]" + ] + }, + { + "actionid": "play", + "sourceid": "movies", + "command": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "itemName": "", + "itemType": "Audio", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending", + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + } + ] + }, + { + "groupid": "music", + "name": "Music based commands", + "defaultValues": { + "itemName": "", + "itemType": "Audio", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "music", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?(music|songs))\\s?(?.*)?", + "commandtemplates": [ + "Show music", + "Show my music", + "Show my songs" + ] + }, + { + "actionid": "play", + "sourceid": "music", + "command": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "itemName": "", + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + }, + { + "actionid": "shuffle", + "sourceid": "music", + "command": "(?shuffle)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?shuffle)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "shuffle": true, + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + } + ] + }, + { + "groupid": "tvseries", + "name": "TV series based commands", + "defaultValues": { + "itemType": "Series", + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "tvseries", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?(tv series))\\s?(?.*)?", + "commandtemplates": [ + "Show tv series", + "Show my tv series" + ] + }, + { + "actionid": "play", + "sourceid": "nextup", + "command": "(?play|Listen to)\\s?(? next up)\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(? next up)", + "commandtemplates": [ "Play next up" ] + }, + { + "actionid": "play", + "sourceid": "latestepisodes", + "command": "(?play)\\s?(?my|me)?\\s?(?latest episodes)\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play)\\s?(?my|me)?\\s?(?latest episodes)", + "commandtemplates": [ "Play my latest episodes" ] + } + ] + }, + { + "groupid": "livetv", + "name": "Live TV based commands", + "items": [ + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "suggestions", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv|live tv suggestions)", + "commandtemplates": [ "Show live tv" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "guide", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv guide)", + "commandtemplates": [ "Show live tv guide" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "channels", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv channels)", + "commandtemplates": [ "Show live tv channels" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "recordings", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv recordings)", + "commandtemplates": [ "Show live tv recordings" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "scheduled", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv scheduled)", + "commandtemplates": [ "Show live tv scheduled" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "series", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv series)", + "commandtemplates": ["Show live tv series"] + } + ] + }, + { + "groupid": "picture", + "name": "Picture based commands", + "defaultValues": { + "itemType": "Picture", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "tvseries", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?pictures)", + "commandtemplates": [ + "Show pictures", + "Show my pictures" + ] + } + ] + }, + { + "groupid": "devices", + "name": "Devices based commands", + "items": [ + { + "actionid": "enable", + "deviceid": "displaymirroring", + "command": "(? enable|turn on)\\s?(?display mirroring)", + "commandtemplates": [ "Turn on display mirroring", "Enable display mirroring" ] + }, + { + "actionid": "disable", + "deviceid": "displaymirroring", + "command": "(? disable|turn off)\\s?(?display mirroring)", + "commandtemplates": [ "Turn off display mirroring", "Disable display mirroring" ] + }, + { + "actionid": "toggle", + "deviceid": "displaymirroring", + "command": "(?toggle)\\s?(?display mirroring)", + "commandtemplates": [ "Toggle display mirroring" ] + }, + { + "actionid": "control", + "command": "(?control)\\s?(?.*)", + "commandtemplates": [ "Control [Device Name]" ] + } + ] + } + +] diff --git a/dashboard-ui/voice/grammar/grammar.json b/dashboard-ui/voice/grammar/grammar.json new file mode 100644 index 0000000000..0a24a4bc78 --- /dev/null +++ b/dashboard-ui/voice/grammar/grammar.json @@ -0,0 +1,330 @@ +[ + { + "groupid": "general", + "name": "General commands", + "items": [ + { + "actionid": "show", + "sourceid": "group", + "groupid": "movie", + "command": "(?show|display|view)\\s?(?Movie)?\\s?(?based commands)", + "commandtemplates": [ "Show Movie based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "music", + "command": "(?show|display|view)\\s?(?Music)?\\s?(?based commands)", + "commandtemplates": [ "Show Music based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "picture", + "command": "(?show|display|view)\\s?(?Picture)?\\s?(?based commands)", + "commandtemplates": [ "Show Picture based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "tvseries", + "command": "(?show|display|view)\\s?(?TV series)?\\s?(?based commands)", + "commandtemplates": [ "Show TV series based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "home", + "command": "(?show|display|view)\\s?(?home page)?\\s?(?based commands)", + "commandtemplates": [ "Show home page based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "general", + "command": "(?show|display|view)\\s?(?general)?\\s?(?group commands)", + "commandtemplates": [ "Show general group commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "devices", + "command": "(?show|display|view)\\s?(?devices)?\\s?(?based commands)", + "commandtemplates": [ "Show devices based commands" ] + }, + { + "actionid": "show", + "sourceid": "group", + "groupid": "livetv", + "command": "(?show|display|view)\\s?(?live tv)?\\s?(?based commands)", + "commandtemplates": [ "Show Live TV based commands" ] + } + ] + }, + { + "groupid": "home", + "name": "Home commands", + "items": [ + { + "actionid": "show", + "sourceid": "home", + "menuid": "home", + "command": "(? show|display|view)\\s?(?home)", + "commandtemplates": [ "Show home" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "nextup", + "command": "(? show|display|view)\\s?(?next up)", + "commandtemplates": [ "Show next up" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "favorites", + "command": "(? show|display|view)\\s?(?favorites)", + "commandtemplates": [ "Show favorites" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "upcoming", + "command": "(? show|display|view)\\s?(?upcoming)", + "commandtemplates": [ "Show upcoming" ] + }, + { + "actionid": "show", + "sourceid": "home", + "menuid": "nowplaying", + "command": "(? show|display|view)\\s?(?now playing)", + "commandtemplates": [ "Show now playing" ] + } + ] + }, + { + "groupid": "movie", + "name": "Movie based commands", + "defaultValues": { + "sourceid": "movies", + "itemType": "Movie", + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "movies", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?movies)\\s?(?.*)?", + "altcommand": "(?show|display|go to|view)\\s?(?the)?\\s?(?movie)\\s?(?.*)?", + "itemName": "", + "commandtemplates": [ + "Show movies", + "Show my movies", + "Show the movie [movie name]" + ] + }, + { + "actionid": "play", + "sourceid": "movies", + "command": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "itemName": "", + "itemType": "Audio", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending", + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + } + ] + }, + { + "groupid": "music", + "name": "Music based commands", + "defaultValues": { + "itemName": "", + "itemType": "Audio", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "music", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?(music|songs))\\s?(?.*)?", + "commandtemplates": [ + "Show music", + "Show my music", + "Show my songs" + ] + }, + { + "actionid": "play", + "sourceid": "music", + "command": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "itemName": "", + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + }, + { + "actionid": "shuffle", + "sourceid": "music", + "command": "(?shuffle)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?shuffle)\\s?(?my|me)?\\s?(? music)\\s?(?.*)?", + "shuffle": true, + "commandtemplates": [ + "Play music", + "Play my music", + "Listen to my music " + ] + } + ] + }, + { + "groupid": "tvseries", + "name": "TV series based commands", + "defaultValues": { + "itemType": "Series", + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "tvseries", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?(tv series))\\s?(?.*)?", + "commandtemplates": [ + "Show tv series", + "Show my tv series" + ] + }, + { + "actionid": "play", + "sourceid": "nextup", + "command": "(?play|Listen to)\\s?(? next up)\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play|Listen to)\\s?(? next up)", + "commandtemplates": [ "Play next up" ] + }, + { + "actionid": "play", + "sourceid": "latestepisodes", + "command": "(?play)\\s?(?my|me)?\\s?(?latest episodes)\\s?(?on device|to device)\\s?(?.*)", + "altcommand": "(?play)\\s?(?my|me)?\\s?(?latest episodes)", + "commandtemplates": [ "Play my latest episodes" ] + } + ] + }, + { + "groupid": "livetv", + "name": "Live TV based commands", + "items": [ + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "suggestions", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv|live tv suggestions)", + "commandtemplates": [ "Show live tv" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "guide", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv guide)", + "commandtemplates": [ "Show live tv guide" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "channels", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv channels)", + "commandtemplates": [ "Show live tv channels" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "recordings", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv recordings)", + "commandtemplates": [ "Show live tv recordings" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "scheduled", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv scheduled)", + "commandtemplates": [ "Show live tv scheduled" ] + }, + { + "actionid": "show", + "sourceid": "livetv", + "menuid": "series", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?Live tv series)", + "commandtemplates": ["Show live tv series"] + } + ] + }, + { + "groupid": "picture", + "name": "Picture based commands", + "defaultValues": { + "itemType": "Picture", + "shuffle": false, + "filters": [ ], + "sortBy": "", + "sortOrder": "Ascending" + }, + "items": [ + { + "actionid": "show", + "sourceid": "tvseries", + "command": "(?show|display|go to|view)\\s?(?my|me)?\\s?(?pictures)", + "commandtemplates": [ + "Show pictures", + "Show my pictures" + ] + } + ] + }, + { + "groupid": "devices", + "name": "Devices based commands", + "items": [ + { + "actionid": "enable", + "deviceid": "displaymirroring", + "command": "(? enable|turn on)\\s?(?display mirroring)", + "commandtemplates": [ "Turn on display mirroring", "Enable display mirroring" ] + }, + { + "actionid": "disable", + "deviceid": "displaymirroring", + "command": "(? disable|turn off)\\s?(?display mirroring)", + "commandtemplates": [ "Turn off display mirroring", "Disable display mirroring" ] + }, + { + "actionid": "toggle", + "deviceid": "displaymirroring", + "command": "(?toggle)\\s?(?display mirroring)", + "commandtemplates": [ "Toggle display mirroring" ] + }, + { + "actionid": "control", + "command": "(?control)\\s?(?.*)", + "commandtemplates": [ "Control [Device Name]" ] + } + ] + } + +] diff --git a/dashboard-ui/voice/grammarprocessor.js b/dashboard-ui/voice/grammarprocessor.js new file mode 100644 index 0000000000..4b0bbca76b --- /dev/null +++ b/dashboard-ui/voice/grammarprocessor.js @@ -0,0 +1,241 @@ +// 09.10.2015 +// grammarprocessor class +define([], function () { + + /// The named register exponent. + var NamedRegExp = function (pattern, string) { + pattern = pattern.toString(); + var regexp = []; + var groupRX = /\(\?\<(.*?)\>\s?(.*?)\)/i; + while (groupRX.test(pattern)) { + + var match = groupRX.exec(pattern); + regexp.push({ + name: match[1].trim().toLowerCase(), + pattern: match[2].trim().toLowerCase(), + value: null, + title: '' + }); + pattern = pattern.replace(groupRX, '(' + match[2].trim() + ')'); + } + + var finalMatch = (new RegExp(pattern, "i")).exec(string); + if (finalMatch) { + for (var i = 0, len = regexp.length; i < len; i++) { + if (finalMatch[(i + 1)] !== false) { + var mth = finalMatch[(i + 1)]; + if (mth) + mth = mth.trim().toLowerCase(); + regexp[i].value = mth; + } + } + } + else + regexp = null; + return regexp; + }; + + /// Processcommands. + /// The text. + /// The command groups. + /// . + function processcommands(text, commandgroups) { + + var result = { + groupid: '', + name: '', + item: { + actionid: '', + groupid: '', + sourceid: '', + menuid: '', + deviceid: '', + itemName: '', + itemType: '', + shuffle: false, + filters: [], + sortBy: '', + sortOrder: '', + item: 100, + category: '', + usedefaultvalues: true + }, + defaultValues: { + sourceid: '', + deviceid: '', + itemName: '', + itemType: '', + shuffle: false, + filters: [], + sortBy: '', + sortOrder: '', + limit: 100, + category: '' + }, + properties: { + movieName: null, + devicename: null, + songName: null, + artistName: null, + albumName: null, + serieName: null, + seasonName: null, + pictureName: null, + authorname: null, + }, + command: null, + text: text, + userId: Dashboard.getCurrentUserId(), + success: false + }; + + var isvalid = false; + + commandgroups.map(function (group) { + if (isvalid) + return; + + if (group.defaultValues && group.defaultValues.length > 0) { + group.defaultValues.map(function (item) { + + }); + } + + if (group.items && group.items.length > 0) { + + group.items.map(function (item) { + + var regex = NamedRegExp(item.command, text); + if (!regex && item.altcommand) + regex = NamedRegExp(item.altcommand, text) + + if (regex && regex.length > 0) { + + //Group data + if (group.groupid) result.groupid = group.groupid; + if (group.name) result.name = group.name; + if (group.defaultValues) { + result.defaultValues.sourceid = group.defaultValues.sourceid || result.defaultValues.sourceid; + result.defaultValues.deviceid = group.defaultValues.deviceid || result.defaultValues.deviceid; + result.defaultValues.itemName = group.defaultValues.itemName || result.defaultValues.itemName; + result.defaultValues.itemType = group.defaultValues.itemType || result.defaultValues.itemType; + result.defaultValues.shuffle = group.defaultValues.shuffle || result.defaultValues.shuffle; + result.defaultValues.filters = group.defaultValues.filters || result.defaultValues.filters; + result.defaultValues.sortBy = group.defaultValues.sortBy || result.defaultValues.sortBy; + result.defaultValues.sortOrder = group.defaultValues.sortOrder || result.defaultValues.sortOrder; + result.defaultValues.limit = group.defaultValues.limit || result.defaultValues.limit; + result.defaultValues.category = group.defaultValues.category || result.defaultValues.category; + } + + + + if (group.name) result.name = group.name; + + //Item data + var usegroupDefault = checkItemProperty(item.usedefaultvalues, result.item.usedefaultvalues); + + result.item.usedefaultvalues = usegroupDefault; + result.item.actionid = checkItemProperty(item.actionid, result.item.actionid); + result.item.groupid = checkItemProperty(item.groupid, result.item.groupid); + result.item.menuid = checkItemProperty(item.menuid, result.item.menuid); + result.item.sourceid = checkItemProperty(item.sourceid, result.item.sourceid, usegroupDefault, result.defaultValues.sourceid); + result.item.deviceid = checkItemProperty(item.deviceid, result.item.deviceid, usegroupDefault, result.defaultValues.deviceid); + result.item.itemName = checkItemProperty(item.itemName, result.item.itemName, usegroupDefault, result.defaultValues.itemName); + result.item.itemType = checkItemProperty(item.itemType, result.item.itemType, usegroupDefault, result.defaultValues.itemType); + result.item.shuffle = checkItemProperty(item.shuffle, result.item.shuffle, usegroupDefault, result.defaultValues.shuffle); + result.item.filters = checkItemProperty(item.filters, result.item.filters, usegroupDefault, result.defaultValues.filters); + result.item.sortBy = checkItemProperty(item.sortBy, result.item.sortBy, usegroupDefault, result.defaultValues.sortBy); + result.item.sortOrder = checkItemProperty(item.sortOrder, result.item.sortOrder, usegroupDefault, result.defaultValues.sortOrder); + result.item.limit = checkItemProperty(item.limit, result.item.limit, usegroupDefault, result.defaultValues.limit); + + result.command = item; + + regex.map(function (regresult) { + + switch (regresult.name) { + case 'moviename': + result.properties.movieName = regresult.value; + break; + case 'devicename': + result.properties.devicename = regresult.value; + break; + case 'songname': + result.properties.songName = regresult.value; + break; + case 'artistname': + result.properties.artistName = regresult.value; + break; + case 'albumname': + result.properties.albumName = regresult.value; + break; + case 'seriename': + result.properties.serieName = regresult.value; + break; + case 'seasonname': + result.properties.seasonName = regresult.value; + break; + case 'picturename': + result.properties.pictureName = regresult.value; + break; + case 'authorname': + result.properties.authorname = regresult.value; + break; + } + + if (result.text) + result.text = result.text.replace(regresult.value, '').trim(); + }); + + isvalid = true; + } + + if (isvalid) + return; + }); + } + }); + + return result; + } + + /// Gets RegEx property. + /// The actions. + /// The property. + /// The RegEx property. + function getRegExProperty(actions, property) { + var idx = -1; + idx = actions.map(function (e) { return e.name; }).indexOf(name); + + if (idx > -1) + return actions[idx]; + else + return null; + } + + /// Check item property. + /// The property. + /// The item default value. + /// The use group default value. + /// The group default value. + /// . + function checkItemProperty(property, itemDefaultValue, useGroupDefaultValue, groupDefaultValue) { + if (property) + return property; + + if (useGroupDefaultValue && groupDefaultValue) + return groupDefaultValue; + + return itemDefaultValue; + } + + return function (commandgroups, text) { + + if (commandgroups) { + var result = processcommands(text, commandgroups); + console.log(text); + console.log(commandgroups); + } + + return result; + } +}); diff --git a/dashboard-ui/voice/textprocessor-en-us.js b/dashboard-ui/voice/textprocessor-en-us.js deleted file mode 100644 index 9ae3e88cea..0000000000 --- a/dashboard-ui/voice/textprocessor-en-us.js +++ /dev/null @@ -1,244 +0,0 @@ -define([], function () { - - function parseTextInternal(text) { - - var result = { - action: '', - itemName: '', - itemType: '', - category: '', - filters: [], - removeWords: [], - sortby: '', - sortorder: 'Ascending', - limit: null, - userId: Dashboard.getCurrentUserId() - }; - - var textLower = text.toLowerCase(); - var words = text.toLowerCase().split(' '); - - var displayWords = [ - 'show', - 'pull up', - 'display', - 'go to', - 'view' - ]; - - if (displayWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - if (words.indexOf('guide') != -1) { - result.action = 'show'; - result.category = 'tvguide'; - } - - if (words.indexOf('recordings') != -1) { - result.action = 'show'; - result.category = 'recordings'; - } - - result.removeWords = displayWords; - return result; - } - - var searchWords = [ - 'search', - 'search for', - 'find', - 'query' - ]; - - if (searchWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Search - result.action = 'search'; - - result.removeWords = searchWords; - return result; - } - - var playWords = [ - 'play', - 'watch' - ]; - - if (playWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Play - result.action = 'play'; - - result.removeWords = playWords; - return result; - } - - var controlWords = [ - 'use', - 'control' - ]; - - if (controlWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Play - result.action = 'control'; - - result.removeWords = controlWords; - return result; - } - - var enableWords = [ - 'enable', - 'turn on' - ]; - - if (enableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Play - result.action = 'enable'; - - result.removeWords = enableWords; - return result; - } - - var disableWords = [ - 'disable', - 'turn off' - ]; - - if (disableWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Play - result.action = 'disable'; - - result.removeWords = disableWords; - return result; - } - - var toggleWords = [ - 'toggle' - ]; - - if (toggleWords.filter(function (w) { return textLower.indexOf(w) == 0; }).length) { - - // Play - result.action = 'toggle'; - - result.removeWords = toggleWords; - return result; - } - - if (words.indexOf('shuffle') != -1) { - - // Play - result.action = 'shuffle'; - - result.removeWords.push('shuffle'); - return result; - } - - if (words.indexOf('record') != -1) { - - // Record - result.action = 'record'; - - result.removeWords.push('record'); - return result; - } - - if (words.indexOf('guide') != -1) { - result.action = 'show'; - result.category = 'tvguide'; - return result; - } - - return result; - } - - function parseContext(text, result) { - - text = text.toLowerCase(); - - var i, length; - - for (i = 0, length = result.removeWords.length; i < length; i++) { - - text = text.replace(result.removeWords[i], ''); - } - - text = text.trim(); - - var removeAtStart = [ - 'my' - ]; - - for (i = 0, length = removeAtStart.length; i < length; i++) { - - if (text.indexOf(removeAtStart[i]) == 0) { - text = text.substring(removeAtStart[i].length); - } - } - - result.what = text; - - text = text.trim(); - var words = text.toLowerCase().split(' '); - - if (words.indexOf('favorite') != -1) { - result.filters.push('favorite'); - } - - if (text.indexOf('latest movies') != -1 || text.indexOf('latest films') != -1) { - - result.sortby = 'datecreated'; - result.sortorder = 'Descending'; - result.filters.push('unplayed'); - result.itemType = 'Movie'; - - return; - } - - if (text.indexOf('latest episodes') != -1) { - - result.sortby = 'datecreated'; - result.sortorder = 'Descending'; - result.filters.push('unplayed'); - result.itemType = 'Episode'; - - return; - } - - if (text.indexOf('next up') != -1) { - - result.category = 'nextup'; - - return; - } - - if (text.indexOf('movies') != -1 || text.indexOf('films') != -1) { - - result.itemType = 'Movie'; - - return; - } - - if (text.indexOf('shows') != -1 || text.indexOf('series') != -1) { - - result.itemType = 'Series'; - - return; - } - - if (text.indexOf('songs') != -1) { - - result.itemType = 'Audio'; - - return; - } - } - - return function (text) { - var result = parseTextInternal(text); - parseContext(text, result); - return result; - } -}); \ No newline at end of file diff --git a/dashboard-ui/voice/voice.js b/dashboard-ui/voice/voice.js index 990b388704..d8ea6c3c70 100644 --- a/dashboard-ui/voice/voice.js +++ b/dashboard-ui/voice/voice.js @@ -1,7 +1,16 @@ -define(['paperdialoghelper'], function (paperDialogHelper) { +define(['paperdialoghelper'], function (paperDialogHelper) { var currentRecognition; + var lang = 'grammar'; + //var lang = 'en-US'; + var commandgroups = getGrammarCommands(lang); + + + + /// Shuffle array. + /// The array. + /// array function shuffleArray(array) { var currentIndex = array.length, temporaryValue, randomIndex; @@ -21,201 +30,61 @@ return array; } - function getSampleCommands() { + /// Gets sample commands. + /// The sample commands. + function getSampleCommands(groupid) { return new Promise(function (resolve, reject) { + groupid = typeof (groupid) !== 'undefined' ? groupid : ''; + var commands = []; + commandgroups.map(function (group) { + if ((group.items && group.items.length > 0) && (groupid == group.groupid || groupid == '')) { - //commands.push('show my movies'); - //commands.push('pull up my tv shows'); + group.items.map(function (item) { - commands.push('play my latest episodes'); - commands.push('play next up'); - commands.push('shuffle my favorite songs'); + if (item.commandtemplates && item.commandtemplates.length > 0) { - commands.push('show my tv guide'); - commands.push('pull up my recordings'); - commands.push('control chromecast'); - commands.push('control [device name]'); - commands.push('turn on display mirroring'); - commands.push('turn off display mirroring'); - commands.push('toggle display mirroring'); + item.commandtemplates.map(function (templates) { + commands.push(templates); + }); + } + + }); + } + }); resolve(shuffleArray(commands)); - }); - } - function processText(text) { - - return new Promise(function (resolve, reject) { - - require(['voice/textprocessor-en-us.js'], function (parseText) { - - var result = parseText(text); - - switch (result.action) { - - case 'show': - showCommand(result); - break; - case 'play': - playCommand(result); - break; - case 'shuffle': - playCommand(result, true); - break; - case 'search': - playCommand(result); - break; - case 'control': - controlCommand(result); - break; - case 'enable': - enableCommand(result); - break; - case 'disable': - disableCommand(result); - break; - case 'toggle': - toggleCommand(result); - break; - default: - reject(); - return; - } - - var dlg = currentDialog; - if (dlg) { - paperDialogHelper.close(dlg); - } - - resolve(); - }); - }); - } - - function showCommand(result) { - - if (result.category == 'tvguide') { - Dashboard.navigate('livetv.html?tab=1'); - return; - } - - if (result.category == 'recordings') { - Dashboard.navigate('livetv.html?tab=3'); - return; - } - } - - function enableCommand(result) { - - var what = result.what.toLowerCase(); - - if (what.indexOf('mirror') != -1) { - MediaController.enableDisplayMirroring(true); - } - } - - function disableCommand(result) { - - var what = result.what.toLowerCase(); - - if (what.indexOf('mirror') != -1) { - MediaController.enableDisplayMirroring(false); - } - } - - function toggleCommand(result) { - - var what = result.what.toLowerCase(); - - if (what.indexOf('mirror') != -1) { - MediaController.toggleDisplayMirroring(); - } - } - - function controlCommand(result) { - - MediaController.trySetActiveDeviceName(result.what); - } - - function playCommand(result, shuffle) { - - var query = { - - Limit: result.limit || 100, - UserId: result.userId, - ExcludeLocationTypes: "Virtual" - }; - - if (result.category == 'nextup') { - - ApiClient.getNextUpEpisodes(query).then(function (queryResult) { - - playItems(queryResult.Items, shuffle); - - }); - return; - } - - if (shuffle) { - result.sortby = result.sortby ? 'Random,' + result.sortby : 'Random'; - } - - query.SortBy = result.sortby; - query.SortOrder = result.sortorder; - query.Recursive = true; - - if (result.filters.indexOf('unplayed') != -1) { - query.IsPlayed = false; - } - if (result.filters.indexOf('played') != -1) { - query.IsPlayed = true; - } - if (result.filters.indexOf('favorite') != -1) { - query.Filters = 'IsFavorite'; - } - - if (result.itemType) { - query.IncludeItemTypes = result.itemType; - } - - ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (queryResult) { - - playItems(queryResult.Items, shuffle); - }); - } - - function playItems(items, shuffle) { - - if (shuffle) { - items = shuffleArray(items); - } - - items = items.map(function (i) { - return i.Id; }); - if (items.length) { - MediaController.play({ - ids: items - }); - } else { - Dashboard.alert({ - message: Globalize.translate('MessageNoItemsFound') - }); + } + + /// Gets command group. + /// The groupid. + /// The command group. + function getCommandGroup(groupid) { + if (commandgroups) { + var idx = -1; + idx = commandgroups.map(function (e) { return e.groupid; }).indexOf(groupid); + + if (idx > -1) + return commandgroups[idx]; + else + return null; } + else + return null; } - function searchCommand(result) { - - - } - + /// Renders the sample commands. + /// The element. + /// The commands. + /// . function renderSampleCommands(elem, commands) { - commands.length = Math.min(commands.length, 4); + commands.length = Math.min(commands.length, 6); commands = commands.map(function (c) { @@ -227,7 +96,9 @@ } var currentDialog; - function showVoiceHelp() { + /// Shows the voice help. + /// . + function showVoiceHelp(groupid, title) { var dlg = paperDialogHelper.createDialog({ size: 'medium', @@ -239,12 +110,17 @@ var html = ''; html += '

'; - html += ''; + html += ''; + if (groupid) { + var grp = getCommandGroup(groupid); + if (grp) + html += ' ' + grp.name; + } html += '

'; html += '
'; - var getCommandsPromise = getSampleCommands(); + var getCommandsPromise = getSampleCommands(groupid); html += '
'; @@ -302,45 +178,75 @@ }); } - function showUnrecognizedCommandHelp() { + /// Hides the voice help. + /// . + function hideVoiceHelp() { + $('.voiceInputHelp').remove(); + } + + /// Shows the unrecognized command help. + /// . + function showUnrecognizedCommandHelp() { + //speak("I don't understend this command"); $('.unrecognizedCommand').show(); $('.defaultVoiceHelp').hide(); } - function destroyCurrentRecognition() { - - var recognition = currentRecognition; - if (recognition) { - recognition.cancelled = true; - recognition.abort(); - currentRecognition = null; - } - } - + /// Process the transcript described by text. + /// The text. + /// . function processTranscript(text, isCancelled) { $('.voiceInputText').html(text); if (text || AppInfo.isNativeApp) { $('.blockedMessage').hide(); - } else { + } + else { $('.blockedMessage').show(); } if (text) { - processText(text).catch(showUnrecognizedCommandHelp); - } else if (!isCancelled) { + require(['voice/voicecommands.js', 'voice/grammarprocessor.js'], function (voicecommands, grammarprocessor) { + + var processor = grammarprocessor(commandgroups, text); + if (processor && processor.command) { + voicecommands(processor) + .then(function (result) { + if (result.item.actionid === 'show' && result.item.sourceid === 'group') { + var dlg = currentDialog; + if (dlg) + showCommands(false, result) + else + showCommands(true, result) + } + }) + .catch(showUnrecognizedCommandHelp); + } + else + showUnrecognizedCommandHelp(); + + var dlg = currentDialog; + if (dlg) { + PaperDialogHelper.close(dlg); + } + }); + + } + else if (!isCancelled) { showUnrecognizedCommandHelp(); } } + /// Starts listening internal. + /// . function startListening(createUI) { destroyCurrentRecognition(); - - var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); - + var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition)(); + recognition.lang = lang; + var groupid = ''; //recognition.continuous = true; //recognition.interimResults = true; @@ -360,25 +266,114 @@ recognition.start(); currentRecognition = recognition; + showCommands(createUI); + } - if (createUI !== false) { - require(['paper-fab', 'css!voice/voice.css'], showVoiceHelp); + /// Destroys the current recognition. + /// . + function destroyCurrentRecognition() { + + var recognition = currentRecognition; + if (recognition) { + recognition.abort(); + currentRecognition = null; } } + /// Cancel listener. + /// . + function cancelListener() { + + destroyCurrentRecognition(); + hideVoiceHelp(); + } + + /// Shows the commands. + /// The create user interface. + /// . + function showCommands(createUI, result) { + if (createUI !== false) { + //speak('Hello, what can I do for you?'); + require(['paper-fab', 'css!voice/voice.css'], function () { + if (result) + showVoiceHelp(result.groupid, result.name); + else + showVoiceHelp(); + }); + } + } + + /// Getgrammars the given language. + /// The language. + /// . + function getGrammarCommands(language) { + + var file = "grammar"; + if (language && language.length > 0) + file = language; + + var grammar = (function () { + var grm = null; + $.ajax({ + async: false, + global: false, + url: "voice/grammar/" + file + ".json", + dataType: "json", + success: function (data) { + grm = data; + } + }); + return grm; + })(); + return grammar; + } + + /// Speaks the given text. + /// The text. + /// . + function speak(text) { + + if (!SpeechSynthesisUtterance) { + console.log('API not supported'); + } + + var utterance = new SpeechSynthesisUtterance(text); + utterance.lang = lang; + utterance.rate = 0.9; + utterance.pitch = 1; + utterance.addEventListener('end', function () { + console.log('Synthesizing completed'); + }); + + utterance.addEventListener('error', function (event) { + console.log('Synthesizing error'); + }); + + console.log('Synthesizing the text: ' + text); + speechSynthesis.speak(utterance); + } + + + /// An enum constant representing the window. voice input manager option. window.VoiceInputManager = { isSupported: function () { if (AppInfo.isNativeApp) { - // TODO: Only return false for crosswalk - return false; + // Crashes on some amazon devices + if (window.device && (device.platform || '').toLowerCase().indexOf('amazon') != -1) { + return false; + } } - return window.SpeechRecognition || window.webkitSpeechRecognition; + return window.SpeechRecognition || + window.webkitSpeechRecognition || + window.mozSpeechRecognition || + window.oSpeechRecognition || + window.msSpeechRecognition; }, - startListening: startListening + startListening: startListening, }; }); \ No newline at end of file diff --git a/dashboard-ui/voice/voicecommands.js b/dashboard-ui/voice/voicecommands.js new file mode 100644 index 0000000000..00bf8133cc --- /dev/null +++ b/dashboard-ui/voice/voicecommands.js @@ -0,0 +1,61 @@ +// 09.10.2015 +// voicecommands class +define([], function () { + + + /// Process the command. + /// Full pathname of the command file. + /// The result. + /// . + function processCommand(commandPath, result) { + + return new Promise(function (resolve, reject) { + + require([commandPath], function (command) { + command(result); + if (result.success) { + resolve(result); + } + reject(); + }); + }); + + } + + return function (result) { + + return new Promise(function (resolve, reject) { + + switch (result.item.actionid) { + + case 'show': + processCommand('voice/commands/showcommands.js', result).then(function (result) { resolve(result); }); + break; + case 'play': + processCommand('voice/commands/playcommands.js', result).then(function (result) { resolve(result); }); + break; + case 'shuffle': + processCommand('voice/commands/playcommands.js', result).then(function (result) { resolve(result); }); + break; + case 'search': + processCommand('voice/commands/searchcommands.js', result).then(function (result) { resolve(result); }); + break; + case 'control': + processCommand('voice/commands/controlcommands.js', result).then(function (result) { resolve(result); }); + break; + case 'enable': + processCommand('voice/commands/enablecommands.js', result).then(function (result) { resolve(result); }); + break; + case 'disable': + processCommand('voice/commands/disablecommands.js', result).then(function (result) { resolve(result); }); + break; + case 'toggle': + processCommand('voice/commands/togglecommands.js', result).then(function (result) { resolve(result); }); + break; + default: + reject(); + return; + } + }); + } +}); \ No newline at end of file