Commit 74ad637e by Valera Rozuvan Committed by Vasyl Nakvasiuk

Basic functionality implemented. Core video module can handle HTML5 sources and…

Basic functionality implemented. Core video module can handle HTML5 sources and YoTube video IDs. Play-pause control works.
Added quality change control. Added qtip usage. Removed unnecessary coffee files.
Slight changes.
Added position slider. Enabled several events related to time progress. Work in progress.
Added volume, and speed controls.
Added captions.
Added full support for captions.
Added correct resize functionality to captions.
Petite work in progress.
add autoplay attr for videoalpha tag
parent 009b8475
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, data)->
# Default parameters that always get logged.
logInfo =
id: @id
code: @youtubeId()
# If extra parameters were passed to the log.
if data
$.each data, (paramName, value) ->
logInfo[paramName] = value
if @videoType is "youtube"
logInfo.code = @youtubeId()
else logInfo.code = "html5" if @videoType is "html5"
Logger.log eventName, logInfo
class @SubviewAlpha
constructor: (options) ->
$.each options, (key, value) =>
@[key] = value
@initialize()
@render()
@bind()
$: (selector) ->
$(selector, @el)
initialize: ->
render: ->
bind: ->
......@@ -3,14 +3,38 @@
// Initialize module.
define(
'videoalpha/display/initialize.js',
['videoalpha/display/bind.js', 'videoalpha/display/video_player.js'],
[
'videoalpha/display/bind.js',
'videoalpha/display/video_player.js'
],
function (bind, VideoPlayer) {
// Initialize() function - what this module "exports".
return function (state, element) {
// Functions which will be accessible via 'state' object.
makeFunctionsPublic(state);
renderElements(state, element);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
// function makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
function makeFunctionsPublic(state) {
state.setSpeed = bind(setSpeed, state);
state.youtubeId = bind(youtubeId, state);
state.getDuration = bind(getDuration, state);
}
// function renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state, element) {
// The parent element of the video, and the ID.
state.el = $(element).find('.video');
state.id = state.el.attr('id').replace(/video_/, '');
......@@ -70,9 +94,6 @@ function (bind, VideoPlayer) {
state.setSpeed($.cookie('video_speed'));
}
// TODO: Check after refactoring whether this can be removed.
state.el.addClass('video-load-complete');
// Configure displaying of captions.
//
// Option
......@@ -116,11 +137,49 @@ function (bind, VideoPlayer) {
}
}($.cookie('current_player_mode')));
// Will be used by various components to register callbacks that can be then called by video core,
// or other components.
state.callbacks = {
'videoPlayer': {
'onPlay': [],
'onPause': [],
'onEnded': [],
'onPlaybackQualityChange': [],
'updatePlayTime': [],
'onSpeedSetChange': []
},
'videoControl': {
'togglePlaybackPlay': [],
'togglePlaybackPause': [],
'toggleFullScreen': []
},
'videoQualityControl': {
'toggleQuality': []
},
'videoProgressSlider': {
'onSlide': [],
'onStop': []
},
'videoVolumeControl': {
'onChange': []
},
'videoSpeedControl': {
'changeVideoSpeed': []
},
'videoCaption': {
'seekPlayer': []
}
};
// Launch embedding of actual video content, or set it up so that it will be done as soon as the
// appropriate video player (YouTube or stand alone HTML5) is loaded, and can handle embedding.
//
// Note that the loading of stand alone HTML5 player API is handled by Require JS. At the time
// when we reach this code, the stand alone HTML5 player is already loaded, so no further testing
// in that case is required.
if (
((state.videoType === 'youtube') && (window.YT) && (window.YT.Player)) ||
((state.videoType === 'html5') && (window.HTML5Video) && (window.HTML5Video.Player))
(state.videoType === 'html5')
) {
embed(state);
} else {
......@@ -128,23 +187,24 @@ function (bind, VideoPlayer) {
window.onYouTubePlayerAPIReady = function() {
embed(state);
};
} else if (state.videoType === 'html5') {
} else { // if (state.videoType === 'html5') {
window.onHTML5PlayerAPIReady = function() {
embed(state);
};
}
}
};
// Private functions start here.
function makeFunctionsPublic(state) {
state.setSpeed = bind(setSpeed, state);
state.youtubeId = bind(youtubeId, state);
state.getDuration = bind(getDuration, state);
state.log = bind(log, state);
}
// function parseYoutubeStreams(state, youtubeStreams)
//
// Take a string in the form:
// "iCawTYPtehk:0.75,KgpclqP-LBA:1.0,9-2670d5nvU:1.5"
// parse it, and make it available via the 'state' object. If we are not given a string, or
// it's length is zero, then we return false.
//
// @return
// false: We don't have YouTube video IDs to work with; most likely we have HTML5 video sources.
// true: Parsing of YouTube video IDs went OK, and we can proceed onwards to play YouTube videos.
function parseYoutubeStreams(state, youtubeStreams) {
if ((typeof youtubeStreams !== 'string') || (youtubeStreams.length === 0)) {
return false;
......@@ -164,6 +224,10 @@ function (bind, VideoPlayer) {
return true;
}
// function parseVideoSources(state, mp4Source, webmSource, oggSource)
//
// Take the HTML5 sources (URLs of videos), and make them available explictly for each type
// of video format (mp4, webm, ogg).
function parseVideoSources(state, mp4Source, webmSource, oggSource) {
state.html5Sources = { 'mp4': null, 'webm': null, 'ogg': null };
......@@ -178,6 +242,11 @@ function (bind, VideoPlayer) {
}
}
// function fetchMetadata(state)
//
// When dealing with YouTube videos, we must fetch meta data that has certain key facts
// not available while the video is loading. For example the length of the video can be
// determined from the meta data.
function fetchMetadata(state) {
state.metadata = {};
......@@ -188,6 +257,9 @@ function (bind, VideoPlayer) {
});
}
// function parseSpeed(state)
//
// Create a separate array of available speeds.
function parseSpeed(state) {
state.speeds = ($.map(state.videos, function(url, speed) {
return speed;
......@@ -196,13 +268,19 @@ function (bind, VideoPlayer) {
state.setSpeed($.cookie('video_speed'));
}
// function embed(state)
//
// This function is called when the current type of video player API becomes available.
// It instantiates the core video module.
function embed(state) {
VideoPlayer(state);
}
// ***************************************************************
// Public functions start here.
// 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().
// ***************************************************************
function setSpeed(newSpeed, updateCookie) {
if (this.speeds.indexOf(newSpeed) !== -1) {
......@@ -227,27 +305,51 @@ function (bind, VideoPlayer) {
return this.metadata[this.youtubeId()].duration;
}
function log(eventName) {
var logInfo;
/*
* Because jQuery events can be triggered on some jQuery object, we must make sure that
* we don't trigger an event on an undefined object. For this we will have an in-between
* method that will check for the existance of an object before triggering an event on it.
*
* @objChain is an array that contains the chain of properties on the 'state' object. For
* example, if
*
* objChain = ['videoPlayer', 'stopVideo'];
*
* then we will check for the existance of the
*
* state.videoPlayer.stopVideo
*
* object, and, if found to be present, will trigger the specified event on this object.
*
* @eventName - the name of the event to trigger on the specified object.
*/
function trigger(objChain, eventName, extraParameters) {
var tmpObj;
// Remember that 'this' is the 'state' object.
tmpObj = this;
getFinalObj(0);
if (tmpObj === null) {
return false;
}
logInfo = {
'id': this.id,
'code': this.youtubeId(),
'currentTime': this.player.currentTime,
'speed': this.speed
};
tmpObj.trigger(eventName, extraParameters);
if (this.videoType === 'youtube') {
logInfo.code = this.youtubeId();
} else {
if (this.videoType === 'html5') {
logInfo.code = 'html5';
return true;
function getFinalObj(i) {
if (objChain.length !== i) {
if (tmpObj.hasOwnProperty(objChain[i]) === true) {
tmpObj = tmpObj[objChain[i]];
getFinalObj(i + 1);
} else {
tmpObj = null;
}
}
}
Logger.log(eventName, logInfo);
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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: ->
$.ajaxWithPrefix
url: @captionURL()
notifyOnError: false
success: (captions) =>
@captions = captions.text
@start = captions.start
@loaded = true
if onTouchBasedDevice()
$('.subtitles').html "<li>Caption will be displayed when you start playing the video.</li>"
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('caption_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
type = 'hide_transcript'
@$('.hide-subtitles').attr('title', 'Turn on captions')
@el.addClass('closed')
else
type = 'show_transcript'
@$('.hide-subtitles').attr('title', 'Turn off captions')
@el.removeClass('closed')
@scrollCaption()
@video.log type,
currentTime: @player.currentTime
$.cookie('hide_captions', hide_captions, expires: 3650, path: '/')
captionHeight: ->
if @el.hasClass('fullscreen')
$(window).height() - @$('.video-controls').height()
else
@$('.video-wrapper').height()
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')
class @VideoProgressSliderAlpha extends SubviewAlpha
initialize: ->
@buildSlider() unless onTouchBasedDevice()
buildSlider: ->
@slider = @el.slider
range: 'min'
change: @onChange
slide: @onSlide
stop: @onStop
@buildHandle()
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
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('slide_seek', ui.value)
onChange: (event, ui) =>
@updateTooltip(ui.value)
onStop: (event, ui) =>
@frozen = true
$(@).trigger('slide_seek', ui.value)
setTimeout (=> @frozen = false), 200
updateTooltip: (value)->
@handle.qtip('option', 'content.text', "#{Time.format(value)}")
(function (requirejs, require, define) {
/*
"This is as true in everyday life as it is in battle: we are given one life
and the decision is ours whether to wait for circumstances to make up our
mind, or whether to act, and in acting, to live."
— Omar N. Bradley
*/
// VideoProgressSlider module.
define(
'videoalpha/display/video_progress_slider.js',
['videoalpha/display/bind.js'],
function (bind) {
// VideoProgressSlider() function - what this module "exports".
return function (state) {
state.videoProgressSlider = {};
makeFunctionsPublic(state);
renderElements(state);
bindHandlers(state);
registerCallbacks(state);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
// function makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
function makeFunctionsPublic(state) {
state.videoProgressSlider.onSlide = bind(onSlide, state);
state.videoProgressSlider.onChange = bind(onChange, state);
state.videoProgressSlider.onStop = bind(onStop, state);
state.videoProgressSlider.updateTooltip = bind(updateTooltip, state);
state.videoProgressSlider.updatePlayTime = bind(updatePlayTime, state);
}
// function renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state) {
if (!onTouchBasedDevice()) {
state.videoProgressSlider.el = state.videoControl.sliderEl;
buildSlider(state);
buildHandle(state);
}
}
// function bindHandlers(state)
//
// Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
function bindHandlers(state) {
}
// function registerCallbacks(state)
//
// Register function callbacks to be called by other modules.
function registerCallbacks(state) {
state.callbacks.videoPlayer.updatePlayTime.push(state.videoProgressSlider.updatePlayTime);
}
function buildSlider(state) {
state.videoProgressSlider.slider = state.videoProgressSlider.el.slider({
'range': 'min',
'change': state.videoProgressSlider.onChange,
'slide': state.videoProgressSlider.onSlide,
'stop': state.videoProgressSlider.onStop
});
}
function buildHandle(state) {
state.videoProgressSlider.handle = state.videoProgressSlider.el.find('.ui-slider-handle');
state.videoProgressSlider.handle.qtip({
'content': '' + Time.format(state.videoProgressSlider.slider.slider('value')),
'position': {
'my': 'bottom center',
'at': 'top center',
'container': state.videoProgressSlider.handle
},
'hide': {
'delay': 700
},
'style': {
'classes': 'ui-tooltip-slider',
'widget': true
}
});
}
// ***************************************************************
// Public functions start here.
// 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().
// ***************************************************************
function onSlide(event, ui) {
this.videoProgressSlider.frozen = true;
this.videoProgressSlider.updateTooltip(ui.value);
$.each(this.callbacks.videoProgressSlider.onSlide, function (index, value) {
// Each value is a registered callback (JavaScript function object).
value(ui.value);
});
}
function onChange(event, ui) {
this.videoProgressSlider.updateTooltip(ui.value);
}
function onStop(event, ui) {
var _this;
_this = this;
this.videoProgressSlider.frozen = true;
$.each(this.callbacks.videoProgressSlider.onStop, function (index, value) {
// Each value is a registered callback (JavaScript function object).
value(ui.value);
});
setTimeout(function() {
_this.videoProgressSlider.frozen = false;
}, 200);
}
function updateTooltip(value) {
this.videoProgressSlider.handle.qtip('option', 'content.text', '' + Time.format(value));
}
function updatePlayTime(currentTime, duration) {
if ((this.videoProgressSlider.slider) && (!this.videoProgressSlider.frozen)) {
this.videoProgressSlider.slider.slider('option', 'max', duration);
this.videoProgressSlider.slider.slider('value', currentTime);
}
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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
(function (requirejs, require, define) {
// VideoQualityControl module.
define(
'videoalpha/display/video_quality_control.js',
['videoalpha/display/bind.js'],
function (bind) {
// VideoQualityControl() function - what this module "exports".
return function (state) {
// Changing quality for now only works for YouTube videos.
if (state.videoType !== 'youtube') {
return;
}
state.videoQualityControl = {};
makeFunctionsPublic(state);
renderElements(state);
bindHandlers(state);
registerCallbacks(state);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
// function makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
function makeFunctionsPublic(state) {
state.videoQualityControl.onQualityChange = bind(onQualityChange, state);
state.videoQualityControl.toggleQuality = bind(toggleQuality, state);
}
// function renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state) {
state.videoQualityControl.el = $(
'<a href="#" class="quality_control" title="HD">HD</a>'
);
state.videoControl.secondaryControlsEl.append(state.videoQualityControl.el);
state.videoQualityControl.quality = null;
if (!onTouchBasedDevice()) {
state.videoQualityControl.el.qtip({
'position': {
'my': 'top right',
'at': 'top center'
}
});
}
}
// function bindHandlers(state)
//
// Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
function bindHandlers(state) {
state.videoQualityControl.el.on('click', state.videoQualityControl.toggleQuality);
}
// function registerCallbacks(state)
//
// Register function callbacks to be called by other modules.
function registerCallbacks(state) {
state.callbacks.videoPlayer.onPlaybackQualityChange.push(state.videoQualityControl.onQualityChange);
}
// ***************************************************************
// Public functions start here.
// 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().
// ***************************************************************
function onQualityChange(value) {
this.videoQualityControl.quality = value;
if ((value === 'hd720') || (value === 'hd1080') || (value === 'highres')) {
this.videoQualityControl.el.addClass('active');
} else {
this.videoQualityControl.el.removeClass('active');
}
}
function toggleQuality(event) {
var newQuality, _ref;
event.preventDefault();
_ref = this.videoQualityControl.quality;
if ((_ref === 'hd720') || (_ref === 'hd1080') || (_ref === 'highres')) {
newQuality = 'large';
} else {
newQuality = 'hd720';
}
$.each(this.callbacks.videoQualityControl.toggleQuality, function (index, value) {
// Each value is a registered callback (JavaScript function object).
value(newQuality);
});
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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")
(function (requirejs, require, define) {
// VideoSpeedControl module.
define(
'videoalpha/display/video_speed_control.js',
['videoalpha/display/bind.js'],
function (bind) {
// VideoSpeedControl() function - what this module "exports".
return function (state) {
state.videoSpeedControl = {};
makeFunctionsPublic(state);
renderElements(state);
bindHandlers(state);
registerCallbacks(state);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
// function makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
function makeFunctionsPublic(state) {
state.videoSpeedControl.changeVideoSpeed = bind(changeVideoSpeed, state);
state.videoSpeedControl.setSpeed = bind(setSpeed, state);
state.videoSpeedControl.reRender = bind(reRender, state);
}
// function renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state) {
state.videoSpeedControl.speeds = state.speeds;
state.videoSpeedControl.el = $(
'<div class="speeds">' +
'<a href="#">' +
'<h3>Speed</h3>' +
'<p class="active"></p>' +
'</a>' +
'<ol class="video_speeds"></ol>' +
'</div>'
);
state.videoSpeedControl.videoSpeedsEl = state.videoSpeedControl.el.find('.video_speeds');
state.videoControl.secondaryControlsEl.prepend(state.videoSpeedControl.el);
$.each(state.videoSpeedControl.speeds, function(index, speed) {
var link;
link = $('<a>').attr({
'href': '#'
}).html('' + speed + 'x');
state.videoSpeedControl.videoSpeedsEl.prepend($('<li>').attr('data-speed', speed).html(link));
});
state.videoSpeedControl.setSpeed(state.speed);
}
// function bindHandlers(state)
//
// Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
function bindHandlers(state) {
state.videoSpeedControl.videoSpeedsEl.find('a').on('click', state.videoSpeedControl.changeVideoSpeed);
if (onTouchBasedDevice()) {
state.videoSpeedControl.el.on('click', function(event) {
event.preventDefault();
$(this).toggleClass('open');
});
} else {
state.videoSpeedControl.el.on('mouseenter', function() {
$(this).addClass('open');
});
state.videoSpeedControl.el.on('mouseleave', function() {
$(this).removeClass('open');
});
state.videoSpeedControl.el.on('click', function(event) {
event.preventDefault();
$(this).removeClass('open');
});
}
}
// function registerCallbacks(state)
//
// Register function callbacks to be called by other modules.
function registerCallbacks(state) {
state.callbacks.videoPlayer.onSpeedSetChange.push(state.videoSpeedControl.reRender);
}
// ***************************************************************
// Public functions start here.
// 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().
// ***************************************************************
function setSpeed(speed) {
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.videoSpeedsEl.find("li[data-speed='" + speed + "']").addClass('active');
this.videoSpeedControl.el.find('p.active').html('' + speed + 'x');
}
function changeVideoSpeed(event) {
var _this;
event.preventDefault();
if (!$(event.target).parent().hasClass('active')) {
this.videoSpeedControl.currentSpeed = $(event.target).parent().data('speed');
this.videoSpeedControl.setSpeed(
parseFloat(this.videoSpeedControl.currentSpeed).toFixed(2).replace(/\.00$/, '.0')
);
_this = this;
$.each(this.callbacks.videoSpeedControl.changeVideoSpeed, function (index, value) {
// Each value is a registered callback (JavaScript function object).
value(_this.videoSpeedControl.currentSpeed);
});
}
}
function reRender(newSpeeds, currentSpeed) {
var _this;
this.videoSpeedControl.videoSpeedsEl.empty();
this.videoSpeedControl.videoSpeedsEl.find('li').removeClass('active');
this.videoSpeedControl.speeds = newSpeeds;
_this = this;
$.each(this.videoSpeedControl.speeds, function(index, speed) {
var link, listItem;
link = $('<a>').attr({
'href': '#'
}).html('' + speed + 'x');
listItem = $('<li>').attr('data-speed', speed).html(link);
if (speed === currentSpeed) {
listItem.addClass('active');
}
_this.videoSpeedControl.videoSpeedsEl.prepend(listItem);
});
this.videoSpeedControl.videoSpeedsEl.find('a').on('click', this.videoSpeedControl.changeVideoSpeed);
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
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
(function (requirejs, require, define) {
// VideoVolumeControl module.
define(
'videoalpha/display/video_volume_control.js',
['videoalpha/display/bind.js'],
function (bind) {
// VideoVolumeControl() function - what this module "exports".
return function (state) {
state.videoVolumeControl = {};
makeFunctionsPublic(state);
renderElements(state);
bindHandlers(state);
registerCallbacks(state);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
// function makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context.
function makeFunctionsPublic(state) {
state.videoVolumeControl.onChange = bind(onChange, state);
state.videoVolumeControl.toggleMute = bind(toggleMute, state);
}
// function renderElements(state)
//
// Create any necessary DOM elements, attach them, and set their initial configuration. Also
// make the created DOM elements available via the 'state' object. Much easier to work this
// way - you don't have to do repeated jQuery element selects.
function renderElements(state) {
state.videoVolumeControl.currentVolume = 100;
state.videoVolumeControl.el = $(
'<div class="volume">' +
'<a href="#"></a>' +
'<div class="volume-slider-container">' +
'<div class="volume-slider"></div>' +
'</div>' +
'</div>'
);
state.videoVolumeControl.buttonEl = state.videoVolumeControl.el.find('a');
state.videoVolumeControl.volumeSliderEl = state.videoVolumeControl.el.find('.volume-slider');
state.videoControl.secondaryControlsEl.prepend(state.videoVolumeControl.el);
state.videoVolumeControl.slider = state.videoVolumeControl.volumeSliderEl.slider({
'orientation': 'vertical',
'range': 'min',
'min': 0,
'max': 100,
'value': 100,
'change': state.videoVolumeControl.onChange,
'slide': state.videoVolumeControl.onChange
});
}
// function bindHandlers(state)
//
// Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
function bindHandlers(state) {
state.videoVolumeControl.buttonEl.on('click', state.videoVolumeControl.toggleMute);
state.videoVolumeControl.el.on('mouseenter', function() {
$(this).addClass('open');
});
state.videoVolumeControl.el.on('mouseleave', function() {
$(this).removeClass('open');
});
}
// function registerCallbacks(state)
//
// Register function callbacks to be called by other modules.
function registerCallbacks(state) {
}
// ***************************************************************
// Public functions start here.
// 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().
// ***************************************************************
function onChange(event, ui) {
this.videoVolumeControl.currentVolume = ui.value;
this.videoVolumeControl.el.toggleClass('muted', this.videoVolumeControl.currentVolume === 0);
$.each(this.callbacks.videoVolumeControl.onChange, function (index, value) {
// Each value is a registered callback (JavaScript function object).
value(ui.value);
});
}
function toggleMute(event) {
event.preventDefault();
if (this.videoVolumeControl.currentVolume > 0) {
this.videoVolumeControl.previousVolume = this.videoVolumeControl.currentVolume;
this.videoVolumeControl.slider.slider('option', 'value', 0);
} else {
this.videoVolumeControl.slider.slider('option', 'value', this.videoVolumeControl.previousVolume);
}
}
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
......@@ -5,8 +5,21 @@ require(
[
'videoalpha/display/initialize.js',
'videoalpha/display/video_control.js',
'videoalpha/display/video_quality_control.js',
'videoalpha/display/video_progress_slider.js',
'videoalpha/display/video_volume_control.js',
'videoalpha/display/video_speed_control.js',
'videoalpha/display/video_caption.js'
],
function (Initialize, VideoControl) {
function (
Initialize,
VideoControl,
VideoQualityControl,
VideoProgressSlider,
VideoVolumeControl,
VideoSpeedControl,
VideoCaption
) {
var previousState;
// Because this constructor can be called multiple times on a single page (when
......@@ -30,10 +43,13 @@ function (Initialize, VideoControl) {
previousState = state;
Initialize(state, element);
VideoControl(state);
console.log('state is:');
console.log(state);
VideoControl(state);
VideoQualityControl(state);
VideoProgressSlider(state);
VideoVolumeControl(state);
VideoSpeedControl(state);
VideoCaption(state);
};
});
......
......@@ -74,6 +74,11 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
resource_string(__name__, 'js/src/videoalpha/display/html5_video.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_player.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_quality_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_progress_slider.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_volume_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_speed_control.js'),
resource_string(__name__, 'js/src/videoalpha/display/video_caption.js'),
resource_string(__name__, 'js/src/videoalpha/main.js')
]
}
......@@ -88,6 +93,11 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
self.youtube_streams = xmltree.get('youtube', '')
self.sub = xmltree.get('sub')
self.autoplay = xmltree.get('autoplay') or ''
if self.autoplay.lower() not in ['true', 'false']:
self.autoplay = ''
self.position = 0
self.show_captions = xmltree.get('show_captions', 'true')
self.sources = {
......@@ -163,6 +173,7 @@ class VideoAlphaModule(VideoAlphaFields, XModule):
'youtube_streams': self.youtube_streams,
'id': self.location.html_id(),
'sub': self.sub,
'autoplay': self.autoplay,
'sources': self.sources,
'track': self.track,
'display_name': self.display_name_with_default,
......
......@@ -13,7 +13,7 @@
% endif
${'data-sub="{}"'.format(sub) if sub else ''}
${'data-autoplay="{}"'.format(sub) if sub else ''}
% if not settings.MITX_FEATURES['STUB_VIDEO_FOR_TESTING']:
${'data-mp4-source="{}"'.format(sources.get('mp4')) if sources.get('mp4') else ''}
${'data-webm-source="{}"'.format(sources.get('webm')) if sources.get('webm') else ''}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment