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