diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js index cadba4c995..636c6aef22 100644 --- a/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js +++ b/common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js @@ -335,21 +335,23 @@ beforeEach(function () { initialize(); - spyOn(videoPlayer, 'updatePlayTime'); - spyOn(videoPlayer, 'log'); - spyOn(videoPlayer.player, 'seekTo'); - - state.videoPlayer.play(); + runs(function () { + state.videoPlayer.play(); + }); waitsFor(function () { - return videoPlayer.isPlaying(); + duration = videoPlayer.duration(); + + return duration > 0 && videoPlayer.isPlaying(); }, 'video begins playing', WAIT_TIMEOUT); }); it('Slider event causes log update', function () { + runs(function () { var currentTime = videoPlayer.currentTime; + spyOn(videoPlayer, 'log'); videoProgressSlider.onSlide( jQuery.Event('slide'), { value: 2 } ); @@ -367,6 +369,7 @@ it('seek the player', function () { runs(function () { + spyOn(videoPlayer.player, 'seekTo'); videoProgressSlider.onSlide( jQuery.Event('slide'), { value: 60 } ); @@ -378,6 +381,7 @@ it('call updatePlayTime on player', function () { runs(function () { + spyOn(videoPlayer, 'updatePlayTime'); videoProgressSlider.onSlide( jQuery.Event('slide'), { value: 60 } ); @@ -476,20 +480,39 @@ videoPlayer.onPlay(); expect(videoPlayer.onSpeedChange.calls.length).toEqual(1); }); + + it('video has a correct volume', function () { + spyOn(videoPlayer.player, 'setVolume'); + state.currentVolume = '0.26'; + videoPlayer.onPlay(); + expect(videoPlayer.player.setVolume).toHaveBeenCalledWith('0.26'); + }); }); }); describe('onVolumeChange', function () { beforeEach(function () { initialize(); - - spyOn(videoPlayer.player, 'setVolume'); - videoPlayer.onVolumeChange(60); }); it('set the volume on player', function () { + spyOn(videoPlayer.player, 'setVolume'); + videoPlayer.onVolumeChange(60); expect(videoPlayer.player.setVolume).toHaveBeenCalledWith(60); }); + + describe('when the video is not playing', function () { + beforeEach(function () { + videoPlayer.player.setVolume('1'); + }); + + it('video has a correct volume', function () { + spyOn(videoPlayer.player, 'setVolume'); + state.currentVolume = '0.26'; + videoPlayer.onPlay(); + expect(videoPlayer.player.setVolume).toHaveBeenCalledWith('0.26'); + }); + }); }); describe('update', function () { @@ -922,7 +945,9 @@ }); waitsFor(function () { - return videoPlayer.isPlaying(); + duration = videoPlayer.duration(); + + return duration > 0 && videoPlayer.isPlaying(); },'Video does not play.' , WAIT_TIMEOUT); runs(function () { diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js index 77fef5ebc7..df7988a270 100644 --- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js +++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js @@ -323,6 +323,7 @@ function (VideoPlayer) { // element has a CSS class 'fullscreen'. this.__dfd__ = $.Deferred(); this.isFullScreen = false; + this.currentVolume = 100; this.isTouch = onTouchBasedDevice() || ''; // The parent element of the video, and the ID. diff --git a/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js b/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js index 85b16ea210..78a81d2d95 100644 --- a/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js +++ b/common/lib/xmodule/xmodule/js/src/video/02_html5_video.js @@ -193,6 +193,9 @@ function () { sourceStr[videoType] = ' '; } diff --git a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js index 7d7472e793..a89aef7b1a 100644 --- a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js +++ b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js @@ -70,6 +70,7 @@ function (HTML5Video, Resizer) { if (state.currentPlayerMode !== 'flash') { state.videoPlayer.onSpeedChange(state.speed); } + state.videoPlayer.player.setVolume(state.currentVolume); }); if (state.videoType === 'youtube') { diff --git a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js index f6b81cc1c7..f06588bd9e 100644 --- a/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js +++ b/common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js @@ -50,59 +50,68 @@ function () { // make the created DOM elements available via the 'state' object. Much easier to work this // way - you don't have to do repeated jQuery element selects. function _renderElements(state) { - state.videoVolumeControl.el = state.el.find('div.volume'); + var volumeControl = state.videoVolumeControl, + element = state.el.find('div.volume'), + button = element.find('a'), + volumeSlider = element.find('.volume-slider'), + // Figure out what the current volume is. If no information about + // volume level could be retrieved, then we will use the default 100 + // level (full volume). + currentVolume = parseInt($.cookie('video_player_volume_level'), 10), + // Set it up so that muting/unmuting works correctly. + previousVolume = 100, + slider, buttonStr, volumeSliderHandleEl; - state.videoVolumeControl.buttonEl = state.videoVolumeControl.el.find('a'); - state.videoVolumeControl.volumeSliderEl = state.videoVolumeControl.el.find('.volume-slider'); + state.videoControl.secondaryControlsEl.prepend(element); - state.videoControl.secondaryControlsEl.prepend(state.videoVolumeControl.el); - - // Figure out what the current volume is. If no information about volume level could be retrieved, - // then we will use the default 100 level (full volume). - state.videoVolumeControl.currentVolume = parseInt($.cookie('video_player_volume_level'), 10); - if (!isFinite(state.videoVolumeControl.currentVolume)) { - state.videoVolumeControl.currentVolume = 100; + if (!isFinite(currentVolume)) { + currentVolume = 100; } - // Set it up so that muting/unmuting works correctly. - state.videoVolumeControl.previousVolume = 100; - - state.videoVolumeControl.slider = state.videoVolumeControl.volumeSliderEl.slider({ + slider = volumeSlider.slider({ orientation: 'vertical', range: 'min', min: 0, max: 100, - value: state.videoVolumeControl.currentVolume, - change: state.videoVolumeControl.onChange, - slide: state.videoVolumeControl.onChange + value: currentVolume, + change: volumeControl.onChange, + slide: volumeControl.onChange }); - state.videoVolumeControl.el.toggleClass('muted', state.videoVolumeControl.currentVolume === 0); + element.toggleClass('muted', currentVolume === 0); // ARIA // Let screen readers know that: - // This anchor behaves as a button named 'Volume'. - var currentVolume = state.videoVolumeControl.currentVolume, - buttonStr = (currentVolume === 0) ? 'Volume muted' : 'Volume'; + buttonStr = (currentVolume === 0) ? 'Volume muted' : 'Volume'; // We add the aria-label attribute because the title attribute cannot be // read. - state.videoVolumeControl.buttonEl.attr('aria-label', gettext(buttonStr)); + button.attr('aria-label', gettext(buttonStr)); // Let screen readers know that this anchor, representing the slider // handle, behaves as a slider named 'volume'. - var volumeSlider = state.videoVolumeControl.slider; - state.videoVolumeControl.volumeSliderHandleEl = state.videoVolumeControl - .volumeSliderEl - .find('.ui-slider-handle'); - state.videoVolumeControl.volumeSliderHandleEl.attr({ + volumeSliderHandleEl = slider.find('.ui-slider-handle'); + + volumeSliderHandleEl.attr({ 'role': 'slider', 'title': 'volume', 'aria-disabled': false, - 'aria-valuemin': volumeSlider.slider('option', 'min'), - 'aria-valuemax': volumeSlider.slider('option', 'max'), - 'aria-valuenow': volumeSlider.slider('option', 'value'), - 'aria-valuetext': getVolumeDescription(volumeSlider.slider('option', 'value')) + 'aria-valuemin': slider.slider('option', 'min'), + 'aria-valuemax': slider.slider('option', 'max'), + 'aria-valuenow': slider.slider('option', 'value'), + 'aria-valuetext': getVolumeDescription(slider.slider('option', 'value')) + }); + + + state.currentVolume = currentVolume; + $.extend(state.videoVolumeControl, { + el: element, + buttonEl: button, + volumeSliderEl: volumeSlider, + currentVolume: currentVolume, + previousVolume: previousVolume, + slider: slider, + volumeSliderHandleEl: volumeSliderHandleEl }); } diff --git a/lms/djangoapps/courseware/features/video.py b/lms/djangoapps/courseware/features/video.py index 440bcb8cb7..2f228853ab 100644 --- a/lms/djangoapps/courseware/features/video.py +++ b/lms/djangoapps/courseware/features/video.py @@ -1,7 +1,6 @@ #pylint: disable=C0111 from lettuce import world, step -from lettuce.django import django_url from common import i_am_registered_for_the_course, section_location, visit_scenario_item from django.utils.translation import ugettext as _ @@ -94,8 +93,10 @@ def video_is_rendered(_step, mode): @step('all sources are correct$') def all_sources_are_correct(_step): - sources = world.css_find('.video video source') - assert set(source['src'] for source in sources) == set(HTML5_SOURCES) + elements = world.css_find('.video video source') + sources = [source['src'].split('?')[0] for source in elements] + + assert set(sources) == set(HTML5_SOURCES) @step('error message is shown$')