Using youtube api (v3) instead of v2 to get the video duration .
TNL-2413
This commit is contained in:
@@ -118,8 +118,8 @@ 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)
|
||||||
|
|
||||||
# Generate a random UUID so that different runs of acceptance tests don't break each other
|
# Generate a random UUID so that different runs of acceptance tests don't break each other
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ if FEATURES['ENABLE_COURSEWARE_INDEX'] or FEATURES['ENABLE_LIBRARY_INDEX']:
|
|||||||
|
|
||||||
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
||||||
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
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)
|
||||||
|
|
||||||
|
|
||||||
################# PROCTORING CONFIGURATION ##################
|
################# PROCTORING CONFIGURATION ##################
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
FEATURES['ENABLE_COURSEWARE_INDEX'] = True
|
FEATURES['ENABLE_COURSEWARE_INDEX'] = True
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ from lms.envs.common import (
|
|||||||
PROFILE_IMAGE_SECRET_KEY, PROFILE_IMAGE_MIN_BYTES, PROFILE_IMAGE_MAX_BYTES,
|
PROFILE_IMAGE_SECRET_KEY, PROFILE_IMAGE_MIN_BYTES, PROFILE_IMAGE_MAX_BYTES,
|
||||||
# The following setting is included as it is used to check whether to
|
# The following setting is included as it is used to check whether to
|
||||||
# display credit eligibility table on the CMS or not.
|
# display credit eligibility table on the CMS or not.
|
||||||
ENABLE_CREDIT_ELIGIBILITY
|
ENABLE_CREDIT_ELIGIBILITY, YOUTUBE_API_KEY
|
||||||
)
|
)
|
||||||
from path import path
|
from path import path
|
||||||
from warnings import simplefilter
|
from warnings import simplefilter
|
||||||
@@ -657,10 +657,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.
|
||||||
@@ -1002,6 +1002,9 @@ ELASTIC_FIELD_MAPPINGS = {
|
|||||||
XBLOCK_SETTINGS = {
|
XBLOCK_SETTINGS = {
|
||||||
"VideoDescriptor": {
|
"VideoDescriptor": {
|
||||||
"licensing_enabled": FEATURES.get("LICENSING", False)
|
"licensing_enabled": FEATURES.get("LICENSING", False)
|
||||||
|
},
|
||||||
|
'VideoModule': {
|
||||||
|
'YOUTUBE_API_KEY': YOUTUBE_API_KEY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = {
|
|||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
|
|
||||||
# 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))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id"
|
id="video_id"
|
||||||
class="video closed"
|
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>
|
<div class="focus_grabber first"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id"
|
id="video_id"
|
||||||
class="video closed"
|
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>
|
<div class="focus_grabber first"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id"
|
id="video_id"
|
||||||
class="video closed"
|
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>
|
<div class="focus_grabber first"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id"
|
id="video_id"
|
||||||
class="video closed"
|
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>
|
<div class="focus_grabber first"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id"
|
id="video_id"
|
||||||
class="video closed"
|
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-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"}'
|
data-poster='{"url": "xmodule/include/fixtures/poster.jpg", "type": "youtube"}'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id1"
|
id="video_id1"
|
||||||
class="video closed"
|
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>
|
<div class="focus_grabber first"></div>
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id2"
|
id="video_id2"
|
||||||
class="video"
|
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">
|
<div class="tc-wrapper">
|
||||||
<article class="video-wrapper">
|
<article class="video-wrapper">
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<div
|
<div
|
||||||
id="video_id3"
|
id="video_id3"
|
||||||
class="video"
|
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">
|
<div class="tc-wrapper">
|
||||||
<article class="video-wrapper">
|
<article class="video-wrapper">
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
return f();
|
return f();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
jasmine.YT = stubbedYT;
|
||||||
// Stub YouTube API.
|
// Stub YouTube API.
|
||||||
window.YT = stubbedYT;
|
window.YT = stubbedYT;
|
||||||
|
|
||||||
@@ -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 @@
|
|||||||
}
|
}
|
||||||
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 @@
|
|||||||
};
|
};
|
||||||
} 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 {
|
||||||
@@ -167,15 +175,6 @@
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
return;
|
return;
|
||||||
} else if (settings.url === '/save_user_state') {
|
} 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};
|
return {success: true};
|
||||||
} else {
|
} else {
|
||||||
throw 'External request attempted for ' +
|
throw 'External request attempted for ' +
|
||||||
@@ -224,6 +223,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;
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,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.
|
||||||
|
|||||||
@@ -71,10 +71,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(
|
|||||||
'video/01_initialize.js',
|
'video/01_initialize.js',
|
||||||
['video/03_video_player.js', 'video/00_i18n.js'],
|
['video/03_video_player.js', 'video/00_i18n.js'],
|
||||||
function (VideoPlayer, i18n) {
|
function (VideoPlayer, i18n) {
|
||||||
|
var moment = window.moment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function
|
* @function
|
||||||
*
|
*
|
||||||
@@ -31,6 +33,9 @@ function (VideoPlayer, 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);
|
||||||
@@ -75,7 +80,10 @@ function (VideoPlayer, 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,
|
||||||
@@ -126,6 +134,9 @@ function (VideoPlayer, 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);
|
||||||
@@ -176,7 +187,6 @@ function (VideoPlayer, 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
|
||||||
@@ -196,24 +206,32 @@ function (VideoPlayer, 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`.
|
||||||
if (!state.youtubeIsAvailable) {
|
if (!state.youtubeApiAvailable) {
|
||||||
console.log('[Video info]: YouTube API is not available.');
|
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);
|
}, 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)
|
// 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)
|
// function initialize(element)
|
||||||
// The function set initial configuration and preparation.
|
// The function set initial configuration and preparation.
|
||||||
|
|
||||||
@@ -484,7 +546,7 @@ function (VideoPlayer, 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(this.metadata, this.storage), {
|
this.config = $.extend({}, _getConfiguration(this.metadata, this.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
|
||||||
@@ -500,6 +562,9 @@ function (VideoPlayer, 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))) {
|
||||||
|
|
||||||
@@ -512,67 +577,30 @@ function (VideoPlayer, 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) {
|
|
||||||
// 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
|
var scriptTag = document.createElement('script');
|
||||||
// (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.
|
scriptTag.src = this.config.ytApiUrl;
|
||||||
|
scriptTag.async = true;
|
||||||
|
|
||||||
el.find('.video-player div')
|
$(scriptTag).on('load', function() {
|
||||||
.removeClass('hidden');
|
self.loadYoutubePlayer();
|
||||||
el.find('.video-player h3')
|
});
|
||||||
.addClass('hidden');
|
$(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
|
window.Video.loadYouTubeIFrameAPI(scriptTag);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return __dfd__.promise();
|
return __dfd__.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,8 +647,9 @@ function (VideoPlayer, i18n) {
|
|||||||
|
|
||||||
metadataXHRs = _.map(this.videos, function (url, speed) {
|
metadataXHRs = _.map(this.videos, function (url, speed) {
|
||||||
return self.getVideoMetadata(url, function (data) {
|
return self.getVideoMetadata(url, function (data) {
|
||||||
if (data.data) {
|
if (data.items.length > 0) {
|
||||||
self.metadata[data.data.id] = data.data;
|
var metaDataItem = data.items[0];
|
||||||
|
self.metadata[metaDataItem.id] = metaDataItem.contentDetails;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -671,16 +700,16 @@ function (VideoPlayer, i18n) {
|
|||||||
if (!(_.isString(url))) {
|
if (!(_.isString(url))) {
|
||||||
url = this.videos['1.0'] || '';
|
url = this.videos['1.0'] || '';
|
||||||
}
|
}
|
||||||
|
// Will hit the API URL iF YT key is defined in settings.
|
||||||
return $.ajax({
|
if (this.config.ytKey) {
|
||||||
url: [
|
return $.ajax({
|
||||||
document.location.protocol, '//', this.config.ytTestUrl, url,
|
url: [this.config.ytMetadataUrl, '?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: _.isFunction(callback) ? callback : null
|
return $.Deferred().reject().promise();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function youtubeId(speed) {
|
function youtubeId(speed) {
|
||||||
@@ -693,7 +722,7 @@ function (VideoPlayer, 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,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);
|
||||||
|
|||||||
@@ -736,8 +736,8 @@ class VideoDescriptorIndexingTestCase(unittest.TestCase):
|
|||||||
# YouTube JavaScript API
|
# YouTube JavaScript API
|
||||||
'API': 'www.youtube.com/iframe_api',
|
'API': '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': '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.
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ log = logging.getLogger(__name__)
|
|||||||
_ = lambda text: text
|
_ = lambda text: text
|
||||||
|
|
||||||
|
|
||||||
|
@XBlock.wants('settings')
|
||||||
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin):
|
class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin):
|
||||||
"""
|
"""
|
||||||
XML source example:
|
XML source example:
|
||||||
@@ -261,6 +262,15 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
|||||||
cdn_exp_group = None
|
cdn_exp_group = None
|
||||||
|
|
||||||
self.youtube_streams = youtube_streams or create_youtube_string(self) # pylint: disable=W0201
|
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 = {
|
metadata = {
|
||||||
'saveStateUrl': self.system.ajax_url + '/save_user_state',
|
'saveStateUrl': self.system.ajax_url + '/save_user_state',
|
||||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
|
||||||
@@ -286,7 +296,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
|||||||
'ytTestTimeout': 1500,
|
'ytTestTimeout': 1500,
|
||||||
|
|
||||||
'ytApiUrl': settings.YOUTUBE['API'],
|
'ytApiUrl': settings.YOUTUBE['API'],
|
||||||
'ytTestUrl': settings.YOUTUBE['TEST_URL'],
|
'ytMetadataUrl': settings.YOUTUBE['METADATA_URL'],
|
||||||
|
'ytKey': yt_api_key,
|
||||||
|
|
||||||
'transcriptTranslationUrl': self.runtime.handler_url(
|
'transcriptTranslationUrl': self.runtime.handler_url(
|
||||||
self, 'transcript', 'translation/__lang__'
|
self, 'transcript', 'translation/__lang__'
|
||||||
).rstrip('/?'),
|
).rstrip('/?'),
|
||||||
|
|||||||
@@ -67,8 +67,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=3_yD_cEKoCk',
|
'transcript': 'http://video.google.com/timedtext?lang=en&v=3_yD_cEKoCk',
|
||||||
|
|||||||
@@ -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_3_yD_cEKoCk.srt.sjson" transcript file in assets
|
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. |
|
| 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
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ class TestVideoYouTube(TestVideo):
|
|||||||
"transcriptLanguage": "en",
|
"transcriptLanguage": "en",
|
||||||
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
|
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
|
||||||
"ytTestTimeout": 1500,
|
"ytTestTimeout": 1500,
|
||||||
"ytApiUrl": "www.youtube.com/iframe_api",
|
"ytApiUrl": "https://www.youtube.com/iframe_api",
|
||||||
"ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
|
"ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
|
||||||
|
"ytKey": None,
|
||||||
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
||||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||||
).rstrip('/?'),
|
).rstrip('/?'),
|
||||||
@@ -139,8 +140,9 @@ class TestVideoNonYouTube(TestVideo):
|
|||||||
"transcriptLanguage": "en",
|
"transcriptLanguage": "en",
|
||||||
"transcriptLanguages": OrderedDict({"en": "English"}),
|
"transcriptLanguages": OrderedDict({"en": "English"}),
|
||||||
"ytTestTimeout": 1500,
|
"ytTestTimeout": 1500,
|
||||||
"ytApiUrl": "www.youtube.com/iframe_api",
|
"ytApiUrl": "https://www.youtube.com/iframe_api",
|
||||||
"ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
|
"ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
|
||||||
|
"ytKey": None,
|
||||||
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
||||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||||
).rstrip('/?'),
|
).rstrip('/?'),
|
||||||
@@ -192,8 +194,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
|||||||
"transcriptLanguage": "en",
|
"transcriptLanguage": "en",
|
||||||
"transcriptLanguages": OrderedDict({"en": "English"}),
|
"transcriptLanguages": OrderedDict({"en": "English"}),
|
||||||
"ytTestTimeout": 1500,
|
"ytTestTimeout": 1500,
|
||||||
"ytApiUrl": "www.youtube.com/iframe_api",
|
"ytApiUrl": "https://www.youtube.com/iframe_api",
|
||||||
"ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
|
"ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
|
||||||
|
"ytKey": None,
|
||||||
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
||||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||||
).rstrip('/?'),
|
).rstrip('/?'),
|
||||||
@@ -1181,8 +1184,9 @@ class TestVideoWithBumper(TestVideo):
|
|||||||
"transcriptLanguage": "en",
|
"transcriptLanguage": "en",
|
||||||
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
|
"transcriptLanguages": OrderedDict({"en": "English", "uk": u"Українська"}),
|
||||||
"ytTestTimeout": 1500,
|
"ytTestTimeout": 1500,
|
||||||
"ytApiUrl": "www.youtube.com/iframe_api",
|
"ytApiUrl": "https://www.youtube.com/iframe_api",
|
||||||
"ytTestUrl": "gdata.youtube.com/feeds/api/videos/",
|
"ytMetadataUrl": "https://www.googleapis.com/youtube/v3/videos/",
|
||||||
|
"ytKey": None,
|
||||||
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
"transcriptTranslationUrl": self.item_descriptor.xmodule_runtime.handler_url(
|
||||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||||
).rstrip('/?'),
|
).rstrip('/?'),
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ 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)
|
||||||
|
|
||||||
if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \
|
if FEATURES.get('ENABLE_COURSEWARE_SEARCH') or \
|
||||||
|
|||||||
@@ -632,6 +632,7 @@ FACEBOOK_APP_ID = AUTH_TOKENS.get("FACEBOOK_APP_ID")
|
|||||||
|
|
||||||
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
||||||
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
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 EXPERIMENT/MONITORING FLAGS #####
|
||||||
CDN_VIDEO_URLS = ENV_TOKENS.get('CDN_VIDEO_URLS', CDN_VIDEO_URLS)
|
CDN_VIDEO_URLS = ENV_TOKENS.get('CDN_VIDEO_URLS', CDN_VIDEO_URLS)
|
||||||
|
|||||||
@@ -131,8 +131,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 ################################
|
||||||
|
|||||||
@@ -1751,10 +1751,10 @@ EMAIL_OPTIN_MINIMUM_AGE = PARENTAL_CONSENT_AGE_LIMIT
|
|||||||
|
|
||||||
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.
|
||||||
@@ -1768,6 +1768,7 @@ YOUTUBE = {
|
|||||||
|
|
||||||
'IMAGE_API': 'http://img.youtube.com/vi/{youtube_id}/0.jpg', # /maxresdefault.jpg for 1920*1080
|
'IMAGE_API': 'http://img.youtube.com/vi/{youtube_id}/0.jpg', # /maxresdefault.jpg for 1920*1080
|
||||||
}
|
}
|
||||||
|
YOUTUBE_API_KEY = None
|
||||||
|
|
||||||
################################### APPS ######################################
|
################################### APPS ######################################
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
|||||||
@@ -61,6 +61,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:
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
"URI": "js/vendor/URI.min",
|
"URI": "js/vendor/URI.min",
|
||||||
"string_utils": "js/src/string_utils",
|
"string_utils": "js/src/string_utils",
|
||||||
"utility": "js/src/utility",
|
"utility": "js/src/utility",
|
||||||
|
"moment": "js/vendor/moment.min",
|
||||||
|
|
||||||
// Files needed by OVA
|
// Files needed by OVA
|
||||||
"annotator": "js/vendor/ova/annotator-full",
|
"annotator": "js/vendor/ova/annotator-full",
|
||||||
|
|||||||
Reference in New Issue
Block a user