From 4e183dd6e14b62c634a1e31e9b823d3e0f45138d Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Wed, 20 Jun 2012 15:14:46 -0400 Subject: [PATCH 01/11] 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() --- lms/static/coffee/spec/helper.coffee | 2 +- .../modules/video/video_caption_spec.coffee | 112 ++++--- .../modules/video/video_control_spec.coffee | 73 ++--- .../modules/video/video_player_spec.coffee | 277 +++++++++--------- .../video/video_progress_slider_spec.coffee | 148 ++++------ .../video/video_speed_control_spec.coffee | 27 +- .../video/video_volume_control_spec.coffee | 42 ++- .../coffee/spec/modules/video_spec.coffee | 4 +- lms/static/coffee/src/_subview.coffee | 14 + lms/static/coffee/src/modules/video.coffee | 2 +- .../src/modules/video/video_caption.coffee | 41 ++- .../src/modules/video/video_control.coffee | 27 +- .../src/modules/video/video_player.coffee | 91 +++--- .../video/video_progress_slider.coffee | 27 +- .../modules/video/video_speed_control.coffee | 21 +- .../modules/video/video_volume_control.coffee | 26 +- lms/static/sass/courseware/_video.scss | 2 + 17 files changed, 442 insertions(+), 494 deletions(-) create mode 100644 lms/static/coffee/src/_subview.coffee diff --git a/lms/static/coffee/spec/helper.coffee b/lms/static/coffee/spec/helper.coffee index eed1a07bd2..c0d3d77950 100644 --- a/lms/static/coffee/spec/helper.coffee +++ b/lms/static/coffee/spec/helper.coffee @@ -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') diff --git a/lms/static/coffee/spec/modules/video/video_caption_spec.coffee b/lms/static/coffee/spec/modules/video/video_caption_spec.coffee index 3fa6fa7daa..8e6f447405 100644 --- a/lms/static/coffee/spec/modules/video/video_caption_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_caption_spec.coffee @@ -1,6 +1,7 @@ describe 'VideoCaption', -> beforeEach -> - @player = jasmine.stubVideoPlayer @ + jasmine.stubVideoPlayer @ + $('.subtitles').remove() afterEach -> YT.Player = undefined @@ -12,10 +13,7 @@ describe 'VideoCaption', -> describe 'always', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' - - it 'set the player', -> - expect(@caption.player).toEqual @player + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' it 'set the youtube id', -> expect(@caption.youtubeId).toEqual 'def456' @@ -30,26 +28,14 @@ describe 'VideoCaption', -> expect($.getWithPrefix).toHaveBeenCalledWith @caption.captionURL(), jasmine.any(Function) it 'bind window resize event', -> - expect($(window)).toHandleWith 'resize', @caption.onWindowResize - - it 'bind player resize event', -> - expect($(@player)).toHandleWith 'resize', @caption.onWindowResize - - it 'bind player seek event', -> - expect($(@player)).toHandleWith 'seek', @caption.onUpdatePlayTime - - it 'bind player updatePlayTime event', -> - expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime - - it 'bind player play event', -> - expect($(@player)).toHandleWith 'play', @caption.onPlay + expect($(window)).toHandleWith 'resize', @caption.resize it 'bind the hide caption button', -> expect($('.hide-subtitles')).toHandleWith 'click', @caption.toggle it 'bind the mouse movement', -> - expect($('.subtitles')).toHandleWith 'mouseenter', @caption.onMouseEnter - expect($('.subtitles')).toHandleWith 'mouseleave', @caption.onMouseLeave + expect($('.subtitles')).toHandleWith 'mouseover', @caption.onMouseEnter + expect($('.subtitles')).toHandleWith 'mouseout', @caption.onMouseLeave expect($('.subtitles')).toHandleWith 'mousemove', @caption.onMovement expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement @@ -57,7 +43,7 @@ describe 'VideoCaption', -> describe 'when on a non touch-based device', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn false - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' it 'render the caption', -> expect($('.subtitles').html()).toMatch new RegExp(''' @@ -81,7 +67,7 @@ describe 'VideoCaption', -> describe 'when on a touch-based device', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn true - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' it 'show explaination message', -> expect($('.subtitles li')).toHaveHtml "Caption will be displayed when you start playing the video." @@ -93,7 +79,7 @@ describe 'VideoCaption', -> beforeEach -> spyOn(window, 'setTimeout').andReturn 100 spyOn window, 'clearTimeout' - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' describe 'when cursor is outside of the caption box', -> beforeEach -> @@ -140,7 +126,7 @@ describe 'VideoCaption', -> describe 'when the player is playing', -> beforeEach -> - spyOn(@player, 'isPlaying').andReturn true + @caption.playing = true $('.subtitles li[data-index]:first').addClass 'current' $('.subtitles').trigger jQuery.Event 'mouseout' @@ -149,7 +135,7 @@ describe 'VideoCaption', -> describe 'when the player is not playing', -> beforeEach -> - spyOn(@player, 'isPlaying').andReturn false + @caption.playing = false $('.subtitles').trigger jQuery.Event 'mouseout' it 'does not scroll the caption', -> @@ -157,7 +143,7 @@ describe 'VideoCaption', -> describe 'search', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' it 'return a correct caption index', -> expect(@caption.search(0)).toEqual 0 @@ -167,20 +153,20 @@ describe 'VideoCaption', -> expect(@caption.search(30000)).toEqual 3 expect(@caption.search(30001)).toEqual 3 - describe 'onPlay', -> + describe 'play', -> describe 'when the caption was not rendered', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn true - @caption = new VideoCaption @player, 'def456' - @caption.onPlay() + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' + @caption.play() it 'render the caption', -> - expect($('.subtitles').html()).toMatch new RegExp(''' -
  • Caption at 0
  • -
  • Caption at 10000
  • -
  • Caption at 20000
  • -
  • Caption at 30000
  • - '''.replace(/\n/g, '')) + expect($('.subtitles').html()).toMatch new RegExp( + '''
  • Caption at 0
  • ''' + + '''
  • Caption at 10000
  • ''' + + '''
  • Caption at 20000
  • ''' + + '''
  • Caption at 30000
  • ''' + ) it 'add a padding element to caption', -> expect($('.subtitles li:first')).toBe '.spacing' @@ -193,22 +179,34 @@ describe 'VideoCaption', -> it 'set rendered to true', -> expect(@caption.rendered).toBeTruthy() - describe 'onUpdatePlayTime', -> + it 'set playing to true', -> + expect(@caption.playing).toBeTruthy() + + describe 'pause', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' + @caption.playing = true + @caption.pause() + + it 'set playing to false', -> + expect(@caption.playing).toBeFalsy() + + describe 'updatePlayTime', -> + beforeEach -> + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' describe 'when the video speed is 1.0x', -> beforeEach -> - @video.setSpeed '1.0' - @caption.onUpdatePlayTime {}, 25.000 + @caption.currentSpeed = '1.0' + @caption.updatePlayTime 25.000 it 'search the caption based on time', -> expect(@caption.currentIndex).toEqual 2 describe 'when the video speed is not 1.0x', -> beforeEach -> - @video.setSpeed '0.75' - @caption.onUpdatePlayTime {}, 25.000 + @caption.currentSpeed = '0.75' + @caption.updatePlayTime 25.000 it 'search the caption based on 1.0x speed', -> expect(@caption.currentIndex).toEqual 1 @@ -217,7 +215,7 @@ describe 'VideoCaption', -> beforeEach -> @caption.currentIndex = 1 $('.subtitles li[data-index=1]').addClass 'current' - @caption.onUpdatePlayTime {}, 25.000 + @caption.updatePlayTime 25.000 it 'deactivate the previous caption', -> expect($('.subtitles li[data-index=1]')).not.toHaveClass 'current' @@ -235,16 +233,16 @@ describe 'VideoCaption', -> beforeEach -> @caption.currentIndex = 1 $('.subtitles li[data-index=1]').addClass 'current' - @caption.onUpdatePlayTime {}, 15.000 + @caption.updatePlayTime 15.000 it 'does not change current subtitle', -> expect($('.subtitles li[data-index=1]')).toHaveClass 'current' - describe 'onWindowResize', -> + describe 'resize', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' $('.subtitles li[data-index=1]').addClass 'current' - @caption.onWindowResize() + @caption.resize() it 'set the height of caption container', -> expect(parseInt($('.subtitles').css('maxHeight'))).toEqual $('.video-wrapper').height() @@ -260,7 +258,7 @@ describe 'VideoCaption', -> describe 'scrollCaption', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' describe 'when frozen', -> beforeEach -> @@ -288,18 +286,18 @@ describe 'VideoCaption', -> @caption.scrollCaption() it 'scroll to current caption', -> - expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @player.element), + expect($.fn.scrollTo).toHaveBeenCalledWith $('.subtitles .current:first', @caption.element), offset: - ($('.video-wrapper').height() / 2 - $('.subtitles .current:first').height() / 2) describe 'seekPlayer', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' @time = null - $(@player).bind 'seek', (event, time) => @time = time + $(@caption).bind 'seek', (event, time) => @time = time describe 'when the video speed is 1.0x', -> beforeEach -> - @video.setSpeed '1.0' + @caption.currentSpeed = '1.0' $('.subtitles li[data-start="30000"]').click() it 'trigger seek event with the correct time', -> @@ -307,7 +305,7 @@ describe 'VideoCaption', -> describe 'when the video speed is not 1.0x', -> beforeEach -> - @video.setSpeed '0.75' + @caption.currentSpeed = '0.75' $('.subtitles li[data-start="30000"]').click() it 'trigger seek event with the correct time', -> @@ -315,25 +313,25 @@ describe 'VideoCaption', -> describe 'toggle', -> beforeEach -> - @caption = new VideoCaption @player, 'def456' + @caption = new VideoCaption element: $('.video'), youtubeId: 'def456', currentSpeed: '1.0' $('.subtitles li[data-index=1]').addClass 'current' describe 'when the caption is visible', -> beforeEach -> - @player.element.removeClass 'closed' + @caption.element.removeClass 'closed' @caption.toggle jQuery.Event('click') it 'hide the caption', -> - expect(@player.element).toHaveClass 'closed' + expect(@caption.element).toHaveClass 'closed' describe 'when the caption is hidden', -> beforeEach -> - @player.element.addClass 'closed' + @caption.element.addClass 'closed' @caption.toggle jQuery.Event('click') it 'show the caption', -> - expect(@player.element).not.toHaveClass 'closed' + expect(@caption.element).not.toHaveClass 'closed' it 'scroll the caption', -> expect($.fn.scrollTo).toHaveBeenCalled() diff --git a/lms/static/coffee/spec/modules/video/video_control_spec.coffee b/lms/static/coffee/spec/modules/video/video_control_spec.coffee index 0c8615b8f7..79adb4ad71 100644 --- a/lms/static/coffee/spec/modules/video/video_control_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_control_spec.coffee @@ -1,11 +1,11 @@ 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 '''
    @@ -21,14 +21,8 @@ describe 'VideoControl', ->
    ''' - 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 + spyOnEvent @control, 'pause' + $('.video_control').addClass 'pause' + @control.togglePlayback jQuery.Event('click') - describe 'when the video is playing', -> - beforeEach -> - spyOn(@player, 'isPlaying').andReturn true - spyOnEvent @player, 'pause' - @control.togglePlayback jQuery.Event('click') + it 'trigger the pause event', -> + expect('pause').toHaveBeenTriggeredOn @control - it 'trigger the pause event', -> - expect('pause').toHaveBeenTriggeredOn @player + describe 'when the video is paused', -> + beforeEach -> + spyOnEvent @control, 'play' + $('.video_control').addClass 'play' + @control.togglePlayback jQuery.Event('click') - describe 'when the video is paused', -> - beforeEach -> - spyOn(@player, 'isPlaying').andReturn false - spyOnEvent @player, 'play' - @control.togglePlayback jQuery.Event('click') - - it 'trigger the play event', -> - expect('play').toHaveBeenTriggeredOn @player + it 'trigger the play event', -> + expect('play').toHaveBeenTriggeredOn @control 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 ebcb2cc009..7c86b2c9ca 100644 --- a/lms/static/coffee/spec/modules/video/video_player_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_player_spec.coffee @@ -15,7 +15,7 @@ describe 'VideoPlayer', -> describe 'always', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video it 'instanticate current time to zero', -> expect(@player.currentTime).toEqual 0 @@ -24,16 +24,16 @@ describe 'VideoPlayer', -> expect(@player.element).toBe '#video_example' it 'create video control', -> - expect(window.VideoControl).toHaveBeenCalledWith @player + expect(window.VideoControl).toHaveBeenCalledWith element: $('.video-controls', @player.element) it 'create video caption', -> - expect(window.VideoCaption).toHaveBeenCalledWith @player, 'def456' + expect(window.VideoCaption).toHaveBeenCalledWith element: @player.element, youtubeId: 'def456', currentSpeed: '1.0' it 'create video speed control', -> - expect(window.VideoSpeedControl).toHaveBeenCalledWith @player, ['0.75', '1.0'] + expect(window.VideoSpeedControl).toHaveBeenCalledWith element: $('.secondary-controls', @player.element), speeds: ['0.75', '1.0'], currentSpeed: '1.0' it 'create video progress slider', -> - expect(window.VideoProgressSlider).toHaveBeenCalledWith @player + expect(window.VideoProgressSlider).toHaveBeenCalledWith element: $('.slider', @player.element) it 'create Youtube player', -> expect(YT.Player).toHaveBeenCalledWith 'example' @@ -48,49 +48,48 @@ describe 'VideoPlayer', -> onReady: @player.onReady onStateChange: @player.onStateChange - it 'bind to seek event', -> - expect($(@player)).toHandleWith 'seek', @player.onSeek + it 'bind to video control play event', -> + expect($(@player.control)).toHandleWith 'play', @player.play - it 'bind to updatePlayTime event', -> - expect($(@player)).toHandleWith 'updatePlayTime', @player.onUpdatePlayTime + it 'bind to video control pause event', -> + expect($(@player.control)).toHandleWith 'pause', @player.pause - it 'bidn to speedChange event', -> - expect($(@player)).toHandleWith 'speedChange', @player.onSpeedChange + it 'bind to video caption seek event', -> + expect($(@player.caption)).toHandleWith 'seek', @player.onSeek - it 'bind to play event', -> - expect($(@player)).toHandleWith 'play', @player.onPlay + it 'bind to video speed control speedChange event', -> + expect($(@player.speedControl)).toHandleWith 'speedChange', @player.onSpeedChange - it 'bind to paused event', -> - expect($(@player)).toHandleWith 'pause', @player.onPause + it 'bind to video progress slider seek event', -> + expect($(@player.progressSlider)).toHandleWith 'seek', @player.onSeek - it 'bind to ended event', -> - expect($(@player)).toHandleWith 'ended', @player.onPause + it 'bind to video volume control volumeChange event', -> + expect($(@player.volumeControl)).toHandleWith 'volumeChange', @player.onVolumeChange it 'bind to key press', -> expect($(document)).toHandleWith 'keyup', @player.bindExitFullScreen it 'bind to fullscreen switching button', -> - console.debug $('.add-fullscreen') expect($('.add-fullscreen')).toHandleWith 'click', @player.toggleFullScreen describe 'when not on a touch based device', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn false $('.add-fullscreen, .hide-subtitles').removeData 'qtip' - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video it 'add the tooltip to fullscreen and subtitle button', -> expect($('.add-fullscreen')).toHaveData 'qtip' expect($('.hide-subtitles')).toHaveData 'qtip' it 'create video volume control', -> - expect(window.VideoVolumeControl).toHaveBeenCalledWith @player + expect(window.VideoVolumeControl).toHaveBeenCalledWith element: $('.secondary-controls', @player.element) describe 'when on a touch based device', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn true $('.add-fullscreen, .hide-subtitles').removeData 'qtip' - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video it 'does not add the tooltip to fullscreen and subtitle button', -> expect($('.add-fullscreen')).not.toHaveData 'qtip' @@ -107,12 +106,6 @@ describe 'VideoPlayer', -> spyOnEvent @player, 'updatePlayTime' @player.onReady() - it 'reset the progress to zero', -> - expect('updatePlayTime').toHaveBeenTriggeredOn @player - - it 'trigger ready event on the player', -> - expect('ready').toHaveBeenTriggeredOn @player - describe 'when not on a touch based device', -> beforeEach -> spyOn(window, 'onTouchBasedDevice').andReturn false @@ -133,93 +126,108 @@ describe 'VideoPlayer', -> describe 'onStateChange', -> beforeEach -> - @player = new VideoPlayer @video - - describe 'when the video is playing', -> - beforeEach -> - spyOnEvent @player, 'play' - @player.onStateChange data: YT.PlayerState.PLAYING - - it 'trigger play event', -> - expect('play').toHaveBeenTriggeredOn @player - - describe 'when the video is paused', -> - beforeEach -> - spyOnEvent @player, 'pause' - @player.onStateChange data: YT.PlayerState.PAUSED - - it 'trigger pause event', -> - expect('pause').toHaveBeenTriggeredOn @player + @player = new VideoPlayer video: @video describe 'when the video is unstarted', -> beforeEach -> - spyOnEvent @player, 'pause' + spyOn @player.control, 'pause' + @player.caption.pause = jasmine.createSpy('VideoCaption.pause') @player.onStateChange data: YT.PlayerState.UNSTARTED - it 'trigger pause event', -> - expect('pause').toHaveBeenTriggeredOn @player + it 'pause the video control', -> + expect(@player.control.pause).toHaveBeenCalled() + + it 'pause the video caption', -> + expect(@player.caption.pause).toHaveBeenCalled() + + describe 'when the video is playing', -> + beforeEach -> + @anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['pauseVideo'] + window.player = @anotherPlayer + spyOn Logger, 'log' + spyOn(window, 'setInterval').andReturn 100 + spyOn @player.control, 'play' + @player.caption.play = jasmine.createSpy('VideoCaption.play') + @player.progressSlider.play = jasmine.createSpy('VideoProgressSlider.play') + @player.player.getVideoEmbedCode.andReturn 'embedCode' + @player.onStateChange data: YT.PlayerState.PLAYING + + it 'log the play_video event', -> + expect(Logger.log).toHaveBeenCalledWith 'play_video', id: @player.currentTime, code: 'embedCode' + + it 'pause other video player', -> + expect(@anotherPlayer.pauseVideo).toHaveBeenCalled() + + it 'set current video player as active player', -> + expect(window.player).toEqual @player.player + + it 'set update interval', -> + expect(window.setInterval).toHaveBeenCalledWith @player.update, 200 + expect(@player.player.interval).toEqual 100 + + it 'play the video control', -> + expect(@player.control.play).toHaveBeenCalled() + + it 'play the video caption', -> + expect(@player.caption.play).toHaveBeenCalled() + + it 'play the video progress slider', -> + expect(@player.progressSlider.play).toHaveBeenCalled() + + describe 'when the video is paused', -> + beforeEach -> + @player = new VideoPlayer video: @video + window.player = @player.player + spyOn Logger, 'log' + spyOn window, 'clearInterval' + spyOn @player.control, 'pause' + @player.caption.pause = jasmine.createSpy('VideoCaption.pause') + @player.player.interval = 100 + @player.player.getVideoEmbedCode.andReturn 'embedCode' + @player.onStateChange data: YT.PlayerState.PAUSED + + it 'log the pause_video event', -> + expect(Logger.log).toHaveBeenCalledWith 'pause_video', id: @player.currentTime, code: 'embedCode' + + it 'set current video player as inactive', -> + expect(window.player).toBeNull() + + it 'clear update interval', -> + expect(window.clearInterval).toHaveBeenCalledWith 100 + expect(@player.player.interval).toBeNull() + + it 'pause the video control', -> + expect(@player.control.pause).toHaveBeenCalled() + + it 'pause the video caption', -> + expect(@player.caption.pause).toHaveBeenCalled() describe 'when the video is ended', -> beforeEach -> - spyOnEvent @player, 'ended' + spyOn @player.control, 'pause' + @player.caption.pause = jasmine.createSpy('VideoCaption.pause') @player.onStateChange data: YT.PlayerState.ENDED - it 'trigger ended event', -> - expect('ended').toHaveBeenTriggeredOn @player + it 'pause the video control', -> + expect(@player.control.pause).toHaveBeenCalled() - describe 'onPlay', -> - beforeEach -> - @player = new VideoPlayer @video - @anotherPlayer = jasmine.createSpyObj 'AnotherPlayer', ['pauseVideo'] - window.player = @anotherPlayer - spyOn Logger, 'log' - spyOn(window, 'setInterval').andReturn 100 - @player.player.getVideoEmbedCode.andReturn 'embedCode' - @player.onPlay() - - it 'log the play_video event', -> - expect(Logger.log).toHaveBeenCalledWith 'play_video', id: @player.currentTime, code: 'embedCode' - - it 'pause other video player', -> - expect(@anotherPlayer.pauseVideo).toHaveBeenCalled() - - it 'set current video player as active player', -> - expect(window.player).toEqual @player.player - - it 'set update interval', -> - expect(window.setInterval).toHaveBeenCalledWith @player.update, 200 - expect(@player.player.interval).toEqual 100 - - describe 'onPause', -> - beforeEach -> - @player = new VideoPlayer @video - window.player = @player.player - spyOn Logger, 'log' - spyOn window, 'clearInterval' - @player.player.interval = 100 - @player.player.getVideoEmbedCode.andReturn 'embedCode' - @player.onPause() - - it 'log the pause_video event', -> - expect(Logger.log).toHaveBeenCalledWith 'pause_video', id: @player.currentTime, code: 'embedCode' - - it 'set current video player as inactive', -> - expect(window.player).toBeNull() - - it 'clear update interval', -> - expect(window.clearInterval).toHaveBeenCalledWith 100 - expect(@player.player.interval).toBeNull() + it 'pause the video caption', -> + expect(@player.caption.pause).toHaveBeenCalled() describe 'onSeek', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video spyOn window, 'clearInterval' @player.player.interval = 100 + spyOn @player, 'updatePlayTime' @player.onSeek {}, 60 it 'seek the player', -> expect(@player.player.seekTo).toHaveBeenCalledWith 60, true + it 'call updatePlayTime on player', -> + expect(@player.updatePlayTime).toHaveBeenCalledWith 60 + describe 'when the player is playing', -> beforeEach -> @player.player.getPlayerState.andReturn YT.PlayerState.PLAYING @@ -231,19 +239,16 @@ describe 'VideoPlayer', -> describe 'when the player is not playing', -> beforeEach -> @player.player.getPlayerState.andReturn YT.PlayerState.PAUSED - spyOnEvent @player, 'updatePlayTime' @player.onSeek {}, 60 it 'set the current time', -> expect(@player.currentTime).toEqual 60 - it 'trigger updatePlayTime event', -> - expect('updatePlayTime').toHaveBeenTriggeredOn @player - describe 'onSpeedChange', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video @player.currentTime = 60 + spyOn @player, 'updatePlayTime' spyOn(@video, 'setSpeed').andCallThrough() describe 'always', -> @@ -256,34 +261,43 @@ describe 'VideoPlayer', -> it 'set video speed to the new speed', -> expect(@video.setSpeed).toHaveBeenCalledWith '0.75' + it 'tell video caption that the speed has changed', -> + expect(@player.caption.currentSpeed).toEqual '0.75' + describe 'when the video is playing', -> beforeEach -> @player.player.getPlayerState.andReturn YT.PlayerState.PLAYING - spyOnEvent @player, 'updatePlayTime' @player.onSpeedChange {}, '0.75' it 'load the video', -> expect(@player.player.loadVideoById).toHaveBeenCalledWith 'abc123', '80.000' it 'trigger updatePlayTime event', -> - expect('updatePlayTime').toHaveBeenTriggeredOn @player + expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000' describe 'when the video is not playing', -> beforeEach -> @player.player.getPlayerState.andReturn YT.PlayerState.PAUSED - spyOnEvent @player, 'updatePlayTime' @player.onSpeedChange {}, '0.75' it 'cue the video', -> expect(@player.player.cueVideoById).toHaveBeenCalledWith 'abc123', '80.000' it 'trigger updatePlayTime event', -> - expect('updatePlayTime').toHaveBeenTriggeredOn @player + expect(@player.updatePlayTime).toHaveBeenCalledWith '80.000' + + describe 'onVolumeChange', -> + beforeEach -> + @player = new VideoPlayer video: @video + @player.onVolumeChange undefined, 60 + + it 'set the volume on player', -> + expect(@player.player.setVolume).toHaveBeenCalledWith 60 describe 'update', -> beforeEach -> - @player = new VideoPlayer @video - spyOnEvent @player, 'updatePlayTime' + @player = new VideoPlayer video: @video + spyOn @player, 'updatePlayTime' describe 'when the current time is unavailable from the player', -> beforeEach -> @@ -291,7 +305,7 @@ describe 'VideoPlayer', -> @player.update() it 'does not trigger updatePlayTime event', -> - expect('updatePlayTime').not.toHaveBeenTriggeredOn @player + expect(@player.updatePlayTime).not.toHaveBeenCalled() describe 'when the current time is available from the player', -> beforeEach -> @@ -299,25 +313,33 @@ describe 'VideoPlayer', -> @player.update() it 'trigger updatePlayTime event', -> - expect('updatePlayTime').toHaveBeenTriggeredOn @player + expect(@player.updatePlayTime).toHaveBeenCalledWith(60) - describe 'onUpdatePlaytime', -> + describe 'updatePlayTime', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video spyOn(@video, 'getDuration').andReturn 1800 - @player.onUpdatePlayTime {}, 60 + @player.caption.updatePlayTime = jasmine.createSpy('VideoCaption.updatePlayTime') + @player.progressSlider.updatePlayTime = jasmine.createSpy('VideoProgressSlider.updatePlayTime') + @player.updatePlayTime 60 it 'update the video playback time', -> expect($('.vidtime')).toHaveHtml '1:00 / 30:00' + it 'update the playback time on caption', -> + expect(@player.caption.updatePlayTime).toHaveBeenCalledWith 60 + + it 'update the playback time on progress slider', -> + expect(@player.progressSlider.updatePlayTime).toHaveBeenCalledWith 60, 1800 + describe 'toggleFullScreen', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video + @player.caption.resize = jasmine.createSpy('VideoCaption.resize') describe 'when the video player is not full screen', -> beforeEach -> @player.element.removeClass 'fullscreen' - spyOnEvent @player, 'resize' @player.toggleFullScreen(jQuery.Event("click")) it 'replace the full screen button tooltip', -> @@ -329,13 +351,12 @@ describe 'VideoPlayer', -> it 'add the fullscreen class', -> expect(@player.element).toHaveClass 'fullscreen' - it 'trigger resize event', -> - expect('resize').toHaveBeenTriggeredOn @player + it 'tell VideoCaption to resize', -> + expect(@player.caption.resize).toHaveBeenCalled() describe 'when the video player already full screen', -> beforeEach -> @player.element.addClass 'fullscreen' - spyOnEvent @player, 'resize' @player.toggleFullScreen(jQuery.Event("click")) it 'replace the full screen button tooltip', -> @@ -347,12 +368,12 @@ describe 'VideoPlayer', -> it 'remove the fullscreen class', -> expect(@player.element).not.toHaveClass 'fullscreen' - it 'trigger resize event', -> - expect('resize').toHaveBeenTriggeredOn @player + it 'tell VideoCaption to resize', -> + expect(@player.caption.resize).toHaveBeenCalled() describe 'play', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video describe 'when the player is not ready', -> beforeEach -> @@ -372,7 +393,7 @@ describe 'VideoPlayer', -> describe 'isPlaying', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video describe 'when the video is playing', -> beforeEach -> @@ -390,7 +411,7 @@ describe 'VideoPlayer', -> describe 'pause', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video @player.pause() it 'delegate to the Youtube player', -> @@ -398,7 +419,7 @@ describe 'VideoPlayer', -> describe 'duration', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video spyOn @video, 'getDuration' @player.duration() @@ -407,22 +428,8 @@ describe 'VideoPlayer', -> describe 'currentSpeed', -> beforeEach -> - @player = new VideoPlayer @video + @player = new VideoPlayer video: @video @video.speed = '3.0' 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_progress_slider_spec.coffee b/lms/static/coffee/spec/modules/video/video_progress_slider_spec.coffee index ae78048c55..d4dd41e41d 100644 --- a/lms/static/coffee/spec/modules/video/video_progress_slider_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_progress_slider_spec.coffee @@ -1,79 +1,13 @@ 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 - - it 'build the slider', -> - expect(@slider.slider).toBe '.slider' - expect($.fn.slider).toHaveBeenCalledWith - range: 'min' - change: @slider.onChange - slide: @slider.onSlide - stop: @slider.onStop - - it 'build the seek handle', -> - expect(@slider.handle).toBe '.slider .ui-slider-handle' - expect($.fn.qtip).toHaveBeenCalledWith - content: "0:00" - position: - my: 'bottom center' - at: 'top center' - container: @slider.handle - hide: - delay: 700 - style: - 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 - - 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', -> - beforeEach -> - @slider = new VideoProgressSlider @player - spyOn($.fn, 'slider').andCallThrough() - - describe 'when the slider was already built', -> - beforeEach -> - @slider.onPlay() - - it 'does not build the slider', -> - expect($.fn.slider).not.toHaveBeenCalled - - describe 'when the slider was not already built', -> - beforeEach -> - @slider.slider = null - @slider.onPlay() + @slider = new VideoProgressSlider element: $('.slider') it 'build the slider', -> expect(@slider.slider).toBe '.slider' @@ -97,16 +31,64 @@ describe 'VideoProgressSlider', -> classes: 'ui-tooltip-slider' widget: true - describe 'onUpdatePlayTime', -> + describe 'on a touch-based device', -> + beforeEach -> + spyOn($.fn, 'slider').andCallThrough() + spyOn(window, 'onTouchBasedDevice').andReturn true + @slider = new VideoProgressSlider element: $('.slider') + + it 'does not build the slider', -> + expect(@slider.slider).toBeUndefined + expect($.fn.slider).not.toHaveBeenCalled() + + describe 'play', -> beforeEach -> - @slider = new VideoProgressSlider @player - spyOn(@player, 'duration').andReturn 120 + @slider = new VideoProgressSlider element: $('.slider') + spyOn($.fn, 'slider').andCallThrough() + + describe 'when the slider was already built', -> + beforeEach -> + @slider.play() + + it 'does not build the slider', -> + expect($.fn.slider).not.toHaveBeenCalled + + describe 'when the slider was not already built', -> + beforeEach -> + @slider.slider = null + @slider.play() + + it 'build the slider', -> + expect(@slider.slider).toBe '.slider' + expect($.fn.slider).toHaveBeenCalledWith + range: 'min' + change: @slider.onChange + slide: @slider.onSlide + stop: @slider.onStop + + it 'build the seek handle', -> + expect(@slider.handle).toBe '.ui-slider-handle' + expect($.fn.qtip).toHaveBeenCalledWith + content: "0:00" + position: + my: 'bottom center' + at: 'top center' + container: @slider.handle + hide: + delay: 700 + style: + classes: 'ui-tooltip-slider' + widget: true + + describe 'updatePlayTime', -> + beforeEach -> + @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', -> 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 737460d1ce..deced6f948 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 @@ -1,12 +1,12 @@ 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', -> ''' - 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' 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 index cbdef03ef0..7ef9c02780 100644 --- a/lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee +++ b/lms/static/coffee/spec/modules/video/video_volume_control_spec.coffee @@ -1,15 +1,15 @@ 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 diff --git a/lms/static/coffee/spec/modules/video_spec.coffee b/lms/static/coffee/spec/modules/video_spec.coffee index 134b38caff..12fac66a5b 100644 --- a/lms/static/coffee/spec/modules/video_spec.coffee +++ b/lms/static/coffee/spec/modules/video_spec.coffee @@ -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', -> diff --git a/lms/static/coffee/src/_subview.coffee b/lms/static/coffee/src/_subview.coffee new file mode 100644 index 0000000000..bddf91a895 --- /dev/null +++ b/lms/static/coffee/src/_subview.coffee @@ -0,0 +1,14 @@ +class @Subview + constructor: (options) -> + $.each options, (key, value) => + @[key] = value + @initialize() + @render() + @bind() + + $: (selector) -> + $(selector, @element) + + initialize: -> + render: -> + bind: -> diff --git a/lms/static/coffee/src/modules/video.coffee b/lms/static/coffee/src/modules/video.coffee index 888acba777..048bf679b8 100644 --- a/lms/static/coffee/src/modules/video.coffee +++ b/lms/static/coffee/src/modules/video.coffee @@ -36,7 +36,7 @@ class @Video @speed = '1.0' embed: -> - @player = new VideoPlayer(this) + @player = new VideoPlayer video: this fetchMetadata: (url) -> @metadata = {} diff --git a/lms/static/coffee/src/modules/video/video_caption.coffee b/lms/static/coffee/src/modules/video/video_caption.coffee index 5dde796b78..87a6f8f890 100644 --- a/lms/static/coffee/src/modules/video/video_caption.coffee +++ b/lms/static/coffee/src/modules/video/video_caption.coffee @@ -1,17 +1,6 @@ -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 - onUpdatePlayTime: (event, time) => + pause: -> + @playing = false + + 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() diff --git a/lms/static/coffee/src/modules/video/video_control.coffee b/lms/static/coffee/src/modules/video/video_control.coffee index 0c303fc4ad..dba18b5883 100644 --- a/lms/static/coffee/src/modules/video/video_control.coffee +++ b/lms/static/coffee/src/modules/video/video_control.coffee @@ -1,19 +1,9 @@ -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 """