diff --git a/README.md b/README.md index 91b79cb..d7ce18c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ AudioXBlock -=========== This is a simple XBlock which will play audio files as an HTML5 audio -element. If unavailable, it will fall back to an embed element. +element and render the transcript as plain text. If unavailable, it will fall back to an embed element. Usage: - + + + open-edx: + + key : "audio" diff --git a/audio/audio.py b/audio/audio.py index 57e1730..c746f1e 100644 --- a/audio/audio.py +++ b/audio/audio.py @@ -6,6 +6,17 @@ from xblock.fields import Scope, Integer, String from xblock.fragment import Fragment +import re +import requests + +regex = re.compile( + r'^(?:http|ftp)s?://' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... + r'localhost|' #localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'(?::\d+)?' # optional port + r'(?:/?|[/?]\S+)$', re.IGNORECASE) + class AudioXBlock(XBlock): """ @@ -14,25 +25,49 @@ class AudioXBlock(XBlock): # Fields are defined on the class. You can access them in your code as # self.. - src = String( - scope = Scope.settings, - help = "URL for MP3 file to play" - ) + + # this variable holds the source of main media file + src = String(scope=Scope.settings, help="URL for .ogg file to play", default="") + # reference for script file + transcript_src = String(scope=Scope.settings, help="plain text", default="") + # holds the downloadable link of media file + downloadable_src = String(scope=Scope.settings, help="URL for .mp3 file to download", default="") + is_transcript_url_valid = String(scope=Scope.settings, help="transcript url validation flag", default="True") + def resource_string(self, path): """Handy helper for getting resources from our kit.""" data = pkg_resources.resource_string(__name__, path) return data.decode("utf8") - # TO-DO: change this view to display your data your own way. def student_view(self, context=None): """ The primary view of the AudioXBlock, shown to students when viewing courses. """ html = self.resource_string("static/html/audio.html") - frag = Fragment(html.format(src = self.src)) - frag.add_css(self.resource_string("static/css/audio.css")) + + # Validate transcript link. + if self.transcript_src: + if not regex.match(self.transcript_src): + self.transcript_src = '' + self.is_transcript_url_valid = "False" + else: + r = requests.get(self.transcript_src) + content_type = r.headers['content-type'] + if "text/plain" != content_type: + self.transcript_src = '' + self.is_transcript_url_valid = "False" + + frag = Fragment(html.format(src=self.src, + transcript_src=self.transcript_src, + downloadable_src=self.downloadable_src, + is_transcript_url_valid=self.is_transcript_url_valid)) + + frag.add_css(self.resource_string("static/css/audio.scss")) + js = self.resource_string("static/js/src/audio.js") + frag.add_javascript(js) + frag.initialize_js('AudioXBlock') return frag def studio_view(self, context): @@ -40,8 +75,8 @@ def studio_view(self, context): The view for editing the AudioXBlock parameters inside Studio. """ html = self.resource_string("static/html/audio_edit.html") - frag = Fragment(html.format(src=self.src)) - + frag = Fragment(html.format(src=self.src, transcript_src=self.transcript_src, downloadable_src=self.downloadable_src)) + frag.add_css(self.resource_string("static/css/audio_edit.scss")) js = self.resource_string("static/js/src/audio_edit.js") frag.add_javascript(js) frag.initialize_js('AudioEditBlock') @@ -54,10 +89,11 @@ def studio_submit(self, data, suffix=''): Called when submitting the form in Studio. """ self.src = data.get('src') + self.transcript_src = data.get('transcript_src') + self.downloadable_src = data.get('downloadable_src') return {'result': 'success'} - # TO-DO: change this to create the scenarios you'd like to see in the # workbench while developing your XBlock. @staticmethod def workbench_scenarios(): @@ -65,9 +101,12 @@ def workbench_scenarios(): return [ ("AudioXBlock", """ - - - - + + + """), ] diff --git a/audio/static/css/audio.css b/audio/static/css/audio.css deleted file mode 100644 index 4e81145..0000000 --- a/audio/static/css/audio.css +++ /dev/null @@ -1,9 +0,0 @@ -/* CSS for AudioXBlock */ - -.audio_block .count { - font-weight: bold; -} - -.audio_block p { - cursor: pointer; -} \ No newline at end of file diff --git a/audio/static/css/audio.scss b/audio/static/css/audio.scss new file mode 100644 index 0000000..f489169 --- /dev/null +++ b/audio/static/css/audio.scss @@ -0,0 +1,380 @@ +/* CSS for AudioXBlock */ + +.audio_block .count { + font-weight: bold; +} + +.audio_block p { + cursor: pointer; +} +.pointer { + cursor: pointer; +} + + + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + +.grid { + margin: 0 auto; + max-width: 1200px; + width: 100%; +} + +.row { + width: 100%; + margin-bottom: 20px; + display: flex; +} + +.col-1 { + width: 8.33%; +} + +.col-2 { + width: 16.66%; +} + +.col-3 { + width: 25%; +} + +.col-4 { + width: 33.33%; +} + +.col-5 { + width: 41.66%; +} + +.col-6 { + width: 50%; +} + +.col-7 { + width: 58.33%; +} + +.col-8 { + width: 66.66%; +} + +.col-9 { + width: 75%; +} + +.col-10 { + width: 83.33%; +} + +.col-11 { + width: 91.66%; +} + +.col-12 { + width: 100%; +} + +.audio-img { + width: 100%; + height: 300px; + background: #f9f9f9 url('data:image/png;base64,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'); + background-position: 50% 50%; +} +.progress-bar { + height: 6px; + position: relative; + overflow: hidden; + border-top: 1px solid #292d30; +} +#play-bar { + position: absolute; + left:0; + top:0; + height: 5px; + z-index: 9; + vertical-align: top; +} +.canvas-bar { + height: 5px; + width: 100%; + line-height: 0; + font-size: 0; + vertical-align: top; +} +.audio-bar { + background: #282C2F; + height: 32px; + padding: 8px 10px; +} +.audio-bar:after { + display: block; + clear: both; + content: ""; +} +.play-btn-holder { + float: left; + width: 40px; +} +.play { + padding: 0; + width: 16px; + height: 0; + background: none; + border: none; + float: left; + margin: 0 0 0 15px; + border-top: 8px solid transparent; + border-left: 12px solid #fff; + border-bottom: 8px solid transparent; +} +.pause { + padding: 0; + background: none; + border:none; + float: left; + margin: 0 0 0 15px; + width: 14px; + height: 16px; + border-right: 4px solid #fff; + border-left: 4px solid #fff; +} +#timer { + float: left; + font-size: 12px; + line-height:16px; + color: #fff; + margin: 0 0 0 20px; +} +.audio-bar-right { + float: right; +} +.speed-holder { + float: left; + width: 110px; + padding: 0 0 0 20px; + position: relative; +} +.speed-holder:after { + position: absolute; + left:0; + top:-8px; + bottom: -8px; + content: ""; + border-left: 1px solid #363C3F; +} +.speed-holder .value { + color: #ffffff; + display: block; + font-size: 12px; + line-height:16px; + position: relative; + padding: 0 0 0 10px; +} +.speed-holder .value:after { + position: absolute; + left: 0; + top: 4px; + content: ""; + border-top: 4px solid transparent; + border-left: 6px solid #fff; + border-bottom: 4px solid transparent; +} +.speed-list { + list-style: none; + margin: 0; + padding: 0 0 9px; + position: absolute; + left: 0; + bottom:100%; + z-index: 999; + width: 100%; + background: #000; +} +.speed-list li { + color: #fff; + padding: 5px; + font-size: 12px; + line-height:16px; + color: #fff; +} +.speed-list li:hover { + background: #333333; +} +.volume-holder { + float: left; + margin: 0 15px 0 0; + position: relative; +} +.volume-holder .icon-volume{ + width: 16px; + display: block; +} +.volume-holder .icon-volume img{ + width: 100%; + height: auto; + display: block; +} +.volume-control { + position: absolute; + width: 50px; + transform: rotate(-90deg); + z-index: 999; + left: -19px; + top: -34px; +} + +input[type=range].slider * { + border: none !important; + border-radius: 0 !important; + box-shadow: none !important; + outline: none !important; +} + +input[type=range].slider:focus { +} + +input[type=range].slider { + -webkit-appearance: none; + -moz-appearance: none; + width: 100%; + margin: 0; + position: relative; +} +input[type=range].slider:focus { + outline: none; +} +input[type=range].slider::-webkit-slider-runnable-track { + width: 100%; + height: 5px; + cursor: pointer; + background:#CD578D; + +} +input[type=range].slider::-webkit-slider-thumb { + height: 5px; + width: 10px; + background: #ddd; + cursor: pointer; + -webkit-appearance: none; + margin-top: 0px; +} +input[type=range].slider:focus::-webkit-slider-runnable-track { + background: #CD578D; +} +input[type=range].slider::-moz-range-track { + width: 100%; + height: 5px; + cursor: pointer; + background: #CD578D; +} +input[type=range].slider::-moz-range-thumb { + height: 10px; + width: 5px; + background:#ddd; + cursor: pointer; +} +input[type=range].slider::-ms-track { + width: 100%; + height: 10px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; +} +input[type=range].slider::-ms-fill-lower { + background: #CD578D; +} +input[type=range].slider::-ms-fill-upper { + background: #CD578D; +} +input[type=range].slider::-ms-thumb { + width: 5px; + background: #CD578D; + cursor: pointer; + height: 5px; +} +input[type=range].slider:focus::-ms-fill-lower { + background: #ddd; +} +input[type=range].slider:focus::-ms-fill-upper { + background: #ddd; +} +.seekbar-style { + z-index: 99; + position: absolute !important; + + top:0; + height: 5px; + background: none; + border:none; +} +.seekbar-style * { + border:none !important; + box-shadow: none !important; + border-radius: 0 !important; +} +input[type=range].seekbar-style.slider::-webkit-slider-runnable-track { + background: none !important; +} +input[type=range].seekbar-style.slider:focus::-webkit-slider-runnable-track { + background: none !important +} +input[type=range].seekbar-style.slider::-moz-range-track { + background: none !important +} +input[type=range].seekbar-style.slider::-ms-fill-lower { + background: none !important +} +input[type=range].seekbar-style.slider::-ms-fill-upper { + background: none !important +} +input[type=range].seekbar-style.slider::-ms-thumb { + background: none !important +} +input[type=range].seekbar-style.slider::-webkit-slider-thumb { + width: 10px; + background: #fff; +} +input[type=range].seekbar-style.slider::-moz-range-thumb { + width: 10px; + background: #fff; +} +input[type=range].seekbar-style.slider:focus::-ms-fill-lower { + background: #fff; +} +input[type=range].seekbar-style.slider:focus::-ms-fill-upper { + background: #fff; +} + +.download-links a{ + margin-right: 15px; +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio/static/css/audio_edit.scss b/audio/static/css/audio_edit.scss new file mode 100644 index 0000000..83f422b --- /dev/null +++ b/audio/static/css/audio_edit.scss @@ -0,0 +1,3 @@ +.wrapper-modal-window-edit-xblock .edit-xblock-modal .modal-actions { + display: block !important; +} \ No newline at end of file diff --git a/audio/static/html/audio.html b/audio/static/html/audio.html index 78778f5..f5caef4 100644 --- a/audio/static/html/audio.html +++ b/audio/static/html/audio.html @@ -1,7 +1,58 @@ - - - - - + + + * No playable audio source found. + * Invalid transcript source. + + + + + + + + + + + + + + + + -:--:-- + + + + + + + + + + Speed 0.25x + Speed 0.5x + Speed 0.75x + Speed 1.0x + Speed 1.25x + Speed 1.5x + Speed 2x + + Speed 1.0x + + + + + + + + Downloads: + + Transcript + Audio (.mp3) + + + + + diff --git a/audio/static/html/audio_edit.html b/audio/static/html/audio_edit.html index 9d4e0b9..424d538 100644 --- a/audio/static/html/audio_edit.html +++ b/audio/static/html/audio_edit.html @@ -2,13 +2,21 @@ - Audio Source Location - + Audio Source (.ogg) + + + + + + Audio Source (.mp3) + + + + + + Transcript Source Location + - - Save - Cancel - - \ No newline at end of file + diff --git a/audio/static/js/src/audio.js b/audio/static/js/src/audio.js index e69de29..57491dc 100644 --- a/audio/static/js/src/audio.js +++ b/audio/static/js/src/audio.js @@ -0,0 +1,217 @@ +function AudioXBlock(runtime, element) { + + // reference of main audio file + var audio = $(element).find('#audio'); + // reference of buffering canvas, that indicates audio file buffered progress + var bufferingCanvas = $(element).find('#buffering-canvas'); + // context of buffering canvas + var bufferingCanvasContext = bufferingCanvas[0].getContext('2d'); + // variable that will hold the incremental progress for buffering canvas + var bufferIncrement; + + // reference of play bar canvas, that indicates the progress of playing audio file + var playBarCanvas =$(element).find('#play-bar'); + // context of play bar canvas + var playBarContext = playBarCanvas[0].getContext('2d'); + + + // Getting DOM references with JQuery + var playBtn = $(element).find('#play-btn'); + var volume = $(element).find('#volume'); + var playbackRateSet = $(element).find('#speed'); + var playbackRateBtn = $(element).find('#playback-rate-controller'); + var pauseBtn = $(element).find('#pause-btn'); + var volumeBtn = $(element).find('#volume-controller'); + var seekBar = $(element).find('#seekbar'); + var timer = $(element).find('#timer'); + var transcript = $(element).find('#transcript-embed'); + var audioSrc = $(element).find('#audio_src').attr('src'); + var audioDownloadableSrc = $(element).find('#audio-link').attr('href'); + + var noAudioSourceMessage = $(element).find('#no-audio-source') + var noAudioTranscriptMessage = $(element).find('#no-transcript-source') + var audioPlayerDiv = $(element).find('#audio-player-div'); + var transcriptDiv = $(element).find('#transcript-div-id'); + var transcriptDownloadableLink = $(element).find('#transcript-link'); + var downloadsHeading = $(element).find('#downloads-heading'); + var downloadsDiv = $(element).find('#downloads-div'); + + // setting up initial state of player + pauseBtn.hide(); + volume.val(audio[0].volume); + playbackRateSet.hide(); + volume.hide(); + seekBar[0].value = 0; + noAudioTranscriptMessage.hide(); + + if(!audioSrc.endsWith('.ogg')){ + noAudioSourceMessage.show(); + } + else{ + noAudioSourceMessage.hide(); + } + + if(!transcript.attr('src') && !audioDownloadableSrc) { + downloadsDiv.hide(); + downloadsHeading.hide(); + } else if(!transcript.attr('src')){ + downloadsDiv.show(); + downloadsHeading.show(); + transcriptDownloadableLink.hide(); + } else if(!audioDownloadableSrc){ + downloadsDiv.show(); + downloadsHeading.show(); + $(element).find('#audio-link').hide(); + } + + if(!transcript.attr('src')){ + transcriptDiv.hide(); + audioPlayerDiv.removeClass('col-6'); + audioPlayerDiv.addClass('col-12'); + } + + if(transcript.attr('is-transcript-url-valid') === "False"){ + noAudioTranscriptMessage.show(); + transcript.hide(); + transcriptDownloadableLink.show(); + } + + // loading the meta data for audio file, e.g. audio length, and playing automatically + audio.bind('loadedmetadata', function() { + bufferIncrement = bufferingCanvas[0].width / audio[0].duration; + }); + + + // setting up the buffering canvas + bufferingCanvasContext.fillStyle = '#4F595D'; + bufferingCanvasContext.fillRect(0, 0, bufferingCanvas[0].width, bufferingCanvas[0].height); + bufferingCanvasContext.fillStyle = '#697275'; + + //setting up the play bar canvas + playBarContext.fillStyle = '#CD578D'; + + + // this event will fired when the time indicated by the currentTime attribute has been updated. + audio.bind('timeupdate', function() { + for ( var i = 0; i < audio[0].buffered.length; i++) { + var startX = audio[0].buffered.start(i) * bufferIncrement; + var endX = audio[0].buffered.end(i) * bufferIncrement; + var width = endX - startX; + // keep updating the buffered canvas + bufferingCanvasContext.fillRect(startX, 0, width, bufferingCanvas[0].height); + bufferingCanvasContext.rect(startX, 0, width, bufferingCanvas[0].height); + } + // calculating the playing progress in percentage + var playedAudio = (audio[0].currentTime / audio[0].duration ) * 100; + // keep updating the play bar canvas + playBarContext.fillRect(0, 0, playedAudio, playBarCanvas[0].height); + }); + + /* + this event is fired when a seek operation completed. + this event is needed to clear the play bar canvas whenever user seek. + */ + audio.bind('seeked', function() { + playBarContext.clearRect(0,0,playBarCanvas[0].width, playBarCanvas[0].height); + }); + + + // handler for play button click event + playBtn.click(function () { + audio[0].play(); + $(this).hide(); + pauseBtn.show(); + }); + + // handler for pause button click event + pauseBtn.click(function () { + audio[0].pause(); + $(this).hide(); + playBtn.show(); + }); + + // volume handler + volume.bind('mousemove', function () { + audio[0].volume = $(this).val(); + if (audio[0].volume == 0) { + audio[0].muted = true; + } else { + audio[0].muted = false; + } + }); + + // handler for volume controller click event + volumeBtn.click(function () { + volume.show(); + }); + + // handler for seek change event to current time of audio + seekBar.change(function () { + audio[0].currentTime = $(this).val(); + }); + + // handler for playback rate button click event + playbackRateBtn.click(function () { + playbackRateSet.show(); + }); + + // handler for rates buttons click event + playbackRateSet.children().click(function () { + // getting new playback rate + var newRate = parseFloat($(this).attr('rate')); + // setting ne playback rate + audio[0].playbackRate = newRate; + + // updating the play back rate button state + playbackRateBtn.html($(this).html()); + //updating rates state + playbackRateSet.hide(); + }); + + + // reference of seek bar + var seekbar = $(element).find('#seekbar'); + + // this event is fired when the duration attribute has been updated + audio.bind('durationchange', function() { + seekbar[0].min = 0; + seekbar[0].max = audio[0].duration; + seekbar[0].value = 0; + }); + + // this event is fired when the time indicated by the currentTime attribute has been updated. + audio.bind('timeupdate', function() { + var sec = audio[0].currentTime; + var h = Math.floor(sec / 3600); + sec = sec % 3600; + var min = Math.floor(sec / 60); + sec = Math.ceil(sec % 60); + if (sec.toString().length < 2) {sec = "0" + sec;} + if (min.toString().length < 2) {min = "0" + min;} + timer.html(h + ":" + min + ":" + sec); + seekbar[0].min = audio[0].startTime; + seekbar[0].max = audio[0].duration; + seekbar[0].value = audio[0].currentTime; + }); + + // this event is fired when playback has stopped. + audio.bind('ended', function() { + playBtn.show(); + pauseBtn.hide(); + }); + + // out side click handler for playback rate set and volume controller + $(document).mouseup(function(e) { + if (!playbackRateSet.is(e.target)) { + playbackRateSet.hide(); + } + if (!volume.is(e.target)) { + volume.hide(); + } + }); + + //______________ + + + transcript[0].height = "100%"; +} diff --git a/audio/static/js/src/audio_edit.js b/audio/static/js/src/audio_edit.js index 42a7543..d73b99f 100644 --- a/audio/static/js/src/audio_edit.js +++ b/audio/static/js/src/audio_edit.js @@ -1,15 +1,19 @@ function AudioEditBlock(runtime, element) { - $(element).find('.save-button').bind('click', function() { - var handlerUrl = runtime.handlerUrl(element, 'studio_submit'); - var data = { - src: $(element).find('input[name=audio_src]').val() - }; - $.post(handlerUrl, JSON.stringify(data)).done(function(response) { - window.location.reload(false); + + $(element).parents('.edit-xblock-modal').find('.action-save').unbind('click').bind('click', function(evt){ + evt.preventDefault(); + var handlerUrl = runtime.handlerUrl(element, 'studio_submit'); + var data = { + src: $(element).find('input[name=audio_src]').val(), + downloadable_src: $(element).find('input[name=audio_src_downloadable]').val(), + transcript_src: $(element).find('input[name=transcript_src]').val() + }; + $.post(handlerUrl, JSON.stringify(data)).done(function(response) { + runtime.notify('save', {state: 'end'}); + }); }); - }); - $(element).find('.cancel-button').bind('click', function() { - runtime.notify('cancel', {}); - }); + $(element).find('.action-cancel').bind('click', function() { + runtime.notify('cancel', {}); + }); } \ No newline at end of file