Commit f2cc2fc5 by Prem Sichanugrist Committed by Matthew Mongeau

Add video volume control to video player

parent 71590419
...@@ -30,7 +30,8 @@ jasmine.stubRequests = -> ...@@ -30,7 +30,8 @@ jasmine.stubRequests = ->
jasmine.stubYoutubePlayer = -> jasmine.stubYoutubePlayer = ->
YT.Player = -> jasmine.createSpyObj 'YT.Player', ['cueVideoById', 'getVideoEmbedCode', 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) -> jasmine.stubVideoPlayer = (context, enableParts) ->
enableParts = [enableParts] unless $.isArray(enableParts) enableParts = [enableParts] unless $.isArray(enableParts)
......
...@@ -387,3 +387,17 @@ describe 'VideoPlayer', -> ...@@ -387,3 +387,17 @@ describe 'VideoPlayer', ->
it 'delegate to the video', -> it 'delegate to the video', ->
expect(@player.currentSpeed()).toEqual '3.0' 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)
...@@ -3,8 +3,6 @@ describe 'VideoSpeedControl', -> ...@@ -3,8 +3,6 @@ describe 'VideoSpeedControl', ->
@player = jasmine.stubVideoPlayer @ @player = jasmine.stubVideoPlayer @
$('.speeds').remove() $('.speeds').remove()
afterEach ->
describe 'constructor', -> describe 'constructor', ->
describe 'always', -> describe 'always', ->
beforeEach -> beforeEach ->
......
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 """
<div class="volume">
<a href="#"></a>
<div class="volume-slider-container">
<div class="volume-slider"></div>
</div>
</div>
"""
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
...@@ -30,6 +30,7 @@ class @VideoPlayer ...@@ -30,6 +30,7 @@ class @VideoPlayer
render: -> render: ->
new VideoControl @ new VideoControl @
new VideoCaption @, @video.youtubeId('1.0') new VideoCaption @, @video.youtubeId('1.0')
new VideoVolumeControl @
new VideoSpeedControl @, @video.speeds new VideoSpeedControl @, @video.speeds
new VideoProgressSlider @ new VideoProgressSlider @
@player = new YT.Player @video.id, @player = new YT.Player @video.id,
...@@ -132,3 +133,9 @@ class @VideoPlayer ...@@ -132,3 +133,9 @@ class @VideoPlayer
currentSpeed: -> currentSpeed: ->
@video.speed @video.speed
volume: (value) ->
if value != undefined
@player.setVolume value
else
@player.getVolume()
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 """
<div class="volume">
<a href="#"></a>
<div class="volume-slider-container">
<div class="volume-slider"></div>
</div>
</div>
"""
@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
...@@ -286,6 +286,86 @@ section.course-content { ...@@ -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 { a.add-fullscreen {
background: url(../images/fullscreen.png) center no-repeat; background: url(../images/fullscreen.png) center no-repeat;
border-right: 1px solid #000; border-right: 1px solid #000;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment