diff --git a/dashboard-ui/scripts/librarymenu.js b/dashboard-ui/scripts/librarymenu.js index 185bddba79..fe7661e666 100644 --- a/dashboard-ui/scripts/librarymenu.js +++ b/dashboard-ui/scripts/librarymenu.js @@ -165,7 +165,7 @@ function showVoice() { require(['voice/voice'], function (voice) { - voice.startListening(); + voice.showDialog(); }); } diff --git a/dashboard-ui/voice/commands/controlcommands.js b/dashboard-ui/voice/commands/controlcommands.js index 46d27418f0..621bb6fde3 100644 --- a/dashboard-ui/voice/commands/controlcommands.js +++ b/dashboard-ui/voice/commands/controlcommands.js @@ -1,5 +1,4 @@ - -define([], function () { +define([], function () { return function (result) { result.success = true; diff --git a/dashboard-ui/voice/commands/disablecommands.js b/dashboard-ui/voice/commands/disablecommands.js index 6338fecd5f..b6cbd120be 100644 --- a/dashboard-ui/voice/commands/disablecommands.js +++ b/dashboard-ui/voice/commands/disablecommands.js @@ -1,5 +1,4 @@ - -define([], function () { +define([], function () { return function (result) { result.success = true; diff --git a/dashboard-ui/voice/commands/enablecommands.js b/dashboard-ui/voice/commands/enablecommands.js index e790731f48..5fc5eff211 100644 --- a/dashboard-ui/voice/commands/enablecommands.js +++ b/dashboard-ui/voice/commands/enablecommands.js @@ -1,5 +1,4 @@ - -define([], function () { +define([], function () { return function (result) { result.success = true; diff --git a/dashboard-ui/voice/commands/searchcommands.js b/dashboard-ui/voice/commands/searchcommands.js index 6338fecd5f..59ed4d3ea9 100644 --- a/dashboard-ui/voice/commands/searchcommands.js +++ b/dashboard-ui/voice/commands/searchcommands.js @@ -1,12 +1,7 @@ - -define([], function () { +define([], function () { return function (result) { - result.success = true; switch (result.item.deviceid) { - case 'displaymirroring': - MediaController.enableDisplayMirroring(false); - break; default: result.success = false; return; diff --git a/dashboard-ui/voice/commands/showcommands.js b/dashboard-ui/voice/commands/showcommands.js index 81850a657d..ebfb7dd5f9 100644 --- a/dashboard-ui/voice/commands/showcommands.js +++ b/dashboard-ui/voice/commands/showcommands.js @@ -1,5 +1,4 @@ - -define([], function () { +define([], function () { return function (result) { result.success = true; diff --git a/dashboard-ui/voice/commands/togglecommands.js b/dashboard-ui/voice/commands/togglecommands.js index 526668229c..1bc588d771 100644 --- a/dashboard-ui/voice/commands/togglecommands.js +++ b/dashboard-ui/voice/commands/togglecommands.js @@ -1,5 +1,4 @@ - -define([], function () { +define([], function () { return function (result) { result.success = true; @@ -12,5 +11,4 @@ define([], function () { 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 index 7c3e4f07e9..5c79188a01 100644 --- a/dashboard-ui/voice/grammar/en-US.json +++ b/dashboard-ui/voice/grammar/en-US.json @@ -139,7 +139,7 @@ "commandtemplates": [ "Play music", "Play my music", - "Listen to my music " + "Listen to my music" ] }, { @@ -188,7 +188,7 @@ "commandtemplates": [ "Play music", "Play my music", - "Listen to my music " + "Listen to my music" ] }, { @@ -200,7 +200,7 @@ "commandtemplates": [ "Play music", "Play my music", - "Listen to my music ", + "Listen to my music", "shuffle my favorite songs" ] } diff --git a/dashboard-ui/voice/voice.css b/dashboard-ui/voice/voice.css index 307b3e0036..a155574a86 100644 --- a/dashboard-ui/voice/voice.css +++ b/dashboard-ui/voice/voice.css @@ -1,5 +1,5 @@ .voiceHelpContent { - max-width: 600px; + max-width: 700px; margin: auto; } diff --git a/dashboard-ui/voice/voice.js b/dashboard-ui/voice/voice.js index b48664f3c1..6d38fc2684 100644 --- a/dashboard-ui/voice/voice.js +++ b/dashboard-ui/voice/voice.js @@ -18,9 +18,9 @@ define([], function () { window.msSpeechRecognition; }, - startListening: function () { + showDialog: function () { require(['voice/voicedialog'], function (voicedialog) { - voicedialog.startListening(); + voicedialog.showDialog(); }); } }; diff --git a/dashboard-ui/voice/voicedialog.js b/dashboard-ui/voice/voicedialog.js index 64445c2a35..308b8efbfe 100644 --- a/dashboard-ui/voice/voicedialog.js +++ b/dashboard-ui/voice/voicedialog.js @@ -1,37 +1,7 @@ -define(['dialogHelper', 'jQuery', 'emby-button'], function (dialogHelper, $) { +define(['dialogHelper', 'voice/voicereceiver', 'voice/voiceprocessor', 'globalize', 'emby-button', 'css!./voice.css', 'material-icons'], function (dialogHelper, voicereceiver, voiceprocessor, globalize) { - var currentRecognition; var lang = 'en-US'; - var commandgroups; - - function getCommandGroups() { - - if (commandgroups) { - return Promise.resolve(commandgroups); - } - - return new Promise(function (resolve, reject) { - - var file = "grammar"; - //if (language && language.length > 0) - // file = language; - - var xhr = new XMLHttpRequest(); - xhr.open('GET', "voice/grammar/" + file + ".json", true); - - xhr.onload = function (e) { - - commandgroups = JSON.parse(this.response); - resolve(commandgroups); - } - - xhr.onerror = reject; - - xhr.send(); - }); - } - /// Shuffle array. /// The array. /// array @@ -58,7 +28,7 @@ define(['dialogHelper', 'jQuery', 'emby-button'], function (dialogHelper, $) { /// The sample commands. function getSampleCommands(groupid) { - return getCommandGroups().then(function (commandGroups) { + return voiceprocessor.getCommandGroups().then(function (commandGroups) { groupid = typeof (groupid) !== 'undefined' ? groupid : ''; var commands = []; @@ -86,17 +56,20 @@ define(['dialogHelper', 'jQuery', 'emby-button'], function (dialogHelper, $) { /// The groupid. /// The command group. function getCommandGroup(groupid) { - if (commandgroups) { - var idx = -1; - idx = commandgroups.map(function (e) { return e.groupid; }).indexOf(groupid); + return voicereceiver.getCommandGroups() + .then(function (commandgroups) { + if (commandgroups) { + var idx = -1; - if (idx > -1) - return commandgroups[idx]; - else - return null; - } - else - return null; + idx = commandgroups.map(function (e) { return e.groupid; }).indexOf(groupid); + + if (idx > -1) + return commandgroups[idx]; + else + return null; + } else + return null; + }); } /// Renders the sample commands. @@ -113,7 +86,7 @@ define(['dialogHelper', 'jQuery', 'emby-button'], function (dialogHelper, $) { }).join(''); - $('.exampleCommands', elem).html(commands); + elem.querySelector('.exampleCommands').innerHTML = commands; } var currentDialog; @@ -121,212 +94,179 @@ define(['dialogHelper', 'jQuery', 'emby-button'], function (dialogHelper, $) { /// . function showVoiceHelp(groupid, title) { - var dlg = dialogHelper.createDialog({ - size: 'medium', - removeOnClose: true - }); + console.log("Showing Voice Help", groupid, title); - dlg.classList.add('ui-body-b'); - dlg.classList.add('background-theme-b'); + var isNewDialog = false; + var dlg; - var html = ''; - html += '

'; - html += ''; - if (groupid) { - var grp = getCommandGroup(groupid); - if (grp) - html += ' ' + grp.name; + if (!currentDialog) { + + isNewDialog = true; + + dlg = dialogHelper.createDialog({ + size: 'medium', + removeOnClose: true + }); + + dlg.classList.add('ui-body-b'); + dlg.classList.add('background-theme-b'); + + var html = ''; + html += '
'; + html += ''; + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + + html += '

' + globalize.translate('HeaderSaySomethingLike') + '

'; + + html += '
'; + html += '
'; + + // defaultVoiceHelp + html += '
'; + + html += '
'; + html += '

' + globalize.translate('HeaderYouSaid') + '

'; + html += + '

'; + html += '

' + globalize.translate('MessageWeDidntRecognizeCommand') + '

'; + + html += '
'; + html += ''; + html += '

' + + globalize.translate('MessageIfYouBlockedVoice') + + '

'; + + html += '
'; + + html += + ''; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + + dlg.innerHTML = html; + document.body.appendChild(dlg); + + dialogHelper.open(dlg); + currentDialog = dlg; + + dlg.addEventListener('close', function () { + currentDialog = null; + }); + + function onCancelClick() { + voicereceiver.cancel(); + dialogHelper.close(dlg); + } + + var closeButtons = dlg.querySelectorAll('.btnCancelVoiceInput'); + for (var i = 0, length = closeButtons.length; i < length; i++) { + closeButtons[i].addEventListener('click', onCancelClick); + } + + dlg.querySelector('.btnRetry').addEventListener('click', function () { + dlg.querySelector('.unrecognizedCommand').classList.add('hide'); + dlg.querySelector('.defaultVoiceHelp').classList.remove('hide'); + listen(); + }); } - html += '

'; - html += '
'; + dlg = currentDialog; - var getCommandsPromise = getSampleCommands(groupid); + if (groupid) { + getCommandGroup(groupid) + .then( + function (grp) { + dlg.querySelector('#voiceDialogGroupName').innerText = ' ' + grp.name; + }); - html += '
'; - html += '
'; + getSampleCommands(groupid) + .then(function (commands) { + renderSampleCommands(currentDialog, commands); + listen(); + }) + .catch(function (e) { console.log("Error", e); }); + } else if (isNewDialog) { + getSampleCommands() + .then(function (commands) { + renderSampleCommands(currentDialog, commands); + }); - html += '

' + Globalize.translate('HeaderSaySomethingLike') + '

'; - - html += '
'; - html += '
'; - - // defaultVoiceHelp - html += '
'; - - html += ''; - - html += ''; - - // voiceHelpContent - html += '
'; - - html += '
'; - - dlg.innerHTML = html; - document.body.appendChild(dlg); - - dialogHelper.open(dlg); - currentDialog = dlg; - - dlg.addEventListener('close', function () { - currentDialog = null; - }); - - $('.btnCancelVoiceInput', dlg).on('click', function () { - destroyCurrentRecognition(); - dialogHelper.close(dlg); - }); - - $('.btnRetry', dlg).on('click', function () { - $('.unrecognizedCommand').hide(); - $('.defaultVoiceHelp').show(); - startListening(false); - }); - - getCommandsPromise.then(function (commands) { - renderSampleCommands(dlg.querySelector('.voiceHelpContent'), commands); - }); + } } - - /// Hides the voice help. - /// . - function hideVoiceHelp() { - - $('.voiceInputHelp').remove(); + function processInput(input) { + return voiceprocessor.processTranscript(input); } /// Shows the unrecognized command help. /// . - function showUnrecognizedCommandHelp() { + function showUnrecognizedCommandHelp(command) { //speak("I don't understend this command"); - $('.unrecognizedCommand').show(); - $('.defaultVoiceHelp').hide(); - } - - /// Process the transcript described by text. - /// The text. - /// . - function processTranscript(text, isCancelled) { - - $('.voiceInputText').html(text); - - if (text || AppInfo.isNativeApp) { - $('.blockedMessage').hide(); - } - else { - $('.blockedMessage').show(); - } - - if (text) { - 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) { - dialogHelper.close(dlg); - } - }); - - } - else if (!isCancelled) { - showUnrecognizedCommandHelp(); - } - } - - /// Starts listening internal. - /// . - function startListening(createUI) { - - destroyCurrentRecognition(); - var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition)(); - recognition.lang = lang; - var groupid = ''; - //recognition.continuous = true; - //recognition.interimResults = true; - - recognition.onresult = function (event) { - if (event.results.length > 0) { - processTranscript(event.results[0][0].transcript || ''); - } - }; - - recognition.onerror = function () { - processTranscript('', recognition.cancelled); - }; - - recognition.onnomatch = function () { - processTranscript('', recognition.cancelled); - }; - - recognition.start(); - currentRecognition = recognition; - showCommands(createUI); - } - - /// Destroys the current recognition. - /// . - function destroyCurrentRecognition() { - - var recognition = currentRecognition; - if (recognition) { - recognition.abort(); - currentRecognition = null; - } - } - - /// Cancel listener. - /// . - function cancelListener() { - - destroyCurrentRecognition(); - hideVoiceHelp(); + if (command) + currentDialog.querySelector('.voiceInputText').innerText = command; + currentDialog.querySelector('.unrecognizedCommand').classList.remove('hide'); + currentDialog.querySelector('.defaultVoiceHelp').classList.add('hide'); } /// 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(); - }); + function showCommands(result) { + //speak('Hello, what can I do for you?'); + if (result) + showVoiceHelp(result.groupid, result.name); + else + showVoiceHelp(); + } + + function resetDialog() { + if (currentDialog) { + currentDialog.querySelector('.unrecognizedCommand').classList.add('hide'); + currentDialog.querySelector('.defaultVoiceHelp').classList.remove('hide'); } } + function showDialog() { + resetDialog(); + showCommands(); + listen(); + } + function listen() { + voicereceiver.listenForCommand(lang || "en-US").then(processInput).then(function (data) { + + closeDialog(); + + }, function (result) { + if (result.error == 'group') { + showVoiceHelp(result.item.groupid, result.groupName); + return; + } + showUnrecognizedCommandHelp(result.text || ''); + }); + } + function closeDialog() { + dialogHelper.close(currentDialog); + voicereceiver.cancel(); + } /// An enum constant representing the window. voice input manager option. return { - startListening: startListening + showDialog: showDialog }; }); \ No newline at end of file diff --git a/dashboard-ui/voice/voiceprocessor.js b/dashboard-ui/voice/voiceprocessor.js new file mode 100644 index 0000000000..5570ab2b8c --- /dev/null +++ b/dashboard-ui/voice/voiceprocessor.js @@ -0,0 +1,65 @@ +define(['voice/voicecommands.js', 'voice/grammarprocessor.js'], function (voicecommands, grammarprocessor) { + + var commandgroups; + + function getCommandGroups() { + + if (commandgroups) { + return Promise.resolve(commandgroups); + } + + return new Promise(function (resolve, reject) { + + var file = "grammar"; + //if (language && language.length > 0) + // file = language; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', "voice/grammar/" + file + ".json", true); + + xhr.onload = function (e) { + + commandgroups = JSON.parse(this.response); + resolve(commandgroups); + } + + xhr.onerror = reject; + + xhr.send(); + }); + } + /// Process the transcript described by text. + /// The text. + /// . + function processTranscript(text) { + if (text) { + var processor = grammarprocessor(commandgroups, text); + if (processor && processor.command) { + console.log("Command from Grammar Processor", processor); + return voicecommands(processor) + .then(function (result) { + console.log("Result of executed command", result); + if (result.item.actionid === 'show' && result.item.sourceid === 'group') { + return Promise.reject({ error: "group", item: result.item, groupName: result.name }); + } else { + return Promise.resolve({ item: result.item }); + } + }, function () { + return Promise.reject({ error: "unrecognized-command", text: text }); + }); + } else { + return Promise.reject({ error: "unrecognized-command", text: text }); + } + + } else { + return Promise.reject({ error: "empty" }); + } + } + + /// An enum constant representing the window. voice input manager option. + return { + processTranscript: processTranscript, + getCommandGroups: getCommandGroups + }; + +}); \ No newline at end of file diff --git a/dashboard-ui/voice/voicereceiver.js b/dashboard-ui/voice/voicereceiver.js new file mode 100644 index 0000000000..d25bd115fb --- /dev/null +++ b/dashboard-ui/voice/voicereceiver.js @@ -0,0 +1,57 @@ +define([], function () { + var currentRecognition = null; + + + /// Starts listening for voice commands + /// . + function listenForCommand(lang) { + return new Promise(function (resolve, reject) { + cancelListener(); + + var recognition = new (window.SpeechRecognition || + window.webkitSpeechRecognition || + window.mozSpeechRecognition || + window.oSpeechRecognition || + window.msSpeechRecognition)(); + recognition.lang = lang; + + recognition.onresult = function (event) { + console.log(event); + if (event.results.length > 0) { + var resultInput = event.results[0][0].transcript || ''; + resolve(resultInput); + } + }; + + recognition.onerror = function () { + reject({ error: event.error, message: event.message }); + }; + + recognition.onnomatch = function () { + reject({ error: "no-match" }); + }; + currentRecognition = recognition; + + currentRecognition.start(); + }); + } + + + /// Cancel listener. + /// . + function cancelListener() { + + if (currentRecognition) { + currentRecognition.abort(); + currentRecognition = null; + } + + } + + /// An enum constant representing the window. voice input manager option. + return { + listenForCommand: listenForCommand, + cancel: cancelListener + }; + +}); \ No newline at end of file