Fix multiple video bug

This commit is contained in:
Anton Stupak
2013-08-30 09:51:48 +03:00
committed by Alexander Kryklia
parent 14708aa30b
commit a09e610403
22 changed files with 463 additions and 90 deletions

View File

@@ -40,6 +40,12 @@ div.video {
padding-bottom: 56.25%;
position: relative;
div {
&.hidden {
display: none;
}
}
object, iframe {
border: none;
height: 100%;
@@ -48,6 +54,15 @@ div.video {
top: 0;
width: 100%;
}
h3 {
text-align: center;
color: white;
&.hidden {
display: none;
}
}
}
section.video-controls {
@@ -516,6 +531,12 @@ div.video {
height: 0px;
}
article.video-wrapper section.video-player {
h3 {
color: black;
}
}
ol.subtitles {
width: 0;
height: 0;
@@ -563,6 +584,12 @@ div.video {
position: static;
}
article.video-wrapper section.video-player {
h3 {
color: white;
}
}
div.tc-wrapper {
@include clearfix;
display: table;

View File

@@ -10,6 +10,8 @@
data-end=""
data-caption-asset-path="/static/subs/"
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
>
<div class="tc-wrapper">
<article class="video-wrapper">

View File

@@ -13,6 +13,8 @@
data-webm-source="test_files/test.webm"
data-ogg-source="test_files/test.ogv"
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
>
<div class="tc-wrapper">
<article class="video-wrapper">
@@ -55,4 +57,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -13,6 +13,8 @@
data-webm-source="test_files/test.webm"
data-ogg-source="test_files/test.ogv"
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
>
<div class="tc-wrapper">
<article class="video-wrapper">
@@ -27,4 +29,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -10,6 +10,8 @@
data-end=""
data-caption-asset-path="/static/subs/"
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
>
<div class="tc-wrapper">
<article class="video-wrapper">

View File

@@ -90,12 +90,24 @@ jasmine.stubbedHtml5Speeds = ['0.75', '1.0', '1.25', '1.50']
jasmine.stubRequests = ->
spyOn($, 'ajax').andCallFake (settings) ->
if match = settings.url.match /youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/
if settings.success
status = match[1].split('_')
if status and status[0] is 'status'
{
always: (callback) ->
callback.call(window, {}, status[1])
error: (callback) ->
callback.call(window, {}, status[1])
done: (callback) ->
callback.call(window, {}, status[1])
}
else if settings.success
# match[1] - it's video ID
settings.success data: jasmine.stubbedMetadata[match[1]]
else {
always: (callback) ->
callback.call(window, {}, 'success');
callback.call(window, {}, 'success')
done: (callback) ->
callback.call(window, {}, 'success')
}
else if match = settings.url.match /static(\/.*)?\/subs\/(.+)\.srt\.sjson/
settings.success jasmine.stubbedCaption

View File

@@ -55,46 +55,6 @@
expect(this.state.speed).toEqual('0.75');
});
});
describe('Check Youtube link existence', function () {
var statusList = {
error: 'html5',
timeout: 'html5',
abort: 'html5',
parsererror: 'html5',
success: 'youtube',
notmodified: 'youtube'
};
function stubDeffered(data, status) {
return {
always: function(callback) {
callback.call(window, data, status);
}
}
}
function checkPlayer(videoType, data, status) {
this.state = new window.Video('#example');
spyOn(this.state , 'getVideoMetadata')
.andReturn(stubDeffered(data, status));
this.state.initialize('#example');
expect(this.state.videoType).toEqual(videoType);
}
it('if video id is incorrect', function () {
checkPlayer('html5', { error: {} }, 'success');
});
$.each(statusList, function(status, mode){
it('Status:' + status + ', mode:' + mode, function () {
checkPlayer(mode, {}, status);
});
});
});
});
describe('HTML5', function () {
@@ -154,10 +114,22 @@
it('parse Html5 sources', function () {
var html5Sources = {
mp4: 'test_files/test.mp4',
webm: 'test_files/test.webm',
ogg: 'test_files/test.ogv'
};
mp4: null,
webm: null,
ogg: null
}, v = document.createElement('video');
if (!!(v.canPlayType && v.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''))) {
html5Sources['webm'] = 'xmodule/include/fixtures/test.webm';
}
if (!!(v.canPlayType && v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''))) {
html5Sources['mp4'] = 'xmodule/include/fixtures/test.mp4';
}
if (!!(v.canPlayType && v.canPlayType('video/ogg; codecs="theora"').replace(/no/, ''))) {
html5Sources['ogg'] = 'xmodule/include/fixtures/test.ogv';
}
expect(state.html5Sources).toEqual(html5Sources);
});

