Commit 24b631f7 by Anton Stupak

Merge pull request #3231 from edx/anton/refactor-speed-control

Video: Refactor speed control.
parents fe4bd4cb c0110c2b
......@@ -102,7 +102,7 @@ def choose_new_lang(lang_code):
def open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('open')".format(
world.browser.execute_script("$('{selector}').parent().addClass('is-opened')".format(
selector=VIDEO_MENUS[menu]
))
......
......@@ -317,7 +317,10 @@ div.video {
div.secondary-controls {
float: right;
div.speeds>a, div.volume>a, a.add-fullscreen, a.quality_control,
a.speed-button,
div.volume > a,
a.add-fullscreen,
a.quality_control,
a.hide-subtitles {
// overflow is used to bypass Firefox CSS :focus outline bug
// http://johndoesdesign.com/blog/2012/css/firefox-and-its-css-focus-outline-bug/
......@@ -334,7 +337,7 @@ div.video {
float: left;
position: relative;
&.open {
&.is-opened {
.menu {
display: block;
opacity: 1;
......@@ -377,7 +380,7 @@ div.video {
}
}
&.active{
&.is-active{
a {
font-weight: bold;
}
......@@ -393,8 +396,8 @@ div.video {
}
div.speeds {
&.open {
& > a {
&.is-opened {
.speed-button {
background-image: url('../images/open-arrow.png');
}
}
......@@ -407,7 +410,7 @@ div.video {
}
}
& > a {
.speed-button {
@extend %video-button;
@include clearfix();
background-image: url('../images/closed-arrow.png');
......@@ -421,7 +424,7 @@ div.video {
width: 60px;
}
h3 {
.label {
float: left;
font-size: em(14);
font-weight: normal;
......@@ -436,7 +439,7 @@ div.video {
}
}
p.active {
.value {
float: left;
font-weight: bold;
margin-bottom: 0;
......
......@@ -41,11 +41,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#" title="Speeds" role="button" aria-disabled="false">
<h3>Speed</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="Speeds" role="button" aria-disabled="false">
<span class="label">Speed</span>
<span class="value"></span>
</a>
<ol class="video_speeds"></ol>
<ol class="video-speeds"></ol>
</div>
<div class="volume">
<a href="#" title="Volume" role="button" aria-disabled="false"></a>
......
......@@ -44,11 +44,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#" title="Speeds" role="button" aria-disabled="false">>
<h3>Speed</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="Speeds" role="button" aria-disabled="false">
<span class="label">Speed</span>
<span class="value"></span>
</a>
<ol class="video_speeds"></ol>
<ol class="video-speeds"></ol>
</div>
<div class="volume">
<a href="#" title="Volume" role="button" aria-disabled="false"></a>
......
......@@ -41,11 +41,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#" title="Speeds" role="button" aria-disabled="false">
<h3>Speed</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="Speeds" role="button" aria-disabled="false">
<span class="label">Speed</span>
<span class="value"></span>
</a>
<ol class="video_speeds"></ol>
<ol class="video-speeds"></ol>
</div>
<div class="volume">
<a href="#" title="Volume" role="button" aria-disabled="false"></a>
......@@ -108,11 +108,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#">
<h3>Speed</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="Speeds" role="button" aria-disabled="false">
<span class="label">Speed</span>
<span class="value"></span>
</a>
<ol class="video_speeds"></ol>
<ol class="video-speeds"></ol>
</div>
<div class="volume">
<a href="#"></a>
......@@ -173,11 +173,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds">
<a href="#">
<h3>Speed</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="Speeds" role="button" aria-disabled="false">
<span class="label">Speed</span>
<span class="value"></span>
</a>
<ol class="video_speeds"></ol>
<ol class="video-speeds"></ol>
</div>
<div class="volume">
<a href="#"></a>
......
(function (require) {
require(
['video/00_iterator.js'],
function (Iterator) {
describe('Iterator', function () {
var list = ['a', 'b', 'c', 'd', 'e'],
iterator;
beforeEach(function() {
iterator = new Iterator(list);
});
it('size contains correct list length', function () {
expect(iterator.size).toBe(list.length);
expect(iterator.lastIndex).toBe(list.length - 1);
});
describe('next', function () {
describe('with passed `index`', function () {
it('returns next item in the list', function () {
expect(iterator.next(2)).toBe('d');
expect(iterator.next(0)).toBe('b');
});
it('returns first item if index equal last item', function () {
expect(iterator.next(4)).toBe('a');
});
it('returns next item if index is not valid', function () {
expect(iterator.next(-4)).toBe('b'); // index < 0
expect(iterator.next(100)).toBe('c'); // index > size
expect(iterator.next('99')).toBe('d'); // incorrect Type
});
});
describe('without passed `index`', function () {
it('returns next item in the list', function () {
expect(iterator.next()).toBe('b');
expect(iterator.next()).toBe('c');
});
it('returns first item if index equal last item', function () {
expect(iterator.next()).toBe('b');
expect(iterator.next()).toBe('c');
expect(iterator.next()).toBe('d');
expect(iterator.next()).toBe('e');
expect(iterator.next()).toBe('a');
});
});
});
describe('prev', function () {
describe('with passed `index`', function () {
it('returns previous item in the list', function () {
expect(iterator.prev(3)).toBe('c');
expect(iterator.prev(1)).toBe('a');
});
it('returns last item if index equal first item', function () {
expect(iterator.prev(0)).toBe('e');
});
it('returns previous item if index is not valid', function () {
expect(iterator.prev(-4)).toBe('e'); // index < 0
expect(iterator.prev(100)).toBe('d'); // index > size
expect(iterator.prev('99')).toBe('c'); // incorrect Type
});
});
describe('without passed `index`', function () {
it('returns previous item in the list', function () {
expect(iterator.prev()).toBe('e');
expect(iterator.prev()).toBe('d');
});
it('returns last item if index equal first item', function () {
expect(iterator.prev()).toBe('e');
});
});
});
it('returns last item in the list', function () {
expect(iterator.last()).toBe('e');
});
it('returns first item in the list', function () {
expect(iterator.first()).toBe('a');
});
it('isEnd works correctly', function () {
expect(iterator.isEnd()).toBeFalsy();
iterator.next(); // => index 1
expect(iterator.isEnd()).toBeFalsy();
iterator.next(); // => index 2
expect(iterator.isEnd()).toBeFalsy();
iterator.next(); // => index 3
expect(iterator.isEnd()).toBeFalsy();
iterator.next(); // => index 4 == last
expect(iterator.isEnd()).toBeTruthy();
});
});
});
}(RequireJS.require));
......@@ -180,7 +180,7 @@
expect(state.lang).toBe('de');
expect(state.storage.setItem)
.toHaveBeenCalledWith('language', 'de');
expect($('.langs-list li.active').length).toBe(1);
expect($('.langs-list li.is-active').length).toBe(1);
});
it('when clicking on link with current language', function () {
......@@ -198,15 +198,15 @@
expect(state.lang).toBe('en');
expect(state.storage.setItem)
.not.toHaveBeenCalledWith('language', 'en');
expect($('.langs-list li.active').length).toBe(1);
expect($('.langs-list li.is-active').length).toBe(1);
});
it('open the language toggle on hover', function () {
state = jasmine.initializePlayer();
$('.lang').mouseenter();
expect($('.lang')).toHaveClass('open');
expect($('.lang')).toHaveClass('is-opened');
$('.lang').mouseleave();
expect($('.lang')).not.toHaveClass('open');
expect($('.lang')).not.toHaveClass('is-opened');
});
});
......
......@@ -52,8 +52,6 @@ function (VideoPlayer) {
it('create video speed control', function () {
expect(state.videoSpeedControl).toBeDefined();
expect(state.videoSpeedControl.el).toHaveClass('speeds');
expect(state.videoSpeedControl.speeds)
.toEqual([ '0.75', '1.0', '1.25', '1.50' ]);
expect(state.speed).toEqual('1.50');
});
......
(function (define) {
define(
'video/00_iterator.js',
[],
function() {
"use strict";
/**
* Provides convenient way to work with iterable data.
* @exports video/00_iterator.js
* @constructor
* @param {array} list Array to be iterated.
*/
var Iterator = function (list) {
this.list = list;
this.index = 0;
this.size = this.list.length;
this.lastIndex = this.list.length - 1;
};
Iterator.prototype = {
/**
* Checks validity of provided index for the iterator.
* @access protected
* @param {numebr} index
* @return {boolean}
*/
_isValid: function (index) {
return _.isNumber(index) && index < this.size && index >= 0;
},
/**
* Returns next element.
* @param {number} [index] Updates current position.
* @return {any}
*/
next: function (index) {
if (!(this._isValid(index))) {
index = this.index;
}
this.index = (index >= this.lastIndex) ? 0: index + 1;
return this.list[this.index];
},
/**
* Returns previous element.
* @param {number} [index] Updates current position.
* @return {any}
*/
prev: function (index) {
if (!(this._isValid(index))) {
index = this.index;
}
this.index = (index < 1) ? this.lastIndex: index - 1;
return this.list[this.index];
},
/**
* Returns last element in the list.
* @return {any}
*/
last: function () {
return this.list[this.lastIndex];
},
/**
* Returns first element in the list.
* @return {any}
*/
first: function () {
return this.list[0];
},
/**
* Returns `true` if current position is last for the iterator.
* @return {boolean}
*/
isEnd: function () {
return this.index === this.lastIndex;
}
};
return Iterator;
});
}(RequireJS.define));
......@@ -74,6 +74,7 @@ function (VideoPlayer, VideoStorage) {
saveState: saveState,
setPlayerMode: setPlayerMode,
setSpeed: setSpeed,
speedToString: speedToString,
trigger: trigger,
youtubeId: youtubeId
},
......@@ -519,9 +520,9 @@ function (VideoPlayer, VideoStorage) {
}
this.lang = this.config.transcriptLanguage;
this.speed = Number(
this.speed = this.speedToString(
this.config.speed || this.config.generalSpeed
).toFixed(2).replace(/\.00$/, '.0');
);
if (!(_parseYouTubeIDs(this))) {
......@@ -630,7 +631,7 @@ function (VideoPlayer, VideoStorage) {
var speed;
video = video.split(/:/);
speed = parseFloat(video[0]).toFixed(2).replace(/\.00$/, '.0');
speed = _this.speedToString(video[0]);
_this.videos[speed] = video[1];
});
......@@ -844,6 +845,10 @@ function (VideoPlayer, VideoStorage) {
return this.getPlayerMode() === 'html5';
}
function speedToString(speed) {
return parseFloat(speed).toFixed(2).replace(/\.00$/, '.0');
}
function getCurrentLanguage() {
var keys = _.keys(this.config.transcriptLanguages);
......
......@@ -167,6 +167,7 @@ function (HTML5Video, Resizer) {
dfd.resolve();
}
}
function _updateVcrAndRegion(state, isYoutube) {
var update = function (state) {
var duration = state.videoPlayer.duration(),
......@@ -384,8 +385,6 @@ function (HTML5Video, Resizer) {
this.setSpeed(newSpeed, true);
this.videoPlayer.setPlaybackRate(newSpeed);
this.el.trigger('speedchange', arguments);
this.saveState(true, { speed: newSpeed });
}
......@@ -522,6 +521,10 @@ function (HTML5Video, Resizer) {
dfd.resolve();
this.el.on('speedchange', function (event, speed) {
_this.videoPlayer.onSpeedChange(speed);
});
this.videoPlayer.log('load_video');
availablePlaybackRates = this.videoPlayer.player
......@@ -590,21 +593,14 @@ function (HTML5Video, Resizer) {
_this.speeds.push(key);
});
this.trigger(
'videoSpeedControl.reRender',
{
newSpeeds: this.speeds,
currentSpeed: this.speed
}
);
this.setSpeed(this.speed);
this.trigger('videoSpeedControl.setSpeed', this.speed);
this.el.trigger('speed:render', [this.speeds, this.speed]);
}
}
if (this.isFlashMode()) {
this.setSpeed(this.speed);
this.trigger('videoSpeedControl.setSpeed', this.speed);
this.el.trigger('speed:set', [this.speed]);
}
if (this.isHtml5Mode()) {
......
......@@ -62,8 +62,6 @@ function () {
previousVolume = 100,
slider, buttonStr, volumeSliderHandleEl;
state.videoControl.secondaryControlsEl.prepend(element);
if (!isFinite(currentVolume)) {
currentVolume = 100;
}
......
......@@ -138,7 +138,7 @@ function (Sjson, AsyncProcess) {
onContainerMouseEnter: function (event) {
event.preventDefault();
$(event.currentTarget).addClass('open');
$(event.currentTarget).addClass('is-opened');
},
/**
......@@ -149,7 +149,7 @@ function (Sjson, AsyncProcess) {
onContainerMouseLeave: function (event) {
event.preventDefault();
$(event.currentTarget).removeClass('open');
$(event.currentTarget).removeClass('is-opened');
},
/**
......@@ -364,7 +364,7 @@ function (Sjson, AsyncProcess) {
link = $('<a href="javascript:void(0);">' + label + '</a>');
if (currentLang === code) {
li.addClass('active');
li.addClass('is-active');
}
li.append(link);
......@@ -381,9 +381,9 @@ function (Sjson, AsyncProcess) {
if (state.lang !== langCode) {
state.lang = langCode;
state.storage.setItem('language', langCode);
el .addClass('active')
el .addClass('is-active')
.siblings('li')
.removeClass('active');
.removeClass('is-active');
self.fetchCaption();
}
......
......@@ -71,6 +71,7 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
resource_string(module, 'js/src/video/00_resizer.js'),
resource_string(module, 'js/src/video/00_async_process.js'),
resource_string(module, 'js/src/video/00_sjson.js'),
resource_string(module, 'js/src/video/00_iterator.js'),
resource_string(module, 'js/src/video/01_initialize.js'),
resource_string(module, 'js/src/video/025_focus_grabber.js'),
resource_string(module, 'js/src/video/02_html5_video.js'),
......
......@@ -218,14 +218,14 @@ def navigate_to_an_item_in_a_sequence(number):
def change_video_speed(speed):
world.browser.execute_script("$('.speeds').addClass('open')")
world.browser.execute_script("$('.speeds').addClass('is-opened')")
speed_css = 'li[data-speed="{0}"] a'.format(speed)
world.wait_for_visible('.speeds')
world.css_click(speed_css)
def open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('open')".format(
world.browser.execute_script("$('{selector}').parent().addClass('is-opened')".format(
selector=VIDEO_MENUS[menu]
))
......@@ -379,7 +379,7 @@ def open_video(_step, player_id):
@step('video "([^"]*)" should start playing at speed "([^"]*)"$')
def check_video_speed(_step, player_id, speed):
speed_css = '.speeds p.active'
speed_css = '.speeds .value'
assert world.css_has_text(speed_css, '{0}x'.format(speed))
......@@ -397,7 +397,6 @@ def video_is_rendered(_step, mode):
}
html_tag = modes[mode.lower()]
assert world.css_find('.video {0}'.format(html_tag)).first
assert world.is_css_present('.speed_link')
@step('videos have rendered in "([^"]*)" mode$')
......@@ -412,7 +411,6 @@ def videos_are_rendered(_step, mode):
actual = len(world.css_find('.video {0}'.format(html_tag)))
expected = len(world.css_find('.xmodule_VideoModule'))
assert actual == expected
assert world.is_css_present('.speed_link')
@step('all sources are correct$')
......@@ -494,8 +492,8 @@ def select_language(_step, code):
world.wait_for_present('.lang.open')
world.css_click(selector)
assert world.css_has_class(selector, 'active')
assert len(world.css_find(VIDEO_MENUS["language"] + ' li.active')) == 1
assert world.css_has_class(selector, 'is-active')
assert len(world.css_find(VIDEO_MENUS["language"] + ' li.is-active')) == 1
# Make sure that all ajax requests that affects the display of captions are finished.
# For example, request to get new translation etc.
......
......@@ -72,11 +72,11 @@
</ul>
<div class="secondary-controls">
<div class="speeds menu-container">
<a href="#" title="${_('Speeds')}" role="button" aria-disabled="false">
<h3>${_('Speed')}</h3>
<p class="active"></p>
<a class="speed-button" href="#" title="${_('Speeds')}" role="button" aria-disabled="false">
<span class="label">${_('Speed')}</span>
<span class="value"></span>
</a>
<ol class="video_speeds menu" role="menu"></ol>
<ol class="video-speeds menu" role="menu"></ol>
</div>
<div class="volume">
<a href="#" title="${_('Volume')}" role="button" aria-disabled="false"></a>
......
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