diff --git a/assets/i18n/en.json b/assets/i18n/en.json index ea26268b4181..1246dce9a57e 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -304,6 +304,7 @@ "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", @@ -311,6 +312,9 @@ "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 <[fileSizeMB]>MB of <[languageDescription]> audio. Continue downloading?", + "I18N_PLAYER_BANDWIDTH_USAGE_WARNING_MODAL_DOWNLOAD_ALL_AUDIO": "Download all <[languageDescription]> audio in this exploration (<[fileSizeMB]>MB)", + "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", diff --git a/assets/i18n/qqq.json b/assets/i18n/qqq.json index 9e68f26b9eb4..febd83e3bc3c 100644 --- a/assets/i18n/qqq.json +++ b/assets/i18n/qqq.json @@ -304,6 +304,7 @@ "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.", @@ -311,6 +312,9 @@ "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}}", diff --git a/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactory.js b/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactory.js index ddb82c3ab427..6b3ef253a5f9 100644 --- a/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactory.js +++ b/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactory.js @@ -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, diff --git a/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactorySpec.js b/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactorySpec.js index 50d450efee3c..f538be8652eb 100644 --- a/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactorySpec.js +++ b/core/templates/dev/head/domain/exploration/AudioTranslationObjectFactorySpec.js @@ -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 }); })); @@ -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 })); })); @@ -45,14 +45,14 @@ 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 })); })); @@ -60,19 +60,25 @@ describe('AudioTranslation object factory', function() { 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); + })); }); }); diff --git a/core/templates/dev/head/domain/exploration/ExplorationObjectFactory.js b/core/templates/dev/head/domain/exploration/ExplorationObjectFactory.js index 7ed086e05170..bbd024514148 100644 --- a/core/templates/dev/head/domain/exploration/ExplorationObjectFactory.js +++ b/core/templates/dev/head/domain/exploration/ExplorationObjectFactory.js @@ -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; }; diff --git a/core/templates/dev/head/domain/exploration/ExplorationObjectFactorySpec.js b/core/templates/dev/head/domain/exploration/ExplorationObjectFactorySpec.js index a88d5cb04678..8ba11932c4eb 100644 --- a/core/templates/dev/head/domain/exploration/ExplorationObjectFactorySpec.js +++ b/core/templates/dev/head/domain/exploration/ExplorationObjectFactorySpec.js @@ -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': { @@ -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 } } @@ -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 } } @@ -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); + }); }); }); diff --git a/core/templates/dev/head/domain/exploration/StatesObjectFactory.js b/core/templates/dev/head/domain/exploration/StatesObjectFactory.js index 0714872d9bc9..2bfa4c6ee2b2 100644 --- a/core/templates/dev/head/domain/exploration/StatesObjectFactory.js +++ b/core/templates/dev/head/domain/exploration/StatesObjectFactory.js @@ -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) { diff --git a/core/templates/dev/head/domain/exploration/StatesObjectFactorySpec.js b/core/templates/dev/head/domain/exploration/StatesObjectFactorySpec.js index d9c6d2266db9..299b2ff1060a 100644 --- a/core/templates/dev/head/domain/exploration/StatesObjectFactorySpec.js +++ b/core/templates/dev/head/domain/exploration/StatesObjectFactorySpec.js @@ -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, @@ -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', @@ -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 + }) + ]); + }); }); }); diff --git a/core/templates/dev/head/pages/exploration_player/AudioControlsDirective.js b/core/templates/dev/head/pages/exploration_player/AudioControlsDirective.js index 5558871756f4..65e22a1856bf 100644 --- a/core/templates/dev/head/pages/exploration_player/AudioControlsDirective.js +++ b/core/templates/dev/head/pages/exploration_player/AudioControlsDirective.js @@ -18,7 +18,8 @@ */ oppia.directive('audioControls', [ - 'UrlInterpolationService', function(UrlInterpolationService) { + 'UrlInterpolationService', 'AudioPreloaderService', + function(UrlInterpolationService, AudioPreloaderService) { return { restrict: 'E', scope: { @@ -29,24 +30,27 @@ oppia.directive('audioControls', [ 'audio_controls_directive.html'), controller: [ '$scope', 'AudioTranslationManagerService', 'AudioPlayerService', - 'LanguageUtilService', + 'LanguageUtilService', 'AssetsBackendApiService', function( $scope, AudioTranslationManagerService, AudioPlayerService, - LanguageUtilService) { + LanguageUtilService, AssetsBackendApiService) { // This ID is passed in to AudioPlayerService as a means of // distinguishing which audio directive is currently playing audio. var directiveId = Math.random().toString(36).substr(2, 10); - var currentAudioLanguageCode = - AudioTranslationManagerService - .getCurrentAudioLanguageCode(); - $scope.currentAudioLanguageDescription = - AudioTranslationManagerService + var getCurrentAudioLanguageCode = function() { + return AudioTranslationManagerService.getCurrentAudioLanguageCode(); + }; + + $scope.getCurrentAudioLanguageDescription = function() { + return AudioTranslationManagerService .getCurrentAudioLanguageDescription(); + }; - var getCurrentAudioTranslation = function() { - return $scope.getAudioTranslations()[currentAudioLanguageCode]; + var getAudioTranslationInCurrentLanguage = function() { + return $scope.getAudioTranslations()[ + AudioTranslationManagerService.getCurrentAudioLanguageCode()]; }; $scope.AudioPlayerService = AudioPlayerService; @@ -56,20 +60,40 @@ oppia.directive('audioControls', [ '/icons/rewind-five.svg')); $scope.isAudioAvailableInCurrentLanguage = function() { - return Boolean(getCurrentAudioTranslation()); + return Boolean(getAudioTranslationInCurrentLanguage()); }; $scope.doesCurrentAudioTranslationNeedUpdate = function() { - return getCurrentAudioTranslation().needsUpdate; + return getAudioTranslationInCurrentLanguage().needsUpdate; }; - $scope.playPauseAudioTranslation = function() { - // TODO(tjiang11): Change from on-demand loading to pre-loading. + $scope.onSpeakerIconClicked = function() { + var audioTranslation = getAudioTranslationInCurrentLanguage(); + if (audioTranslation) { + // If this language hasn't been preloaded for the exploration, + // and this audio translation hasn't been loaded, then ask to + // preload all audio translations for the current language. + if (!AudioPreloaderService.hasPreloadedLanguage( + getCurrentAudioLanguageCode()) && + !isCached(audioTranslation)) { + AudioPreloaderService.showBandwidthConfirmationModal( + $scope.getAudioTranslations(), getCurrentAudioLanguageCode(), + playPauseAudioTranslation); + } else { + playPauseAudioTranslation(getCurrentAudioLanguageCode()); + } + } else { + // If the audio translation isn't available in the current + // language, then open the settings modal. + $scope.openAudioTranslationSettings(); + } + }; - // TODO(tjiang11): On first play, ask learner to pick language - // and subsequently for confirmation to use bandwidth - // to download audio files. + var isCached = function(audioTranslation) { + return AssetsBackendApiService.isCached(audioTranslation.filename); + }; + var playPauseAudioTranslation = function(languageCode) { $scope.extraAudioControlsAreShown = true; if (!AudioPlayerService.isPlaying()) { @@ -95,7 +119,7 @@ oppia.directive('audioControls', [ }; var loadAndPlayAudioTranslation = function() { - var audioTranslation = getCurrentAudioTranslation(); + var audioTranslation = getAudioTranslationInCurrentLanguage(); if (audioTranslation) { AudioPlayerService.load( audioTranslation.filename, directiveId).then(function() { @@ -109,13 +133,7 @@ oppia.directive('audioControls', [ }; $scope.openAudioTranslationSettings = function() { - AudioTranslationManagerService - .showAudioTranslationSettingsModal(function(newLanguageCode) { - currentAudioLanguageCode = newLanguageCode; - $scope.currentAudioLanguageDescription = - LanguageUtilService.getAudioLanguageDescription( - newLanguageCode); - }); + AudioTranslationManagerService.showAudioTranslationSettingsModal(); }; }] } diff --git a/core/templates/dev/head/pages/exploration_player/AudioPreloaderService.js b/core/templates/dev/head/pages/exploration_player/AudioPreloaderService.js new file mode 100644 index 000000000000..3e48c588d55c --- /dev/null +++ b/core/templates/dev/head/pages/exploration_player/AudioPreloaderService.js @@ -0,0 +1,132 @@ +// Copyright 2017 The Oppia Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Service to preload audio into AssetsBackendApiService's cache. + */ + +oppia.factory('AudioPreloaderService', [ + '$modal', 'explorationContextService', 'AssetsBackendApiService', + 'ExplorationPlayerStateService', 'UrlInterpolationService', + 'AudioTranslationManagerService', + function($modal, explorationContextService, AssetsBackendApiService, + ExplorationPlayerStateService, UrlInterpolationService, + AudioTranslationManagerService) { + // List of languages that have been preloaded in the exploration. + var _preloadedLanguageCodes = []; + + var _preloadAllAudioFiles = function(languageCode) { + var allAudioTranslations = + ExplorationPlayerStateService + .getExploration().getAllAudioTranslations(languageCode); + + allAudioTranslations.map(function(audioTranslation) { + AssetsBackendApiService.loadAudio( + explorationContextService.getExplorationId(), + audioTranslation.filename); + }); + + _preloadedLanguageCodes.push(languageCode); + }; + + var _showBandwidthConfirmationModal = function( + audioTranslationsForContent, languageCode, + confirmationCallback) { + $modal.open({ + templateUrl: UrlInterpolationService.getDirectiveTemplateUrl( + '/pages/exploration_player/' + + 'audio_preload_bandwidth_confirmation_modal_directive.html'), + resolve: {}, + backdrop: true, + controller: [ + '$scope', '$modalInstance', + 'ExplorationPlayerStateService', 'AudioPreloaderService', + 'LanguageUtilService', + function( + $scope, $modalInstance, + ExplorationPlayerStateService, AudioPreloaderService, + LanguageUtilService) { + $scope.fileSizeOfCurrentAudioTranslationMB = + audioTranslationsForContent[languageCode] + .getFileSizeMB().toPrecision(3); + $scope.totalFileSizeOfAllAudioTranslationsMB = + ExplorationPlayerStateService.getExploration() + .getAllAudioTranslationsFileSizeMB(languageCode) + .toPrecision(3); + $scope.currentLanguageDescription = + LanguageUtilService.getAudioLanguageDescription(languageCode); + $scope.shouldDownloadAllAudioInExploration = false; + + $scope.confirm = function() { + $modalInstance.close({ + shouldDownloadAllAudioInExploration: + $scope.shouldDownloadAllAudioInExploration, + shouldOpenSettingsModal: false + }); + }; + + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); + }; + + $scope.chooseDifferentLanguage = function() { + $modalInstance.close({ + shouldDownloadAllAudioInExploration: false, + shouldOpenSettingsModal: true + }); + }; + }] + }).result.then(function(result) { + if (result.shouldOpenSettingsModal) { + // If the user elected to choose a different language, open + // the settings modal (later can isolate to a language-only + // modal), and on the callback re-open the bandwidth confirmation + // modal if the file for the new language hasn't been loaded. + AudioTranslationManagerService + .showAudioTranslationSettingsModal(function(newLanguageCode) { + var newAudioTranslation = + audioTranslationsForContent[newLanguageCode]; + if (newAudioTranslation && !AssetsBackendApiService.isCached( + newAudioTranslation.filename)) { + _showBandwidthConfirmationModal( + audioTranslationsForContent, newLanguageCode, + confirmationCallback) + } + }); + } else { + confirmationCallback(languageCode); + if (result.shouldDownloadAllAudioInExploration) { + _preloadAllAudioFiles(languageCode); + } + } + }); + }; + + return { + init: function() { + _init(); + }, + hasPreloadedLanguage: function(languageCode) { + return _preloadedLanguageCodes.indexOf(languageCode) !== -1; + }, + showBandwidthConfirmationModal: function( + audioTranslationsForContent, languageCode, + confirmationCallback) { + _showBandwidthConfirmationModal( + audioTranslationsForContent, languageCode, + confirmationCallback); + } + }; + } +]); diff --git a/core/templates/dev/head/pages/exploration_player/AudioTranslationManagerService.js b/core/templates/dev/head/pages/exploration_player/AudioTranslationManagerService.js index 4c9782870fe6..a9be8d4f7a27 100644 --- a/core/templates/dev/head/pages/exploration_player/AudioTranslationManagerService.js +++ b/core/templates/dev/head/pages/exploration_player/AudioTranslationManagerService.js @@ -52,6 +52,7 @@ oppia.factory('AudioTranslationManagerService', [ '/pages/exploration_player/' + 'audio_translation_settings_modal_directive.html'), resolve: {}, + backdrop: true, controller: [ '$scope', '$filter', '$modalInstance', 'AudioTranslationManagerService', 'LanguageUtilService', @@ -86,7 +87,9 @@ oppia.factory('AudioTranslationManagerService', [ _currentAudioLanguageCode = result.languageCode; AudioPlayerService.stop(); AudioPlayerService.clear(); - onLanguageChangedCallback(_currentAudioLanguageCode); + if (onLanguageChangedCallback) { + onLanguageChangedCallback(_currentAudioLanguageCode); + } } }); }; @@ -106,7 +109,7 @@ oppia.factory('AudioTranslationManagerService', [ return _allLanguageCodesInExploration; }, showAudioTranslationSettingsModal: function(onLanguageChangedCallback) { - return _showAudioTranslationSettingsModal(onLanguageChangedCallback); + _showAudioTranslationSettingsModal(onLanguageChangedCallback); } }; }]); diff --git a/core/templates/dev/head/pages/exploration_player/PlayerServices.js b/core/templates/dev/head/pages/exploration_player/PlayerServices.js index 06fc550a64c6..667cfa53fc79 100644 --- a/core/templates/dev/head/pages/exploration_player/PlayerServices.js +++ b/core/templates/dev/head/pages/exploration_player/PlayerServices.js @@ -251,6 +251,9 @@ oppia.factory('oppiaPlayerService', [ getStateContentAudioTranslation: function(stateName, languageCode) { return exploration.getAudioTranslation(stateName, languageCode); }, + getAllAudioTranslations: function() { + return exploration.getAllAudioTranslations(); + }, isContentAudioTranslationAvailable: function(stateName) { return Object.keys( exploration.getAudioTranslations(stateName)).length > 0; diff --git a/core/templates/dev/head/pages/exploration_player/audio_controls_directive.html b/core/templates/dev/head/pages/exploration_player/audio_controls_directive.html index 5da80ade9e4f..129b6b6eb73c 100644 --- a/core/templates/dev/head/pages/exploration_player/audio_controls_directive.html +++ b/core/templates/dev/head/pages/exploration_player/audio_controls_directive.html @@ -9,9 +9,9 @@ ng-click="rewindAudioFiveSec()"> diff --git a/core/templates/dev/head/pages/exploration_player/audio_preload_bandwidth_confirmation_modal_directive.html b/core/templates/dev/head/pages/exploration_player/audio_preload_bandwidth_confirmation_modal_directive.html new file mode 100644 index 000000000000..fb4796579996 --- /dev/null +++ b/core/templates/dev/head/pages/exploration_player/audio_preload_bandwidth_confirmation_modal_directive.html @@ -0,0 +1,22 @@ +
Try typing some code.
interaction: @@ -82,11 +82,11 @@ states: audio_translations: en: filename: 'test_audio_1_en.mp3' - file_size_bytes: 1 + file_size_bytes: 95426 needs_update: false hi-en: filename: 'test_audio_1_hi_en.mp3' - file_size_bytes: 4 + file_size_bytes: 132719 needs_update: false html:Try clicking on the speaker! After listening, click continue.
interaction: