Commit 92cd4627 by polesye

BLD-950: Update captions logic on front-end.

parent 52ca4226
...@@ -201,7 +201,9 @@ def upload_file(_step, file_name): ...@@ -201,7 +201,9 @@ def upload_file(_step, file_name):
@step('I see "([^"]*)" text in the captions') @step('I see "([^"]*)" text in the captions')
def check_text_in_the_captions(_step, text): def check_text_in_the_captions(_step, text):
assert world.browser.is_text_present(text.strip(), 5) world.wait_for(lambda _: world.css_text('.subtitles'))
actual_text = world.css_text('.subtitles')
assert (text in actual_text)
@step('I see value "([^"]*)" in the field "([^"]*)"$') @step('I see value "([^"]*)" in the field "([^"]*)"$')
......
(function (require) {
require(
['video/00_async_process.js'],
function (AsyncProcess) {
var getArrayNthLength = function (n, multiplier) {
var result = [],
mul = multiplier || 1;
for (var i = 0; i < n; i++) {
result[i] = i * mul;
}
return result;
},
items = getArrayNthLength(1000);
describe('AsyncProcess', function () {
it ('Array is processed successfully', function () {
var processedArray,
expectedArray = getArrayNthLength(1000, 2),
process = function (item) {
return 2 * item;
};
runs(function () {
AsyncProcess.array(items, process).done(function (result) {
processedArray = result;
});
});
waitsFor(function () {
return processedArray;
}, 'Array processing takes too much time', WAIT_TIMEOUT);
runs(function () {
expect(processedArray).toEqual(expectedArray);
});
});
it ('If non-array is passed, error callback is called', function () {
var isError,
process = function () {};
runs(function () {
AsyncProcess.array('string', process).fail(function () {
isError = true;
});
});
waitsFor(function () {
return isError;
}, 'Error callback wasn\'t called', WAIT_TIMEOUT);
runs(function () {
expect(isError).toBeTruthy();
});
});
it ('If an empty array is passed, returns initial array', function () {
var processedArray,
process = function () {};
runs(function () {
AsyncProcess.array([], process).done(function (result) {
processedArray = result;
});
});
waitsFor(function () {
return processedArray;
}, 'Array processing takes too much time', WAIT_TIMEOUT);
runs(function () {
expect(processedArray).toEqual([]);
});
});
it ('If no process function passed, returns initial array', function () {
var processedArray;
runs(function () {
AsyncProcess.array(items).done(function (result) {
processedArray = result;
});
});
waitsFor(function () {
return processedArray;
}, 'Array processing takes too much time', WAIT_TIMEOUT);
runs(function () {
expect(processedArray).toEqual(items);
});
});
});
});
}(RequireJS.require));
(function (require) {
require(
['video/00_sjson.js'],
function (Sjson) {
describe('Sjson', function () {
var data = jasmine.stubbedCaption,
sjson;
beforeEach(function() {
sjson = new Sjson(data);
});
it ('returns captions', function () {
expect(sjson.getCaptions()).toEqual(data.text);
});
it ('returns start times', function () {
expect(sjson.getStartTimes()).toEqual(data.start);
});
it ('returns correct length', function () {
expect(sjson.getSize()).toEqual(data.text.length);
});
it('search returns a correct caption index', function () {
expect(sjson.search(0)).toEqual(-1);
expect(sjson.search(3120)).toEqual(1);
expect(sjson.search(6270)).toEqual(2);
expect(sjson.search(8490)).toEqual(2);
expect(sjson.search(21620)).toEqual(4);
expect(sjson.search(24920)).toEqual(5);
});
});
});
}(RequireJS.require));
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
}); });
expect($.ajaxWithPrefix.mostRecentCall.args[0].data) expect($.ajaxWithPrefix.mostRecentCall.args[0].data)
.toEqual({ .toEqual({
videoId: 'abcdefghijkl' videoId: 'cogebirgzzM'
}); });
}); });
}); });
...@@ -237,73 +237,94 @@ ...@@ -237,73 +237,94 @@
describe('when on a non touch-based device', function () { describe('when on a non touch-based device', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}); });
it('render the caption', function () { it('render the caption', function () {
var captionsData; runs(function () {
var captionsData;
captionsData = jasmine.stubbedCaption;
$('.subtitles li[data-index]').each( captionsData = jasmine.stubbedCaption;
function (index, link) { $('.subtitles li[data-index]').each(
function (index, link) {
expect($(link)).toHaveData('index', index);
expect($(link)).toHaveData( expect($(link)).toHaveData('index', index);
'start', captionsData.start[index] expect($(link)).toHaveData(
); 'start', captionsData.start[index]
expect($(link)).toHaveAttr('tabindex', 0); );
expect($(link)).toHaveText(captionsData.text[index]); expect($(link)).toHaveAttr('tabindex', 0);
expect($(link)).toHaveText(captionsData.text[index]);
});
}); });
}); });
it('add a padding element to caption', function () { it('add a padding element to caption', function () {
expect($('.subtitles li:first').hasClass('spacing')) runs(function () {
.toBe(true); expect($('.subtitles li:first').hasClass('spacing'))
expect($('.subtitles li:last').hasClass('spacing')) .toBe(true);
.toBe(true); expect($('.subtitles li:last').hasClass('spacing'))
.toBe(true);
});
}); });
it('bind all the caption link', function () { it('bind all the caption link', function () {
var handlerList = ['captionMouseOverOut', 'captionClick', runs(function () {
'captionMouseDown', 'captionFocus', 'captionBlur', var handlerList = ['captionMouseOverOut', 'captionClick',
'captionKeyDown' 'captionMouseDown', 'captionFocus', 'captionBlur',
]; 'captionKeyDown'
];
$.each(handlerList, function(index, handler) { $.each(handlerList, function(index, handler) {
spyOn(state.videoCaption, handler); spyOn(state.videoCaption, handler);
}); });
$('.subtitles li[data-index]').each( $('.subtitles li[data-index]').each(
function (index, link) { function (index, link) {
$(link).trigger('mouseover'); $(link).trigger('mouseover');
expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled();
state.videoCaption.captionMouseOverOut.reset(); state.videoCaption.captionMouseOverOut.reset();
$(link).trigger('mouseout'); $(link).trigger('mouseout');
expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled(); expect(state.videoCaption.captionMouseOverOut).toHaveBeenCalled();
$(this).click(); $(this).click();
expect(state.videoCaption.captionClick).toHaveBeenCalled(); expect(state.videoCaption.captionClick).toHaveBeenCalled();
$(this).trigger('mousedown'); $(this).trigger('mousedown');
expect(state.videoCaption.captionMouseDown).toHaveBeenCalled(); expect(state.videoCaption.captionMouseDown).toHaveBeenCalled();
$(this).trigger('focus'); $(this).trigger('focus');
expect(state.videoCaption.captionFocus).toHaveBeenCalled(); expect(state.videoCaption.captionFocus).toHaveBeenCalled();
$(this).trigger('blur'); $(this).trigger('blur');
expect(state.videoCaption.captionBlur).toHaveBeenCalled(); expect(state.videoCaption.captionBlur).toHaveBeenCalled();
$(this).trigger('keydown'); $(this).trigger('keydown');
expect(state.videoCaption.captionKeyDown).toHaveBeenCalled(); expect(state.videoCaption.captionKeyDown).toHaveBeenCalled();
});
}); });
}); });
it('set rendered to true', function () { it('set rendered to true', function () {
state = jasmine.initializePlayer(); runs(function () {
expect(state.videoCaption.rendered).toBeTruthy(); state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
runs(function () {
expect(state.videoCaption.rendered).toBeTruthy();
});
}); });
}); });
...@@ -345,49 +366,58 @@ ...@@ -345,49 +366,58 @@
beforeEach(function () { beforeEach(function () {
jasmine.Clock.useMock(); jasmine.Clock.useMock();
spyOn(window, 'clearTimeout'); spyOn(window, 'clearTimeout');
state = jasmine.initializePlayer();
});
describe('when cursor is outside of the caption box', function () { runs(function () {
beforeEach(function () { state = jasmine.initializePlayer();
$(window).trigger(jQuery.Event('mousemove')); jasmine.Clock.tick(50);
jasmine.Clock.tick(state.config.captionsFreezeTime);
}); });
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
});
describe('when cursor is outside of the caption box', function () {
it('does not set freezing timeout', function () { it('does not set freezing timeout', function () {
expect(state.videoCaption.frozen).toBeFalsy(); runs(function () {
expect(state.videoCaption.frozen).toBeFalsy();
});
}); });
}); });
describe('when cursor is in the caption box', function () { describe('when cursor is in the caption box', function () {
beforeEach(function () { beforeEach(function () {
spyOn(state.videoCaption, 'onMouseLeave'); spyOn(state.videoCaption, 'onMouseLeave');
$('.subtitles').trigger(jQuery.Event('mouseenter')); runs(function () {
jasmine.Clock.tick(state.config.captionsFreezeTime); $(window).trigger(jQuery.Event('mousemove'));
jasmine.Clock.tick(state.config.captionsFreezeTime);
$('.subtitles').trigger(jQuery.Event('mouseenter'));
jasmine.Clock.tick(state.config.captionsFreezeTime);
});
}); });
it('set the freezing timeout', function () { it('set the freezing timeout', function () {
expect(state.videoCaption.frozen).not.toBeFalsy(); runs(function () {
expect(state.videoCaption.onMouseLeave).toHaveBeenCalled(); expect(state.videoCaption.frozen).not.toBeFalsy();
expect(state.videoCaption.onMouseLeave).toHaveBeenCalled();
});
}); });
describe('when the cursor is moving', function () { describe('when the cursor is moving', function () {
beforeEach(function () {
$('.subtitles').trigger(jQuery.Event('mousemove'));
});
it('reset the freezing timeout', function () { it('reset the freezing timeout', function () {
expect(window.clearTimeout).toHaveBeenCalled(); runs(function () {
$('.subtitles').trigger(jQuery.Event('mousemove'));
expect(window.clearTimeout).toHaveBeenCalled();
});
}); });
}); });
describe('when the mouse is scrolling', function () { describe('when the mouse is scrolling', function () {
beforeEach(function () {
$('.subtitles').trigger(jQuery.Event('mousewheel'));
});
it('reset the freezing timeout', function () { it('reset the freezing timeout', function () {
expect(window.clearTimeout).toHaveBeenCalled(); runs(function () {
$('.subtitles').trigger(jQuery.Event('mousewheel'));
expect(window.clearTimeout).toHaveBeenCalled();
});
}); });
}); });
}); });
...@@ -441,25 +471,6 @@ ...@@ -441,25 +471,6 @@
}); });
}); });
it('reRenderCaption', function () {
state = jasmine.initializePlayer();
var Caption = state.videoCaption,
li;
Caption.captions = ['test'];
Caption.start = [500];
spyOn(Caption, 'addPaddings');
Caption.reRenderCaption();
li = $('ol.subtitles li');
expect(Caption.addPaddings).toHaveBeenCalled();
expect(li.length).toBe(1);
expect(li).toHaveData('start', '500');
});
describe('fetchCaption', function () { describe('fetchCaption', function () {
var Caption, msg; var Caption, msg;
...@@ -467,7 +478,6 @@ ...@@ -467,7 +478,6 @@
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
Caption = state.videoCaption; Caption = state.videoCaption;
spyOn($, 'ajaxWithPrefix').andCallThrough(); spyOn($, 'ajaxWithPrefix').andCallThrough();
spyOn(Caption, 'reRenderCaption');
spyOn(Caption, 'renderCaption'); spyOn(Caption, 'renderCaption');
spyOn(Caption, 'bindHandlers'); spyOn(Caption, 'bindHandlers');
spyOn(Caption, 'updatePlayTime'); spyOn(Caption, 'updatePlayTime');
...@@ -511,7 +521,6 @@ ...@@ -511,7 +521,6 @@
expect(Caption.bindHandlers).toHaveBeenCalled(); expect(Caption.bindHandlers).toHaveBeenCalled();
expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.renderCaption).not.toHaveBeenCalled();
expect(Caption.updatePlayTime).not.toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled();
expect(Caption.reRenderCaption).not.toHaveBeenCalled();
expect(Caption.loaded).toBeTruthy(); expect(Caption.loaded).toBeTruthy();
}); });
...@@ -527,7 +536,6 @@ ...@@ -527,7 +536,6 @@
expect(Caption.bindHandlers).not.toHaveBeenCalled(); expect(Caption.bindHandlers).not.toHaveBeenCalled();
expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.renderCaption).not.toHaveBeenCalled();
expect(Caption.updatePlayTime).not.toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled();
expect(Caption.reRenderCaption).not.toHaveBeenCalled();
expect(Caption.loaded).toBeTruthy(); expect(Caption.loaded).toBeTruthy();
}); });
...@@ -539,9 +547,8 @@ ...@@ -539,9 +547,8 @@
expect($.ajaxWithPrefix).toHaveBeenCalled(); expect($.ajaxWithPrefix).toHaveBeenCalled();
expect(Caption.bindHandlers).not.toHaveBeenCalled(); expect(Caption.bindHandlers).not.toHaveBeenCalled();
expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.renderCaption).toHaveBeenCalled();
expect(Caption.updatePlayTime).toHaveBeenCalled(); expect(Caption.updatePlayTime).toHaveBeenCalled();
expect(Caption.reRenderCaption).toHaveBeenCalled();
expect(Caption.loaded).toBeTruthy(); expect(Caption.loaded).toBeTruthy();
}); });
...@@ -553,19 +560,18 @@ ...@@ -553,19 +560,18 @@
expect(Caption.bindHandlers).toHaveBeenCalled(); expect(Caption.bindHandlers).toHaveBeenCalled();
expect(Caption.renderCaption).toHaveBeenCalled(); expect(Caption.renderCaption).toHaveBeenCalled();
expect(Caption.updatePlayTime).not.toHaveBeenCalled(); expect(Caption.updatePlayTime).not.toHaveBeenCalled();
expect(Caption.reRenderCaption).not.toHaveBeenCalled();
expect(Caption.loaded).toBeTruthy(); expect(Caption.loaded).toBeTruthy();
}); });
it('on success: re-rendered correct', function () { it('on success: re-rendered correct', function () {
Caption.loaded = true; Caption.loaded = true;
Caption.rendered = true;
Caption.fetchCaption(); Caption.fetchCaption();
expect($.ajaxWithPrefix).toHaveBeenCalled(); expect($.ajaxWithPrefix).toHaveBeenCalled();
expect(Caption.bindHandlers).not.toHaveBeenCalled(); expect(Caption.bindHandlers).not.toHaveBeenCalled();
expect(Caption.renderCaption).not.toHaveBeenCalled(); expect(Caption.renderCaption).toHaveBeenCalled();
expect(Caption.updatePlayTime).toHaveBeenCalled(); expect(Caption.updatePlayTime).toHaveBeenCalled();
expect(Caption.reRenderCaption).toHaveBeenCalled();
expect(Caption.loaded).toBeTruthy(); expect(Caption.loaded).toBeTruthy();
}); });
...@@ -680,53 +686,57 @@ ...@@ -680,53 +686,57 @@
}); });
}); });
describe('search', function () {
it('return a correct caption index', function () {
state = jasmine.initializePlayer();
expect(state.videoCaption.search(0)).toEqual(-1);
expect(state.videoCaption.search(3120)).toEqual(1);
expect(state.videoCaption.search(6270)).toEqual(2);
expect(state.videoCaption.search(8490)).toEqual(2);
expect(state.videoCaption.search(21620)).toEqual(4);
expect(state.videoCaption.search(24920)).toEqual(5);
});
});
describe('play', function () { describe('play', function () {
describe('when the caption was not rendered', function () { describe('when the caption was not rendered', function () {
beforeEach(function () { beforeEach(function () {
window.onTouchBasedDevice.andReturn(['iPad']); window.onTouchBasedDevice.andReturn(['iPad']);
state = jasmine.initializePlayer();
state.videoCaption.play();
});
it('render the caption', function () { runs(function () {
var captionsData; state = jasmine.initializePlayer();
state.videoCaption.play();
});
captionsData = jasmine.stubbedCaption; waitsFor(function () {
$('.subtitles li[data-index]').each( return state.videoCaption.rendered;
function (index, link) { }, 'Captions are not rendered', WAIT_TIMEOUT);
});
expect($(link)).toHaveData('index', index); it('render the caption', function () {
expect($(link)).toHaveData( runs(function () {
'start', captionsData.start[index] var captionsData;
);
expect($(link)).toHaveAttr('tabindex', 0); captionsData = jasmine.stubbedCaption;
expect($(link)).toHaveText(captionsData.text[index]); $('.subtitles li[data-index]').each(
function (index, link) {
expect($(link)).toHaveData('index', index);
expect($(link)).toHaveData(
'start', captionsData.start[index]
);
expect($(link)).toHaveAttr('tabindex', 0);
expect($(link)).toHaveText(captionsData.text[index]);
});
}); });
}); });
it('add a padding element to caption', function () { it('add a padding element to caption', function () {
expect($('.subtitles li:first')).toBe('.spacing'); runs(function () {
expect($('.subtitles li:last')).toBe('.spacing'); expect($('.subtitles li:first')).toBe('.spacing');
expect($('.subtitles li:last')).toBe('.spacing');
});
}); });
it('set rendered to true', function () { it('set rendered to true', function () {
expect(state.videoCaption.rendered).toBeTruthy(); runs(function () {
expect(state.videoCaption.rendered).toBeTruthy();
});
}); });
it('set playing to true', function () { it('set playing to true', function () {
expect(state.videoCaption.playing).toBeTruthy(); runs(function () {
expect(state.videoCaption.playing).toBeTruthy();
});
}); });
}); });
}); });
...@@ -745,219 +755,269 @@ ...@@ -745,219 +755,269 @@
describe('updatePlayTime', function () { describe('updatePlayTime', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}); });
describe('when the video speed is 1.0x', function () { describe('when the video speed is 1.0x', function () {
beforeEach(function () {
state.videoSpeedControl.currentSpeed = '1.0';
state.videoCaption.updatePlayTime(25.000);
});
it('search the caption based on time', function () { it('search the caption based on time', function () {
expect(state.videoCaption.currentIndex).toEqual(5); runs(function () {
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(5);
// Flash mode
spyOn(state, 'isFlashMode').andReturn(true);
state.speed = '1.0';
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(5);
});
}); });
}); });
describe('when the video speed is not 1.0x', function () { describe('when the video speed is not 1.0x', function () {
beforeEach(function () {
state.videoSpeedControl.currentSpeed = '0.75';
state.videoCaption.updatePlayTime(25.000);
});
it('search the caption based on 1.0x speed', function () { it('search the caption based on 1.0x speed', function () {
expect(state.videoCaption.currentIndex).toEqual(5); runs(function () {
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(5);
// Flash mode
state.speed = '2.0';
spyOn(state, 'isFlashMode').andReturn(true);
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(9);
state.speed = '0.75';
state.videoCaption.updatePlayTime(25.000);
expect(state.videoCaption.currentIndex).toEqual(3);
});
}); });
}); });
describe('when the index is not the same', function () { describe('when the index is not the same', function () {
beforeEach(function () { beforeEach(function () {
state.videoCaption.currentIndex = 1; runs(function () {
$('.subtitles li[data-index=5]').addClass('current'); state.videoCaption.currentIndex = 1;
state.videoCaption.updatePlayTime(25.000); $('.subtitles li[data-index=5]').addClass('current');
state.videoCaption.updatePlayTime(25.000);
});
}); });
it('deactivate the previous caption', function () { it('deactivate the previous caption', function () {
expect($('.subtitles li[data-index=1]')) runs(function () {
.not.toHaveClass('current'); expect($('.subtitles li[data-index=1]'))
.not.toHaveClass('current');
});
}); });
it('activate new caption', function () { it('activate new caption', function () {
expect($('.subtitles li[data-index=5]')) runs(function () {
.toHaveClass('current'); expect($('.subtitles li[data-index=5]'))
.toHaveClass('current');
});
}); });
it('save new index', function () { it('save new index', function () {
expect(state.videoCaption.currentIndex).toEqual(5); runs(function () {
expect(state.videoCaption.currentIndex).toEqual(5);
});
}); });
// Disabled 11/25/13 due to flakiness in master it('scroll caption to new position', function () {
xit('scroll caption to new position', function () { runs(function () {
expect($.fn.scrollTo).toHaveBeenCalled(); expect($.fn.scrollTo).toHaveBeenCalled();
});
}); });
}); });
describe('when the index is the same', function () { describe('when the index is the same', function () {
beforeEach(function () {
state.videoCaption.currentIndex = 1;
$('.subtitles li[data-index=3]').addClass('current');
state.videoCaption.updatePlayTime(15.000);
});
it('does not change current subtitle', function () { it('does not change current subtitle', function () {
expect($('.subtitles li[data-index=3]')) runs(function () {
.toHaveClass('current'); state.videoCaption.currentIndex = 1;
$('.subtitles li[data-index=3]').addClass('current');
state.videoCaption.updatePlayTime(15.000);
expect($('.subtitles li[data-index=3]'))
.toHaveClass('current');
});
}); });
}); });
}); });
describe('resize', function () { describe('resize', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
videoControl = state.videoControl; state = jasmine.initializePlayer();
$('.subtitles li[data-index=1]').addClass('current'); });
state.videoCaption.resize();
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
runs(function () {
videoControl = state.videoControl;
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.resize();
});
}); });
describe('set the height of caption container', function () { describe('set the height of caption container', function () {
it('when CC button is enabled', function () { it('when CC button is enabled', function () {
var realHeight = parseInt( runs(function () {
$('.subtitles').css('maxHeight'), 10 var realHeight = parseInt(
), $('.subtitles').css('maxHeight'), 10
shouldBeHeight = $('.video-wrapper').height(); ),
shouldBeHeight = $('.video-wrapper').height();
// Because of some problems with rounding on different
// environments: Linux * Mac * FF * Chrome // Because of some problems with rounding on different
expect(realHeight).toBeCloseTo(shouldBeHeight, 2); // environments: Linux * Mac * FF * Chrome
expect(realHeight).toBeCloseTo(shouldBeHeight, 2);
});
}); });
it('when CC button is disabled ', function () { it('when CC button is disabled ', function () {
var realHeight, videoWrapperHeight, progressSliderHeight, runs(function () {
controlHeight, shouldBeHeight; var realHeight, videoWrapperHeight, progressSliderHeight,
controlHeight, shouldBeHeight;
state.captionsHidden = true;
state.videoCaption.setSubtitlesHeight();
realHeight = parseInt( state.captionsHidden = true;
$('.subtitles').css('maxHeight'), 10 state.videoCaption.setSubtitlesHeight();
);
videoWrapperHeight = $('.video-wrapper').height();
progressSliderHeight = videoControl.sliderEl.height();
controlHeight = videoControl.el.height();
shouldBeHeight = videoWrapperHeight -
0.5 * progressSliderHeight -
controlHeight;
expect(realHeight).toBe(shouldBeHeight); realHeight = parseInt(
$('.subtitles').css('maxHeight'), 10
);
videoWrapperHeight = $('.video-wrapper').height();
progressSliderHeight = videoControl.sliderEl.height();
controlHeight = videoControl.el.height();
shouldBeHeight = videoWrapperHeight -
0.5 * progressSliderHeight -
controlHeight;
expect(realHeight).toBe(shouldBeHeight);
});
}); });
}); });
it('set the height of caption spacing', function () { it('set the height of caption spacing', function () {
var firstSpacing, lastSpacing; runs(function () {
var firstSpacing, lastSpacing;
firstSpacing = Math.abs(parseInt(
$('.subtitles .spacing:first').css('height'), 10 firstSpacing = Math.abs(parseInt(
)); $('.subtitles .spacing:first').css('height'), 10
lastSpacing = Math.abs(parseInt( ));
$('.subtitles .spacing:last').css('height'), 10 lastSpacing = Math.abs(parseInt(
)); $('.subtitles .spacing:last').css('height'), 10
));
expect(firstSpacing - state.videoCaption.topSpacingHeight())
.toBeLessThan(1); expect(firstSpacing - state.videoCaption.topSpacingHeight())
expect(lastSpacing - state.videoCaption.bottomSpacingHeight()) .toBeLessThan(1);
.toBeLessThan(1); expect(lastSpacing - state.videoCaption.bottomSpacingHeight())
.toBeLessThan(1);
});
}); });
it('scroll caption to new position', function () { it('scroll caption to new position', function () {
expect($.fn.scrollTo).toHaveBeenCalled(); runs(function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
}); });
}); });
// Disabled 11/25/13 due to flakiness in master
xdescribe('scrollCaption', function () { xdescribe('scrollCaption', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}); });
describe('when frozen', function () { describe('when frozen', function () {
beforeEach(function () {
state.videoCaption.frozen = true;
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.scrollCaption();
});
it('does not scroll the caption', function () { it('does not scroll the caption', function () {
expect($.fn.scrollTo).not.toHaveBeenCalled(); runs(function () {
state.videoCaption.frozen = true;
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.scrollCaption();
expect($.fn.scrollTo).not.toHaveBeenCalled();
});
}); });
}); });
describe('when not frozen', function () { describe('when not frozen', function () {
beforeEach(function () { beforeEach(function () {
state.videoCaption.frozen = false; runs(function () {
state.videoCaption.frozen = false;
});
}); });
describe('when there is no current caption', function () { describe('when there is no current caption', function () {
beforeEach(function () {
state.videoCaption.scrollCaption();
});
it('does not scroll the caption', function () { it('does not scroll the caption', function () {
expect($.fn.scrollTo).not.toHaveBeenCalled(); runs(function () {
state.videoCaption.scrollCaption();
expect($.fn.scrollTo).not.toHaveBeenCalled();
});
}); });
}); });
describe('when there is a current caption', function () { describe('when there is a current caption', function () {
beforeEach(function () {
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.scrollCaption();
});
it('scroll to current caption', function () { it('scroll to current caption', function () {
expect($.fn.scrollTo).toHaveBeenCalled(); runs(function () {
$('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.scrollCaption();
expect($.fn.scrollTo).toHaveBeenCalled();
});
}); });
}); });
}); });
}); });
// Disabled 10/9/13 due to flakiness in master
xdescribe('seekPlayer', function () { xdescribe('seekPlayer', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
var duration = state.videoPlayer.duration(),
isRendered = state.videoCaption.rendered;
return isRendered && duration;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}); });
describe('when the video speed is 1.0x', function () { describe('when the video speed is 1.0x', function () {
beforeEach(function () {
state.videoSpeedControl.currentSpeed = '1.0';
$('.subtitles li[data-start="14910"]').trigger('click');
});
it('trigger seek event with the correct time', function () { it('trigger seek event with the correct time', function () {
expect(state.videoPlayer.currentTime).toEqual(14.91); runs(function () {
state.videoSpeedControl.currentSpeed = '1.0';
$('.subtitles li[data-start="14910"]').trigger('click');
expect(state.videoPlayer.currentTime).toEqual(14.91);
});
}); });
}); });
describe('when the video speed is not 1.0x', function () { describe('when the video speed is not 1.0x', function () {
beforeEach(function () {
state.videoSpeedControl.currentSpeed = '0.75';
$('.subtitles li[data-start="14910"]').trigger('click');
});
it('trigger seek event with the correct time', function () { it('trigger seek event with the correct time', function () {
expect(state.videoPlayer.currentTime).toEqual(14.91); runs(function () {
state.videoSpeedControl.currentSpeed = '0.75';
$('.subtitles li[data-start="14910"]').trigger('click');
expect(state.videoPlayer.currentTime).toEqual(14.91);
});
}); });
}); });
describe('when the player type is Flash at speed 0.75x', describe('when the player type is Flash at speed 0.75x',
function () { function () {
beforeEach(function () {
state.videoSpeedControl.currentSpeed = '0.75';
state.currentPlayerMode = 'flash';
$('.subtitles li[data-start="14910"]').trigger('click');
});
it('trigger seek event with the correct time', function () { it('trigger seek event with the correct time', function () {
expect(state.videoPlayer.currentTime).toEqual(15); runs(function () {
state.videoSpeedControl.currentSpeed = '0.75';
state.currentPlayerMode = 'flash';
$('.subtitles li[data-start="14910"]').trigger('click');
expect(state.videoPlayer.currentTime).toEqual(15);
});
}); });
}); });
}); });
...@@ -998,7 +1058,6 @@ ...@@ -998,7 +1058,6 @@
beforeEach(function () { beforeEach(function () {
state.el.addClass('closed'); state.el.addClass('closed');
state.videoCaption.toggle(jQuery.Event('click')); state.videoCaption.toggle(jQuery.Event('click'));
jasmine.Clock.useMock(); jasmine.Clock.useMock();
}); });
...@@ -1039,41 +1098,59 @@ ...@@ -1039,41 +1098,59 @@
describe('caption accessibility', function () { describe('caption accessibility', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); runs(function () {
state = jasmine.initializePlayer();
});
waitsFor(function () {
return state.videoCaption.rendered;
}, 'Captions are not rendered', WAIT_TIMEOUT);
}); });
describe('when getting focus through TAB key', function () { describe('when getting focus through TAB key', function () {
beforeEach(function () { beforeEach(function () {
state.videoCaption.isMouseFocus = false; runs(function () {
$('.subtitles li[data-index=0]').trigger( state.videoCaption.isMouseFocus = false;
jQuery.Event('focus') $('.subtitles li[data-index=0]').trigger(
); jQuery.Event('focus')
);
});
}); });
it('shows an outline around the caption', function () { it('shows an outline around the caption', function () {
expect($('.subtitles li[data-index=0]')) runs(function () {
.toHaveClass('focused'); expect($('.subtitles li[data-index=0]'))
.toHaveClass('focused');
});
}); });
it('has automatic scrolling disabled', function () { it('has automatic scrolling disabled', function () {
expect(state.videoCaption.autoScrolling).toBe(false); runs(function () {
expect(state.videoCaption.autoScrolling).toBe(false);
});
}); });
}); });
describe('when loosing focus through TAB key', function () { describe('when loosing focus through TAB key', function () {
beforeEach(function () { beforeEach(function () {
$('.subtitles li[data-index=0]').trigger( runs(function () {
jQuery.Event('blur') $('.subtitles li[data-index=0]').trigger(
); jQuery.Event('blur')
);
});
}); });
it('does not show an outline around the caption', function () { it('does not show an outline around the caption', function () {
expect($('.subtitles li[data-index=0]')) runs(function () {
.not.toHaveClass('focused'); expect($('.subtitles li[data-index=0]'))
.not.toHaveClass('focused');
});
}); });
it('has automatic scrolling enabled', function () { it('has automatic scrolling enabled', function () {
expect(state.videoCaption.autoScrolling).toBe(true); runs(function () {
expect(state.videoCaption.autoScrolling).toBe(true);
});
}); });
}); });
...@@ -1083,20 +1160,26 @@ ...@@ -1083,20 +1160,26 @@
function () { function () {
beforeEach(function () { beforeEach(function () {
state.videoCaption.isMouseFocus = false; runs(function () {
$('.subtitles li[data-index=0]') state.videoCaption.isMouseFocus = false;
.trigger(jQuery.Event('focus')); $('.subtitles li[data-index=0]')
$('.subtitles li[data-index=0]') .trigger(jQuery.Event('focus'));
.trigger(jQuery.Event('mousedown')); $('.subtitles li[data-index=0]')
.trigger(jQuery.Event('mousedown'));
});
}); });
it('does not show an outline around it', function () { it('does not show an outline around it', function () {
expect($('.subtitles li[data-index=0]')) runs(function () {
.not.toHaveClass('focused'); expect($('.subtitles li[data-index=0]'))
.not.toHaveClass('focused');
});
}); });
it('has automatic scrolling enabled', function () { it('has automatic scrolling enabled', function () {
expect(state.videoCaption.autoScrolling).toBe(true); runs(function () {
expect(state.videoCaption.autoScrolling).toBe(true);
});
}); });
}); });
...@@ -1108,29 +1191,37 @@ ...@@ -1108,29 +1191,37 @@
var subDataLiIdx__0, subDataLiIdx__1; var subDataLiIdx__0, subDataLiIdx__1;
beforeEach(function () { beforeEach(function () {
subDataLiIdx__0 = $('.subtitles li[data-index=0]'); runs(function () {
subDataLiIdx__1 = $('.subtitles li[data-index=1]'); subDataLiIdx__0 = $('.subtitles li[data-index=0]');
subDataLiIdx__1 = $('.subtitles li[data-index=1]');
state.videoCaption.isMouseFocus = false; state.videoCaption.isMouseFocus = false;
subDataLiIdx__0.trigger(jQuery.Event('focus')); subDataLiIdx__0.trigger(jQuery.Event('focus'));
subDataLiIdx__0.trigger(jQuery.Event('blur')); subDataLiIdx__0.trigger(jQuery.Event('blur'));
state.videoCaption.isMouseFocus = true; state.videoCaption.isMouseFocus = true;
subDataLiIdx__1.trigger(jQuery.Event('mousedown')); subDataLiIdx__1.trigger(jQuery.Event('mousedown'));
});
}); });
it('does not show an outline around the first', function () { it('does not show an outline around the first', function () {
expect(subDataLiIdx__0).not.toHaveClass('focused'); runs(function () {
expect(subDataLiIdx__0).not.toHaveClass('focused');
});
}); });
it('does not show an outline around the second', function () { it('does not show an outline around the second', function () {
expect(subDataLiIdx__1).not.toHaveClass('focused'); runs(function () {
expect(subDataLiIdx__1).not.toHaveClass('focused');
});
}); });
it('has automatic scrolling enabled', function () { it('has automatic scrolling enabled', function () {
expect(state.videoCaption.autoScrolling).toBe(true); runs(function () {
expect(state.videoCaption.autoScrolling).toBe(true);
});
}); });
}); });
}); });
......
(function (define) {
define(
'video/00_async_process.js',
[],
function() {
"use strict";
/**
* Provides convenient way to process big amount of data without UI blocking.
*
* @param {array} list Array to process.
* @param {function} process Calls this function on each item in the list.
* @return {array} Returns a Promise object to observe when all actions of a
certain type bound to the collection, queued or not, have finished.
*/
var AsyncProcess = {
array: function (list, process) {
if (!_.isArray(list)) {
return $.Deferred().reject().promise();
}
if (!_.isFunction(process) || !list.length) {
return $.Deferred().resolve(list).promise();
}
var MAX_DELAY = 50, // maximum amount of time that js code should be allowed to run continuously
dfd = $.Deferred(),
result = [],
index = 0,
len = list.length;
var getCurrentTime = function () {
return (new Date()).getTime();
};
var handler = function () {
var start = getCurrentTime();
do {
result[index] = process(list[index], index);
index++;
} while (index < len && getCurrentTime() - start < MAX_DELAY);
if (index < len) {
setTimeout(handler, 25);
} else {
dfd.resolve(result);
}
};
setTimeout(handler, 25);
return dfd.promise();
}
};
return AsyncProcess;
});
}(RequireJS.define));
(function (define) {
define(
'video/00_sjson.js',
[],
function() {
"use strict";
var Sjson = function (data) {
var sjson = {
start: data.start.concat(),
text: data.text.concat()
},
module = {};
var getter = function (propertyName) {
return function () {
return sjson[propertyName];
};
};
var getStartTimes = getter('start');
var getCaptions = getter('text');
var size = function () {
return sjson.text.length;
};
var search = function (time) {
var start = getStartTimes(),
max = size() - 1,
min = 0,
index;
if (time < start[min]) {
return -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;
};
return {
getCaptions: getCaptions,
getStartTimes: getStartTimes,
getSize: size,
search: search
};
};
return Sjson;
});
}(RequireJS.define));
(function (requirejs, require, define) { (function (define) {
// VideoCaption module. // VideoCaption module.
define( define(
'video/09_video_caption.js', 'video/09_video_caption.js',
[], ['video/00_sjson.js', 'video/00_async_process.js'],
function () { function (Sjson, AsyncProcess) {
/** /**
* @desc VideoCaption module exports a function. * @desc VideoCaption module exports a function.
...@@ -21,16 +21,11 @@ function () { ...@@ -21,16 +21,11 @@ function () {
* @returns {undefined} * @returns {undefined}
*/ */
return function (state) { return function (state) {
var dfd = $.Deferred();
state.videoCaption = {}; state.videoCaption = {};
_makeFunctionsPublic(state); _makeFunctionsPublic(state);
state.videoCaption.renderElements(); state.videoCaption.renderElements();
dfd.resolve(); return $.Deferred().resolve().promise();
return dfd.promise();
}; };
// *************************************************************** // ***************************************************************
...@@ -65,10 +60,8 @@ function () { ...@@ -65,10 +60,8 @@ function () {
renderCaption: renderCaption, renderCaption: renderCaption,
renderElements: renderElements, renderElements: renderElements,
renderLanguageMenu: renderLanguageMenu, renderLanguageMenu: renderLanguageMenu,
reRenderCaption: reRenderCaption,
resize: resize, resize: resize,
scrollCaption: scrollCaption, scrollCaption: scrollCaption,
search: search,
seekPlayer: seekPlayer, seekPlayer: seekPlayer,
setSubtitlesHeight: setSubtitlesHeight, setSubtitlesHeight: setSubtitlesHeight,
toggle: toggle, toggle: toggle,
...@@ -133,19 +126,47 @@ function () { ...@@ -133,19 +126,47 @@ function () {
// mousemove, etc.). // mousemove, etc.).
function bindHandlers() { function bindHandlers() {
var self = this, var self = this,
Caption = this.videoCaption; Caption = this.videoCaption,
events = [
'mouseover', 'mouseout', 'mousedown', 'click', 'focus', 'blur',
'keydown'
].join(' ');
Caption.hideSubtitlesEl.on({ Caption.hideSubtitlesEl.on({
'click': Caption.toggle 'click': Caption.toggle
}); });
Caption.subtitlesEl.on({ Caption.subtitlesEl
mouseenter: Caption.onMouseEnter, .on({
mouseleave: Caption.onMouseLeave, mouseenter: Caption.onMouseEnter,
mousemove: Caption.onMovement, mouseleave: Caption.onMouseLeave,
mousewheel: Caption.onMovement, mousemove: Caption.onMovement,
DOMMouseScroll: Caption.onMovement mousewheel: Caption.onMovement,
}); DOMMouseScroll: Caption.onMovement
})
.on(events, 'li[data-index]', function (event) {
switch (event.type) {
case 'mouseover':
case 'mouseout':
Caption.captionMouseOverOut(event);
break;
case 'mousedown':
Caption.captionMouseDown(event);
break;
case 'click':
Caption.captionClick(event);
break;
case 'focusin':
Caption.captionFocus(event);
break;
case 'focusout':
Caption.captionBlur(event);
break;
case 'keydown':
Caption.captionKeyDown(event);
break;
}
});
if (Caption.showLanguageMenu) { if (Caption.showLanguageMenu) {
Caption.container.on({ Caption.container.on({
...@@ -154,12 +175,6 @@ function () { ...@@ -154,12 +175,6 @@ function () {
}); });
} }
this.el.on('speedchange', function () {
if (self.isFlashMode()) {
Caption.fetchCaption();
}
});
if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { if ((this.videoType === 'html5') && (this.config.autohideHtml5)) {
Caption.subtitlesEl.on('scroll', this.videoControl.showControls); Caption.subtitlesEl.on('scroll', this.videoControl.showControls);
} }
...@@ -241,7 +256,7 @@ function () { ...@@ -241,7 +256,7 @@ function () {
if (this.videoType === 'youtube') { if (this.videoType === 'youtube') {
data = { data = {
videoId: this.youtubeId() videoId: this.youtubeId('1.0')
}; };
} }
...@@ -251,13 +266,15 @@ function () { ...@@ -251,13 +266,15 @@ function () {
url: self.config.transcriptTranslationUrl + '/' + language, url: self.config.transcriptTranslationUrl + '/' + language,
notifyOnError: false, notifyOnError: false,
data: data, data: data,
success: function (captions) { success: function (response) {
Caption.captions = captions.text; Caption.sjson = new Sjson(response);
Caption.start = captions.start;
var start = Caption.sjson.getStartTimes(),
captions = Caption.sjson.getCaptions();
if (Caption.loaded) { if (Caption.loaded) {
if (Caption.rendered) { if (Caption.rendered) {
Caption.reRenderCaption(); Caption.renderCaption(start, captions);
Caption.updatePlayTime(self.videoPlayer.currentTime); Caption.updatePlayTime(self.videoPlayer.currentTime);
} }
} else { } else {
...@@ -269,7 +286,7 @@ function () { ...@@ -269,7 +286,7 @@ function () {
) )
); );
} else { } else {
Caption.renderCaption(); Caption.renderCaption(start, captions);
} }
Caption.bindHandlers(); Caption.bindHandlers();
...@@ -334,7 +351,6 @@ function () { ...@@ -334,7 +351,6 @@ function () {
.height(this.videoCaption.bottomSpacingHeight()); .height(this.videoCaption.bottomSpacingHeight());
this.videoCaption.scrollCaption(); this.videoCaption.scrollCaption();
this.videoCaption.setSubtitlesHeight(); this.videoCaption.setSubtitlesHeight();
} }
...@@ -380,90 +396,53 @@ function () { ...@@ -380,90 +396,53 @@ function () {
}); });
} }
function buildCaptions (container, captions, start) { function buildCaptions (container, start, captions) {
var fragment = document.createDocumentFragment(); var process = function(text, index) {
var liEl = $('<li>', {
$.each(captions, function(index, text) { 'data-index': index,
var liEl = $('<li>'); 'data-start': start[index],
'tabindex': 0
}).html(text);
liEl.html(text); return liEl[0];
};
liEl.attr({
'data-index': index,
'data-start': start[index],
'tabindex': 0
});
fragment.appendChild(liEl[0]); return AsyncProcess.array(captions, process).done(function (list) {
container.append(list);
}); });
container.append([fragment]);
} }
function renderCaption() { function renderCaption(start, captions) {
var Caption = this.videoCaption, var Caption = this.videoCaption;
events = ['mouseover', 'mouseout', 'mousedown', 'click', 'focus',
'blur', 'keydown'].join(' ');
Caption.setSubtitlesHeight();
buildCaptions(Caption.subtitlesEl, Caption.captions, Caption.start); var onRender = function () {
Caption.addPaddings();
Caption.subtitlesEl.on(events, 'li[data-index]', function (event) { // Enables or disables automatic scrolling of the captions when the
switch (event.type) { // video is playing. This feature has to be disabled when tabbing
case 'mouseover': // through them as it interferes with that action. Initially, have
case 'mouseout': // this flag enabled as we assume mouse use. Then, if the first
Caption.captionMouseOverOut(event); // caption (through forward tabbing) or the last caption (through
break; // backwards tabbing) gets the focus, disable that feature.
case 'mousedown': // Re-enable it if tabbing then cycles out of the the captions.
Caption.captionMouseDown(event); Caption.autoScrolling = true;
break; // Keeps track of where the focus is situated in the array of
case 'click': // captions. Used to implement the automatic scrolling behavior and
Caption.captionClick(event); // decide if the outline around a caption has to be hidden or shown
break; // on a mouseenter or mouseleave. Initially, no caption has the
case 'focusin': // focus, set the index to -1.
Caption.captionFocus(event); Caption.currentCaptionIndex = -1;
break; // Used to track if the focus is coming from a click or tabbing. This
case 'focusout': // has to be known to decide if, when a caption gets the focus, an
Caption.captionBlur(event); // outline has to be drawn (tabbing) or not (mouse click).
break; Caption.isMouseFocus = false;
case 'keydown': Caption.rendered = true;
Caption.captionKeyDown(event); };
break;
}
});
// Enables or disables automatic scrolling of the captions when the
// video is playing. This feature has to be disabled when tabbing
// through them as it interferes with that action. Initially, have this
// flag enabled as we assume mouse use. Then, if the first caption
// (through forward tabbing) or the last caption (through backwards
// tabbing) gets the focus, disable that feature. Re-enable it if tabbing
// then cycles out of the the captions.
Caption.autoScrolling = true;
// Keeps track of where the focus is situated in the array of captions.
// Used to implement the automatic scrolling behavior and decide if the
// outline around a caption has to be hidden or shown on a mouseenter
// or mouseleave. Initially, no caption has the focus, set the
// index to -1.
Caption.currentCaptionIndex = -1;
// Used to track if the focus is coming from a click or tabbing. This
// has to be known to decide if, when a caption gets the focus, an
// outline has to be drawn (tabbing) or not (mouse click).
Caption.isMouseFocus = false;
Caption.addPaddings();
Caption.rendered = true;
}
function reRenderCaption() {
var Caption = this.videoCaption;
Caption.currentIndex = null;
Caption.rendered = false; Caption.rendered = false;
Caption.subtitlesEl.empty(); Caption.subtitlesEl.empty();
buildCaptions(Caption.subtitlesEl, Caption.captions, Caption.start); Caption.setSubtitlesHeight();
Caption.addPaddings(); buildCaptions(Caption.subtitlesEl, start, captions).done(onRender);
Caption.rendered = true;
} }
function addPaddings() { function addPaddings() {
...@@ -529,7 +508,7 @@ function () { ...@@ -529,7 +508,7 @@ function () {
// off again as it may have been enabled in captionBlur. // off again as it may have been enabled in captionBlur.
if ( if (
captionIndex <= 1 || captionIndex <= 1 ||
captionIndex >= this.videoCaption.captions.length - 2 captionIndex >= this.videoCaption.sjson.getSize() - 2
) { ) {
this.videoCaption.autoScrolling = false; this.videoCaption.autoScrolling = false;
} }
...@@ -547,7 +526,7 @@ function () { ...@@ -547,7 +526,7 @@ function () {
// tabbing back out of the captions or on the last element and tabbing // tabbing back out of the captions or on the last element and tabbing
// forward out of the captions. // forward out of the captions.
if (captionIndex === 0 || if (captionIndex === 0 ||
captionIndex === this.videoCaption.captions.length - 1) { captionIndex === this.videoCaption.sjson.getSize() - 1) {
this.videoCaption.autoScrolling = true; this.videoCaption.autoScrolling = true;
} }
...@@ -579,38 +558,13 @@ function () { ...@@ -579,38 +558,13 @@ function () {
} }
} }
function search(time) {
var index, max, min;
if (this.videoCaption.loaded) {
min = 0;
max = this.videoCaption.start.length - 1;
if (time < this.videoCaption.start[min]) {
return -1;
}
while (min < max) {
index = Math.ceil((max + min) / 2);
if (time < this.videoCaption.start[index]) {
max = index - 1;
}
if (time >= this.videoCaption.start[index]) {
min = index;
}
}
return min;
}
return undefined;
}
function play() { function play() {
if (this.videoCaption.loaded) { if (this.videoCaption.loaded) {
if (!this.videoCaption.rendered) { if (!this.videoCaption.rendered) {
this.videoCaption.renderCaption(); var start = this.videoCaption.sjson.getStartTimes(),
captions = this.videoCaption.sjson.getCaptions();
this.videoCaption.renderCaption(start, captions);
} }
this.videoCaption.playing = true; this.videoCaption.playing = true;
...@@ -627,20 +581,12 @@ function () { ...@@ -627,20 +581,12 @@ function () {
var newIndex; var newIndex;
if (this.videoCaption.loaded) { if (this.videoCaption.loaded) {
// Current mode === 'flash' can only be for YouTube videos. So, we
// don't have to also check for videoType === 'youtube'.
if (this.isFlashMode()) { if (this.isFlashMode()) {
// Total play time changes with speed change. Also there is time = Time.convert(time, this.speed, '1.0');
// a 250 ms delay we have to take into account.
time = Math.round(
Time.convert(time, this.speed, '1.0') * 1000 + 100
);
} else {
// Total play time remains constant when speed changes.
time = Math.round(time * 1000 + 100);
} }
newIndex = this.videoCaption.search(time); time = Math.round(time * 1000 + 100);
newIndex = this.videoCaption.sjson.search(time);
if ( if (
typeof newIndex !== 'undefined' && typeof newIndex !== 'undefined' &&
...@@ -658,39 +604,27 @@ function () { ...@@ -658,39 +604,27 @@ function () {
.addClass('current'); .addClass('current');
this.videoCaption.currentIndex = newIndex; this.videoCaption.currentIndex = newIndex;
this.videoCaption.scrollCaption(); this.videoCaption.scrollCaption();
} }
} }
} }
function seekPlayer(event) { function seekPlayer(event) {
var time; var time = parseInt($(event.target).data('start'), 10);
event.preventDefault();
// Current mode === 'flash' can only be for YouTube videos. So, we
// don't have to also check for videoType === 'youtube'.
if (this.isFlashMode()) { if (this.isFlashMode()) {
// Total play time changes with speed change. Also there is time = Math.round(Time.convert(time, '1.0', this.speed));
// a 250 ms delay we have to take into account.
time = Math.round(
Time.convert(
$(event.target).data('start'), '1.0', this.speed
) / 1000
);
} else {
// Total play time remains constant when speed changes.
time = parseInt($(event.target).data('start'), 10)/1000;
} }
this.trigger( this.trigger(
'videoPlayer.onCaptionSeek', 'videoPlayer.onCaptionSeek',
{ {
'type': 'onCaptionSeek', 'type': 'onCaptionSeek',
'time': time 'time': time/1000
} }
); );
event.preventDefault();
} }
function calculateOffset(element) { function calculateOffset(element) {
...@@ -801,4 +735,4 @@ function () { ...@@ -801,4 +735,4 @@ function () {
} }
}); });
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); }(RequireJS.define));
...@@ -72,6 +72,8 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule): ...@@ -72,6 +72,8 @@ class VideoModule(VideoFields, VideoStudentViewHandlers, XModule):
'js': [ 'js': [
resource_string(module, 'js/src/video/00_video_storage.js'), resource_string(module, 'js/src/video/00_video_storage.js'),
resource_string(module, 'js/src/video/00_resizer.js'), 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/01_initialize.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/025_focus_grabber.js'),
resource_string(module, 'js/src/video/02_html5_video.js'), resource_string(module, 'js/src/video/02_html5_video.js'),
......
...@@ -111,18 +111,18 @@ Feature: LMS Video component ...@@ -111,18 +111,18 @@ Feature: LMS Video component
# 11 # 11
Scenario: Language menu works correctly in Video component Scenario: Language menu works correctly in Video component
Given I am registered for the course "test_course" Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode: And it has a video in "Youtube" mode:
| transcripts | sub | | transcripts | sub |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | | {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM |
And I make sure captions are closed And I make sure captions are closed
And I see video menu "language" with correct items And I see video menu "language" with correct items
And I select language with code "zh" And I select language with code "zh"
Then I see "好 各位同学" text in the captions Then I see "好 各位同学" text in the captions
And I select language with code "en" And I select language with code "en"
And I see "Hi, welcome to Edx." text in the captions And I see "Hi, welcome to Edx." text in the captions
# 12 # 12
Scenario: CC button works correctly w/o english transcript in HTML5 mode of Video component Scenario: CC button works correctly w/o english transcript in HTML5 mode of Video component
...@@ -237,29 +237,31 @@ Feature: LMS Video component ...@@ -237,29 +237,31 @@ Feature: LMS Video component
# 21 # 21
Scenario: Download button works correctly for non-english transcript in Youtube mode of Video component Scenario: Download button works correctly for non-english transcript in Youtube mode of Video component
Given I am registered for the course "test_course" Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode: And it has a video in "Youtube" mode:
| transcripts | sub | download_track | | transcripts | sub | download_track |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true | | {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true |
Then I can download transcript in "srt" format that has text "Hi, welcome to Edx." And I see "Hi, welcome to Edx." text in the captions
And I select language with code "zh" Then I can download transcript in "srt" format that has text "Hi, welcome to Edx."
And I see "好 各位同学" text in the captions And I select language with code "zh"
Then I can download transcript in "srt" format that has text "好 各位同学" And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format that has text "好 各位同学"
# 22 # 22
Scenario: Download button works correctly for non-english transcript in HTML5 mode of Video component Scenario: Download button works correctly for non-english transcript in HTML5 mode of Video component
Given I am registered for the course "test_course" Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "HTML5" mode: And it has a video in "HTML5" mode:
| transcripts | sub | download_track | | transcripts | sub | download_track |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true | | {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true |
Then I can download transcript in "srt" format that has text "Hi, welcome to Edx." And I see "Hi, welcome to Edx." text in the captions
And I select language with code "zh" Then I can download transcript in "srt" format that has text "Hi, welcome to Edx."
And I see "好 各位同学" text in the captions And I select language with code "zh"
Then I can download transcript in "srt" format that has text "好 各位同学" And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format that has text "好 各位同学"
# 23 # 23
Scenario: Download button works correctly w/o english transcript in HTML5 mode of Video component Scenario: Download button works correctly w/o english transcript in HTML5 mode of Video component
...@@ -298,8 +300,10 @@ Feature: LMS Video component ...@@ -298,8 +300,10 @@ Feature: LMS Video component
And a video "D" in "Youtube_HTML5" mode in position "3" of sequential And a video "D" in "Youtube_HTML5" mode in position "3" of sequential
And I open the section with videos And I open the section with videos
Then videos have rendered in "HTML5" mode Then videos have rendered in "HTML5" mode
And I see "Hi, welcome to Edx." text in the captions And I see text in the captions:
And I see "Equal transcripts" text in the captions | text |
| Hi, welcome to Edx. |
| Equal transcripts |
When I open video "C" When I open video "C"
Then the video has rendered in "HTML5" mode Then the video has rendered in "HTML5" mode
And I make sure captions are opened And I make sure captions are opened
......
...@@ -240,25 +240,25 @@ def set_window_dimensions(width, height): ...@@ -240,25 +240,25 @@ def set_window_dimensions(width, height):
def duration(): def duration():
""" """
Total duration of the video, in seconds. Total duration of the video, in seconds.
""" """
elapsed_time, duration = video_time() elapsed_time, duration = video_time()
return duration return duration
def video_time(): def video_time():
""" """
Return a tuple `(elapsed_time, duration)`, each in seconds. Return a tuple `(elapsed_time, duration)`, each in seconds.
""" """
# The full time has the form "0:32 / 3:14" # The full time has the form "0:32 / 3:14"
full_time = world.css_text('div.vidtime') full_time = world.css_text('div.vidtime')
# Split the time at the " / ", to get ["0:32", "3:14"] # Split the time at the " / ", to get ["0:32", "3:14"]
elapsed_str, duration_str = full_time.split(' / ') elapsed_str, duration_str = full_time.split(' / ')
# Convert each string to seconds # Convert each string to seconds
return (parse_time_str(elapsed_str), parse_time_str(duration_str)) return (parse_time_str(elapsed_str), parse_time_str(duration_str))
def parse_time_str(time_str): def parse_time_str(time_str):
...@@ -316,13 +316,13 @@ def visit_video_section(_step): ...@@ -316,13 +316,13 @@ def visit_video_section(_step):
@step('I select the "([^"]*)" speed$') @step('I select the "([^"]*)" speed$')
def i_select_video_speed(_step, speed): def i_select_video_speed(_step, speed):
change_video_speed(speed) change_video_speed(speed)
@step('I select the "([^"]*)" speed on video "([^"]*)"$') @step('I select the "([^"]*)" speed on video "([^"]*)"$')
def change_video_speed_on_video(_step, speed, player_id): def change_video_speed_on_video(_step, speed, player_id):
navigate_to_an_item_in_a_sequence(world.video_sequences[player_id]) navigate_to_an_item_in_a_sequence(world.video_sequences[player_id])
change_video_speed(speed) change_video_speed(speed)
@step('I open video "([^"]*)"$') @step('I open video "([^"]*)"$')
...@@ -419,7 +419,15 @@ def i_see_menu(_step, menu): ...@@ -419,7 +419,15 @@ def i_see_menu(_step, menu):
@step('I see "([^"]*)" text in the captions$') @step('I see "([^"]*)" text in the captions$')
def check_text_in_the_captions(_step, text): def check_text_in_the_captions(_step, text):
assert world.browser.is_text_present(text.strip()) world.wait_for(lambda _: world.css_text('.subtitles'))
actual_text = world.css_text('.subtitles')
assert (text in actual_text)
@step('I see text in the captions:')
def check_captions(_step):
for index, video in enumerate(_step.hashes):
assert (video.get('text') in world.css_text('.subtitles', index=index))
@step('I select language with code "([^"]*)"$') @step('I select language with code "([^"]*)"$')
...@@ -441,14 +449,14 @@ def select_language(_step, code): ...@@ -441,14 +449,14 @@ def select_language(_step, code):
# Make sure that all ajax requests that affects the display of captions are finished. # Make sure that all ajax requests that affects the display of captions are finished.
# For example, request to get new translation etc. # For example, request to get new translation etc.
world.wait_for_ajax_complete() world.wait_for_ajax_complete()
assert world.css_visible('.subtitles') world.wait_for_visible('.subtitles')
@step('I click video button "([^"]*)"$') @step('I click video button "([^"]*)"$')
def click_button(_step, button): def click_button(_step, button):
world.css_click(VIDEO_BUTTONS[button]) world.css_click(VIDEO_BUTTONS[button])
@step('I see video starts playing from "([^"]*)" position$') @step('I see video starts playing from "([^"]*)" position$')
def start_playing_video_from_n_seconds(_step, position): def start_playing_video_from_n_seconds(_step, position):
world.wait_for( world.wait_for(
...@@ -504,9 +512,7 @@ def video_alignment(_step, transcript_visibility): ...@@ -504,9 +512,7 @@ def video_alignment(_step, transcript_visibility):
height = abs(expected['height'] - real['height']) <= 5 height = abs(expected['height'] - real['height']) <= 5
# Restore initial window size # Restore initial window size
set_window_dimensions( set_window_dimensions(initial['width'], initial['height'])
initial['width'], initial['height']
)
assert all([width, height]) assert all([width, height])
......
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