Skip to content

Commit

Permalink
Preloading of audio and bandwidth confirmation (oppia#3727)
Browse files Browse the repository at this point in the history
* Add preloading

* bandwidth confirmation

* internationalized strings

* lint

* lint

* remove todo

* suggested changes

* lint

* Option for on-demand loading

* Some comments and bug fix - error on switching to unavailable language

* renaming

* suggested changes

* Keep track of audio files being requested in AssetsBackendApiService to prevent duplicate requests

* lint

* renaming
  • Loading branch information
tjiang11 authored and seanlip committed Aug 11, 2017
1 parent 12020eb commit b2ccf54
Show file tree
Hide file tree
Showing 17 changed files with 382 additions and 85 deletions.
4 changes: 4 additions & 0 deletions assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,17 @@
"I18N_LIBRARY_VIEWS_TOOLTIP": "Views",
"I18N_LIBRARY_VIEW_ALL": "View all",
"I18N_MODAL_CANCEL_BUTTON": "Cancel",
"I18N_MODAL_CONTINUE_BUTTON": "Continue",
"I18N_ONE_SUBSCRIBER_TEXT": "You have 1 subscriber.",
"I18N_PLAYER_AUDIO_LANGUAGE": "Language",
"I18N_PLAYER_AUDIO_MIGHT_NOT_MATCH_TEXT": "Audio might not fully match text",
"I18N_PLAYER_AUDIO_NOT_AVAILABLE_IN": "Not available in <[languageDescription]>",
"I18N_PLAYER_AUDIO_TRANSLATION_SETTINGS": "Audio Translation Settings",
"I18N_PLAYER_BACK": "Back",
"I18N_PLAYER_BACK_TO_COLLECTION": "Back to collection",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_BODY": "This audio translation contains <b><[fileSizeMB]>MB</b> of <b><[languageDescription]></b> audio. Continue downloading?",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_DOWNLOAD_ALL_AUDIO": "Download all <b><[languageDescription]></b> audio in this exploration <b>(<[fileSizeMB]>MB)</b>",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_TITLE": "Bandwidth Usage Warning",
"I18N_PLAYER_CARD_NUMBER_TOOLTIP": "Card #",
"I18N_PLAYER_COMMUNITY_EDITABLE_TOOLTIP": "Community editable",
"I18N_PLAYER_CONTINUE_BUTTON": "Continue",
Expand Down
4 changes: 4 additions & 0 deletions assets/i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,17 @@
"I18N_LIBRARY_VIEWS_TOOLTIP": "Tooltip displayed inside the exploration card in the library - It's on top of the number of times the exploration has been viewed for the users.\n{{Identical|View}}",
"I18N_LIBRARY_VIEW_ALL": "Text of a button shown to the side of the exploration group. - When clicked, it shows all the explorations included on the associated group. It should be a small text.\n{{Identical|View all}}",
"I18N_MODAL_CANCEL_BUTTON": "Text that is displayed in a button of a modal. On clicking it the modal closes.\n{{Identical|Cancel}}",
"I18N_MODAL_CONTINUE_BUTTON": "Text that is displayed in a button of a modal. On clicking it the user confirms to continue with the action.",
"I18N_ONE_SUBSCRIBER_TEXT": "Text displayed under the subscribers tab in creator dashboard. If the creator has one subscriber, this text is displayed which informs him/her about the same.",
"I18N_PLAYER_AUDIO_LANGUAGE": "Text displayed in the audio translation settings modal, asking the learner to pick what language they want to listen to audio translations in.",
"I18N_PLAYER_AUDIO_MIGHT_NOT_MATCH_TEXT": "Text displayed under the audio controls to the learner when the audio translation for the current language is flagged as needing an update by the creator.",
"I18N_PLAYER_AUDIO_NOT_AVAILABLE_IN": "Text displayed in a tooltip over the speaker icon to play audio when there is no audio available in the selected language.",
"I18N_PLAYER_AUDIO_TRANSLATION_SETTINGS": "Title displayed at the top of the audio translation settings modal in the learner view.",
"I18N_PLAYER_BACK": "Text read to users with screenreaders when they navigate through an exploration. - This labels the leftward-pointing arrow that is used to go backward by one card in the exploration.\n{{Identical|Back}}",
"I18N_PLAYER_BACK_TO_COLLECTION": "Text shown to users after they complete an exploration in a collection. - This labels the link that is used to return back to the home page of the collection the user is currently exploring",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_BODY": "Text displayed in the body of the modal shown when the user clicks to play audio for the first time asking if they would like to use bandwidth to download audio.",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_DOWNLOAD_ALL_AUDIO": "Text displayed by the checkbox of the modal shown when the user clicks to play audio for the first time asking if they would like to use bandwidth to download audio. Checking the assoicated checkbox signifies that the user intends to predownload all audio translations in the exploration of the selected language.",
"I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_TITLE": "Text displayed at the top of the modal shown when the user clicks to play audio for the first time asking if they would like to use bandwidth to download audio.",
"I18N_PLAYER_CARD_NUMBER_TOOLTIP": "Text displayed when the user is playing an exploration. - On top of the player there are buttons that allow the user to navigate to the previous cards he has completed. This text is shown as a part of the tooltip of this button.\n{{Identical|Card}}",
"I18N_PLAYER_COMMUNITY_EDITABLE_TOOLTIP": "Text displayed as a tooltip when the user views a dialog with information about the exploration. - This labels the globe icon that indicates that an exploration is editable by the community.",
"I18N_PLAYER_CONTINUE_BUTTON": "Text displayed when the user is playing an exploration. - Text shown in a button. When the user clicks the button, the next card on the exploration is loaded.\n{{Identical|Continue}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ oppia.factory('AudioTranslationObjectFactory', [function() {
this.needsUpdate = !this.needsUpdate;
};

