diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b7d1d7a5ce..0c636e4390 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,6 +12,16 @@ is enabled.
Studio: Added improvements to Course Creation: richer error messaging, tip
text, and fourth field for course run.
+Blades: New features for VideoAlpha player:
+1.) Controls are auto hidden after a delay of mouse inactivity - the full video
+becomes visible.
+2.) When captions (CC) button is pressed, captions stick (not auto hidden after
+a delay of mouse inactivity). The video player size does not change - the video
+is down-sized and placed in the middle of the black area.
+3.) All source code of Video Alpha 2 is written in JavaScript. It is not a basic
+conversion from CoffeeScript. The structure of the player has been changed.
+4.) A lot of additional unit tests.
+
LMS: Added user preferences (arbitrary user/key/value tuples, for which
which user/key is unique) and a REST API for reading users and
preferences. Access to the REST API is restricted by use of the
diff --git a/common/lib/xmodule/.gitignore b/common/lib/xmodule/.gitignore
deleted file mode 100644
index cc2973bc1e..0000000000
--- a/common/lib/xmodule/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-test.mp4
-test.ogv
-test.webm
-jasmine_test_runner.html
diff --git a/common/lib/xmodule/test_files/test.mp4 b/common/lib/xmodule/test_files/test.mp4
new file mode 100644
index 0000000000..81d11df5f2
Binary files /dev/null and b/common/lib/xmodule/test_files/test.mp4 differ
diff --git a/common/lib/xmodule/test_files/test.ogv b/common/lib/xmodule/test_files/test.ogv
new file mode 100644
index 0000000000..e128d82b5c
Binary files /dev/null and b/common/lib/xmodule/test_files/test.ogv differ
diff --git a/common/lib/xmodule/test_files/test.webm b/common/lib/xmodule/test_files/test.webm
new file mode 100644
index 0000000000..42e2130ca4
Binary files /dev/null and b/common/lib/xmodule/test_files/test.webm differ
diff --git a/common/lib/xmodule/xmodule/css/videoalpha/display.scss b/common/lib/xmodule/xmodule/css/videoalpha/display.scss
index fc746fa846..4db641a99a 100644
--- a/common/lib/xmodule/xmodule/css/videoalpha/display.scss
+++ b/common/lib/xmodule/xmodule/css/videoalpha/display.scss
@@ -12,6 +12,7 @@ div.videoalpha {
div.tc-wrapper {
position: relative;
+ @include clearfix;
}
article.video-wrapper {
@@ -82,7 +83,7 @@ div.videoalpha {
-moz-transition: -moz-transform 0.7s ease-in-out;
-ms-transition: -ms-transform 0.7s ease-in-out;
transition: transform 0.7s ease-in-out;
- @include transform(scaleY(0.5) translateY(50%));
+ @include transform(scaleY(0.5) translate3d(0, 50%, 0));
div.ui-widget-header {
background: #777;
@@ -244,7 +245,7 @@ div.videoalpha {
// fix for now
ol.video_speeds {
- box-shadow: inset 1px 0 0 #555, 0 3px 0 #444;
+ box-shadow: inset 1px 0 0 #555, 0 4px 0 #444;
@include transition(none);
background-color: #444;
border: 1px solid #000;
@@ -252,7 +253,7 @@ div.videoalpha {
display: none;
opacity: 0.0;
position: absolute;
- width: 133px;
+ width: 131px;
z-index: 10;
li {
@@ -454,7 +455,7 @@ div.videoalpha {
}
div.slider {
- @include transform(scaleY(1) translateY(0));
+ @include transform(scaleY(1) translate3d(0, 0, 0));
a.ui-slider-handle {
@include transform(scale(1) translate3d(-50%, -15%, 0));
@@ -509,6 +510,7 @@ div.videoalpha {
left: 0px;
right: 0px;
position: absolute;
+ z-index: 1;
}
article.video-wrapper section.video-controls div.secondary-controls a.hide-subtitles {
@@ -528,11 +530,14 @@ div.videoalpha {
ol.subtitles.html5 {
background-color: rgba(243, 243, 243, 0.8);
- height: 380px;
+ height: 100%;
position: absolute;
right: 0;
+ bottom: 0;
+ top: 0;
width: 275px;
- margin-top: 20px;
+ padding: 0 20px;
+ z-index: 0;
}
}
diff --git a/common/lib/xmodule/xmodule/js/fixtures/videoalpha_all.html b/common/lib/xmodule/xmodule/js/fixtures/videoalpha_all.html
index 1dd7e7c44e..f26cbde151 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/videoalpha_all.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/videoalpha_all.html
@@ -9,9 +9,9 @@
data-end=""
data-caption-asset-path="/static/subs/"
data-sub="test_name_of_the_subtitles"
- data-mp4-source="test.mp4"
- data-webm-source="test.webm"
- data-ogg-source="test.ogv"
+ data-mp4-source="test_files/test.mp4"
+ data-webm-source="test_files/test.webm"
+ data-ogg-source="test_files/test.ogv"
data-autoplay="False"
>
diff --git a/common/lib/xmodule/xmodule/js/fixtures/videoalpha_html5.html b/common/lib/xmodule/xmodule/js/fixtures/videoalpha_html5.html
index 0663541bac..f44e0ca66e 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/videoalpha_html5.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/videoalpha_html5.html
@@ -9,9 +9,9 @@
data-end=""
data-caption-asset-path="/static/subs/"
data-sub="test_name_of_the_subtitles"
- data-mp4-source="test.mp4"
- data-webm-source="test.webm"
- data-ogg-source="test.ogv"
+ data-mp4-source="test_files/test.mp4"
+ data-webm-source="test_files/test.webm"
+ data-ogg-source="test_files/test.ogv"
data-autoplay="False"
>
diff --git a/common/lib/xmodule/xmodule/js/spec/videoalpha/general_spec.js b/common/lib/xmodule/xmodule/js/spec/videoalpha/general_spec.js
index 00f47dacaf..353480268a 100644
--- a/common/lib/xmodule/xmodule/js/spec/videoalpha/general_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/videoalpha/general_spec.js
@@ -117,9 +117,9 @@
it('parse Html5 sources', function () {
var html5Sources = {
- mp4: 'test.mp4',
- webm: 'test.webm',
- ogg: 'test.ogv'
+ mp4: 'test_files/test.mp4',
+ webm: 'test_files/test.webm',
+ ogg: 'test_files/test.ogv'
};
expect(state.html5Sources).toEqual(html5Sources);
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js b/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js
index 19a7de4cc8..a1222d084d 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/01_initialize.js
@@ -134,8 +134,8 @@ function (VideoPlayer) {
) {
VideoPlayer(state);
} else {
- onPlayerReadyFunc = (this.videoType === 'youtube') ? 'onYouTubePlayerAPIReady' : 'onHTML5PlayerAPIReady';
- window[onPlayerReadyFunc] = _.bind(window.VideoPlayer, state);
+ onPlayerReadyFunc = (state.videoType === 'youtube') ? 'onYouTubePlayerAPIReady' : 'onHTML5PlayerAPIReady';
+ window[onPlayerReadyFunc] = _.bind(VideoPlayer, window, state);
}
}
@@ -341,70 +341,30 @@ function (VideoPlayer) {
return this.metadata[this.youtubeId()].duration;
}
- /* The function .trigger() expects the parameter @callType one of
- *
- * 'event'
- * 'method'
- *
- * The default value (if @callType and @eventName are not specified) is 'method'. Based on this parameter, this
- * function can be used in two ways.
- *
- *
- *
- * First use: A safe way to trigger jQuery events.
- * -----------------------------------------------
- *
- * @callType === 'event'
- *
- * 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 is a string the name of the event to trigger on the specified object.
- *
- * @extraParameters is an object or an array that should be passed to the triggered method.
- *
- *
- * Second use: A safe way to call methods.
- * ---------------------------------------
- *
- * @callType === 'method'
- *
- * Parameter @eventName is NOT necessary.
- *
+ /*
* The trigger() function will assume that the @objChain is a complete chain with a method
* (function) at the end. It will call this function. So for example, when trigger() is
* called like so:
*
- * state.trigger(['videoPlayer', 'pause'], {'param1': 10}, 'method');
+ * state.trigger('videoPlayer.pause', {'param1': 10});
*
* Then trigger() will execute:
*
* state.videoPlayer.pause({'param1': 10});
*/
- function trigger(objChain, extraParameters, callType, eventName) {
- var i, tmpObj;
+ function trigger(objChain, extraParameters) {
+ var i, tmpObj, chain;
// Remember that 'this' is the 'state' object.
tmpObj = this;
+ chain = objChain.split('.');
// At the end of the loop the variable 'tmpObj' will either be the correct
- // object/function to trigger/invoke. If the 'objChain' chain of object is
+ // object/function to trigger/invoke. If the 'chain' chain of object is
// incorrect (one of the link is non-existent), then the loop will immediately
// exit.
- while (objChain.length) {
- i = objChain.shift();
+ while (chain.length) {
+ i = chain.shift();
if (tmpObj.hasOwnProperty(i)) {
tmpObj = tmpObj[i];
@@ -415,18 +375,7 @@ function (VideoPlayer) {
}
}
- if ((typeof callType === 'undefined') && (typeof eventName === 'undefined')) {
- callType = 'method';
- }
-
- // Based on the type, either trigger, or invoke.
- if (callType === 'event') {
- tmpObj.trigger(eventName, extraParameters);
- } else if (callType === 'method') {
- tmpObj(extraParameters);
- } else {
- return false;
- }
+ tmpObj(extraParameters);
return true;
}
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js b/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js
index ed76065efb..824b199c8f 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/02_html5_video.js
@@ -218,6 +218,7 @@ function () {
// 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.video.load();
this.playerState = HTML5Video.PlayerState.UNSTARTED;
// this.callStateChangeCallback();
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js b/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js
index fbbdb20e58..1b9a14cf73 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/03_video_player.js
@@ -225,7 +225,7 @@ function (HTML5Video) {
}
}
- // Every 200 ms, if the video is playing, we call the function update, via
+ // Every 200 ms, if the video is playing, we call the function update, via
// clearInterval. This interval is called updateInterval.
// It is created on a onPlay event. Cleared on a onPause event.
// Reinitialized on a onSeek event.
@@ -252,10 +252,10 @@ function (HTML5Video) {
}
function onEnded() {
- this.trigger(['videoControl','pause'], null);
+ this.trigger('videoControl.pause', null);
if (this.config.show_captions) {
- this.trigger(['videoCaption','pause'], null);
+ this.trigger('videoCaption.pause', null);
}
}
@@ -270,10 +270,10 @@ function (HTML5Video) {
clearInterval(this.videoPlayer.updateInterval);
delete this.videoPlayer.updateInterval;
- this.trigger(['videoControl','pause'], null);
+ this.trigger('videoControl.pause', null);
if (this.config.show_captions) {
- this.trigger(['videoCaption','pause'], null);
+ this.trigger('videoCaption.pause', null);
}
}
@@ -289,10 +289,10 @@ function (HTML5Video) {
this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
}
- this.trigger(['videoControl','play'], null);
+ this.trigger('videoControl.play', null);
if (this.config.show_captions) {
- this.trigger(['videoCaption','play'], null);
+ this.trigger('videoCaption.play', null);
}
}
@@ -307,7 +307,7 @@ function (HTML5Video) {
quality = this.videoPlayer.player.getPlaybackQuality();
- this.trigger(['videoQualityControl', 'onQualityChange'], quality);
+ this.trigger('videoQualityControl.onQualityChange', quality);
}
function onReady() {
@@ -327,7 +327,7 @@ function (HTML5Video) {
baseSpeedSubs = this.videos['1.0'];
_this = this;
- // this.videos is a dictionary containing various frame rates
+ // this.videos is a dictionary containing various frame rates
// and their associated subs.
// First clear the dictionary.
@@ -342,7 +342,7 @@ function (HTML5Video) {
_this.speeds.push(value.toFixed(2).replace(/\.00$/, '.0'));
});
- this.trigger(['videoSpeedControl', 'reRender'], {'newSpeeds': this.speeds, 'currentSpeed': this.speed});
+ this.trigger('videoSpeedControl.reRender', {'newSpeeds': this.speeds, 'currentSpeed': this.speed});
this.setSpeed($.cookie('video_speed'));
}
@@ -379,9 +379,9 @@ function (HTML5Video) {
duration = this.videoPlayer.duration();
- this.trigger(['videoProgressSlider', 'updatePlayTime'], {'time': time, 'duration': duration});
- this.trigger(['videoControl', 'updateVcrVidTime'], {'time': time, 'duration': duration});
- this.trigger(['videoCaption', 'updatePlayTime'], time);
+ this.trigger('videoProgressSlider.updatePlayTime', {'time': time, 'duration': duration});
+ this.trigger('videoControl.updateVcrVidTime', {'time': time, 'duration': duration});
+ this.trigger('videoCaption.updatePlayTime', time);
}
function isPlaying() {
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js
index fe72d55b6d..85e5c27131 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/04_video_control.js
@@ -146,9 +146,9 @@ function () {
event.preventDefault();
if (this.videoControl.isPlaying) {
- this.trigger(['videoPlayer', 'pause'], null);
+ this.trigger('videoPlayer.pause', null);
} else {
- this.trigger(['videoPlayer', 'play'], null);
+ this.trigger('videoPlayer.play', null);
}
}
@@ -167,7 +167,7 @@ function () {
this.videoControl.fullScreenEl.attr('title', 'Exit fullscreen');
}
- this.trigger(['videoCaption', 'resize'], null);
+ this.trigger('videoCaption.resize', null);
}
function exitFullScreen(event) {
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js
index 6ea48cea9f..597531b01a 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/05_video_quality_control.js
@@ -90,7 +90,7 @@ function () {
newQuality = 'hd720';
}
- this.trigger(['videoPlayer', 'handlePlaybackQualityChange'], newQuality);
+ this.trigger('videoPlayer.handlePlaybackQualityChange', newQuality);
}
});
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js
index 219d5b73a5..e2d9f8dd1f 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/06_video_progress_slider.js
@@ -19,7 +19,7 @@ function () {
makeFunctionsPublic(state);
renderElements(state);
- bindHandlers(state);
+ // No callbacks to DOM events (click, mousemove, etc.).
};
// ***************************************************************
@@ -54,13 +54,6 @@ function () {
}
}
- // function bindHandlers(state)
- //
- // Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
- function bindHandlers(state) {
-
- }
-
function buildSlider(state) {
state.videoProgressSlider.slider = state.videoProgressSlider.el.slider({
range: 'min',
@@ -100,7 +93,7 @@ function () {
this.videoProgressSlider.frozen = true;
this.videoProgressSlider.updateTooltip(ui.value);
- this.trigger(['videoPlayer', 'onSlideSeek'], {'type': 'onSlideSeek', 'time': ui.value});
+ this.trigger('videoPlayer.onSlideSeek', {'type': 'onSlideSeek', 'time': ui.value});
}
function onChange(event, ui) {
@@ -112,7 +105,7 @@ function () {
this.videoProgressSlider.frozen = true;
- this.trigger(['videoPlayer', 'onSlideSeek'], {'type': 'onSlideSeek', 'time': ui.value});
+ this.trigger('videoPlayer.onSlideSeek', {'type': 'onSlideSeek', 'time': ui.value});
setTimeout(function() {
_this.videoProgressSlider.frozen = false;
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js
index 64c80b8324..5e8663265f 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/07_video_volume_control.js
@@ -94,7 +94,7 @@ function () {
path: '/'
});
- this.trigger(['videoPlayer', 'onVolumeChange'], ui.value);
+ this.trigger('videoPlayer.onVolumeChange', ui.value);
}
function toggleMute(event) {
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js b/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js
index a6fc90d2d0..3185ac1bab 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/08_video_speed_control.js
@@ -105,7 +105,7 @@ function () {
parseFloat(this.videoSpeedControl.currentSpeed).toFixed(2).replace(/\.00$/, '.0')
);
- this.trigger(['videoPlayer', 'onSpeedChange'], this.videoSpeedControl.currentSpeed);
+ this.trigger('videoPlayer.onSpeedChange', this.videoSpeedControl.currentSpeed);
}
}
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js
index dc0dc440d1..bfe6023eb3 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/09_video_caption.js
@@ -65,7 +65,7 @@ function () {
this.el.find('.video-controls .secondary-controls').append(this.videoCaption.hideSubtitlesEl);
this.el.find('.subtitles').css({
- maxHeight: this.el.find('.video-wrapper').height() - 5
+ maxHeight: this.el.find('.video-wrapper').height()
});
this.videoCaption.fetchCaption();
@@ -329,7 +329,7 @@ function () {
event.preventDefault();
time = Math.round(Time.convert($(event.target).data('start'), '1.0', this.speed) / 1000);
- this.trigger(['videoPlayer', 'onCaptionSeek'], {'type': 'onCaptionSeek', 'time': time});
+ this.trigger('videoPlayer.onCaptionSeek', {'type': 'onCaptionSeek', 'time': time});
}
function calculateOffset(element) {