Using youtube api (v3) instead of v2 to get the video duration .

TNL-2413
This commit is contained in:
muzaffaryousaf
2015-06-23 14:23:15 +05:00
parent 69a1a069e9
commit 7dfd567cb5
30 changed files with 222 additions and 148 deletions

View File

@@ -28,8 +28,7 @@ SERVICES = {
YOUTUBE_API_URLS = {
'main': 'https://www.youtube.com/',
'player': 'http://www.youtube.com/iframe_api',
'metadata': 'http://gdata.youtube.com/feeds/api/videos/',
'player': 'https://www.youtube.com/iframe_api',
# For transcripts, you need to check an actual video, so we will
# just specify our default video and see if that one is available.
'transcript': 'http://video.google.com/timedtext?lang=en&v=OEoXaMPEzfM',

View File

@@ -95,6 +95,9 @@ class StubYouTubeHandler(StubHttpRequestHandler):
if self.server.config.get('youtube_api_blocked'):
self.send_response(404, content='', headers={'Content-type': 'text/plain'})
else:
# Delay the response to simulate network latency
time.sleep(self.server.config.get('time_to_response', self.DEFAULT_DELAY_SEC))
# Get the response to send from YouTube.
# We need to do this every time because Google sometimes sends different responses
# as part of their own experiments, which has caused our tests to become "flaky"
@@ -117,17 +120,16 @@ class StubYouTubeHandler(StubHttpRequestHandler):
# Construct the response content
callback = self.get_params['callback']
youtube_metadata = json.loads(
requests.get(
"http://gdata.youtube.com/feeds/api/videos/{id}?v=2&alt=jsonc".format(id=youtube_id)
).text
)
data = OrderedDict({
'data': OrderedDict({
'id': youtube_id,
'message': message,
'duration': youtube_metadata['data']['duration'],
})
'items': list(
OrderedDict({
'contentDetails': OrderedDict({
'id': youtube_id,
'duration': 'PT2M20S',
})
})
)
})
response = "{cb}({data})".format(cb=callback, data=json.dumps(data))

View File

@@ -4,7 +4,7 @@
<div
id="video_id"
class="video closed"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": "[]", "speed": "1.5", "startTime": "", "streams": "0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": "[]", "speed": "1.5", "startTime": "", "streams": "0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="focus_grabber first"></div>

View File

@@ -4,7 +4,7 @@
<div
id="video_id"
class="video closed"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="focus_grabber first"></div>

View File

@@ -4,7 +4,7 @@
<div
id="video_id"
class="video closed"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/", "source": "", "html5_sources": ["http://youtu.be/3_yD_cEKoCk.mp4"]}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/", "source": "", "html5_sources": ["http://youtu.be/3_yD_cEKoCk.mp4"]}'
>
<div class="focus_grabber first"></div>

View File

@@ -4,7 +4,7 @@
<div
id="video_id"
class="video closed"
data-metadata='{"streams":"0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "showCaptions": false, "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "speed": "1.5", "startTime": "", "end": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"streams":"0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "showCaptions": false, "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "speed": "1.5", "startTime": "", "end": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="focus_grabber first"></div>

View File

@@ -4,7 +4,7 @@
<div
id="video_id"
class="video closed"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "", "sub": "Z5KLxerq05Y", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
data-bumper-metadata='{"transcriptLanguage": "en", "showCaptions": "true", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "transcriptTranslationUrl": "/transcript/translation/__lang__/?is_bumper=1", "transcriptAvailableTranslationsUrl": "/transcript/available_translations/?is_bumper=1", "streams": "", "saveStateUrl": "/save_user_state"}'
data-poster='{"url": "xmodule/include/fixtures/poster.jpg", "type": "youtube"}'
>

View File

@@ -4,7 +4,7 @@
<div
id="video_id1"
class="video closed"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.5", "startTime": "", "streams": "0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="focus_grabber first"></div>
@@ -38,7 +38,7 @@
<div
id="video_id2"
class="video"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.0", "startTime": "", "streams": "0.75:7tqY6eQzVhE,1.0:cogebirgzzM", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.0", "startTime": "", "streams": "0.75:7tqY6eQzVhE,1.0:cogebirgzzM", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="tc-wrapper">
<article class="video-wrapper">
@@ -68,7 +68,7 @@
<div
id="video_id3"
class="video"
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.0", "startTime": "", "streams": "0.75:7tqY6eQzVhE,1.0:cogebirgzzM", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytTestUrl": "gdata.youtube.com/feeds/api/videos/"}'
data-metadata='{"autohideHtml5": "true", "autoplay": "false", "captionDataDir": "", "endTime": "", "generalSpeed": "1.0", "saveStateUrl": "/save_user_state", "savedVideoPosition": "0", "showCaptions": "true", "sources": ["xmodule/include/fixtures/test.mp4","xmodule/include/fixtures/test.webm","xmodule/include/fixtures/test.ogv"], "speed": "1.0", "startTime": "", "streams": "0.75:7tqY6eQzVhE,1.0:cogebirgzzM", "sub": "", "transcriptAvailableTranslationsUrl": "/transcript/available_translations", "transcriptLanguage": "en", "transcriptLanguages": {"en": "English", "de": "Deutsch", "zh": "普通话"}, "transcriptTranslationUrl": "/transcript/translation/__lang__", "ytApiUrl": "www.youtube.com/iframe_api", "ytImageUrl": "", "ytTestTimeout": "1500", "ytMetadataUrl": "www.googleapis.com/youtube/v3/videos/"}'
>
<div class="tc-wrapper">
<article class="video-wrapper">

View File

@@ -59,6 +59,7 @@ lib_paths:
- common_static/js/src/utility.js
- public/js/split_test_staff.js
- common_static/js/src/accessibility_tools.js
- common_static/js/vendor/moment.min.js
# Paths to spec (test) JavaScript files
spec_paths:

View File

@@ -36,7 +36,7 @@
return f();
}
};
jasmine.YT = stubbedYT;
// Stub YouTube API.
window.YT = stubbedYT;
@@ -76,19 +76,27 @@
jasmine.stubbedMetadata = {
'7tqY6eQzVhE': {
id: '7tqY6eQzVhE',
duration: 300
contentDetails : {
id: '7tqY6eQzVhE',
duration: 'PT5M0S'
}
},
'cogebirgzzM': {
id: 'cogebirgzzM',
duration: 200
contentDetails : {
id: 'cogebirgzzM',
duration: 'PT3M20S'
}
},
'abcdefghijkl': {
id: 'abcdefghijkl',
duration: 400
contentDetails : {
id: 'abcdefghijkl',
duration: 'PT6M40S'
}
},
bogus: {
duration: 100
contentDetails : {
duration: 'PT1M40S'
}
}
};
@@ -122,7 +130,7 @@
}
return spy.andCallFake(function (settings) {
var match = settings.url
.match(/youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/),
.match(/googleapis\.com\/.+\/videos\/\?id=(.+)&part=contentDetails/),
status, callCallback;
if (match) {
status = match[1].split('_');
@@ -138,7 +146,7 @@
};
} else if (settings.success) {
return settings.success({
data: jasmine.stubbedMetadata[match[1]]
items: jasmine.stubbedMetadata[match[1]]
});
} else {
return {
@@ -167,15 +175,6 @@
// Do nothing.
return;
} else if (settings.url === '/save_user_state') {
return {success: true};
} else if (settings.url === 'http://www.youtube.com/iframe_api') {
// Stub YouTube API.
window.YT = stubbedYT;
// Call the callback that must be called when YouTube API is
// loaded. By specification.
window.onYouTubeIframeAPIReady();
return {success: true};
} else {
throw 'External request attempted for ' +
@@ -224,6 +223,19 @@
// Stub jQuery.scrollTo module.
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo');
// Stub window.Video.loadYouTubeIFrameAPI()
window.Video.loadYouTubeIFrameAPI = jasmine.createSpy('window.Video.loadYouTubeIFrameAPI').andReturn(
function (scriptTag) {
var event = document.createEvent('Event');
if (fixture === "video.html") {
event.initEvent('load', false, false);
} else {
event.initEvent('error', false, false);
}
scriptTag.dispatchEvent(event);
}
);
jasmine.initializePlayer = function (fixture, params) {
var state;

View File

@@ -115,6 +115,12 @@
return state.youtubeApiAvailable === true;
}, 'YouTube API is loaded', 3000);
window.YT = jasmine.YT;
// Call the callback that must be called when YouTube API is
// loaded. By specification.
window.onYouTubeIframeAPIReady();
runs(function () {
// If YouTube API is not loaded, then the code will should create
// a global callback that will be called by API once it is loaded.

View File

@@ -71,10 +71,10 @@ function (Initialize) {
speed: '1.50',
metadata: {
'testId': {
duration: 400
duration: 'PT6M40S'
},
'videoId': {
duration: 100
duration: 'PT1M40S'
}
},
videos: {

View File

@@ -16,6 +16,8 @@ define(
'video/01_initialize.js',
['video/03_video_player.js', 'video/00_i18n.js'],
function (VideoPlayer, i18n) {
var moment = window.moment;
/**
* @function
*
@@ -31,6 +33,9 @@ function (VideoPlayer, i18n) {
state.initialize(element)
.done(function () {
if (state.isYoutubeType()) {
state.parseSpeed();
}
// On iPhones and iPods native controls are used.
if (/iP(hone|od)/i.test(state.isTouch[0])) {
_hideWaitPlaceholder(state);
@@ -75,7 +80,10 @@ function (VideoPlayer, i18n) {
setSpeed: setSpeed,
speedToString: speedToString,
trigger: trigger,
youtubeId: youtubeId
youtubeId: youtubeId,
loadHtmlPlayer: loadHtmlPlayer,
loadYoutubePlayer: loadYoutubePlayer,
loadYouTubeIFrameAPI: loadYouTubeIFrameAPI
},
_youtubeApiDeferred = null,
@@ -126,6 +134,9 @@ function (VideoPlayer, i18n) {
onYTApiReady = function () {
console.log('[Video info]: YouTube API is available and is loaded.');
if (state.htmlPlayerLoaded) { return; }
console.log('[Video info]: Starting YouTube player.');
video = VideoPlayer(state);
state.modules.push(video);
@@ -176,7 +187,6 @@ function (VideoPlayer, i18n) {
if (!_youtubeApiDeferred) {
_youtubeApiDeferred = $.Deferred();
setupOnYouTubeIframeAPIReady();
_loadYoutubeApi(state);
} else if (!window.onYouTubeIframeAPIReady || !window.onYouTubeIframeAPIReady.done) {
// The Deferred object could have been already defined in a previous
// initialization of the video module. However, since then the global variable
@@ -196,24 +206,32 @@ function (VideoPlayer, i18n) {
state.modules.push(video);
state.__dfd__.resolve();
state.htmlPlayerLoaded = true;
}
}
function _loadYoutubeApi(state) {
console.log('[Video info]: YouTube API is not loaded. Will try to load...');
function _waitForYoutubeApi(state) {
console.log('[Video info]: Starting to wait for YouTube API to load.');
window.setTimeout(function () {
// If YouTube API will load OK, it will run `onYouTubeIframeAPIReady`
// callback, which will set `state.youtubeApiAvailable` to `true`.
// If something goes wrong at this stage, `state.youtubeApiAvailable` is
// `false`.
if (!state.youtubeIsAvailable) {
if (!state.youtubeApiAvailable) {
console.log('[Video info]: YouTube API is not available.');
if (!state.htmlPlayerLoaded) {
state.loadHtmlPlayer();
}
}
state.el.trigger('youtube_availability', [state.youtubeIsAvailable]);
state.el.trigger('youtube_availability', [state.youtubeApiAvailable]);
}, state.config.ytTestTimeout);
$.getScript(document.location.protocol + '//' + state.config.ytApiUrl);
}
function loadYouTubeIFrameAPI(scriptTag) {
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag);
}
// function _configureCaptions(state)
@@ -454,6 +472,50 @@ function (VideoPlayer, i18n) {
});
}
function loadYoutubePlayer() {
if (this.htmlPlayerLoaded) { return; }
console.log(
'[Video info]: Fetch metadata for YouTube video.'
);
this.fetchMetadata();
this.parseSpeed();
}
function loadHtmlPlayer() {
// When the youtube link doesn't work for any reason
// (for example, firewall) any
// alternate sources should automatically play.
if (!_prepareHTML5Video(this)) {
console.log(
'[Video info]: Continue loading ' +
'YouTube video.'
);
// 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.loadYoutubePlayer();
} else {
console.log(
'[Video info]: Start HTML5 player.'
);
// In-browser HTML5 player does not support quality
// control.
this.el.find('a.quality_control').hide();
_renderElements(this);
}
}
// function initialize(element)
// The function set initial configuration and preparation.
@@ -484,7 +546,7 @@ function (VideoPlayer, i18n) {
// jQuery .data() return object with keys in lower camelCase format.
this.config = $.extend({}, _getConfiguration(this.metadata, this.storage), {
element: element,
fadeOutTimeout: 1400,
fadeOutTimeout: 1400,
captionsFreezeTime: 10000,
mode: $.cookie('edX_video_player_mode'),
// Available HD qualities will only be accessible once the video has
@@ -500,6 +562,9 @@ function (VideoPlayer, i18n) {
this.speed = this.speedToString(
this.config.speed || this.config.generalSpeed
);
this.htmlPlayerLoaded = false;
_setConfigurations(this);
if (!(_parseYouTubeIDs(this))) {
@@ -512,67 +577,30 @@ function (VideoPlayer, i18n) {
}
console.log('[Video info]: Start player in HTML5 mode.');
_setConfigurations(this);
_renderElements(this);
} else {
if (!this.youtubeXhr) {
this.youtubeXhr = this.getVideoMetadata();
}
_renderElements(this);
this.youtubeXhr
.always(function (json, status) {
// It will work for both if statusCode is 200 or 410.
var didSucceed = (json.error && json.error.code === 410) || status === 'success' || status === 'notmodified';
if (!didSucceed) {
console.log(
'[Video info]: YouTube returned an error for ' +
'video with id "' + id + '".'
);
_waitForYoutubeApi(this);
// When the youtube link doesn't work for any reason
// (for example, the great firewall in china) any
// alternate sources should automatically play.
if (!_prepareHTML5Video(self)) {
console.log(
'[Video info]: Continue loading ' +
'YouTube video.'
);
var scriptTag = document.createElement('script');
// Non-YouTube sources were not found either.
scriptTag.src = this.config.ytApiUrl;
scriptTag.async = true;
el.find('.video-player div')
.removeClass('hidden');
el.find('.video-player h3')
.addClass('hidden');
$(scriptTag).on('load', function() {
self.loadYoutubePlayer();
});
$(scriptTag).on('error', function() {
console.log(
'[Video info]: YouTube returned an error for ' +
'video with id "' + self.id + '".'
);
self.loadHtmlPlayer();
});
// If in reality the timeout was to short, try to
// continue loading the YouTube video anyways.
self.fetchMetadata();
self.parseSpeed();
} else {
console.log(
'[Video info]: Change player mode to HTML5.'
);
// In-browser HTML5 player does not support quality
// control.
el.find('a.quality_control').hide();
}
} else {
console.log(
'[Video info]: Start player in YouTube mode.'
);
self.fetchMetadata();
self.parseSpeed();
}
_setConfigurations(self);
_renderElements(self);
});
window.Video.loadYouTubeIFrameAPI(scriptTag);
}
return __dfd__.promise();
}
@@ -619,8 +647,9 @@ function (VideoPlayer, i18n) {
metadataXHRs = _.map(this.videos, function (url, speed) {
return self.getVideoMetadata(url, function (data) {
if (data.data) {
self.metadata[data.data.id] = data.data;
if (data.items.length > 0) {
var metaDataItem = data.items[0];
self.metadata[metaDataItem.id] = metaDataItem.contentDetails;
}
});
});
@@ -671,16 +700,16 @@ function (VideoPlayer, i18n) {
if (!(_.isString(url))) {
url = this.videos['1.0'] || '';
}
return $.ajax({
url: [
document.location.protocol, '//', this.config.ytTestUrl, url,
'?v=2&alt=jsonc'
].join(''),
dataType: 'jsonp',
timeout: this.config.ytTestTimeout,
success: _.isFunction(callback) ? callback : null
});
// Will hit the API URL iF YT key is defined in settings.
if (this.config.ytKey) {
return $.ajax({
url: [this.config.ytMetadataUrl, '?id=', url, '&part=contentDetails&key=', this.config.ytKey].join(''),
timeout: this.config.ytTestTimeout,
success: _.isFunction(callback) ? callback : null
});
} else {
return $.Deferred().reject().promise();
}
}
function youtubeId(speed) {
@@ -693,7 +722,7 @@ function (VideoPlayer, i18n) {
function getDuration() {
try {
return this.metadata[this.youtubeId()].duration;
return moment.duration(this.metadata[this.youtubeId()].duration, moment.ISO_8601).asSeconds();
} catch (err) {
return _.result(this.metadata[this.youtubeId('1.0')], 'duration') || 0;
}

View File

@@ -160,6 +160,8 @@
youtubeXhr = null;
};
window.Video.loadYouTubeIFrameAPI = initialize.prototype.loadYouTubeIFrameAPI;
// Invoke the mock Video constructor so that the elements stored within it can be processed by the real
// `window.Video` constructor.
oldVideo(null, true);

View File

@@ -736,8 +736,8 @@ class VideoDescriptorIndexingTestCase(unittest.TestCase):
# YouTube JavaScript API
'API': 'www.youtube.com/iframe_api',
# URL to test YouTube availability
'TEST_URL': 'gdata.youtube.com/feeds/api/videos/',
# URL to get YouTube metadata
'METADATA_URL': 'www.googleapis.com/youtube/v3/videos/',
# Current youtube api for requesting transcripts.
# For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g.

View File

@@ -86,6 +86,7 @@ log = logging.getLogger(__name__)
_ = lambda text: text
@XBlock.wants('settings')
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin):
"""
XML source example:
@@ -261,6 +262,15 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
cdn_exp_group = None
self.youtube_streams = youtube_streams or create_youtube_string(self) # pylint: disable=W0201
settings_service = self.runtime.service(self, 'settings')
yt_api_key = None
if settings_service:
xblock_settings = settings_service.get_settings_bucket(self)
if xblock_settings and 'YOUTUBE_API_KEY' in xblock_settings:
yt_api_key = xblock_settings['YOUTUBE_API_KEY']
metadata = {
'saveStateUrl': self.system.ajax_url + '/save_user_state',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
@@ -286,7 +296,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
'ytTestTimeout': 1500,
'ytApiUrl': settings.YOUTUBE['API'],
'ytTestUrl': settings.YOUTUBE['TEST_URL'],
'ytMetadataUrl': settings.YOUTUBE['METADATA_URL'],
'ytKey': yt_api_key,
'transcriptTranslationUrl': self.runtime.handler_url(
self, 'transcript', 'translation/__lang__'
).rstrip('/?'),

View File

@@ -67,8 +67,7 @@ def is_youtube_available():
youtube_api_urls = {
'main': 'https://www.youtube.com/',
'player': 'http://www.youtube.com/iframe_api',
'metadata': 'http://gdata.youtube.com/feeds/api/videos/',
'player': 'https://www.youtube.com/iframe_api',
# For transcripts, you need to check an actual video, so we will
# just specify our default video and see if that one is available.
'transcript': 'http://video.google.com/timedtext?lang=en&v=3_yD_cEKoCk',