Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
23dc10d0
Commit
23dc10d0
authored
Dec 24, 2013
by
Anton Stupak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1998 from edx/anton/fix-video-in-ipad
Fix video controls on iPad.
parents
a68f5929
934b5198
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
755 additions
and
316 deletions
+755
-316
CHANGELOG.rst
+8
-0
cms/djangoapps/contentstore/features/video.py
+3
-2
cms/static/coffee/src/main.coffee
+1
-1
common/lib/xmodule/xmodule/css/video/display.scss
+40
-1
common/lib/xmodule/xmodule/js/fixtures/video.html
+4
-2
common/lib/xmodule/xmodule/js/fixtures/video_all.html
+4
-2
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+4
-2
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
+4
-2
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
+4
-2
common/lib/xmodule/xmodule/js/spec/video/events_spec.js
+164
-0
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
+0
-1
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
+0
-0
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
+11
-27
common/lib/xmodule/xmodule/js/spec/video/video_control_spec.js
+144
-6
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+83
-48
common/lib/xmodule/xmodule/js/spec/video/video_progress_slider_spec.js
+38
-108
common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js
+2
-2
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
+9
-13
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
+12
-12
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+37
-8
common/lib/xmodule/xmodule/js/src/video/02_html5_video.js
+42
-21
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+57
-21
common/lib/xmodule/xmodule/js/src/video/04_video_control.js
+53
-6
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
+1
-0
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
+3
-5
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
+7
-0
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
+8
-1
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
+8
-20
lms/static/coffee/src/main.coffee
+1
-1
lms/templates/video.html
+3
-2
No files found.
CHANGELOG.rst
View file @
23dc10d0
...
@@ -5,6 +5,14 @@ These are notable changes in edx-platform. This is a rolling list of changes,
...
@@ -5,6 +5,14 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
the top. Include a label indicating the component affected.
Blades: Video player improvements:
- Disable edX controls on iPhone/iPod (native controls are used).
- Disable unsupported controls (volume, playback rate) on iPad/Android.
- Controls becomes visible after click on video or play placeholder to avoid
issues with YouTube API on iPad/Android.
- Captions becomes visible just after full initialization of video player.
- Fix blinking of captions after initialization of video player. BLD-206.
LMS: Fix answer distribution download for small courses. LMS-922, LMS-811
LMS: Fix answer distribution download for small courses. LMS-922, LMS-811
Blades: Add template for the zooming image in studio. BLD-206.
Blades: Add template for the zooming image in studio. BLD-206.
...
...
cms/djangoapps/contentstore/features/video.py
View file @
23dc10d0
...
@@ -141,12 +141,13 @@ def the_youtube_video_is_shown(_step):
...
@@ -141,12 +141,13 @@ def the_youtube_video_is_shown(_step):
@step
(
'Make sure captions are (.+)$'
)
@step
(
'Make sure captions are (.+)$'
)
def
set_captions_visibility_state
(
_step
,
captions_state
):
def
set_captions_visibility_state
(
_step
,
captions_state
):
SELECTOR
=
'.closed .subtitles'
SELECTOR
=
'.closed .subtitles'
world
.
wait_for_visible
(
'.hide-subtitles'
)
if
captions_state
==
'closed'
:
if
captions_state
==
'closed'
:
if
not
world
.
is_css_present
(
SELECTOR
):
if
not
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
world
.
css_find
(
'.hide-subtitles'
)
.
click
()
else
:
else
:
if
world
.
is_css_present
(
SELECTOR
):
if
world
.
is_css_present
(
SELECTOR
):
world
.
browser
.
find_by_css
(
'.hide-subtitles'
)
.
click
()
world
.
css_find
(
'.hide-subtitles'
)
.
click
()
@step
(
'I hover over button "([^"]*)"$'
)
@step
(
'I hover over button "([^"]*)"$'
)
...
...
cms/static/coffee/src/main.coffee
View file @
23dc10d0
...
@@ -9,7 +9,7 @@ define ["domReady", "jquery", "underscore.string", "backbone", "gettext",
...
@@ -9,7 +9,7 @@ define ["domReady", "jquery", "underscore.string", "backbone", "gettext",
window
.
CMS
=
window
.
CMS
or
{}
window
.
CMS
=
window
.
CMS
or
{}
CMS
.
URL
=
CMS
.
URL
or
{}
CMS
.
URL
=
CMS
.
URL
or
{}
window
.
onTouchBasedDevice
=
->
window
.
onTouchBasedDevice
=
->
navigator
.
userAgent
.
match
/iPhone|iPod|iPad/i
navigator
.
userAgent
.
match
/iPhone|iPod|iPad
|Android
/i
_
.
extend
CMS
,
Backbone
.
Events
_
.
extend
CMS
,
Backbone
.
Events
Backbone
.
emulateHTTP
=
true
Backbone
.
emulateHTTP
=
true
...
...
common/lib/xmodule/xmodule/css/video/display.scss
View file @
23dc10d0
...
@@ -2,6 +2,10 @@
...
@@ -2,6 +2,10 @@
margin-bottom
:
30px
;
margin-bottom
:
30px
;
}
}
.is-hidden
{
display
:
none
;
}
div
.video
{
div
.video
{
@include
clearfix
();
@include
clearfix
();
background
:
#f3f3f3
;
background
:
#f3f3f3
;
...
@@ -97,12 +101,35 @@ div.video {
...
@@ -97,12 +101,35 @@ div.video {
}
}
}
}
.btn-play
{
@include
transform
(
translate
(
-50%
,
-50%
));
position
:
absolute
;
z-index
:
1
;
background
:
rgba
(
0
,
0
,
0
,
0
.7
);
top
:
50%
;
left
:
50%
;
padding
:
30px
;
border-radius
:
25%
;
&
:after
{
content
:
''
;
display
:
block
;
width
:
0px
;
height
:
0px
;
border-style
:
solid
;
border-width
:
30px
0
30px
50px
;
border-color
:
transparent
transparent
transparent
#ffffff
;
position
:
relative
;
}
}
section
.video-player
{
section
.video-player
{
overflow
:
hidden
;
overflow
:
hidden
;
min-height
:
300px
;
min-height
:
300px
;
div
{
>
div
{
height
:
100%
;
&
.hidden
{
&
.hidden
{
display
:
none
;
display
:
none
;
}
}
...
@@ -674,6 +701,7 @@ div.video {
...
@@ -674,6 +701,7 @@ div.video {
width
:
275px
;
width
:
275px
;
padding
:
0
20px
;
padding
:
0
20px
;
z-index
:
0
;
z-index
:
0
;
display
:
none
;
}
}
}
}
...
@@ -764,6 +792,17 @@ div.video {
...
@@ -764,6 +792,17 @@ div.video {
}
}
}
}
}
}
&
.is-touch
{
div
.tc-wrapper
{
article
.video-wrapper
{
object
,
iframe
,
video
{
width
:
100%
;
height
:
100%
;
}
}
}
}
}
}
common/lib/xmodule/xmodule/js/fixtures/video.html
View file @
23dc10d0
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
<div
id=
"example"
>
<div
id=
"example"
>
<div
<div
id=
"video_id"
id=
"video_id"
class=
"video"
class=
"video
closed
"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-show-captions=
"true"
data-show-captions=
"true"
data-start=
""
data-start=
""
...
@@ -18,12 +18,14 @@
...
@@ -18,12 +18,14 @@
<div
class=
"tc-wrapper"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<div
class=
"video-player-pre"
></div>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"id"
></div>
<div
id=
"id"
></div>
</section>
</section>
<div
class=
"video-player-post"
></div>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<section
class=
"video-controls
is-hidden
"
>
<div
class=
"slider"
></div>
<div
class=
"slider"
></div>
<div>
<div>
<ul
class=
"vcr"
>
<ul
class=
"vcr"
>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_all.html
View file @
23dc10d0
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
<div
id=
"example"
>
<div
id=
"example"
>
<div
<div
id=
"video_id"
id=
"video_id"
class=
"video"
class=
"video
closed
"
data-show-captions=
"true"
data-show-captions=
"true"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
...
@@ -21,12 +21,14 @@
...
@@ -21,12 +21,14 @@
<div
class=
"tc-wrapper"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<div
class=
"video-player-pre"
></div>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"id"
></div>
<div
id=
"id"
></div>
</section>
</section>
<div
class=
"video-player-post"
></div>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<section
class=
"video-controls
is-hidden
"
>
<div
class=
"slider"
></div>
<div
class=
"slider"
></div>
<div>
<div>
<ul
class=
"vcr"
>
<ul
class=
"vcr"
>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_html5.html
View file @
23dc10d0
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
<div
id=
"example"
>
<div
id=
"example"
>
<div
<div
id=
"video_id"
id=
"video_id"
class=
"video"
class=
"video
closed
"
data-show-captions=
"true"
data-show-captions=
"true"
data-start=
""
data-start=
""
data-end=
""
data-end=
""
...
@@ -21,10 +21,12 @@
...
@@ -21,10 +21,12 @@
<div
class=
"tc-wrapper"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"id"
></div>
<div
id=
"id"
></div>
</section>
</section>
<section
class=
"video-controls"
></section>
<section
class=
"video-controls
is-hidden
"
></section>
</article>
</article>
<ol
class=
"subtitles"
><li></li></ol>
<ol
class=
"subtitles"
><li></li></ol>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
View file @
23dc10d0
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
<div
id=
"example"
>
<div
id=
"example"
>
<div
<div
id=
"video_id"
id=
"video_id"
class=
"video"
class=
"video
closed
"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-show-captions=
"false"
data-show-captions=
"false"
data-start=
""
data-start=
""
...
@@ -18,10 +18,12 @@
...
@@ -18,10 +18,12 @@
<div
class=
"tc-wrapper"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"id"
></div>
<div
id=
"id"
></div>
</section>
</section>
<section
class=
"video-controls"
></section>
<section
class=
"video-controls
is-hidden
"
></section>
</article>
</article>
</div>
</div>
...
...
common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
View file @
23dc10d0
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
<div
id=
"example1"
>
<div
id=
"example1"
>
<div
<div
id=
"video_id1"
id=
"video_id1"
class=
"video"
class=
"video
closed
"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-streams=
"0.75:7tqY6eQzVhE,1.0:cogebirgzzM"
data-show-captions=
"true"
data-show-captions=
"true"
data-start=
""
data-start=
""
...
@@ -18,12 +18,14 @@
...
@@ -18,12 +18,14 @@
<div
class=
"tc-wrapper"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<div
class=
"video-player-pre"
></div>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"id1"
></div>
<div
id=
"id1"
></div>
</section>
</section>
<div
class=
"video-player-post"
></div>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<section
class=
"video-controls
is-hidden
"
>
<div
class=
"slider"
></div>
<div
class=
"slider"
></div>
<div>
<div>
<ul
class=
"vcr"
>
<ul
class=
"vcr"
>
...
...
common/lib/xmodule/xmodule/js/spec/video/events_spec.js
0 → 100644
View file @
23dc10d0
(
function
()
{
describe
(
'VideoPlayer Events'
,
function
()
{
var
state
,
videoPlayer
,
player
,
videoControl
,
videoCaption
,
videoProgressSlider
,
videoSpeedControl
,
videoVolumeControl
,
oldOTBD
;
function
initialize
(
fixture
,
params
)
{
if
(
_
.
isString
(
fixture
))
{
loadFixtures
(
fixture
);
}
else
{
if
(
_
.
isObject
(
fixture
))
{
params
=
fixture
;
}
loadFixtures
(
'video_all.html'
);
}
if
(
_
.
isObject
(
params
))
{
$
(
'#example'
)
.
find
(
'#video_id'
)
.
data
(
params
);
}
state
=
new
Video
(
'#example'
);
state
.
videoEl
=
$
(
'video, iframe'
);
videoPlayer
=
state
.
videoPlayer
;
player
=
videoPlayer
.
player
;
videoControl
=
state
.
videoControl
;
videoCaption
=
state
.
videoCaption
;
videoProgressSlider
=
state
.
videoProgressSlider
;
videoSpeedControl
=
state
.
videoSpeedControl
;
videoVolumeControl
=
state
.
videoVolumeControl
;
state
.
resizer
=
(
function
()
{
var
methods
=
[
'align'
,
'alignByWidthOnly'
,
'alignByHeightOnly'
,
'setParams'
,
'setMode'
],
obj
=
{};
$
.
each
(
methods
,
function
(
index
,
method
)
{
obj
[
method
]
=
jasmine
.
createSpy
(
method
).
andReturn
(
obj
);
});
return
obj
;
}());
}
function
initializeYouTube
()
{
initialize
(
'video.html'
);
}
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
null
);
this
.
oldYT
=
window
.
YT
;
jasmine
.
stubRequests
();
window
.
YT
=
{
Player
:
function
()
{
return
{
getPlaybackQuality
:
function
()
{}
};
},
PlayerState
:
this
.
oldYT
.
PlayerState
,
ready
:
function
(
callback
)
{
callback
();
}
};
});
afterEach
(
function
()
{
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
window
.
YT
=
this
.
oldYT
;
});
it
(
'initialize'
,
function
(){
runs
(
function
()
{
initialize
();
});
waitsFor
(
function
()
{
return
state
.
el
.
hasClass
(
'is-initialized'
);
},
'Player is not initialized.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
'initialize'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
});
it
(
'ready'
,
function
()
{
runs
(
function
()
{
initialize
();
});
waitsFor
(
function
()
{
return
state
.
el
.
hasClass
(
'is-initialized'
);
},
'Player is not initialized.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
'ready'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
});
it
(
'play'
,
function
()
{
initialize
();
videoPlayer
.
play
();
expect
(
'play'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'pause'
,
function
()
{
initialize
();
videoPlayer
.
play
();
videoPlayer
.
pause
();
expect
(
'pause'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'volumechange'
,
function
()
{
initialize
();
videoPlayer
.
onVolumeChange
(
60
);
expect
(
'volumechange'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'speedchange'
,
function
()
{
initialize
();
videoPlayer
.
onSpeedChange
(
'2.0'
);
expect
(
'speedchange'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'qualitychange'
,
function
()
{
initializeYouTube
();
videoPlayer
.
onPlaybackQualityChange
();
expect
(
'qualitychange'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'seek'
,
function
()
{
initialize
();
videoPlayer
.
onCaptionSeek
({
time
:
1
,
type
:
'any'
});
expect
(
'seek'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
it
(
'ended'
,
function
()
{
initialize
();
videoPlayer
.
onEnded
();
expect
(
'ended'
).
not
.
toHaveBeenTriggeredOn
(
'.video'
);
});
});
}).
call
(
this
);
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
View file @
23dc10d0
...
@@ -60,7 +60,6 @@
...
@@ -60,7 +60,6 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
loadFixtures
(
'video_html5.html'
);
loadFixtures
(
'video_html5.html'
);
this
.
stubVideoPlayer
=
jasmine
.
createSpy
(
'VideoPlayer'
);
$
.
cookie
.
andReturn
(
'0.75'
);
$
.
cookie
.
andReturn
(
'0.75'
);
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
View file @
23dc10d0
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
View file @
23dc10d0
...
@@ -15,7 +15,7 @@
...
@@ -15,7 +15,7 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
false
);
.
andReturn
(
null
);
initialize
();
initialize
();
});
});
...
@@ -175,7 +175,7 @@
...
@@ -175,7 +175,7 @@
describe
(
'when on a touch-based device'
,
function
()
{
describe
(
'when on a touch-based device'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
window
.
onTouchBasedDevice
.
andReturn
(
[
'iPad'
]
);
initialize
();
initialize
();
});
});
...
@@ -209,34 +209,15 @@
...
@@ -209,34 +209,15 @@
});
});
describe
(
'mouse movement'
,
function
()
{
describe
(
'mouse movement'
,
function
()
{
// We will store default window.setTimeout() function here.
var
oldSetTimeout
=
null
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
// Store original window.setTimeout() function. If we do not do
jasmine
.
Clock
.
useMock
();
// this, then all other tests that rely on code which uses
// window.setTimeout() function might (and probably will) fail.
oldSetTimeout
=
window
.
setTimeout
;
// Redefine window.setTimeout() function as a spy.
window
.
setTimeout
=
jasmine
.
createSpy
().
andCallFake
(
function
(
callback
,
timeout
)
{
return
5
;
}
);
window
.
setTimeout
.
andReturn
(
100
);
spyOn
(
window
,
'clearTimeout'
);
spyOn
(
window
,
'clearTimeout'
);
});
});
afterEach
(
function
()
{
// Reset the default window.setTimeout() function. If we do not
// do this, then all other tests that rely on code which uses
// window.setTimeout() function might (and probably will) fail.
window
.
setTimeout
=
oldSetTimeout
;
});
describe
(
'when cursor is outside of the caption box'
,
function
()
{
describe
(
'when cursor is outside of the caption box'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
$
(
window
).
trigger
(
jQuery
.
Event
(
'mousemove'
));
$
(
window
).
trigger
(
jQuery
.
Event
(
'mousemove'
));
jasmine
.
Clock
.
tick
(
state
.
config
.
captionsFreezeTime
);
});
});
it
(
'does not set freezing timeout'
,
function
()
{
it
(
'does not set freezing timeout'
,
function
()
{
...
@@ -246,11 +227,14 @@
...
@@ -246,11 +227,14 @@
describe
(
'when cursor is in the caption box'
,
function
()
{
describe
(
'when cursor is in the caption box'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
spyOn
(
videoCaption
,
'onMouseLeave'
);
$
(
'.subtitles'
).
trigger
(
jQuery
.
Event
(
'mouseenter'
));
$
(
'.subtitles'
).
trigger
(
jQuery
.
Event
(
'mouseenter'
));
jasmine
.
Clock
.
tick
(
state
.
config
.
captionsFreezeTime
);
});
});
it
(
'set the freezing timeout'
,
function
()
{
it
(
'set the freezing timeout'
,
function
()
{
expect
(
videoCaption
.
frozen
).
toEqual
(
100
);
expect
(
videoCaption
.
frozen
).
not
.
toBeFalsy
();
expect
(
videoCaption
.
onMouseLeave
).
toHaveBeenCalled
();
});
});
describe
(
'when the cursor is moving'
,
function
()
{
describe
(
'when the cursor is moving'
,
function
()
{
...
@@ -259,7 +243,7 @@
...
@@ -259,7 +243,7 @@
});
});
it
(
'reset the freezing timeout'
,
function
()
{
it
(
'reset the freezing timeout'
,
function
()
{
expect
(
window
.
clearTimeout
).
toHaveBeenCalled
With
(
100
);
expect
(
window
.
clearTimeout
).
toHaveBeenCalled
(
);
});
});
});
});
...
@@ -269,7 +253,7 @@
...
@@ -269,7 +253,7 @@
});
});
it
(
'reset the freezing timeout'
,
function
()
{
it
(
'reset the freezing timeout'
,
function
()
{
expect
(
window
.
clearTimeout
).
toHaveBeenCalled
With
(
100
);
expect
(
window
.
clearTimeout
).
toHaveBeenCalled
(
);
});
});
});
});
});
});
...
@@ -337,7 +321,7 @@
...
@@ -337,7 +321,7 @@
describe
(
'play'
,
function
()
{
describe
(
'play'
,
function
()
{
describe
(
'when the caption was not rendered'
,
function
()
{
describe
(
'when the caption was not rendered'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
window
.
onTouchBasedDevice
.
andReturn
(
[
'iPad'
]
);
initialize
();
initialize
();
videoCaption
.
play
();
videoCaption
.
play
();
});
});
...
...
common/lib/xmodule/xmodule/js/spec/video/video_control_spec.js
View file @
23dc10d0
...
@@ -2,15 +2,23 @@
...
@@ -2,15 +2,23 @@
describe
(
'VideoControl'
,
function
()
{
describe
(
'VideoControl'
,
function
()
{
var
state
,
videoControl
,
oldOTBD
;
var
state
,
videoControl
,
oldOTBD
;
function
initialize
()
{
function
initialize
(
fixture
)
{
loadFixtures
(
'video_all.html'
);
if
(
fixture
)
{
loadFixtures
(
fixture
);
}
else
{
loadFixtures
(
'video_all.html'
);
}
state
=
new
Video
(
'#example'
);
state
=
new
Video
(
'#example'
);
videoControl
=
state
.
videoControl
;
videoControl
=
state
.
videoControl
;
}
}
function
initializeYouTube
()
{
initialize
(
'video.html'
);
}
beforeEach
(
function
(){
beforeEach
(
function
(){
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
false
);
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
null
);
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -75,13 +83,13 @@
...
@@ -75,13 +83,13 @@
describe
(
'when on a touch based device'
,
function
()
{
describe
(
'when on a touch based device'
,
function
()
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
window
.
onTouchBasedDevice
.
andReturn
(
[
'iPad'
]
);
initialize
();
initialize
();
});
});
it
(
'does not add the play class to video control'
,
function
()
{
it
(
'does not add the play class to video control'
,
function
()
{
expect
(
$
(
'.video_control'
)).
not
.
toHaveClass
(
'play'
);
expect
(
$
(
'.video_control'
)).
toHaveClass
(
'play'
);
expect
(
$
(
'.video_control'
)).
not
.
toHaveAttr
(
'title'
,
'Play'
);
expect
(
$
(
'.video_control'
)).
toHaveAttr
(
'title'
,
'Play'
);
});
});
});
});
});
});
...
@@ -147,6 +155,136 @@
...
@@ -147,6 +155,136 @@
});
});
});
});
});
});
describe
(
'Play placeholder'
,
function
()
{
beforeEach
(
function
()
{
this
.
oldYT
=
window
.
YT
;
jasmine
.
stubRequests
();
window
.
YT
=
{
Player
:
function
()
{
},
PlayerState
:
this
.
oldYT
.
PlayerState
,
ready
:
function
(
callback
)
{
callback
();
}
};
spyOn
(
window
.
YT
,
'Player'
);
});
afterEach
(
function
()
{
window
.
YT
=
this
.
oldYT
;
});
it
(
'works correctly on calling proper methods'
,
function
()
{
initialize
();
var
btnPlay
=
state
.
el
.
find
(
'.btn-play'
);
videoControl
.
showPlayPlaceholder
();
expect
(
btnPlay
).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
btnPlay
).
toHaveAttrs
({
'aria-hidden'
:
'false'
,
'tabindex'
:
0
});
videoControl
.
hidePlayPlaceholder
();
expect
(
btnPlay
).
toHaveClass
(
'is-hidden'
);
expect
(
btnPlay
).
toHaveAttrs
({
'aria-hidden'
:
'true'
,
'tabindex'
:
-
1
});
});
var
cases
=
[
{
name
:
'PC'
,
isShown
:
false
,
isTouch
:
null
},
{
name
:
'iPad'
,
isShown
:
true
,
isTouch
:
[
'iPad'
]
},
{
name
:
'Android'
,
isShown
:
true
,
isTouch
:
[
'Android'
]
},
{
name
:
'iPhone'
,
isShown
:
false
,
isTouch
:
[
'iPhone'
]
}
];
$
.
each
(
cases
,
function
(
index
,
data
)
{
var
message
=
[
(
data
.
isShown
)
?
'is'
:
'is not'
,
' shown on'
,
data
.
name
].
join
(
''
);
it
(
message
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
(
data
.
isTouch
);
initialize
();
var
btnPlay
=
state
.
el
.
find
(
'.btn-play'
);
if
(
data
.
isShown
)
{
expect
(
btnPlay
).
not
.
toHaveClass
(
'is-hidden'
);
}
else
{
expect
(
btnPlay
).
toHaveClass
(
'is-hidden'
);
}
});
});
$
.
each
([
'iPad'
,
'Android'
],
function
(
index
,
device
)
{
it
(
'is shown on paused video on '
+
device
+
' in HTML5 player'
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
initialize
();
var
btnPlay
=
state
.
el
.
find
(
'.btn-play'
);
videoControl
.
play
();
videoControl
.
pause
();
expect
(
btnPlay
).
not
.
toHaveClass
(
'is-hidden'
);
});
it
(
'is hidden on playing video on '
+
device
+
' in HTML5 player'
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
initialize
();
var
btnPlay
=
state
.
el
.
find
(
'.btn-play'
);
videoControl
.
play
();
expect
(
btnPlay
).
toHaveClass
(
'is-hidden'
);
});
it
(
'is hidden on paused video on '
+
device
+
' in YouTube player'
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
initializeYouTube
();
var
btnPlay
=
state
.
el
.
find
(
'.btn-play'
);
videoControl
.
play
();
videoControl
.
pause
();
expect
(
btnPlay
).
toHaveClass
(
'is-hidden'
);
});
});
});
it
(
'show'
,
function
()
{
initialize
();
var
controls
=
state
.
el
.
find
(
'.video-controls'
);
controls
.
addClass
(
'is-hidden'
);
videoControl
.
show
();
expect
(
controls
).
not
.
toHaveClass
(
'is-hidden'
);
});
});
});
}).
call
(
this
);
}).
call
(
this
);
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
23dc10d0
...
@@ -57,7 +57,7 @@
...
@@ -57,7 +57,7 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
false
);
.
andReturn
(
null
);
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -119,8 +119,8 @@
...
@@ -119,8 +119,8 @@
window
.
YT
=
{
window
.
YT
=
{
Player
:
function
()
{
},
Player
:
function
()
{
},
PlayerState
:
oldYT
.
PlayerState
,
PlayerState
:
oldYT
.
PlayerState
,
ready
:
function
(
f
)
{
ready
:
function
(
callback
)
{
f
();
callback
();
}
}
};
};
...
@@ -156,19 +156,18 @@
...
@@ -156,19 +156,18 @@
// available globally. It is defined within the scope of Require
// available globally. It is defined within the scope of Require
// JS.
// JS.
describe
(
'when not on a touch based device'
,
function
()
{
describe
(
'when on a touch based device'
,
function
()
{
beforeEach
(
function
()
{
$
.
each
([
'iPad'
,
'Android'
],
function
(
index
,
device
)
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
it
(
'create video volume control on'
+
device
,
function
()
{
initialize
();
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
});
initialize
();
expect
(
videoVolumeControl
).
toBeUndefined
();
it
(
'create video volume control'
,
function
()
{
expect
(
state
.
el
.
find
(
'div.volume'
)).
not
.
toExist
();
expect
(
videoVolumeControl
).
toBeDefined
();
});
expect
(
videoVolumeControl
.
el
).
toHaveClass
(
'volume'
);
});
});
});
});
describe
(
'when on a touch based device'
,
function
()
{
describe
(
'when
not
on a touch based device'
,
function
()
{
var
oldOTBD
;
var
oldOTBD
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
...
@@ -343,16 +342,8 @@
...
@@ -343,16 +342,8 @@
state
.
videoPlayer
.
play
();
state
.
videoPlayer
.
play
();
waitsFor
(
function
()
{
waitsFor
(
function
()
{
var
duration
=
videoPlayer
.
duration
(),
return
videoPlayer
.
isPlaying
();
currentTime
=
videoPlayer
.
currentTime
;
},
'video begins playing'
,
WAIT_TIMEOUT
);
return
(
isFinite
(
currentTime
)
&&
currentTime
>
0
&&
isFinite
(
duration
)
&&
duration
>
0
);
},
'video begins playing'
,
10000
);
});
});
it
(
'Slider event causes log update'
,
function
()
{
it
(
'Slider event causes log update'
,
function
()
{
...
@@ -555,34 +546,24 @@
...
@@ -555,34 +546,24 @@
});
});
it
(
'video is paused on first endTime, start & end time are reset'
,
function
()
{
it
(
'video is paused on first endTime, start & end time are reset'
,
function
()
{
var
checkForStartEndTimeSet
=
true
;
var
duration
;
videoProgressSlider
.
notifyThroughHandleEnd
.
reset
();
videoProgressSlider
.
notifyThroughHandleEnd
.
reset
();
videoPlayer
.
pause
.
reset
();
videoPlayer
.
pause
.
reset
();
videoPlayer
.
play
();
videoPlayer
.
play
();
waitsFor
(
function
()
{
waitsFor
(
function
()
{
if
(
duration
=
Math
.
round
(
videoPlayer
.
currentTime
);
!
isFinite
(
videoPlayer
.
currentTime
)
||
videoPlayer
.
currentTime
<=
0
)
{
return
false
;
}
if
(
checkForStartEndTimeSet
)
{
checkForStartEndTimeSet
=
false
;
expect
(
videoPlayer
.
startTime
).
toBe
(
START_TIME
);
expect
(
videoPlayer
.
endTime
).
toBe
(
END_TIME
);
}
return
videoPlayer
.
pause
.
calls
.
length
===
1
return
videoPlayer
.
pause
.
calls
.
length
===
1
;
},
5000
,
'pause() has been called'
);
},
'pause() has been called'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
expect
(
videoPlayer
.
startTime
).
toBe
(
0
);
expect
(
videoPlayer
.
startTime
).
toBe
(
0
);
expect
(
videoPlayer
.
endTime
).
toBe
(
null
);
expect
(
videoPlayer
.
endTime
).
toBe
(
null
);
expect
(
duration
).
toBe
(
END_TIME
);
expect
(
videoProgressSlider
.
notifyThroughHandleEnd
)
expect
(
videoProgressSlider
.
notifyThroughHandleEnd
)
.
toHaveBeenCalledWith
({
end
:
true
});
.
toHaveBeenCalledWith
({
end
:
true
});
});
});
...
@@ -608,7 +589,7 @@
...
@@ -608,7 +589,7 @@
}
}
return
false
;
return
false
;
},
'Video is fully loaded.'
,
1000
);
},
'Video is fully loaded.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
var
htmlStr
;
var
htmlStr
;
...
@@ -637,7 +618,7 @@
...
@@ -637,7 +618,7 @@
it
(
'update the playback time on caption'
,
function
()
{
it
(
'update the playback time on caption'
,
function
()
{
waitsFor
(
function
()
{
waitsFor
(
function
()
{
return
videoPlayer
.
duration
()
>
0
;
return
videoPlayer
.
duration
()
>
0
;
},
'Video is fully loaded.'
,
1000
);
},
'Video is fully loaded.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
videoPlayer
.
updatePlayTime
(
60
);
videoPlayer
.
updatePlayTime
(
60
);
...
@@ -654,7 +635,7 @@
...
@@ -654,7 +635,7 @@
duration
=
videoPlayer
.
duration
();
duration
=
videoPlayer
.
duration
();
return
duration
>
0
;
return
duration
>
0
;
},
'Video is fully loaded.'
,
1000
);
},
'Video is fully loaded.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
videoPlayer
.
updatePlayTime
(
60
);
videoPlayer
.
updatePlayTime
(
60
);
...
@@ -692,9 +673,9 @@
...
@@ -692,9 +673,9 @@
waitsFor
(
function
()
{
waitsFor
(
function
()
{
duration
=
videoPlayer
.
duration
();
duration
=
videoPlayer
.
duration
();
return
duration
>
0
&&
return
videoPlayer
.
isPlaying
()
&&
videoPlayer
.
initialSeekToStartTime
===
false
;
videoPlayer
.
initialSeekToStartTime
===
false
;
},
'duration becomes available'
,
1000
);
},
'duration becomes available'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
expect
(
videoPlayer
.
startTime
).
toBe
(
START_TIME
);
expect
(
videoPlayer
.
startTime
).
toBe
(
START_TIME
);
...
@@ -724,11 +705,9 @@
...
@@ -724,11 +705,9 @@
videoPlayer
.
play
();
videoPlayer
.
play
();
waitsFor
(
function
()
{
waitsFor
(
function
()
{
duration
=
videoPlayer
.
duration
();
return
videoPlayer
.
isPlaying
()
&&
return
duration
>
0
&&
videoPlayer
.
initialSeekToStartTime
===
false
;
videoPlayer
.
initialSeekToStartTime
===
false
;
},
'updatePlayTime was invoked and duration is set'
,
5000
);
},
'updatePlayTime was invoked and duration is set'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
runs
(
function
()
{
expect
(
videoPlayer
.
endTime
).
toBe
(
null
);
expect
(
videoPlayer
.
endTime
).
toBe
(
null
);
...
@@ -896,6 +875,62 @@
...
@@ -896,6 +875,62 @@
expect
(
realValue
).
toEqual
(
expectedValue
);
expect
(
realValue
).
toEqual
(
expectedValue
);
});
});
});
});
describe
(
'on Touch devices'
,
function
()
{
it
(
'`is-touch` class name is added to container'
,
function
()
{
$
.
each
([
'iPad'
,
'Android'
,
'iPhone'
],
function
(
index
,
device
)
{
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
initialize
();
expect
(
state
.
el
).
toHaveClass
(
'is-touch'
);
});
});
it
(
'modules are not initialized on iPhone'
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
([
'iPhone'
]);
initialize
();
var
modules
=
[
videoControl
,
videoCaption
,
videoProgressSlider
,
videoSpeedControl
,
videoVolumeControl
];
$
.
each
(
modules
,
function
(
index
,
module
)
{
expect
(
module
).
toBeUndefined
();
});
});
$
.
each
([
'iPad'
,
'Android'
],
function
(
index
,
device
)
{
var
message
=
'controls become visible after playing starts on '
+
device
;
it
(
message
,
function
()
{
var
controls
;
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
runs
(
function
()
{
initialize
();
controls
=
state
.
el
.
find
(
'.video-controls'
);
});
waitsFor
(
function
()
{
return
state
.
el
.
hasClass
(
'is-initialized'
);
},
'Video is not initialized.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
controls
).
toHaveClass
(
'is-hidden'
);
videoPlayer
.
play
();
});
waitsFor
(
function
()
{
return
videoPlayer
.
isPlaying
();
},
'Video does not play.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
controls
).
not
.
toHaveClass
(
'is-hidden'
);
});
});
});
});
});
});
}).
call
(
this
);
}).
call
(
this
);
common/lib/xmodule/xmodule/js/spec/video/video_progress_slider_spec.js
View file @
23dc10d0
...
@@ -12,7 +12,7 @@
...
@@ -12,7 +12,7 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
false
);
.
andReturn
(
null
);
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -44,18 +44,23 @@
...
@@ -44,18 +44,23 @@
});
});
describe
(
'on a touch-based device'
,
function
()
{
describe
(
'on a touch-based device'
,
function
()
{
beforeEach
(
function
()
{
it
(
'does not build the slider on iPhone'
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
spyOn
(
$
.
fn
,
'slider'
).
andCallThrough
(
);
window
.
onTouchBasedDevice
.
andReturn
([
'iPhone'
]
);
initialize
();
initialize
();
});
it
(
'does not build the slider'
,
function
()
{
expect
(
videoProgressSlider
).
toBeUndefined
();
expect
(
videoProgressSlider
.
slider
).
toBeUndefined
();
// We can't expect $.fn.slider not to have been called,
// We can't expect $.fn.slider not to have been called,
// because sliders are used in other parts of Video.
// because sliders are used in other parts of Video.
});
});
$
.
each
([
'iPad'
,
'Android'
],
function
(
index
,
device
)
{
it
(
'build the slider on '
+
device
,
function
()
{
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
initialize
();
expect
(
videoProgressSlider
.
slider
).
toBeDefined
();
});
});
});
});
});
});
...
@@ -127,125 +132,58 @@
...
@@ -127,125 +132,58 @@
initialize
();
initialize
();
spyOn
(
$
.
fn
,
'slider'
).
andCallThrough
();
spyOn
(
$
.
fn
,
'slider'
).
andCallThrough
();
spyOn
(
videoPlayer
,
'onSlideSeek'
).
andCallThrough
();
spyOn
(
videoPlayer
,
'onSlideSeek'
).
andCallThrough
();
state
.
videoPlayer
.
play
();
waitsFor
(
function
()
{
var
duration
=
videoPlayer
.
duration
(),
currentTime
=
videoPlayer
.
currentTime
;
return
(
isFinite
(
currentTime
)
&&
currentTime
>
0
&&
isFinite
(
duration
)
&&
duration
>
0
);
},
'video begins playing'
,
10000
);
});
});
it
(
'freeze the slider'
,
function
()
{
it
(
'freeze the slider'
,
function
()
{
runs
(
function
()
{
videoProgressSlider
.
onSlide
(
videoProgressSlider
.
onSlide
(
jQuery
.
Event
(
'slide'
),
{
value
:
20
}
jQuery
.
Event
(
'slide'
),
{
value
:
20
}
);
);
expect
(
videoProgressSlider
.
frozen
).
toBeTruthy
();
expect
(
videoProgressSlider
.
frozen
).
toBeTruthy
();
});
});
});
// Turned off test due to flakiness (11/25/13)
it
(
'trigger seek event'
,
function
()
{
xit
(
'trigger seek event'
,
function
()
{
videoProgressSlider
.
onSlide
(
runs
(
function
()
{
jQuery
.
Event
(
'slide'
),
{
value
:
20
}
videoProgressSlider
.
onSlide
(
);
jQuery
.
Event
(
'slide'
),
{
value
:
20
}
);
expect
(
videoPlayer
.
onSlideSeek
).
toHaveBeenCalled
();
waitsFor
(
function
()
{
expect
(
videoPlayer
.
onSlideSeek
).
toHaveBeenCalled
();
return
Math
.
round
(
videoPlayer
.
currentTime
)
===
20
;
},
'currentTime got updated'
,
10000
);
});
});
});
});
});
describe
(
'onStop'
,
function
()
{
describe
(
'onStop'
,
function
()
{
// We will store default window.setTimeout() function here.
var
oldSetTimeout
=
null
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
// Store original window.setTimeout() function. If we do not do
jasmine
.
Clock
.
useMock
();
// this, then all other tests that rely on code which uses
// window.setTimeout() function might (and probably will) fail.
oldSetTimeout
=
window
.
setTimeout
;
// Redefine window.setTimeout() function as a spy.
window
.
setTimeout
=
jasmine
.
createSpy
()
.
andCallFake
(
function
(
callback
,
timeout
)
{
return
5
;
});
window
.
setTimeout
.
andReturn
(
100
);
initialize
();
initialize
();
spyOn
(
videoPlayer
,
'onSlideSeek'
).
andCallThrough
();
spyOn
(
videoPlayer
,
'onSlideSeek'
).
andCallThrough
();
videoPlayer
.
play
();
waitsFor
(
function
()
{
var
duration
=
videoPlayer
.
duration
(),
currentTime
=
videoPlayer
.
currentTime
;
return
(
isFinite
(
currentTime
)
&&
currentTime
>
0
&&
isFinite
(
duration
)
&&
duration
>
0
);
},
'video begins playing'
,
10000
);
});
afterEach
(
function
()
{
// Reset the default window.setTimeout() function. If we do not
// do this, then all other tests that rely on code which uses
// window.setTimeout() function might (and probably will) fail.
window
.
setTimeout
=
oldSetTimeout
;
});
});
it
(
'freeze the slider'
,
function
()
{
it
(
'freeze the slider'
,
function
()
{
runs
(
function
()
{
videoProgressSlider
.
onStop
(
videoProgressSlider
.
onStop
(
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
);
);
expect
(
videoProgressSlider
.
frozen
).
toBeTruthy
();
expect
(
videoProgressSlider
.
frozen
).
toBeTruthy
();
});
});
});
// Turned off test due to flakiness (11/25/13)
it
(
'trigger seek event'
,
function
()
{
xit
(
'trigger seek event'
,
function
()
{
videoProgressSlider
.
onStop
(
runs
(
function
()
{
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
videoProgressSlider
.
onStop
(
);
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
);
expect
(
videoPlayer
.
onSlideSeek
).
toHaveBeenCalled
();
waitsFor
(
function
()
{
expect
(
videoPlayer
.
onSlideSeek
).
toHaveBeenCalled
();
return
Math
.
round
(
videoPlayer
.
currentTime
)
===
20
;
},
'currentTime got updated'
,
10000
);
});
});
});
it
(
'set timeout to unfreeze the slider'
,
function
()
{
it
(
'set timeout to unfreeze the slider'
,
function
()
{
runs
(
function
()
{
videoProgressSlider
.
onStop
(
videoProgressSlider
.
onStop
(
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
jQuery
.
Event
(
'stop'
),
{
value
:
20
}
);
);
expect
(
window
.
setTimeout
).
toHaveBeenCalledWith
(
jasmine
.
Clock
.
tick
(
200
);
jasmine
.
any
(
Function
),
200
);
expect
(
videoProgressSlider
.
frozen
).
toBeFalsy
();
window
.
setTimeout
.
mostRecentCall
.
args
[
0
]();
expect
(
videoProgressSlider
.
frozen
).
toBeFalsy
();
});
});
});
});
});
...
@@ -317,15 +255,7 @@
...
@@ -317,15 +255,7 @@
videoPlayer
.
play
();
videoPlayer
.
play
();
waitsFor
(
function
()
{
waitsFor
(
function
()
{
var
duration
=
videoPlayer
.
duration
(),
return
videoPlayer
.
isPlaying
();
currentTime
=
videoPlayer
.
currentTime
;
return
(
isFinite
(
duration
)
&&
duration
>
0
&&
isFinite
(
currentTime
)
&&
currentTime
>
0
);
},
'duration is set, video is playing'
,
5000
);
},
'duration is set, video is playing'
,
5000
);
runs
(
function
()
{
runs
(
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_quality_control_spec.js
View file @
23dc10d0
...
@@ -13,7 +13,7 @@
...
@@ -13,7 +13,7 @@
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
false
);
.
andReturn
(
null
);
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -49,7 +49,7 @@
...
@@ -49,7 +49,7 @@
'role'
:
'button'
,
'role'
:
'button'
,
'title'
:
'HD off'
,
'title'
:
'HD off'
,
'aria-disabled'
:
'false'
'aria-disabled'
:
'false'
});
});
});
});
it
(
'bind the quality control'
,
function
()
{
it
(
'bind the quality control'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_speed_control_spec.js
View file @
23dc10d0
...
@@ -12,7 +12,7 @@
...
@@ -12,7 +12,7 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
false
);
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
null
);
});
});
...
@@ -48,7 +48,7 @@
...
@@ -48,7 +48,7 @@
'role'
:
'button'
,
'role'
:
'button'
,
'title'
:
'Speeds'
,
'title'
:
'Speeds'
,
'aria-disabled'
:
'false'
'aria-disabled'
:
'false'
});
});
});
});
it
(
'bind to change video speed link'
,
function
()
{
it
(
'bind to change video speed link'
,
function
()
{
...
@@ -57,16 +57,12 @@
...
@@ -57,16 +57,12 @@
});
});
describe
(
'when running on touch based device'
,
function
()
{
describe
(
'when running on touch based device'
,
function
()
{
beforeEach
(
function
()
{
$
.
each
([
'iPad'
,
'Android'
],
function
(
index
,
device
)
{
window
.
onTouchBasedDevice
.
andReturn
(
true
);
it
(
'is not rendered on'
+
device
,
function
()
{
initialize
();
window
.
onTouchBasedDevice
.
andReturn
([
device
]);
});
initialize
();
expect
(
state
.
el
.
find
(
'div.speeds'
)).
not
.
toExist
();
it
(
'open the speed toggle on click'
,
function
()
{
});
$
(
'.speeds'
).
click
();
expect
(
$
(
'.speeds'
)).
toHaveClass
(
'open'
);
$
(
'.speeds'
).
click
();
expect
(
$
(
'.speeds'
)).
not
.
toHaveClass
(
'open'
);
});
});
});
});
...
@@ -96,7 +92,7 @@
...
@@ -96,7 +92,7 @@
// 2. Speed anchor
// 2. Speed anchor
// 3. A number of speed entry anchors
// 3. A number of speed entry anchors
// 4. Volume anchor
// 4. Volume anchor
// If an
other focusable element is inserted or if the order is changed, things will
// If an
other focusable element is inserted or if the order is changed, things will
// malfunction as a flag, state.previousFocus, is set in the 1,3,4 elements and is
// malfunction as a flag, state.previousFocus, is set in the 1,3,4 elements and is
// used to determine the behavior of foucus() and blur() for the speed anchor.
// used to determine the behavior of foucus() and blur() for the speed anchor.
it
(
'checks for a certain order in focusable elements in video controls'
,
function
()
{
it
(
'checks for a certain order in focusable elements in video controls'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_volume_control_spec.js
View file @
23dc10d0
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
beforeEach
(
function
()
{
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
false
);
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
).
andReturn
(
null
);
});
});
afterEach
(
function
()
{
afterEach
(
function
()
{
...
@@ -58,9 +58,9 @@
...
@@ -58,9 +58,9 @@
});
});
expect
(
sliderHandle
.
attr
(
'aria-valuenow'
)).
toBeInRange
(
0
,
100
);
expect
(
sliderHandle
.
attr
(
'aria-valuenow'
)).
toBeInRange
(
0
,
100
);
expect
(
sliderHandle
.
attr
(
'aria-valuetext'
)).
toBeInArray
(
arr
);
expect
(
sliderHandle
.
attr
(
'aria-valuetext'
)).
toBeInArray
(
arr
);
});
});
it
(
'add ARIA attributes to volume control'
,
function
()
{
it
(
'add ARIA attributes to volume control'
,
function
()
{
var
volumeControl
=
$
(
'div.volume>a'
);
var
volumeControl
=
$
(
'div.volume>a'
);
expect
(
volumeControl
).
toHaveAttrs
({
expect
(
volumeControl
).
toHaveAttrs
({
...
@@ -121,38 +121,38 @@
...
@@ -121,38 +121,38 @@
{
{
range
:
'muted'
,
range
:
'muted'
,
value
:
0
,
value
:
0
,
expectation
:
'muted'
expectation
:
'muted'
},
},
{
{
range
:
'in ]0,20]'
,
range
:
'in ]0,20]'
,
value
:
10
,
value
:
10
,
expectation
:
'very low'
expectation
:
'very low'
},
},
{
{
range
:
'in ]20,40]'
,
range
:
'in ]20,40]'
,
value
:
30
,
value
:
30
,
expectation
:
'low'
expectation
:
'low'
},
},
{
{
range
:
'in ]40,60]'
,
range
:
'in ]40,60]'
,
value
:
50
,
value
:
50
,
expectation
:
'average'
expectation
:
'average'
},
},
{
{
range
:
'in ]60,80]'
,
range
:
'in ]60,80]'
,
value
:
70
,
value
:
70
,
expectation
:
'loud'
expectation
:
'loud'
},
},
{
{
range
:
'in ]80,100['
,
range
:
'in ]80,100['
,
value
:
90
,
value
:
90
,
expectation
:
'very loud'
expectation
:
'very loud'
},
},
{
{
range
:
'maximum'
,
range
:
'maximum'
,
value
:
100
,
value
:
100
,
expectation
:
'maximum'
expectation
:
'maximum'
}
}
];
];
$
.
each
(
initialData
,
function
(
index
,
data
)
{
$
.
each
(
initialData
,
function
(
index
,
data
)
{
...
@@ -162,7 +162,7 @@
...
@@ -162,7 +162,7 @@
value
:
data
.
value
value
:
data
.
value
});
});
});
});
it
(
'changes ARIA attributes'
,
function
()
{
it
(
'changes ARIA attributes'
,
function
()
{
var
sliderHandle
=
$
(
'div.volume-slider>a.ui-slider-handle'
);
var
sliderHandle
=
$
(
'div.volume-slider>a.ui-slider-handle'
);
expect
(
sliderHandle
).
toHaveAttrs
({
expect
(
sliderHandle
).
toHaveAttrs
({
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
23dc10d0
...
@@ -44,15 +44,29 @@ function (VideoPlayer) {
...
@@ -44,15 +44,29 @@ function (VideoPlayer) {
state
.
initialize
(
element
)
state
.
initialize
(
element
)
.
done
(
function
()
{
.
done
(
function
()
{
// On iPhones and iPods native controls are used.
if
(
/iP
(
hone|od
)
/i
.
test
(
state
.
isTouch
[
0
]))
{
_hideWaitPlaceholder
(
state
);
state
.
el
.
trigger
(
'initialize'
,
arguments
);
return
false
;
}
_initializeModules
(
state
)
_initializeModules
(
state
)
.
done
(
function
()
{
.
done
(
function
()
{
state
.
el
// On iPad ready state occurs just after start playing.
.
addClass
(
'is-initialized'
)
// We hide controls before video starts playing.
.
find
(
'.spinner'
)
if
(
/iPad|Android/i
.
test
(
state
.
isTouch
[
0
]))
{
.
attr
({
state
.
el
.
on
(
'play'
,
_
.
once
(
function
()
{
'aria-hidden'
:
'true'
,
state
.
trigger
(
'videoControl.show'
,
null
);
'tabindex'
:
-
1
}));
});
}
else
{
// On PC show controls immediately.
state
.
trigger
(
'videoControl.show'
,
null
);
}
_hideWaitPlaceholder
(
state
);
state
.
el
.
trigger
(
'initialize'
,
arguments
);
});
});
});
});
};
};
...
@@ -235,6 +249,16 @@ function (VideoPlayer) {
...
@@ -235,6 +249,16 @@ function (VideoPlayer) {
return
true
;
return
true
;
}
}
function
_hideWaitPlaceholder
(
state
)
{
state
.
el
.
addClass
(
'is-initialized'
)
.
find
(
'.spinner'
)
.
attr
({
'aria-hidden'
:
'true'
,
'tabindex'
:
-
1
});
}
function
_setConfigurations
(
state
)
{
function
_setConfigurations
(
state
)
{
_configureCaptions
(
state
);
_configureCaptions
(
state
);
_setPlayerMode
(
state
);
_setPlayerMode
(
state
);
...
@@ -242,7 +266,7 @@ function (VideoPlayer) {
...
@@ -242,7 +266,7 @@ function (VideoPlayer) {
// Possible value are: 'visible', 'hiding', and 'invisible'.
// Possible value are: 'visible', 'hiding', and 'invisible'.
state
.
controlState
=
'visible'
;
state
.
controlState
=
'visible'
;
state
.
controlHideTimeout
=
null
;
state
.
controlHideTimeout
=
null
;
state
.
captionState
=
'visible'
;
state
.
captionState
=
'
in
visible'
;
state
.
captionHideTimeout
=
null
;
state
.
captionHideTimeout
=
null
;
}
}
...
@@ -299,12 +323,17 @@ function (VideoPlayer) {
...
@@ -299,12 +323,17 @@ function (VideoPlayer) {
// element has a CSS class 'fullscreen'.
// element has a CSS class 'fullscreen'.
this
.
__dfd__
=
$
.
Deferred
();
this
.
__dfd__
=
$
.
Deferred
();
this
.
isFullScreen
=
false
;
this
.
isFullScreen
=
false
;
this
.
isTouch
=
onTouchBasedDevice
()
||
''
;
// The parent element of the video, and the ID.
// The parent element of the video, and the ID.
this
.
el
=
$
(
element
).
find
(
'.video'
);
this
.
el
=
$
(
element
).
find
(
'.video'
);
this
.
elVideoWrapper
=
this
.
el
.
find
(
'.video-wrapper'
);
this
.
elVideoWrapper
=
this
.
el
.
find
(
'.video-wrapper'
);
this
.
id
=
this
.
el
.
attr
(
'id'
).
replace
(
/video_/
,
''
);
this
.
id
=
this
.
el
.
attr
(
'id'
).
replace
(
/video_/
,
''
);
if
(
this
.
isTouch
)
{
this
.
el
.
addClass
(
'is-touch'
);
}
// jQuery .data() return object with keys in lower camelCase format.
// jQuery .data() return object with keys in lower camelCase format.
data
=
this
.
el
.
data
();
data
=
this
.
el
.
data
();
...
...
common/lib/xmodule/xmodule/js/src/video/02_html5_video.js
View file @
23dc10d0
...
@@ -90,6 +90,10 @@ function () {
...
@@ -90,6 +90,10 @@ function () {
return
[
0.75
,
1.0
,
1.25
,
1.5
];
return
[
0.75
,
1.0
,
1.25
,
1.5
];
};
};
Player
.
prototype
.
_getLogs
=
function
()
{
return
this
.
logs
;
};
return
Player
;
return
Player
;
/*
/*
...
@@ -129,8 +133,10 @@ function () {
...
@@ -129,8 +133,10 @@ function () {
* }
* }
*/
*/
function
Player
(
el
,
config
)
{
function
Player
(
el
,
config
)
{
var
sourceStr
,
_this
,
errorMessage
;
var
isTouch
=
onTouchBasedDevice
()
||
''
,
sourceStr
,
_this
,
errorMessage
;
this
.
logs
=
[];
// Initially we assume that el is a DOM element. If jQuery selector
// Initially we assume that el is a DOM element. If jQuery selector
// fails to select something, we assume that el is an ID of a DOM
// fails to select something, we assume that el is an ID of a DOM
// element. We try to select by ID. If jQuery fails this time, we
// element. We try to select by ID. If jQuery fails this time, we
...
@@ -214,40 +220,51 @@ function () {
...
@@ -214,40 +220,51 @@ function () {
// determine what the video is currently doing.
// determine what the video is currently doing.
this
.
videoEl
=
$
(
this
.
video
);
this
.
videoEl
=
$
(
this
.
video
);
if
(
/iP
(
hone|od
)
/i
.
test
(
isTouch
[
0
]))
{
this
.
videoEl
.
prop
(
'controls'
,
true
);
}
this
.
playerState
=
HTML5Video
.
PlayerState
.
UNSTARTED
;
this
.
playerState
=
HTML5Video
.
PlayerState
.
UNSTARTED
;
// Attach a 'click' event on the <video> element. It will cause the
// Attach a 'click' event on the <video> element. It will cause the
// video to pause/play.
// video to pause/play.
this
.
videoEl
.
on
(
'click'
,
function
(
event
)
{
this
.
videoEl
.
on
(
'click'
,
function
(
event
)
{
if
(
_this
.
playerState
===
HTML5Video
.
PlayerState
.
PAUSED
)
{
var
PlayerState
=
HTML5Video
.
PlayerState
;
_this
.
playVideo
();
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
if
(
_this
.
playerState
===
PlayerState
.
PLAYING
)
{
_this
.
callStateChangeCallback
();
}
else
if
(
_this
.
playerState
===
HTML5Video
.
PlayerState
.
PLAYING
)
{
_this
.
pauseVideo
();
_this
.
pauseVideo
();
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
_this
.
playerState
=
PlayerState
.
PAUSED
;
_this
.
callStateChangeCallback
();
}
else
{
_this
.
playVideo
();
_this
.
playerState
=
PlayerState
.
PLAYING
;
_this
.
callStateChangeCallback
();
_this
.
callStateChangeCallback
();
}
}
});
});
var
events
=
[
'loadstart'
,
'progress'
,
'suspend'
,
'abort'
,
'error'
,
'emptied'
,
'stalled'
,
'play'
,
'pause'
,
'loadedmetadata'
,
'loadeddata'
,
'waiting'
,
'playing'
,
'canplay'
,
'canplaythrough'
,
'seeking'
,
'seeked'
,
'timeupdate'
,
'ended'
,
'ratechange'
,
'durationchange'
,
'volumechange'
];
$
.
each
(
events
,
function
(
index
,
eventName
)
{
_this
.
video
.
addEventListener
(
eventName
,
function
()
{
_this
.
logs
.
push
({
'event name'
:
eventName
,
'state'
:
_this
.
playerState
});
el
.
trigger
(
'html5:'
+
eventName
,
arguments
);
});
});
// When the <video> tag has been processed by the browser, and it
// When the <video> tag has been processed by the browser, and it
// is ready for playback, notify other parts of the VideoPlayer,
// is ready for playback, notify other parts of the VideoPlayer,
// and initially pause the video.
// and initially pause the video.
this
.
video
.
addEventListener
(
'canplay'
,
function
()
{
this
.
video
.
addEventListener
(
'loadedmetadata'
,
function
()
{
// Because Firefox triggers 'canplay' event every time when
// 'currentTime' property changes, we must make sure that this
// block of code runs only once. Otherwise, this will be an
// endless loop ('currentTime' property is changed below).
//
// Chrome is immune to this behavior.
if
(
_this
.
playerState
!==
HTML5Video
.
PlayerState
.
UNSTARTED
)
{
return
;
}
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
if
(
$
.
isFunction
(
_this
.
config
.
events
.
onReady
))
{
if
(
$
.
isFunction
(
_this
.
config
.
events
.
onReady
))
{
_this
.
config
.
events
.
onReady
(
null
);
_this
.
config
.
events
.
onReady
(
null
);
}
}
...
@@ -259,6 +276,10 @@ function () {
...
@@ -259,6 +276,10 @@ function () {
_this
.
callStateChangeCallback
();
_this
.
callStateChangeCallback
();
},
false
);
},
false
);
this
.
video
.
addEventListener
(
'playing'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
},
false
);
// Register the 'pause' event.
// Register the 'pause' event.
this
.
video
.
addEventListener
(
'pause'
,
function
()
{
this
.
video
.
addEventListener
(
'pause'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
23dc10d0
...
@@ -60,7 +60,7 @@ function (HTML5Video, Resizer) {
...
@@ -60,7 +60,7 @@ function (HTML5Video, Resizer) {
// via the 'state' object. Much easier to work this way - you don't
// via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
// have to do repeated jQuery element selects.
function
_initialize
(
state
)
{
function
_initialize
(
state
)
{
var
youTubeId
,
player
,
videoWidth
,
videoHeight
;
var
youTubeId
,
player
;
// The function is called just once to apply pre-defined configurations
// The function is called just once to apply pre-defined configurations
// by student before video starts playing. Waits until the video's
// by student before video starts playing. Waits until the video's
...
@@ -124,6 +124,24 @@ function (HTML5Video, Resizer) {
...
@@ -124,6 +124,24 @@ function (HTML5Video, Resizer) {
onStateChange
:
state
.
videoPlayer
.
onStateChange
onStateChange
:
state
.
videoPlayer
.
onStateChange
}
}
});
});
player
=
state
.
videoEl
=
state
.
videoPlayer
.
player
.
videoEl
;
player
[
0
].
addEventListener
(
'loadedmetadata'
,
function
()
{
var
videoWidth
=
player
[
0
].
videoWidth
||
player
.
width
(),
videoHeight
=
player
[
0
].
videoHeight
||
player
.
height
();
_resize
(
state
,
videoWidth
,
videoHeight
);
state
.
trigger
(
'videoControl.updateVcrVidTime'
,
{
time
:
0
,
duration
:
state
.
videoPlayer
.
duration
()
}
);
},
false
);
}
else
{
// if (state.videoType === 'youtube') {
}
else
{
// if (state.videoType === 'youtube') {
if
(
state
.
currentPlayerMode
===
'flash'
)
{
if
(
state
.
currentPlayerMode
===
'flash'
)
{
youTubeId
=
state
.
youtubeId
();
youTubeId
=
state
.
youtubeId
();
...
@@ -140,11 +158,18 @@ function (HTML5Video, Resizer) {
...
@@ -140,11 +158,18 @@ function (HTML5Video, Resizer) {
.
onPlaybackQualityChange
.
onPlaybackQualityChange
}
}
});
});
player
=
state
.
videoEl
=
state
.
el
.
find
(
'iframe'
);
videoWidth
=
player
.
attr
(
'width'
)
||
player
.
width
();
videoHeight
=
player
.
attr
(
'height'
)
||
player
.
height
();
_resize
(
state
,
videoWidth
,
videoHeight
);
state
.
el
.
on
(
'initialize'
,
function
()
{
var
player
=
state
.
videoEl
=
state
.
el
.
find
(
'iframe'
),
videoWidth
=
player
.
attr
(
'width'
)
||
player
.
width
(),
videoHeight
=
player
.
attr
(
'height'
)
||
player
.
height
();
_resize
(
state
,
videoWidth
,
videoHeight
);
});
}
if
(
state
.
isTouch
)
{
dfd
.
resolve
();
}
}
}
}
...
@@ -154,10 +179,17 @@ function (HTML5Video, Resizer) {
...
@@ -154,10 +179,17 @@ function (HTML5Video, Resizer) {
elementRatio
:
videoWidth
/
videoHeight
,
elementRatio
:
videoWidth
/
videoHeight
,
container
:
state
.
videoEl
.
parent
()
container
:
state
.
videoEl
.
parent
()
})
})
.
setMode
(
'width'
)
.
callbacks
.
once
(
function
()
{
.
callbacks
.
once
(
function
()
{
state
.
trigger
(
'videoCaption.resize'
,
null
);
state
.
trigger
(
'videoCaption.resize'
,
null
);
})
.
setMode
(
'width'
);
// Update captions size when controls becomes visible on iPad or Android
if
(
/iPad|Android/i
.
test
(
state
.
isTouch
[
0
]))
{
state
.
el
.
on
(
'controls:show'
,
function
()
{
state
.
trigger
(
'videoCaption.resize'
,
null
);
});
});
}
$
(
window
).
bind
(
'resize'
,
_
.
debounce
(
state
.
resizer
.
align
,
100
));
$
(
window
).
bind
(
'resize'
,
_
.
debounce
(
state
.
resizer
.
align
,
100
));
}
}
...
@@ -229,7 +261,7 @@ function (HTML5Video, Resizer) {
...
@@ -229,7 +261,7 @@ function (HTML5Video, Resizer) {
// video. `endTime` will be set to `null`, and this if statement
// video. `endTime` will be set to `null`, and this if statement
// will not be executed on next runs.
// will not be executed on next runs.
if
(
if
(
this
.
videoPlayer
.
endTime
!=
null
&&
this
.
videoPlayer
.
endTime
!=
=
null
&&
this
.
videoPlayer
.
endTime
<=
this
.
videoPlayer
.
currentTime
this
.
videoPlayer
.
endTime
<=
this
.
videoPlayer
.
currentTime
)
{
)
{
this
.
videoPlayer
.
pause
();
this
.
videoPlayer
.
pause
();
...
@@ -297,6 +329,8 @@ function (HTML5Video, Resizer) {
...
@@ -297,6 +329,8 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
player
[
methodName
](
youtubeId
,
time
);
this
.
videoPlayer
.
player
[
methodName
](
youtubeId
,
time
);
this
.
videoPlayer
.
updatePlayTime
(
time
);
this
.
videoPlayer
.
updatePlayTime
(
time
);
}
}
this
.
el
.
trigger
(
'speedchange'
,
arguments
);
}
}
// Every 200 ms, if the video is playing, we call the function update, via
// Every 200 ms, if the video is playing, we call the function update, via
...
@@ -343,6 +377,8 @@ function (HTML5Video, Resizer) {
...
@@ -343,6 +377,8 @@ function (HTML5Video, Resizer) {
}
}
this
.
videoPlayer
.
updatePlayTime
(
newTime
);
this
.
videoPlayer
.
updatePlayTime
(
newTime
);
this
.
el
.
trigger
(
'seek'
,
arguments
);
}
}
function
onEnded
()
{
function
onEnded
()
{
...
@@ -368,6 +404,8 @@ function (HTML5Video, Resizer) {
...
@@ -368,6 +404,8 @@ function (HTML5Video, Resizer) {
// `duration`. In this case, slider doesn't reach the end point of
// `duration`. In this case, slider doesn't reach the end point of
// timeline.
// timeline.
this
.
videoPlayer
.
updatePlayTime
(
time
);
this
.
videoPlayer
.
updatePlayTime
(
time
);
this
.
el
.
trigger
(
'ended'
,
arguments
);
}
}
function
onPause
()
{
function
onPause
()
{
...
@@ -386,6 +424,8 @@ function (HTML5Video, Resizer) {
...
@@ -386,6 +424,8 @@ function (HTML5Video, Resizer) {
if
(
this
.
config
.
show_captions
)
{
if
(
this
.
config
.
show_captions
)
{
this
.
trigger
(
'videoCaption.pause'
,
null
);
this
.
trigger
(
'videoCaption.pause'
,
null
);
}
}
this
.
el
.
trigger
(
'pause'
,
arguments
);
}
}
function
onPlay
()
{
function
onPlay
()
{
...
@@ -415,6 +455,8 @@ function (HTML5Video, Resizer) {
...
@@ -415,6 +455,8 @@ function (HTML5Video, Resizer) {
}
}
this
.
videoPlayer
.
ready
();
this
.
videoPlayer
.
ready
();
this
.
el
.
trigger
(
'play'
,
arguments
);
}
}
function
onUnstarted
()
{
}
function
onUnstarted
()
{
}
...
@@ -429,22 +471,17 @@ function (HTML5Video, Resizer) {
...
@@ -429,22 +471,17 @@ function (HTML5Video, Resizer) {
quality
=
this
.
videoPlayer
.
player
.
getPlaybackQuality
();
quality
=
this
.
videoPlayer
.
player
.
getPlaybackQuality
();
this
.
trigger
(
'videoQualityControl.onQualityChange'
,
quality
);
this
.
trigger
(
'videoQualityControl.onQualityChange'
,
quality
);
this
.
el
.
trigger
(
'qualitychange'
,
arguments
);
}
}
function
onReady
()
{
function
onReady
()
{
var
availablePlaybackRates
,
baseSpeedSubs
,
_this
,
var
_this
=
this
,
availablePlaybackRates
,
baseSpeedSubs
,
player
,
videoWidth
,
videoHeight
;
player
,
videoWidth
,
videoHeight
;
dfd
.
resolve
();
dfd
.
resolve
();
if
(
this
.
videoType
===
'html5'
)
{
player
=
this
.
videoEl
=
this
.
videoPlayer
.
player
.
videoEl
;
videoWidth
=
player
[
0
].
videoWidth
||
player
.
width
();
videoHeight
=
player
[
0
].
videoHeight
||
player
.
height
();
_resize
(
this
,
videoWidth
,
videoHeight
);
}
this
.
videoPlayer
.
log
(
'load_video'
);
this
.
videoPlayer
.
log
(
'load_video'
);
availablePlaybackRates
=
this
.
videoPlayer
.
player
availablePlaybackRates
=
this
.
videoPlayer
.
player
...
@@ -469,7 +506,7 @@ function (HTML5Video, Resizer) {
...
@@ -469,7 +506,7 @@ function (HTML5Video, Resizer) {
this
.
currentPlayerMode
===
'html5'
&&
this
.
currentPlayerMode
===
'html5'
&&
this
.
videoType
===
'youtube'
this
.
videoType
===
'youtube'
)
{
)
{
if
(
availablePlaybackRates
.
length
===
1
)
{
if
(
availablePlaybackRates
.
length
===
1
&&
!
this
.
isTouch
)
{
// This condition is needed in cases when Firefox version is
// This condition is needed in cases when Firefox version is
// less than 20. In those versions HTML5 playback could only
// less than 20. In those versions HTML5 playback could only
// happen at 1 speed (no speed changing). Therefore, in this
// happen at 1 speed (no speed changing). Therefore, in this
...
@@ -479,14 +516,11 @@ function (HTML5Video, Resizer) {
...
@@ -479,14 +516,11 @@ function (HTML5Video, Resizer) {
// have 1 speed available, we fall back to Flash.
// have 1 speed available, we fall back to Flash.
_restartUsingFlash
(
this
);
_restartUsingFlash
(
this
);
return
;
}
else
if
(
availablePlaybackRates
.
length
>
1
)
{
}
else
if
(
availablePlaybackRates
.
length
>
1
)
{
// We need to synchronize available frame rates with the ones
// We need to synchronize available frame rates with the ones
// that the user specified.
// that the user specified.
baseSpeedSubs
=
this
.
videos
[
'1.0'
];
baseSpeedSubs
=
this
.
videos
[
'1.0'
];
_this
=
this
;
// this.videos is a dictionary containing various frame rates
// this.videos is a dictionary containing various frame rates
// and their associated subs.
// and their associated subs.
...
@@ -520,10 +554,11 @@ function (HTML5Video, Resizer) {
...
@@ -520,10 +554,11 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
player
.
setPlaybackRate
(
this
.
speed
);
this
.
videoPlayer
.
player
.
setPlaybackRate
(
this
.
speed
);
}
}
this
.
el
.
trigger
(
'ready'
,
arguments
);
/* The following has been commented out to make sure autoplay is
/* The following has been commented out to make sure autoplay is
disabled for students.
disabled for students.
if (
if (
!
onTouchBasedDevice()
&&
!
this.isTouch
&&
$('.video:first').data('autoplay') === 'True'
$('.video:first').data('autoplay') === 'True'
) {
) {
this.videoPlayer.play();
this.videoPlayer.play();
...
@@ -735,6 +770,7 @@ function (HTML5Video, Resizer) {
...
@@ -735,6 +770,7 @@ function (HTML5Video, Resizer) {
function
onVolumeChange
(
volume
)
{
function
onVolumeChange
(
volume
)
{
this
.
videoPlayer
.
player
.
setVolume
(
volume
);
this
.
videoPlayer
.
player
.
setVolume
(
volume
);
this
.
el
.
trigger
(
'volumechange'
,
arguments
);
}
}
});
});
...
...
common/lib/xmodule/xmodule/js/src/video/04_video_control.js
View file @
23dc10d0
...
@@ -32,9 +32,12 @@ function () {
...
@@ -32,9 +32,12 @@ function () {
var
methodsDict
=
{
var
methodsDict
=
{
exitFullScreen
:
exitFullScreen
,
exitFullScreen
:
exitFullScreen
,
hideControls
:
hideControls
,
hideControls
:
hideControls
,
hidePlayPlaceholder
:
hidePlayPlaceholder
,
pause
:
pause
,
pause
:
pause
,
play
:
play
,
play
:
play
,
show
:
show
,
showControls
:
showControls
,
showControls
:
showControls
,
showPlayPlaceholder
:
showPlayPlaceholder
,
toggleFullScreen
:
toggleFullScreen
,
toggleFullScreen
:
toggleFullScreen
,
togglePlayback
:
togglePlayback
,
togglePlayback
:
togglePlayback
,
updateVcrVidTime
:
updateVcrVidTime
updateVcrVidTime
:
updateVcrVidTime
...
@@ -54,16 +57,16 @@ function () {
...
@@ -54,16 +57,16 @@ function () {
state
.
videoControl
.
sliderEl
=
state
.
videoControl
.
el
.
find
(
'.slider'
);
state
.
videoControl
.
sliderEl
=
state
.
videoControl
.
el
.
find
(
'.slider'
);
state
.
videoControl
.
playPauseEl
=
state
.
videoControl
.
el
.
find
(
'.video_control'
);
state
.
videoControl
.
playPauseEl
=
state
.
videoControl
.
el
.
find
(
'.video_control'
);
state
.
videoControl
.
playPlaceholder
=
state
.
el
.
find
(
'.btn-play'
);
state
.
videoControl
.
secondaryControlsEl
=
state
.
videoControl
.
el
.
find
(
'.secondary-controls'
);
state
.
videoControl
.
secondaryControlsEl
=
state
.
videoControl
.
el
.
find
(
'.secondary-controls'
);
state
.
videoControl
.
fullScreenEl
=
state
.
videoControl
.
el
.
find
(
'.add-fullscreen'
);
state
.
videoControl
.
fullScreenEl
=
state
.
videoControl
.
el
.
find
(
'.add-fullscreen'
);
state
.
videoControl
.
vidTimeEl
=
state
.
videoControl
.
el
.
find
(
'.vidtime'
);
state
.
videoControl
.
vidTimeEl
=
state
.
videoControl
.
el
.
find
(
'.vidtime'
);
state
.
videoControl
.
fullScreenState
=
false
;
state
.
videoControl
.
fullScreenState
=
false
;
state
.
videoControl
.
pause
();
if
(
!
onTouchBasedDevice
())
{
if
(
state
.
isTouch
&&
state
.
videoType
===
'html5'
)
{
state
.
videoControl
.
pause
();
state
.
videoControl
.
showPlayPlaceholder
();
}
else
{
state
.
videoControl
.
play
();
}
}
if
((
state
.
videoType
===
'html5'
)
&&
(
state
.
config
.
autohideHtml5
))
{
if
((
state
.
videoType
===
'html5'
)
&&
(
state
.
config
.
autohideHtml5
))
{
...
@@ -99,6 +102,13 @@ function () {
...
@@ -99,6 +102,13 @@ function () {
state
.
videoControl
.
playPauseEl
.
on
(
'blur'
,
function
()
{
state
.
videoControl
.
playPauseEl
.
on
(
'blur'
,
function
()
{
state
.
previousFocus
=
'playPause'
;
state
.
previousFocus
=
'playPause'
;
});
});
if
(
/iPad|Android/i
.
test
(
state
.
isTouch
[
0
]))
{
state
.
videoControl
.
playPlaceholder
.
on
(
'click'
,
function
()
{
state
.
trigger
(
'videoPlayer.play'
,
null
);
});
}
}
}
// ***************************************************************
// ***************************************************************
...
@@ -106,6 +116,11 @@ function () {
...
@@ -106,6 +116,11 @@ function () {
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
// These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
// The magic private function that makes them available and sets up their context is makeFunctionsPublic().
// The magic private function that makes them available and sets up their context is makeFunctionsPublic().
// ***************************************************************
// ***************************************************************
function
show
()
{
this
.
videoControl
.
el
.
removeClass
(
'is-hidden'
);
this
.
el
.
trigger
(
'controls:show'
,
arguments
);
}
function
showControls
(
event
)
{
function
showControls
(
event
)
{
if
(
!
this
.
controlShowLock
)
{
if
(
!
this
.
controlShowLock
)
{
if
(
!
this
.
captionsHidden
)
{
if
(
!
this
.
captionsHidden
)
{
...
@@ -157,14 +172,46 @@ function () {
...
@@ -157,14 +172,46 @@ function () {
});
});
}
}
function
showPlayPlaceholder
(
event
)
{
this
.
videoControl
.
playPlaceholder
.
removeClass
(
'is-hidden'
)
.
attr
({
'aria-hidden'
:
'false'
,
'tabindex'
:
0
});
}
function
hidePlayPlaceholder
(
event
)
{
this
.
videoControl
.
playPlaceholder
.
addClass
(
'is-hidden'
)
.
attr
({
'aria-hidden'
:
'true'
,
'tabindex'
:
-
1
});
}
function
play
()
{
function
play
()
{
this
.
videoControl
.
playPauseEl
.
removeClass
(
'play'
).
addClass
(
'pause'
).
attr
(
'title'
,
gettext
(
'Pause'
));
this
.
videoControl
.
isPlaying
=
true
;
this
.
videoControl
.
isPlaying
=
true
;
this
.
videoControl
.
playPauseEl
.
removeClass
(
'play'
)
.
addClass
(
'pause'
)
.
attr
(
'title'
,
gettext
(
'Pause'
));
if
(
/iPad|Android/i
.
test
(
this
.
isTouch
[
0
])
&&
this
.
videoType
===
'html5'
)
{
this
.
videoControl
.
hidePlayPlaceholder
();
}
}
}
function
pause
()
{
function
pause
()
{
this
.
videoControl
.
playPauseEl
.
removeClass
(
'pause'
).
addClass
(
'play'
).
attr
(
'title'
,
gettext
(
'Play'
));
this
.
videoControl
.
isPlaying
=
false
;
this
.
videoControl
.
isPlaying
=
false
;
this
.
videoControl
.
playPauseEl
.
removeClass
(
'pause'
)
.
addClass
(
'play'
)
.
attr
(
'title'
,
gettext
(
'Play'
));
if
(
/iPad|Android/i
.
test
(
this
.
isTouch
[
0
])
&&
this
.
videoType
===
'html5'
)
{
this
.
videoControl
.
showPlayPlaceholder
();
}
}
}
function
togglePlayback
(
event
)
{
function
togglePlayback
(
event
)
{
...
...
common/lib/xmodule/xmodule/js/src/video/05_video_quality_control.js
View file @
23dc10d0
...
@@ -12,6 +12,7 @@ function () {
...
@@ -12,6 +12,7 @@ function () {
// Changing quality for now only works for YouTube videos.
// Changing quality for now only works for YouTube videos.
if
(
state
.
videoType
!==
'youtube'
)
{
if
(
state
.
videoType
!==
'youtube'
)
{
state
.
el
.
find
(
'a.quality_control'
).
remove
();
return
;
return
;
}
}
...
...
common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
View file @
23dc10d0
...
@@ -55,12 +55,10 @@ function () {
...
@@ -55,12 +55,10 @@ function () {
// via the 'state' object. Much easier to work this way - you don't
// via the 'state' object. Much easier to work this way - you don't
// have to do repeated jQuery element selects.
// have to do repeated jQuery element selects.
function
_renderElements
(
state
)
{
function
_renderElements
(
state
)
{
if
(
!
onTouchBasedDevice
())
{
state
.
videoProgressSlider
.
el
=
state
.
videoControl
.
sliderEl
;
state
.
videoProgressSlider
.
el
=
state
.
videoControl
.
sliderEl
;
buildSlider
(
state
);
buildSlider
(
state
);
_buildHandle
(
state
);
_buildHandle
(
state
);
}
}
}
function
_buildHandle
(
state
)
{
function
_buildHandle
(
state
)
{
...
...
common/lib/xmodule/xmodule/js/src/video/07_video_volume_control.js
View file @
23dc10d0
...
@@ -10,6 +10,13 @@ function () {
...
@@ -10,6 +10,13 @@ function () {
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
var
dfd
=
$
.
Deferred
();
if
(
state
.
isTouch
)
{
// iOS doesn't support volume change
state
.
el
.
find
(
'div.volume'
).
remove
();
dfd
.
resolve
();
return
dfd
.
promise
();
}
state
.
videoVolumeControl
=
{};
state
.
videoVolumeControl
=
{};
_makeFunctionsPublic
(
state
);
_makeFunctionsPublic
(
state
);
...
...
common/lib/xmodule/xmodule/js/src/video/08_video_speed_control.js
View file @
23dc10d0
...
@@ -10,6 +10,13 @@ function () {
...
@@ -10,6 +10,13 @@ function () {
return
function
(
state
)
{
return
function
(
state
)
{
var
dfd
=
$
.
Deferred
();
var
dfd
=
$
.
Deferred
();
if
(
state
.
isTouch
)
{
// iOS doesn't support speed change
state
.
el
.
find
(
'div.speeds'
).
remove
();
dfd
.
resolve
();
return
dfd
.
promise
();
}
state
.
videoSpeedControl
=
{};
state
.
videoSpeedControl
=
{};
_initialize
(
state
);
_initialize
(
state
);
...
@@ -131,7 +138,7 @@ function () {
...
@@ -131,7 +138,7 @@ function () {
state
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'a'
)
state
.
videoSpeedControl
.
videoSpeedsEl
.
find
(
'a'
)
.
on
(
'click'
,
state
.
videoSpeedControl
.
changeVideoSpeed
);
.
on
(
'click'
,
state
.
videoSpeedControl
.
changeVideoSpeed
);
if
(
onTouchBasedDevice
()
)
{
if
(
state
.
isTouch
)
{
state
.
videoSpeedControl
.
el
.
on
(
'click'
,
function
(
event
)
{
state
.
videoSpeedControl
.
el
.
on
(
'click'
,
function
(
event
)
{
// So that you can't highlight this control via a drag
// So that you can't highlight this control via a drag
// operation, we disable the default browser actions on a
// operation, we disable the default browser actions on a
...
...
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
View file @
23dc10d0
...
@@ -211,6 +211,8 @@ function () {
...
@@ -211,6 +211,8 @@ function () {
return
false
;
return
false
;
}
}
this
.
videoCaption
.
hideCaptions
(
this
.
hide_captions
);
// Fetch the captions file. If no file was specified, or if an error
// Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button
// occurred, then we hide the captions panel, and the "CC" button
$
.
ajaxWithPrefix
({
$
.
ajaxWithPrefix
({
...
@@ -221,7 +223,7 @@ function () {
...
@@ -221,7 +223,7 @@ function () {
_this
.
videoCaption
.
start
=
captions
.
start
;
_this
.
videoCaption
.
start
=
captions
.
start
;
_this
.
videoCaption
.
loaded
=
true
;
_this
.
videoCaption
.
loaded
=
true
;
if
(
onTouchBasedDevice
()
)
{
if
(
_this
.
isTouch
)
{
_this
.
videoCaption
.
subtitlesEl
.
find
(
'li'
).
html
(
_this
.
videoCaption
.
subtitlesEl
.
find
(
'li'
).
html
(
gettext
(
gettext
(
'Caption will be displayed when '
+
'Caption will be displayed when '
+
...
@@ -231,6 +233,8 @@ function () {
...
@@ -231,6 +233,8 @@ function () {
}
else
{
}
else
{
_this
.
videoCaption
.
renderCaption
();
_this
.
videoCaption
.
renderCaption
();
}
}
_this
.
videoCaption
.
bindHandlers
();
},
},
error
:
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
error
:
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
console
.
log
(
'[Video info]: ERROR while fetching captions.'
);
console
.
log
(
'[Video info]: ERROR while fetching captions.'
);
...
@@ -349,7 +353,8 @@ function () {
...
@@ -349,7 +353,8 @@ function () {
function
renderCaption
()
{
function
renderCaption
()
{
var
container
=
$
(
'<ol>'
),
var
container
=
$
(
'<ol>'
),
_this
=
this
;
_this
=
this
,
autohideHtml5
=
this
.
config
.
autohideHtml5
;
this
.
elVideoWrapper
.
after
(
this
.
videoCaption
.
subtitlesEl
);
this
.
elVideoWrapper
.
after
(
this
.
videoCaption
.
subtitlesEl
);
this
.
el
.
find
(
'.video-controls .secondary-controls'
)
this
.
el
.
find
(
'.video-controls .secondary-controls'
)
...
@@ -357,28 +362,11 @@ function () {
...
@@ -357,28 +362,11 @@ function () {
this
.
videoCaption
.
setSubtitlesHeight
();
this
.
videoCaption
.
setSubtitlesHeight
();
if
((
this
.
videoType
===
'html5'
)
&&
(
this
.
config
.
autohideHtml5
))
{
if
((
this
.
videoType
===
'html5'
&&
autohideHtml5
)
||
!
autohideHtml5
)
{
this
.
videoCaption
.
fadeOutTimeout
=
this
.
config
.
fadeOutTimeout
;
this
.
videoCaption
.
subtitlesEl
.
addClass
(
'html5'
);
this
.
captionHideTimeout
=
setTimeout
(
this
.
videoCaption
.
autoHideCaptions
,
this
.
videoCaption
.
fadeOutTimeout
);
}
else
if
(
!
this
.
config
.
autohideHtml5
)
{
this
.
videoCaption
.
fadeOutTimeout
=
this
.
config
.
fadeOutTimeout
;
this
.
videoCaption
.
fadeOutTimeout
=
this
.
config
.
fadeOutTimeout
;
this
.
videoCaption
.
subtitlesEl
.
addClass
(
'html5'
);
this
.
videoCaption
.
subtitlesEl
.
addClass
(
'html5'
);
this
.
captionHideTimeout
=
setTimeout
(
this
.
videoCaption
.
autoHideCaptions
,
0
);
}
}
this
.
videoCaption
.
hideCaptions
(
this
.
hide_captions
);
this
.
videoCaption
.
bindHandlers
();
$
.
each
(
this
.
videoCaption
.
captions
,
function
(
index
,
text
)
{
$
.
each
(
this
.
videoCaption
.
captions
,
function
(
index
,
text
)
{
var
liEl
=
$
(
'<li>'
);
var
liEl
=
$
(
'<li>'
);
...
...
lms/static/coffee/src/main.coffee
View file @
23dc10d0
...
@@ -6,7 +6,7 @@ $ ->
...
@@ -6,7 +6,7 @@ $ ->
dataType
:
'json'
dataType
:
'json'
window
.
onTouchBasedDevice
=
->
window
.
onTouchBasedDevice
=
->
navigator
.
userAgent
.
match
/iPhone|iPod|iPad/i
navigator
.
userAgent
.
match
/iPhone|iPod|iPad
|Android
/i
$
(
'body'
).
addClass
'touch-based-device'
if
onTouchBasedDevice
()
$
(
'body'
).
addClass
'touch-based-device'
if
onTouchBasedDevice
()
...
...
lms/templates/video.html
View file @
23dc10d0
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
<div
<div
id=
"video_${id}"
id=
"video_${id}"
class=
"video"
class=
"video
closed
"
data-streams=
"${youtube_streams}"
data-streams=
"${youtube_streams}"
...
@@ -48,13 +48,14 @@
...
@@ -48,13 +48,14 @@
<article
class=
"video-wrapper"
>
<article
class=
"video-wrapper"
>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"0"
class=
"spinner"
aria-hidden=
"false"
aria-label=
"${_('Loading video player')}"
></span>
<span
tabindex=
"-1"
class=
"btn-play is-hidden"
aria-hidden=
"true"
aria-label=
"${_('Play video')}"
></span>
<div
class=
"video-player-pre"
></div>
<div
class=
"video-player-pre"
></div>
<section
class=
"video-player"
>
<section
class=
"video-player"
>
<div
id=
"${id}"
></div>
<div
id=
"${id}"
></div>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
<h3
class=
"hidden"
>
${_('ERROR: No playable video sources found!')}
</h3>
</section>
</section>
<div
class=
"video-player-post"
></div>
<div
class=
"video-player-post"
></div>
<section
class=
"video-controls"
>
<section
class=
"video-controls
is-hidden
"
>
<div
class=
"slider"
title=
"Video position"
></div>
<div
class=
"slider"
title=
"Video position"
></div>
<div>
<div>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment