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
1f61f6ce
Commit
1f61f6ce
authored
Feb 14, 2013
by
Carlos Andrés Rocha
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1409 from MITx/feature/valera/video_alpha
Feature/valera/video alpha
parents
5134c99d
237e6ff6
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
1817 additions
and
13 deletions
+1817
-13
common/lib/xmodule/setup.py
+1
-0
common/lib/xmodule/xmodule/css/videoalpha/display.scss
+559
-0
common/lib/xmodule/xmodule/js/src/.gitignore
+1
-2
common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
+103
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/_subview.coffee
+14
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
+285
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_caption.coffee
+152
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_control.coffee
+35
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
+283
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_progress_slider.coffee
+49
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_quality_control.coffee
+27
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_speed_control.coffee
+53
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_volume_control.coffee
+40
-0
common/lib/xmodule/xmodule/templates/videoalpha/default.yaml
+7
-0
common/lib/xmodule/xmodule/video_module.py
+2
-3
common/lib/xmodule/xmodule/videoalpha_module.py
+155
-0
lms/templates/courseware/courseware.html
+8
-8
lms/templates/videoalpha.html
+43
-0
No files found.
common/lib/xmodule/setup.py
View file @
1f61f6ce
...
@@ -37,6 +37,7 @@ setup(
...
@@ -37,6 +37,7 @@ setup(
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor"
,
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor"
,
"vertical = xmodule.vertical_module:VerticalDescriptor"
,
"vertical = xmodule.vertical_module:VerticalDescriptor"
,
"video = xmodule.video_module:VideoDescriptor"
,
"video = xmodule.video_module:VideoDescriptor"
,
"videoalpha = xmodule.videoalpha_module:VideoAlphaDescriptor"
,
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor"
,
"videosequence = xmodule.seq_module:SequenceDescriptor"
,
"videosequence = xmodule.seq_module:SequenceDescriptor"
,
"discussion = xmodule.discussion_module:DiscussionDescriptor"
,
"discussion = xmodule.discussion_module:DiscussionDescriptor"
,
...
...
common/lib/xmodule/xmodule/css/videoalpha/display.scss
0 → 100644
View file @
1f61f6ce
&
{
margin-bottom
:
30px
;
}
div
.video
{
@include
clearfix
();
background
:
#f3f3f3
;
display
:
block
;
margin
:
0
-12px
;
padding
:
12px
;
border-radius
:
5px
;
article
.video-wrapper
{
float
:
left
;
margin-right
:
flex-gutter
(
9
);
width
:
flex-grid
(
6
,
9
);
section
.video-player
{
height
:
0
;
overflow
:
hidden
;
padding-bottom
:
56
.25%
;
position
:
relative
;
object
,
iframe
{
border
:
none
;
height
:
100%
;
left
:
0
;
position
:
absolute
;
top
:
0
;
width
:
100%
;
}
}
section
.video-controls
{
@include
clearfix
();
background
:
#333
;
border
:
1px
solid
#000
;
border-top
:
0
;
color
:
#ccc
;
position
:
relative
;
&
:hover
{
ul
,
div
{
opacity
:
1
;
}
}
div
.slider
{
@include
clearfix
();
background
:
#c2c2c2
;
border
:
1px
solid
#000
;
@include
border-radius
(
0
);
border-top
:
1px
solid
#000
;
@include
box-shadow
(
inset
0
1px
0
#eee
,
0
1px
0
#555
);
height
:
7px
;
margin-left
:
-1px
;
margin-right
:
-1px
;
@include
transition
(
height
2
.0s
ease-in-out
);
div
.ui-widget-header
{
background
:
#777
;
@include
box-shadow
(
inset
0
1px
0
#999
);
}
a
.ui-slider-handle
{
background
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
@include
background-size
(
50%
);
border
:
1px
solid
darken
(
$pink
,
20%
);
@include
border-radius
(
15px
);
@include
box-shadow
(
inset
0
1px
0
lighten
(
$pink
,
10%
));
cursor
:
pointer
;
height
:
15px
;
margin-left
:
-7px
;
top
:
-4px
;
@include
transition
(
height
2
.0s
ease-in-out
,
width
2
.0s
ease-in-out
);
width
:
15px
;
&
:focus
,
&
:hover
{
background-color
:
lighten
(
$pink
,
10%
);
outline
:
none
;
}
}
}
ul
.vcr
{
@extend
.dullify
;
float
:
left
;
list-style
:
none
;
margin
:
0
lh
()
0
0
;
padding
:
0
;
li
{
float
:
left
;
margin-bottom
:
0
;
a
{
border-bottom
:
none
;
border-right
:
1px
solid
#000
;
@include
box-shadow
(
1px
0
0
#555
);
cursor
:
pointer
;
display
:
block
;
line-height
:
46px
;
padding
:
0
lh
(
.75
);
text-indent
:
-9999px
;
@include
transition
(
background-color
,
opacity
);
width
:
14px
;
background
:
url('../images/vcr.png')
15px
15px
no-repeat
;
outline
:
0
;
&
:focus
{
outline
:
0
;
}
&
:empty
{
height
:
46px
;
background
:
url('../images/vcr.png')
15px
15px
no-repeat
;
}
&
.play
{
background-position
:
17px
-114px
;
&
:hover
{
background-color
:
#444
;
}
}
&
.pause
{
background-position
:
16px
-50px
;
&
:hover
{
background-color
:
#444
;
}
}
}
div
.vidtime
{
padding-left
:
lh
(
.75
);
font-weight
:
bold
;
line-height
:
46px
;
//height of play pause buttons
padding-left
:
lh
(
.75
);
-webkit-font-smoothing
:
antialiased
;
}
}
}
div
.secondary-controls
{
@extend
.dullify
;
float
:
right
;
div
.speeds
{
float
:
left
;
position
:
relative
;
&
.open
{
&
>
a
{
background
:
url('../images/open-arrow.png')
10px
center
no-repeat
;
}
ol
.video_speeds
{
display
:
block
;
opacity
:
1
;
padding
:
0
;
margin
:
0
;
list-style
:
none
;
}
}
&
>
a
{
background
:
url('../images/closed-arrow.png')
10px
center
no-repeat
;
border-left
:
1px
solid
#000
;
border-right
:
1px
solid
#000
;
@include
box-shadow
(
1px
0
0
#555
,
inset
1px
0
0
#555
);
@include
clearfix
();
color
:
#fff
;
cursor
:
pointer
;
display
:
block
;
line-height
:
46px
;
//height of play pause buttons
margin-right
:
0
;
padding-left
:
15px
;
position
:
relative
;
@include
transition
();
-webkit-font-smoothing
:
antialiased
;
width
:
116px
;
outline
:
0
;
&
:focus
{
outline
:
0
;
}
h3
{
color
:
#999
;
float
:
left
;
font-size
:
em
(
14
);
font-weight
:
normal
;
letter-spacing
:
1px
;
padding
:
0
lh
(
.25
)
0
lh
(
.5
);
line-height
:
46px
;
text-transform
:
uppercase
;
}
p
.active
{
float
:
left
;
font-weight
:
bold
;
margin-bottom
:
0
;
padding
:
0
lh
(
.5
)
0
0
;
line-height
:
46px
;
color
:
#fff
;
}
&
:hover
,
&
:active
,
&
:focus
{
opacity
:
1
;
background-color
:
#444
;
}
}
// fix for now
ol
.video_speeds
{
@include
box-shadow
(
inset
1px
0
0
#555
,
0
3px
0
#444
);
@include
transition
();
background-color
:
#444
;
border
:
1px
solid
#000
;
bottom
:
46px
;
display
:
none
;
opacity
:
0
;
position
:
absolute
;
width
:
133px
;
z-index
:
10
;
li
{
@include
box-shadow
(
0
1px
0
#555
);
border-bottom
:
1px
solid
#000
;
color
:
#fff
;
cursor
:
pointer
;
a
{
border
:
0
;
color
:
#fff
;
display
:
block
;
padding
:
lh
(
.5
);
&
:hover
{
background-color
:
#666
;
color
:
#aaa
;
}
}
&
.active
{
font-weight
:
bold
;
}
&
:last-child
{
@include
box-shadow
(
none
);
border-bottom
:
0
;
margin-top
:
0
;
}
}
}
}
div
.volume
{
float
:
left
;
position
:
relative
;
&
.open
{
.volume-slider-container
{
display
:
block
;
opacity
:
1
;
}
}
&
.muted
{
&
>
a
{
background
:
url('../images/mute.png')
10px
center
no-repeat
;
}
}
>
a
{
background
:
url('../images/volume.png')
10px
center
no-repeat
;
border-right
:
1px
solid
#000
;
@include
box-shadow
(
1px
0
0
#555
,
inset
1px
0
0
#555
);
@include
clearfix
();
color
:
#fff
;
cursor
:
pointer
;
display
:
block
;
height
:
46px
;
margin-right
:
0
;
padding-left
:
15px
;
position
:
relative
;
@include
transition
();
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
&
:hover
,
&
:active
,
&
:focus
{
background-color
:
#444
;
}
}
.volume-slider-container
{
@include
box-shadow
(
inset
1px
0
0
#555
,
0
3px
0
#444
);
@include
transition
();
background-color
:
#444
;
border
:
1px
solid
#000
;
bottom
:
46px
;
display
:
none
;
opacity
:
0
;
position
:
absolute
;
width
:
45px
;
height
:
125px
;
margin-left
:
-1px
;
z-index
:
10
;
.volume-slider
{
height
:
100px
;
border
:
0
;
width
:
5px
;
margin
:
14px
auto
;
background
:
#666
;
border
:
1px
solid
#000
;
@include
box-shadow
(
0
1px
0
#333
);
a
.ui-slider-handle
{
background
:
$pink
url(../images/slider-handle.png)
center
center
no-repeat
;
@include
background-size
(
50%
);
border
:
1px
solid
darken
(
$pink
,
20%
);
@include
border-radius
(
15px
);
@include
box-shadow
(
inset
0
1px
0
lighten
(
$pink
,
10%
));
cursor
:
pointer
;
height
:
15px
;
left
:
-6px
;
@include
transition
(
height
2
.0s
ease-in-out
,
width
2
.0s
ease-in-out
);
width
:
15px
;
}
.ui-slider-range
{
background
:
#ddd
;
}
}
}
}
a
.add-fullscreen
{
background
:
url(../images/fullscreen.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
@include
box-shadow
(
1px
0
0
#555
,
inset
1px
0
0
#555
);
color
:
#797979
;
display
:
block
;
float
:
left
;
line-height
:
46px
;
//height of play pause buttons
margin-left
:
0
;
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
@include
transition
();
width
:
30px
;
&
:hover
{
background-color
:
#444
;
color
:
#fff
;
text-decoration
:
none
;
}
}
a
.quality_control
{
background
:
url(../images/hd.png)
center
no-repeat
;
border-right
:
1px
solid
#000
;
@include
box-shadow
(
1px
0
0
#555
,
inset
1px
0
0
#555
);
color
:
#797979
;
display
:
block
;
float
:
left
;
line-height
:
46px
;
//height of play pause buttons
margin-left
:
0
;
padding
:
0
lh
(
.5
);
text-indent
:
-9999px
;
@include
transition
();
width
:
30px
;
&
:hover
{
background-color
:
#444
;
color
:
#fff
;
text-decoration
:
none
;
}
&
.active
{
background-color
:
#F44
;
color
:
#0ff
;
text-decoration
:
none
;
}
}
a
.hide-subtitles
{
background
:
url('../images/cc.png')
center
no-repeat
;
color
:
#797979
;
display
:
block
;
float
:
left
;
font-weight
:
800
;
line-height
:
46px
;
//height of play pause buttons
margin-left
:
0
;
opacity
:
1
;
padding
:
0
lh
(
.5
);
position
:
relative
;
text-indent
:
-9999px
;
@include
transition
();
-webkit-font-smoothing
:
antialiased
;
width
:
30px
;
&
:hover
{
background-color
:
#444
;
color
:
#fff
;
text-decoration
:
none
;
}
&
.off
{
opacity
:
.7
;
}
}
}
}
&
:hover
section
.video-controls
{
ul
,
div
{
opacity
:
1
;
}
div
.slider
{
height
:
14px
;
margin-top
:
-7px
;
a
.ui-slider-handle
{
@include
border-radius
(
20px
);
height
:
20px
;
margin-left
:
-10px
;
top
:
-4px
;
width
:
20px
;
}
}
}
}
ol
.subtitles
{
padding-left
:
0
;
float
:
left
;
max-height
:
460px
;
overflow
:
auto
;
width
:
flex-grid
(
3
,
9
);
margin
:
0
;
font-size
:
14px
;
list-style
:
none
;
li
{
border
:
0
;
color
:
#666
;
cursor
:
pointer
;
margin-bottom
:
8px
;
padding
:
0
;
line-height
:
lh
();
&
.current
{
color
:
#333
;
font-weight
:
700
;
}
&
:hover
{
color
:
$blue
;
}
&
:empty
{
margin-bottom
:
0px
;
}
}
}
&
.closed
{
@extend
.trans
;
article
.video-wrapper
{
width
:
flex-grid
(
9
,
9
);
}
ol
.subtitles
{
width
:
0
;
height
:
0
;
}
}
&
.fullscreen
{
background
:
rgba
(
#000
,
.95
);
border
:
0
;
bottom
:
0
;
height
:
100%
;
left
:
0
;
margin
:
0
;
overflow
:
hidden
;
padding
:
0
;
position
:
fixed
;
top
:
0
;
width
:
100%
;
z-index
:
999
;
vertical-align
:
middle
;
&
.closed
{
ol
.subtitles
{
right
:
-
(
flex-grid
(
4
));
width
:
auto
;
}
}
div
.tc-wrapper
{
@include
clearfix
;
display
:
table
;
width
:
100%
;
height
:
100%
;
article
.video-wrapper
{
width
:
100%
;
display
:
table-cell
;
vertical-align
:
middle
;
float
:
none
;
}
object
,
iframe
{
bottom
:
0
;
height
:
100%
;
left
:
0
;
overflow
:
hidden
;
position
:
fixed
;
top
:
0
;
}
section
.video-controls
{
bottom
:
0
;
left
:
0
;
position
:
absolute
;
width
:
100%
;
z-index
:
9999
;
}
}
ol
.subtitles
{
background
:
rgba
(
#000
,
.8
);
bottom
:
0
;
height
:
100%
;
max-height
:
100%
;
max-width
:
flex-grid
(
3
);
padding
:
lh
();
position
:
fixed
;
right
:
0
;
top
:
0
;
@include
transition
();
li
{
color
:
#aaa
;
&
.current
{
color
:
#fff
;
}
}
}
}
}
common/lib/xmodule/xmodule/js/src/.gitignore
View file @
1f61f6ce
*.js
# Please do not ignore *.js files. Some xmodules are written in JS.
common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoAlpha
constructor
:
(
element
)
->
@
el
=
$
(
element
).
find
(
'.video'
)
@
id
=
@
el
.
attr
(
'id'
).
replace
(
/video_/
,
''
)
@
start
=
@
el
.
data
(
'start'
)
@
end
=
@
el
.
data
(
'end'
)
@
caption_data_dir
=
@
el
.
data
(
'caption-data-dir'
)
@
caption_asset_path
=
@
el
.
data
(
'caption-asset-path'
)
@
show_captions
=
@
el
.
data
(
'show-captions'
).
toString
()
==
"true"
@
el
=
$
(
"#video_
#{
@
id
}
"
)
if
@
parseYoutubeId
(
@
el
.
data
(
"streams"
))
is
true
@
videoType
=
"youtube"
@
fetchMetadata
()
@
parseSpeed
()
else
@
videoType
=
"html5"
@
parseHtml5Sources
@
el
.
data
(
'mp4-source'
),
@
el
.
data
(
'webm-source'
),
@
el
.
data
(
'ogg-source'
)
@
speeds
=
[
'0.75'
,
'1.0'
,
'1.25'
,
'1.50'
]
sub
=
@
el
.
data
(
'sub'
)
if
(
typeof
sub
isnt
"string"
)
or
(
sub
.
length
is
0
)
sub
=
""
@
show_captions
=
false
@
videos
=
"0.75"
:
sub
"1.0"
:
sub
"1.25"
:
sub
"1.5"
:
sub
@
setSpeed
$
.
cookie
(
'video_speed'
)
$
(
"#video_
#{
@
id
}
"
).
data
(
'video'
,
this
).
addClass
(
'video-load-complete'
)
if
@
show_captions
is
true
@
hide_captions
=
$
.
cookie
(
'hide_captions'
)
==
'true'
else
@
hide_captions
=
true
$
.
cookie
(
'hide_captions'
,
@
hide_captions
,
expires
:
3650
,
path
:
'/'
)
@
el
.
addClass
'closed'
if
((
@
videoType
is
"youtube"
)
and
(
YT
.
Player
))
or
((
@
videoType
is
"html5"
)
and
(
HTML5Video
.
Player
))
@
embed
()
else
if
@
videoType
is
"youtube"
window
.
onYouTubePlayerAPIReady
=
=>
@
embed
()
else
if
@
videoType
is
"html5"
window
.
onHTML5PlayerAPIReady
=
=>
@
embed
()
youtubeId
:
(
speed
)
->
@
videos
[
speed
||
@
speed
]
parseYoutubeId
:
(
videos
)
->
return
false
if
(
typeof
videos
isnt
"string"
)
or
(
videos
.
length
is
0
)
@
videos
=
{}
$
.
each
videos
.
split
(
/,/
),
(
index
,
video
)
=>
speed
=
undefined
video
=
video
.
split
(
/:/
)
speed
=
parseFloat
(
video
[
0
]).
toFixed
(
2
).
replace
(
/\.00$/
,
".0"
)
@
videos
[
speed
]
=
video
[
1
]
true
parseHtml5Sources
:
(
mp4Source
,
webmSource
,
oggSource
)
->
@
html5Sources
=
mp4
:
null
webm
:
null
ogg
:
null
@
html5Sources
.
mp4
=
mp4Source
if
(
typeof
mp4Source
is
"string"
)
and
(
mp4Source
.
length
>
0
)
@
html5Sources
.
webm
=
webmSource
if
(
typeof
webmSource
is
"string"
)
and
(
webmSource
.
length
>
0
)
@
html5Sources
.
ogg
=
oggSource
if
(
typeof
oggSource
is
"string"
)
and
(
oggSource
.
length
>
0
)
parseSpeed
:
->
@
speeds
=
(
$
.
map
@
videos
,
(
url
,
speed
)
->
speed
).
sort
()
@
setSpeed
$
.
cookie
(
'video_speed'
)
setSpeed
:
(
newSpeed
,
updateCookie
)
->
if
@
speeds
.
indexOf
(
newSpeed
)
isnt
-
1
@
speed
=
newSpeed
if
updateCookie
isnt
false
$
.
cookie
"video_speed"
,
""
+
newSpeed
,
expires
:
3650
path
:
"/"
else
@
speed
=
"1.0"
embed
:
->
@
player
=
new
VideoPlayerAlpha
video
:
this
fetchMetadata
:
(
url
)
->
@
metadata
=
{}
$
.
each
@
videos
,
(
speed
,
url
)
=>
$
.
get
"https://gdata.youtube.com/feeds/api/videos/
#{
url
}
?v=2&alt=jsonc"
,
((
data
)
=>
@
metadata
[
data
.
data
.
id
]
=
data
.
data
)
,
'jsonp'
getDuration
:
->
@
metadata
[
@
youtubeId
()].
duration
log
:
(
eventName
)
->
logInfo
=
id
:
@
id
code
:
@
youtubeId
()
currentTime
:
@
player
.
currentTime
speed
:
@
speed
if
@
videoType
is
"youtube"
logInfo
.
code
=
@
youtubeId
()
else
logInfo
.
code
=
"html5"
if
@
videoType
is
"html5"
Logger
.
log
eventName
,
logInfo
common/lib/xmodule/xmodule/js/src/videoalpha/display/_subview.coffee
0 → 100644
View file @
1f61f6ce
class
@
SubviewAlpha
constructor
:
(
options
)
->
$
.
each
options
,
(
key
,
value
)
=>
@
[
key
]
=
value
@
initialize
()
@
render
()
@
bind
()
$
:
(
selector
)
->
$
(
selector
,
@
el
)
initialize
:
->
render
:
->
bind
:
->
common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
0 → 100644
View file @
1f61f6ce
this
.
HTML5Video
=
(
function
()
{
var
HTML5Video
;
HTML5Video
=
{};
HTML5Video
.
Player
=
(
function
()
{
Player
.
prototype
.
callStateChangeCallback
=
function
()
{
if
(
$
.
isFunction
(
this
.
config
.
events
.
onStateChange
)
===
true
)
{
this
.
config
.
events
.
onStateChange
({
'data'
:
this
.
playerState
});
}
};
Player
.
prototype
.
pauseVideo
=
function
()
{
this
.
video
.
pause
();
};
Player
.
prototype
.
seekTo
=
function
(
value
)
{
if
((
typeof
value
===
'number'
)
&&
(
value
<=
this
.
video
.
duration
)
&&
(
value
>=
0
))
{
this
.
start
=
0
;
this
.
end
=
this
.
video
.
duration
;
this
.
video
.
currentTime
=
value
;
}
};
Player
.
prototype
.
setVolume
=
function
(
value
)
{
if
((
typeof
value
===
'number'
)
&&
(
value
<=
100
)
&&
(
value
>=
0
))
{
this
.
video
.
volume
=
value
*
0.01
;
}
};
Player
.
prototype
.
getCurrentTime
=
function
()
{
return
this
.
video
.
currentTime
;
};
Player
.
prototype
.
playVideo
=
function
()
{
this
.
video
.
play
();
};
Player
.
prototype
.
getPlayerState
=
function
()
{
return
this
.
playerState
;
};
Player
.
prototype
.
getVolume
=
function
()
{
return
this
.
video
.
volume
;
};
Player
.
prototype
.
getDuration
=
function
()
{
if
(
isFinite
(
this
.
video
.
duration
)
===
false
)
{
return
0
;
}
return
this
.
video
.
duration
;
};
Player
.
prototype
.
setPlaybackRate
=
function
(
value
)
{
var
newSpeed
;
newSpeed
=
parseFloat
(
value
);
if
(
isFinite
(
newSpeed
)
===
true
)
{
this
.
video
.
playbackRate
=
value
;
}
};
Player
.
prototype
.
getAvailablePlaybackRates
=
function
()
{
return
[
0.75
,
1.0
,
1.25
,
1.5
];
};
return
Player
;
/*
* Constructor function for HTML5 Video player.
*
* @el - A DOM element where the HTML5 player will be inserted (as returned by jQuery(selector) function),
* or a selector string which will be used to select an element. This is a required parameter.
*
* @config - An object whose properties will be used as configuration options for the HTML5 video
* player. This is an optional parameter. In the case if this parameter is missing, or some of the config
* object's properties are missing, defaults will be used. The available options (and their defaults) are as
* follows:
*
* config = {
*
* 'videoSources': {}, // An object with properties being video sources. The property name is the
* // video format of the source. Supported video formats are: 'mp4', 'webm', and
* // 'ogg'.
*
* 'playerVars': { // Object's properties identify player parameters.
* 'start': 0, // Possible values: positive integer. Position from which to start playing the
* // video. Measured in seconds. If value is non-numeric, or 'start' property is
* // not specified, the video will start playing from the beginning.
*
* 'end': null // Possible values: positive integer. Position when to stop playing the
* // video. Measured in seconds. If value is null, or 'end' property is not
* // specified, the video will end playing at the end.
*
* },
*
* 'events': { // Object's properties identify the events that the API fires, and the
* // functions (event listeners) that the API will call when those events occur.
* // If value is null, or property is not specified, then no callback will be
* // called for that event.
*
* 'onReady': null,
* 'onStateChange': null
* }
* }
*/
function
Player
(
el
,
config
)
{
var
sourceStr
,
_this
;
// If el is string, we assume it is an ID of a DOM element. Get the element, and check that the ID
// really belongs to an element. If we didn't get a DOM element, return. At this stage, nothing will
// break because other parts of the video player are waiting for 'onReady' callback to be called.
if
(
typeof
el
===
'string'
)
{
this
.
el
=
$
(
el
);
if
(
this
.
el
.
length
===
0
)
{
return
;
}
}
else
if
(
el
instanceof
jQuery
)
{
this
.
el
=
el
;
}
else
{
return
;
}
// A simple test to see that the 'config' is a normal object.
if
(
$
.
isPlainObject
(
config
)
===
true
)
{
this
.
config
=
config
;
}
else
{
return
;
}
// We should have at least one video source. Otherwise there is no point to continue.
if
(
config
.
hasOwnProperty
(
'videoSources'
)
===
false
)
{
return
;
}
// From the start, all sources are empty. We will populate this object below.
sourceStr
=
{
'mp4'
:
' '
,
'webm'
:
' '
,
'ogg'
:
' '
};
// Will be used in inner functions to point to the current object.
_this
=
this
;
// Create HTML markup for individual sources of the HTML5 <video> element.
$
.
each
(
sourceStr
,
function
(
videoType
,
videoSource
)
{
if
(
(
_this
.
config
.
videoSources
.
hasOwnProperty
(
videoType
)
===
true
)
&&
(
typeof
_this
.
config
.
videoSources
[
videoType
]
===
'string'
)
&&
(
_this
.
config
.
videoSources
[
videoType
].
length
>
0
)
)
{
sourceStr
[
videoType
]
=
'<source '
+
'src="'
+
_this
.
config
.
videoSources
[
videoType
]
+
'" '
+
'type="video/'
+
videoType
+
'" '
+
'/> '
;
}
});
// We should have at least one video source. Otherwise there is no point to continue.
if
((
sourceStr
.
mp4
===
' '
)
&&
(
sourceStr
.
webm
===
' '
)
&&
(
sourceStr
.
ogg
===
' '
))
{
return
;
}
// Determine the starting and ending time for the video.
this
.
start
=
0
;
this
.
end
=
null
;
if
(
config
.
hasOwnProperty
(
'playerVars'
)
===
true
)
{
this
.
start
=
parseFloat
(
config
.
playerVars
.
start
);
if
((
isFinite
(
this
.
start
)
!==
true
)
||
(
this
.
start
<
0
))
{
this
.
start
=
0
;
}
this
.
end
=
parseFloat
(
config
.
playerVars
.
end
);
if
((
isFinite
(
this
.
end
)
!==
true
)
||
(
this
.
end
<
this
.
start
))
{
this
.
end
=
null
;
}
}
// Create HTML markup for the <video> element, populating it with sources from previous step.
this
.
videoEl
=
$
(
'<video style="width: 100%;">'
+
sourceStr
.
mp4
+
sourceStr
.
webm
+
sourceStr
.
ogg
+
'</video>'
);
// Get the DOM element (to access the HTML5 video API), and set the player state to UNSTARTED.
// The player state is used by other parts of the VideoPlayer to detrermine what the video is
// currently doing.
this
.
video
=
this
.
videoEl
[
0
];
this
.
playerState
=
HTML5Video
.
PlayerState
.
UNSTARTED
;
// this.callStateChangeCallback();
// Attach a 'click' event on the <video> element. It will cause the video to pause/play.
this
.
videoEl
.
on
(
'click'
,
function
(
event
)
{
if
(
_this
.
playerState
===
HTML5Video
.
PlayerState
.
PAUSED
)
{
_this
.
video
.
play
();
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
_this
.
callStateChangeCallback
();
}
else
if
(
_this
.
playerState
===
HTML5Video
.
PlayerState
.
PLAYING
)
{
_this
.
video
.
pause
();
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
_this
.
callStateChangeCallback
();
}
});
// When the <video> tag has been processed by the browser, and it is ready for playback,
// notify other parts of the VideoPlayer, and initially pause the video.
//
// Also, at this time we can get the real duration of the video. Update the starting end ending
// points of the video. Note that first time, the video will start playing at the specified start time,
// and end playing at the specified end time. After it was paused, or when a seek operation happeded,
// the starting time and ending time will reset to the beginning and the end of the video respectively.
this
.
video
.
addEventListener
(
'canplay'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
if
(
_this
.
start
>
_this
.
video
.
duration
)
{
_this
.
start
=
0
;
}
if
((
_this
.
end
===
null
)
||
(
_this
.
end
>
_this
.
video
.
duration
))
{
_this
.
end
=
_this
.
video
.
duration
;
}
_this
.
video
.
currentTime
=
_this
.
start
;
if
(
$
.
isFunction
(
_this
.
config
.
events
.
onReady
)
===
true
)
{
_this
.
config
.
events
.
onReady
(
null
);
}
},
false
);
// Register the 'play' event.
this
.
video
.
addEventListener
(
'play'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PLAYING
;
_this
.
callStateChangeCallback
();
},
false
);
// Register the 'pause' event.
this
.
video
.
addEventListener
(
'pause'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
PAUSED
;
_this
.
callStateChangeCallback
();
},
false
);
// Register the 'ended' event.
this
.
video
.
addEventListener
(
'ended'
,
function
()
{
_this
.
playerState
=
HTML5Video
.
PlayerState
.
ENDED
;
_this
.
callStateChangeCallback
();
},
false
);
// Register the 'timeupdate' event. This is the place where we control when the video ends.
// If an ending time was specified, then after the video plays through to this spot, pauses, we
// must make sure to update the ending time to the end of the video. This way, the user can watch
// any parts of it afterwards.
this
.
video
.
addEventListener
(
'timeupdate'
,
function
(
data
)
{
if
(
_this
.
end
<
_this
.
video
.
currentTime
)
{
// When we call video.pause(), a 'pause' event will be formed, and we will catch it
// in another handler (see above).
_this
.
video
.
pause
();
_this
.
end
=
_this
.
video
.
duration
;
}
},
false
);
// Place the <video> element on the page.
this
.
videoEl
.
appendTo
(
this
.
el
.
find
(
'.video-player div'
));
}
}());
HTML5Video
.
PlayerState
=
{
'UNSTARTED'
:
-
1
,
'ENDED'
:
0
,
'PLAYING'
:
1
,
'PAUSED'
:
2
,
'BUFFERING'
:
3
,
'CUED'
:
5
};
return
HTML5Video
;
}());
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_caption.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoCaptionAlpha
extends
SubviewAlpha
initialize
:
->
@
loaded
=
false
bind
:
->
$
(
window
).
bind
(
'resize'
,
@
resize
)
@
$
(
'.hide-subtitles'
).
click
@
toggle
@
$
(
'.subtitles'
).
mouseenter
(
@
onMouseEnter
).
mouseleave
(
@
onMouseLeave
)
.
mousemove
(
@
onMovement
).
bind
(
'mousewheel'
,
@
onMovement
)
.
bind
(
'DOMMouseScroll'
,
@
onMovement
)
captionURL
:
->
"
#{
@
captionAssetPath
}#{
@
youtubeId
}
.srt.sjson"
render
:
->
# TODO: make it so you can have a video with no captions.
#@$('.video-wrapper').after """
# <ol class="subtitles"><li>Attempting to load captions...</li></ol>
# """
@
$
(
'.video-wrapper'
).
after
"""
<ol class="subtitles"></ol>
"""
@
$
(
'.video-controls .secondary-controls'
).
append
"""
<a href="#" class="hide-subtitles" title="Turn off captions">Captions</a>
"""
#"
@
$
(
'.subtitles'
).
css
maxHeight
:
@
$
(
'.video-wrapper'
).
height
()
-
5
@
fetchCaption
()
fetchCaption
:
->
$
.
getWithPrefix
@
captionURL
(),
(
captions
)
=>
@
captions
=
captions
.
text
@
start
=
captions
.
start
@
loaded
=
true
if
onTouchBasedDevice
()
$
(
'.subtitles li'
).
html
"Caption will be displayed when you start playing the video."
else
@
renderCaption
()
renderCaption
:
->
container
=
$
(
'<ol>'
)
$
.
each
@
captions
,
(
index
,
text
)
=>
container
.
append
$
(
'<li>'
).
html
(
text
).
attr
'data-index'
:
index
'data-start'
:
@
start
[
index
]
@
$
(
'.subtitles'
).
html
(
container
.
html
())
@
$
(
'.subtitles li[data-index]'
).
click
@
seekPlayer
# prepend and append an empty <li> for cosmetic reason
@
$
(
'.subtitles'
).
prepend
(
$
(
'<li class="spacing">'
).
height
(
@
topSpacingHeight
()))
.
append
(
$
(
'<li class="spacing">'
).
height
(
@
bottomSpacingHeight
()))
@
rendered
=
true
search
:
(
time
)
->
if
@
loaded
min
=
0
max
=
@
start
.
length
-
1
while
min
<
max
index
=
Math
.
ceil
((
max
+
min
)
/
2
)
if
time
<
@
start
[
index
]
max
=
index
-
1
if
time
>=
@
start
[
index
]
min
=
index
return
min
play
:
->
if
@
loaded
@
renderCaption
()
unless
@
rendered
@
playing
=
true
pause
:
->
if
@
loaded
@
playing
=
false
updatePlayTime
:
(
time
)
->
if
@
loaded
# This 250ms offset is required to match the video speed
time
=
Math
.
round
(
Time
.
convert
(
time
,
@
currentSpeed
,
'1.0'
)
*
1000
+
250
)
newIndex
=
@
search
time
if
newIndex
!=
undefined
&&
@
currentIndex
!=
newIndex
if
@
currentIndex
@
$
(
".subtitles li.current"
).
removeClass
(
'current'
)
@
$
(
".subtitles li[data-index='
#{
newIndex
}
']"
).
addClass
(
'current'
)
@
currentIndex
=
newIndex
@
scrollCaption
()
resize
:
=>
@
$
(
'.subtitles'
).
css
maxHeight
:
@
captionHeight
()
@
$
(
'.subtitles .spacing:first'
).
height
(
@
topSpacingHeight
())
@
$
(
'.subtitles .spacing:last'
).
height
(
@
bottomSpacingHeight
())
@
scrollCaption
()
onMouseEnter
:
=>
clearTimeout
@
frozen
if
@
frozen
@
frozen
=
setTimeout
@
onMouseLeave
,
10000
onMovement
:
=>
@
onMouseEnter
()
onMouseLeave
:
=>
clearTimeout
@
frozen
if
@
frozen
@
frozen
=
null
@
scrollCaption
()
if
@
playing
scrollCaption
:
->
if
!
@
frozen
&&
@
$
(
'.subtitles .current:first'
).
length
@
$
(
'.subtitles'
).
scrollTo
@
$
(
'.subtitles .current:first'
),
offset
:
-
@
calculateOffset
(
@
$
(
'.subtitles .current:first'
))
seekPlayer
:
(
event
)
=>
event
.
preventDefault
()
time
=
Math
.
round
(
Time
.
convert
(
$
(
event
.
target
).
data
(
'start'
),
'1.0'
,
@
currentSpeed
)
/
1000
)
$
(
@
).
trigger
(
'seek'
,
time
)
calculateOffset
:
(
element
)
->
@
captionHeight
()
/
2
-
element
.
height
()
/
2
topSpacingHeight
:
->
@
calculateOffset
(
@
$
(
'.subtitles li:not(.spacing):first'
))
bottomSpacingHeight
:
->
@
calculateOffset
(
@
$
(
'.subtitles li:not(.spacing):last'
))
toggle
:
(
event
)
=>
event
.
preventDefault
()
if
@
el
.
hasClass
(
'closed'
)
# Captions are "closed" e.g. turned off
@
hideCaptions
(
false
)
else
# Captions are on
@
hideCaptions
(
true
)
hideCaptions
:
(
hide_captions
)
=>
if
hide_captions
@
$
(
'.hide-subtitles'
).
attr
(
'title'
,
'Turn on captions'
)
@
el
.
addClass
(
'closed'
)
else
@
$
(
'.hide-subtitles'
).
attr
(
'title'
,
'Turn off captions'
)
@
el
.
removeClass
(
'closed'
)
@
scrollCaption
()
$
.
cookie
(
'hide_captions'
,
hide_captions
,
expires
:
3650
,
path
:
'/'
)
captionHeight
:
->
if
@
el
.
hasClass
(
'fullscreen'
)
$
(
window
).
height
()
-
@
$
(
'.video-controls'
).
height
()
else
@
$
(
'.video-wrapper'
).
height
()
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_control.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoControlAlpha
extends
SubviewAlpha
bind
:
->
@
$
(
'.video_control'
).
click
@
togglePlayback
render
:
->
@
el
.
append
"""
<div class="slider"></div>
<div>
<ul class="vcr">
<li><a class="video_control" href="#"></a></li>
<li>
<div class="vidtime">0:00 / 0:00</div>
</li>
</ul>
<div class="secondary-controls">
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
</div>
</div>
"""
#"
unless
onTouchBasedDevice
()
@
$
(
'.video_control'
).
addClass
(
'play'
).
html
(
'Play'
)
play
:
->
@
$
(
'.video_control'
).
removeClass
(
'play'
).
addClass
(
'pause'
).
html
(
'Pause'
)
pause
:
->
@
$
(
'.video_control'
).
removeClass
(
'pause'
).
addClass
(
'play'
).
html
(
'Play'
)
togglePlayback
:
(
event
)
=>
event
.
preventDefault
()
if
@
$
(
'.video_control'
).
hasClass
(
'play'
)
$
(
@
).
trigger
(
'play'
)
else
if
@
$
(
'.video_control'
).
hasClass
(
'pause'
)
$
(
@
).
trigger
(
'pause'
)
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoPlayerAlpha
extends
SubviewAlpha
initialize
:
->
# If we switch verticals while the video is playing, then HTML content is
# removed, but JS code is still executing (setInterval() method), and there will
# arise conflicts (no HTML content, but code tries to access it). Therefore
# we must pause the player (stop setInterval() method).
if
(
window
.
OldVideoPlayerAlpha
)
and
(
window
.
OldVideoPlayerAlpha
.
onPause
)
window
.
OldVideoPlayerAlpha
.
onPause
()
window
.
OldVideoPlayerAlpha
=
this
if
@
video
.
videoType
is
'youtube'
@
PlayerState
=
YT
.
PlayerState
# Define a missing constant of Youtube API
@
PlayerState
.
UNSTARTED
=
-
1
else
if
@
video
.
videoType
is
'html5'
@
PlayerState
=
HTML5Video
.
PlayerState
@
currentTime
=
0
@
el
=
$
(
"#video_
#{
@
video
.
id
}
"
)
bind
:
->
$
(
@
control
).
bind
(
'play'
,
@
play
)
.
bind
(
'pause'
,
@
pause
)
if
@
video
.
videoType
is
'youtube'
$
(
@
qualityControl
).
bind
(
'changeQuality'
,
@
handlePlaybackQualityChange
)
if
@
video
.
show_captions
is
true
$
(
@
caption
).
bind
(
'seek'
,
@
onSeek
)
$
(
@
speedControl
).
bind
(
'speedChange'
,
@
onSpeedChange
)
$
(
@
progressSlider
).
bind
(
'seek'
,
@
onSeek
)
if
@
volumeControl
$
(
@
volumeControl
).
bind
(
'volumeChange'
,
@
onVolumeChange
)
$
(
document
).
keyup
@
bindExitFullScreen
@
$
(
'.add-fullscreen'
).
click
@
toggleFullScreen
@
addToolTip
()
unless
onTouchBasedDevice
()
bindExitFullScreen
:
(
event
)
=>
if
@
el
.
hasClass
(
'fullscreen'
)
&&
event
.
keyCode
==
27
@
toggleFullScreen
(
event
)
render
:
->
@
control
=
new
VideoControlAlpha
el
:
@
$
(
'.video-controls'
)
if
@
video
.
videoType
is
'youtube'
@
qualityControl
=
new
VideoQualityControlAlpha
el
:
@
$
(
'.secondary-controls'
)
if
@
video
.
show_captions
is
true
@
caption
=
new
VideoCaptionAlpha
el
:
@
el
youtubeId
:
@
video
.
youtubeId
(
'1.0'
)
currentSpeed
:
@
currentSpeed
()
captionAssetPath
:
@
video
.
caption_asset_path
unless
onTouchBasedDevice
()
@
volumeControl
=
new
VideoVolumeControlAlpha
el
:
@
$
(
'.secondary-controls'
)
@
speedControl
=
new
VideoSpeedControlAlpha
el
:
@
$
(
'.secondary-controls'
),
speeds
:
@
video
.
speeds
,
currentSpeed
:
@
currentSpeed
()
@
progressSlider
=
new
VideoProgressSliderAlpha
el
:
@
$
(
'.slider'
)
@
playerVars
=
controls
:
0
wmode
:
'transparent'
rel
:
0
showinfo
:
0
enablejsapi
:
1
modestbranding
:
1
html5
:
1
if
@
video
.
start
@
playerVars
.
start
=
@
video
.
start
@
playerVars
.
wmode
=
'window'
if
@
video
.
end
# work in AS3, not HMLT5. but iframe use AS3
@
playerVars
.
end
=
@
video
.
end
if
@
video
.
videoType
is
'html5'
@
player
=
new
HTML5Video
.
Player
@
video
.
el
,
playerVars
:
@
playerVars
,
videoSources
:
@
video
.
html5Sources
,
events
:
onReady
:
@
onReady
onStateChange
:
@
onStateChange
else
if
@
video
.
videoType
is
'youtube'
prev_player_type
=
$
.
cookie
(
'prev_player_type'
)
if
prev_player_type
==
'html5'
youTubeId
=
@
video
.
videos
[
'1.0'
]
else
youTubeId
=
@
video
.
youtubeId
()
@
player
=
new
YT
.
Player
@
video
.
id
,
playerVars
:
@
playerVars
videoId
:
youTubeId
events
:
onReady
:
@
onReady
onStateChange
:
@
onStateChange
onPlaybackQualityChange
:
@
onPlaybackQualityChange
if
@
video
.
show_captions
is
true
@
caption
.
hideCaptions
(
@
[
'video'
].
hide_captions
)
addToolTip
:
->
@
$
(
'.add-fullscreen, .hide-subtitles'
).
qtip
position
:
my
:
'top right'
at
:
'top center'
onReady
:
(
event
)
=>
if
@
video
.
videoType
is
'html5'
@
player
.
setPlaybackRate
@
video
.
speed
unless
onTouchBasedDevice
()
$
(
'.video-load-complete:first'
).
data
(
'video'
).
player
.
play
()
onStateChange
:
(
event
)
=>
_this
=
this
switch
event
.
data
when
@
PlayerState
.
UNSTARTED
# Before the video starts playing, let us see if we are in YouTube player,
# and if YouTube is in HTML5 mode. If both cases are true, then we can make
# it so that speed switching happens natively.
if
@
video
.
videoType
is
"youtube"
# Because YouTube API does not have a direct method to determine the mode we
# are in (Flash or HTML5), we rely on an indirect method. Currently, when in
# Flash mode, YouTube player reports that there is only one (1.0) speed
# available. When in HTML5 mode, it reports multiple speeds available. We
# will use this fact.
#
# NOTE: It is my strong belief that in the future YouTube Flash player will
# not get speed changes. This is a dying technology. So we can safely use
# this indirect method to determine player mode.
availableSpeeds
=
@
player
.
getAvailablePlaybackRates
()
prev_player_type
=
$
.
cookie
(
'prev_player_type'
)
if
availableSpeeds
.
length
>
1
# If the user last accessed the page and watched a movie via YouTube
# player, and it was using Flash mode, then we must reset the current
# YouTube speed to 1.0 (by loading appropriate video that is encoded at
# 1.0 speed).
if
prev_player_type
==
'youtube'
$
.
cookie
(
'prev_player_type'
,
'html5'
,
expires
:
3650
,
path
:
'/'
)
@
onSpeedChange
null
,
'1.0'
,
false
else
if
prev_player_type
!=
'html5'
$
.
cookie
(
'prev_player_type'
,
'html5'
,
expires
:
3650
,
path
:
'/'
)
# Now we must update all the speeds to the ones available via the YouTube
# HTML5 API. The default speeds are not exactly the same as reported by
# YouTube, so we will remove the default speeds, and populate all the
# necessary data with correct available speeds.
baseSpeedSubs
=
@
video
.
videos
[
"1.0"
]
$
.
each
@
video
.
videos
,
(
index
,
value
)
->
delete
_this
.
video
.
videos
[
index
]
@
video
.
speeds
=
[]
$
.
each
availableSpeeds
,
(
index
,
value
)
->
_this
.
video
.
videos
[
value
.
toFixed
(
2
).
replace
(
/\.00$/
,
".0"
)]
=
baseSpeedSubs
_this
.
video
.
speeds
.
push
value
.
toFixed
(
2
).
replace
(
/\.00$/
,
".0"
)
# We must update the Speed Control to reflect the new avialble speeds.
@
speedControl
.
reRender
@
video
.
speeds
,
@
video
.
speed
# Now we set the videoType to 'HTML5'. This works because my HTML5Video
# class is fully compatible with YouTube HTML5 API.
@
video
.
videoType
=
'html5'
@
video
.
setSpeed
$
.
cookie
(
'video_speed'
)
# Change the speed to the required one.
@
player
.
setPlaybackRate
@
video
.
speed
else
# We are in YouTube player, and in Flash mode. Check previos mode.
if
prev_player_type
!=
'youtube'
$
.
cookie
(
'prev_player_type'
,
'youtube'
,
expires
:
3650
,
path
:
'/'
)
# We need to set the proper speed when previous mode was not 'youtube'.
@
onSpeedChange
null
,
$
.
cookie
(
'video_speed'
)
@
onUnstarted
()
when
@
PlayerState
.
PLAYING
@
onPlay
()
when
@
PlayerState
.
PAUSED
@
onPause
()
when
@
PlayerState
.
ENDED
@
onEnded
()
onPlaybackQualityChange
:
(
event
,
value
)
=>
quality
=
@
player
.
getPlaybackQuality
()
@
qualityControl
.
onQualityChange
(
quality
)
handlePlaybackQualityChange
:
(
event
,
value
)
=>
@
player
.
setPlaybackQuality
(
value
)
onUnstarted
:
=>
@
control
.
pause
()
if
@
video
.
show_captions
is
true
@
caption
.
pause
()
onPlay
:
=>
@
video
.
log
'play_video'
unless
@
player
.
interval
@
player
.
interval
=
setInterval
(
@
update
,
200
)
if
@
video
.
show_captions
is
true
@
caption
.
play
()
@
control
.
play
()
@
progressSlider
.
play
()
onPause
:
=>
@
video
.
log
'pause_video'
clearInterval
(
@
player
.
interval
)
@
player
.
interval
=
null
if
@
video
.
show_captions
is
true
@
caption
.
pause
()
@
control
.
pause
()
onEnded
:
=>
@
control
.
pause
()
if
@
video
.
show_captions
is
true
@
caption
.
pause
()
onSeek
:
(
event
,
time
)
=>
@
player
.
seekTo
(
time
,
true
)
if
@
isPlaying
()
clearInterval
(
@
player
.
interval
)
@
player
.
interval
=
setInterval
(
@
update
,
200
)
else
@
currentTime
=
time
@
updatePlayTime
time
onSpeedChange
:
(
event
,
newSpeed
,
updateCookie
)
=>
if
@
video
.
videoType
is
'youtube'
@
currentTime
=
Time
.
convert
(
@
currentTime
,
parseFloat
(
@
currentSpeed
()),
newSpeed
)
newSpeed
=
parseFloat
(
newSpeed
).
toFixed
(
2
).
replace
/\.00$/
,
'.0'
@
video
.
setSpeed
newSpeed
,
updateCookie
if
@
video
.
videoType
is
'youtube'
if
@
video
.
show_captions
is
true
@
caption
.
currentSpeed
=
newSpeed
if
@
video
.
videoType
is
'html5'
@
player
.
setPlaybackRate
newSpeed
else
if
@
video
.
videoType
is
'youtube'
if
@
isPlaying
()
@
player
.
loadVideoById
(
@
video
.
youtubeId
(),
@
currentTime
)
else
@
player
.
cueVideoById
(
@
video
.
youtubeId
(),
@
currentTime
)
if
@
video
.
videoType
is
'youtube'
@
updatePlayTime
@
currentTime
onVolumeChange
:
(
event
,
volume
)
=>
@
player
.
setVolume
volume
update
:
=>
if
@
currentTime
=
@
player
.
getCurrentTime
()
@
updatePlayTime
@
currentTime
updatePlayTime
:
(
time
)
->
progress
=
Time
.
format
(
time
)
+
' / '
+
Time
.
format
(
@
duration
())
@
$
(
".vidtime"
).
html
(
progress
)
if
@
video
.
show_captions
is
true
@
caption
.
updatePlayTime
(
time
)
@
progressSlider
.
updatePlayTime
(
time
,
@
duration
())
toggleFullScreen
:
(
event
)
=>
event
.
preventDefault
()
if
@
el
.
hasClass
(
'fullscreen'
)
@
$
(
'.add-fullscreen'
).
attr
(
'title'
,
'Fill browser'
)
@
el
.
removeClass
(
'fullscreen'
)
else
@
el
.
addClass
(
'fullscreen'
)
@
$
(
'.add-fullscreen'
).
attr
(
'title'
,
'Exit fill browser'
)
if
@
video
.
show_captions
is
true
@
caption
.
resize
()
# Delegates
play
:
=>
@
player
.
playVideo
()
if
@
player
.
playVideo
isPlaying
:
->
@
player
.
getPlayerState
()
==
@
PlayerState
.
PLAYING
pause
:
=>
@
player
.
pauseVideo
()
if
@
player
.
pauseVideo
duration
:
->
duration
=
@
player
.
getDuration
()
if
isFinite
(
duration
)
is
false
duration
=
@
video
.
getDuration
()
duration
currentSpeed
:
->
@
video
.
speed
volume
:
(
value
)
->
if
value
?
@
player
.
setVolume
value
else
@
player
.
getVolume
()
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_progress_slider.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoProgressSliderAlpha
extends
SubviewAlpha
initialize
:
->
@
buildSlider
()
unless
onTouchBasedDevice
()
buildSlider
:
->
@
slider
=
@
el
.
slider
range
:
'min'
change
:
@
onChange
slide
:
@
onSlide
stop
:
@
onStop
@
buildHandle
()
buildHandle
:
->
@
handle
=
@
$
(
'.slider .ui-slider-handle'
)
@
handle
.
qtip
content
:
"
#{
Time
.
format
(
@
slider
.
slider
(
'value'
))
}
"
position
:
my
:
'bottom center'
at
:
'top center'
container
:
@
handle
hide
:
delay
:
700
style
:
classes
:
'ui-tooltip-slider'
widget
:
true
play
:
=>
@
buildSlider
()
unless
@
slider
updatePlayTime
:
(
currentTime
,
duration
)
->
if
@
slider
&&
!
@
frozen
@
slider
.
slider
(
'option'
,
'max'
,
duration
)
@
slider
.
slider
(
'value'
,
currentTime
)
onSlide
:
(
event
,
ui
)
=>
@
frozen
=
true
@
updateTooltip
(
ui
.
value
)
$
(
@
).
trigger
(
'seek'
,
ui
.
value
)
onChange
:
(
event
,
ui
)
=>
@
updateTooltip
(
ui
.
value
)
onStop
:
(
event
,
ui
)
=>
@
frozen
=
true
$
(
@
).
trigger
(
'seek'
,
ui
.
value
)
setTimeout
(
=>
@
frozen
=
false
),
200
updateTooltip
:
(
value
)
->
@
handle
.
qtip
(
'option'
,
'content.text'
,
"
#{
Time
.
format
(
value
)
}
"
)
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_quality_control.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoQualityControlAlpha
extends
SubviewAlpha
initialize
:
->
@
quality
=
null
;
bind
:
->
@
$
(
'.quality_control'
).
click
@
toggleQuality
render
:
->
@
el
.
append
"""
<a href="#" class="quality_control" title="HD">HD</a>
"""
#"
onQualityChange
:
(
value
)
->
@
quality
=
value
if
@
quality
in
[
'hd720'
,
'hd1080'
,
'highres'
]
@
el
.
addClass
(
'active'
)
else
@
el
.
removeClass
(
'active'
)
toggleQuality
:
(
event
)
=>
event
.
preventDefault
()
if
@
quality
in
[
'hd720'
,
'hd1080'
,
'highres'
]
newQuality
=
'large'
else
newQuality
=
'hd720'
$
(
@
).
trigger
(
'changeQuality'
,
newQuality
)
\ No newline at end of file
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_speed_control.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoSpeedControlAlpha
extends
SubviewAlpha
bind
:
->
@
$
(
'.video_speeds a'
).
click
@
changeVideoSpeed
if
onTouchBasedDevice
()
@
$
(
'.speeds'
).
click
(
event
)
->
event
.
preventDefault
()
$
(
this
).
toggleClass
(
'open'
)
else
@
$
(
'.speeds'
).
mouseenter
->
$
(
this
).
addClass
(
'open'
)
@
$
(
'.speeds'
).
mouseleave
->
$
(
this
).
removeClass
(
'open'
)
@
$
(
'.speeds'
).
click
(
event
)
->
event
.
preventDefault
()
$
(
this
).
removeClass
(
'open'
)
render
:
->
@
el
.
prepend
"""
<div class="speeds">
<a href="#">
<h3>Speed</h3>
<p class="active"></p>
</a>
<ol class="video_speeds"></ol>
</div>
"""
$
.
each
@
speeds
,
(
index
,
speed
)
=>
link
=
$
(
'<a>'
).
attr
(
href
:
"#"
).
html
(
"
#{
speed
}
x"
)
@
$
(
'.video_speeds'
).
prepend
(
$
(
'<li>'
).
attr
(
'data-speed'
,
speed
).
html
(
link
))
@
setSpeed
@
currentSpeed
reRender
:
(
newSpeeds
,
currentSpeed
)
->
@
$
(
'.video_speeds'
).
empty
()
@
$
(
'.video_speeds li'
).
removeClass
(
'active'
)
@
speeds
=
newSpeeds
$
.
each
@
speeds
,
(
index
,
speed
)
=>
link
=
$
(
'<a>'
).
attr
(
href
:
"#"
).
html
(
"
#{
speed
}
x"
)
listItem
=
$
(
'<li>'
).
attr
(
'data-speed'
,
speed
).
html
(
link
);
listItem
.
addClass
(
'active'
)
if
speed
is
currentSpeed
@
$
(
'.video_speeds'
).
prepend
listItem
@
$
(
'.video_speeds a'
).
click
@
changeVideoSpeed
changeVideoSpeed
:
(
event
)
=>
event
.
preventDefault
()
unless
$
(
event
.
target
).
parent
().
hasClass
(
'active'
)
@
currentSpeed
=
$
(
event
.
target
).
parent
().
data
(
'speed'
)
$
(
@
).
trigger
'speedChange'
,
$
(
event
.
target
).
parent
().
data
(
'speed'
)
@
setSpeed
(
parseFloat
(
@
currentSpeed
).
toFixed
(
2
).
replace
/\.00$/
,
'.0'
)
setSpeed
:
(
speed
)
->
@
$
(
'.video_speeds li'
).
removeClass
(
'active'
)
@
$
(
".video_speeds li[data-speed='
#{
speed
}
']"
).
addClass
(
'active'
)
@
$
(
'.speeds p.active'
).
html
(
"
#{
speed
}
x"
)
common/lib/xmodule/xmodule/js/src/videoalpha/display/video_volume_control.coffee
0 → 100644
View file @
1f61f6ce
class
@
VideoVolumeControlAlpha
extends
SubviewAlpha
initialize
:
->
@
currentVolume
=
100
bind
:
->
@
$
(
'.volume'
).
mouseenter
->
$
(
this
).
addClass
(
'open'
)
@
$
(
'.volume'
).
mouseleave
->
$
(
this
).
removeClass
(
'open'
)
@
$
(
'.volume>a'
).
click
(
@
toggleMute
)
render
:
->
@
el
.
prepend
"""
<div class="volume">
<a href="#"></a>
<div class="volume-slider-container">
<div class="volume-slider"></div>
</div>
</div>
"""
#"
@
slider
=
@
$
(
'.volume-slider'
).
slider
orientation
:
"vertical"
range
:
"min"
min
:
0
max
:
100
value
:
100
change
:
@
onChange
slide
:
@
onChange
onChange
:
(
event
,
ui
)
=>
@
currentVolume
=
ui
.
value
$
(
@
).
trigger
'volumeChange'
,
@
currentVolume
@
$
(
'.volume'
).
toggleClass
'muted'
,
@
currentVolume
==
0
toggleMute
:
=>
if
@
currentVolume
>
0
@
previousVolume
=
@
currentVolume
@
slider
.
slider
'option'
,
'value'
,
0
else
@
slider
.
slider
'option'
,
'value'
,
@
previousVolume
common/lib/xmodule/xmodule/templates/videoalpha/default.yaml
0 → 100644
View file @
1f61f6ce
---
metadata
:
display_name
:
default
data_dir
:
a_made_up_name
data
:
|
<videoalpha youtube="0.75:JMD_ifUUfsU,1.0:OEoXaMPEzfM,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY"/>
children
:
[]
common/lib/xmodule/xmodule/video_module.py
View file @
1f61f6ce
...
@@ -4,6 +4,8 @@ import logging
...
@@ -4,6 +4,8 @@ import logging
from
lxml
import
etree
from
lxml
import
etree
from
pkg_resources
import
resource_string
,
resource_listdir
from
pkg_resources
import
resource_string
,
resource_listdir
from
django.http
import
Http404
from
xmodule.x_module
import
XModule
from
xmodule.x_module
import
XModule
from
xmodule.raw_module
import
RawDescriptor
from
xmodule.raw_module
import
RawDescriptor
from
xmodule.modulestore.xml
import
XMLModuleStore
from
xmodule.modulestore.xml
import
XMLModuleStore
...
@@ -13,9 +15,6 @@ from xmodule.contentstore.content import StaticContent
...
@@ -13,9 +15,6 @@ from xmodule.contentstore.content import StaticContent
import
datetime
import
datetime
import
time
import
time
import
datetime
import
time
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
...
common/lib/xmodule/xmodule/videoalpha_module.py
0 → 100644
View file @
1f61f6ce
import
json
import
logging
from
lxml
import
etree
from
pkg_resources
import
resource_string
,
resource_listdir
from
django.http
import
Http404
from
xmodule.x_module
import
XModule
from
xmodule.raw_module
import
RawDescriptor
from
xmodule.modulestore.mongo
import
MongoModuleStore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.content
import
StaticContent
import
datetime
import
time
log
=
logging
.
getLogger
(
__name__
)
class
VideoAlphaModule
(
XModule
):
"""
XML source example:
<videoalpha show_captions="true"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
url_name="lecture_21_3" display_name="S19V3: Vacancies"
>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.mp4"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.webm"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
</videoalpha>
"""
video_time
=
0
icon_class
=
'video'
js
=
{
'js'
:
[
resource_string
(
__name__
,
'js/src/videoalpha/display/html5_video.js'
)],
'coffee'
:
[
resource_string
(
__name__
,
'js/src/time.coffee'
),
resource_string
(
__name__
,
'js/src/videoalpha/display.coffee'
)]
+
[
resource_string
(
__name__
,
'js/src/videoalpha/display/'
+
filename
)
for
filename
in
sorted
(
resource_listdir
(
__name__
,
'js/src/videoalpha/display'
))
if
filename
.
endswith
(
'.coffee'
)]}
css
=
{
'scss'
:
[
resource_string
(
__name__
,
'css/videoalpha/display.scss'
)]}
js_module_name
=
"VideoAlpha"
def
__init__
(
self
,
system
,
location
,
definition
,
descriptor
,
instance_state
=
None
,
shared_state
=
None
,
**
kwargs
):
XModule
.
__init__
(
self
,
system
,
location
,
definition
,
descriptor
,
instance_state
,
shared_state
,
**
kwargs
)
xmltree
=
etree
.
fromstring
(
self
.
definition
[
'data'
])
self
.
youtube_streams
=
xmltree
.
get
(
'youtube'
)
self
.
sub
=
xmltree
.
get
(
'sub'
)
self
.
position
=
0
self
.
show_captions
=
xmltree
.
get
(
'show_captions'
,
'true'
)
self
.
sources
=
{
'main'
:
self
.
_get_source
(
xmltree
),
'mp4'
:
self
.
_get_source
(
xmltree
,
[
'mp4'
]),
'webm'
:
self
.
_get_source
(
xmltree
,
[
'webm'
]),
'ogv'
:
self
.
_get_source
(
xmltree
,
[
'ogv'
]),
}
self
.
track
=
self
.
_get_track
(
xmltree
)
self
.
start_time
,
self
.
end_time
=
self
.
_get_timeframe
(
xmltree
)
if
instance_state
is
not
None
:
state
=
json
.
loads
(
instance_state
)
if
'position'
in
state
:
self
.
position
=
int
(
float
(
state
[
'position'
]))
def
_get_source
(
self
,
xmltree
,
exts
=
None
):
"""Find the first valid source, which ends with one of `exts`."""
exts
=
[
'mp4'
,
'ogv'
,
'avi'
,
'webm'
]
if
exts
is
None
else
exts
condition
=
lambda
src
:
any
([
src
.
endswith
(
ext
)
for
ext
in
exts
])
return
self
.
_get_first_external
(
xmltree
,
'source'
,
condition
)
def
_get_track
(
self
,
xmltree
):
# find the first valid track
return
self
.
_get_first_external
(
xmltree
,
'track'
)
def
_get_first_external
(
self
,
xmltree
,
tag
,
condition
=
bool
):
"""Will return the first 'valid' element of the given tag.
'valid' means that `condition('src' attribute) == True`
"""
result
=
None
for
element
in
xmltree
.
findall
(
tag
):
src
=
element
.
get
(
'src'
)
if
condition
(
src
):
result
=
src
break
return
result
def
_get_timeframe
(
self
,
xmltree
):
""" Converts 'from' and 'to' parameters in video tag to seconds.
If there are no parameters, returns empty string. """
def
parse_time
(
s
):
"""Converts s in '12:34:45' format to seconds. If s is
None, returns empty string"""
if
s
is
None
:
return
''
else
:
x
=
time
.
strptime
(
s
,
'
%
H:
%
M:
%
S'
)
return
datetime
.
timedelta
(
hours
=
x
.
tm_hour
,
minutes
=
x
.
tm_min
,
seconds
=
x
.
tm_sec
)
.
total_seconds
()
return
parse_time
(
xmltree
.
get
(
'from'
)),
parse_time
(
xmltree
.
get
(
'to'
))
def
handle_ajax
(
self
,
dispatch
,
get
):
"""Handle ajax calls to this video.
TODO (vshnayder): This is not being called right now, so the
position is not being saved.
"""
log
.
debug
(
u"GET {0}"
.
format
(
get
))
log
.
debug
(
u"DISPATCH {0}"
.
format
(
dispatch
))
if
dispatch
==
'goto_position'
:
self
.
position
=
int
(
float
(
get
[
'position'
]))
log
.
info
(
u"NEW POSITION {0}"
.
format
(
self
.
position
))
return
json
.
dumps
({
'success'
:
True
})
raise
Http404
()
def
get_instance_state
(
self
):
return
json
.
dumps
({
'position'
:
self
.
position
})
def
get_html
(
self
):
if
isinstance
(
modulestore
(),
MongoModuleStore
):
caption_asset_path
=
StaticContent
.
get_base_url_path_for_course_assets
(
self
.
location
)
+
'/subs_'
else
:
# VS[compat]
# cdodge: filesystem static content support.
caption_asset_path
=
"/static/{0}/subs/"
.
format
(
self
.
metadata
[
'data_dir'
])
return
self
.
system
.
render_template
(
'videoalpha.html'
,
{
'youtube_streams'
:
self
.
youtube_streams
,
'id'
:
self
.
location
.
html_id
(),
'sub'
:
self
.
sub
,
'sources'
:
self
.
sources
,
'track'
:
self
.
track
,
'display_name'
:
self
.
display_name
,
# TODO (cpennington): This won't work when we move to data that isn't on the filesystem
'data_dir'
:
self
.
metadata
[
'data_dir'
],
'caption_asset_path'
:
caption_asset_path
,
'show_captions'
:
self
.
show_captions
,
'start'
:
self
.
start_time
,
'end'
:
self
.
end_time
})
class
VideoAlphaDescriptor
(
RawDescriptor
):
module_class
=
VideoAlphaModule
stores_state
=
True
template_dir_name
=
"videoalpha"
lms/templates/courseware/courseware.html
View file @
1f61f6ce
...
@@ -32,7 +32,7 @@
...
@@ -32,7 +32,7 @@
<!-- TODO: http://docs.jquery.com/Plugins/Validation -->
<!-- TODO: http://docs.jquery.com/Plugins/Validation -->
<script
type=
"text/javascript"
>
<script
type=
"text/javascript"
>
document
.
write
(
'
\
x3Cscript type="text/javascript" src="'
+
document
.
write
(
'
\
x3Cscript type="text/javascript" src="'
+
document
.
location
.
protocol
+
'//www.youtube.com/
player
_api">
\
x3C/script>'
);
document
.
location
.
protocol
+
'//www.youtube.com/
iframe
_api">
\
x3C/script>'
);
</script>
</script>
<script
type=
"text/javascript"
>
<script
type=
"text/javascript"
>
...
@@ -61,7 +61,7 @@
...
@@ -61,7 +61,7 @@
</script>
</script>
% if timer_expiration_duration:
% if timer_expiration_duration:
<script
type=
"text/javascript"
>
<script
type=
"text/javascript"
>
var
timer
=
{
var
timer
=
{
timer_inst
:
null
,
timer_inst
:
null
,
end_time
:
null
,
end_time
:
null
,
...
@@ -79,8 +79,8 @@
...
@@ -79,8 +79,8 @@
remaining_secs
=
remaining_secs
%
3600
;
remaining_secs
=
remaining_secs
%
3600
;
var
minutes
=
pretty_time_string
(
Math
.
floor
(
remaining_secs
/
60
));
var
minutes
=
pretty_time_string
(
Math
.
floor
(
remaining_secs
/
60
));
remaining_secs
=
remaining_secs
%
60
;
remaining_secs
=
remaining_secs
%
60
;
var
seconds
=
pretty_time_string
(
Math
.
floor
(
remaining_secs
));
var
seconds
=
pretty_time_string
(
Math
.
floor
(
remaining_secs
));
var
remainingTimeString
=
hours
+
":"
+
minutes
+
":"
+
seconds
;
var
remainingTimeString
=
hours
+
":"
+
minutes
+
":"
+
seconds
;
return
remainingTimeString
;
return
remainingTimeString
;
},
},
...
@@ -100,11 +100,11 @@
...
@@ -100,11 +100,11 @@
end
:
function
(
self
)
{
end
:
function
(
self
)
{
clearInterval
(
self
.
timer_inst
);
clearInterval
(
self
.
timer_inst
);
// redirect to specified URL:
// redirect to specified URL:
window
.
location
=
"${time_expired_redirect_url}"
;
window
.
location
=
"${time_expired_redirect_url}"
;
}
}
}
}
// start timer right away:
// start timer right away:
timer
.
start
();
timer
.
start
();
</script>
</script>
% endif
% endif
...
...
lms/templates/videoalpha.html
0 → 100644
View file @
1f61f6ce
% if display_name is not UNDEFINED and display_name is not None:
<h2>
${display_name}
</h2>
% endif
%if settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
<div
id=
"stub_out_video_for_testing"
></div>
%else:
<div
id=
"video_${id}"
class=
"video"
data-streams=
"${youtube_streams}"
${'
data-sub=
"{}"
'.
format
(
sub
)
if
sub
else
''}
${'
data-mp4-source=
"{}"
'.
format
(
sources
.
get
('
mp4
'))
if
sources
.
get
('
mp4
')
else
''}
${'
data-webm-source=
"{}"
'.
format
(
sources
.
get
('
webm
'))
if
sources
.
get
('
webm
')
else
''}
${'
data-ogg-source=
"{}"
'.
format
(
sources
.
get
('
ogv
'))
if
sources
.
get
('
ogv
')
else
''}
data-caption-data-dir=
"${data_dir}"
data-show-captions=
"${show_captions}"
data-start=
"${start}"
data-end=
"${end}"
data-caption-asset-path=
"${caption_asset_path}"
>
<div
class=
"tc-wrapper"
>
<article
class=
"video-wrapper"
>
<section
class=
"video-player"
>
<div
id=
"${id}"
></div>
</section>
<section
class=
"video-controls"
></section>
</article>
</div>
</div>
%endif
% if sources.get('main'):
<div
class=
"video-sources"
>
<p>
Download video
<a
href=
"${sources.get('main')}"
>
here
</a>
.
</p>
</div>
% endif
% if track:
<div
class=
"video-tracks"
>
<p>
Download subtitles
<a
href=
"${track}"
>
here
</a>
.
</p>
</div>
% endif
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