Commit cafbe74d by muzaffaryousaf Committed by David Baumgold

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

TNL-2413
parent 57b9d8dc
...@@ -118,6 +118,6 @@ except ImportError: ...@@ -118,6 +118,6 @@ except ImportError:
pass pass
# Point the URL used to test YouTube availability to our stub YouTube server # 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['API'] = "http://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['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) YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
...@@ -312,3 +312,6 @@ VIDEO_UPLOAD_PIPELINE = ENV_TOKENS.get('VIDEO_UPLOAD_PIPELINE', VIDEO_UPLOAD_PIP ...@@ -312,3 +312,6 @@ VIDEO_UPLOAD_PIPELINE = ENV_TOKENS.get('VIDEO_UPLOAD_PIPELINE', VIDEO_UPLOAD_PIP
#date format the api will be formatting the datetime values #date format the api will be formatting the datetime values
API_DATE_FORMAT = '%Y-%m-%d' API_DATE_FORMAT = '%Y-%m-%d'
API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT) API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT)
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
...@@ -73,8 +73,8 @@ DEBUG = True ...@@ -73,8 +73,8 @@ DEBUG = True
# Point the URL used to test YouTube availability to our stub YouTube server # Point the URL used to test YouTube availability to our stub YouTube server
YOUTUBE_PORT = 9080 YOUTUBE_PORT = 9080
YOUTUBE['API'] = "127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT) YOUTUBE['API'] = "http://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['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) YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
##################################################################### #####################################################################
......
...@@ -36,7 +36,8 @@ import lms.envs.common ...@@ -36,7 +36,8 @@ import lms.envs.common
# Although this module itself may not use these imported variables, other dependent modules may. # Although this module itself may not use these imported variables, other dependent modules may.
from lms.envs.common import ( from lms.envs.common import (
USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, ALL_LANGUAGES, WIKI_ENABLED, MODULESTORE, USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, ALL_LANGUAGES, WIKI_ENABLED, MODULESTORE,
update_module_store_settings, ASSET_IGNORE_REGEX, COPYRIGHT_YEAR update_module_store_settings, ASSET_IGNORE_REGEX, COPYRIGHT_YEAR,
YOUTUBE_API_KEY,
) )
from path import path from path import path
from warnings import simplefilter from warnings import simplefilter
...@@ -594,10 +595,10 @@ CELERY_QUEUES = { ...@@ -594,10 +595,10 @@ CELERY_QUEUES = {
YOUTUBE = { YOUTUBE = {
# YouTube JavaScript API # YouTube JavaScript API
'API': 'www.youtube.com/iframe_api', 'API': 'https://www.youtube.com/iframe_api',
# URL to test YouTube availability # URL to get YouTube metadata
'TEST_URL': 'gdata.youtube.com/feeds/api/videos/', 'METADATA_URL': 'https://www.googleapis.com/youtube/v3/videos',
# Current youtube api for requesting transcripts. # Current youtube api for requesting transcripts.
# For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g. # For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g.
...@@ -868,3 +869,10 @@ FILES_AND_UPLOAD_TYPE_FILTERS = { ...@@ -868,3 +869,10 @@ FILES_AND_UPLOAD_TYPE_FILTERS = {
'application/vnd.ms-powerpoint', 'application/vnd.ms-powerpoint',
], ],
} }
XBLOCK_SETTINGS = {
'VideoModule': {
'YOUTUBE_API_KEY': YOUTUBE_API_KEY
}
}
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
<h2>Video</h2> <h2>Video</h2>
<div id="video_i4x-AndyA-ABT101-video-72b5a0d74e8c4ed4a4d4e6bf67837c09" class="video closed is-initialized" data-streams="1.00:OEoXaMPEzfM" data-save-state-url="/preview/xblock/i4x:;_;_AndyA;_ABT101;_video;_72b5a0d74e8c4ed4a4d4e6bf67837c09/handler/xmodule_handler/save_user_state" data-caption-data-dir="None" data-show-captions="true" data-general-speed="1.0" data-speed="null" data-start="0.0" data-end="0.0" data-caption-asset-path="/c4x/AndyA/ABT101/asset/subs_" data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" data-autohide-html5="False" tabindex="-1"> <div id="video_i4x-AndyA-ABT101-video-72b5a0d74e8c4ed4a4d4e6bf67837c09" class="video closed is-initialized" data-streams="1.00:OEoXaMPEzfM" data-save-state-url="/preview/xblock/i4x:;_;_AndyA;_ABT101;_video;_72b5a0d74e8c4ed4a4d4e6bf67837c09/handler/xmodule_handler/save_user_state" data-caption-data-dir="None" data-show-captions="true" data-general-speed="1.0" data-speed="null" data-start="0.0" data-end="0.0" data-caption-asset-path="/c4x/AndyA/ABT101/asset/subs_" data-autoplay="False" data-yt-test-timeout="1500" data-yt-test-url="https://www.googleapis.com/youtube/v3/videos/" data-autohide-html5="False" tabindex="-1">
<div class="focus_grabber first" tabindex="-1"></div> <div class="focus_grabber first" tabindex="-1"></div>
<div class="tc-wrapper"> <div class="tc-wrapper">
......
...@@ -28,8 +28,7 @@ SERVICES = { ...@@ -28,8 +28,7 @@ SERVICES = {
YOUTUBE_API_URLS = { YOUTUBE_API_URLS = {
'main': 'https://www.youtube.com/', 'main': 'https://www.youtube.com/',
'player': 'http://www.youtube.com/iframe_api', 'player': 'https://www.youtube.com/iframe_api',
'metadata': 'http://gdata.youtube.com/feeds/api/videos/',
# For transcripts, you need to check an actual video, so we will # For transcripts, you need to check an actual video, so we will
# just specify our default video and see if that one is available. # just specify our default video and see if that one is available.
'transcript': 'http://video.google.com/timedtext?lang=en&v=OEoXaMPEzfM', 'transcript': 'http://video.google.com/timedtext?lang=en&v=OEoXaMPEzfM',
......
...@@ -95,6 +95,9 @@ class StubYouTubeHandler(StubHttpRequestHandler): ...@@ -95,6 +95,9 @@ class StubYouTubeHandler(StubHttpRequestHandler):
if self.server.config.get('youtube_api_blocked'): if self.server.config.get('youtube_api_blocked'):
self.send_response(404, content='', headers={'Content-type': 'text/plain'}) self.send_response(404, content='', headers={'Content-type': 'text/plain'})
else: 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. # Get the response to send from YouTube.
# We need to do this every time because Google sometimes sends different responses # 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" # as part of their own experiments, which has caused our tests to become "flaky"
...@@ -117,17 +120,16 @@ class StubYouTubeHandler(StubHttpRequestHandler): ...@@ -117,17 +120,16 @@ class StubYouTubeHandler(StubHttpRequestHandler):
# Construct the response content # Construct the response content
callback = self.get_params['callback'] 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({
'data': OrderedDict({ 'items': list(
'id': youtube_id, OrderedDict({
'message': message, 'contentDetails': OrderedDict({
'duration': youtube_metadata['data']['duration'], 'id': youtube_id,
}) 'duration': 'PT2M20S',
})
})
)
}) })
response = "{cb}({data})".format(cb=callback, data=json.dumps(data)) response = "{cb}({data})".format(cb=callback, data=json.dumps(data))
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-api-url="www.youtube.com/iframe_api" data-yt-api-url="www.youtube.com/iframe_api"
data-yt-test-url="gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
<div class="focus_grabber first"></div> <div class="focus_grabber first"></div>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-api-url="www.youtube.com/iframe_api" data-yt-api-url="www.youtube.com/iframe_api"
data-yt-test-url="gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
<div class="focus_grabber first"></div> <div class="focus_grabber first"></div>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-api-url="www.youtube.com/iframe_api" data-yt-api-url="www.youtube.com/iframe_api"
data-yt-test-url="gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
<div class="focus_grabber first"></div> <div class="focus_grabber first"></div>
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-api-url="www.youtube.com/iframe_api" data-yt-api-url="www.youtube.com/iframe_api"
data-yt-test-url="gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
<div class="focus_grabber first"></div> <div class="focus_grabber first"></div>
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-api-url="www.youtube.com/iframe_api" data-yt-api-url="www.youtube.com/iframe_api"
data-yt-test-url="gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
<div class="focus_grabber first"></div> <div class="focus_grabber first"></div>
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
data-transcript-available-translations-url="/transcript/available_translations" data-transcript-available-translations-url="/transcript/available_translations"
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
...@@ -153,7 +153,7 @@ ...@@ -153,7 +153,7 @@
data-transcript-available-translations-url="/transcript/available_translations" data-transcript-available-translations-url="/transcript/available_translations"
data-autoplay="False" data-autoplay="False"
data-yt-test-timeout="1500" data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/" data-yt-test-url="www.googleapis.com/youtube/v3/videos/"
data-autohide-html5="True" data-autohide-html5="True"
> >
......
...@@ -59,6 +59,7 @@ lib_paths: ...@@ -59,6 +59,7 @@ lib_paths:
- common_static/js/src/utility.js - common_static/js/src/utility.js
- public/js/split_test_staff.js - public/js/split_test_staff.js
- common_static/js/src/accessibility_tools.js - common_static/js/src/accessibility_tools.js
- common_static/js/vendor/moment.min.js
# Paths to spec (test) JavaScript files # Paths to spec (test) JavaScript files
spec_paths: spec_paths:
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
return f(); return f();
} }
}; };
jasmine.YT = stubbedYT;
// Stub YouTube API. // Stub YouTube API.
window.YT = stubbedYT; window.YT = stubbedYT;
...@@ -76,19 +76,27 @@ ...@@ -76,19 +76,27 @@
jasmine.stubbedMetadata = { jasmine.stubbedMetadata = {
'7tqY6eQzVhE': { '7tqY6eQzVhE': {
id: '7tqY6eQzVhE', contentDetails : {
duration: 300 id: '7tqY6eQzVhE',
duration: 'PT5M0S'
}
}, },
'cogebirgzzM': { 'cogebirgzzM': {
id: 'cogebirgzzM', contentDetails : {
duration: 200 id: 'cogebirgzzM',
duration: 'PT3M20S'
}
}, },
'abcdefghijkl': { 'abcdefghijkl': {
id: 'abcdefghijkl', contentDetails : {
duration: 400 id: 'abcdefghijkl',
duration: 'PT6M40S'
}
}, },
bogus: { bogus: {
duration: 100 contentDetails : {
duration: 'PT1M40S'
}
} }
}; };
...@@ -122,7 +130,7 @@ ...@@ -122,7 +130,7 @@
} }
return spy.andCallFake(function (settings) { return spy.andCallFake(function (settings) {
var match = settings.url var match = settings.url
.match(/youtube\.com\/.+\/videos\/(.+)\?v=2&alt=jsonc/), .match(/googleapis\.com\/.+\/videos\/\?id=(.+)&part=contentDetails/),
status, callCallback; status, callCallback;
if (match) { if (match) {
status = match[1].split('_'); status = match[1].split('_');
...@@ -138,7 +146,7 @@ ...@@ -138,7 +146,7 @@
}; };
} else if (settings.success) { } else if (settings.success) {
return settings.success({ return settings.success({
data: jasmine.stubbedMetadata[match[1]] items: jasmine.stubbedMetadata[match[1]]
}); });
} else { } else {
return { return {
...@@ -168,15 +176,6 @@ ...@@ -168,15 +176,6 @@
return; return;
} else if (settings.url === '/save_user_state') { } else if (settings.url === '/save_user_state') {
return {success: true}; 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 { } else {
throw 'External request attempted for ' + throw 'External request attempted for ' +
settings.url + settings.url +
...@@ -221,6 +220,19 @@ ...@@ -221,6 +220,19 @@
// Stub jQuery.scrollTo module. // Stub jQuery.scrollTo module.
$.fn.scrollTo = jasmine.createSpy('jQuery.scrollTo'); $.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) { jasmine.initializePlayer = function (fixture, params) {
var state; var state;
......
...@@ -122,6 +122,12 @@ ...@@ -122,6 +122,12 @@
return state.youtubeApiAvailable === true; return state.youtubeApiAvailable === true;
}, 'YouTube API is loaded', 3000); }, '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 () { runs(function () {
// If YouTube API is not loaded, then the code will should create // 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. // a global callback that will be called by API once it is loaded.
......
...@@ -223,10 +223,10 @@ function (Initialize) { ...@@ -223,10 +223,10 @@ function (Initialize) {
speed: '1.50', speed: '1.50',
metadata: { metadata: {
'testId': { 'testId': {
duration: 400 duration: 'PT6M40S'
}, },
'videoId': { 'videoId': {
duration: 100 duration: 'PT1M40S'
} }
}, },
videos: { videos: {
......
...@@ -16,6 +16,8 @@ define( ...@@ -16,6 +16,8 @@ define(
'video/01_initialize.js', 'video/01_initialize.js',
['video/03_video_player.js', 'video/00_video_storage.js', 'video/00_i18n.js'], ['video/03_video_player.js', 'video/00_video_storage.js', 'video/00_i18n.js'],
function (VideoPlayer, VideoStorage, i18n) { function (VideoPlayer, VideoStorage, i18n) {
var moment = window.moment;
/** /**
* @function * @function
* *
...@@ -31,6 +33,9 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -31,6 +33,9 @@ function (VideoPlayer, VideoStorage, i18n) {
state.initialize(element) state.initialize(element)
.done(function () { .done(function () {
if (state.isYoutubeType()) {
state.parseSpeed();
}
// On iPhones and iPods native controls are used. // On iPhones and iPods native controls are used.
if (/iP(hone|od)/i.test(state.isTouch[0])) { if (/iP(hone|od)/i.test(state.isTouch[0])) {
_hideWaitPlaceholder(state); _hideWaitPlaceholder(state);
...@@ -76,7 +81,10 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -76,7 +81,10 @@ function (VideoPlayer, VideoStorage, i18n) {
setSpeed: setSpeed, setSpeed: setSpeed,
speedToString: speedToString, speedToString: speedToString,
trigger: trigger, trigger: trigger,
youtubeId: youtubeId youtubeId: youtubeId,
loadHtmlPlayer: loadHtmlPlayer,
loadYoutubePlayer: loadYoutubePlayer,
loadYouTubeIFrameAPI: loadYouTubeIFrameAPI
}, },
_youtubeApiDeferred = null, _youtubeApiDeferred = null,
...@@ -127,6 +135,9 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -127,6 +135,9 @@ function (VideoPlayer, VideoStorage, i18n) {
onYTApiReady = function () { onYTApiReady = function () {
console.log('[Video info]: YouTube API is available and is loaded.'); 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); video = VideoPlayer(state);
state.modules.push(video); state.modules.push(video);
...@@ -179,7 +190,6 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -179,7 +190,6 @@ function (VideoPlayer, VideoStorage, i18n) {
if (!_youtubeApiDeferred) { if (!_youtubeApiDeferred) {
_youtubeApiDeferred = $.Deferred(); _youtubeApiDeferred = $.Deferred();
setupOnYouTubeIframeAPIReady(); setupOnYouTubeIframeAPIReady();
_loadYoutubeApi(state);
} else if (!window.onYouTubeIframeAPIReady || !window.onYouTubeIframeAPIReady.done) { } else if (!window.onYouTubeIframeAPIReady || !window.onYouTubeIframeAPIReady.done) {
// The Deferred object could have been already defined in a previous // The Deferred object could have been already defined in a previous
// initialization of the video module. However, since then the global variable // initialization of the video module. However, since then the global variable
...@@ -201,21 +211,32 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -201,21 +211,32 @@ function (VideoPlayer, VideoStorage, i18n) {
state.modules.push(video); state.modules.push(video);
state.__dfd__.resolve(); state.__dfd__.resolve();
state.htmlPlayerLoaded = true;
} }
} }
function _loadYoutubeApi(state) { function _waitForYoutubeApi(state) {
console.log('[Video info]: YouTube API is not loaded. Will try to load...'); console.log('[Video info]: Starting to wait for YouTube API to load.');
window.setTimeout(function () { window.setTimeout(function () {
// If YouTube API will load OK, it will run `onYouTubeIframeAPIReady` // If YouTube API will load OK, it will run `onYouTubeIframeAPIReady`
// callback, which will set `state.youtubeApiAvailable` to `true`. // callback, which will set `state.youtubeApiAvailable` to `true`.
// If something goes wrong at this stage, `state.youtubeApiAvailable` is // If something goes wrong at this stage, `state.youtubeApiAvailable` is
// `false`. // `false`.
_reportToServer(state, state.youtubeApiAvailable); if (!state.youtubeApiAvailable) {
console.log('[Video info]: YouTube API is not available.');
if (!state.htmlPlayerLoaded) {
state.loadHtmlPlayer();
}
}
state.el.trigger('youtube_availability', [state.youtubeApiAvailable]);
}, state.config.ytTestTimeout); }, 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 _reportToServer(state, youtubeIsAvailable) { function _reportToServer(state, youtubeIsAvailable) {
...@@ -458,6 +479,50 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -458,6 +479,50 @@ function (VideoPlayer, VideoStorage, 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) // function initialize(element)
// The function set initial configuration and preparation. // The function set initial configuration and preparation.
...@@ -494,7 +559,7 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -494,7 +559,7 @@ function (VideoPlayer, VideoStorage, i18n) {
// jQuery .data() return object with keys in lower camelCase format. // jQuery .data() return object with keys in lower camelCase format.
this.config = $.extend({}, _getConfiguration(el.data(), storage), { this.config = $.extend({}, _getConfiguration(el.data(), storage), {
element: element, element: element,
fadeOutTimeout: 1400, fadeOutTimeout: 1400,
captionsFreezeTime: 10000, captionsFreezeTime: 10000,
mode: $.cookie('edX_video_player_mode'), mode: $.cookie('edX_video_player_mode'),
// Available HD qualities will only be accessible once the video has // Available HD qualities will only be accessible once the video has
...@@ -510,6 +575,9 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -510,6 +575,9 @@ function (VideoPlayer, VideoStorage, i18n) {
this.speed = this.speedToString( this.speed = this.speedToString(
this.config.speed || this.config.generalSpeed this.config.speed || this.config.generalSpeed
); );
this.htmlPlayerLoaded = false;
_setConfigurations(this);
if (!(_parseYouTubeIDs(this))) { if (!(_parseYouTubeIDs(this))) {
...@@ -522,70 +590,30 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -522,70 +590,30 @@ function (VideoPlayer, VideoStorage, i18n) {
} }
console.log('[Video info]: Start player in HTML5 mode.'); console.log('[Video info]: Start player in HTML5 mode.');
_setConfigurations(this);
_renderElements(this); _renderElements(this);
} else { } else {
if (!this.youtubeXhr) { _renderElements(this);
this.youtubeXhr = this.getVideoMetadata();
}
this.youtubeXhr _waitForYoutubeApi(this);
.always(function (json, status) {
var err = $.isPlainObject(json.error) ||
(
status !== 'success' &&
status !== 'notmodified'
);
if (err) {
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.'
);
// In-browser HTML5 player does not support quality var scriptTag = document.createElement('script');
// control.
el.find('a.quality_control').hide();
}
} else {
console.log(
'[Video info]: Start player in YouTube mode.'
);
self.fetchMetadata(); scriptTag.src = this.config.ytApiUrl;
self.parseSpeed(); scriptTag.async = true;
}
_setConfigurations(self); $(scriptTag).on('load', function() {
_renderElements(self); 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(); return __dfd__.promise();
} }
...@@ -633,23 +661,22 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -633,23 +661,22 @@ function (VideoPlayer, VideoStorage, i18n) {
// example the length of the video can be determined from the meta // example the length of the video can be determined from the meta
// data. // data.
function fetchMetadata() { function fetchMetadata() {
var _this = this, var self = this,
metadataXHRs = []; metadataXHRs = [];
this.metadata = {}; this.metadata = {};
$.each(this.videos, function (speed, url) { metadataXHRs = _.map(this.videos, function (url, speed) {
var xhr = _this.getVideoMetadata(url, function (data) { return self.getVideoMetadata(url, function (data) {
if (data.data) { if (data.items.length > 0) {
_this.metadata[data.data.id] = data.data; var metaDataItem = data.items[0];
self.metadata[metaDataItem.id] = metaDataItem.contentDetails;
} }
}); });
metadataXHRs.push(xhr);
}); });
$.when.apply(this, metadataXHRs).done(function () { $.when.apply(this, metadataXHRs).done(function () {
_this.el.trigger('metadata_received'); self.el.trigger('metadata_received');
// Not only do we trigger the "metadata_received" event, we also // Not only do we trigger the "metadata_received" event, we also
// set a flag to notify that metadata has been received. This // set a flag to notify that metadata has been received. This
...@@ -657,7 +684,7 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -657,7 +684,7 @@ function (VideoPlayer, VideoStorage, i18n) {
// to know that metadata has been received. This is important in // to know that metadata has been received. This is important in
// cases when some code will subscribe to the "metadata_received" // cases when some code will subscribe to the "metadata_received"
// event after it has been triggered. // event after it has been triggered.
_this.youtubeMetadataReceived = true; self.youtubeMetadataReceived = true;
}); });
} }
...@@ -703,18 +730,16 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -703,18 +730,16 @@ function (VideoPlayer, VideoStorage, i18n) {
if (typeof url !== 'string') { if (typeof url !== 'string') {
url = this.videos['1.0'] || ''; url = this.videos['1.0'] || '';
} }
successHandler = ($.isFunction(callback)) ? callback : null; // Will hit the API URL iF YT key is defined in settings.
xhr = $.ajax({ if (this.config.ytKey) {
url: [ return $.ajax({
document.location.protocol, '//', this.config.ytTestUrl, url, url: [this.config.ytTestUrl, '?id=', url, '&part=contentDetails&key=', this.config.ytKey].join(''),
'?v=2&alt=jsonc' timeout: this.config.ytTestTimeout,
].join(''), success: _.isFunction(callback) ? callback : null
dataType: 'jsonp', });
timeout: this.config.ytTestTimeout, } else {
success: successHandler return $.Deferred().reject().promise();
}); }
return xhr;
} }
function saveState(async, data) { function saveState(async, data) {
...@@ -754,7 +779,7 @@ function (VideoPlayer, VideoStorage, i18n) { ...@@ -754,7 +779,7 @@ function (VideoPlayer, VideoStorage, i18n) {
function getDuration() { function getDuration() {
try { try {
return this.metadata[this.youtubeId()].duration; return moment.duration(this.metadata[this.youtubeId()].duration, moment.ISO_8601).asSeconds();
} catch (err) { } catch (err) {
return _.result(this.metadata[this.youtubeId('1.0')], 'duration') || 0; return _.result(this.metadata[this.youtubeId('1.0')], 'duration') || 0;
} }
......
...@@ -113,6 +113,8 @@ ...@@ -113,6 +113,8 @@
youtubeXhr = null; 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 // Invoke the mock Video constructor so that the elements stored within it can be processed by the real
// `window.Video` constructor. // `window.Video` constructor.
oldVideo(null, true); oldVideo(null, true);
......
...@@ -582,3 +582,4 @@ class VideoCdnTest(unittest.TestCase): ...@@ -582,3 +582,4 @@ class VideoCdnTest(unittest.TestCase):
cdn_response.return_value = Mock(status_code=404) cdn_response.return_value = Mock(status_code=404)
fake_cdn_url = 'http://fake_cdn.com/' fake_cdn_url = 'http://fake_cdn.com/'
self.assertIsNone(get_video_from_cdn(fake_cdn_url, original_video_url)) self.assertIsNone(get_video_from_cdn(fake_cdn_url, original_video_url))
...@@ -26,6 +26,7 @@ from pkg_resources import resource_string ...@@ -26,6 +26,7 @@ from pkg_resources import resource_string
from django.conf import settings from django.conf import settings
from xblock.core import XBlock
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData from xblock.runtime import KvsFieldData
...@@ -77,6 +78,7 @@ log = logging.getLogger(__name__) ...@@ -77,6 +78,7 @@ log = logging.getLogger(__name__)
_ = lambda text: text _ = lambda text: text
@XBlock.wants('settings')
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule): class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule):
""" """
XML source example: XML source example:
...@@ -230,6 +232,14 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, ...@@ -230,6 +232,14 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
track_url, transcript_language, sorted_languages = self.get_transcripts_for_student() track_url, transcript_language, sorted_languages = self.get_transcripts_for_student()
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']
return self.system.render_template('video.html', { return self.system.render_template('video.html', {
'ajax_url': self.system.ajax_url + '/save_user_state', 'ajax_url': self.system.ajax_url + '/save_user_state',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
...@@ -254,7 +264,8 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, ...@@ -254,7 +264,8 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
# configuration setting field. # configuration setting field.
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': settings.YOUTUBE['API'], 'yt_api_url': settings.YOUTUBE['API'],
'yt_test_url': settings.YOUTUBE['TEST_URL'], 'yt_test_url': settings.YOUTUBE['METADATA_URL'],
'yt_key': yt_api_key,
'transcript_download_format': transcript_download_format, 'transcript_download_format': transcript_download_format,
'transcript_download_formats_list': self.descriptor.fields['transcript_download_format'].values, 'transcript_download_formats_list': self.descriptor.fields['transcript_download_format'].values,
'transcript_language': transcript_language, 'transcript_language': transcript_language,
......
...@@ -49,8 +49,7 @@ def is_youtube_available(): ...@@ -49,8 +49,7 @@ def is_youtube_available():
youtube_api_urls = { youtube_api_urls = {
'main': 'https://www.youtube.com/', 'main': 'https://www.youtube.com/',
'player': 'http://www.youtube.com/iframe_api', 'player': 'https://www.youtube.com/iframe_api',
'metadata': 'http://gdata.youtube.com/feeds/api/videos/',
# For transcripts, you need to check an actual video, so we will # For transcripts, you need to check an actual video, so we will
# just specify our default video and see if that one is available. # just specify our default video and see if that one is available.
'transcript': 'http://video.google.com/timedtext?lang=en&v=OEoXaMPEzfM', 'transcript': 'http://video.google.com/timedtext?lang=en&v=OEoXaMPEzfM',
......
...@@ -3,7 +3,7 @@ Feature: LMS.Video component ...@@ -3,7 +3,7 @@ Feature: LMS.Video component
As a student, I want to view course videos in LMS As a student, I want to view course videos in LMS
# 1 # 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 Given youtube server is up and response time is 2 seconds
And I am registered for the course "test_course" And I am registered for the course "test_course"
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
...@@ -24,9 +24,9 @@ Feature: LMS.Video component ...@@ -24,9 +24,9 @@ Feature: LMS.Video component
| Hi, welcome to Edx. | | Hi, welcome to Edx. |
| Equal transcripts | | 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 "YOUTUBE" mode
And I make sure captions are opened And I make sure captions are opened
And I see "好 各位同学" text in the captions And I see "好 各位同学" text in the captions
When I open video "D" When I open video "D"
Then the video has rendered in "HTML5" mode Then the video has rendered in "YOUTUBE" mode
And the video does not show the captions And I make sure captions are opened
...@@ -55,8 +55,9 @@ class TestVideoYouTube(TestVideo): ...@@ -55,8 +55,9 @@ class TestVideoYouTube(TestVideo):
'track': None, 'track': None,
'youtube_streams': create_youtube_string(self.item_descriptor), 'youtube_streams': create_youtube_string(self.item_descriptor),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -119,8 +120,9 @@ class TestVideoNonYouTube(TestVideo): ...@@ -119,8 +120,9 @@ class TestVideoNonYouTube(TestVideo):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -221,8 +223,9 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -221,8 +223,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
} }
...@@ -337,8 +340,9 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -337,8 +340,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -476,8 +480,9 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -476,8 +480,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -593,8 +598,9 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -593,8 +598,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -696,8 +702,9 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -696,8 +702,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_api_url': 'www.youtube.com/iframe_api', 'yt_api_url': 'https://www.youtube.com/iframe_api',
'yt_test_url': 'gdata.youtube.com/feeds/api/videos/', 'yt_test_url': 'https://www.googleapis.com/youtube/v3/videos/',
'yt_key': None,
'transcript_download_format': 'srt', 'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}], 'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': u'en', 'transcript_language': u'en',
...@@ -891,3 +898,4 @@ class VideoDescriptorTest(VideoDescriptorTestBase): ...@@ -891,3 +898,4 @@ class VideoDescriptorTest(VideoDescriptorTestBase):
] ]
rendered_context = self.descriptor.get_context() rendered_context = self.descriptor.get_context()
self.assertListEqual(rendered_context['tabs'], correct_tabs) self.assertListEqual(rendered_context['tabs'], correct_tabs)
...@@ -176,6 +176,6 @@ XQUEUE_INTERFACE = { ...@@ -176,6 +176,6 @@ XQUEUE_INTERFACE = {
} }
# Point the URL used to test YouTube availability to our stub YouTube server # 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['API'] = "http://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['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) YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
...@@ -500,3 +500,6 @@ PDF_RECEIPT_LOGO_HEIGHT_MM = ENV_TOKENS.get('PDF_RECEIPT_LOGO_HEIGHT_MM', PDF_RE ...@@ -500,3 +500,6 @@ PDF_RECEIPT_LOGO_HEIGHT_MM = ENV_TOKENS.get('PDF_RECEIPT_LOGO_HEIGHT_MM', PDF_RE
PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get( PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get(
'PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM', PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM 'PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM', PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM
) )
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoModule", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
...@@ -103,8 +103,8 @@ FEATURES['ENTRANCE_EXAMS'] = True ...@@ -103,8 +103,8 @@ FEATURES['ENTRANCE_EXAMS'] = True
# Point the URL used to test YouTube availability to our stub YouTube server # Point the URL used to test YouTube availability to our stub YouTube server
YOUTUBE_PORT = 9080 YOUTUBE_PORT = 9080
YOUTUBE['API'] = "127.0.0.1:{0}/get_youtube_api/".format(YOUTUBE_PORT) YOUTUBE['API'] = "http://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['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) YOUTUBE['TEXT_API']['url'] = "127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
############################# SECURITY SETTINGS ################################ ############################# SECURITY SETTINGS ################################
......
...@@ -1459,10 +1459,10 @@ EMAIL_OPTIN_MINIMUM_AGE = 13 ...@@ -1459,10 +1459,10 @@ EMAIL_OPTIN_MINIMUM_AGE = 13
YOUTUBE = { YOUTUBE = {
# YouTube JavaScript API # YouTube JavaScript API
'API': 'www.youtube.com/iframe_api', 'API': 'https://www.youtube.com/iframe_api',
# URL to test YouTube availability # URL to get YouTube metadata
'TEST_URL': 'gdata.youtube.com/feeds/api/videos/', 'METADATA_URL': 'https://www.googleapis.com/youtube/v3/videos/',
# Current youtube api for requesting transcripts. # Current youtube api for requesting transcripts.
# For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g. # For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g.
...@@ -1474,6 +1474,7 @@ YOUTUBE = { ...@@ -1474,6 +1474,7 @@ YOUTUBE = {
}, },
}, },
} }
YOUTUBE_API_KEY = None
################################### APPS ###################################### ################################### APPS ######################################
INSTALLED_APPS = ( INSTALLED_APPS = (
......
...@@ -58,6 +58,7 @@ lib_paths: ...@@ -58,6 +58,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min.js - xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min.js
- xmodule_js/common_static/js/test/i18n.js - xmodule_js/common_static/js/test/i18n.js
- xmodule_js/common_static/js/vendor/date.js - xmodule_js/common_static/js/vendor/date.js
- xmodule_js/common_static/js/vendor/moment.min.js
# Paths to source JavaScript files # Paths to source JavaScript files
src_paths: src_paths:
......
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