From 19e97de0fbb2de41347276c8ca04ca282cff48d4 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Wed, 13 Jun 2012 11:46:27 -0400 Subject: [PATCH] Add video volume control to video player --- lms/static/coffee/spec/helper.coffee | 3 +- .../modules/video/video_player_spec.coffee | 14 +++ .../video/video_speed_control_spec.coffee | 2 - .../video/video_volume_control_spec.coffee | 102 ++++++++++++++++++ .../src/modules/video/video_player.coffee | 7 ++ .../modules/video/video_volume_control.coffee | 48 +++++++++ lms/static/sass_old/courseware/_video.scss | 80 ++++++++++++++ 7 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee create mode 100644 lms/static/coffee/src/modules/video/video_volume_control.coffee diff --git a/lms/static/coffee/spec/helper.coffee b/lms/static/coffee/spec/helper.coffee index 3d9537f872..9b575452c7 100644 --- a/lms/static/coffee/spec/helper.coffee +++ b/lms/static/coffee/spec/helper.coffee @@ -30,7 +30,8 @@ jasmine.stubRequests = -> jasmine.stubYoutubePlayer = -> YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode', - 'getCurrentTime', 'getPlayerState', 'loadVideoById', 'playVideo', 'pauseVideo', 'seekTo'] + 'getCurrentTime', 'getPlayerState', 'getVolume', 'setVolume', 'loadVideoById', + 'playVideo', 'pauseVideo', 'seekTo'] jasmine.stubVideoPlayer = (context, enableParts) -> enableParts = [enableParts] unless $.isArray(enableParts) diff --git a/lms/static/coffee/spec/modules/video/video_player_spec.coffee b/lms/static/coffee/spec/modules/video/video_player_spec.coffee index cdfedcb4f9..05f447b8f9 100644 --- a/lms/static/coffee/spec/modules/video/video_player_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_player_spec.coffee @@ -387,3 +387,17 @@ describe 'VideoPlayer', -> it 'delegate to the video', -> expect(@player.currentSpeed()).toEqual '3.0' + + describe 'volume', -> + beforeEach -> + @player = new VideoPlayer @video + @player.player.getVolume.andReturn 42 + + describe 'without value', -> + it 'return current volume', -> + expect(@player.volume()).toEqual 42 + + describe 'with value', -> + it 'set player volume', -> + @player.volume(60) + expect(@player.player.setVolume).toHaveBeenCalledWith(60) diff --git a/lms/static/coffee/spec/modules/video/video_speed_control_spec.coffee b/lms/static/coffee/spec/modules/video/video_speed_control_spec.coffee index 9d52ac9b0d..737460d1ce 100644 --- a/lms/static/coffee/spec/modules/video/video_speed_control_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_speed_control_spec.coffee @@ -3,8 +3,6 @@ describe 'VideoSpeedControl', -> @player = jasmine.stubVideoPlayer @ $('.speeds').remove() - afterEach -> - describe 'constructor', -> describe 'always', -> beforeEach -> diff --git a/lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee b/lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee new file mode 100644 index 0000000000..cbdef03ef0 --- /dev/null +++ b/lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee @@ -0,0 +1,102 @@ +describe 'VideoVolumeControl', -> + beforeEach -> + @player = jasmine.stubVideoPlayer @ + $('.volume').remove() + + describe 'constructor', -> + beforeEach -> + spyOn($.fn, 'slider') + @volumeControl = new VideoVolumeControl @player + + it 'initialize previousVolume to 100', -> + expect(@volumeControl.previousVolume).toEqual 100 + + it 'render the volume control', -> + expect($('.secondary-controls').html()).toContain """ +
+ +
+
+
+
+ """ + + it 'create the slider', -> + expect($.fn.slider).toHaveBeenCalledWith + orientation: "vertical" + range: "min" + min: 0 + max: 100 + value: 100 + change: @volumeControl.onChange + slide: @volumeControl.onChange + + it 'bind the volume control', -> + expect($(@player)).toHandleWith 'ready', @volumeControl.onReady + expect($('.volume>a')).toHandleWith 'click', @volumeControl.toggleMute + + expect($('.volume')).not.toHaveClass 'open' + $('.volume').mouseenter() + expect($('.volume')).toHaveClass 'open' + $('.volume').mouseleave() + expect($('.volume')).not.toHaveClass 'open' + + describe 'onReady', -> + beforeEach -> + @volumeControl = new VideoVolumeControl @player + spyOn $.fn, 'slider' + spyOn(@player, 'volume').andReturn 60 + @volumeControl.onReady() + + it 'set the max value of the slider', -> + expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 60 + + describe 'onChange', -> + beforeEach -> + spyOn @player, 'volume' + @volumeControl = new VideoVolumeControl @player + + describe 'when the new volume is more than 0', -> + beforeEach -> + @volumeControl.onChange undefined, value: 60 + + it 'set the player volume', -> + expect(@player.volume).toHaveBeenCalledWith 60 + + it 'remote muted class', -> + expect($('.volume')).not.toHaveClass 'muted' + + describe 'when the new volume is 0', -> + beforeEach -> + @volumeControl.onChange undefined, value: 0 + + it 'set the player volume', -> + expect(@player.volume).toHaveBeenCalledWith 0 + + it 'add muted class', -> + expect($('.volume')).toHaveClass 'muted' + + describe 'toggleMute', -> + beforeEach -> + spyOn @player, 'volume' + @volumeControl = new VideoVolumeControl @player + + describe 'when the current volume is more than 0', -> + beforeEach -> + @player.volume.andReturn 60 + @volumeControl.toggleMute() + + it 'save the previous volume', -> + expect(@volumeControl.previousVolume).toEqual 60 + + it 'set the player volume', -> + expect(@player.volume).toHaveBeenCalledWith 0 + + describe 'when the current volume is 0', -> + beforeEach -> + @player.volume.andReturn 0 + @volumeControl.previousVolume = 60 + @volumeControl.toggleMute() + + it 'set the player volume to previous volume', -> + expect(@player.volume).toHaveBeenCalledWith 60 diff --git a/lms/static/coffee/src/modules/video/video_player.coffee b/lms/static/coffee/src/modules/video/video_player.coffee index 28f87b2d53..cccc6873b1 100644 --- a/lms/static/coffee/src/modules/video/video_player.coffee +++ b/lms/static/coffee/src/modules/video/video_player.coffee @@ -30,6 +30,7 @@ class @VideoPlayer render: -> new VideoControl @ new VideoCaption @, @video.youtubeId('1.0') + new VideoVolumeControl @ new VideoSpeedControl @, @video.speeds new VideoProgressSlider @ @player = new YT.Player @video.id, @@ -132,3 +133,9 @@ class @VideoPlayer currentSpeed: -> @video.speed + + volume: (value) -> + if value != undefined + @player.setVolume value + else + @player.getVolume() diff --git a/lms/static/coffee/src/modules/video/video_volume_control.coffee b/lms/static/coffee/src/modules/video/video_volume_control.coffee new file mode 100644 index 0000000000..1c0d3440d5 --- /dev/null +++ b/lms/static/coffee/src/modules/video/video_volume_control.coffee @@ -0,0 +1,48 @@ +class @VideoVolumeControl + constructor: (@player) -> + @previousVolume = 100 + @render() + @bind() + + $: (selector) -> + @player.$(selector) + + bind: -> + $(@player).bind('ready', @onReady) + @$('.volume').mouseenter -> + $(this).addClass('open') + @$('.volume').mouseleave -> + $(this).removeClass('open') + @$('.volume>a').click(@toggleMute) + + render: -> + @$('.secondary-controls').prepend """ +
+ +
+
+
+
+ """ + @slider = @$('.volume-slider').slider + orientation: "vertical" + range: "min" + min: 0 + max: 100 + value: 100 + change: @onChange + slide: @onChange + + onReady: => + @slider.slider 'option', 'max', @player.volume() + + onChange: (event, ui) => + @player.volume ui.value + @$('.secondary-controls .volume').toggleClass 'muted', ui.value == 0 + + toggleMute: => + if @player.volume() > 0 + @previousVolume = @player.volume() + @slider.slider 'option', 'value', 0 + else + @slider.slider 'option', 'value', @previousVolume diff --git a/lms/static/sass_old/courseware/_video.scss b/lms/static/sass_old/courseware/_video.scss index 7130cc8b7b..f6c6ac5a98 100644 --- a/lms/static/sass_old/courseware/_video.scss +++ b/lms/static/sass_old/courseware/_video.scss @@ -286,6 +286,86 @@ section.course-content { } } + div.volume { + float: left; + position: relative; + + &.open { + .volume-slider-container { + display: block; + opacity: 1; + } + } + + &.muted { + &>a { + // TODO: Replace this with muted speaker icon + background: url('../images/closed-arrow.png') 10px center no-repeat; + } + } + + &>a { + // TODO: Replace this with speaker icon + background: url('../images/open-arrow.png') 10px center no-repeat; + border-right: 1px solid #000; + @include box-shadow(1px 0 0 #555, inset 1px 0 0 #555); + @include clearfix(); + color: #fff; + cursor: pointer; + display: block; + height: 46px; + margin-right: 0; + padding-left: 15px; + position: relative; + @include transition(); + -webkit-font-smoothing: antialiased; + width: 30px; + + &:hover, &:active, &:focus { + background-color: #444; + } + } + + .volume-slider-container { + @include box-shadow(inset 1px 0 0 #555, 0 3px 0 #444); + @include transition(); + background-color: #444; + border: 1px solid #000; + bottom: 46px; + display: none; + opacity: 0; + position: absolute; + width: 35px; + height: 125px; + margin-left: 5px; + z-index: 10; + + .volume-slider { + height: 100px; + border: 0; + width: 5px; + margin: 14px auto; + + a.ui-slider-handle { + background: $mit-red url(../images/slider-handle.png) center center no-repeat; + @include background-size(50%); + border: 1px solid darken($mit-red, 20%); + @include border-radius(15px); + @include box-shadow(inset 0 1px 0 lighten($mit-red, 10%)); + cursor: pointer; + height: 15px; + left: -6px; + @include transition(height 2.0s ease-in-out, width 2.0s ease-in-out); + width: 15px; + } + + .ui-slider-range { + background: #666; + } + } + } + } + a.add-fullscreen { background: url(../images/fullscreen.png) center no-repeat; border-right: 1px solid #000;