AudioTranslation.prototype.getFileSizeMB = function() {
var NUM_BYTES_IN_MB = 1 << 20;
return this.fileSizeBytes / NUM_BYTES_IN_MB;
};

AudioTranslation.prototype.toBackendDict = function() {
return {
filename: this.filename,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('AudioTranslation object factory', function() {
atof = $injector.get('AudioTranslationObjectFactory');
audioTranslation = atof.createFromBackendDict({
filename: 'a.mp3',
file_size_bytes: 20,
file_size_bytes: 200000,
needs_update: false
});
}));
Expand All @@ -36,7 +36,7 @@ describe('AudioTranslation object factory', function() {
audioTranslation.markAsNeedingUpdate();
expect(audioTranslation).toEqual(atof.createFromBackendDict({
filename: 'a.mp3',
file_size_bytes: 20,
file_size_bytes: 200000,
needs_update: true
}));
}));
Expand All @@ -45,34 +45,40 @@ describe('AudioTranslation object factory', function() {
audioTranslation.toggleNeedsUpdateAttribute();
expect(audioTranslation).toEqual(atof.createFromBackendDict({
filename: 'a.mp3',
file_size_bytes: 20,
file_size_bytes: 200000,
needs_update: true
}));

audioTranslation.toggleNeedsUpdateAttribute();
expect(audioTranslation).toEqual(atof.createFromBackendDict({
filename: 'a.mp3',
file_size_bytes: 20,
file_size_bytes: 200000,
needs_update: false
}));
}));

it('should convert to backend dict correctly', inject(function() {
expect(audioTranslation.toBackendDict()).toEqual({
filename: 'a.mp3',
file_size_bytes: 20,
file_size_bytes: 200000,
needs_update: false
});
}));

