Rewritten video module
This commit is contained in:
@@ -30,32 +30,19 @@ class Module(XModule):
|
||||
def get_xml_tags(c):
|
||||
'''Tags in the courseware file guaranteed to correspond to the module'''
|
||||
return ["video"]
|
||||
|
||||
|
||||
def video_list(self):
|
||||
l = self.youtube.split(',')
|
||||
l = [i.split(":") for i in l]
|
||||
return json.dumps(dict(l))
|
||||
|
||||
|
||||
def get_html(self):
|
||||
return render_to_string('video.html',{'streams':self.video_list(),
|
||||
'id':self.item_id,
|
||||
'position':self.position,
|
||||
'name':self.name,
|
||||
'position':self.position,
|
||||
'name':self.name,
|
||||
'annotations':self.annotations})
|
||||
|
||||
def get_init_js(self):
|
||||
'''JavaScript code to be run when problem is shown. Be aware
|
||||
that this may happen several times on the same page
|
||||
(e.g. student switching tabs). Common functions should be put
|
||||
in the main course .js files for now. '''
|
||||
log.debug(u"INIT POSITION {0}".format(self.position))
|
||||
return render_to_string('video_init.js',{'streams':self.video_list(),
|
||||
'id':self.item_id,
|
||||
'position':self.position})+self.annotations_init
|
||||
|
||||
def get_destroy_js(self):
|
||||
return "videoDestroy(\"{0}\");".format(self.item_id)+self.annotations_destroy
|
||||
|
||||
def __init__(self, system, xml, item_id, state=None):
|
||||
XModule.__init__(self, system, xml, item_id, state)
|
||||
xmltree=etree.fromstring(xml)
|
||||
@@ -69,5 +56,3 @@ class Module(XModule):
|
||||
|
||||
self.annotations=[(e.get("name"),self.render_function(e)) \
|
||||
for e in xmltree]
|
||||
self.annotations_init="".join([e[1]['init_js'] for e in self.annotations if 'init_js' in e[1]])
|
||||
self.annotations_destroy="".join([e[1]['destroy_js'] for e in self.annotations if 'destroy_js' in e[1]])
|
||||
|
||||
@@ -13,6 +13,8 @@ class window.Courseware
|
||||
autoHeight: false
|
||||
$('#open_close_accordion a').click navigation.toggle
|
||||
|
||||
$('#accordion').show()
|
||||
|
||||
log: (event, ui) ->
|
||||
log_event 'accordion',
|
||||
newheader: ui.newHeader.text()
|
||||
|
||||
@@ -2,7 +2,11 @@ $ ->
|
||||
$.ajaxSetup
|
||||
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
|
||||
|
||||
window.onTouchBasedDevice = ->
|
||||
navigator.userAgent.match /iPhone|iPod|iPad/i
|
||||
|
||||
Calculator.bind()
|
||||
Courseware.bind()
|
||||
FeedbackForm.bind()
|
||||
$("a[rel*=leanModal]").leanModal()
|
||||
|
||||
|
||||
11
static/js/jquery.scrollTo-1.4.2-min.js
vendored
Normal file
11
static/js/jquery.scrollTo-1.4.2-min.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* jQuery.ScrollTo - Easy element scrolling using jQuery.
|
||||
* Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
|
||||
* Dual licensed under MIT and GPL.
|
||||
* Date: 5/25/2009
|
||||
* @author Ariel Flesler
|
||||
* @version 1.4.2
|
||||
*
|
||||
* http://flesler.blogspot.com/2007/10/jqueryscrollto.html
|
||||
*/
|
||||
;(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);
|
||||
@@ -14,38 +14,29 @@ section.course-content {
|
||||
}
|
||||
}
|
||||
|
||||
div.video-subtitles {
|
||||
div.video {
|
||||
@include clearfix();
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
@include clearfix();
|
||||
display: block;
|
||||
margin: 0 (-(lh()));
|
||||
padding: 6px lh();
|
||||
|
||||
div.video-wrapper {
|
||||
article.video-wrapper {
|
||||
float: left;
|
||||
margin-right: flex-gutter(9);
|
||||
width: flex-grid(6, 9);
|
||||
|
||||
div.video-player {
|
||||
section.video-player {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 56.25%;
|
||||
padding-top: 30px;
|
||||
position: relative;
|
||||
|
||||
object {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
iframe#html5_player {
|
||||
object, iframe {
|
||||
border: none;
|
||||
display: none;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
@@ -68,7 +59,7 @@ section.course-content {
|
||||
}
|
||||
}
|
||||
|
||||
div#slider {
|
||||
div.slider {
|
||||
@extend .clearfix;
|
||||
background: #c2c2c2;
|
||||
border: none;
|
||||
@@ -175,7 +166,8 @@ section.course-content {
|
||||
}
|
||||
}
|
||||
|
||||
div#vidtime {
|
||||
div.vidtime {
|
||||
padding-left: lh(.75);
|
||||
font-weight: bold;
|
||||
line-height: 46px; //height of play pause buttons
|
||||
padding-left: lh(.75);
|
||||
@@ -190,6 +182,7 @@ section.course-content {
|
||||
|
||||
div.speeds {
|
||||
float: left;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
background: url('../images/closed-arrow.png') 10px center no-repeat;
|
||||
@@ -211,7 +204,7 @@ section.course-content {
|
||||
&.open {
|
||||
background: url('../images/open-arrow.png') 10px center no-repeat;
|
||||
|
||||
ol#video_speeds {
|
||||
ol.video_speeds {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -234,47 +227,52 @@ section.course-content {
|
||||
padding: 0 lh(.5) 0 0;
|
||||
}
|
||||
|
||||
// fix for now
|
||||
ol#video_speeds {
|
||||
&:hover, &:active, &:focus {
|
||||
opacity: 1;
|
||||
background-color: #444;
|
||||
border: 1px solid #000;
|
||||
@include box-shadow(inset 1px 0 0 #555, 0 3px 0 #444);
|
||||
display: none;
|
||||
left: -1px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top:0;
|
||||
@include transition();
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
border-bottom: 1px solid #000;
|
||||
@include box-shadow( 0 1px 0 #555);
|
||||
// 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: 125px;
|
||||
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;
|
||||
cursor: pointer;
|
||||
padding: 0 lh(.5);
|
||||
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
@include box-shadow(none);
|
||||
margin-top: 0;
|
||||
}
|
||||
display: block;
|
||||
padding: lh(.5);
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #444;
|
||||
opacity: 1;
|
||||
&.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@include box-shadow(none);
|
||||
border-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +332,7 @@ section.course-content {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div#slider {
|
||||
div.slider {
|
||||
height: 14px;
|
||||
margin-top: -7px;
|
||||
|
||||
@@ -352,7 +350,7 @@ section.course-content {
|
||||
ol.subtitles {
|
||||
float: left;
|
||||
max-height: 460px;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
padding-top: 10px;
|
||||
width: flex-grid(3, 9);
|
||||
|
||||
@@ -360,7 +358,7 @@ section.course-content {
|
||||
border: 0;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0;
|
||||
@include transition(all, .5s, ease-in);
|
||||
|
||||
@@ -373,11 +371,7 @@ section.course-content {
|
||||
color: $mit-red;
|
||||
}
|
||||
|
||||
div {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
div:empty {
|
||||
&:empty {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
@@ -386,7 +380,7 @@ section.course-content {
|
||||
&.closed {
|
||||
@extend .trans;
|
||||
|
||||
div.video-wrapper {
|
||||
article.video-wrapper {
|
||||
width: flex-grid(9,9);
|
||||
}
|
||||
|
||||
@@ -441,11 +435,11 @@ section.course-content {
|
||||
}
|
||||
|
||||
div.tc-wrapper {
|
||||
div.video-wrapper {
|
||||
article.video-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
object#myytplayer, iframe {
|
||||
object, iframe {
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
@@ -487,7 +481,7 @@ section.course-content {
|
||||
}
|
||||
}
|
||||
|
||||
div.course-wrapper.closed section.course-content div.video-subtitles {
|
||||
div.course-wrapper.closed section.course-content div.video {
|
||||
ol.subtitles {
|
||||
max-height: 577px;
|
||||
}
|
||||
|
||||
41
templates/coffee/src/modules/video.coffee
Normal file
41
templates/coffee/src/modules/video.coffee
Normal file
@@ -0,0 +1,41 @@
|
||||
class window.Video
|
||||
constructor: (@id, videos) ->
|
||||
@element = $("#video_#{@id}")
|
||||
@parseVideos videos
|
||||
@fetchMetadata()
|
||||
@parseSpeed()
|
||||
$("#video_#{@id}").data('video', this)
|
||||
window.onYouTubePlayerAPIReady = =>
|
||||
$('.course-content .video').each ->
|
||||
$(this).data('video').embed()
|
||||
|
||||
youtubeId: (speed)->
|
||||
@videos[speed || @speed]
|
||||
|
||||
parseVideos: (videos) ->
|
||||
@videos = {}
|
||||
$.each videos, (speed, url) =>
|
||||
speed = parseFloat(speed).toFixed(2).replace /\.00$/, '.0'
|
||||
@videos[speed] = url
|
||||
|
||||
parseSpeed: ->
|
||||
@setSpeed($.cookie('video_speed'))
|
||||
@speeds = ($.map @videos, (url, speed) -> speed).sort()
|
||||
|
||||
setSpeed: (newSpeed) ->
|
||||
if @videos[newSpeed] != undefined
|
||||
@speed = newSpeed
|
||||
$.cookie('video_speed', "#{newSpeed}", expires: 3650, path: '/')
|
||||
else
|
||||
@speed = '1.0'
|
||||
|
||||
embed: ->
|
||||
@player = new VideoPlayer(this)
|
||||
|
||||
fetchMetadata: (url) ->
|
||||
@metadata = {}
|
||||
$.each @videos, (speed, url) =>
|
||||
$.get "http://gdata.youtube.com/feeds/api/videos/#{url}?v=2&alt=jsonc", ((data) => @metadata[data.data.id] = data.data) , 'jsonp'
|
||||
|
||||
getDuration: ->
|
||||
@metadata[@youtubeId()].duration
|
||||
116
templates/coffee/src/modules/video/video_caption.coffee
Normal file
116
templates/coffee/src/modules/video/video_caption.coffee
Normal file
@@ -0,0 +1,116 @@
|
||||
class VideoCaption
|
||||
constructor: (@player, @youtubeId) ->
|
||||
@index = []
|
||||
@fetchCaption()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
bind: ->
|
||||
$(window).bind('resize', @onWindowResize)
|
||||
$(@player).bind('resize', @onWindowResize)
|
||||
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
@$('.hide-subtitles').click @toggle
|
||||
|
||||
fetchCaption: ->
|
||||
$.getJSON @captionURL(), (captions) =>
|
||||
@captions = captions.text
|
||||
@start = captions.start
|
||||
for index in [0...captions.start.length]
|
||||
for time in [captions.start[index]..captions.end[index]]
|
||||
@index[time] ||= []
|
||||
@index[time].push(index)
|
||||
@render()
|
||||
|
||||
captionURL: ->
|
||||
"/static/subs/#{@youtubeId}.srt.sjson"
|
||||
|
||||
render: ->
|
||||
container = $('<ol class="subtitles">')
|
||||
container.css maxHeight: @$('.video-wrapper').height() - 5
|
||||
|
||||
$.each @captions, (index, text) =>
|
||||
container.append $('<li>').html(text).attr
|
||||
'data-index': index
|
||||
'data-start': @start[index]
|
||||
|
||||
@$('.subtitles').replaceWith(container)
|
||||
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
|
||||
.mousemove(@onMovement).bind('mousewheel', @onMovement)
|
||||
.bind('DOMMouseScroll', @onMovement)
|
||||
@$('.subtitles li[data-index]').click @seekPlayer
|
||||
|
||||
# prepend and append an empty <li> for cosmatic reason
|
||||
@$('.subtitles').prepend($('<li class="spacing">').height(@topSpacingHeight()))
|
||||
.append($('<li class="spacing">').height(@bottomSpacingHeight()))
|
||||
|
||||
onUpdatePlayTime: (event, time) =>
|
||||
# This 250ms offset is required to match the video speed
|
||||
time = Math.round(Time.convert(time, @player.currentSpeed(), '1.0') * 1000 + 250)
|
||||
newIndex = @index[time]
|
||||
|
||||
if newIndex != undefined && @currentIndex != newIndex
|
||||
if @currentIndex
|
||||
for index in @currentIndex
|
||||
@$(".subtitles li[data-index='#{index}']").removeClass('current')
|
||||
|
||||
for index in newIndex
|
||||
@$(".subtitles li[data-index='#{newIndex}']").addClass('current')
|
||||
|
||||
@currentIndex = newIndex
|
||||
@scrollCaption()
|
||||
|
||||
onWindowResize: =>
|
||||
@$('.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: (event) =>
|
||||
@onMouseEnter()
|
||||
|
||||
onMouseLeave: =>
|
||||
clearTimeout @frozen if @frozen
|
||||
@frozen = null
|
||||
@scrollCaption() if @player.isPlaying()
|
||||
|
||||
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', @player.currentSpeed()) / 1000)
|
||||
$(@player).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 @player.element.hasClass('closed')
|
||||
@$('.hide-subtitles').attr('title', 'Turn off captions')
|
||||
@player.element.removeClass('closed')
|
||||
else
|
||||
@$('.hide-subtitles').attr('title', 'Turn on captions')
|
||||
@player.element.addClass('closed')
|
||||
@scrollCaption()
|
||||
|
||||
captionHeight: ->
|
||||
if @player.element.hasClass('fullscreen')
|
||||
$(window).height() - @$('.video-controls').height()
|
||||
else
|
||||
@$('.video-wrapper').height()
|
||||
|
||||
145
templates/coffee/src/modules/video/video_player.coffee
Normal file
145
templates/coffee/src/modules/video/video_player.coffee
Normal file
@@ -0,0 +1,145 @@
|
||||
class VideoPlayer
|
||||
constructor: (@video) ->
|
||||
@currentTime = 0
|
||||
@element = $("#video_#{@video.id}")
|
||||
@buildPlayer()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @element)
|
||||
|
||||
bind: ->
|
||||
$(@).bind('seek', @onSeek)
|
||||
$(@).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
$(@).bind('speedChange', @onSpeedChange)
|
||||
$(document).keyup @bindExitFullScreen
|
||||
|
||||
@$('.video_control').click @togglePlayback
|
||||
@$('.add-fullscreen').click @toggleFullScreen
|
||||
@addToolTip unless onTouchBasedDevice()
|
||||
|
||||
bindExitFullScreen: (event) =>
|
||||
if @element.hasClass('fullscreen') && event.keyCode == 27
|
||||
@toggleFullScreen(event)
|
||||
|
||||
buildPlayer: ->
|
||||
new VideoCaption(this, @video.youtubeId('1.0'))
|
||||
new VideoSpeedControl(this, @video.speeds)
|
||||
new VideoProgressSlider(this)
|
||||
@player = new YT.Player @video.id,
|
||||
playerVars:
|
||||
controls: 0
|
||||
wmode: 'transparent'
|
||||
rel: 0
|
||||
showinfo: 0
|
||||
enablejsapi: 1
|
||||
videoId: @video.youtubeId()
|
||||
events:
|
||||
onReady: @onReady
|
||||
onStateChange: @onStateChange
|
||||
|
||||
addToolTip: ->
|
||||
@$('.add-fullscreen, .hide-subtitles').qtip
|
||||
position:
|
||||
my: 'top right'
|
||||
at: 'top center'
|
||||
|
||||
onReady: =>
|
||||
@setProgress(0, @duration())
|
||||
$(@).trigger('ready')
|
||||
unless true || onTouchBasedDevice()
|
||||
$('.course-content .video:first').data('video').player.play()
|
||||
|
||||
onStateChange: (event) =>
|
||||
switch event.data
|
||||
when YT.PlayerState.PLAYING
|
||||
if window.player && window.player != @player
|
||||
window.player.pauseVideo()
|
||||
window.player = @player
|
||||
@onPlay()
|
||||
when YT.PlayerState.PAUSED
|
||||
if window.player == @player
|
||||
window.player = null
|
||||
@onPause()
|
||||
when YT.PlayerState.ENDED
|
||||
if window.player == @player
|
||||
window.player = null
|
||||
@onPause()
|
||||
|
||||
onPlay: ->
|
||||
@$('.video_control').removeClass('play').addClass('pause').html('Pause')
|
||||
unless @player.interval
|
||||
@player.interval = setInterval(@update, 200)
|
||||
|
||||
onPause: ->
|
||||
@$('.video_control').removeClass('pause').addClass('play').html('Play')
|
||||
clearInterval(@player.interval)
|
||||
@player.interval = null
|
||||
|
||||
onSeek: (event, time) ->
|
||||
@player.seekTo(time, true)
|
||||
if @isPlaying()
|
||||
clearInterval(@player.interval)
|
||||
@player.interval = setInterval(@update, 200)
|
||||
else
|
||||
@currentTime = time
|
||||
$(@).trigger('updatePlayTime', time)
|
||||
|
||||
onSpeedChange: (event, newSpeed) =>
|
||||
@currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed)
|
||||
@video.setSpeed(parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0')
|
||||
|
||||
if @isPlaying()
|
||||
@player.loadVideoById(@video.youtubeId(), @currentTime)
|
||||
else
|
||||
@player.cueVideoById(@video.youtubeId(), @currentTime)
|
||||
@setProgress(@currentTime, @duration())
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
|
||||
update: =>
|
||||
if @currentTime = @player.getCurrentTime()
|
||||
$(@).trigger('updatePlayTime', @currentTime)
|
||||
|
||||
onUpdatePlayTime: (event, time) =>
|
||||
@setProgress(@currentTime) if time
|
||||
|
||||
setProgress: (time) =>
|
||||
progress = Time.format(time) + ' / ' + Time.format(@duration())
|
||||
if @progress != progress
|
||||
@$(".vidtime").html(progress)
|
||||
@progress = progress
|
||||
|
||||
togglePlayback: (event) =>
|
||||
event.preventDefault()
|
||||
if $(event.target).hasClass('play')
|
||||
@play()
|
||||
else
|
||||
@pause()
|
||||
|
||||
toggleFullScreen: (event) =>
|
||||
event.preventDefault()
|
||||
if @element.hasClass('fullscreen')
|
||||
@$('.exit').remove()
|
||||
@$('.add-fullscreen').attr('title', 'Fill browser')
|
||||
@element.removeClass('fullscreen')
|
||||
else
|
||||
@element.append('<a href="#" class="exit">Exit</a>').addClass('fullscreen')
|
||||
@$('.add-fullscreen').attr('title', 'Exit fill browser')
|
||||
@$('.exit').click @toggleFullScreen
|
||||
$(@).trigger('resize')
|
||||
|
||||
# Delegates
|
||||
play: ->
|
||||
@player.playVideo() if @player.playVideo
|
||||
|
||||
isPlaying: ->
|
||||
@player.getPlayerState() == YT.PlayerState.PLAYING
|
||||
|
||||
pause: ->
|
||||
@player.pauseVideo()
|
||||
|
||||
duration: ->
|
||||
@video.getDuration()
|
||||
|
||||
currentSpeed: ->
|
||||
@video.speed
|
||||
@@ -0,0 +1,54 @@
|
||||
class VideoProgressSlider
|
||||
constructor: (@player) ->
|
||||
@buildSlider()
|
||||
@buildHandle()
|
||||
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
|
||||
$(@player).bind('ready', @onReady)
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
buildSlider: ->
|
||||
@slider = @$('.slider').slider
|
||||
range: 'min'
|
||||
change: @onChange
|
||||
slide: @onSlide
|
||||
stop: @onStop
|
||||
|
||||
buildHandle: ->
|
||||
@handle = @$('.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
|
||||
|
||||
onReady: =>
|
||||
@slider.slider('option', 'max', @player.duration())
|
||||
|
||||
onUpdatePlayTime: (event, currentTime) =>
|
||||
if !@frozen
|
||||
@slider.slider('option', 'max', @player.duration())
|
||||
@slider.slider('value', currentTime)
|
||||
|
||||
onSlide: (event, ui) =>
|
||||
@frozen = true
|
||||
@updateTooltip(ui.value)
|
||||
$(@player).trigger('seek', ui.value)
|
||||
|
||||
onChange: (event, ui) =>
|
||||
@updateTooltip(ui.value)
|
||||
|
||||
onStop: (event, ui) =>
|
||||
@frozen = true
|
||||
$(@player).trigger('seek', ui.value)
|
||||
setTimeout (=> @frozen = false), 200
|
||||
|
||||
updateTooltip: (value)->
|
||||
@handle.qtip('option', 'content.text', "#{Time.format(value)}")
|
||||
@@ -0,0 +1,38 @@
|
||||
class VideoSpeedControl
|
||||
constructor: (@player, @speeds) ->
|
||||
@build()
|
||||
@bind()
|
||||
|
||||
$: (selector) ->
|
||||
@player.$(selector)
|
||||
|
||||
bind: ->
|
||||
$(@player).bind('speedChange', @onSpeedChange)
|
||||
@$('.video_speeds a').click @changeVideoSpeed
|
||||
if onTouchBasedDevice()
|
||||
@$('.speeds').click -> $(this).toggleClass('open')
|
||||
else
|
||||
@$('.speeds').mouseover -> $(this).addClass('open')
|
||||
.mouseout -> $(this).removeClass('open')
|
||||
.click (event) ->
|
||||
event.preventDefault()
|
||||
$(this).removeClass('open')
|
||||
|
||||
build: ->
|
||||
$.each @speeds, (index, speed) =>
|
||||
link = $('<a>').attr(href: "#").html("#{speed}x")
|
||||
@$('.video_speeds').prepend($('<li>').attr('data-speed', speed).html(link))
|
||||
@setSpeed(@player.currentSpeed())
|
||||
|
||||
changeVideoSpeed: (event) =>
|
||||
event.preventDefault()
|
||||
unless $(event.target).parent().hasClass('active')
|
||||
$(@player).trigger 'speedChange', $(event.target).parent().data('speed')
|
||||
|
||||
onSpeedChange: (event, speed) =>
|
||||
@setSpeed(parseFloat(speed).toFixed(2).replace /\.00$/, '.0')
|
||||
|
||||
setSpeed: (speed) ->
|
||||
@$('.video_speeds li').removeClass('active')
|
||||
@$(".video_speeds li[data-speed='#{speed}']").addClass('active')
|
||||
@$('.speeds p.active').html("#{speed}x")
|
||||
21
templates/coffee/src/time.coffee
Normal file
21
templates/coffee/src/time.coffee
Normal file
@@ -0,0 +1,21 @@
|
||||
class @Time
|
||||
@format: (time) ->
|
||||
seconds = Math.floor time
|
||||
minutes = Math.floor seconds / 60
|
||||
hours = Math.floor minutes / 60
|
||||
seconds = seconds % 60
|
||||
minutes = minutes % 60
|
||||
|
||||
if hours
|
||||
"#{hours}:#{@pad(minutes)}:#{@pad(seconds % 60)}"
|
||||
else
|
||||
"#{minutes}:#{@pad(seconds % 60)}"
|
||||
|
||||
@pad: (number) ->
|
||||
if number < 10
|
||||
"0#{number}"
|
||||
else
|
||||
number
|
||||
|
||||
@convert: (time, oldSpeed, newSpeed) ->
|
||||
(time * oldSpeed / newSpeed).toFixed(3)
|
||||
@@ -26,7 +26,7 @@
|
||||
<a href="#">close</a>
|
||||
</header>
|
||||
|
||||
<div id="accordion">
|
||||
<div id="accordion" style="display: none">
|
||||
<nav>
|
||||
${accordion}
|
||||
</nav>
|
||||
|
||||
@@ -116,6 +116,11 @@
|
||||
<script type="text/javascript" src="${static.url('js/video_player.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/schematic.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/cktsim.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/jquery.scrollTo-1.4.2-min.js')}"></script>
|
||||
<script type="text/javascript">
|
||||
document.write('\x3Cscript type="text/javascript" src="' +
|
||||
document.location.protocol + '//www.youtube.com/player_api">\x3C/script>');
|
||||
</script>
|
||||
|
||||
<%block name="js_extra"/>
|
||||
</body>
|
||||
|
||||
@@ -2,123 +2,49 @@
|
||||
<h1> ${name} </h1>
|
||||
% endif
|
||||
|
||||
<div class="video-subtitles">
|
||||
<div class="tc-wrapper">
|
||||
<div id="video_${id}" class="video">
|
||||
<div class="tc-wrapper">
|
||||
<article class="video-wrapper">
|
||||
<section class="video-player">
|
||||
<div id="${id}"></div>
|
||||
</section>
|
||||
|
||||
<div class="video-wrapper">
|
||||
<div class="video-player">
|
||||
<div id="ytapiplayer">
|
||||
</div>
|
||||
<section class="video-controls">
|
||||
<div class="slider"></div>
|
||||
<div>
|
||||
<ul class="vcr">
|
||||
<li><a class="video_control play">Play</a></li>
|
||||
<li>
|
||||
<div class="vidtime">0:00 / 0:00</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<iframe id="html5_player" type="text/html" frameborder="0">
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="secondary-controls">
|
||||
<div class="speeds">
|
||||
<a href="#">
|
||||
<h3>Speed</h3>
|
||||
<p class="active"></p>
|
||||
</a>
|
||||
<ol class="video_speeds"></ol>
|
||||
</div>
|
||||
|
||||
<section class="video-controls">
|
||||
<div id="slider"></div>
|
||||
|
||||
<section>
|
||||
<ul class="vcr">
|
||||
<li><a id="video_control" class="pause">Pause</a></li>
|
||||
|
||||
<li>
|
||||
<div id="vidtime">0:00 / 0:00</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<div class="secondary-controls">
|
||||
<div class="speeds">
|
||||
<a href="#">
|
||||
<h3>Speed</h3>
|
||||
<p class="active"></p>
|
||||
<ol id="video_speeds"></ol>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
|
||||
|
||||
<a href="#" class="hide-subtitles" title="Turn off captions">Captions</a>
|
||||
<a href="#" class="add-fullscreen" title="Fill browser">Fill Browser</a>
|
||||
<a href="#" class="hide-subtitles" title="Turn off captions">Captions</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<ol class="subtitles">
|
||||
<li>Attempting to load captions...</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<ol class="subtitles">
|
||||
<!-- <li id="stt_n5"><div id="std_n7" onclick="title_seek(-7);"></div></li> -->
|
||||
<li id="stt_n4"><div id="std_n6" onclick="title_seek(-6);"></div></li>
|
||||
<li id="stt_n4"><div id="std_n5" onclick="title_seek(-5);"></div></li>
|
||||
<li id="stt_n4"><div id="std_n4" onclick="title_seek(-4);"></div></li>
|
||||
<li id="stt_n3"><div id="std_n3" onclick="title_seek(-3);"></div></li>
|
||||
<li id="stt_n2"><div id="std_n2" onclick="title_seek(-2);"></div></li>
|
||||
<li id="stt_n1"><div id="std_n1" onclick="title_seek(-1);"></div></li>
|
||||
<li id="stt_0 "class="current"><div id="std_0" onclick="title_seek(0);"></div></li>
|
||||
<li id="stt_p1"><div id="std_p1" onclick="title_seek( 1);"></div></li>
|
||||
<li id="stt_p2"><div id="std_p2" onclick="title_seek( 2);"></div></li>
|
||||
<li id="stt_p3"><div id="std_p3" onclick="title_seek( 3);"></div></li>
|
||||
<li id="stt_p4"><div id="std_p4" onclick="title_seek( 4);"></div></li>
|
||||
<li id="stt_p5"><div id="std_p5" onclick="title_seek( 5);"></div></li>
|
||||
<li id="stt_p6"><div id="std_p7" onclick="title_seek( 6);"></div></li>
|
||||
<li id="stt_p6"><div id="std_p7" onclick="title_seek( 7);"></div></li>
|
||||
<li id="stt_p6"><div id="std_p7" onclick="title_seek( 8);"></div></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script src="/static/js/jquery.ui.touch-punch.min.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function() {
|
||||
// tooltips for full browser and closed caption
|
||||
$('.add-fullscreen, .hide-subtitles ').qtip({
|
||||
position: {
|
||||
my: 'top right',
|
||||
at: 'top center'
|
||||
}
|
||||
});
|
||||
|
||||
//full browser
|
||||
$('.add-fullscreen').click(function() {
|
||||
|
||||
$('div.video-subtitles').toggleClass('fullscreen');
|
||||
|
||||
if ($('div.video-subtitles').hasClass('fullscreen')) {
|
||||
$('div.video-subtitles').append('<a href="#" class="exit">Exit</a>');
|
||||
} else {
|
||||
$('a.exit').remove();
|
||||
}
|
||||
|
||||
$('.exit').click(function() {
|
||||
$('div.video-subtitles').removeClass('fullscreen');
|
||||
$(this).remove();
|
||||
return false;
|
||||
});
|
||||
|
||||
var link_title = $(this).attr('title');
|
||||
$(this).attr('title', (link_title == 'Exit fill browser') ? 'Fill browser' : 'Exit fill browser');
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
//hide subtitles
|
||||
$('.hide-subtitles').click(function() {
|
||||
$('div.video-subtitles').toggleClass('closed');
|
||||
|
||||
var link_title = $(this).attr('title');
|
||||
$(this).attr('title', (link_title == 'Turn on captions') ? 'Turn off captions' : 'Turn on captions');
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$("div.speeds a").hover(function() {
|
||||
$(this).toggleClass("open");
|
||||
});
|
||||
|
||||
$("div.speeds a").click(function() {
|
||||
return false;
|
||||
});
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
new Video('${id}', ${streams});
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
Reference in New Issue
Block a user