{"name":"Super Interactive Video","key":"superinteractivevideo","version":"1.0.4","instructions":"Shows a video + interactive transcript. The captions are made and edited here on Moodle. ","showatto":"0","showplayers":"1","requirecss":"","requirejs":"","shim":"","defaults":"lang=\"en\",vimeoid=empty","amd":"1","body":"
\n\n\t
\n\t
\n
\n \n \n \n \n \n \n \n \n \n
\n
\n
\n
\n","bodyend":"","script":"//Define interactive_transcript (it) see cloud poodll submission amd/src/interactivetranscript.js\nvar it={\n init: function(itoptions){\n var config={};\n var that =this; \n config.settings ={};\n if(itoptions['theplayer']) {\n config.prefix = itoptions['cssprefix'];\n config.theplayer = itoptions['theplayer'];\n config.dummyplayerid = itoptions['dummyplayerid'];\n config.dummyplayer = $('#' + config.dummyplayerid)[0]; \n config.title = itoptions['title'];\n config.containerid = itoptions['containerid'];\n config.scrollingthing = itoptions['scrollingthing'];\n config.currentTrack = 0;\n config.theduration= itoptions['duration'];\n config.textTranscript='';\n var transcript = this.transcript(config);\n\n $('#' + itoptions['scrollcontainerid']).append(transcript.el());\n \n //unregisgter old, then register download transcript event\n $('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + 'download').off('click');\n \n $('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + 'download').on('click',function(){\n that.downloadTranscript(config);\n return false;\n });\n \n }//end of if player\n\n },\n\n downloadTranscript: function(config){\n\t\t\t\tvar element = document.createElement('a');\n\t\t\t\telement.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config.textTranscript));\n\t\t\t\telement.setAttribute('download', config.title + '.txt');\n\t\t\t\telement.style.display = 'none';\n\t\t\t\tdocument.body.appendChild(element);\n\t\t\t\telement.click();\n\t\t\t\tdocument.body.removeChild(element);\n },\n\n // Defaults\n defaults: {\n autoscroll: true,\n clickArea: 'line', //the clickable part of line text,line,timestamp, none\n showTrackSelector: false, //the drop down box of caption tracks\n followPlayerTrack: true,\n scrollToCenter: false, //show current text in center\n stopScrollWhenInUse: false, //stop scrolling when user interacting\n },\n\n /*global */\n utils: {\n prefix: 'transcript',\n secondsToTime: function (timeInSeconds) {\n var hour = Math.floor(timeInSeconds / 3600);\n var min = Math.floor(timeInSeconds % 3600 / 60);\n var sec = Math.floor(timeInSeconds % 60);\n sec = (sec < 10) ? '0' + sec : sec;\n min = (hour > 0 && min < 10) ? '0' + min : min;\n if (hour > 0) {\n return hour + ':' + min + ':' + sec;\n }\n return min + ':' + sec;\n },\n localize: function (string) {\n return string; // TODO: do something here;\n },\n createEl: function (elementName, className) {\n className = className || '';\n var el = document.createElement(elementName);\n el.className = className;\n return el;\n },\n extend: function(obj) {\n var type = typeof obj;\n if (!(type === 'function' || type === 'object' && !!obj)) {\n return obj;\n }\n var source, prop;\n for (var i = 1, length = arguments.length; i < length; i++) {\n source = arguments[i];\n for (prop in source) {\n obj[prop] = source[prop];\n }\n }\n return obj;\n }\n \n },\n\n eventEmitter: {\n handlers_: [],\n on: function on (object, eventtype, callback) {\n if (typeof callback === 'function') {\n this.handlers_.push([object, eventtype, callback]);\n } else {\n throw new TypeError('Callback is not a function.');\n }\n },\n trigger: function trigger (object, eventtype) {\n this.handlers_.forEach( function(h) {\n if (h[0] === object &&\n h[1] === eventtype) {\n h[2].apply();\n }\n });\n }\n },\n\n scrollerProto: function(config) {\n\n var initHandlers = function (el) {\n var self = this;\n // The scroll event. We want to keep track of when the user is scrolling the transcript.\n el.addEventListener('scroll', function () {\n if (self.isAutoScrolling) {\n\n // If isAutoScrolling was set to true, we can set it to false and then ignore this event.\n // It wasn't the user.\n self.isAutoScrolling = false; // event handled\n } else {\n\n // We only care about when the user scrolls. Set userIsScrolling to true and add a nice class.\n self.userIsScrolling = true;\n el.classList.add('is-inuse');\n }\n });\n\n // The mouseover event.\n el.addEventListener('mouseenter', function () {\n self.mouseIsOverTranscript = true;\n });\n el.addEventListener('mouseleave', function () {\n self.mouseIsOverTranscript = false;\n\n // Have a small delay before deciding user as done interacting.\n setTimeout(function () {\n\n // Make sure the user didn't move the pointer back in.\n if (!self.mouseIsOverTranscript) {\n self.userIsScrolling = false;\n el.classList.remove('is-inuse');\n }\n }, 1000);\n });\n };\n\n // Init instance variables\n var init = function (element) {\n this.element = element;\n this.userIsScrolling = false;\n\n //default to true in case user isn't using a mouse;\n this.mouseIsOverTranscript = true;\n this.isAutoScrolling = true;\n initHandlers.call(this, this.element);\n return this;\n };\n\n // Easing function for smoothness.\n var easeOut = function (time, start, change, duration) {\n return start + change * Math.sin(Math.min(1, time / duration) * (Math.PI / 2));\n };\n\n // Animate the scrolling.\n var scrollTo = function (element, newPos, duration) {\n var startTime = Date.now();\n var startPos = element.scrollTop;\n var self = this;\n\n // Don't try to scroll beyond the limits. You won't get there and this will loop forever.\n newPos = Math.max(0, newPos);\n newPos = Math.min(element.scrollHeight - element.clientHeight, newPos);\n var change = newPos - startPos;\n \n //if not animating\n if(true){ \n element.scrollTop= newPos;\n //if animating ... which doesn't work\n }else{\n // This inner function is called until the elements scrollTop reaches newPos.\n var updateScroll = function () {\n var now = Date.now();\n var time = now - startTime;\n self.isAutoScrolling = true;\n element.scrollTop = easeOut(time, startPos, change, duration);\n if (element.scrollTop !== newPos) {\n window.requestAnimationFrame(updateScroll, element);\n }\n };//end of update scroll\n window.requestAnimationFrame(updateScroll, element);\n }\n };\n\n // Scroll an element's parent so the element is brought into view.\n var scrollToElement = function (element) {\n if (this.canScroll()) {\n //elem=\"line\" parent=\"body\" parent.parent=\"scrolling\" parent.parent.parent.scrollable\n var parent = element.parentElement.parentElement.parentElement;\n var parentOffsetBottom = parent.offsetTop + parent.clientHeight;\n var elementOffsetBottom = element.offsetTop + element.clientHeight;\n var relTop = element.offsetTop;\n var relBottom = (element.offsetTop + element.clientHeight);\n var centerPosCorrection = 0;\n var newPos;\n /*\n console.log('element.offsetTop: ' + element.offsetTop );\n console.log('element.clientHeight: ' + element.clientHeight );\n console.log('parent.offsetTop: ' + parent.offsetTop );\n console.log('parent.scrollTop: ' + parent.scrollTop );\n console.log('parent.clientHeight: ' + parent.clientHeight );\n console.log(element);\n console.log(parent);\n */ \n \n //scroll to center if we must\n if (config.settings.scrollToCenter){\n centerPosCorrection = Math.round(parent.clientHeight/2 - element.clientHeight/2);\n }\n // If the top of the line is above the top of the parent view, were scrolling up,\n // so we want to move the top of the element downwards to match the top of the parent.\n if (relTop < parent.scrollTop + centerPosCorrection) {\n newPos = element.offsetTop -centerPosCorrection;\n\n // If the bottom of the line is below the parent view, we're scrolling down, so we want the\n // bottom edge of the line to move up to meet the bottom edge of the parent.\n } else if (relBottom > (parent.scrollTop + parent.clientHeight) - centerPosCorrection) {\n newPos = elementOffsetBottom + centerPosCorrection;\n }\n\n // Don't try to scroll if we haven't set a new position. If we didn't\n // set a new position the line is already in view (i.e. It's not above\n // or below the view)\n // And don't try to scroll when the element is already in position.\n if (newPos !== undefined && parent.scrollTop !== newPos) {\n scrollTo(parent, newPos, 400);\n }\n }\n };\n\n\n // Return whether the element is scrollable.\n var canScroll = function () {\n var el = this.element;\n //console.log(el.scrollHeight + ' ' + el.offsetHeight);\n return true;//el.scrollHeight > el.offsetHeight;\n };\n\n // Return whether the user is interacting with the transcript.\n var inUse = function () {\n return this.userIsScrolling;\n };\n\n return {\n init: init,\n to : scrollToElement,\n canScroll : canScroll,\n inUse : inUse\n }\n },\n\n scroller: function(element,config) {\n return Object.create(this.scrollerProto(config)).init(element);\n },\n\n\n /*global config*/\n trackList: function(config) {\n var activeTrack;\n return {\n get: function () {\n var validTracks = [];\n var i, track;\n config.tracks = config.dummyplayer.textTracks;\n for (i = 0; i < config.tracks.length; i++) {\n track = config.tracks[i];\n if (track.kind === 'captions' || track.kind === 'subtitles') {\n validTracks.push(track);\n }\n }\n return validTracks;\n },\n active: function (tracks) {\n var i, track;\n for (i = 0; i < config.tracks.length; i++) {\n track = config.tracks[i];\n if (track.mode === 'showing') {\n activeTrack = track;\n return track;\n }\n }\n // fallback to first track\n return activeTrack || tracks[0];\n },\n };\n },\n\n /*globals utils, eventEmitter,scrollable*/\n\n widget: function(config) {\n var that = this;\n var thewidget = {};\n thewidget.element = {};\n thewidget.body = {};\n var on = function (event, callback) {\n eventEmitter.on(that, event, callback);\n };\n var trigger = function (event) {\n eventEmitter.trigger(that, event);\n };\n var initToolbar = function () {\n $('#' + config.containerid + ' .' + config.prefix + 'title').text(config.title);\n };\n var createSelector = function (){\n var selector = that.utils.createEl('select', config.prefix + '-selector');\n config.validTracks.forEach(function (track, i) {\n var option = document.createElement('option');\n option.value = i;\n option.textContent = track.label + ' (' + track.language + ')';\n selector.appendChild(option);\n });\n selector.addEventListener('change', function (e) {\n setTrack(document.querySelector('#' + config.prefix + '-' + config.dummyplayerid + ' option:checked').value);\n trigger('trackchanged');\n });\n return selector;\n };\n var clickToSeekHandler = function (event) {\n var clickedClasses = event.target.classList;\n var clickedTime = event.target.getAttribute('data-begin') || event.target.parentElement.getAttribute('data-begin');\n if (clickedTime !== undefined && clickedTime !== null) { // can be zero\n if ((config.settings.clickArea === 'line') || // clickArea: 'line' activates on all elements\n (config.settings.clickArea === 'timestamp' && clickedClasses.contains(config.prefix + '-timestamp')) ||\n (config.settings.clickArea === 'text' && clickedClasses.contains(config.prefix + '-text'))) {\n config.theplayer.currentTime= clickedTime;\n }\n }\n };\n var createLine = function (cue) {\n var line = that.utils.createEl('div', config.prefix +'-line');\n var timestamp = that.utils.createEl('span',config.prefix + '-timestamp');\n var text = that.utils.createEl('span', config.prefix + '-text');\n line.setAttribute('data-begin', cue.startTime);\n line.setAttribute('tabindex', thewidget._options.tabIndex || 0);\n timestamp.textContent = that.utils.secondsToTime(cue.startTime);\n text.innerHTML = cue.text;\n line.appendChild(timestamp);\n line.appendChild(text);\n return line;\n };\n \n var createTranscriptBody = function (track) {\n if (typeof track !== 'object') {\n track = config.dummyplayer.textTracks()[track];\n }\n var body = that.utils.createEl('div', config.prefix + '-body');\n var line, i;\n var fragment = document.createDocumentFragment();\n // activeCues returns null when the track isn't loaded (for now?)\n if (!track.activeCues) {\n // If cues aren't loaded, set mode to hidden, wait, and try again.\n // But don't hide an active track. In that case, just wait and try again.\n if (track.mode !== 'showing') {\n track.mode = 'hidden';\n }\n window.setTimeout(function() {\n createTranscriptBody(track);\n }, 100);\n } else {\n var cues = track.cues;\n var textTranscript =[];\n for (i = 0; i < cues.length; i++) {\n line = createLine(cues[i]);\n if(!(cues[i].text==='')){\n textTranscript.push(cues[i].text);\n }\n fragment.appendChild(line);\n }\n //prepare text transcript\n config.textTranscript=textTranscript.join(' ');\n \n //build body of transcript\n body.innerHTML = '';\n body.appendChild(fragment);\n body.setAttribute('lang', track.language);\n body.scroll = that.scroller(thewidget.element,config);\n body.addEventListener('click', clickToSeekHandler);\n thewidget.element.replaceChild(body, thewidget.body);\n thewidget.body = body;\n }\n\n };\n var create = function (options) {\n initToolbar();\n var el = document.createElement('div');\n thewidget._options = options;\n thewidget.element = el;\n el.setAttribute('id', config.scrollingthing);\n \n if (config.settings.showTrackSelector) {\n var selector = createSelector();\n el.appendChild(selector);\n }\n thewidget.body = that.utils.createEl('div',config.prefix + '-body');\n el.appendChild(thewidget.body);\n setTrack(config.currentTrack);\n return this;\n };\n var setTrack = function (track, trackCreated) {\n createTranscriptBody(track, trackCreated);\n };\n var setCue = function (time) {\n var active, i, line, begin, end;\n var lines = thewidget.body.children;\n for (i = 0; i < lines.length; i++) {\n line = lines[i];\n begin = line.getAttribute('data-begin');\n if (i < lines.length - 1) {\n end = lines[i + 1].getAttribute('data-begin');\n } else {\n end = config.theduration;\n }\n if (time > begin && time < end) {\n if (!line.classList.contains('is-active')) { // don't update if it hasn't changed\n line.classList.add('is-active');\n if (config.settings.autoscroll && !(config.settings.stopScrollWhenInUse && thewidget.body.scroll.inUse())) {\n thewidget.body.scroll.to(line);\n }\n }\n } else {\n line.classList.remove('is-active');\n }\n }\n };\n var el = function () {\n return thewidget.element;\n };\n return {\n create: create,\n setTrack: setTrack,\n setCue: setCue,\n el : el,\n on: on,\n trigger: trigger,\n };\n },\n\n transcript: function(config){\n var that=this;\n var options=this.defaults;\n this.utils.prefix='transcript';\n\n config.validTracks = this.trackList(config).get();\n config.currentTrack = this.trackList(config).active(config.validTracks);\n config.settings = options;\n config.widget = this.widget(config).create(options);\n\n var timeUpdate = function (eventdata) {\n config.widget.setCue(config.theplayer.currentTime);\n };\n var updateTrack = function () {\n config.currentTrack = that.trackList(config).active(config.validTracks);\n config.widget.setTrack(config.currentTrack);\n };\n if (config.validTracks.length > 0) {\n config.theplayer.ontimeupdate =timeUpdate;\n } else {\n throw new Error('transcript: No tracks found!');\n }\n return {\n el: function () {\n return config.widget.el();\n },\n setTrack: config.widget.setTrack\n };\n }\n };//end of interactive transcript\n\nvar processNewVideo = function(player, itoptions){\n //first of all clear the existing transcript if we have one\n $('#' + @@AUTOID@@ + '_transcriptscrollable').empty();\n\t itoptions.title=@@TITLE@@;\n\t itoptions.theduration=player.duration;\n\t //finally load interactive transcript\n\t it.init(itoptions); \n\n\n}// end of processNewVideo\n \n//Set up our player \nvar theplayer = $('#' + @@AUTOID@@ + '_player')[0];\n\n \n //init our interactive transcript options (IT)\nvar itoptions = {};\nitoptions.containerid = @@AUTOID@@ + '_transcriptcontainer';\nitoptions.scrollcontainerid = @@AUTOID@@ + '_transcriptscrollable';\nitoptions.scrollingthing = @@AUTOID@@ + '_transcriptscrolling';\nitoptions.dummyplayerid = @@AUTOID@@ + '_dummyplayer';\nitoptions.splitter = @@AUTOID@@ + '_splitter';\nitoptions.playerid = @@AUTOID@@ + '_player';\nitoptions.theplayer = theplayer;\nitoptions.cssprefix = 'filterpoodll_siv_transcript';\n\n\n//window stuff for resizing\nif ($(window).width() >= 992) {\n $('#' + @@AUTOID@@ + '_player').resizable({\n handles: {'e' : $('#' + @@AUTOID@@ + '_splitter')},\n resizeHeight: false\n });\n}\n\n//display toolbar buttons (they appear too soon otherwise)\n$('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + 'toolbar .' + itoptions.cssprefix + 'tools').show();\n\n//Toggle time stamp \n$('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + 'showtime').on('click',function(){\n \n $('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + '-body').toggleClass('notimestamp');\n return false;\n\n});\n\n//Toggle layout \n$('#' + itoptions.containerid + ' .' +\n itoptions.cssprefix + 'layout').on('click',function(){\n \n var maincontainer = $('.filterpoodll_siv_transcriptmain_container');\n var sivvideo = $('.sivvideo') ;\n if(maincontainer.hasClass('fpv_flexrow')){\n maincontainer.removeClass('fpv_flexrow');\n maincontainer.addClass('fpv_flexcol'); \n $('#' + itoptions.splitter).hide();\n //clear any resized widths \n sivvideo.css('width','');\n }else{\n maincontainer.removeClass('fpv_flexcol'); \n maincontainer.addClass('fpv_flexrow');\n $('#' + itoptions.splitter).show();\n\n }\n return false;\n\n});\n\n\n//kick it all off\nprocessNewVideo(theplayer,itoptions);\n","style":".splitter {\n flex: 0 0 auto;\n width: 2%;\n background: url(https://raw.githubusercontent.com/RickStrahl/jquery-resizable/master/assets/vsizegrip.png) center center no-repeat #535353;\n min-height: 200px;\n cursor: col-resize;\n height: 480px;\n}\n.fpv_flexrow{\n display: flex;flex-direction: row;\n}\n.fpv_flexcol{\n display: flex;flex-direction: column;\n max-width: 800px;\n\n}\n.fpv_flexcol .sivvideo{\n\twidth: 100%;\n} \n.fpv_flexcol .filterpoodll_siv_transcriptcontainer{\n height: 320px;\n}\n.sivvideo{\n\twidth: 50%;\n} \n.filterpoodll_siv_transcriptcontainer{\n flex: 1 1 auto;\n}\n.transcript-body{\n\theight: 100% !important;\n}\n.filterpoodll_siv_transcriptcontainer{\n height: 480px;\n}\n.filterpoodll_siv_transcriptscrollable {\n height: calc(100% - 37px);\n}\n@media screen and (max-width: 992px) {\n\t.filterpoodll_siv_transcriptmain_container{\n display: block !important;\n }\n .splitter{\n display: none;\n }\n\n .filterpoodll_siv_transcripttools{\n display: none;\n }\n\n .sivvideo{\n\t\twidth: 100%;\n\t\tfloat: none;\n\t\theight: 200px;\n } \n .filterpoodll_siv_transcriptcontainer{\n\t\tfloat: none;\n\t\tmargin-left: 0%;\t\t\n }\n .filterpoodll_siv_transcriptcontainer{\n\t\theight: 200px !important;\n }\n .filterpoodll_siv_transcriptscrollable {\n\t\theight: calc(100%-37px);\n /* 163px !important; height - toolbar height */\n }\n}\n\n/* interactive transcript */\n.filterpoodll_siv_transcriptcontainer {\n font-family: Arial, sans-serif;\n border: 1px solid #111;\n}\n\n.filterpoodll_siv_transcript-header {\n height: 19px;\n padding: 2px;\n font-weight: bold;\n text-align: center;\n}\n.filterpoodll_siv_transcript-selector {\n height: 25px;\n}\n.filterpoodll_siv_transcript-body {\n overflow-y: scroll;\n background-color: #e7e7e7;\n position: relative;\n margin: auto;\n}\n\n.filterpoodll_siv_transcript-line {\n position: relative;\n padding: 5px;\n cursor: pointer;\n line-height: 1.3;\n}\n\n.filterpoodll_siv_transcript-line:nth-child(odd) {\n background-color: #f5f5f5;\n}\n\n\n.filterpoodll_siv_transcript-timestamp {\n position: absolute;\n display: inline-block;\n color: #333;\n width: 50px;\n}\n\n.filterpoodll_siv_transcript-text {\n display: block;\n margin-left: 50px;\n}\n\n.filterpoodll_siv_transcript-line:hover,\n.filterpoodll_siv_transcript-line:hover .filterpoodll_siv_transcript-timestamp,\n.filterpoodll_siv_transcript-line:hover .filterpoodll_siv_transcript-text {\n background-color: #777;\n color: #e7e7e7;\n}\n\n.filterpoodll_siv_transcript-line.is-active,\n.filterpoodll_siv_transcript-line.is-active .filterpoodll_siv_transcript-timestamp,\n.filterpoodll_siv_transcript-line.is-active .filterpoodll_siv_transcript-text {\n background-color: #555;\n color: #e7e7e7;\n width: auto;\n}\n\n.filterpoodll_siv_transcripttitle {\n /* Set line height to same height at toolbar */\n line-height: 35px;\n margin-left: 5px;\n font-weight: bold;\n}\n\n.filterpoodll_siv_transcripttoolbar {\n background-color: #CCC;\n max-height: 35px;\n min-height: 35px;\n overflow:hidden\n}\n.filterpoodll_siv_transcriptscrollable {\n width: 100%;\n font-family: Arial, sans-serif;\n overflow-x: hidden;\n overflow-y: scroll;\n scroll-behavior: smooth;\n}\n.filterpoodll_siv_transcripttools {\n float: right;\n}\n\n/* toggle timestamp on and off */\n.filterpoodll_siv_transcript-body.notimestamp .filterpoodll_siv_transcript-timestamp {\n display: none;\n}\n\n.filterpoodll_siv_transcript-body.notimestamp .filterpoodll_siv_transcript-text {\n margin-left: 5px;\n}\n","dataset":"","datasetvars":"","alternate":"","alternateend":""}