diff --git a/common/djangoapps/terrain/setup_prereqs.py b/common/djangoapps/terrain/setup_prereqs.py
index 98f5c6798373..90337c386775 100644
--- a/common/djangoapps/terrain/setup_prereqs.py
+++ b/common/djangoapps/terrain/setup_prereqs.py
@@ -28,8 +28,7 @@
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',
diff --git a/common/djangoapps/terrain/stubs/youtube.py b/common/djangoapps/terrain/stubs/youtube.py
index af3a87f5ecdd..27f9d5ef2e1d 100644
--- a/common/djangoapps/terrain/stubs/youtube.py
+++ b/common/djangoapps/terrain/stubs/youtube.py
@@ -95,6 +95,9 @@ def do_GET(self):
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 @@ def _send_video_response(self, youtube_id, message):
# 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))
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html
index dabb3801b97f..f7d04cce65cc 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video.html
@@ -4,7 +4,7 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_all.html b/common/lib/xmodule/xmodule/js/fixtures/video_all.html
index 617d95835760..5b20840630c8 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_all.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_all.html
@@ -4,7 +4,7 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
index 47be4f04fcb9..f82451e377cf 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
@@ -4,7 +4,7 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
index 77017d403dd1..267897e5988f 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
@@ -4,7 +4,7 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_with_bumper.html b/common/lib/xmodule/xmodule/js/fixtures/video_with_bumper.html
index 22bd2062689f..efada9d13fc4 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_with_bumper.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_with_bumper.html
@@ -4,7 +4,7 @@
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
index 8842b1e5926f..0af562049f5f 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
@@ -4,7 +4,7 @@
@@ -38,7 +38,7 @@
@@ -68,7 +68,7 @@
diff --git a/common/lib/xmodule/xmodule/js/js_test.yml b/common/lib/xmodule/xmodule/js/js_test.yml
index 6af10abaa553..6c72d4b64930 100644
--- a/common/lib/xmodule/xmodule/js/js_test.yml
+++ b/common/lib/xmodule/xmodule/js/js_test.yml
@@ -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:
diff --git a/common/lib/xmodule/xmodule/js/spec/helper.js b/common/lib/xmodule/xmodule/js/spec/helper.js
index 97d422d5d891..9c83fd006306 100644
--- a/common/lib/xmodule/xmodule/js/spec/helper.js
+++ b/common/lib/xmodule/xmodule/js/spec/helper.js
@@ -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;
diff --git a/common/lib/xmodule/xmodule/js/spec/video/general_spec.js b/common/lib/xmodule/xmodule/js/spec/video/general_spec.js
index 7cefab5e4999..f9c0eabf2dfa 100644
--- a/common/lib/xmodule/xmodule/js/spec/video/general_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/video/general_spec.js
@@ -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.
diff --git a/common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js b/common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js
index f3194b8bce3a..040400514472 100644
--- a/common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/video/initialize_spec.js
@@ -71,10 +71,10 @@ function (Initialize) {
speed: '1.50',
metadata: {
'testId': {
- duration: 400
+ duration: 'PT6M40S'
},
'videoId': {
- duration: 100
+ duration: 'PT1M40S'
}
},
videos: {
diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
index 2a30ceb45452..cc43a2335674 100644
--- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
@@ -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 + '".'
- );
-
- // 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.'
- );
-
- // Non-YouTube sources were not found either.
-
- el.find('.video-player div')
- .removeClass('hidden');
- el.find('.video-player h3')
- .addClass('hidden');
-
- // 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.'
- );
+ _waitForYoutubeApi(this);
- // 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.'
- );
+ var scriptTag = document.createElement('script');
- self.fetchMetadata();
- self.parseSpeed();
- }
+ scriptTag.src = this.config.ytApiUrl;
+ scriptTag.async = true;
- _setConfigurations(self);
- _renderElements(self);
- });
- }
+ $(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();
+ });
+ 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;
}
diff --git a/common/lib/xmodule/xmodule/js/src/video/10_main.js b/common/lib/xmodule/xmodule/js/src/video/10_main.js
index be3bbc8ae87f..23763aaf1c2d 100644
--- a/common/lib/xmodule/xmodule/js/src/video/10_main.js
+++ b/common/lib/xmodule/xmodule/js/src/video/10_main.js
@@ -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);
diff --git a/common/lib/xmodule/xmodule/tests/test_video.py b/common/lib/xmodule/xmodule/tests/test_video.py
index 269adf6109bf..5b0ae3b2fe8c 100644
--- a/common/lib/xmodule/xmodule/tests/test_video.py
+++ b/common/lib/xmodule/xmodule/tests/test_video.py
@@ -736,8 +736,8 @@ def setUp(self):
# 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.
diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py
index f9134c820cbe..a85e14f9e098 100644
--- a/common/lib/xmodule/xmodule/video_module/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module/video_module.py
@@ -86,6 +86,7 @@
_ = lambda text: text
+@XBlock.wants('settings')
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin):
"""
XML source example:
@@ -261,6 +262,15 @@ def get_html(self):
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 @@ def get_html(self):
'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('/?'),
diff --git a/common/test/acceptance/tests/helpers.py b/common/test/acceptance/tests/helpers.py
index 1b8b597520d0..b521ddfca0db 100644
--- a/common/test/acceptance/tests/helpers.py
+++ b/common/test/acceptance/tests/helpers.py
@@ -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',
diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature
index 7eead5c995d8..8e3581888bf7 100644
--- a/lms/djangoapps/courseware/features/video.feature
+++ b/lms/djangoapps/courseware/features/video.feature
@@ -3,7 +3,7 @@ Feature: LMS.Video component
As a student, I want to view course videos in LMS
# 1
- Scenario: Verify that each video in each sub-section includes a transcript for non-Youtube countries
+ Scenario: Verify that each video in sub-section includes a transcript for Youtube and non-Youtube countries
Given youtube server is up and response time is 2 seconds
And I am registered for the course "test_course"
And I have a "subs_3_yD_cEKoCk.srt.sjson" transcript file in assets
@@ -24,9 +24,9 @@ Feature: LMS.Video component
| Welcome to edX. |
| Equal transcripts |
When I open video "C"
- Then the video has rendered in "HTML5" mode
+ Then the video has rendered in "YOUTUBE" mode
And I make sure captions are opened
And I see "好 各位同学" text in the captions
When I open video "D"
- Then the video has rendered in "HTML5" mode
- And the video does not show the captions
+ Then the video has rendered in "YOUTUBE" mode
+ And I make sure captions are opened
diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py
index cb06a2cbedc1..701f244167cd 100644
--- a/lms/djangoapps/courseware/tests/test_video_mongo.py
+++ b/lms/djangoapps/courseware/tests/test_video_mongo.py
@@ -62,8 +62,9 @@ def test_video_constructor(self):
"transcriptLanguage": "en",
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
"ytTestTimeout": 1500,
- "ytApiUrl": "www.youtube.com/iframe_api",
- "ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
+ "ytApiUrl": "https://www.youtube.com/iframe_api",
+ "ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
+ "ytKey": None,
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript', 'translation/__lang__'
).rstrip('/?'),
@@ -139,8 +140,9 @@ def test_video_constructor(self):
"transcriptLanguage": "en",
"transcriptLanguages": OrderedDict({"en": "English"}),
"ytTestTimeout": 1500,
- "ytApiUrl": "www.youtube.com/iframe_api",
- "ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
+ "ytApiUrl": "https://www.youtube.com/iframe_api",
+ "ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
+ "ytKey": None,
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript', 'translation/__lang__'
).rstrip('/?'),
@@ -192,8 +194,9 @@ def setUp(self):
"transcriptLanguage": "en",
"transcriptLanguages": OrderedDict({"en": "English"}),
"ytTestTimeout": 1500,
- "ytApiUrl": "www.youtube.com/iframe_api",
- "ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
+ "ytApiUrl": "https://www.youtube.com/iframe_api",
+ "ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
+ "ytKey": None,
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript', 'translation/__lang__'
).rstrip('/?'),
@@ -1181,8 +1184,9 @@ def test_bumper_metadata(self, get_url_for_profiles, get_bumper_settings, is_bum
"transcriptLanguage": "en",
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
"ytTestTimeout": 1500,
- "ytApiUrl": "www.youtube.com/iframe_api",
- "ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
+ "ytApiUrl": "https://www.youtube.com/iframe_api",
+ "ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
+ "ytKey": None,
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript', 'translation/__lang__'
).rstrip('/?'),
diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py
index e932dbf34aa8..c7ac6e811f84 100644
--- a/lms/envs/acceptance.py
+++ b/lms/envs/acceptance.py
@@ -175,8 +175,8 @@ def seed():
}
# Point the URL used to test YouTube availability to our stub YouTube server
-YOUTUBE['API'] = "127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT)
-YOUTUBE['TEST_URL'] = "127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
+YOUTUBE['API'] = "http://127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT)
+YOUTUBE['METADATA_URL'] = "http://127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index 5a497f425f00..d878a1e78340 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -625,6 +625,7 @@
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
+XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
##### CDN EXPERIMENT/MONITORING FLAGS #####
CDN_VIDEO_URLS = ENV_TOKENS.get('CDN_VIDEO_URLS', CDN_VIDEO_URLS)
diff --git a/lms/envs/bok_choy.py b/lms/envs/bok_choy.py
index 897029a772d4..bc4141ea537f 100644
--- a/lms/envs/bok_choy.py
+++ b/lms/envs/bok_choy.py
@@ -115,8 +115,8 @@
# Point the URL used to test YouTube availability to our stub YouTube server
YOUTUBE_PORT = 9080
-YOUTUBE['API'] = "127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT)
-YOUTUBE['TEST_URL'] = "127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
+YOUTUBE['API'] = "http://127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT)
+YOUTUBE['METADATA_URL'] = "http://127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
############################# SECURITY SETTINGS ################################
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 5b6d67d20b75..cffb5a299c55 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -1737,10 +1737,10 @@
YOUTUBE = {
# YouTube JavaScript API
- 'API': 'www.youtube.com/iframe_api',
+ 'API': 'https://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': 'https://www.googleapis.com/youtube/v3/videos/',
# Current youtube api for requesting transcripts.
# For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g.
@@ -1754,6 +1754,7 @@
'IMAGE_API': 'http://img.youtube.com/vi/{youtube_id}/0.jpg', # /maxresdefault.jpg for 1920*1080
}
+YOUTUBE_API_KEY = None
################################### APPS ######################################
INSTALLED_APPS = (
diff --git a/lms/static/js_test.yml b/lms/static/js_test.yml
index dab385edde83..82b875da5046 100644
--- a/lms/static/js_test.yml
+++ b/lms/static/js_test.yml
@@ -61,6 +61,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min.js
- xmodule_js/common_static/js/test/i18n.js
- xmodule_js/common_static/js/vendor/date.js
+ - xmodule_js/common_static/js/vendor/moment.min.js
# Paths to source JavaScript files
src_paths: