diff --git a/static/coffee/spec/helper.coffee b/static/coffee/spec/helper.coffee
index 34e03e9d32..3d9537f872 100644
--- a/static/coffee/spec/helper.coffee
+++ b/static/coffee/spec/helper.coffee
@@ -11,12 +11,8 @@ jasmine.stubbedMetadata =
duration: 300
jasmine.stubbedCaption =
- start: [0, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000,
- 100000, 110000, 120000]
- text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000',
- 'Caption at 30000', 'Caption at 40000', 'Caption at 50000', 'Caption at 60000',
- 'Caption at 70000', 'Caption at 80000', 'Caption at 90000', 'Caption at 100000',
- 'Caption at 110000', 'Caption at 120000']
+ start: [0, 10000, 20000, 30000]
+ text: ['Caption at 0', 'Caption at 10000', 'Caption at 20000', 'Caption at 30000']
jasmine.stubRequests = ->
spyOn($, 'ajax').andCallFake (settings) ->
diff --git a/static/coffee/spec/modules/video/video_caption_spec.coffee b/static/coffee/spec/modules/video/video_caption_spec.coffee
index acf900fdcf..e816da593a 100644
--- a/static/coffee/spec/modules/video/video_caption_spec.coffee
+++ b/static/coffee/spec/modules/video/video_caption_spec.coffee
@@ -9,66 +9,82 @@ describe 'VideoCaption', ->
describe 'constructor', ->
beforeEach ->
spyOn($, 'getWithPrefix').andCallThrough()
- @caption = new VideoCaption @player, 'def456'
- it 'set the player', ->
- expect(@caption.player).toEqual @player
+ describe 'always', ->
+ beforeEach ->
+ @caption = new VideoCaption @player, 'def456'
- it 'set the youtube id', ->
- expect(@caption.youtubeId).toEqual 'def456'
+ it 'set the player', ->
+ expect(@caption.player).toEqual @player
- it 'create the caption element', ->
- expect($('.video')).toContain 'ol.subtitles'
+ it 'set the youtube id', ->
+ expect(@caption.youtubeId).toEqual 'def456'
- it 'add caption control to video player', ->
- expect($('.video')).toContain 'a.hide-subtitles'
+ it 'create the caption element', ->
+ expect($('.video')).toContain 'ol.subtitles'
- it 'fetch the caption', ->
- expect($.getWithPrefix).toHaveBeenCalledWith @caption.captionURL(), jasmine.any(Function)
+ it 'add caption control to video player', ->
+ expect($('.video')).toContain 'a.hide-subtitles'
- it 'render the caption', ->
- expect($('.subtitles').html()).toMatch new RegExp('''
-
Caption at 0
- Caption at 10000
- Caption at 20000
- Caption at 30000
- Caption at 40000
- Caption at 50000
- Caption at 60000
- Caption at 70000
- Caption at 80000
- Caption at 90000
- Caption at 100000
- Caption at 110000
- Caption at 120000
- '''.replace(/\n/g, ''))
+ it 'fetch the caption', ->
+ expect($.getWithPrefix).toHaveBeenCalledWith @caption.captionURL(), jasmine.any(Function)
- it 'add a padding element to caption', ->
- expect($('.subtitles li:first')).toBe '.spacing'
- expect($('.subtitles li:last')).toBe '.spacing'
+ it 'bind window resize event', ->
+ expect($(window)).toHandleWith 'resize', @caption.onWindowResize
- it 'bind all the caption link', ->
- $('.subtitles li[data-index]').each (index, link) =>
- expect($(link)).toHandleWith 'click', @caption.seekPlayer
+ it 'bind player resize event', ->
+ expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
- it 'bind window resize event', ->
- expect($(window)).toHandleWith 'resize', @caption.onWindowResize
+ it 'bind player updatePlayTime event', ->
+ expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
- it 'bind player resize event', ->
- expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
+ it 'bind player play event', ->
+ expect($(@player)).toHandleWith 'play', @caption.onPlay
- it 'bind player updatePlayTime event', ->
- expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
+ it 'bind the hide caption button', ->
+ expect($('.hide-subtitles')).toHandleWith 'click', @caption.toggle
- 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 'mousemove', @caption.onMovement
+ expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement
+ expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement
- it 'bind the mouse movement', ->
- expect($('.subtitles')).toHandleWith 'mouseenter', @caption.onMouseEnter
- expect($('.subtitles')).toHandleWith 'mouseleave', @caption.onMouseLeave
- expect($('.subtitles')).toHandleWith 'mousemove', @caption.onMovement
- expect($('.subtitles')).toHandleWith 'mousewheel', @caption.onMovement
- expect($('.subtitles')).toHandleWith 'DOMMouseScroll', @caption.onMovement
+ describe 'when on a non touch-based device', ->
+ beforeEach ->
+ spyOn(window, 'onTouchBasedDevice').andReturn false
+ @caption = new VideoCaption @player, 'def456'
+
+ 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, ''))
+
+ it 'add a padding element to caption', ->
+ expect($('.subtitles li:first')).toBe '.spacing'
+ expect($('.subtitles li:last')).toBe '.spacing'
+
+ it 'bind all the caption link', ->
+ $('.subtitles li[data-index]').each (index, link) =>
+ expect($(link)).toHandleWith 'click', @caption.seekPlayer
+
+ it 'set rendered to true', ->
+ expect(@caption.rendered).toBeTruthy()
+
+ describe 'when on a touch-based device', ->
+ beforeEach ->
+ spyOn(window, 'onTouchBasedDevice').andReturn true
+ @caption = new VideoCaption @player, 'def456'
+
+ it 'show explaination message', ->
+ expect($('.subtitles li')).toHaveHtml "Caption will be displayed when you start playing the video."
+
+ it 'does not set rendered to true', ->
+ expect(@caption.rendered).toBeFalsy()
describe 'mouse movement', ->
beforeEach ->
@@ -145,8 +161,34 @@ describe 'VideoCaption', ->
expect(@caption.search(9999)).toEqual 0
expect(@caption.search(10000)).toEqual 1
expect(@caption.search(15000)).toEqual 1
- expect(@caption.search(120000)).toEqual 12
- expect(@caption.search(120001)).toEqual 12
+ expect(@caption.search(30000)).toEqual 3
+ expect(@caption.search(30001)).toEqual 3
+
+ describe 'onPlay', ->
+ describe 'when the caption was not rendered', ->
+ beforeEach ->
+ spyOn(window, 'onTouchBasedDevice').andReturn true
+ @caption = new VideoCaption @player, 'def456'
+ @caption.onPlay()
+
+ 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, ''))
+
+ it 'add a padding element to caption', ->
+ expect($('.subtitles li:first')).toBe '.spacing'
+ expect($('.subtitles li:last')).toBe '.spacing'
+
+ it 'bind all the caption link', ->
+ $('.subtitles li[data-index]').each (index, link) =>
+ expect($(link)).toHandleWith 'click', @caption.seekPlayer
+
+ it 'set rendered to true', ->
+ expect(@caption.rendered).toBeTruthy()
describe 'onUpdatePlayTime', ->
beforeEach ->
diff --git a/static/coffee/spec/modules/video/video_control_spec.coffee b/static/coffee/spec/modules/video/video_control_spec.coffee
index a912abb2d0..0c8615b8f7 100644
--- a/static/coffee/spec/modules/video/video_control_spec.coffee
+++ b/static/coffee/spec/modules/video/video_control_spec.coffee
@@ -1,17 +1,16 @@
describe 'VideoControl', ->
beforeEach ->
@player = jasmine.stubVideoPlayer @
+ $('.video-controls').html ''
describe 'constructor', ->
- beforeEach ->
- @control = new VideoControl @player
-
it 'render the video controls', ->
+ new VideoControl @player
expect($('.video-controls').html()).toContain '''
- - Play
+ - Play
-
0:00 / 0:00
@@ -23,12 +22,33 @@ describe 'VideoControl', ->
'''
it 'bind player events', ->
- expect($(@player)).toHandleWith 'play', @control.onPlay
- expect($(@player)).toHandleWith 'pause', @control.onPause
- expect($(@player)).toHandleWith 'ended', @control.onPause
+ 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', ->
- expect($('.video_control')).toHandleWith 'click', @control.togglePlayback
+ control = new VideoControl @player
+ expect($('.video_control')).toHandleWith 'click', control.togglePlayback
+
+ describe 'when on a touch based device', ->
+ beforeEach ->
+ spyOn(window, 'onTouchBasedDevice').andReturn true
+
+ it 'does not add the play class to video control', ->
+ new VideoControl @player
+ expect($('.video_control')).not.toHaveClass 'play'
+ expect($('.video_control')).not.toHaveHtml 'Play'
+
+
+ describe 'when on a non-touch based device', ->
+ beforeEach ->
+ spyOn(window, 'onTouchBasedDevice').andReturn false
+
+ it 'add the play class to video control', ->
+ new VideoControl @player
+ expect($('.video_control')).toHaveClass 'play'
+ expect($('.video_control')).toHaveHtml 'Play'
describe 'onPlay', ->
beforeEach ->
@@ -54,20 +74,47 @@ describe 'VideoControl', ->
beforeEach ->
@control = new VideoControl @player
- describe 'when the video is playing', ->
+ describe 'when the control does not have play or pause class', ->
beforeEach ->
- spyOn(@player, 'isPlaying').andReturn true
- spyOnEvent @player, 'pause'
- @control.togglePlayback jQuery.Event('click')
+ $('.video_control').removeClass('play').removeClass('pause')
- it 'trigger the pause event', ->
- expect('pause').toHaveBeenTriggeredOn @player
+ describe 'when the video is playing', ->
+ beforeEach ->
+ spyOn(@player, 'isPlaying').andReturn true
+ spyOnEvent @player, 'pause'
+ @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 'does not trigger the pause event', ->
+ expect('pause').not.toHaveBeenTriggeredOn @player
- it 'trigger the play event', ->
- expect('play').toHaveBeenTriggeredOn @player
+ describe 'when the video is paused', ->
+ beforeEach ->
+ spyOn(@player, 'isPlaying').andReturn false
+ spyOnEvent @player, 'play'
+ @control.togglePlayback jQuery.Event('click')
+
+ it 'does not trigger the play event', ->
+ expect('play').not.toHaveBeenTriggeredOn @player
+
+ for className in ['play', 'pause']
+ describe "when the control has #{className} class", ->
+ 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')
+
+ it 'trigger the pause event', ->
+ expect('pause').toHaveBeenTriggeredOn @player
+
+ 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
diff --git a/static/coffee/spec/modules/video/video_player_spec.coffee b/static/coffee/spec/modules/video/video_player_spec.coffee
index 26f5e3b5ed..cdfedcb4f9 100644
--- a/static/coffee/spec/modules/video/video_player_spec.coffee
+++ b/static/coffee/spec/modules/video/video_player_spec.coffee
@@ -90,7 +90,7 @@ describe 'VideoPlayer', ->
describe 'when not on a touch based device', ->
beforeEach ->
- window.onTouchBasedDevice = -> false
+ spyOn(window, 'onTouchBasedDevice').andReturn false
spyOn @player, 'play'
@player.onReady()
@@ -99,7 +99,7 @@ describe 'VideoPlayer', ->
describe 'when on a touch based device', ->
beforeEach ->
- window.onTouchBasedDevice = -> true
+ spyOn(window, 'onTouchBasedDevice').andReturn true
spyOn @player, 'play'
@player.onReady()
diff --git a/static/coffee/spec/modules/video/video_progress_slider_spec.coffee b/static/coffee/spec/modules/video/video_progress_slider_spec.coffee
index b2ade26ba8..ba7d439a94 100644
--- a/static/coffee/spec/modules/video/video_progress_slider_spec.coffee
+++ b/static/coffee/spec/modules/video/video_progress_slider_spec.coffee
@@ -3,34 +3,51 @@ describe 'VideoProgressSlider', ->
@player = jasmine.stubVideoPlayer @
describe 'constructor', ->
- beforeEach ->
- spyOn($.fn, 'slider').andCallThrough()
- @slider = new VideoProgressSlider @player
+ 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 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
+ 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
- it 'bind player events', ->
- expect($(@player)).toHandleWith 'updatePlayTime', @slider.onUpdatePlayTime
+ 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 ->
@@ -41,6 +58,45 @@ describe 'VideoProgressSlider', ->
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()
+
+ 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 'onUpdatePlayTime', ->
beforeEach ->
@slider = new VideoProgressSlider @player
diff --git a/static/coffee/src/modules/video/video_caption.coffee b/static/coffee/src/modules/video/video_caption.coffee
index 31a3d09e09..7d796245bb 100644
--- a/static/coffee/src/modules/video/video_caption.coffee
+++ b/static/coffee/src/modules/video/video_caption.coffee
@@ -10,6 +10,7 @@ class @VideoCaption
$(window).bind('resize', @onWindowResize)
$(@player).bind('resize', @onWindowResize)
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
+ $(@player).bind('play', @onPlay)
@$('.hide-subtitles').click @toggle
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
.mousemove(@onMovement).bind('mousewheel', @onMovement)
@@ -32,7 +33,11 @@ class @VideoCaption
$.getWithPrefix @captionURL(), (captions) =>
@captions = captions.text
@start = captions.start
- @renderCaption()
+
+ if onTouchBasedDevice()
+ $('.subtitles li').html "Caption will be displayed when you start playing the video."
+ else
+ @renderCaption()
renderCaption: ->
container = $('')
@@ -49,6 +54,8 @@ class @VideoCaption
@$('.subtitles').prepend($('- ').height(@topSpacingHeight()))
.append($('
- ').height(@bottomSpacingHeight()))
+ @rendered = true
+
search: (time) ->
min = 0
max = @start.length - 1
@@ -62,6 +69,9 @@ class @VideoCaption
return min
+ onPlay: =>
+ @renderCaption() unless @rendered
+
onUpdatePlayTime: (event, time) =>
# This 250ms offset is required to match the video speed
time = Math.round(Time.convert(time, @player.currentSpeed(), '1.0') * 1000 + 250)
diff --git a/static/coffee/src/modules/video/video_control.coffee b/static/coffee/src/modules/video/video_control.coffee
index c9bbf95c0a..0c303fc4ad 100644
--- a/static/coffee/src/modules/video/video_control.coffee
+++ b/static/coffee/src/modules/video/video_control.coffee
@@ -17,7 +17,7 @@ class @VideoControl
- """
+ """
+
+ unless onTouchBasedDevice()
+ @$('.video_control').addClass('play').html('Play')
onPlay: =>
@$('.video_control').removeClass('play').addClass('pause').html('Pause')
@@ -36,7 +39,8 @@ class @VideoControl
togglePlayback: (event) =>
event.preventDefault()
- if @player.isPlaying()
- $(@player).trigger('pause')
- else
- $(@player).trigger('play')
+ if $('.video_control').hasClass('play') || $('.video_control').hasClass('pause')
+ if @player.isPlaying()
+ $(@player).trigger('pause')
+ else
+ $(@player).trigger('play')
diff --git a/static/coffee/src/modules/video/video_progress_slider.coffee b/static/coffee/src/modules/video/video_progress_slider.coffee
index 4dfd558e4c..923f06b0c3 100644
--- a/static/coffee/src/modules/video/video_progress_slider.coffee
+++ b/static/coffee/src/modules/video/video_progress_slider.coffee
@@ -1,9 +1,9 @@
class @VideoProgressSlider
constructor: (@player) ->
- @buildSlider()
- @buildHandle()
+ @buildSlider() unless onTouchBasedDevice()
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
$(@player).bind('ready', @onReady)
+ $(@player).bind('play', @onPlay)
$: (selector) ->
@player.$(selector)
@@ -14,6 +14,7 @@ class @VideoProgressSlider
change: @onChange
slide: @onSlide
stop: @onStop
+ @buildHandle()
buildHandle: ->
@handle = @$('.ui-slider-handle')
@@ -30,10 +31,13 @@ class @VideoProgressSlider
widget: true
onReady: =>
- @slider.slider('option', 'max', @player.duration())
+ @slider.slider('option', 'max', @player.duration()) if @slider
+
+ onPlay: =>
+ @buildSlider() unless @slider
onUpdatePlayTime: (event, currentTime) =>
- if !@frozen
+ if @slider && !@frozen
@slider.slider('option', 'max', @player.duration())
@slider.slider('value', currentTime)
diff --git a/static/images/pause-icon.png b/static/images/pause-icon.png
deleted file mode 100644
index e3eac51947..0000000000
Binary files a/static/images/pause-icon.png and /dev/null differ
diff --git a/static/images/play-icon.png b/static/images/play-icon.png
deleted file mode 100644
index 2888b1c271..0000000000
Binary files a/static/images/play-icon.png and /dev/null differ
diff --git a/static/images/vcr.png b/static/images/vcr.png
new file mode 100644
index 0000000000..aa2ac99e47
Binary files /dev/null and b/static/images/vcr.png differ
diff --git a/static/sass/courseware/_video.scss b/static/sass/courseware/_video.scss
index 9fc3b4eb3d..7130cc8b7b 100644
--- a/static/sass/courseware/_video.scss
+++ b/static/sass/courseware/_video.scss
@@ -137,6 +137,7 @@ section.course-content {
float: left;
margin-bottom: 0;
+
a {
border-bottom: none;
border-right: 1px solid #000;
@@ -146,11 +147,17 @@ section.course-content {
line-height: 46px;
padding: 0 lh(.75);
text-indent: -9999px;
- @include transition();
+ @include transition(background-color, opacity);
width: 14px;
+ background: url('../images/vcr.png') 15px 15px no-repeat;
+
+ &:empty {
+ height: 46px;
+ background: url('../images/vcr.png') 15px 15px no-repeat;
+ }
&.play {
- background: url('../images/play-icon.png') center center no-repeat;
+ background-position: 17px -114px;
&:hover {
background-color: #444;
@@ -158,7 +165,7 @@ section.course-content {
}
&.pause {
- background: url('../images/pause-icon.png') center center no-repeat;
+ background-position: 16px -50px;
&:hover {
background-color: #444;
@@ -361,7 +368,6 @@ section.course-content {
cursor: pointer;
margin-bottom: 8px;
padding: 0;
- @include transition(all, .5s, ease-in);
&.current {
color: #333;
@@ -386,7 +392,6 @@ section.course-content {
}
ol.subtitles {
- height: 0;
width: 0px;
}
}
@@ -408,7 +413,6 @@ section.course-content {
&.closed {
ol.subtitles {
- height: auto;
right: -(flex-grid(4));
width: auto;
}