Skip to content
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

Merged
merged 11 commits into from
Sep 18, 2017

Conversation

tjiang11
Copy link
Contributor

No description provided.

@tjiang11 tjiang11 requested a review from seanlip August 28, 2017 04:24
Copy link
Member

@seanlip seanlip left a 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.

@@ -271,11 +271,20 @@ var constants = {
"SUPPORTED_AUDIO_LANGUAGES": [{
"id": "en",
"text": "English",
"related_languages": ["en"]
"related_languages": ["en"],
"speech_synthesis_code": "en-GB"
Copy link
Member

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.

Copy link
Contributor Author

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 ||
Copy link
Member

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

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"reliability" is misspelled

Copy link
Contributor Author

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 = {};
Copy link
Member

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.

Copy link
Contributor Author

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() {
Copy link
Member

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]{' +
Copy link
Member

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.

Copy link
Contributor Author

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') {
Copy link
Member

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?

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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) + ' ' +
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@codecov-io
Copy link

codecov-io commented Sep 16, 2017

Codecov Report

Merging #3818 into develop will decrease coverage by 0.13%.
The diff coverage is 35.71%.

Impacted file tree graph

@@             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
Impacted Files Coverage Δ
.../pages/exploration_player/AudioPreloaderService.js 2.5% <0%> (-0.14%) ⬇️
...ev/head/pages/exploration_player/PlayerServices.js 1.11% <0%> (-0.01%) ⬇️
...pages/exploration_player/AudioControlsDirective.js 1.35% <0%> (-0.54%) ⬇️
...ead/pages/exploration_player/TutorCardDirective.js 2.38% <0%> (-0.03%) ⬇️
...ilities/AutogeneratedAudioLanguageObjectFactory.js 100% <100%> (ø)
...ead/domain/utilities/AudioLanguageObjectFactory.js 100% <100%> (ø)
...dev/head/services/SpeechSynthesisChunkerService.js 21.12% <21.12%> (ø)
...v/head/services/AutogeneratedAudioPlayerService.js 26.66% <26.66%> (ø)
...dev/head/domain/utilities/BrowserCheckerService.js 47.36% <47.36%> (ø)
...ploration_player/AudioTranslationManagerService.js 45.58% <7.69%> (-8.96%) ⬇️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e04304f...dc2ed3f. Read the comment docs.

Copy link
Member

@seanlip seanlip left a 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.

@@ -703,6 +703,7 @@ oppia.factory('rteHelperService', [
var that = this;

_RICH_TEXT_COMPONENTS.forEach(function(componentDefn) {
console.log(componentDefn);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please drop

Copy link
Contributor Author

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();
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

available --> is available

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing semicolon

Copy link
Contributor Author

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;
Copy link
Member

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

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

karat --> caret

Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@tjiang11
Copy link
Contributor Author

tjiang11 commented Sep 17, 2017

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;
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@seanlip
Copy link
Member

seanlip commented Sep 17, 2017

LGTM. Thanks! Just one small fix, then let's merge.

@seanlip seanlip merged commit aecb981 into oppia:develop Sep 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants