From 93d186511fabab6b3ad9f9fb55bd6a2e7c4b9620 Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Tue, 12 Feb 2013 10:23:32 -0500
Subject: [PATCH 01/29] clean up templates that were removed from disk - we
need to remove from DB as well
---
common/lib/xmodule/xmodule/templates.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/common/lib/xmodule/xmodule/templates.py b/common/lib/xmodule/xmodule/templates.py
index ce37df929f..eaf821155e 100644
--- a/common/lib/xmodule/xmodule/templates.py
+++ b/common/lib/xmodule/xmodule/templates.py
@@ -56,6 +56,10 @@ def update_templates():
available from the installed plugins
"""
+ # cdodge: build up a list of all existing templates. This will be used to determine which
+ # templates have been removed from disk - and thus we need to remove from the DB
+ templates_to_delete = modulestore('direct').get_items(['i4x', 'edx', 'templates', None, None, None])
+
for category, templates in all_templates().items():
for template in templates:
if 'display_name' not in template.metadata:
@@ -85,3 +89,12 @@ def update_templates():
modulestore('direct').update_item(template_location, template.data)
modulestore('direct').update_children(template_location, template.children)
modulestore('direct').update_metadata(template_location, template.metadata)
+
+ # remove template from list of templates to delete
+ templates_to_delete = [t for t in templates_to_delete if t.location != template_location]
+
+ # now remove all templates which appear to have removed from disk
+ if len(templates_to_delete) > 0:
+ logging.debug('deleting dangling templates = {0}'.format(templates_to_delete))
+ for template in templates_to_delete:
+ modulestore('direct').delete_item(template.location)
From fbe3d29f195640edffa0e0b490faee34f7143efe Mon Sep 17 00:00:00 2001
From: Chris Dodge
Date: Wed, 13 Feb 2013 09:25:09 -0500
Subject: [PATCH 02/29] added tests
---
.../contentstore/tests/test_contentstore.py | 31 +++++++++++++++++++
cms/djangoapps/contentstore/tests/tests.py | 1 -
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index adecd392eb..4360b38f95 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -27,10 +27,12 @@ from xmodule.contentstore.django import contentstore
from xmodule.templates import update_templates
from xmodule.modulestore.xml_exporter import export_to_xml
from xmodule.modulestore.xml_importer import import_from_xml
+from xmodule.templates import update_templates
from xmodule.capa_module import CapaDescriptor
from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor
+from xmodule.modulestore.exceptions import ItemNotFoundError
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
@@ -409,3 +411,32 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertIn('markdown', context, "markdown is missing from context")
self.assertIn('markdown', problem.metadata, "markdown is missing from metadata")
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields")
+
+
+class TemplateTestCase(ModuleStoreTestCase):
+
+ def test_template_cleanup(self):
+ ms = modulestore('direct')
+
+ # insert a bogus template in the store
+ bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus')
+ source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Empty')
+
+ ms.clone_item(source_template_location, bogus_template_location)
+
+ verify_create = ms.get_item(bogus_template_location)
+ self.assertIsNotNone(verify_create)
+
+ # now run cleanup
+ update_templates()
+
+ # now try to find dangling template, it should not be in DB any longer
+ asserted = False
+ try:
+ verify_create = ms.get_item(bogus_template_location)
+ except ItemNotFoundError:
+ asserted = True
+
+ self.assertTrue(asserted)
+
+
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 9af5b09276..393fd59f5c 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -86,7 +86,6 @@ class ContentStoreTestCase(ModuleStoreTestCase):
# Now make sure that the user is now actually activated
self.assertTrue(user(email).is_active)
-
class AuthTestCase(ContentStoreTestCase):
"""Check that various permissions-related things work"""
From 857edaf6f38649bbbe834cb7437fcc0fbfade950 Mon Sep 17 00:00:00 2001
From: Ned Batchelder
Date: Wed, 13 Feb 2013 12:23:08 -0500
Subject: [PATCH 03/29] Remove dogfood and quickedit.
---
lms/envs/common.py | 1 -
lms/envs/dev_edx4edx.py | 1 -
lms/envs/edx4edx_aws.py | 1 -
lms/lib/dogfood/README.md | 1 -
lms/lib/dogfood/__init__.py | 1 -
lms/lib/dogfood/check.py | 61 -------
lms/lib/dogfood/views.py | 325 -----------------------------------
lms/templates/dogfood.html | 144 ----------------
lms/templates/gitupdate.html | 32 ----
lms/templates/quickedit.html | 180 -------------------
lms/urls.py | 4 -
11 files changed, 751 deletions(-)
delete mode 100644 lms/lib/dogfood/README.md
delete mode 100644 lms/lib/dogfood/__init__.py
delete mode 100644 lms/lib/dogfood/check.py
delete mode 100644 lms/lib/dogfood/views.py
delete mode 100644 lms/templates/dogfood.html
delete mode 100644 lms/templates/gitupdate.html
delete mode 100644 lms/templates/quickedit.html
diff --git a/lms/envs/common.py b/lms/envs/common.py
index f3bf223451..eb8c9989f0 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -200,7 +200,6 @@ COURSE_TITLE = "Circuits and Electronics"
### Dark code. Should be enabled in local settings for devel.
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
-QUICKEDIT = False
WIKI_ENABLED = False
diff --git a/lms/envs/dev_edx4edx.py b/lms/envs/dev_edx4edx.py
index c138ed81ae..2ebd24e68b 100644
--- a/lms/envs/dev_edx4edx.py
+++ b/lms/envs/dev_edx4edx.py
@@ -34,7 +34,6 @@ EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
DEBUG = True
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
-QUICKEDIT = True
MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT]
diff --git a/lms/envs/edx4edx_aws.py b/lms/envs/edx4edx_aws.py
index de377c0b57..b82048824f 100644
--- a/lms/envs/edx4edx_aws.py
+++ b/lms/envs/edx4edx_aws.py
@@ -6,7 +6,6 @@ COURSE_TITLE = "edx4edx: edX Author Course"
EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
### Dark code. Should be enabled in local settings for devel.
-QUICKEDIT = True
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
###
PIPELINE_CSS_COMPRESSOR = None
diff --git a/lms/lib/dogfood/README.md b/lms/lib/dogfood/README.md
deleted file mode 100644
index c6a7113049..0000000000
--- a/lms/lib/dogfood/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This is a library for edx4edx, allowing users to practice writing problems.
diff --git a/lms/lib/dogfood/__init__.py b/lms/lib/dogfood/__init__.py
deleted file mode 100644
index d00d8ea793..0000000000
--- a/lms/lib/dogfood/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from check import *
diff --git a/lms/lib/dogfood/check.py b/lms/lib/dogfood/check.py
deleted file mode 100644
index 070d3f9262..0000000000
--- a/lms/lib/dogfood/check.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/python
-
-from random import choice
-import string
-import traceback
-
-from django.conf import settings
-import capa.capa_problem as lcp
-from dogfood.views import update_problem
-
-
-def GenID(length=8, chars=string.letters + string.digits):
- return ''.join([choice(chars) for i in range(length)])
-
-randomid = GenID()
-
-
-def check_problem_code(ans, the_lcp, correct_answers, false_answers):
- """
- ans = student's answer
- the_lcp = LoncapaProblem instance
-
- returns dict {'ok':is_ok,'msg': message with iframe}
- """
- pfn = "dog%s" % randomid
- pfn += the_lcp.problem_id.replace('filename', '') # add problem ID to dogfood problem name
- update_problem(pfn, ans, filestore=the_lcp.system.filestore)
- msg = ''
- msg += '' % (settings.MITX_ROOT_URL, pfn)
- msg += ''
-
- endmsg = """
Note: if the code text box disappears after clicking on "Check",
- please type something in the box to make it refresh properly. This is a
- bug with Chrome; it does not happen with Firefox. It is being fixed.
-
+% endif
From ca83c3953a18cdd7a3d91fbe2138633d595649c2 Mon Sep 17 00:00:00 2001
From: Valera Rozuvan
Date: Wed, 30 Jan 2013 18:06:27 +0200
Subject: [PATCH 05/29] Adding HTML5Video class and modifying coffee sources to
use it when video sources are provided instead of YouTube IDs.
---
common/lib/xmodule/xmodule/js/src/.gitignore | 3 +-
.../xmodule/js/src/videoalpha/display.coffee | 80 +++++--
.../js/src/videoalpha/display/html5_video.js | 196 ++++++++++++++++++
.../videoalpha/display/video_player.coffee | 32 ++-
.../lib/xmodule/xmodule/videoalpha_module.py | 10 +-
lms/templates/videoalpha.html | 15 +-
6 files changed, 301 insertions(+), 35 deletions(-)
create mode 100644 common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
diff --git a/common/lib/xmodule/xmodule/js/src/.gitignore b/common/lib/xmodule/xmodule/js/src/.gitignore
index 03534687ca..bbd93c90e3 100644
--- a/common/lib/xmodule/xmodule/js/src/.gitignore
+++ b/common/lib/xmodule/xmodule/js/src/.gitignore
@@ -1,2 +1 @@
-*.js
-
+# Please do not ignore *.js files. Some xmodules are written in JS.
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee b/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
index 1876330340..b97b02e68c 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
@@ -1,4 +1,4 @@
-class @Video
+class @VideoAlpha
constructor: (element) ->
@el = $(element).find('.video')
@id = @el.attr('id').replace(/video_/, '')
@@ -9,40 +9,77 @@ class @Video
@show_captions = @el.data('show-captions') == "true"
window.player = null
@el = $("#video_#{@id}")
- @parseVideos @el.data('streams')
- @fetchMetadata()
- @parseSpeed()
+
+ if @parseVideos(@el.data("streams")) is true
+ @videoType = "youtube"
+ @fetchMetadata()
+ @parseSpeed()
+ else
+ @videoType = "html5"
+ @parseVideoSources @el.data("mp4-source"), @el.data("webm-source"), @el.data("ogg-source")
+ @speeds = ["0.75", "1.0", "1.25", "1.5"]
+ @setSpeed($.cookie('video_speed'))
+
$("#video_#{@id}").data('video', this).addClass('video-load-complete')
@hide_captions = $.cookie('hide_captions') == 'true'
- if YT.Player
+ if ((@videoType is "youtube") and (YT.Player)) or ((@videoType is "html5") and (HTML5Video.Player))
+ console.log 'one'
@embed()
else
- window.onYouTubePlayerAPIReady = =>
- @el.each ->
- $(this).data('video').embed()
+ console.log 'two'
+ if @videoType is "youtube"
+ console.log 'three'
+ window.onYouTubePlayerAPIReady = ->
+ _this.embed()
+ else if @videoType is "html5"
+ console.log 'four'
+ console.log @videoType
+ console.log HTML5Video.Player
+ window.onHTML5PlayerAPIReady = ->
+ _this.embed()
youtubeId: (speed)->
@videos[speed || @speed]
- parseVideos: (videos) ->
+ VideoAlpha::parseVideos = (videos) ->
+ return false if (typeof videos isnt "string") or (videos.length is 0)
+
+ console.log 'We got this far'
+ console.log videos
+
@videos = {}
- $.each videos.split(/,/), (index, video) =>
+ _this = this
+ $.each videos.split(/,/), (index, video) ->
+ speed = undefined
video = video.split(/:/)
- speed = parseFloat(video[0]).toFixed(2).replace /\.00$/, '.0'
- @videos[speed] = video[1]
+ speed = parseFloat(video[0]).toFixed(2).replace(/\.00$/, ".0")
+ _this.videos[speed] = video[1]
+ true
+
+ VideoAlpha::parseVideoSources = (mp4Source, webmSource, oggSource) ->
+ @html5Sources =
+ mp4: null
+ webm: null
+ ogg: null
+
+ @html5Sources.mp4 = mp4Source if (typeof mp4Source is "string") and (mp4Source.length > 0)
+ @html5Sources.webm = webmSource if (typeof webmSource is "string") and (webmSource.length > 0)
+ @html5Sources.ogg = oggSource if (typeof oggSource is "string") and (oggSource.length > 0)
parseSpeed: ->
- @setSpeed($.cookie('video_speed'))
@speeds = ($.map @videos, (url, speed) -> speed).sort()
+ @setSpeed($.cookie('video_speed'))
- setSpeed: (newSpeed) ->
- if @videos[newSpeed] != undefined
+ VideoAlpha::setSpeed = (newSpeed) ->
+ if @speeds.indexOf(newSpeed) isnt -1
@speed = newSpeed
- $.cookie('video_speed', "#{newSpeed}", expires: 3650, path: '/')
+ $.cookie "video_speed", "" + newSpeed,
+ expires: 3650
+ path: "/"
else
- @speed = '1.0'
+ @speed = "1.0"
embed: ->
@player = new VideoPlayer video: this
@@ -55,9 +92,14 @@ class @Video
getDuration: ->
@metadata[@youtubeId()].duration
- log: (eventName) ->
- Logger.log eventName,
+ VideoAlpha::log = (eventName) ->
+ logInfo =
id: @id
code: @youtubeId()
currentTime: @player.currentTime
speed: @speed
+
+ if @videoType is "youtube"
+ logInfo.code = @youtubeId()
+ else logInfo.code = "html5" if @videoType is "html5"
+ Logger.log eventName, logInfo
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js b/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
new file mode 100644
index 0000000000..3db8bb97ed
--- /dev/null
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
@@ -0,0 +1,196 @@
+console.log('We are in "html5_video.js" script.');
+
+this.HTML5Video = (function () {
+ var HTML5Video = {};
+
+ HTML5Video.Player = (function () {
+
+ /*
+ * Constructor function for HTML5 Video player.
+ *
+ * @el - A DOM element where the HTML5 player will be inserted (as returned by jQuery(selector) function),
+ * or a selector string which will be used to select an element. This is a required parameter.
+ *
+ * @config - An object whose properties will be used as configuration options for the HTML5 video
+ * player. This is an optional parameter. In the case if this parameter is missing, or some of the config
+ * object's properties are missing, defaults will be used. The available options (and their defaults) are as
+ * follows:
+ *
+ * config = {
+ * 'width': 640,
+ *
+ * 'height': 390,
+ *
+ * 'videoSources': null, // An object of with properties being video sources. The property name is the
+ * // video format of the source. Supported video formats are: 'mp4', 'webm', and
+ * // 'ogg'. By default videoSources property is null. This means that the
+ * // player will initialize, and not play anything. If you do not provide a
+ * // 'videoSource' option, you can later call loadVideoBySource() method to load
+ * // a video and start playing it.
+ *
+ * 'playerVars': { // Object's properties identify player parameters.
+ *
+ * 'controls': 1, // Possible values: 0, or 1. Value of 1 will enable the default browser video
+ * // controls.
+ *
+ * 'start': null, // Possible values: positive integer. Position from which to start playing the
+ * // video. Measured in seconds. If value is null, or 'start' property is not
+ * // specified, the video will start playing from the beginning.
+ *
+ * 'end': null // Possible values: positive integer. Position when to stop playing the
+ * // video. Measured in seconds. If value is null, or 'end' property is not
+ * // specified, the video will end playing at the end.
+ *
+ * },
+ *
+ * 'events': { // Object's properties identify the events that the API fires, and the
+ * // functions (event listeners) that the API will call when those events occur.
+ * // If value is null, or property is not specified, then no callback will be
+ * // called for that event.
+ *
+ * 'onReady': null,
+ * 'onStateChange': null,
+ * 'onPlaybackQualityChange': null
+ * }
+ * }
+ */
+ function Player(el, config) {
+ console.log('We are inside HTML5Video.Player constructor.');
+
+ if (typeof el === 'string') {
+ this.el = $(el);
+ } else if ($.isPlainObject(el) === true) {
+ this.el = el;
+ } else {
+ // Error. el parameter is required.
+
+ // TODO: Make sure that nothing breaks if one of the methods available via this object's prototype
+ // is called after we return.
+
+ return;
+ }
+
+ console.log('We got a proper DOM element.');
+
+ if ($.isPlainObject(config) === true) {
+ this.config = config;
+ } else {
+ this.config = {
+ 'width': 640,
+ 'height': 390,
+ 'videoSource': '',
+ 'playerVars': {
+ 'controls': 1,
+ 'start': null,
+ 'end': null
+ },
+ 'events': {
+ 'onReady': null,
+ 'onStateChange': null,
+ 'onPlaybackQualityChange': null
+ }
+ };
+ }
+
+ console.log('The config is:');
+ console.log(this.config);
+ }
+
+ /*
+ * This function returns the quality of the video. Possible return values are (type String)
+ *
+ * highres
+ * hd1080
+ * hd720
+ * large
+ * medium
+ * small
+ *
+ * It returns undefined if there is no current video.
+ *
+ * If there is a current video, but it is impossible to determine it's quality, the function will return
+ * 'medium'.
+ */
+ Player.prototype.getPlayBackQuality = function () {
+ if (this.config.videoSource === '') {
+ return undefined;
+ }
+
+ // TODO: Figure out if we can get the quality of a video from a source (when it is loaded by the browser).
+
+ return 'medium';
+ };
+
+ /*
+ * The original YouTube API function player.setPlayBackQuality changed (if it was possible) the quality of the
+ * played video. In our case, this function will not do anything because we can't change the quality of HTML5
+ * video since we only get one source of video with one quality.
+ */
+ Player.prototype.setPlayBackQuality = function (value) {
+
+ };
+
+ Player.prototype.pauseVideo = function () {
+
+ };
+
+ Player.prototype.sekkTo = function () {
+
+ };
+
+ // YouTube API has player.loadVideoById, but since we are working with a video source, we will rename this
+ // function accordingly.
+ Player.prototype.loadVideoBySource = function () {
+
+ };
+
+ // YouTube API has player.cueVideoById, but since we are working with a video source, we will rename this
+ // function accordingly.
+ Player.prototype.cueVideoBySource = function () {
+
+ };
+
+ Player.prototype.setVolume = function () {
+
+ };
+
+ Player.prototype.getCurrentTime = function () {
+
+ };
+
+ Player.prototype.playVideo = function () {
+
+ };
+
+ Player.prototype.getPlayerState = function () {
+
+ };
+
+ Player.prototype.pauseVideo = function () {
+
+ };
+
+ Player.prototype.setVolume = function () {
+
+ };
+
+ Player.prototype.getVolume = function () {
+
+ };
+
+ return Player;
+ }());
+
+ HTML5Video.PlayerState = {
+ 'UNSTARTED': -1,
+ 'ENDED': 0,
+ 'PLAYING': 1,
+ 'PAUSED': 2,
+ 'BUFFERING': 3,
+ 'CUED': 5
+ };
+
+ return HTML5Video;
+}());
+
+console.log(HTML5Video);
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
index 22308a5568..3bee570bc8 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
@@ -1,7 +1,8 @@
class @VideoPlayer extends Subview
initialize: ->
- # Define a missing constant of Youtube API
- YT.PlayerState.UNSTARTED = -1
+ if @video.videoType is 'youtube'
+ # Define a missing constant of Youtube API
+ YT.PlayerState.UNSTARTED = -1
@currentTime = 0
@el = $("#video_#{@video.id}")
@@ -25,6 +26,7 @@ class @VideoPlayer extends Subview
@toggleFullScreen(event)
render: ->
+ console.log '1.1'
@control = new VideoControl el: @$('.video-controls')
@qualityControl = new VideoQualityControl el: @$('.secondary-controls')
@caption = new VideoCaption
@@ -34,6 +36,7 @@ class @VideoPlayer extends Subview
captionAssetPath: @video.caption_asset_path
unless onTouchBasedDevice()
@volumeControl = new VideoVolumeControl el: @$('.secondary-controls')
+ console.log '1.2'
@speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
@progressSlider = new VideoProgressSlider el: @$('.slider')
@playerVars =
@@ -43,20 +46,31 @@ class @VideoPlayer extends Subview
showinfo: 0
enablejsapi: 1
modestbranding: 1
+ console.log '1.3'
if @video.start
@playerVars.start = @video.start
@playerVars.wmode = 'window'
if @video.end
# work in AS3, not HMLT5. but iframe use AS3
@playerVars.end = @video.end
+ console.log '1.4'
- @player = new YT.Player @video.id,
- playerVars: @playerVars
- videoId: @video.youtubeId()
- events:
- onReady: @onReady
- onStateChange: @onStateChange
- onPlaybackQualityChange: @onPlaybackQualityChange
+ if @video.videoType is 'html5'
+ @player = new HTML5Video.Player @video.id,
+ playerVars: @playerVars,
+ videoSources: @video.html5Sources,
+ events:
+ onReady: @onReady
+ onStateChange: @onStateChange
+ onPlaybackQualityChange: @onPlaybackQualityChange
+ else if @video.videoType is 'youtube'
+ @player = new YT.Player @video.id,
+ playerVars: @playerVars
+ videoId: @video.youtubeId()
+ events:
+ onReady: @onReady
+ onStateChange: @onStateChange
+ onPlaybackQualityChange: @onPlaybackQualityChange
@caption.hideCaptions(@['video'].hide_captions)
addToolTip: ->
diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py
index e41f9783e4..912505d0a6 100644
--- a/common/lib/xmodule/xmodule/videoalpha_module.py
+++ b/common/lib/xmodule/xmodule/videoalpha_module.py
@@ -19,11 +19,13 @@ import time
log = logging.getLogger(__name__)
-class VideoModule(XModule):
+class VideoAlphaModule(XModule):
video_time = 0
icon_class = 'video'
- js = {'coffee':
+ js = {
+ 'js': [resource_string(__name__, 'js/src/videoalpha/display/html5_video.js')],
+ 'coffee':
[resource_string(__name__, 'js/src/time.coffee'),
resource_string(__name__, 'js/src/videoalpha/display.coffee')] +
[resource_string(__name__, 'js/src/videoalpha/display/' + filename)
@@ -31,7 +33,7 @@ class VideoModule(XModule):
in sorted(resource_listdir(__name__, 'js/src/videoalpha/display'))
if filename.endswith('.coffee')]}
css = {'scss': [resource_string(__name__, 'css/videoalpha/display.scss')]}
- js_module_name = "Video"
+ js_module_name = "VideoAlpha"
def __init__(self, system, location, definition, descriptor,
instance_state=None, shared_state=None, **kwargs):
@@ -145,6 +147,6 @@ class VideoModule(XModule):
class VideoAlphaDescriptor(RawDescriptor):
- module_class = VideoModule
+ module_class = VideoAlphaModule
stores_state = True
template_dir_name = "videoalpha"
diff --git a/lms/templates/videoalpha.html b/lms/templates/videoalpha.html
index 6cee9ed39b..58704ed6b1 100644
--- a/lms/templates/videoalpha.html
+++ b/lms/templates/videoalpha.html
@@ -2,11 +2,24 @@
% endif
From 0b73f0a59dd3ea52880202a9e9fe36078f9fd817 Mon Sep 17 00:00:00 2001
From: Vasyl Nakvasiuk
Date: Wed, 13 Feb 2013 13:11:59 +0200
Subject: [PATCH 23/29] remove unnecessary code
---
common/lib/xmodule/xmodule/videoalpha_module.py | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py
index f47a433fa7..b12dd359c3 100644
--- a/common/lib/xmodule/xmodule/videoalpha_module.py
+++ b/common/lib/xmodule/xmodule/videoalpha_module.py
@@ -111,16 +111,6 @@ class VideoAlphaModule(XModule):
return json.dumps({'success': True})
raise Http404()
- def get_progress(self):
- ''' TODO (vshnayder): Get and save duration of youtube video, then return
- fraction watched.
- (Be careful to notice when video link changes and update)
-
- For now, we have no way of knowing if the video has even been watched, so
- just return None.
- '''
- return None
-
def get_instance_state(self):
#log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({'position': self.position})
@@ -139,7 +129,6 @@ class VideoAlphaModule(XModule):
return self.system.render_template('videoalpha.html', {
'streams': self.videoalpha_list(),
'id': self.location.html_id(),
- 'position': self.position,
'sub': self.sub,
'sources': self.sources,
'track': self.track,
From 8cf8dcd10bcbdb45a019eccd072bc59c3675cb22 Mon Sep 17 00:00:00 2001
From: Vasyl Nakvasiuk
Date: Wed, 13 Feb 2013 13:28:09 +0200
Subject: [PATCH 24/29] rm `videoalpha_list` method
---
common/lib/xmodule/xmodule/videoalpha_module.py | 7 ++-----
lms/templates/videoalpha.html | 2 +-
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py
index b12dd359c3..2861fe57f8 100644
--- a/common/lib/xmodule/xmodule/videoalpha_module.py
+++ b/common/lib/xmodule/xmodule/videoalpha_module.py
@@ -39,7 +39,7 @@ class VideoAlphaModule(XModule):
XModule.__init__(self, system, location, definition, descriptor,
instance_state, shared_state, **kwargs)
xmltree = etree.fromstring(self.definition['data'])
- self.youtube = xmltree.get('youtube')
+ self.youtube_streams = xmltree.get('youtube')
self.sub = xmltree.get('sub')
self.position = 0
self.show_captions = xmltree.get('show_captions', 'true')
@@ -115,9 +115,6 @@ class VideoAlphaModule(XModule):
#log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({'position': self.position})
- def videoalpha_list(self):
- return self.youtube
-
def get_html(self):
if isinstance(modulestore(), MongoModuleStore):
caption_asset_path = StaticContent.get_base_url_path_for_course_assets(self.location) + '/subs_'
@@ -127,7 +124,7 @@ class VideoAlphaModule(XModule):
caption_asset_path = "/static/{0}/subs/".format(self.metadata['data_dir'])
return self.system.render_template('videoalpha.html', {
- 'streams': self.videoalpha_list(),
+ 'youtube_streams': self.youtube_streams,
'id': self.location.html_id(),
'sub': self.sub,
'sources': self.sources,
diff --git a/lms/templates/videoalpha.html b/lms/templates/videoalpha.html
index baed857a56..2ddcdd57e1 100644
--- a/lms/templates/videoalpha.html
+++ b/lms/templates/videoalpha.html
@@ -8,7 +8,7 @@
Date: Wed, 13 Feb 2013 13:35:10 +0200
Subject: [PATCH 25/29] add xml exmaple for videoalpja module in docstring
---
common/lib/xmodule/xmodule/videoalpha_module.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py
index 2861fe57f8..067932a5de 100644
--- a/common/lib/xmodule/xmodule/videoalpha_module.py
+++ b/common/lib/xmodule/xmodule/videoalpha_module.py
@@ -19,6 +19,18 @@ log = logging.getLogger(__name__)
class VideoAlphaModule(XModule):
+ """
+ XML source example:
+
+
+
+
+
+
+ """
video_time = 0
icon_class = 'video'
From b69b88a718cd7b61fc9513414c65cf11135380c7 Mon Sep 17 00:00:00 2001
From: Vasyl Nakvasiuk
Date: Wed, 13 Feb 2013 13:35:34 +0200
Subject: [PATCH 26/29] some small fix
---
common/lib/xmodule/xmodule/videoalpha_module.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py
index 067932a5de..6910728d8c 100644
--- a/common/lib/xmodule/xmodule/videoalpha_module.py
+++ b/common/lib/xmodule/xmodule/videoalpha_module.py
@@ -22,14 +22,14 @@ class VideoAlphaModule(XModule):
"""
XML source example:
-
-
-
-
-
+
+
+
+
+
"""
video_time = 0
icon_class = 'video'
From 1254e11836b89ae49630ce288a923f15f0bfa427 Mon Sep 17 00:00:00 2001
From: Valera Rozuvan
Date: Wed, 13 Feb 2013 14:09:35 +0200
Subject: [PATCH 27/29] Fixes and additions. Addressing comments by Carlos for
pull request 1409.
---
.../xmodule/js/src/videoalpha/display.coffee | 32 ++++++------
.../js/src/videoalpha/display/html5_video.js | 4 ++
.../videoalpha/display/video_player.coffee | 52 ++++++++++++++++---
3 files changed, 64 insertions(+), 24 deletions(-)
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee b/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
index 8079383f6f..a27362b094 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display.coffee
@@ -8,13 +8,13 @@ class @VideoAlpha
@caption_asset_path = @el.data('caption-asset-path')
@show_captions = @el.data('show-captions').toString() == "true"
@el = $("#video_#{@id}")
- if @parseVideos(@el.data("streams")) is true
+ if @parseYoutubeId(@el.data("streams")) is true
@videoType = "youtube"
@fetchMetadata()
@parseSpeed()
else
@videoType = "html5"
- @parseVideoSources @el.data('mp4-source'), @el.data('webm-source'), @el.data('ogg-source')
+ @parseHtml5Sources @el.data('mp4-source'), @el.data('webm-source'), @el.data('ogg-source')
@speeds = ['0.75', '1.0', '1.25', '1.50']
sub = @el.data('sub')
if (typeof sub isnt "string") or (sub.length is 0)
@@ -33,32 +33,30 @@ class @VideoAlpha
@hide_captions = true
$.cookie('hide_captions', @hide_captions, expires: 3650, path: '/')
@el.addClass 'closed'
- _this = this
if ((@videoType is "youtube") and (YT.Player)) or ((@videoType is "html5") and (HTML5Video.Player))
@embed()
else
if @videoType is "youtube"
- window.onYouTubePlayerAPIReady = ->
- _this.embed()
+ window.onYouTubePlayerAPIReady = =>
+ @embed()
else if @videoType is "html5"
- window.onHTML5PlayerAPIReady = ->
- _this.embed()
+ window.onHTML5PlayerAPIReady = =>
+ @embed()
youtubeId: (speed)->
@videos[speed || @speed]
- parseVideos: (videos)->
+ parseYoutubeId: (videos)->
return false if (typeof videos isnt "string") or (videos.length is 0)
@videos = {}
- _this = this
- $.each videos.split(/,/), (index, video) ->
+ $.each videos.split(/,/), (index, video) =>
speed = undefined
video = video.split(/:/)
speed = parseFloat(video[0]).toFixed(2).replace(/\.00$/, ".0")
- _this.videos[speed] = video[1]
+ @videos[speed] = video[1]
true
- parseVideoSources: (mp4Source, webmSource, oggSource)->
+ parseHtml5Sources: (mp4Source, webmSource, oggSource)->
@html5Sources =
mp4: null
webm: null
@@ -71,12 +69,14 @@ class @VideoAlpha
@speeds = ($.map @videos, (url, speed) -> speed).sort()
@setSpeed $.cookie('video_speed')
- setSpeed: (newSpeed)->
+ setSpeed: (newSpeed, updateCookie)->
if @speeds.indexOf(newSpeed) isnt -1
@speed = newSpeed
- $.cookie "video_speed", "" + newSpeed,
- expires: 3650
- path: "/"
+
+ if updateCookie isnt false
+ $.cookie "video_speed", "" + newSpeed,
+ expires: 3650
+ path: "/"
else
@speed = "1.0"
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js b/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
index fb34733323..acdc03932c 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display/html5_video.js
@@ -48,6 +48,10 @@ this.HTML5Video = (function () {
};
Player.prototype.getDuration = function () {
+ if (isFinite(this.video.duration) === false) {
+ return 0;
+ }
+
return this.video.duration;
};
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
index 566e4d785a..2def749d23 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
@@ -1,8 +1,13 @@
class @VideoPlayerAlpha extends SubviewAlpha
initialize: ->
+ # If we switch verticals while the video is playing, then HTML content is
+ # removed, but JS code is still executing (setInterval() method), and there will
+ # arise conflicts (no HTML content, but code tries to access it). Therefore
+ # we must pause the player (stop setInterval() method).
if (window.OldVideoPlayerAlpha) and (window.OldVideoPlayerAlpha.onPause)
window.OldVideoPlayerAlpha.onPause()
window.OldVideoPlayerAlpha = this
+
if @video.videoType is 'youtube'
@PlayerState = YT.PlayerState
# Define a missing constant of Youtube API
@@ -99,16 +104,37 @@ class @VideoPlayerAlpha extends SubviewAlpha
_this = this
switch event.data
when @PlayerState.UNSTARTED
+ # Before the video starts playing, let us see if we are in YouTube player,
+ # and if YouTube is in HTML5 mode. If both cases are true, then we can make
+ # it so that speed switching happens natively.
+
if @video.videoType is "youtube"
+ # Because YouTube API does not have a direct method to determine the mode we
+ # are in (Flash or HTML5), we rely on an indirect method. Currently, when in
+ # Flash mode, YouTube player reports that there is only one (1.0) speed
+ # available. When in HTML5 mode, it reports multiple speeds available. We
+ # will use this fact.
+ #
+ # NOTE: It is my strong belief that in the future YouTube Flash player will
+ # not get speed changes. This is a dying technology. So we can safely use
+ # this indirect method to determine player mode.
availableSpeeds = @player.getAvailablePlaybackRates()
prev_player_type = $.cookie('prev_player_type')
if availableSpeeds.length > 1
+ # If the user last accessed the page and watched a movie via YouTube
+ # player, and it was using Flash mode, then we must reset the current
+ # YouTube speed to 1.0 (by loading appropriate video that is encoded at
+ # 1.0 speed).
if prev_player_type == 'youtube'
$.cookie('prev_player_type', 'html5', expires: 3650, path: '/')
- @onSpeedChange null, '1.0'
+ @onSpeedChange null, '1.0', false
else if prev_player_type != 'html5'
$.cookie('prev_player_type', 'html5', expires: 3650, path: '/')
+ # Now we must update all the speeds to the ones available via the YouTube
+ # HTML5 API. The default speeds are not exactly the same as reported by
+ # YouTube, so we will remove the default speeds, and populate all the
+ # necessary data with correct available speeds.
baseSpeedSubs = @video.videos["1.0"]
$.each @video.videos, (index, value) ->
delete _this.video.videos[index]
@@ -116,15 +142,26 @@ class @VideoPlayerAlpha extends SubviewAlpha
$.each availableSpeeds, (index, value) ->
_this.video.videos[value.toFixed(2).replace(/\.00$/, ".0")] = baseSpeedSubs
_this.video.speeds.push value.toFixed(2).replace(/\.00$/, ".0")
+
+ # We must update the Speed Control to reflect the new avialble speeds.
@speedControl.reRender @video.speeds, @video.speed
+
+ # Now we set the videoType to 'HTML5'. This works because my HTML5Video
+ # class is fully compatible with YouTube HTML5 API.
@video.videoType = 'html5'
@video.setSpeed $.cookie('video_speed')
+
+ # Change the speed to the required one.
@player.setPlaybackRate @video.speed
else
+ # We are in YouTube player, and in Flash mode. Check previos mode.
if prev_player_type != 'youtube'
$.cookie('prev_player_type', 'youtube', expires: 3650, path: '/')
+ # We need to set the proper speed when previous mode was not 'youtube'.
+ @onSpeedChange null, $.cookie('video_speed')
+
@onUnstarted()
when @PlayerState.PLAYING
@onPlay()
@@ -176,11 +213,11 @@ class @VideoPlayerAlpha extends SubviewAlpha
@currentTime = time
@updatePlayTime time
- onSpeedChange: (event, newSpeed) =>
+ onSpeedChange: (event, newSpeed, updateCookie) =>
if @video.videoType is 'youtube'
@currentTime = Time.convert(@currentTime, parseFloat(@currentSpeed()), newSpeed)
newSpeed = parseFloat(newSpeed).toFixed(2).replace /\.00$/, '.0'
- @video.setSpeed newSpeed
+ @video.setSpeed newSpeed, updateCookie
if @video.videoType is 'youtube'
if @video.show_captions is true
@caption.currentSpeed = newSpeed
@@ -230,11 +267,10 @@ class @VideoPlayerAlpha extends SubviewAlpha
@player.pauseVideo() if @player.pauseVideo
duration: ->
- if @video.videoType is "youtube"
- return @video.getDuration()
- else if @video.videoType is "html5"
- return @player.getDuration()
- 0
+ duration = @player.getDuration()
+ if isFinite(duration) is false
+ duration = @video.getDuration()
+ duration
currentSpeed: ->
@video.speed
From 92dc8859a3fd619396ce4cc520e0e2ebefe58d95 Mon Sep 17 00:00:00 2001
From: Valera Rozuvan
Date: Wed, 13 Feb 2013 16:47:06 +0200
Subject: [PATCH 28/29] YouTube HTML5 mode is used by default. Fix typo in
video alpha template.
---
.../xmodule/js/src/videoalpha/display/video_player.coffee | 1 +
lms/templates/videoalpha.html | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
index 2def749d23..1b761594de 100644
--- a/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
+++ b/common/lib/xmodule/xmodule/js/src/videoalpha/display/video_player.coffee
@@ -59,6 +59,7 @@ class @VideoPlayerAlpha extends SubviewAlpha
showinfo: 0
enablejsapi: 1
modestbranding: 1
+ html5: 1
if @video.start
@playerVars.start = @video.start
@playerVars.wmode = 'window'
diff --git a/lms/templates/videoalpha.html b/lms/templates/videoalpha.html
index 2ddcdd57e1..2028d3c320 100644
--- a/lms/templates/videoalpha.html
+++ b/lms/templates/videoalpha.html
@@ -32,7 +32,7 @@
% if sources.get('main'):