Commit 4e183dd6 by Prem Sichanugrist

Clean up events binding in Video Player

* Video Player now acting as a parent that always knowing the state of
  their children.
* Events on the sub-controls are now triggered on itself, and binded by
  the Video Player instead of triggering everything on the Video Player.
* A new helper class, SubView, has been introduced to cleanup repeat
  logic on scoped jQuery selector, render() and bind()
parent 207777fb
...@@ -52,7 +52,7 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) -> ...@@ -52,7 +52,7 @@ jasmine.stubVideoPlayer = (context, enableParts, createPlayer=true) ->
context.video = new Video 'example', '.75:abc123,1.0:def456' context.video = new Video 'example', '.75:abc123,1.0:def456'
jasmine.stubYoutubePlayer() jasmine.stubYoutubePlayer()
if createPlayer if createPlayer
return new VideoPlayer context.video return new VideoPlayer(video: context.video)
spyOn(window, 'onunload') spyOn(window, 'onunload')
......
describe 'VideoControl', -> describe 'VideoControl', ->
beforeEach -> beforeEach ->
@player = jasmine.stubVideoPlayer @ jasmine.stubVideoPlayer @
$('.video-controls').html '' $('.video-controls').html ''
describe 'constructor', -> describe 'constructor', ->
it 'render the video controls', -> it 'render the video controls', ->
new VideoControl @player new VideoControl(element: $('.video-controls'))
expect($('.video-controls').html()).toContain ''' expect($('.video-controls').html()).toContain '''
<div class="slider"></div> <div class="slider"></div>
<div> <div>
...@@ -21,14 +21,8 @@ describe 'VideoControl', -> ...@@ -21,14 +21,8 @@ describe 'VideoControl', ->
</div> </div>
''' '''
it 'bind player events', ->
control = new VideoControl @player
expect($(@player)).toHandleWith 'play', control.onPlay
expect($(@player)).toHandleWith 'pause', control.onPause
expect($(@player)).toHandleWith 'ended', control.onPause
it 'bind the playback button', -> it 'bind the playback button', ->
control = new VideoControl @player control = new VideoControl(element: $('.video-controls'))
expect($('.video_control')).toHandleWith 'click', control.togglePlayback expect($('.video_control')).toHandleWith 'click', control.togglePlayback
describe 'when on a touch based device', -> describe 'when on a touch based device', ->
...@@ -36,7 +30,7 @@ describe 'VideoControl', -> ...@@ -36,7 +30,7 @@ describe 'VideoControl', ->
spyOn(window, 'onTouchBasedDevice').andReturn true spyOn(window, 'onTouchBasedDevice').andReturn true
it 'does not add the play class to video control', -> it 'does not add the play class to video control', ->
new VideoControl @player new VideoControl(element: $('.video-controls'))
expect($('.video_control')).not.toHaveClass 'play' expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).not.toHaveHtml 'Play' expect($('.video_control')).not.toHaveHtml 'Play'
...@@ -46,24 +40,24 @@ describe 'VideoControl', -> ...@@ -46,24 +40,24 @@ describe 'VideoControl', ->
spyOn(window, 'onTouchBasedDevice').andReturn false spyOn(window, 'onTouchBasedDevice').andReturn false
it 'add the play class to video control', -> it 'add the play class to video control', ->
new VideoControl @player new VideoControl(element: $('.video-controls'))
expect($('.video_control')).toHaveClass 'play' expect($('.video_control')).toHaveClass 'play'
expect($('.video_control')).toHaveHtml 'Play' expect($('.video_control')).toHaveHtml 'Play'
describe 'onPlay', -> describe 'play', ->
beforeEach -> beforeEach ->
@control = new VideoControl @player @control = new VideoControl(element: $('.video-controls'))
@control.onPlay() @control.play()
it 'switch playback button to play state', -> it 'switch playback button to play state', ->
expect($('.video_control')).not.toHaveClass 'play' expect($('.video_control')).not.toHaveClass 'play'
expect($('.video_control')).toHaveClass 'pause' expect($('.video_control')).toHaveClass 'pause'
expect($('.video_control')).toHaveHtml 'Pause' expect($('.video_control')).toHaveHtml 'Pause'
describe 'onPause', -> describe 'pause', ->
beforeEach -> beforeEach ->
@control = new VideoControl @player @control = new VideoControl(element: $('.video-controls'))
@control.onPause() @control.pause()
it 'switch playback button to pause state', -> it 'switch playback button to pause state', ->
expect($('.video_control')).not.toHaveClass 'pause' expect($('.video_control')).not.toHaveClass 'pause'
...@@ -72,7 +66,7 @@ describe 'VideoControl', -> ...@@ -72,7 +66,7 @@ describe 'VideoControl', ->
describe 'togglePlayback', -> describe 'togglePlayback', ->
beforeEach -> beforeEach ->
@control = new VideoControl @player @control = new VideoControl(element: $('.video-controls'))
describe 'when the control does not have play or pause class', -> describe 'when the control does not have play or pause class', ->
beforeEach -> beforeEach ->
...@@ -80,41 +74,36 @@ describe 'VideoControl', -> ...@@ -80,41 +74,36 @@ describe 'VideoControl', ->
describe 'when the video is playing', -> describe 'when the video is playing', ->
beforeEach -> beforeEach ->
spyOn(@player, 'isPlaying').andReturn true $('.video_control').addClass('play')
spyOnEvent @player, 'pause' spyOnEvent @control, 'pause'
@control.togglePlayback jQuery.Event('click') @control.togglePlayback jQuery.Event('click')
it 'does not trigger the pause event', -> it 'does not trigger the pause event', ->
expect('pause').not.toHaveBeenTriggeredOn @player expect('pause').not.toHaveBeenTriggeredOn @control
describe 'when the video is paused', -> describe 'when the video is paused', ->
beforeEach -> beforeEach ->
spyOn(@player, 'isPlaying').andReturn false $('.video_control').addClass('pause')
spyOnEvent @player, 'play' spyOnEvent @control, 'play'
@control.togglePlayback jQuery.Event('click') @control.togglePlayback jQuery.Event('click')
it 'does not trigger the play event', -> it 'does not trigger the play event', ->
expect('play').not.toHaveBeenTriggeredOn @player expect('play').not.toHaveBeenTriggeredOn @control
for className in ['play', 'pause']
describe "when the control has #{className} class", ->
beforeEach ->
$('.video_control').addClass className
describe 'when the video is playing', -> describe 'when the video is playing', ->
beforeEach -> beforeEach ->
spyOn(@player, 'isPlaying').andReturn true spyOnEvent @control, 'pause'
spyOnEvent @player, 'pause' $('.video_control').addClass 'pause'
@control.togglePlayback jQuery.Event('click') @control.togglePlayback jQuery.Event('click')
it 'trigger the pause event', -> it 'trigger the pause event', ->
expect('pause').toHaveBeenTriggeredOn @player expect('pause').toHaveBeenTriggeredOn @control
describe 'when the video is paused', -> describe 'when the video is paused', ->
beforeEach -> beforeEach ->
spyOn(@player, 'isPlaying').andReturn false spyOnEvent @control, 'play'
spyOnEvent @player, 'play' $('.video_control').addClass 'play'
@control.togglePlayback jQuery.Event('click') @control.togglePlayback jQuery.Event('click')
it 'trigger the play event', -> it 'trigger the play event', ->
expect('play').toHaveBeenTriggeredOn @player expect('play').toHaveBeenTriggeredOn @control
describe 'VideoProgressSlider', -> describe 'VideoProgressSlider', ->
beforeEach -> beforeEach ->
@player = jasmine.stubVideoPlayer @ jasmine.stubVideoPlayer @
describe 'constructor', -> describe 'constructor', ->
describe 'on a non-touch based device', -> describe 'on a non-touch based device', ->
beforeEach -> beforeEach ->
spyOn($.fn, 'slider').andCallThrough() spyOn($.fn, 'slider').andCallThrough()
spyOn(window, 'onTouchBasedDevice').andReturn false spyOn(window, 'onTouchBasedDevice').andReturn false
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
it 'build the slider', -> it 'build the slider', ->
expect(@slider.slider).toBe '.slider' expect(@slider.slider).toBe '.slider'
...@@ -18,7 +18,7 @@ describe 'VideoProgressSlider', -> ...@@ -18,7 +18,7 @@ describe 'VideoProgressSlider', ->
stop: @slider.onStop stop: @slider.onStop
it 'build the seek handle', -> it 'build the seek handle', ->
expect(@slider.handle).toBe '.slider .ui-slider-handle' expect(@slider.handle).toBe '.ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00" content: "0:00"
position: position:
...@@ -31,41 +31,24 @@ describe 'VideoProgressSlider', -> ...@@ -31,41 +31,24 @@ describe 'VideoProgressSlider', ->
classes: 'ui-tooltip-slider' classes: 'ui-tooltip-slider'
widget: true widget: true
it 'bind player events', ->
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
expect($(@player)).toHandleWith 'ready', @slider.onReady
expect($(@player)).toHandleWith 'play', @slider.onPlay
describe 'on a touch-based device', -> describe 'on a touch-based device', ->
beforeEach -> beforeEach ->
spyOn($.fn, 'slider').andCallThrough() spyOn($.fn, 'slider').andCallThrough()
spyOn(window, 'onTouchBasedDevice').andReturn true spyOn(window, 'onTouchBasedDevice').andReturn true
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
it 'does not build the slider', -> it 'does not build the slider', ->
expect(@slider.slider).toBeUndefined expect(@slider.slider).toBeUndefined
expect($.fn.slider).not.toHaveBeenCalled() expect($.fn.slider).not.toHaveBeenCalled()
it 'bind player events', -> describe 'play', ->
expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
describe 'onReady', ->
beforeEach ->
spyOn(@player, 'duration').andReturn 120
@slider = new VideoProgressSlider @player
@slider.onReady()
it 'set the max value to the length of video', ->
expect(@slider.slider.slider('option', 'max')).toEqual 120
describe 'onPlay', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
spyOn($.fn, 'slider').andCallThrough() spyOn($.fn, 'slider').andCallThrough()
describe 'when the slider was already built', -> describe 'when the slider was already built', ->
beforeEach -> beforeEach ->
@slider.onPlay() @slider.play()
it 'does not build the slider', -> it 'does not build the slider', ->
expect($.fn.slider).not.toHaveBeenCalled expect($.fn.slider).not.toHaveBeenCalled
...@@ -73,7 +56,7 @@ describe 'VideoProgressSlider', -> ...@@ -73,7 +56,7 @@ describe 'VideoProgressSlider', ->
describe 'when the slider was not already built', -> describe 'when the slider was not already built', ->
beforeEach -> beforeEach ->
@slider.slider = null @slider.slider = null
@slider.onPlay() @slider.play()
it 'build the slider', -> it 'build the slider', ->
expect(@slider.slider).toBe '.slider' expect(@slider.slider).toBe '.slider'
...@@ -97,16 +80,15 @@ describe 'VideoProgressSlider', -> ...@@ -97,16 +80,15 @@ describe 'VideoProgressSlider', ->
classes: 'ui-tooltip-slider' classes: 'ui-tooltip-slider'
widget: true widget: true
describe 'onUpdatePlayTime', -> describe 'updatePlayTime', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
spyOn(@player, 'duration').andReturn 120
spyOn($.fn, 'slider').andCallThrough() spyOn($.fn, 'slider').andCallThrough()
describe 'when frozen', -> describe 'when frozen', ->
beforeEach -> beforeEach ->
@slider.frozen = true @slider.frozen = true
@slider.onUpdatePlayTime {}, 20 @slider.updatePlayTime 20, 120
it 'does not update the slider', -> it 'does not update the slider', ->
expect($.fn.slider).not.toHaveBeenCalled() expect($.fn.slider).not.toHaveBeenCalled()
...@@ -114,7 +96,7 @@ describe 'VideoProgressSlider', -> ...@@ -114,7 +96,7 @@ describe 'VideoProgressSlider', ->
describe 'when not frozen', -> describe 'when not frozen', ->
beforeEach -> beforeEach ->
@slider.frozen = false @slider.frozen = false
@slider.onUpdatePlayTime {}, 20 @slider.updatePlayTime 20, 120
it 'update the max value of the slider', -> it 'update the max value of the slider', ->
expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 120 expect($.fn.slider).toHaveBeenCalledWith 'option', 'max', 120
...@@ -124,10 +106,10 @@ describe 'VideoProgressSlider', -> ...@@ -124,10 +106,10 @@ describe 'VideoProgressSlider', ->
describe 'onSlide', -> describe 'onSlide', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
@time = null @time = null
$(@player).bind 'seek', (event, time) => @time = time $(@slider).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek' spyOnEvent @slider, 'seek'
@slider.onSlide {}, value: 20 @slider.onSlide {}, value: 20
it 'freeze the slider', -> it 'freeze the slider', ->
...@@ -137,12 +119,12 @@ describe 'VideoProgressSlider', -> ...@@ -137,12 +119,12 @@ describe 'VideoProgressSlider', ->
expect($.fn.qtip).toHaveBeenCalled() expect($.fn.qtip).toHaveBeenCalled()
it 'trigger seek event', -> it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player expect('seek').toHaveBeenTriggeredOn @slider
expect(@time).toEqual 20 expect(@time).toEqual 20
describe 'onChange', -> describe 'onChange', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
@slider.onChange {}, value: 20 @slider.onChange {}, value: 20
it 'update the tooltip', -> it 'update the tooltip', ->
...@@ -150,10 +132,10 @@ describe 'VideoProgressSlider', -> ...@@ -150,10 +132,10 @@ describe 'VideoProgressSlider', ->
describe 'onStop', -> describe 'onStop', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
@time = null @time = null
$(@player).bind 'seek', (event, time) => @time = time $(@slider).bind 'seek', (event, time) => @time = time
spyOnEvent @player, 'seek' spyOnEvent @slider, 'seek'
spyOn(window, 'setTimeout') spyOn(window, 'setTimeout')
@slider.onStop {}, value: 20 @slider.onStop {}, value: 20
...@@ -161,7 +143,7 @@ describe 'VideoProgressSlider', -> ...@@ -161,7 +143,7 @@ describe 'VideoProgressSlider', ->
expect(@slider.frozen).toBeTruthy() expect(@slider.frozen).toBeTruthy()
it 'trigger seek event', -> it 'trigger seek event', ->
expect('seek').toHaveBeenTriggeredOn @player expect('seek').toHaveBeenTriggeredOn @slider
expect(@time).toEqual 20 expect(@time).toEqual 20
it 'set timeout to unfreeze the slider', -> it 'set timeout to unfreeze the slider', ->
...@@ -171,7 +153,7 @@ describe 'VideoProgressSlider', -> ...@@ -171,7 +153,7 @@ describe 'VideoProgressSlider', ->
describe 'updateTooltip', -> describe 'updateTooltip', ->
beforeEach -> beforeEach ->
@slider = new VideoProgressSlider @player @slider = new VideoProgressSlider element: $('.slider')
@slider.updateTooltip 90 @slider.updateTooltip 90
it 'set the tooltip value', -> it 'set the tooltip value', ->
......
describe 'VideoSpeedControl', -> describe 'VideoSpeedControl', ->
beforeEach -> beforeEach ->
@player = jasmine.stubVideoPlayer @ jasmine.stubVideoPlayer @
$('.speeds').remove() $('.speeds').remove()
describe 'constructor', -> describe 'constructor', ->
describe 'always', -> describe 'always', ->
beforeEach -> beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds @speedControl = new VideoSpeedControl element: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'add the video speed control to player', -> it 'add the video speed control to player', ->
expect($('.secondary-controls').html()).toContain ''' expect($('.secondary-controls').html()).toContain '''
...@@ -19,9 +19,6 @@ describe 'VideoSpeedControl', -> ...@@ -19,9 +19,6 @@ describe 'VideoSpeedControl', ->
</div> </div>
''' '''
it 'bind to player speedChange event', ->
expect($(@player)).toHandleWith 'speedChange', @speedControl.onSpeedChange
it 'bind to change video speed link', -> it 'bind to change video speed link', ->
expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed expect($('.video_speeds a')).toHandleWith 'click', @speedControl.changeVideoSpeed
...@@ -29,7 +26,7 @@ describe 'VideoSpeedControl', -> ...@@ -29,7 +26,7 @@ describe 'VideoSpeedControl', ->
beforeEach -> beforeEach ->
spyOn(window, 'onTouchBasedDevice').andReturn true spyOn(window, 'onTouchBasedDevice').andReturn true
$('.speeds').removeClass 'open' $('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds @speedControl = new VideoSpeedControl element: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'open the speed toggle on click', -> it 'open the speed toggle on click', ->
$('.speeds').click() $('.speeds').click()
...@@ -41,7 +38,7 @@ describe 'VideoSpeedControl', -> ...@@ -41,7 +38,7 @@ describe 'VideoSpeedControl', ->
beforeEach -> beforeEach ->
spyOn(window, 'onTouchBasedDevice').andReturn false spyOn(window, 'onTouchBasedDevice').andReturn false
$('.speeds').removeClass 'open' $('.speeds').removeClass 'open'
@speedControl = new VideoSpeedControl @player, @video.speeds @speedControl = new VideoSpeedControl element: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
it 'open the speed toggle on hover', -> it 'open the speed toggle on hover', ->
$('.speeds').mouseenter() $('.speeds').mouseenter()
...@@ -59,31 +56,31 @@ describe 'VideoSpeedControl', -> ...@@ -59,31 +56,31 @@ describe 'VideoSpeedControl', ->
describe 'changeVideoSpeed', -> describe 'changeVideoSpeed', ->
beforeEach -> beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds @speedControl = new VideoSpeedControl element: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
@video.setSpeed '1.0' @video.setSpeed '1.0'
describe 'when new speed is the same', -> describe 'when new speed is the same', ->
beforeEach -> beforeEach ->
spyOnEvent @player, 'speedChange' spyOnEvent @speedControl, 'speedChange'
$('li[data-speed="1.0"] a').click() $('li[data-speed="1.0"] a').click()
it 'does not trigger speedChange event', -> it 'does not trigger speedChange event', ->
expect('speedChange').not.toHaveBeenTriggeredOn @player expect('speedChange').not.toHaveBeenTriggeredOn @speedControl
describe 'when new speed is not the same', -> describe 'when new speed is not the same', ->
beforeEach -> beforeEach ->
@newSpeed = null @newSpeed = null
$(@player).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed $(@speedControl).bind 'speedChange', (event, newSpeed) => @newSpeed = newSpeed
spyOnEvent @player, 'speedChange' spyOnEvent @speedControl, 'speedChange'
$('li[data-speed="0.75"] a').click() $('li[data-speed="0.75"] a').click()
it 'trigger player speedChange event', -> it 'trigger speedChange event', ->
expect('speedChange').toHaveBeenTriggeredOn @player expect('speedChange').toHaveBeenTriggeredOn @speedControl
expect(@newSpeed).toEqual 0.75 expect(@newSpeed).toEqual 0.75
describe 'onSpeedChange', -> describe 'onSpeedChange', ->
beforeEach -> beforeEach ->
@speedControl = new VideoSpeedControl @player, @video.speeds @speedControl = new VideoSpeedControl element: $('.secondary-controls'), speeds: @video.speeds, currentSpeed: '1.0'
$('li[data-speed="1.0"] a').addClass 'active' $('li[data-speed="1.0"] a').addClass 'active'
@speedControl.setSpeed '0.75' @speedControl.setSpeed '0.75'
......
describe 'VideoVolumeControl', -> describe 'VideoVolumeControl', ->
beforeEach -> beforeEach ->
@player = jasmine.stubVideoPlayer @ jasmine.stubVideoPlayer @
$('.volume').remove() $('.volume').remove()
describe 'constructor', -> describe 'constructor', ->
beforeEach -> beforeEach ->
spyOn($.fn, 'slider') spyOn($.fn, 'slider')
@volumeControl = new VideoVolumeControl @player @volumeControl = new VideoVolumeControl element: $('.secondary-controls')
it 'initialize previousVolume to 100', -> it 'initialize currentVolume to 100', ->
expect(@volumeControl.previousVolume).toEqual 100 expect(@volumeControl.currentVolume).toEqual 100
it 'render the volume control', -> it 'render the volume control', ->
expect($('.secondary-controls').html()).toContain """ expect($('.secondary-controls').html()).toContain """
...@@ -32,7 +32,6 @@ describe 'VideoVolumeControl', -> ...@@ -32,7 +32,6 @@ describe 'VideoVolumeControl', ->
slide: @volumeControl.onChange slide: @volumeControl.onChange
it 'bind the volume control', -> it 'bind the volume control', ->
expect($(@player)).toHandleWith 'ready', @volumeControl.onReady
expect($('.volume>a')).toHandleWith 'click', @volumeControl.toggleMute expect($('.volume>a')).toHandleWith 'click', @volumeControl.toggleMute
expect($('.volume')).not.toHaveClass 'open' expect($('.volume')).not.toHaveClass 'open'
...@@ -41,27 +40,19 @@ describe 'VideoVolumeControl', -> ...@@ -41,27 +40,19 @@ describe 'VideoVolumeControl', ->
$('.volume').mouseleave() $('.volume').mouseleave()
expect($('.volume')).not.toHaveClass 'open' 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', -> describe 'onChange', ->
beforeEach -> beforeEach ->
spyOn @player, 'volume' spyOnEvent @volumeControl, 'volumeChange'
@volumeControl = new VideoVolumeControl @player @newVolume = undefined
@volumeControl = new VideoVolumeControl element: $('.secondary-controls')
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
describe 'when the new volume is more than 0', -> describe 'when the new volume is more than 0', ->
beforeEach -> beforeEach ->
@volumeControl.onChange undefined, value: 60 @volumeControl.onChange undefined, value: 60
it 'set the player volume', -> it 'set the player volume', ->
expect(@player.volume).toHaveBeenCalledWith 60 expect(@newVolume).toEqual 60
it 'remote muted class', -> it 'remote muted class', ->
expect($('.volume')).not.toHaveClass 'muted' expect($('.volume')).not.toHaveClass 'muted'
...@@ -71,32 +62,33 @@ describe 'VideoVolumeControl', -> ...@@ -71,32 +62,33 @@ describe 'VideoVolumeControl', ->
@volumeControl.onChange undefined, value: 0 @volumeControl.onChange undefined, value: 0
it 'set the player volume', -> it 'set the player volume', ->
expect(@player.volume).toHaveBeenCalledWith 0 expect(@newVolume).toEqual 0
it 'add muted class', -> it 'add muted class', ->
expect($('.volume')).toHaveClass 'muted' expect($('.volume')).toHaveClass 'muted'
describe 'toggleMute', -> describe 'toggleMute', ->
beforeEach -> beforeEach ->
spyOn @player, 'volume' @newVolume = undefined
@volumeControl = new VideoVolumeControl @player @volumeControl = new VideoVolumeControl element: $('.secondary-controls')
$(@volumeControl).bind 'volumeChange', (event, volume) => @newVolume = volume
describe 'when the current volume is more than 0', -> describe 'when the current volume is more than 0', ->
beforeEach -> beforeEach ->
@player.volume.andReturn 60 @volumeControl.currentVolume = 60
@volumeControl.toggleMute() @volumeControl.toggleMute()
it 'save the previous volume', -> it 'save the previous volume', ->
expect(@volumeControl.previousVolume).toEqual 60 expect(@volumeControl.previousVolume).toEqual 60
it 'set the player volume', -> it 'set the player volume', ->
expect(@player.volume).toHaveBeenCalledWith 0 expect(@newVolume).toEqual 0
describe 'when the current volume is 0', -> describe 'when the current volume is 0', ->
beforeEach -> beforeEach ->
@player.volume.andReturn 0 @volumeControl.currentVolume = 0
@volumeControl.previousVolume = 60 @volumeControl.previousVolume = 60
@volumeControl.toggleMute() @volumeControl.toggleMute()
it 'set the player volume to previous volume', -> it 'set the player volume to previous volume', ->
expect(@player.volume).toHaveBeenCalledWith 60 expect(@newVolume).toEqual 60
...@@ -57,7 +57,7 @@ describe 'Video', -> ...@@ -57,7 +57,7 @@ describe 'Video', ->
window.YT = @originalYT window.YT = @originalYT
it 'create the Video Player', -> it 'create the Video Player', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video expect(window.VideoPlayer).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayer expect(@video.player).toEqual @stubVideoPlayer
describe 'when the Youtube API is not ready', -> describe 'when the Youtube API is not ready', ->
...@@ -84,7 +84,7 @@ describe 'Video', -> ...@@ -84,7 +84,7 @@ describe 'Video', ->
window.YT = @originalYT window.YT = @originalYT
it 'create the Video Player for all video elements', -> it 'create the Video Player for all video elements', ->
expect(window.VideoPlayer).toHaveBeenCalledWith @video expect(window.VideoPlayer).toHaveBeenCalledWith(video: @video)
expect(@video.player).toEqual @stubVideoPlayer expect(@video.player).toEqual @stubVideoPlayer
describe 'youtubeId', -> describe 'youtubeId', ->
......
class @Subview
constructor: (options) ->
$.each options, (key, value) =>
@[key] = value
@initialize()
@render()
@bind()
$: (selector) ->
$(selector, @element)
initialize: ->
render: ->
bind: ->
...@@ -36,7 +36,7 @@ class @Video ...@@ -36,7 +36,7 @@ class @Video
@speed = '1.0' @speed = '1.0'
embed: -> embed: ->
@player = new VideoPlayer(this) @player = new VideoPlayer video: this
fetchMetadata: (url) -> fetchMetadata: (url) ->
@metadata = {} @metadata = {}
......
class @VideoCaption class @VideoCaption extends Subview
constructor: (@player, @youtubeId) ->
@render()
@bind()
$: (selector) ->
@player.$(selector)
bind: -> bind: ->
$(window).bind('resize', @onWindowResize) $(window).bind('resize', @resize)
$(@player).bind('resize', @onWindowResize)
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
$(@player).bind('seek', @onUpdatePlayTime)
$(@player).bind('play', @onPlay)
@$('.hide-subtitles').click @toggle @$('.hide-subtitles').click @toggle
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave) @$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
.mousemove(@onMovement).bind('mousewheel', @onMovement) .mousemove(@onMovement).bind('mousewheel', @onMovement)
...@@ -70,12 +59,16 @@ class @VideoCaption ...@@ -70,12 +59,16 @@ class @VideoCaption
return min return min
onPlay: => play: ->
@renderCaption() unless @rendered @renderCaption() unless @rendered
@playing = true
pause: ->
@playing = false
onUpdatePlayTime: (event, time) => updatePlayTime: (time) ->
# This 250ms offset is required to match the video speed # This 250ms offset is required to match the video speed
time = Math.round(Time.convert(time, @player.currentSpeed(), '1.0') * 1000 + 250) time = Math.round(Time.convert(time, @currentSpeed, '1.0') * 1000 + 250)
newIndex = @search time newIndex = @search time
if newIndex != undefined && @currentIndex != newIndex if newIndex != undefined && @currentIndex != newIndex
...@@ -86,7 +79,7 @@ class @VideoCaption ...@@ -86,7 +79,7 @@ class @VideoCaption
@currentIndex = newIndex @currentIndex = newIndex
@scrollCaption() @scrollCaption()
onWindowResize: => resize: =>
@$('.subtitles').css maxHeight: @captionHeight() @$('.subtitles').css maxHeight: @captionHeight()
@$('.subtitles .spacing:first').height(@topSpacingHeight()) @$('.subtitles .spacing:first').height(@topSpacingHeight())
@$('.subtitles .spacing:last').height(@bottomSpacingHeight()) @$('.subtitles .spacing:last').height(@bottomSpacingHeight())
...@@ -102,7 +95,7 @@ class @VideoCaption ...@@ -102,7 +95,7 @@ class @VideoCaption
onMouseLeave: => onMouseLeave: =>
clearTimeout @frozen if @frozen clearTimeout @frozen if @frozen
@frozen = null @frozen = null
@scrollCaption() if @player.isPlaying() @scrollCaption() if @playing
scrollCaption: -> scrollCaption: ->
if !@frozen && @$('.subtitles .current:first').length if !@frozen && @$('.subtitles .current:first').length
...@@ -111,8 +104,8 @@ class @VideoCaption ...@@ -111,8 +104,8 @@ class @VideoCaption
seekPlayer: (event) => seekPlayer: (event) =>
event.preventDefault() event.preventDefault()
time = Math.round(Time.convert($(event.target).data('start'), '1.0', @player.currentSpeed()) / 1000) time = Math.round(Time.convert($(event.target).data('start'), '1.0', @currentSpeed) / 1000)
$(@player).trigger('seek', time) $(@).trigger('seek', time)
calculateOffset: (element) -> calculateOffset: (element) ->
@captionHeight() / 2 - element.height() / 2 @captionHeight() / 2 - element.height() / 2
...@@ -125,16 +118,16 @@ class @VideoCaption ...@@ -125,16 +118,16 @@ class @VideoCaption
toggle: (event) => toggle: (event) =>
event.preventDefault() event.preventDefault()
if @player.element.hasClass('closed') if @element.hasClass('closed')
@$('.hide-subtitles').attr('title', 'Turn off captions') @$('.hide-subtitles').attr('title', 'Turn off captions')
@player.element.removeClass('closed') @element.removeClass('closed')
@scrollCaption() @scrollCaption()
else else
@$('.hide-subtitles').attr('title', 'Turn on captions') @$('.hide-subtitles').attr('title', 'Turn on captions')
@player.element.addClass('closed') @element.addClass('closed')
captionHeight: -> captionHeight: ->
if @player.element.hasClass('fullscreen') if @element.hasClass('fullscreen')
$(window).height() - @$('.video-controls').height() $(window).height() - @$('.video-controls').height()
else else
@$('.video-wrapper').height() @$('.video-wrapper').height()
class @VideoControl class @VideoControl extends Subview
constructor: (@player) ->
@render()
@bind()
$: (selector) ->
@player.$(selector)
bind: -> bind: ->
$(@player).bind('play', @onPlay)
.bind('pause', @onPause)
.bind('ended', @onPause)
@$('.video_control').click @togglePlayback @$('.video_control').click @togglePlayback
render: -> render: ->
@$('.video-controls').append """ @element.append """
<div class="slider"></div> <div class="slider"></div>
<div> <div>
<ul class="vcr"> <ul class="vcr">
...@@ -31,16 +21,15 @@ class @VideoControl ...@@ -31,16 +21,15 @@ class @VideoControl
unless onTouchBasedDevice() unless onTouchBasedDevice()
@$('.video_control').addClass('play').html('Play') @$('.video_control').addClass('play').html('Play')
onPlay: => play: ->
@$('.video_control').removeClass('play').addClass('pause').html('Pause') @$('.video_control').removeClass('play').addClass('pause').html('Pause')
onPause: => pause: ->
@$('.video_control').removeClass('pause').addClass('play').html('Play') @$('.video_control').removeClass('pause').addClass('play').html('Play')
togglePlayback: (event) => togglePlayback: (event) =>
event.preventDefault() event.preventDefault()
if $('.video_control').hasClass('play') || $('.video_control').hasClass('pause') if @$('.video_control').hasClass('play')
if @player.isPlaying() $(@).trigger('play')
$(@player).trigger('pause') else if @$('.video_control').hasClass('pause')
else $(@).trigger('pause')
$(@player).trigger('play')
class @VideoPlayer class @VideoPlayer extends Subview
constructor: (@video) -> initialize: ->
# Define a missing constant of Youtube API # Define a missing constant of Youtube API
YT.PlayerState.UNSTARTED = -1 YT.PlayerState.UNSTARTED = -1
@currentTime = 0 @currentTime = 0
@element = $("#video_#{@video.id}") @element = $("#video_#{@video.id}")
@render()
@bind()
$: (selector) ->
$(selector, @element)
bind: -> bind: ->
$(@).bind('seek', @onSeek) $(@control).bind('play', @play)
.bind('updatePlayTime', @onUpdatePlayTime) .bind('pause', @pause)
.bind('speedChange', @onSpeedChange) $(@caption).bind('seek', @onSeek)
.bind('play', @onPlay) $(@speedControl).bind('speedChange', @onSpeedChange)
.bind('pause', @onPause) $(@progressSlider).bind('seek', @onSeek)
.bind('ended', @onPause) if @volumeControl
$(@volumeControl).bind('volumeChange', @onVolumeChange)
$(document).keyup @bindExitFullScreen $(document).keyup @bindExitFullScreen
@$('.add-fullscreen').click @toggleFullScreen @$('.add-fullscreen').click @toggleFullScreen
...@@ -28,11 +24,12 @@ class @VideoPlayer ...@@ -28,11 +24,12 @@ class @VideoPlayer
@toggleFullScreen(event) @toggleFullScreen(event)
render: -> render: ->
new VideoControl @ @control = new VideoControl element: @$('.video-controls')
new VideoCaption @, @video.youtubeId('1.0') @caption = new VideoCaption element: @element, youtubeId: @video.youtubeId('1.0'), currentSpeed: @currentSpeed()
new VideoVolumeControl @ unless onTouchBasedDevice() unless onTouchBasedDevice()
new VideoSpeedControl @, @video.speeds @volumeControl = new VideoVolumeControl element: @$('.secondary-controls')
new VideoProgressSlider @ @speedControl = new VideoSpeedControl element: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
@progressSlider = new VideoProgressSlider element: @$('.slider')
@player = new YT.Player @video.id, @player = new YT.Player @video.id,
playerVars: playerVars:
controls: 0 controls: 0
...@@ -52,19 +49,23 @@ class @VideoPlayer ...@@ -52,19 +49,23 @@ class @VideoPlayer
at: 'top center' at: 'top center'
onReady: => onReady: =>
$(@).trigger('ready')
$(@).trigger('updatePlayTime', 0)
unless onTouchBasedDevice() unless onTouchBasedDevice()
$('.course-content .video:first').data('video').player.play() $('.course-content .video:first').data('video').player.play()
onStateChange: (event) => onStateChange: (event) =>
switch event.data switch event.data
when YT.PlayerState.UNSTARTED
@onUnstarted()
when YT.PlayerState.PLAYING when YT.PlayerState.PLAYING
$(@).trigger('play') @onPlay()
when YT.PlayerState.PAUSED, YT.PlayerState.UNSTARTED when YT.PlayerState.PAUSED
$(@).trigger('pause') @onPause()
when YT.PlayerState.ENDED when YT.PlayerState.ENDED
$(@).trigger('ended') @onEnded()
onUnstarted: =>
@control.pause()
@caption.pause()
onPlay: => onPlay: =>
Logger.log 'play_video', id: @currentTime, code: @player.getVideoEmbedCode() Logger.log 'play_video', id: @currentTime, code: @player.getVideoEmbedCode()
...@@ -72,39 +73,55 @@ class @VideoPlayer ...@@ -72,39 +73,55 @@ class @VideoPlayer
window.player = @player window.player = @player
unless @player.interval unless @player.interval
@player.interval = setInterval(@update, 200) @player.interval = setInterval(@update, 200)
@caption.play()
@control.play()
@progressSlider.play()
onPause: => onPause: =>
Logger.log 'pause_video', id: @currentTime, code: @player.getVideoEmbedCode() Logger.log 'pause_video', id: @currentTime, code: @player.getVideoEmbedCode()
window.player = null if window.player == @player window.player = null if window.player == @player
clearInterval(@player.interval) clearInterval(@player.interval)
@player.interval = null @player.interval = null
@caption.pause()
@control.pause()
onEnded: =>
@control.pause()
@caption.pause()
onSeek: (event, time) -> onSeek: (event, time) =>
@player.seekTo(time, true) @player.seekTo(time, true)
if @isPlaying() if @isPlaying()
clearInterval(@player.interval) clearInterval(@player.interval)
@player.interval = setInterval(@update, 200) @player.interval = setInterval(@update, 200)
else else
@currentTime = time @currentTime = time
$(@).trigger('updatePlayTime', time) @updatePlayTime time
onSpeedChange: (event, newSpeed) => onSpeedChange: (event, newSpeed) =>
@currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed) @currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed)
@video.setSpeed(parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0') newSpeed = parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0'
@video.setSpeed(newSpeed)
@caption.currentSpeed = newSpeed
if @isPlaying() if @isPlaying()
@player.loadVideoById(@video.youtubeId(), @currentTime) @player.loadVideoById(@video.youtubeId(), @currentTime)
else else
@player.cueVideoById(@video.youtubeId(), @currentTime) @player.cueVideoById(@video.youtubeId(), @currentTime)
$(@).trigger('updatePlayTime', @currentTime) @updatePlayTime @currentTime
onVolumeChange: (event, volume) =>
@player.setVolume volume
update: => update: =>
if @currentTime = @player.getCurrentTime() if @currentTime = @player.getCurrentTime()
$(@).trigger('updatePlayTime', @currentTime) @updatePlayTime @currentTime
onUpdatePlayTime: (event, time) => updatePlayTime: (time) ->
progress = Time.format(time) + ' / ' + Time.format(@duration()) progress = Time.format(time) + ' / ' + Time.format(@duration())
@$(".vidtime").html(progress) @$(".vidtime").html(progress)
@caption.updatePlayTime(time)
@progressSlider.updatePlayTime(time, @duration())
toggleFullScreen: (event) => toggleFullScreen: (event) =>
event.preventDefault() event.preventDefault()
...@@ -116,26 +133,20 @@ class @VideoPlayer ...@@ -116,26 +133,20 @@ class @VideoPlayer
@element.append('<a href="#" class="exit">Exit</a>').addClass('fullscreen') @element.append('<a href="#" class="exit">Exit</a>').addClass('fullscreen')
@$('.add-fullscreen').attr('title', 'Exit fill browser') @$('.add-fullscreen').attr('title', 'Exit fill browser')
@$('.exit').click @toggleFullScreen @$('.exit').click @toggleFullScreen
$(@).trigger('resize') @caption.resize()
# Delegates # Delegates
play: -> play: =>
@player.playVideo() if @player.playVideo @player.playVideo() if @player.playVideo
isPlaying: -> isPlaying: ->
@player.getPlayerState() == YT.PlayerState.PLAYING @player.getPlayerState() == YT.PlayerState.PLAYING
pause: -> pause: =>
@player.pauseVideo() @player.pauseVideo() if @player.pauseVideo
duration: -> duration: ->
@video.getDuration() @video.getDuration()
currentSpeed: -> currentSpeed: ->
@video.speed @video.speed
volume: (value) ->
if value?
@player.setVolume value
else
@player.getVolume()
class @VideoProgressSlider class @VideoProgressSlider extends Subview
constructor: (@player) -> initialize: ->
@buildSlider() unless onTouchBasedDevice() @buildSlider() unless onTouchBasedDevice()
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
$(@player).bind('ready', @onReady)
$(@player).bind('play', @onPlay)
$: (selector) ->
@player.$(selector)
buildSlider: -> buildSlider: ->
@slider = @$('.slider').slider @slider = @element.slider
range: 'min' range: 'min'
change: @onChange change: @onChange
slide: @onSlide slide: @onSlide
...@@ -17,7 +11,7 @@ class @VideoProgressSlider ...@@ -17,7 +11,7 @@ class @VideoProgressSlider
@buildHandle() @buildHandle()
buildHandle: -> buildHandle: ->
@handle = @$('.slider .ui-slider-handle') @handle = @$('.ui-slider-handle')
@handle.qtip @handle.qtip
content: "#{Time.format(@slider.slider('value'))}" content: "#{Time.format(@slider.slider('value'))}"
position: position:
...@@ -30,28 +24,25 @@ class @VideoProgressSlider ...@@ -30,28 +24,25 @@ class @VideoProgressSlider
classes: 'ui-tooltip-slider' classes: 'ui-tooltip-slider'
widget: true widget: true
onReady: => play: =>
@slider.slider('option', 'max', @player.duration()) if @slider
onPlay: =>
@buildSlider() unless @slider @buildSlider() unless @slider
onUpdatePlayTime: (event, currentTime) => updatePlayTime: (currentTime, duration) ->
if @slider && !@frozen if @slider && !@frozen
@slider.slider('option', 'max', @player.duration()) @slider.slider('option', 'max', duration)
@slider.slider('value', currentTime) @slider.slider('value', currentTime)
onSlide: (event, ui) => onSlide: (event, ui) =>
@frozen = true @frozen = true
@updateTooltip(ui.value) @updateTooltip(ui.value)
$(@player).trigger('seek', ui.value) $(@).trigger('seek', ui.value)
onChange: (event, ui) => onChange: (event, ui) =>
@updateTooltip(ui.value) @updateTooltip(ui.value)
onStop: (event, ui) => onStop: (event, ui) =>
@frozen = true @frozen = true
$(@player).trigger('seek', ui.value) $(@).trigger('seek', ui.value)
setTimeout (=> @frozen = false), 200 setTimeout (=> @frozen = false), 200
updateTooltip: (value)-> updateTooltip: (value)->
......
class @VideoSpeedControl class @VideoSpeedControl extends Subview
constructor: (@player, @speeds) ->
@render()
@bind()
$: (selector) ->
@player.$(selector)
bind: -> bind: ->
$(@player).bind('speedChange', @onSpeedChange)
@$('.video_speeds a').click @changeVideoSpeed @$('.video_speeds a').click @changeVideoSpeed
if onTouchBasedDevice() if onTouchBasedDevice()
@$('.speeds').click (event) -> @$('.speeds').click (event) ->
...@@ -23,7 +15,7 @@ class @VideoSpeedControl ...@@ -23,7 +15,7 @@ class @VideoSpeedControl
$(this).removeClass('open') $(this).removeClass('open')
render: -> render: ->
@$('.secondary-controls').prepend """ @element.prepend """
<div class="speeds"> <div class="speeds">
<a href="#"> <a href="#">
<h3>Speed</h3> <h3>Speed</h3>
...@@ -36,15 +28,14 @@ class @VideoSpeedControl ...@@ -36,15 +28,14 @@ class @VideoSpeedControl
$.each @speeds, (index, speed) => $.each @speeds, (index, speed) =>
link = $('<a>').attr(href: "#").html("#{speed}x") link = $('<a>').attr(href: "#").html("#{speed}x")
@$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link)) @$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link))
@setSpeed(@player.currentSpeed()) @setSpeed(@currentSpeed)
changeVideoSpeed: (event) => changeVideoSpeed: (event) =>
event.preventDefault() event.preventDefault()
unless $(event.target).parent().hasClass('active') unless $(event.target).parent().hasClass('active')
$(@player).trigger 'speedChange', $(event.target).parent().data('speed') @currentSpeed = $(event.target).parent().data('speed')
$(@).trigger 'speedChange', $(event.target).parent().data('speed')
onSpeedChange: (event, speed) => @setSpeed(parseFloat(@currentSpeed).toFixed(2).replace /\.00$/, '.0')
@setSpeed(parseFloat(speed).toFixed(2).replace /\.00$/, '.0')
setSpeed: (speed) -> setSpeed: (speed) ->
@$('.video_speeds li').removeClass('active') @$('.video_speeds li').removeClass('active')
......
class @VideoVolumeControl class @VideoVolumeControl extends Subview
constructor: (@player) -> initialize: ->
@previousVolume = 100 @currentVolume = 100
@render()
@bind()
$: (selector) ->
@player.$(selector)
bind: -> bind: ->
$(@player).bind('ready', @onReady)
@$('.volume').mouseenter -> @$('.volume').mouseenter ->
$(this).addClass('open') $(this).addClass('open')
@$('.volume').mouseleave -> @$('.volume').mouseleave ->
...@@ -16,7 +10,7 @@ class @VideoVolumeControl ...@@ -16,7 +10,7 @@ class @VideoVolumeControl
@$('.volume>a').click(@toggleMute) @$('.volume>a').click(@toggleMute)
render: -> render: ->
@$('.secondary-controls').prepend """ @element.prepend """
<div class="volume"> <div class="volume">
<a href="#"></a> <a href="#"></a>
<div class="volume-slider-container"> <div class="volume-slider-container">
...@@ -33,16 +27,14 @@ class @VideoVolumeControl ...@@ -33,16 +27,14 @@ class @VideoVolumeControl
change: @onChange change: @onChange
slide: @onChange slide: @onChange
onReady: =>
@slider.slider 'option', 'max', @player.volume()
onChange: (event, ui) => onChange: (event, ui) =>
@player.volume ui.value @currentVolume = ui.value
@$('.secondary-controls .volume').toggleClass 'muted', ui.value == 0 $(@).trigger 'volumeChange', @currentVolume
@$('.volume').toggleClass 'muted', @currentVolume == 0
toggleMute: => toggleMute: =>
if @player.volume() > 0 if @currentVolume > 0
@previousVolume = @player.volume() @previousVolume = @currentVolume
@slider.slider 'option', 'value', 0 @slider.slider 'option', 'value', 0
else else
@slider.slider 'option', 'value', @previousVolume @slider.slider 'option', 'value', @previousVolume
...@@ -476,6 +476,7 @@ section.course-content { ...@@ -476,6 +476,7 @@ section.course-content {
ol.subtitles { ol.subtitles {
width: 0px; width: 0px;
display: none;
} }
} }
...@@ -498,6 +499,7 @@ section.course-content { ...@@ -498,6 +499,7 @@ section.course-content {
ol.subtitles { ol.subtitles {
right: -(flex-grid(4)); right: -(flex-grid(4));
width: auto; width: auto;
display: block;
} }
} }
......
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