Commit 40d5f1e5 by Anton Stupak

Merge pull request #3195 from edx/anton/force-flash

Force video player in flash mode.
parents da470afc d5555470
......@@ -201,7 +201,8 @@ def upload_file(_step, file_name):
@step('I see "([^"]*)" text in the captions')
def check_text_in_the_captions(_step, text):
world.wait_for(lambda _: world.css_text('.subtitles'), 30)
world.wait_for_present('.video.is-captions-rendered')
world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
actual_text = world.css_text('.subtitles')
assert (text in actual_text)
......
......@@ -197,11 +197,15 @@ def find_caption_line_by_data_index(index):
@step('I focus on caption line with data-index "([^"]*)"$')
def focus_on_caption_line(_step, index):
world.wait_for_present('.video.is-captions-rendered')
world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
find_caption_line_by_data_index(int(index.strip()))._element.send_keys(Keys.TAB)
@step('I press "enter" button on caption line with data-index "([^"]*)"$')
def click_on_the_caption(_step, index):
world.wait_for_present('.video.is-captions-rendered')
world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
find_caption_line_by_data_index(int(index.strip()))._element.send_keys(Keys.ENTER)
......@@ -214,7 +218,6 @@ def caption_line_has_class(_step, index, className):
@step('I see a range on slider$')
def see_a_range_slider_with_proper_range(_step):
world.wait_for_visible(VIDEO_BUTTONS['pause'])
assert world.css_visible(".slider-range")
......
......@@ -396,6 +396,89 @@ function (Initialize) {
});
});
});
describe('setPlayerMode', function () {
beforeEach(function () {
state = {
currentPlayerMode: 'flash',
};
});
it('updates player mode', function () {
var setPlayerMode = Initialize.prototype.setPlayerMode;
setPlayerMode.call(state, 'html5');
expect(state.currentPlayerMode).toBe('html5');
setPlayerMode.call(state, 'flash');
expect(state.currentPlayerMode).toBe('flash');
});
it('sets default mode if passed is not supported', function () {
var setPlayerMode = Initialize.prototype.setPlayerMode;
setPlayerMode.call(state, '77html77');
expect(state.currentPlayerMode).toBe('html5');
});
});
describe('getPlayerMode', function () {
beforeEach(function () {
state = {
currentPlayerMode: 'flash',
};
});
it('returns current player mode', function () {
var getPlayerMode = Initialize.prototype.getPlayerMode,
actual = getPlayerMode.call(state);
expect(actual).toBe(state.currentPlayerMode);
});
});
describe('isFlashMode', function () {
it('returns `true` if player in `flash` mode', function () {
var state = {
getPlayerMode: jasmine.createSpy().andReturn('flash'),
},
isFlashMode = Initialize.prototype.isFlashMode,
actual = isFlashMode.call(state);
expect(actual).toBeTruthy();
});
it('returns `false` if player is not in `flash` mode', function () {
var state = {
getPlayerMode: jasmine.createSpy().andReturn('html5'),
},
isFlashMode = Initialize.prototype.isFlashMode,
actual = isFlashMode.call(state);
expect(actual).toBeFalsy();
});
});
describe('isHtml5Mode', function () {
it('returns `true` if player in `html5` mode', function () {
var state = {
getPlayerMode: jasmine.createSpy().andReturn('html5'),
},
isHtml5Mode = Initialize.prototype.isHtml5Mode,
actual = isHtml5Mode.call(state);
expect(actual).toBeTruthy();
});
it('returns `false` if player is not in `html5` mode', function () {
var state = {
getPlayerMode: jasmine.createSpy().andReturn('flash'),
},
isHtml5Mode = Initialize.prototype.isHtml5Mode,
actual = isHtml5Mode.call(state);
expect(actual).toBeFalsy();
});
});
});
});
......
......@@ -1070,6 +1070,9 @@ function (VideoPlayer) {
beforeEach(function () {
state = {
youtubeId: jasmine.createSpy().andReturn('videoId'),
isFlashMode: jasmine.createSpy().andReturn(false),
isHtml5Mode: jasmine.createSpy().andReturn(true),
setPlayerMode: jasmine.createSpy(),
videoPlayer: {
currentTime: 60,
isPlaying: jasmine.createSpy(),
......@@ -1083,7 +1086,8 @@ function (VideoPlayer) {
});
it('in Flash mode and video is playing', function () {
state.currentPlayerMode = 'flash';
state.isFlashMode.andReturn(true);
state.isHtml5Mode.andReturn(false);
state.videoPlayer.isPlaying.andReturn(true);
VideoPlayer.prototype.setPlaybackRate.call(state, '0.75');
expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(60);
......@@ -1092,7 +1096,8 @@ function (VideoPlayer) {
});
it('in Flash mode and video not started', function () {
state.currentPlayerMode = 'flash';
state.isFlashMode.andReturn(true);
state.isHtml5Mode.andReturn(false);
state.videoPlayer.isPlaying.andReturn(false);
VideoPlayer.prototype.setPlaybackRate.call(state, '0.75');
expect(state.videoPlayer.updatePlayTime).toHaveBeenCalledWith(60);
......@@ -1101,13 +1106,11 @@ function (VideoPlayer) {
});
it('in HTML5 mode', function () {
state.currentPlayerMode = 'html5';
VideoPlayer.prototype.setPlaybackRate.call(state, '0.75');
expect(state.videoPlayer.player.setPlaybackRate).toHaveBeenCalledWith('0.75');
});
it('Youtube video in FF, with new speed equal 1.0', function () {
state.currentPlayerMode = 'html5';
state.videoType = 'youtube';
state.browserIsFirefox = true;
......
......@@ -10,7 +10,7 @@ function() {
* @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.
* certain type bound to the collection, queued or not, have finished.
*/
var AsyncProcess = {
array: function (list, process) {
......
......@@ -63,13 +63,16 @@ function (VideoPlayer, VideoStorage) {
fetchMetadata: fetchMetadata,
getCurrentLanguage: getCurrentLanguage,
getDuration: getDuration,
getPlayerMode: getPlayerMode,
getVideoMetadata: getVideoMetadata,
initialize: initialize,
isHtml5Mode: isHtml5Mode,
isFlashMode: isFlashMode,
parseSpeed: parseSpeed,
parseVideoSources: parseVideoSources,
parseYoutubeStreams: parseYoutubeStreams,
saveState: saveState,
setPlayerMode: setPlayerMode,
setSpeed: setSpeed,
trigger: trigger,
youtubeId: youtubeId
......@@ -250,18 +253,6 @@ function (VideoPlayer, VideoStorage) {
}
}
// function _setPlayerMode(state)
// By default we will be forcing HTML5 player mode. Only in the case
// when, after initializtion, we will get one available playback rate,
// we will change to Flash player mode. There is a need to store this
// setting in cookies because otherwise we will have to change from
// HTML5 to Flash on every page load in a browser that doesn't fully
// support HTML5. When we have this setting in cookies, we can select
// the proper mode from the start (not having to change mode later on).
function _setPlayerMode(state) {
state.currentPlayerMode = 'html5';
}
// function _parseYouTubeIDs(state)
// The function parse YouTube stream ID's.
// @return
......@@ -339,8 +330,7 @@ function (VideoPlayer, VideoStorage) {
function _setConfigurations(state) {
_configureCaptions(state);
_setPlayerMode(state);
state.setPlayerMode(state.config.mode);
// Possible value are: 'visible', 'hiding', and 'invisible'.
state.controlState = 'visible';
state.controlHideTimeout = null;
......@@ -520,7 +510,8 @@ function (VideoPlayer, VideoStorage) {
element: element,
fadeOutTimeout: 1400,
captionsFreezeTime: 10000,
availableQualities: ['hd720', 'hd1080', 'highres']
availableQualities: ['hd720', 'hd1080', 'highres'],
mode: $.cookie('edX_video_player_mode')
});
if (this.config.endTime < this.config.startTime) {
......@@ -811,8 +802,46 @@ function (VideoPlayer, VideoStorage) {
}
}
/**
* Sets player mode.
*
* @param {string} mode Mode to set for the video player if it is supported.
* Otherwise, `html5` is used by default.
*/
function setPlayerMode(mode) {
var supportedModes = ['html5', 'flash'];
mode = _.contains(supportedModes, mode) ? mode : 'html5';
this.currentPlayerMode = mode;
}
/**
* Returns current player mode.
*
* @return {string} Returns string that describes player mode
*/
function getPlayerMode() {
return this.currentPlayerMode;
}
/**
* Checks if current player mode is Flash.
*
* @return {boolean} Returns `true` if current mode is `flash`, otherwise
* it returns `false`
*/
function isFlashMode() {
return this.currentPlayerMode === 'flash';
return this.getPlayerMode() === 'flash';
}
/**
* Checks if current player mode is Html5.
*
* @return {boolean} Returns `true` if current mode is `html5`, otherwise
* it returns `false`
*/
function isHtml5Mode() {
return this.getPlayerMode() === 'html5';
}
function getCurrentLanguage() {
......
......@@ -251,7 +251,7 @@ function (HTML5Video, Resizer) {
// Remove from the page current iFrame with HTML5 video.
state.videoPlayer.player.destroy();
state.currentPlayerMode = 'flash';
state.setPlayerMode('flash');
console.log('[Video info]: Changing YouTube player mode to "flash".');
......@@ -334,7 +334,7 @@ function (HTML5Video, Resizer) {
methodName, youtubeId;
if (
this.currentPlayerMode === 'html5' &&
this.isHtml5Mode() &&
!(
this.browserIsFirefox &&
newSpeed === '1.0' &&
......@@ -554,7 +554,7 @@ function (HTML5Video, Resizer) {
// For more information, please see the PR that introduced this change:
// https://github.com/edx/edx-platform/pull/2841
if (
(this.currentPlayerMode === 'html5' || availablePlaybackRates.length > 1) &&
(this.isHtml5Mode() || availablePlaybackRates.length > 1) &&
this.videoType === 'youtube'
) {
if (availablePlaybackRates.length === 1 && !this.isTouch) {
......@@ -568,7 +568,7 @@ function (HTML5Video, Resizer) {
_restartUsingFlash(this);
} else if (availablePlaybackRates.length > 1) {
this.currentPlayerMode = 'html5';
this.setPlayerMode('html5');
// We need to synchronize available frame rates with the ones
// that the user specified.
......@@ -607,7 +607,7 @@ function (HTML5Video, Resizer) {
this.trigger('videoSpeedControl.setSpeed', this.speed);
}
if (this.currentPlayerMode === 'html5') {
if (this.isHtml5Mode()) {
this.videoPlayer.player.setPlaybackRate(this.speed);
}
......
......@@ -234,6 +234,7 @@ function (Sjson, AsyncProcess) {
};
}
state.el.removeClass('is-captions-rendered');
// Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button
this.fetchXHR = $.ajaxWithPrefix({
......@@ -447,9 +448,9 @@ function (Sjson, AsyncProcess) {
// outline has to be drawn (tabbing) or not (mouse click).
self.isMouseFocus = false;
self.rendered = true;
self.state.el.addClass('is-captions-rendered');
};
this.rendered = false;
this.subtitlesEl.empty();
this.setSubtitlesHeight();
......
......@@ -10,7 +10,6 @@ in-browser HTML5 video method (when in HTML5 mode).
- Navigational subtitles can be disabled altogether via an attribute
in XML.
"""
import os
import json
import logging
from operator import itemgetter
......@@ -36,8 +35,6 @@ from .video_utils import create_youtube_string
from .video_xfields import VideoFields
from .video_handlers import VideoStudentViewHandlers, VideoStudioViewHandlers
from xmodule.modulestore.inheritance import InheritanceKeyValueStore
from xblock.runtime import KvsFieldData
from urlparse import urlparse
def get_ext(filename):
......
......@@ -310,3 +310,34 @@ Feature: LMS Video component
When I open video "D"
Then the video has rendered in "HTML5" mode
And the video does not show the captions
# 27
Scenario: Transcripts are available on different speeds of Flash mode
Given I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Flash" mode
Then the video has rendered in "Flash" mode
And I make sure captions are opened
And I see "Hi, welcome to Edx." text in the captions
Then I select the "1.50" speed
And I see "Hi, welcome to Edx." text in the captions
Then I select the "0.75" speed
And I see "Hi, welcome to Edx." text in the captions
Then I select the "1.25" speed
And I see "Hi, welcome to Edx." text in the captions
# 28
Scenario: Elapsed time calculates correctly on different speeds of Flash mode
Given I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Flash" mode
And I make sure captions are opened
Then I select the "1.50" speed
And I click video button "pause"
And I click on caption line "4", video module shows elapsed time "7"
Then I select the "0.75" speed
And I click video button "pause"
And I click on caption line "3", video module shows elapsed time "9"
Then I select the "1.25" speed
And I click video button "pause"
And I click on caption line "2", video module shows elapsed time "4"
# -*- coding: utf-8 -*-
# pylint: disable=C0111
from lettuce import world, step, before
from lettuce import world, step, before, after
import json
import os
import time
......@@ -26,6 +26,13 @@ HTML5_SOURCES = [
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.ogv',
]
FLASH_SOURCES = {
'youtube_id_1_0': 'OEoXaMPEzfM',
'youtube_id_0_75': 'JMD_ifUUfsU',
'youtube_id_1_25': 'AKqURZnYqpk',
'youtube_id_1_5': 'DYpADpL7jAY',
}
HTML5_SOURCES_INCORRECT = [
'https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp99',
]
......@@ -52,6 +59,11 @@ def setUp(scenario):
world.video_sequences = {}
@after.each_scenario
def tearDown(scenario):
world.browser.cookies.delete('edX_video_player_mode')
class RequestHandlerWithSessionId(object):
def get(self, url):
"""
......@@ -98,19 +110,6 @@ def get_metadata(parent_location, player_mode, data, display_name='Video'):
'metadata': {},
}
if data:
conversions = {
'transcripts': json.loads,
'download_track': json.loads,
'download_video': json.loads,
}
for key in data:
if key in conversions:
data[key] = conversions[key](data[key])
kwargs['metadata'].update(data)
if player_mode == 'html5':
kwargs['metadata'].update({
'youtube_id_1_0': '',
......@@ -136,6 +135,23 @@ def get_metadata(parent_location, player_mode, data, display_name='Video'):
'html5_sources': HTML5_SOURCES_INCORRECT
})
if player_mode == 'flash':
kwargs['metadata'].update(FLASH_SOURCES)
world.browser.cookies.add({'edX_video_player_mode': 'flash'})
if data:
conversions = {
'transcripts': json.loads,
'download_track': json.loads,
'download_video': json.loads,
}
for key in data:
if key in conversions:
data[key] = conversions[key](data[key])
kwargs['metadata'].update(data)
return kwargs
......@@ -251,6 +267,14 @@ def duration():
return duration
def elapsed_time():
"""
Elapsed time of the video, in seconds.
"""
elapsed_time, duration = video_time()
return elapsed_time
def video_time():
"""
Return a tuple `(elapsed_time, duration)`, each in seconds.
......@@ -273,6 +297,11 @@ def parse_time_str(time_str):
return time_obj.tm_min * 60 + time_obj.tm_sec
def find_caption_line_by_data_index(index):
SELECTOR = ".subtitles > li[data-index='{index}']".format(index=index)
return world.css_find(SELECTOR).first
@step('youtube stub server (.*) YouTube API')
def configure_youtube_api(_step, action):
action=action.strip()
......@@ -349,7 +378,8 @@ def set_youtube_response_timeout(_step, time):
def video_is_rendered(_step, mode):
modes = {
'html5': 'video',
'youtube': 'iframe'
'youtube': 'iframe',
'flash': 'iframe',
}
html_tag = modes[mode.lower()]
assert world.css_find('.video {0}'.format(html_tag)).first
......@@ -360,7 +390,8 @@ def video_is_rendered(_step, mode):
def videos_are_rendered(_step, mode):
modes = {
'html5': 'video',
'youtube': 'iframe'
'youtube': 'iframe',
'flash': 'iframe',
}
html_tag = modes[mode.lower()]
......@@ -423,6 +454,7 @@ def i_see_menu(_step, menu):
@step('I see "([^"]*)" text in the captions$')
def check_text_in_the_captions(_step, text):
world.wait_for_present('.video.is-captions-rendered')
world.wait_for(lambda _: world.css_text('.subtitles'))
actual_text = world.css_text('.subtitles')
assert (text in actual_text)
......@@ -430,6 +462,7 @@ def check_text_in_the_captions(_step, text):
@step('I see text in the captions:')
def check_captions(_step):
world.wait_for_present('.video.is-captions-rendered')
for index, video in enumerate(_step.hashes):
assert (video.get('text') in world.css_text('.subtitles', index=index))
......@@ -439,12 +472,12 @@ def select_language(_step, code):
# Make sure that all ajax requests that affects the language menu are finished.
# For example, request to get new translation etc.
world.wait_for_ajax_complete()
selector = VIDEO_MENUS["language"] + ' li[data-lang-code="{code}"]'.format(
code=code
)
world.css_find(VIDEO_BUTTONS["CC"])[0].mouse_over()
world.wait_for_present('.lang.open')
world.css_click(selector)
assert world.css_has_class(selector, 'active')
......@@ -454,6 +487,7 @@ def select_language(_step, code):
# For example, request to get new translation etc.
world.wait_for_ajax_complete()
world.wait_for_visible('.subtitles')
world.wait_for_present('.video.is-captions-rendered')
@step('I click video button "([^"]*)"$')
......@@ -472,10 +506,12 @@ def start_playing_video_from_n_seconds(_step, position):
@step('I see duration "([^"]*)"$')
def i_see_duration(_step, position):
world.wait_for(
func=lambda _: duration() == parse_time_str(position),
func=lambda _: duration() > 0,
timeout=30
)
assert duration() == parse_time_str(position)
@step('I seek video to "([^"]*)" seconds$')
def seek_video_to_n_seconds(_step, seconds):
......@@ -507,14 +543,11 @@ def video_alignment(_step, transcript_visibility):
set_window_dimensions(300, 600)
real, expected = get_all_dimensions()
width = round(100 * real['width']/expected['width']) == wrapper_width
set_window_dimensions(600, 300)
real, expected = get_all_dimensions()
height = abs(expected['height'] - real['height']) <= 5
# Restore initial window size
set_window_dimensions(initial['width'], initial['height'])
......@@ -569,3 +602,12 @@ def shows_captions(_step, show_captions):
assert world.is_css_present('div.video.closed')
else:
assert world.is_css_not_present('div.video.closed')
@step('I click on caption line "([^"]*)", video module shows elapsed time "([^"]*)"$')
def click_on_the_caption(_step, index, expected_time):
world.wait_for_present('.video.is-captions-rendered')
find_caption_line_by_data_index(int(index)).click()
actual_time = elapsed_time()
assert int(expected_time) == actual_time
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