it('should create a new audio translation', inject(function() {
expect(atof.createNew('filename.mp3', 10)).toEqual(
expect(atof.createNew('filename.mp3', 100000)).toEqual(
atof.createFromBackendDict({
filename: 'filename.mp3',
file_size_bytes: 10,
file_size_bytes: 100000,
needs_update: false
})
);
}));

it('should get the correct file size in MB', inject(function() {
var NUM_BYTES_IN_MB = 1 << 20;
expect(audioTranslation.getFileSizeMB()).toEqual(
200000 / NUM_BYTES_IN_MB);
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ oppia.factory('ExplorationObjectFactory', [
languageCode);
};

Exploration.prototype.getAllAudioTranslations = function(languageCode) {
return this.states.getAllAudioTranslations(languageCode);
};

Exploration.prototype.getAllAudioTranslationsFileSizeMB =
function(languageCode) {
var totalFileSizeMB = 0;
var allAudioTranslations =
this.states.getAllAudioTranslations(languageCode);
allAudioTranslations.map(function(audioTranslation) {
totalFileSizeMB += audioTranslation.getFileSizeMB();
});
return totalFileSizeMB;
};

Exploration.prototype.getLanguageCode = function() {
return this.languageCode;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ describe('Exploration object factory', function() {
beforeEach(module('oppia'));

describe('ExplorationObjectFactory', function() {
var scope, eof, explorationDict, exploration;
var scope, eof, atof, explorationDict, exploration;
beforeEach(inject(function($rootScope, $injector) {
scope = $rootScope.$new();
eof = $injector.get('ExplorationObjectFactory');
sof = $injector.get('StateObjectFactory');
atof = $injector.get('AudioTranslationObjectFactory');

var statesDict = {
'first state': {
Expand All @@ -33,12 +34,12 @@ describe('Exploration object factory', function() {
audio_translations: {
en: {
filename: 'myfile1.mp3',
file_size_bytes: 0.5,
file_size_bytes: 210000,
needs_update: false
},
'hi-en': {
filename: 'myfile3.mp3',
file_size_bytes: 0.8,
file_size_bytes: 430000,
needs_update: false
}
}
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('Exploration object factory', function() {
audio_translations: {
'hi-en': {
filename: 'myfile2.mp3',
file_size_bytes: 0.8,
file_size_bytes: 120000,
needs_update: false
}
}
Expand Down Expand Up @@ -119,5 +120,37 @@ describe('Exploration object factory', function() {
expect(exploration.getUninterpolatedContentHtml('first state'))
.toEqual('content');
});

it('should correctly get audio translations from an exploration',
function() {
expect(exploration.getAllAudioTranslations('hi-en')).toEqual([
atof.createFromBackendDict({
filename: 'myfile3.mp3',
file_size_bytes: 430000,
needs_update: false
}),
atof.createFromBackendDict({
filename: 'myfile2.mp3',
file_size_bytes: 120000,
needs_update: false
})
]);
expect(exploration.getAllAudioTranslations('en')).toEqual([
atof.createFromBackendDict({
filename: 'myfile1.mp3',
file_size_bytes: 210000,
needs_update: false
})
]);
});

it('should get the correct file size in MB of all audio translations',
function() {
var NUM_BYTES_IN_MB = 1 << 20;
expect(exploration.getAllAudioTranslationsFileSizeMB('hi-en')).toEqual(
550000 / NUM_BYTES_IN_MB);
expect(exploration.getAllAudioTranslationsFileSizeMB('en')).toEqual(
210000 / NUM_BYTES_IN_MB);
});
});
});
12 changes: 12 additions & 0 deletions core/templates/dev/head/domain/exploration/StatesObjectFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ oppia.factory('StatesObjectFactory', [
return allAudioLanguageCodes;
};

States.prototype.getAllAudioTranslations = function(languageCode) {
var allAudioTranslations = [];
for (var stateName in this._states) {
var audioTranslationsForState =
this._states[stateName].content.getBindableAudioTranslations();
if (audioTranslationsForState.hasOwnProperty(languageCode)) {
allAudioTranslations.push(audioTranslationsForState[languageCode]);
}
}
return allAudioTranslations;
};

States.createFromBackendDict = function(statesBackendDict) {
var stateObjectsDict = {};
for (var stateName in statesBackendDict) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ describe('States object factory', function() {
beforeEach(module('oppia'));

describe('StatesObjectFactory', function() {
var scope, sof, statesDict;
var scope, sof, statesDict, statesWithAudioDict, atof;
beforeEach(inject(function($injector) {
ssof = $injector.get('StatesObjectFactory');
sof = $injector.get('StateObjectFactory');
atof = $injector.get('AudioTranslationObjectFactory');

GLOBALS.NEW_STATE_TEMPLATE = {
classifier_model_id: null,
Expand Down Expand Up @@ -87,44 +88,8 @@ describe('States object factory', function() {
param_changes: []
}
};
}));

it('should create a new state given a state name', function() {
var newStates = ssof.createFromBackendDict(statesDict);
newStates.addState('new state');
expect(newStates.getState('new state')).toEqual(
sof.createFromBackendDict('new state', {
classifier_model_id: null,
content: {
html: '',
audio_translations: {}
},
interaction: {
answer_groups: [],
confirmed_unclassified_answers: [],
customization_args: {
rows: {
value: 1
},
placeholder: {
value: 'Type your answer here.'
}
},
default_outcome: {
dest: 'new state',
feedback: [],
param_changes: []
},
fallbacks: [],
hints: [],
id: 'TextInput'
},
param_changes: []
}));
});

it('should correctly get all audio language codes in states', function() {
var statesWithAudioDict = {
statesWithAudioDict = {
'first state': {
content: {
html: 'content',
Expand Down Expand Up @@ -187,10 +152,63 @@ describe('States object factory', function() {
param_changes: []
}
}
}));

it('should create a new state given a state name', function() {
var newStates = ssof.createFromBackendDict(statesDict);
newStates.addState('new state');
expect(newStates.getState('new state')).toEqual(
sof.createFromBackendDict('new state', {
classifier_model_id: null,
content: {
html: '',
audio_translations: {}
},
interaction: {
answer_groups: [],
confirmed_unclassified_answers: [],
customization_args: {
rows: {
value: 1
},
placeholder: {
value: 'Type your answer here.'
}
},
default_outcome: {
dest: 'new state',
feedback: [],
param_changes: []
},
fallbacks: [],
hints: [],
id: 'TextInput'
},
param_changes: []
}));
});

it('should correctly get all audio language codes in states', function() {
var statesWithAudio = ssof.createFromBackendDict(statesWithAudioDict);
expect(statesWithAudio.getAllAudioLanguageCodes())
.toEqual(['en', 'hi-en']);
});

it('should correctly get all audio translations in states', function() {
var statesWithAudio = ssof.createFromBackendDict(statesWithAudioDict);
expect(statesWithAudio.getAllAudioTranslations('hi-en'))
.toEqual([
atof.createFromBackendDict({
filename: 'myfile3.mp3',
file_size_bytes: 0.8,
needs_update: false
}),
atof.createFromBackendDict({
filename: 'myfile2.mp3',
file_size_bytes: 0.8,
needs_update: false
})
]);
});
});
});
Loading

0 comments on commit b2ccf54

Please sign in to comment.