-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FLUID-5936: fix bug in fluid.textToSpeech.checkTTSSupport, rewrite tests to use IoC testing #732
Changes from 43 commits
f3de9d5
8a0255f
a66947d
e85d86a
2dba2cf
03f3dd1
8be2d97
15b6cf6
a92d6ed
9143c35
53099b7
d6077c5
d05ac06
3f162b6
5ca02a8
13ba66a
5b08b2a
12d3b15
5f6809e
fca22cd
49bd622
d4e918e
ad41acf
6f8f445
09caf85
5a73622
5129f87
e35df8e
1e5af4d
7db392b
2accf4f
543b95d
71f116b
df09711
98a0330
00bedf1
e9cd164
71bc4ec
5b2c2da
75f3de3
f4d3152
e9e6069
265f3fe
839f735
fe384eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,12 @@ Licenses. | |
|
||
You may obtain a copy of the ECL 2.0 License and BSD License at | ||
https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt | ||
|
||
Includes code from Underscore.js 1.8.3 | ||
http://underscorejs.org | ||
(c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors | ||
Underscore may be freely distributed under the MIT license. | ||
|
||
*/ | ||
|
||
/* global speechSynthesis, SpeechSynthesisUtterance*/ | ||
|
@@ -45,15 +51,15 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
var toSpeak = new SpeechSynthesisUtterance(" "); // short text to attempt to speak | ||
toSpeak.volume = 0; // mutes the Speech Synthesizer | ||
var timeout = setTimeout(function () { | ||
speechSynthesis.cancel(); | ||
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel"); | ||
promise.reject(); | ||
}, delay || 1000); | ||
toSpeak.onstop = function () { | ||
toSpeak.onend = function () { | ||
clearTimeout(timeout); | ||
speechSynthesis.cancel(); | ||
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel"); | ||
promise.resolve(); | ||
}; | ||
speechSynthesis.speak(toSpeak); | ||
fluid.textToSpeech.deferredSpeechSynthesisControl("speak", toSpeak); | ||
} else { | ||
setTimeout(promise.reject, 0); | ||
} | ||
|
@@ -66,15 +72,19 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
events: { | ||
onStart: null, | ||
onStop: null, | ||
onPause: null, | ||
onResume: null, | ||
onError: null, | ||
onSpeechQueued: null | ||
}, | ||
members: { | ||
queue: [] | ||
queue: { | ||
texts: [] | ||
// out-of-function-scope storage for the currently spoken utterance | ||
// prevents issues with premature garbage collection in | ||
// some browsers | ||
// currentUtterance: | ||
} | ||
}, | ||
// Model paths: speaking, pending, paused, utteranceOpts | ||
// Model paths: speaking, pending, paused, utteranceOpts, pauseRequested, resumeRequested | ||
model: { | ||
// Changes to the utteranceOpts will only text that is queued after the change. | ||
// All of these options can be overriden in the queueSpeech method by passing in | ||
|
@@ -83,7 +93,7 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
utteranceOpts: { | ||
// text: "", // text to synthesize. avoid as it will override any other text passed in | ||
// lang: "", // the language of the synthesized text | ||
// voiceURI: "" // a uri pointing at a voice synthesizer to use. If not set, will use the default one provided by the browser | ||
// voice: {} // a WebSpeechSynthesis object; if not set, will use the default one provided by the browser | ||
// volume: 1, // a value between 0 and 1 | ||
// rate: 1, // a value from 0.1 to 10 although different synthesizers may have a smaller range | ||
// pitch: 1, // a value from 0 to 2 | ||
|
@@ -94,9 +104,13 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
listener: "fluid.textToSpeech.speak", | ||
args: ["{that}", "{change}.value"] | ||
}, | ||
"paused": { | ||
listener: "fluid.textToSpeech.pause", | ||
args: ["{that}", "{change}.value"] | ||
"pauseRequested": { | ||
listener: "fluid.textToSpeech.requestControl", | ||
args: ["{that}", "pause", "{change}"] | ||
}, | ||
"resumeRequested": { | ||
listener: "fluid.textToSpeech.requestControl", | ||
args: ["{that}", "resume", "{change}"] | ||
} | ||
}, | ||
invokers: { | ||
|
@@ -109,12 +123,12 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
args: ["{that}"] | ||
}, | ||
pause: { | ||
"this": "speechSynthesis", | ||
"method": "pause" | ||
changePath: "pauseRequested", | ||
value: true | ||
}, | ||
resume: { | ||
"this": "speechSynthesis", | ||
"method": "resume" | ||
changePath: "resumeRequested", | ||
value: true | ||
}, | ||
getVoices: { | ||
"this": "speechSynthesis", | ||
|
@@ -142,23 +156,81 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
} | ||
}); | ||
|
||
// Issue commands to the speechSynthesis interface with deferral (1 ms timeout); | ||
// this makes the wrapper behave better when issuing commands, especially | ||
// play and pause | ||
fluid.textToSpeech.deferredSpeechSynthesisControl = function (control, args) { | ||
setTimeout(function () { | ||
speechSynthesis[control](args); | ||
}, 1); | ||
}; | ||
|
||
// Throttle implementation adapted from underscore.js 1.8.3; see | ||
// file header for license details | ||
// Returns a version of a function that will only be called max once | ||
// every "wait" MS | ||
fluid.textToSpeech.throttle = function (func, wait, options) { | ||
var timeout, context, args, result; | ||
var previous = 0; | ||
if (!options) options = {}; | ||
|
||
var later = function() { | ||
previous = options.leading === false ? 0 : new Date().getTime(); | ||
timeout = null; | ||
result = func.apply(context, args); | ||
if (!timeout) context = args = null; | ||
}; | ||
|
||
var throttled = function() { | ||
var now = new Date().getTime(); | ||
if (!previous && options.leading === false) previous = now; | ||
var remaining = wait - (now - previous); | ||
context = this; | ||
args = arguments; | ||
if (remaining <= 0 || remaining > wait) { | ||
if (timeout) { | ||
clearTimeout(timeout); | ||
timeout = null; | ||
} | ||
previous = now; | ||
|
||
result = func.apply(context, args); | ||
if (!timeout) context = args = null; | ||
} else if (!timeout && options.trailing !== false) { | ||
timeout = setTimeout(later, remaining); | ||
} | ||
return result; | ||
}; | ||
|
||
throttled.cancel = function() { | ||
clearTimeout(timeout); | ||
previous = 0; | ||
timeout = context = args = null; | ||
}; | ||
return throttled; | ||
}; | ||
|
||
// Throttled version of deferred speech synthesis control | ||
fluid.textToSpeech.throttleControl = fluid.textToSpeech.throttle(fluid.textToSpeech.deferredSpeechSynthesisControl, 100, {leading: false}); | ||
|
||
fluid.textToSpeech.speak = function (that, speaking) { | ||
that.events[speaking ? "onStart" : "onStop"].fire(); | ||
}; | ||
|
||
fluid.textToSpeech.pause = function (that, paused) { | ||
if (paused) { | ||
that.events.onPause.fire(); | ||
} else if (that.model.speaking) { | ||
that.events.onResume.fire(); | ||
fluid.textToSpeech.requestControl = function (that, control, change) { | ||
// If there's a control request (value change to true), clear and | ||
// execute it | ||
if(change.value) { | ||
that.applier.change(change.path, false); | ||
fluid.textToSpeech.throttleControl(control); | ||
} | ||
}; | ||
|
||
fluid.textToSpeech.handleStart = function (that) { | ||
that.queue.shift(); | ||
that.queue.texts.shift(); | ||
that.applier.change("speaking", true); | ||
|
||
if (that.queue.length) { | ||
if (that.queue.texts.length) { | ||
that.applier.change("pending", true); | ||
} | ||
}; | ||
|
@@ -170,7 +242,11 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
paused: false | ||
}; | ||
|
||
if (!that.queue.length) { | ||
if (that.queue.currentUtterance) { | ||
that.queue.currentUtterance = undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually meant to put it in the queue item, rather than to rebase the entire queue structure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree this is better; I had to refactor the queue behaviour a little so |
||
} | ||
|
||
if (!that.queue.texts.length) { | ||
var newModel = $.extend({}, that.model, resetValues); | ||
that.applier.change("", newModel); | ||
} | ||
|
@@ -186,6 +262,12 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
}; | ||
|
||
var toSpeak = new SpeechSynthesisUtterance(text); | ||
// Store toSpeak additionally on the queue.currentUtterance member to help deal with the issue | ||
// with premature garbage collection described at https://bugs.chromium.org/p/chromium/issues/detail?id=509488#c11 | ||
// this makes the speech synthesis behave much better in Safari in | ||
// particular | ||
that.queue.currentUtterance = toSpeak; | ||
|
||
var eventBinding = { | ||
onstart: that.handleStart, | ||
onend: that.handleEnd, | ||
|
@@ -195,14 +277,14 @@ var fluid_2_0_0 = fluid_2_0_0 || {}; | |
}; | ||
$.extend(toSpeak, that.model.utteranceOpts, options, eventBinding); | ||
|
||
that.queue.push(text); | ||
that.queue.texts.push(text); | ||
that.events.onSpeechQueued.fire(text); | ||
speechSynthesis.speak(toSpeak); | ||
fluid.textToSpeech.deferredSpeechSynthesisControl("speak", toSpeak); | ||
}; | ||
|
||
fluid.textToSpeech.cancel = function (that) { | ||
that.queue = []; | ||
speechSynthesis.cancel(); | ||
that.queue.texts = []; | ||
fluid.textToSpeech.deferredSpeechSynthesisControl("cancel"); | ||
}; | ||
|
||
})(jQuery, fluid_2_0_0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume still fails linting :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What fails (the sequel) is actually my ability to install my precommit hooks on my laptop as well as my work desktop to make sure I can't commit unlinted code.