View File

@@ -143,8 +143,6 @@ function (VideoPlayer) {
if (state.parseYoutubeStreams(state.config.youtubeStreams)) {
state.videoType = 'youtube';
state.fetchMetadata();
state.parseSpeed();
return true;
}
return false;
@@ -153,9 +151,7 @@ function (VideoPlayer) {
// function _prepareHTML5Video(state)
// The function prepare HTML5 video, parse HTML5
// video sources etc.
function _prepareHTML5Video(state) {
state.videoType = 'html5';
function _prepareHTML5Video(state, html5Mode) {
state.parseVideoSources(
{
mp4: state.config.mp4Source,
@@ -164,20 +160,39 @@ function (VideoPlayer) {
}
);
if (html5Mode) {
state.speeds = ['0.75', '1.0', '1.25', '1.50'];
state.videos = {
'0.75': state.config.sub,
'1.0': state.config.sub,
'1.25': state.config.sub,
'1.5': state.config.sub
};
}
// We must have at least one non-YouTube video source available.
// Otherwise, return a negative.
if (
state.html5Sources.webm === null &&
state.html5Sources.mp4 === null &&
state.html5Sources.ogg === null
) {
state.el.find('.video-player div').addClass('hidden');
state.el.find('.video-player h3').removeClass('hidden');
return false;
}
state.videoType = 'html5';
if (!state.config.sub || !state.config.sub.length) {
state.config.sub = '';
state.config.show_captions = false;
}
state.speeds = ['0.75', '1.0', '1.25', '1.50'];
state.videos = {
'0.75': state.config.sub,
'1.0': state.config.sub,
'1.25': state.config.sub,
'1.5': state.config.sub
};
state.setSpeed($.cookie('video_speed'));
return true;
}
function _setConfigurations(state) {
@@ -201,7 +216,7 @@ function (VideoPlayer) {
// The function set initial configuration and preparation.
function initialize(element) {
var _this = this;
var _this = this, tempYtTestTimeout;
// This is used in places where we instead would have to check if an element has a CSS class 'fullscreen'.
this.isFullScreen = false;
@@ -227,28 +242,61 @@ function (VideoPlayer) {
webmSource: this.el.data('webm-source'),
oggSource: this.el.data('ogg-source'),
ytTestUrl: this.el.data('yt-test-url'),
fadeOutTimeout: 1400,
availableQualities: ['hd720', 'hd1080', 'highres']
};
// Check if the YT test timeout has been set. If not, or it is in
// improper format, then set to default value.
tempYtTestTimeout = parseInt(this.el.data('yt-test-timeout'), 10);
if (!isFinite(tempYtTestTimeout)) {
tempYtTestTimeout = 1500;
}
this.config.ytTestTimeout = tempYtTestTimeout;
if (!(_parseYouTubeIDs(this))) {
// If we do not have YouTube ID's, try parsing HTML5 video sources.
_prepareHTML5Video(this);
if (!_prepareHTML5Video(this, true)) {
// Non-YouTube sources were not found either.
return;
}
_setConfigurations(this);
_renderElements(this);
} else {
this.getVideoMetadata()
if (!this.youtubeXhr) {
this.youtubeXhr = this.getVideoMetadata();
}
this.youtubeXhr
.always(function(json, status) {
var err = $.isPlainObject(json.error) ||
(status !== "success" && status !== "notmodified");
if (err){
(status !== 'success' && status !== 'notmodified');
if (err) {
// When the youtube link doesn't work for any reason
// (for example, the great firewall in china) any
// alternate sources should automatically play.
_prepareHTML5Video(_this);
_this.el.find('a.quality_control').hide();
if (!_prepareHTML5Video(_this)) {
// Non-YouTube sources were not found either.
_this.el.find('.video-player div').removeClass('hidden');
_this.el.find('.video-player h3').addClass('hidden');
// If in reality the timeout was to short, try to
// continue loading the YouTube video anyways.
_this.fetchMetadata();
_this.parseSpeed();
} else {
// In-browser HTML5 player does not support quality
// control.
_this.el.find('a.quality_control').hide();
}
} else {
_this.fetchMetadata();
_this.parseSpeed();
}
_setConfigurations(_this);
@@ -294,7 +342,13 @@ function (VideoPlayer) {
// Take the HTML5 sources (URLs of videos), and make them available explictly for each type
// of video format (mp4, webm, ogg).
function parseVideoSources(sources) {
var _this = this;
var _this = this,
v = document.createElement('video'),
sourceCodecs = {
mp4: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
webm: 'video/webm; codecs="vp8, vorbis"',
ogg: 'video/ogg; codecs="theora"'
};
this.html5Sources = {
mp4: null,
@@ -304,7 +358,14 @@ function (VideoPlayer) {
$.each(sources, function (name, source) {
if (source && source.length) {
_this.html5Sources[name] = source;
if (
Boolean(
v.canPlayType &&
v.canPlayType(sourceCodecs[name]).replace(/no/, '')
)
) {
_this.html5Sources[name] = source;
}
}
});
}
@@ -321,7 +382,9 @@ function (VideoPlayer) {
$.each(this.videos, function (speed, url) {
_this.getVideoMetadata(url, function(data) {
_this.metadata[data.data.id] = data.data;
if (data.data) {
_this.metadata[data.data.id] = data.data;
}
});
});
}
@@ -358,12 +421,11 @@ function (VideoPlayer) {
if (typeof url !== 'string') {
url = this.videos['1.0'] || '';
}
successHandler = ($.isFunction(callback)) ? callback : null;
xhr = $.ajax({
url: 'https://gdata.youtube.com/feeds/api/videos/' + url + '?v=2&alt=jsonc',
timeout: 500,
url: this.config.ytTestUrl + url + '?v=2&alt=jsonc',
dataType: 'jsonp',
timeout: this.config.ytTestTimeout,
success: successHandler
});

View File

@@ -10,21 +10,31 @@ function () {
return function (state) {
state.videoSpeedControl = {};
if (state.videoType === 'html5') {
_initialize(state);
} else if (state.videoType === 'youtube' && state.youtubeXhr) {
state.youtubeXhr.done(function () {
_initialize(state);
});
}
if (state.videoType === 'html5' && !(_checkPlaybackRates())) {
_hideSpeedControl(state);
return;
}
_makeFunctionsPublic(state);
_renderElements(state);
_bindHandlers(state);
};
// ***************************************************************
// Private functions start here.
// ***************************************************************
function _initialize(state) {
_makeFunctionsPublic(state);
_renderElements(state);
_bindHandlers(state);
}
// function _makeFunctionsPublic(state)
//
// Functions which will be accessible via 'state' object. When called,

View File

@@ -20,7 +20,8 @@ function (
VideoSpeedControl,
VideoCaption
) {
var previousState;
var previousState,
youtubeXhr = null;
// Because this constructor can be called multiple times on a single page (when
// the user switches verticals, the page doesn't reload, but the content changes), we must
@@ -53,7 +54,11 @@ function (
state = {};
previousState = state;
state.youtubeXhr = youtubeXhr;
Initialize(state, element);
if (!youtubeXhr) {
youtubeXhr = state.youtubeXhr;
}
VideoControl(state);
VideoQualityControl(state);
@@ -67,6 +72,10 @@ function (
// Video with Jasmine.
return state;
};
window.Video.clearYoutubeXhr = function () {
youtubeXhr = null;
};
});
}(RequireJS.requirejs, RequireJS.require, RequireJS.define));

View File

@@ -167,6 +167,12 @@ class VideoModule(VideoFields, XModule):
sources = {get_ext(src): src for src in self.html5_sources}
sources['main'] = self.source
# for testing Youtube timeout in acceptance tests
if getattr(settings, 'VIDEO_PORT', None):
yt_test_url = "http://127.0.0.1:" + str(settings.VIDEO_PORT) + '/test_youtube/'
else:
yt_test_url = 'https://gdata.youtube.com/feeds/api/videos/'
return self.system.render_template('video.html', {
'youtube_streams': _create_youtube_string(self),
'id': self.location.html_id(),
@@ -181,7 +187,11 @@ class VideoModule(VideoFields, XModule):
'show_captions': json.dumps(self.show_captions),
'start': self.start_time,
'end': self.end_time,
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True)
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True),
# TODO: Later on the value 1500 should be taken from some global
# configuration setting field.
'yt_test_timeout': 1500,
'yt_test_url': yt_test_url
})