-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Fix #3790: Add automatic text-to-speech audio to explorations #3818
Conversation
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.
Thanks, @tjiang11! I'm still kind of amazed this is even possible, it's very cool :)
PTAL at comments; happy to talk about anything that needs discussion.
assets/constants.js
Outdated
@@ -271,11 +271,20 @@ var constants = { | |||
"SUPPORTED_AUDIO_LANGUAGES": [{ | |||
"id": "en", | |||
"text": "English", | |||
"related_languages": ["en"] | |||
"related_languages": ["en"], | |||
"speech_synthesis_code": "en-GB" |
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'm not sure the modelling here is right. I don't think we should have dicts with different keys inside a list that should be homogeneous.
Could we instead have two separate top-level constants: SUPPORTED_AUDIO_LANGUAGES and AUTOGENERATED_AUDIO_LANGUAGES, perhaps? The two things are handled rather differently anyway in e.g. the editor view, so I think it might make sense to separate them. Though you may need to unify both when generating the dropdown choices for the audio preferences.
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.
done
// For details on the reliabiltiy of this check, see | ||
// https://stackoverflow.com/questions/4565112/ | ||
// javascript-how-to-find-out-if-the-user-browser-is-chrome | ||
if (isIOSChrome || |
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.
Maybe use an additional paren since I don't think people tend to remember whether || or && takes precedence.
Incidentally, audio doesn't work on Chromium on Ubuntu, at least on my machine. Maybe related to https://askubuntu.com/questions/761975/chromium-is-not-generating-voice
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.
fixed
var isIOSChrome = winNav.userAgent.match('CriOS'); | ||
|
||
var _isChrome = function() { | ||
// For details on the reliabiltiy of this check, see |
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.
"reliability" is misspelled
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.
fixed
@@ -36,6 +36,16 @@ oppia.factory('LanguageUtilService', [function() { | |||
audioLanguage.related_languages; | |||
}); | |||
|
|||
var generatedAudioLanguageCodesToSpeechSynthesisLanguageCode = {}; |
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.
Again should we be storing human-generated audio + autogenerated audio separately (and only unifying where needed)? Feels like it'd be less implicit; you're currently relying on adding custom strings and stuff to the IDs which feels a bit less maintainable.
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.
fixed
@@ -53,39 +56,61 @@ oppia.directive('audioControls', [ | |||
AudioTranslationManagerService.getCurrentAudioLanguageCode()]; | |||
}; | |||
|
|||
$scope.AudioPlayerService = AudioPlayerService; | |||
$scope.showSpeakerPlayingIcon = function() { |
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.
Why not replace these two methods with $scope.isAudioPlaying()?
} | ||
else { | ||
var chunkLength = (settings && settings.chunkLength) || 160; | ||
var pattRegex = new RegExp('^[\\s\\S]{' + |
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.
Some intuitive explanation may be helpful here to explain what this regex is meant to represent.
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.
done
newUtt = new SpeechSynthesisUtterance(chunk); | ||
var x; | ||
for (x in utt) { | ||
if (x !== 'text') { |
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.
No idea what x is, or what the significance of 'text' is. Maybe use better name / comment?
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.
done
html = html.replace(new RegExp('</li>', 'g'), '.').trim(); | ||
// Strip away HTML tags. | ||
var tmp = document.createElement('div'); | ||
tmp.innerHTML = html; |
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.
In general we eschew using innerHTML; there are security concerns. Can you find another way to do this? There's an rteHelperService in app.js that uses .html(), perhaps that might be better. Also maybe it's worth adding a convertToReadableText() thing in there (see previous comment) --- though I'm not 100% sure if that's the best place for it or if having a new service is better.
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.
used html()
@@ -449,7 +449,7 @@ states: | |||
html: 'When mathematicians talk about graph theory, they usually aren''t referring | |||
to curve sketching!<div><br></div><div>A graph is a mathematical object that | |||
consists of "vertices" and "edges". Does this sound complicated? Actually, | |||
it isn''t: in simple terms, graphs just dots joined by lines! The dots are | |||
it isn''t: in simple terms, graphs are just dots joined by lines! The dots are |
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.
Nice catch :)
}; | ||
|
||
var _insertSpaceAfterIndex = function(targetString, index) { | ||
return targetString.substr(0, index + 1) + ' ' + |
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.
So much copying and creation of new strings ... perhaps just build up the new string character by character in the for loop instead?
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.
done
Codecov Report
@@ Coverage Diff @@
## develop #3818 +/- ##
===========================================
- Coverage 46% 45.86% -0.14%
===========================================
Files 283 288 +5
Lines 20942 21131 +189
Branches 3287 3319 +32
===========================================
+ Hits 9634 9692 +58
- Misses 11308 11439 +131
Continue to review full report at Codecov.
|
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.
This is very cool! Just a few minor comments.
Please also resolve merge conflicts and ensure that all existing comments are replied to.
core/templates/dev/head/app.js
Outdated
@@ -703,6 +703,7 @@ oppia.factory('rteHelperService', [ | |||
var that = this; | |||
|
|||
_RICH_TEXT_COMPONENTS.forEach(function(componentDefn) { | |||
console.log(componentDefn); |
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.
please drop
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.
done
return Boolean(getAudioTranslationInCurrentLanguage()); | ||
return Boolean(getAudioTranslationInCurrentLanguage()) || | ||
AudioTranslationManagerService | ||
.isAutogeneratedLanguageCodeSelected(); |
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.
Hm, might want a helper function locally here for checking isAutogeneratedLanguageCodeSelected(). The length of this whole thing seems to affect the readability of not only this line, but below too.
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.
done
// that language if it is available. | ||
// 2. If the exploration language has a related audio language, then set | ||
// it to that. | ||
// 3. If only the autogenerated audio language available, then set it |
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.
available --> is available
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.
done
_explorationLanguageCode)) { | ||
_currentAudioLanguageCode = | ||
LanguageUtilService.getAutogeneratedAudioLanguage( | ||
_explorationLanguageCode).id |
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.
missing semicolon
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.
added
}, | ||
getSpeechSynthesisLanguageCode: function() { | ||
return LanguageUtilService.getAutogeneratedAudioLanguage( | ||
_explorationLanguageCode).speech_synthesis_code; |
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.
Should make domain object and use speechSynthesisCode
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.
done
var _currentAudioLanguageCode = null; | ||
var _allAudioLanguageCodesInExploration = null; | ||
var _explorationLanguageCode = null; | ||
var _autogeneratedLanguageCodeIsSelected = false; |
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.
Would it be better to deduce this on the fly from the current language code, instead of maintaining a state variable that devs need to remember to update? I'm not sure. But the current "manual update" approach seems a little fragile to me.
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.
yeah true, done
// Replace dashes with 'minus'. | ||
.replace(/-/g, 'minus') | ||
// Ensure that 'x^2' is pronounced 'x squared' rather than | ||
// 'x karat 2'. |
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.
karat --> caret
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.
oops, was recently interviewing with a company called Karat that used a ^ in their logo and this slipped out haha, fixed
elt.find('oppia-noninteractive-' + RTE_COMPONENT_NAMES.Math) | ||
.replaceWith(function() { | ||
if (this.attributes['raw_latex-with-value'] !== undefined) { | ||
return this.attributes['raw_latex-with-value'].textContent |
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.
Consider pulling this part out into a function and adding karma tests that feed in a raw-latex-with-value string and that check the result after all the replacements? I can see this breaking as new improvements are added.
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.
done
…text; add tests for LaTeX conversion to speakable text
Sorry the LaTeX formatting at the moment is pretty sloppy (haven't done much regex before..) and will likely need a good deal of rework later. I just whipped some stuff up to handle basic things for now, but it will need a lot of polishing to handle function composition cleanly. |
@@ -61,28 +61,32 @@ oppia.directive('audioControls', [ | |||
AutogeneratedAudioPlayerService.isPlaying(); | |||
}; | |||
|
|||
$scope.isAudioLoading = false; |
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.
Consider audioIsLoading. Otherwise, looks like a function.
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.
done
LGTM. Thanks! Just one small fix, then let's merge. |
No description provided.