From fa9400904da3d0b37faca73dd67640f2175c42aa Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 08:22:19 -1000 Subject: [PATCH 001/137] Added Seeked Functionailty Added code to handle seeked event and trigger an xAPI statement --- scripts/html5.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 3dbd1cec..48ed9ea5 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -34,7 +34,19 @@ H5P.VideoHtml5 = (function ($) { */ var stateBeforeChangingQuality; var currentTimeBeforeChangingQuality; - + + /* + * tracks last time when paused used for completing seeked action + * also variables for tracking played segments + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; /** * Avoids firing the same event twice. * @private @@ -134,6 +146,22 @@ H5P.VideoHtml5 = (function ($) { video.appendChild(trackElement); } }); + + // common math functions + function formatFloat(number) { + if(number == null) + return null; + + return +(parseFloat(number).toFixed(3)); + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } /** * Helps registering events. @@ -156,9 +184,44 @@ H5P.VideoHtml5 = (function ($) { video.currentTime = options.startAt; delete options.startAt; } - + if( arg === H5P.Video.PLAYING ){ + played_segments_segment_start = video.currentTime; + } + if( arg === H5P.Video.PAUSED ){ + previousTime = video.currentTime; + } break; - + case 'seeking': + if ( lastState == arg ){ + return; + } + previousTime = formatFloat(video.currentTime); + return; //just need to store current time for seeked event + break; + case 'seeked': + if ( lastState == arg ){ + return; + } + seekStart = formatFloat(video.currentTime); + if ( Math.abs( seekStart - previousTime ) < 1 ) { + seekStart = null; + return; //don't send for seeks less than a second + } + dateTime = new Date(); + timeStamp = dateTime.toISOString(); + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + break; case 'loaded': isLoaded = true; @@ -588,6 +651,8 @@ H5P.VideoHtml5 = (function ($) { mapEvent('loadedmetadata', 'loaded'); mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); + mapEvent('seeking','seekeing', H5P.Video.PAUSED); + mapEvent('seeked', 'seeked', H5P.Video.PLAYING); if (!video.controls) { // Disable context menu(right click) to prevent controls. @@ -595,6 +660,12 @@ H5P.VideoHtml5 = (function ($) { event.preventDefault(); }, false); } + + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); // Display throbber when buffering/loading video. self.on('stateChange', function (event) { From 6aea7885ee54bb05626feee0a1c6174afed5de7e Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 11:32:06 -1000 Subject: [PATCH 002/137] Added xAPI extending functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit added code for sending following events -seek -volume change -fullscreen -play -pause -Initiated I haven’t actually been able to get the fullscreen event to trigger --- scripts/html5.js | 250 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 19 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 48ed9ea5..1f0af4fe 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -36,8 +36,7 @@ H5P.VideoHtml5 = (function ($) { var currentTimeBeforeChangingQuality; /* - * tracks last time when paused used for completing seeked action - * also variables for tracking played segments + * variables to track add extra xAPI statements for video * @type private */ var previousTime = null; @@ -47,6 +46,8 @@ H5P.VideoHtml5 = (function ($) { var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; /** * Avoids firing the same event twice. * @private @@ -154,6 +155,48 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / video.duration).toFixed(2); + return progress; + } function end_played_segment(end_time) { var arr; arr = (played_segments == "")? []:played_segments.split("[,]"); @@ -173,6 +216,14 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { + + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var extraArg = null; + var extraTrigger = null; + var resultExtTime = formatFloat(video.currentTime); + switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -189,26 +240,58 @@ H5P.VideoHtml5 = (function ($) { } if( arg === H5P.Video.PAUSED ){ previousTime = video.currentTime; + //put together extraArg for sending to xAPI statement + + if( ! video.seeking ) { + + end_played_segment(resultExtTime); + + var progress = get_progress(); + extraTrigger = "paused"; + extraArg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + } + } + //send extra trigger for giving progress on ended call to xAPI + if ( arg === H5P.Video.ENDED ){ + var length = video.duration; + if ( length > 0 ) { + var progress = get_progress(); + + if (progress >= 1 ){ + //send statement + extraTrigger = "completed"; + extraArg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + } + } } break; case 'seeking': - if ( lastState == arg ){ - return; - } previousTime = formatFloat(video.currentTime); return; //just need to store current time for seeked event break; case 'seeked': - if ( lastState == arg ){ - return; - } seekStart = formatFloat(video.currentTime); if ( Math.abs( seekStart - previousTime ) < 1 ) { seekStart = null; return; //don't send for seeks less than a second } - dateTime = new Date(); - timeStamp = dateTime.toISOString(); end_played_segment(previousTime); played_segments_segment_start = seekStart; //put together data for xAPI statement to be sent with event @@ -222,6 +305,60 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp } break; + case 'volumechange' : + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + break; + case 'play': + seekStart = null; + played_segments_segment_start = resultExtTime; + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + } + break; + case 'fullscreen': + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + + if ( state ){ + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + break; case 'loaded': isLoaded = true; @@ -243,8 +380,46 @@ H5P.VideoHtml5 = (function ($) { video.addEventListener('durationchange', andLoaded, false); return; } + + //send extra xAPI statement + extraTrigger = 'xAPIloaded'; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; + } + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + extraArg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + break; - case 'error': // Handle error and get message. arg = error(arguments[0], arguments[1]); @@ -269,6 +444,11 @@ H5P.VideoHtml5 = (function ($) { break; } self.trigger(h5p, arg); + + //make extra calls for events with needed values for xAPI statement + if( extraTrigger != null && extraArg != null ){ + self.trigger(extraTrigger, extraArg); + } }, false); }; @@ -651,8 +831,12 @@ H5P.VideoHtml5 = (function ($) { mapEvent('loadedmetadata', 'loaded'); mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); - mapEvent('seeking','seekeing', H5P.Video.PAUSED); + mapEvent('seeking','seeking', H5P.Video.PAUSED); mapEvent('seeked', 'seeked', H5P.Video.PLAYING); + mapEvent('volumechange', 'volumechange'); + mapEvent('play', 'play', H5P.Video.PLAYING); + //fuscreen events + mapEvent('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', 'fullscreen'); if (!video.controls) { // Disable context menu(right click) to prevent controls. @@ -660,13 +844,6 @@ H5P.VideoHtml5 = (function ($) { event.preventDefault(); }, false); } - - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); - }); - // Display throbber when buffering/loading video. self.on('stateChange', function (event) { var state = event.data; @@ -691,6 +868,41 @@ H5P.VideoHtml5 = (function ($) { } }); }); + + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }) + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); // Video controls are ready nextTick(function () { From 28c3266a549c487e14fcb64f37544ea518e444b4 Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 15:40:09 -1000 Subject: [PATCH 003/137] Added xAPI for Youtube Used Youtube Video API to implement: -Seek -Play -Pause -Initiated This is all that was possible via their API --- scripts/youtube.js | 285 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) diff --git a/scripts/youtube.js b/scripts/youtube.js index 1bae803a..376cb764 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -16,6 +16,21 @@ H5P.VideoYouTube = (function ($) { var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; numInstances++; + /* + * variables to track add extra xAPI statements for video + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; + var seeking = false; + var lastState; var $wrapper = $('
'); var $placeholder = $('
', { @@ -77,6 +92,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); + self.trigger('xAPIloaded',getLoadedParams()) }, onApiChange: function () { if (loadCaptionsModule) { @@ -116,6 +132,54 @@ H5P.VideoYouTube = (function ($) { // End IE11 fix self.trigger('stateChange', state.data); + + //calls for xAPI events + if ( state.data == 1 ){ + if ( seeking ){ + //call from a seek we run seek command not play + self.trigger('seeked', getSeekParams()); + seeking = false; + } else { + //get and send play call + self.trigger('play', getPlayParams()); + } + } + if ( lastState == 2 && state.data == 3 ){ + seeking = true; + } + + if ( state.data == 2 ) { + //trigger call for xAPI paused but not seek + previousTime = player.getCurrentTime(); + if( ! seeking ) { + //this is a paused event + seeking = false; + // execute your code here for paused state + self.trigger('paused', getPausedParams()); + + } + } else if ( state.data == 0 ) { + //send xapi trigger if video progress indicates completed + var length = player.getDuration(); + if ( length > 0){ + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + if ( progress >= 1 ){ + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + self.trigger('completed',arg); + } + } + } + + lastState = state.data; } }, onPlaybackQualityChange: function (quality) { @@ -149,7 +213,228 @@ H5P.VideoYouTube = (function ($) { } }); }; + + //function used when putting together object to send for xAPI calls + function getWidthOrHeight ( returnType ){ + var quality = player.getPlaybackQuality(); + var width; + var height; + switch (quality) { + case 'small': + width: '320'; + height: '240'; + break; + case 'medium': + width: '640'; + height: '360'; + break; + case 'large': + width: '853'; + height: '480'; + break; + case 'hd720': + width: '640'; + height: '360'; + break; + case 'hd1080': + width: '1920'; + height: '1080'; + break; + case 'highres': + width: '1920'; + height: '1080'; + break; + } + + return (returnType.toLowerCase().trim()=='width')? width : height; + } + + function getLoadedParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + var quality = player.getPlaybackQuality(); + var height = getWidthOrHeight('height'); + var width = getWidthOrHeight('width'); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = player.getVolume(); + var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccLanguage; + if ( ccEnabled ) { + ccLanguage = player.getOptions('cc', 'track').languageCode; + } + var userAgent = navigator.userAgent; + var playbackRate = player.getPlaybackRate(); + + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + return arg; + } + function getPlayParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + played_segments_segment_start = resultExtTime; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + }; + return arg; + } + //paused Params called on pause statement used by xAPI event + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getSeekParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + + return arg; + } + // common math functions + function formatFloat(number) { + if(number == null) + return null; + return +(parseFloat(number).toFixed(3)); + } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }); + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. From 6df9a910cfe0ed9af6e2e5224f8a47e070ec5d99 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 10:00:34 -1000 Subject: [PATCH 004/137] xAPI on Youtube, html5, and videojs --- scripts/html5.js | 268 ++++++++++++++++++++---------------- scripts/video.js | 336 +++++++++++++++++++++++++++++++++++++++++++++ scripts/youtube.js | 2 +- 3 files changed, 484 insertions(+), 122 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1f0af4fe..a10097ae 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -48,6 +48,7 @@ H5P.VideoHtml5 = (function ($) { var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; + var seeking = false; /** * Avoids firing the same event twice. * @private @@ -204,8 +205,145 @@ H5P.VideoHtml5 = (function ($) { played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; - } + } + function getLoadedParams() { + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; + } + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + } + }, + "timestamp": timeStamp + }; + } + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + end_played_segment(resultExtTime); + + var progress = get_progress(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + } + function getSeekedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = formatFloat(video.currentTime); + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + seeking = false; + //put together data for xAPI statement to be sent with event + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + }; + } + function getVolumeChangeParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + } + function getPlayParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = null; + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + } + } + function getFullScreenParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } /** * Helps registering events. * @@ -216,14 +354,8 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { - - //variables used in compiling xAPI results - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var extraArg = null; - var extraTrigger = null; - var resultExtTime = formatFloat(video.currentTime); - + var extraArg = null; + var extraTrigger = null; switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -244,20 +376,8 @@ H5P.VideoHtml5 = (function ($) { if( ! video.seeking ) { - end_played_segment(resultExtTime); - - var progress = get_progress(); extraTrigger = "paused"; - extraArg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "timestamp" : timeStamp - }; + extraArg = getPausedParams(); } } //send extra trigger for giving progress on ended call to xAPI @@ -287,77 +407,17 @@ H5P.VideoHtml5 = (function ($) { return; //just need to store current time for seeked event break; case 'seeked': - seekStart = formatFloat(video.currentTime); - if ( Math.abs( seekStart - previousTime ) < 1 ) { - seekStart = null; - return; //don't send for seeks less than a second - } - end_played_segment(previousTime); - played_segments_segment_start = seekStart; //put together data for xAPI statement to be sent with event - arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "timestamp" : timeStamp - } + arg = getSeekedParams(); break; case 'volumechange' : - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); - } - arg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; + arg = getVolumeChangeParams(); break; case 'play': - seekStart = null; - played_segments_segment_start = resultExtTime; - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "timestamp": timeStamp - } + arg = getPlayParams(); break; case 'fullscreen': - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - if ( state ){ - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } - }, - "timestamp" : timeStamp - }; - } + arg = getFullScreenParams(); break; case 'loaded': isLoaded = true; @@ -383,41 +443,7 @@ H5P.VideoHtml5 = (function ($) { //send extra xAPI statement extraTrigger = 'xAPIloaded'; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - var ccEnabled = false; - var ccLanguage; - - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ - ccEnabled = true; - ccLanguage = video.textTracks[i].language; - } - } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; - extraArg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume - - } - }, - "timestamp": timeStamp - }; + extraArg = getLoadedParams(); break; case 'error': diff --git a/scripts/video.js b/scripts/video.js index 2460b05f..5c5b8e14 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -134,6 +134,342 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } + + + //xapi video profile setup and calls for video.js + /* + * variables to track add extra xAPI statements for video + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; + var seeking = false; + var lastState; + var start = false; + var tracks = Video.textTracks(); + var skipPlayEvent = false; + var currentTime = 0; + var next_completion_check = 0; + var sent_completed = false; + + + function getLoadedParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + var state = Video.isFullscreen(); + var screenSize = screen.width + "x" + screen.height; + var quality = (Video.videoHeight() < Video.videoWidth())? Video.videoHeight():videoWidth(); + var height = Video.currentHeight(); + var width = Video.currentWidth(); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = formatFloat(video.volume()); + var ccEnabled = false; + var ccLanguage = "None Set"; + + //Captions/Subtitles values + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // If it is showing then CC is enabled and determine the language + if (track.mode === 'showing') { + ccEnabled = true; + ccLanguage = track.language; + } + } + var userAgent = navigator.userAgent; + var playbackRate = Video.playbackRate(); + + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + return arg; + } + function getPlayParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + played_segments_segment_start = resultExtTime; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + }; + return arg; + } + //paused Params called on pause statement used by xAPI event + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getSeekParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + + return arg; + } + + function check_completion() { + if(sent_completed) + { + //console.log("completed statement already sent"); + return; + } + + var currentTimestamp = (new Date()).getTime(); + + if(currentTimestamp < next_completion_check) { + //console.log(new Date(next_completion_check) + " in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); + return; + } + var length = Video.duration(); + //console.log("length: " + length); + if(length <= 0) + return; + + var progress = get_progress(); + if(progress >= 1) { + sent_completed = true; + var resultExtTime = formatFloat(Video.currentTime()); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + self.trigger('completed',arg); + } + var remaining_seconds = (1 - progress) * length; + //console.log("remaining_seconds: " + remaining_seconds); + next_completion_check = currentTimestamp + remaining_seconds.toFixed(3) * 1000; + //console.log("Progress: " + progress + " currentTimestamp: " + currentTimestamp + " next completion check in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); + } + function getVolumeChangeParams() { + volume_changed_at = Video.currentTime(); + var isMuted = Video.muted(); + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(Video.volume()); + } + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getFullScreenParams() { + var state = Video.isFullscreen(); + var resultExtTime = formatFloat(Video.currentTime()); + + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = Video.currentWidth() + "x" + Video.currentHeight(); + + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + // common math functions + function formatFloat(number) { + if(number == null) + return null; + + return +(parseFloat(number).toFixed(3)); + } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(Video.currentTime())); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / Video.duration()).toFixed(2); + return progress; + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }); + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); + + //event listeners + Video.on('play', function() { + if(start == false){ + start = true; + self.trigger('xAPIloaded',getLoadedParams()); + } + + if (skipPlayEvent !== true) { + self.trigger('play',getPlayParams()); + } else { + skipPlayEvent = false; + self.trigger('seeked',getSeekParams()); + } + }); + + Video.on('pause', function() { + if (this.seeking() === false ){ + self.trigger('paused', getPausedParams()); + } else { + skipPlayEvent = true; + } + }); + Video.on("timeupdate", function() { + previousTime = currentTime; + currentTime = formatFloat(Video.currentTime()); + check_completion(); + }); + Video.on("volumechange",function() { + self.trigger('volumechange',getVolumeChangeParams()); + }); + Video.on("fullscreenchange",function(){ + self.trigger('fullscreen',getFullScreenParams()); + }); + } // Extends the event dispatcher diff --git a/scripts/youtube.js b/scripts/youtube.js index 376cb764..67c092a0 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -92,7 +92,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); - self.trigger('xAPIloaded',getLoadedParams()) + self.trigger('xAPIloaded',getLoadedParams()); }, onApiChange: function () { if (loadCaptionsModule) { From ba72e7d6012849e7fae6b698fbf4aa05d4a68a95 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 13:45:29 -1000 Subject: [PATCH 005/137] changes to get Seek working correctly --- scripts/html5.js | 35 ++++++++++++++++++++++++----------- scripts/video.js | 5 ++--- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index a10097ae..0ff3b93c 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -39,7 +39,7 @@ H5P.VideoHtml5 = (function ($) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -274,7 +274,7 @@ H5P.VideoHtml5 = (function ($) { played_segments_segment_start = seekStart; seeking = false; //put together data for xAPI statement to be sent with event - return { + var arg = { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -283,6 +283,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; + return arg; } function getVolumeChangeParams() { var dateTime = new Date(); @@ -368,14 +369,12 @@ H5P.VideoHtml5 = (function ($) { delete options.startAt; } if( arg === H5P.Video.PLAYING ){ - played_segments_segment_start = video.currentTime; + previousTime = video.currentTime; } if( arg === H5P.Video.PAUSED ){ - previousTime = video.currentTime; //put together extraArg for sending to xAPI statement if( ! video.seeking ) { - extraTrigger = "paused"; extraArg = getPausedParams(); } @@ -385,6 +384,7 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if ( length > 0 ) { var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); if (progress >= 1 ){ //send statement @@ -402,19 +402,32 @@ H5P.VideoHtml5 = (function ($) { } } break; + case 'timeupdate' : + if((Math.abs( previousTime - video.currentTime) > 2) ){ + h5p = 'seeked'; + arg = getSeekedParams(); + played_segments_segment_start = video.currentTime; + } else { + previousTime = video.currentTime; + } + break; + case 'seeked': + return; //seek is tracked differently based on time difference in timeupdate + break; case 'seeking': - previousTime = formatFloat(video.currentTime); return; //just need to store current time for seeked event break; - case 'seeked': - //put together data for xAPI statement to be sent with event - arg = getSeekedParams(); - break; case 'volumechange' : arg = getVolumeChangeParams(); break; case 'play': + if ( Math.abs(previousTime - video.currentTime) > 2 ){ + h5p = 'seeked'; + arg = getSeekedParams(); + played_segments_segment_start = video.currentTime; + } else { arg = getPlayParams(); + } break; case 'fullscreen': arg = getFullScreenParams(); @@ -858,7 +871,7 @@ H5P.VideoHtml5 = (function ($) { mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); mapEvent('seeking','seeking', H5P.Video.PAUSED); - mapEvent('seeked', 'seeked', H5P.Video.PLAYING); + mapEvent('timeupdate', 'timeupdate', H5P.Video.PLAYING); mapEvent('volumechange', 'volumechange'); mapEvent('play', 'play', H5P.Video.PLAYING); //fuscreen events diff --git a/scripts/video.js b/scripts/video.js index 5c5b8e14..cbe054c2 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -141,7 +141,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -228,7 +228,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(Video.currentTime()); - previousTime = resultExtTime; end_played_segment(resultExtTime); var progress = get_progress(); var arg = { @@ -445,7 +444,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { if (skipPlayEvent !== true) { self.trigger('play',getPlayParams()); - } else { + } else if ( Math.abs(previousTime - Video.currentTime()) > 1 ){ skipPlayEvent = false; self.trigger('seeked',getSeekParams()); } From 3ffd605c1f3941ba61c72c06cd7b7bd81e106d09 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 14:12:51 -1000 Subject: [PATCH 006/137] Fixed seeked on Youtube player --- scripts/youtube.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index 67c092a0..52f1f574 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -20,7 +20,7 @@ H5P.VideoYouTube = (function ($) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -135,7 +135,7 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( seeking ){ + if ( Math.abs( previousTime - player.getCurrentTime() ) > 1 ){ //call from a seek we run seek command not play self.trigger('seeked', getSeekParams()); seeking = false; @@ -144,20 +144,11 @@ H5P.VideoYouTube = (function ($) { self.trigger('play', getPlayParams()); } } - if ( lastState == 2 && state.data == 3 ){ - seeking = true; - } - if ( state.data == 2 ) { - //trigger call for xAPI paused but not seek - previousTime = player.getCurrentTime(); - if( ! seeking ) { - //this is a paused event - seeking = false; - // execute your code here for paused state - self.trigger('paused', getPausedParams()); - - } + //this is a paused event + seeking = false; + // execute your code here for paused state + self.trigger('paused', getPausedParams()); } else if ( state.data == 0 ) { //send xapi trigger if video progress indicates completed var length = player.getDuration(); @@ -214,6 +205,8 @@ H5P.VideoYouTube = (function ($) { }); }; + //Youtube player has no timeupdate event so need to use setInterval + setInterval(function(){ previousTime = player.getCurrentTime(); }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); From 3a1949996731c20aaad6a7ec5f12371287735b86 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:02:13 -1000 Subject: [PATCH 007/137] Got Context added to xAPI statements --- scripts/html5.js | 125 +++++++++++++++++++++++++++++++++++++-------- scripts/video.js | 94 +++++++++++++++++++++++++++++++--- scripts/youtube.js | 60 +++++++++++++++++++++- 3 files changed, 250 insertions(+), 29 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 0ff3b93c..1e2637bd 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,6 +49,7 @@ H5P.VideoHtml5 = (function ($) { var volume_changed_on = null; var volume_changed_at = 0; var seeking = false; + var sessionID = guid(); /** * Avoids firing the same event twice. * @private @@ -156,6 +157,15 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } //determine video progress function get_progress() { var arr, arr2; @@ -231,22 +241,30 @@ H5P.VideoHtml5 = (function ($) { var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; var userAgent = navigator.userAgent; return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; + } + }, + "timestamp": timeStamp + }; } function getPausedParams() { var dateTime = new Date(); @@ -263,6 +281,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; } @@ -281,6 +312,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -299,8 +343,21 @@ H5P.VideoHtml5 = (function ($) { return { "result" : { "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange + "https://w3id.org/xapi/video/extensions/time": volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } }, "timestamp" : timeStamp @@ -318,6 +375,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp } } @@ -336,10 +406,23 @@ H5P.VideoHtml5 = (function ($) { return { "result": { "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } }, "timestamp" : timeStamp diff --git a/scripts/video.js b/scripts/video.js index cbe054c2..856259fa 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -158,6 +158,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var currentTime = 0; var next_completion_check = 0; var sent_completed = false; + var sessionID = guid(); function getLoadedParams(){ @@ -188,7 +189,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var playbackRate = Video.playbackRate(); var arg = { - "result" : { + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, "extensions": { "https://w3id.org/xapi/video/extensions/full-screen": state, "https://w3id.org/xapi/video/extensions/screen-size": screenSize, @@ -219,6 +227,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp }; return arg; @@ -238,6 +259,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -257,6 +291,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp } @@ -314,7 +361,20 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "result" : { "extensions": { "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } }, "timestamp" : timeStamp @@ -334,10 +394,23 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { arg = { "result": { "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } }, "timestamp" : timeStamp @@ -350,6 +423,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { return +(parseFloat(number).toFixed(3)); } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } //determine video progress function get_progress() { var arr, arr2; diff --git a/scripts/youtube.js b/scripts/youtube.js index 52f1f574..ce178f57 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -31,6 +31,7 @@ H5P.VideoYouTube = (function ($) { var volume_changed_at = 0; var seeking = false; var lastState; + var sessionID = guid(); var $wrapper = $('
'); var $placeholder = $('
', { @@ -262,7 +263,14 @@ H5P.VideoYouTube = (function ($) { var playbackRate = player.getPlaybackRate(); var arg = { - "result" : { + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, "extensions": { "https://w3id.org/xapi/video/extensions/full-screen": state, "https://w3id.org/xapi/video/extensions/screen-size": screenSize, @@ -293,6 +301,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp }; return arg; @@ -313,6 +334,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -332,6 +366,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp } @@ -393,7 +440,16 @@ H5P.VideoYouTube = (function ($) { played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; - } + } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } ////////xAPI extension events for video///// //catch for seeked event self.on('seeked', function(event) { From 910f71446cf3ee195798ca9d14ab7beb7e647250 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:20:31 -1000 Subject: [PATCH 008/137] =?UTF-8?q?Adjusted=20Youtube=E2=80=99s=20Seek=20c?= =?UTF-8?q?apabilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/youtube.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index ce178f57..451d6b98 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -32,6 +32,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var lastState; var sessionID = guid(); + var currentTime = 0; var $wrapper = $('
'); var $placeholder = $('
', { @@ -136,7 +137,8 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( Math.abs( previousTime - player.getCurrentTime() ) > 1 ){ + + if ( (Math.abs( previousTime - player.getCurrentTime() ) > 1) || seeking ){ //call from a seek we run seek command not play self.trigger('seeked', getSeekParams()); seeking = false; @@ -147,7 +149,6 @@ H5P.VideoYouTube = (function ($) { } if ( state.data == 2 ) { //this is a paused event - seeking = false; // execute your code here for paused state self.trigger('paused', getPausedParams()); } else if ( state.data == 0 ) { @@ -207,7 +208,15 @@ H5P.VideoYouTube = (function ($) { }; //Youtube player has no timeupdate event so need to use setInterval - setInterval(function(){ previousTime = player.getCurrentTime(); }, 1000); + setInterval(function(){ + if( typeof player !== undefined ){ + previousTime = currentTime; + currentTime = formatFloat(player.getCurrentTime()); + if( Math.abs(previousTime - currentTime) > 1){ + seeking = true; + } + } + }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); From 749f8961297d4c4753c6c1cbaaa9a91904b97d13 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:24:12 -1000 Subject: [PATCH 009/137] Removed unused variables from Youtube --- scripts/youtube.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index 451d6b98..a07b8d18 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -22,15 +22,12 @@ H5P.VideoYouTube = (function ($) { */ var previousTime = 0; var seekStart = null; - var dateTime; - var timeStamp; var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; var seeking = false; - var lastState; var sessionID = guid(); var currentTime = 0; @@ -171,8 +168,6 @@ H5P.VideoYouTube = (function ($) { } } } - - lastState = state.data; } }, onPlaybackQualityChange: function (quality) { From 73f9072a5c22d8338e1a3fdd96a1e47ea1a8d89a Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:27:54 -1000 Subject: [PATCH 010/137] Ensured Session ID was in every call for each video player --- scripts/video.js | 3 ++- scripts/youtube.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 856259fa..0956631a 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -206,7 +206,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID } }, diff --git a/scripts/youtube.js b/scripts/youtube.js index a07b8d18..48390bea 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -284,8 +284,8 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume - + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID } }, "timestamp": timeStamp From 9ee8c078835a6f82b2a302931016f9abe0eeaa5e Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:51:49 -1000 Subject: [PATCH 011/137] Ensure xAPI played event sent appropriately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Play wasn’t firing with play button was hit all the time so I added functionality in the state change event to evaluate if play event needed to be triggered. --- scripts/html5.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1e2637bd..1cf1836d 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -41,8 +41,6 @@ H5P.VideoHtml5 = (function ($) { */ var previousTime = 0; var seekStart = null; - var dateTime; - var timeStamp; var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; @@ -50,6 +48,7 @@ H5P.VideoHtml5 = (function ($) { var volume_changed_at = 0; var seeking = false; var sessionID = guid(); + var lastSend = null; /** * Avoids firing the same event twice. * @private @@ -453,6 +452,11 @@ H5P.VideoHtml5 = (function ($) { } if( arg === H5P.Video.PLAYING ){ previousTime = video.currentTime; + if( lastSend != 'play' ){ + extraTrigger = 'play'; + extraArg = getPlayParams(); + lastSend = 'play'; + } } if( arg === H5P.Video.PAUSED ){ //put together extraArg for sending to xAPI statement @@ -460,6 +464,7 @@ H5P.VideoHtml5 = (function ($) { if( ! video.seeking ) { extraTrigger = "paused"; extraArg = getPausedParams(); + lastSend = 'paused'; } } //send extra trigger for giving progress on ended call to xAPI @@ -470,6 +475,8 @@ H5P.VideoHtml5 = (function ($) { var resultExtTime = formatFloat(video.currentTime); if (progress >= 1 ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); //send statement extraTrigger = "completed"; extraArg = { @@ -481,6 +488,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; + lastSend = 'completed'; } } } @@ -490,6 +498,7 @@ H5P.VideoHtml5 = (function ($) { h5p = 'seeked'; arg = getSeekedParams(); played_segments_segment_start = video.currentTime; + lastSend = 'seeked'; } else { previousTime = video.currentTime; } @@ -502,18 +511,22 @@ H5P.VideoHtml5 = (function ($) { break; case 'volumechange' : arg = getVolumeChangeParams(); + lastSend = 'volumechange'; break; case 'play': if ( Math.abs(previousTime - video.currentTime) > 2 ){ h5p = 'seeked'; arg = getSeekedParams(); played_segments_segment_start = video.currentTime; + lastSend = 'seeked'; } else { arg = getPlayParams(); + lastSend = h5p; } break; case 'fullscreen': arg = getFullScreenParams(); + lastSend = h5p; break; case 'loaded': isLoaded = true; @@ -540,6 +553,7 @@ H5P.VideoHtml5 = (function ($) { //send extra xAPI statement extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); + lastSend = 'xAPIloaded'; break; case 'error': From 6cc6009054f94163d8d54553e057d350d1862004 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 15:31:56 -1000 Subject: [PATCH 012/137] Moved Competion Prams into a function Moved completion params into a function --- scripts/html5.js | 61 ++++++++++++++++++++++++++++++---------------- scripts/youtube.js | 57 +++++++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1cf1836d..1f56f382 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -42,7 +42,7 @@ H5P.VideoHtml5 = (function ($) { var previousTime = 0; var seekStart = null; var played_segments = []; - var played_segments_segment_start; + var played_segments_segment_start = 0; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; @@ -209,12 +209,15 @@ H5P.VideoHtml5 = (function ($) { } function end_played_segment(end_time) { var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } + if (end_time !== played_segments_segment_start){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } +} function getLoadedParams() { //variables used in compiling xAPI results var dateTime = new Date(); @@ -270,7 +273,7 @@ H5P.VideoHtml5 = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); - + played_segments_segment_start == resultExtTime; var progress = get_progress(); return { "result": { @@ -427,6 +430,34 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp }; } + function getCompletedParams() { + var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, + "timestamp" : timeStamp + }; + } /** * Helps registering events. * @@ -472,22 +503,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if ( length > 0 ) { var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - if (progress >= 1 ){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); //send statement extraTrigger = "completed"; - extraArg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; + extraArg = getCompletedParams(); lastSend = 'completed'; } } diff --git a/scripts/youtube.js b/scripts/youtube.js index 48390bea..66e381dd 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -23,7 +23,7 @@ H5P.VideoYouTube = (function ($) { var previousTime = 0; var seekStart = null; var played_segments = []; - var played_segments_segment_start; + var played_segments_segment_start =0; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; @@ -153,18 +153,8 @@ H5P.VideoYouTube = (function ($) { var length = player.getDuration(); if ( length > 0){ var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); if ( progress >= 1 ){ - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; - self.trigger('completed',arg); + var arg = getCompletedParams(); } } } @@ -298,7 +288,6 @@ H5P.VideoYouTube = (function ($) { var resultExtTime = formatFloat(player.getCurrentTime()); played_segments_segment_start = resultExtTime; seekStart = null; - played_segments_segment_start = resultExtTime; var arg = { "result": { "extensions": { @@ -329,6 +318,7 @@ H5P.VideoYouTube = (function ($) { var resultExtTime = formatFloat(player.getCurrentTime()); previousTime = resultExtTime; end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; var progress = get_progress(); var arg = { "result": { @@ -388,6 +378,34 @@ H5P.VideoYouTube = (function ($) { return arg; } + function getCompletedParams() { + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, + "timestamp" : timeStamp + }; + } // common math functions function formatFloat(number) { if(number == null) @@ -439,11 +457,14 @@ H5P.VideoYouTube = (function ($) { } function end_played_segment(end_time) { var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; + if (end_time !== played_segments_segment_start){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } } function guid() { function s4() { From fff638d02fc77e39262f8df6287fa33a36343bd6 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 15:34:48 -1000 Subject: [PATCH 013/137] Reverted Changes on Video JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Haven’t been able to get this code to call for testing in any video upload with Wordpress so I am not adding functionality for this. --- scripts/video.js | 418 ----------------------------------------------- 1 file changed, 418 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 0956631a..2460b05f 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -134,424 +134,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } - - - //xapi video profile setup and calls for video.js - /* - * variables to track add extra xAPI statements for video - * @type private - */ - var previousTime = 0; - var seekStart = null; - var dateTime; - var timeStamp; - var played_segments = []; - var played_segments_segment_start; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var lastState; - var start = false; - var tracks = Video.textTracks(); - var skipPlayEvent = false; - var currentTime = 0; - var next_completion_check = 0; - var sent_completed = false; - var sessionID = guid(); - - - function getLoadedParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - var state = Video.isFullscreen(); - var screenSize = screen.width + "x" + screen.height; - var quality = (Video.videoHeight() < Video.videoWidth())? Video.videoHeight():videoWidth(); - var height = Video.currentHeight(); - var width = Video.currentWidth(); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; - var volume = formatFloat(video.volume()); - var ccEnabled = false; - var ccLanguage = "None Set"; - - //Captions/Subtitles values - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - - // If it is showing then CC is enabled and determine the language - if (track.mode === 'showing') { - ccEnabled = true; - ccLanguage = track.language; - } - } - var userAgent = navigator.userAgent; - var playbackRate = Video.playbackRate(); - - var arg = { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; - } - function getPlayParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - played_segments_segment_start = resultExtTime; - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; - } - //paused Params called on pause statement used by xAPI event - function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - end_played_segment(resultExtTime); - var progress = get_progress(); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; - return arg; - } - function getSeekParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - - return arg; - } - - function check_completion() { - if(sent_completed) - { - //console.log("completed statement already sent"); - return; - } - - var currentTimestamp = (new Date()).getTime(); - - if(currentTimestamp < next_completion_check) { - //console.log(new Date(next_completion_check) + " in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); - return; - } - var length = Video.duration(); - //console.log("length: " + length); - if(length <= 0) - return; - - var progress = get_progress(); - if(progress >= 1) { - sent_completed = true; - var resultExtTime = formatFloat(Video.currentTime()); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; - self.trigger('completed',arg); - } - var remaining_seconds = (1 - progress) * length; - //console.log("remaining_seconds: " + remaining_seconds); - next_completion_check = currentTimestamp + remaining_seconds.toFixed(3) * 1000; - //console.log("Progress: " + progress + " currentTimestamp: " + currentTimestamp + " next completion check in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); - } - function getVolumeChangeParams() { - volume_changed_at = Video.currentTime(); - var isMuted = Video.muted(); - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(Video.volume()); - } - var arg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - - } - }, - "timestamp" : timeStamp - }; - return arg; - } - function getFullScreenParams() { - var state = Video.isFullscreen(); - var resultExtTime = formatFloat(Video.currentTime()); - - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = Video.currentWidth() + "x" + Video.currentHeight(); - - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - - } - }, - "timestamp" : timeStamp - }; - } - // common math functions - function formatFloat(number) { - if(number == null) - return null; - - return +(parseFloat(number).toFixed(3)); - } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - //determine video progress - function get_progress() { - var arr, arr2; - - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(Video.currentTime())); - } - - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / Video.duration()).toFixed(2); - return progress; - } - function end_played_segment(end_time) { - var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - ////////xAPI extension events for video///// - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); - }); - //catch volumeChanged Event - self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); - }); - self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); - }) - //catch fullscreen Event - self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); - }); - //catch play Event - self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); - }); - self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); - }); - //catch play Event - self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); - }); - - //event listeners - Video.on('play', function() { - if(start == false){ - start = true; - self.trigger('xAPIloaded',getLoadedParams()); - } - - if (skipPlayEvent !== true) { - self.trigger('play',getPlayParams()); - } else if ( Math.abs(previousTime - Video.currentTime()) > 1 ){ - skipPlayEvent = false; - self.trigger('seeked',getSeekParams()); - } - }); - - Video.on('pause', function() { - if (this.seeking() === false ){ - self.trigger('paused', getPausedParams()); - } else { - skipPlayEvent = true; - } - }); - Video.on("timeupdate", function() { - previousTime = currentTime; - currentTime = formatFloat(Video.currentTime()); - check_completion(); - }); - Video.on("volumechange",function() { - self.trigger('volumechange',getVolumeChangeParams()); - }); - Video.on("fullscreenchange",function(){ - self.trigger('fullscreen',getFullScreenParams()); - }); - } // Extends the event dispatcher From 68c24826fe2261d73fa8ded0093403717d58ff81 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:44:14 -1000 Subject: [PATCH 014/137] Modifying for seek Fine tuned seek event more. It is working very well on HTML5 still need some work on Youtube for the events when a user is scrubbing. --- scripts/html5.js | 116 ++++++++++++++++++++++++--------------------- scripts/youtube.js | 105 +++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 104 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1f56f382..e865795c 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,6 +49,8 @@ H5P.VideoHtml5 = (function ($) { var seeking = false; var sessionID = guid(); var lastSend = null; + var lastSeekedEvent = { seek_from: null, seek_to: null }; + var seekedTo = 0; /** * Avoids firing the same event twice. * @private @@ -207,9 +209,11 @@ H5P.VideoHtml5 = (function ($) { var progress = 1 * (progress_length / video.duration).toFixed(2); return progress; } + function end_played_segment(end_time) { var arr; - if (end_time !== played_segments_segment_start){ + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); arr.push(played_segments_segment_start + "[.]" + end_time); @@ -217,7 +221,7 @@ H5P.VideoHtml5 = (function ($) { played_segments_segment_end = end_time; played_segments_segment_start = null; } -} + } function getLoadedParams() { //variables used in compiling xAPI results var dateTime = new Date(); @@ -273,7 +277,7 @@ H5P.VideoHtml5 = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); - played_segments_segment_start == resultExtTime; + played_segments_segment_start = resultExtTime; var progress = get_progress(); return { "result": { @@ -299,37 +303,38 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp }; } - function getSeekedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = formatFloat(video.currentTime); - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - seeking = false; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] + function getSeekedParams( time ) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - return arg; + } + }, + "timestamp" : timeStamp + } + return arg; } function getVolumeChangeParams() { var dateTime = new Date(); @@ -434,12 +439,14 @@ H5P.VideoHtml5 = (function ($) { var progress = get_progress(); var resultExtTime = formatFloat(video.currentTime); var dateTime = new Date(); + end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, "context": { @@ -482,17 +489,23 @@ H5P.VideoHtml5 = (function ($) { delete options.startAt; } if( arg === H5P.Video.PLAYING ){ - previousTime = video.currentTime; - if( lastSend != 'play' ){ - extraTrigger = 'play'; + if ( seeking === true ){ + extraArg = getSeekedParams( seekedTo ); + extraTrigger = 'seeked'; + lastSend = 'seeked'; + seeking = false; + seeking = false; + } else if ( lastSend !== 'play' ){ extraArg = getPlayParams(); + extraTrigger = 'play'; lastSend = 'play'; } + } if( arg === H5P.Video.PAUSED ){ //put together extraArg for sending to xAPI statement - if( ! video.seeking ) { + if( ! video.seeking && seeking === false) { extraTrigger = "paused"; extraArg = getPausedParams(); lastSend = 'paused'; @@ -512,16 +525,6 @@ H5P.VideoHtml5 = (function ($) { } } break; - case 'timeupdate' : - if((Math.abs( previousTime - video.currentTime) > 2) ){ - h5p = 'seeked'; - arg = getSeekedParams(); - played_segments_segment_start = video.currentTime; - lastSend = 'seeked'; - } else { - previousTime = video.currentTime; - } - break; case 'seeked': return; //seek is tracked differently based on time difference in timeupdate break; @@ -533,14 +536,15 @@ H5P.VideoHtml5 = (function ($) { lastSend = 'volumechange'; break; case 'play': - if ( Math.abs(previousTime - video.currentTime) > 2 ){ - h5p = 'seeked'; - arg = getSeekedParams(); - played_segments_segment_start = video.currentTime; - lastSend = 'seeked'; - } else { + if ( seeking === false && lastSend != h5p ){ + arg = getPlayParams(); lastSend = h5p; + } else { + arg = getSeekedParams( seekedTo ); + lastSend = 'seeked'; + seeking = false; + h5p = 'seeked'; } break; case 'fullscreen': @@ -815,8 +819,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - + if(seeking === false) { + previousTime = video.currentTime; + } video.currentTime = time; + seeking = true; + seekedTo = time; }; /** diff --git a/scripts/youtube.js b/scripts/youtube.js index 66e381dd..12c8946d 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -30,6 +30,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var sessionID = guid(); var currentTime = 0; + var lastSeekedEvent = { seek_from: null, seek_to: null }; var $wrapper = $('
'); var $placeholder = $('
', { @@ -135,19 +136,18 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( (Math.abs( previousTime - player.getCurrentTime() ) > 1) || seeking ){ - //call from a seek we run seek command not play - self.trigger('seeked', getSeekParams()); - seeking = false; - } else { - //get and send play call - self.trigger('play', getPlayParams()); - } + //get and send play call when not seeking + if ( seeking === false ) { + self.trigger('play', getPlayParams()); + } + seeking = false; } if ( state.data == 2 ) { //this is a paused event // execute your code here for paused state + if(seeking === false){ self.trigger('paused', getPausedParams()); + } } else if ( state.data == 0 ) { //send xapi trigger if video progress indicates completed var length = player.getDuration(); @@ -191,17 +191,6 @@ H5P.VideoYouTube = (function ($) { } }); }; - - //Youtube player has no timeupdate event so need to use setInterval - setInterval(function(){ - if( typeof player !== undefined ){ - previousTime = currentTime; - currentTime = formatFloat(player.getCurrentTime()); - if( Math.abs(previousTime - currentTime) > 1){ - seeking = true; - } - } - }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); @@ -345,49 +334,61 @@ H5P.VideoYouTube = (function ($) { }; return arg; } - function getSeekParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] + function sendSeeked( time ) { + + //only trigger if first time running or if the last event is different + //this prevents multiple runs of seek when user is scrubbing + if ( (lastSeekedEvent.seek_from == null && lastSeekedEvent.seek_to == null) || + (( Math.abs(time - lastSeekedEvent.seek_to) > 1 && Math.abs(previousTime - lastSeekedEvent.seek_from) > 1) && + Math.abs(previousTime - time) > 1) ){ + lastSeekedEvent.seek_from = previousTime; + lastSeekedEvent.seek_to = time; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + "timestamp" : timeStamp + } - } - }, - "timestamp" : timeStamp + self.trigger('seeked',arg); } - return arg; } function getCompletedParams() { var progress = get_progress(); var resultExtTime = formatFloat(player.getCurrentTime()); var dateTime = new Date(); + end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, "context": { @@ -457,7 +458,8 @@ H5P.VideoYouTube = (function ($) { } function end_played_segment(end_time) { var arr; - if (end_time !== played_segments_segment_start){ + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); arr.push(played_segments_segment_start + "[.]" + end_time); @@ -624,8 +626,11 @@ H5P.VideoYouTube = (function ($) { if (!player || !player.seekTo) { return; } - + + previousTime = player.getCurrentTime(); player.seekTo(time, true); + sendSeeked( time ); + seeking = true; }; /** From 3b357b43834d3924ebad97f0ed43a80a31681c72 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:51:39 -1000 Subject: [PATCH 015/137] Modified Seek Event for YouTube --- scripts/html5.js | 1 - scripts/youtube.js | 27 ++++++++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index e865795c..fde6742b 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,7 +49,6 @@ H5P.VideoHtml5 = (function ($) { var seeking = false; var sessionID = guid(); var lastSend = null; - var lastSeekedEvent = { seek_from: null, seek_to: null }; var seekedTo = 0; /** * Avoids firing the same event twice. diff --git a/scripts/youtube.js b/scripts/youtube.js index 12c8946d..43e49008 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -30,7 +30,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var sessionID = guid(); var currentTime = 0; - var lastSeekedEvent = { seek_from: null, seek_to: null }; + var seekedTo = 0; var $wrapper = $('
'); var $placeholder = $('
', { @@ -139,8 +139,10 @@ H5P.VideoYouTube = (function ($) { //get and send play call when not seeking if ( seeking === false ) { self.trigger('play', getPlayParams()); + } else { + self.trigger('seeked', getSeekedParams( seekedTo )); + seeking = false; } - seeking = false; } if ( state.data == 2 ) { //this is a paused event @@ -334,15 +336,7 @@ H5P.VideoYouTube = (function ($) { }; return arg; } - function sendSeeked( time ) { - - //only trigger if first time running or if the last event is different - //this prevents multiple runs of seek when user is scrubbing - if ( (lastSeekedEvent.seek_from == null && lastSeekedEvent.seek_to == null) || - (( Math.abs(time - lastSeekedEvent.seek_to) > 1 && Math.abs(previousTime - lastSeekedEvent.seek_from) > 1) && - Math.abs(previousTime - time) > 1) ){ - lastSeekedEvent.seek_from = previousTime; - lastSeekedEvent.seek_to = time; + function getSeekedParams( time ) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat( time ); @@ -372,10 +366,7 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp } - - self.trigger('seeked',arg); - } - + return arg; } function getCompletedParams() { var progress = get_progress(); @@ -627,9 +618,11 @@ H5P.VideoYouTube = (function ($) { return; } - previousTime = player.getCurrentTime(); + if( seeking === false ){ + previousTime = player.getCurrentTime(); + } player.seekTo(time, true); - sendSeeked( time ); + seekedTo = time; seeking = true; }; From 8680f05bbe29f19e5443386a387319defc17fabb Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:55:11 -1000 Subject: [PATCH 016/137] Format Float on played segments Made sure all floats in played segments were formatted to correct precision --- scripts/html5.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index fde6742b..427ca4c9 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -215,7 +215,7 @@ H5P.VideoHtml5 = (function ($) { if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; diff --git a/scripts/youtube.js b/scripts/youtube.js index 43e49008..c4490319 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -453,7 +453,7 @@ H5P.VideoYouTube = (function ($) { if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; From b55940743b29ea9b38ee015dc2bc4ad71143293d Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 16 Nov 2017 14:38:57 -1000 Subject: [PATCH 017/137] Fix whitespace maintainer uses 2 spaces for indentation, make sure to follow this style. --- scripts/html5.js | 740 ++++++++++++++++++++++----------------------- scripts/youtube.js | 594 ++++++++++++++++++------------------ 2 files changed, 651 insertions(+), 683 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 427ca4c9..34979133 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -34,10 +34,10 @@ H5P.VideoHtml5 = (function ($) { */ var stateBeforeChangingQuality; var currentTimeBeforeChangingQuality; - - /* - * variables to track add extra xAPI statements for video - * @type private + + /** + * Track xAPI statement data for video events. + * @private */ var previousTime = 0; var seekStart = null; @@ -50,6 +50,7 @@ H5P.VideoHtml5 = (function ($) { var sessionID = guid(); var lastSend = null; var seekedTo = 0; + /** * Avoids firing the same event twice. * @private @@ -149,321 +150,313 @@ H5P.VideoHtml5 = (function ($) { video.appendChild(trackElement); } }); - - // common math functions - function formatFloat(number) { - if(number == null) - return null; - return +(parseFloat(number).toFixed(3)); - } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); + // Format parameter as float (or null if invalid). + var formatFloat = function (number) { + if (number == null) { + return null; } - //determine video progress - function get_progress() { - var arr, arr2; + return +(parseFloat(number).toFixed(3)); + }; - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); - } + // Generate a random GUID string. + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / video.duration).toFixed(2); - return progress; - } - - function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; + // Calculate video progress. + var get_progress = function () { + var arr, arr2; + + // Get played segments array. + arr = played_segments == "" ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + // Calculate progress_length. + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; } - } + }); + + var progress = 1 * (progress_length / video.duration).toFixed(2); + + return progress; + }; + + function end_played_segment(end_time) { + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + } + function getLoadedParams() { - //variables used in compiling xAPI results - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - var ccEnabled = false; - var ccLanguage; - - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ - ccEnabled = true; - ccLanguage = video.textTracks[i].language; - } + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; - return { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; } - function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; + var progress = get_progress(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } + function getSeekedParams( time ) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + } + return arg; } - function getVolumeChangeParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); - } - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; + function getVolumeChangeParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; } - function getPlayParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = null; - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - } + function getPlayParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = null; + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + } } + function getFullScreenParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - } - }, - "timestamp" : timeStamp - }; + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; } + function getCompletedParams() { - var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; + var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); + var dateTime = new Date(); + end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } + /** * Helps registering events. * @@ -474,8 +467,8 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { - var extraArg = null; - var extraTrigger = null; + var extraArg = null; + var extraTrigger = null; switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -487,63 +480,63 @@ H5P.VideoHtml5 = (function ($) { video.currentTime = options.startAt; delete options.startAt; } - if( arg === H5P.Video.PLAYING ){ - if ( seeking === true ){ - extraArg = getSeekedParams( seekedTo ); - extraTrigger = 'seeked'; - lastSend = 'seeked'; - seeking = false; - seeking = false; - } else if ( lastSend !== 'play' ){ - extraArg = getPlayParams(); - extraTrigger = 'play'; - lastSend = 'play'; - } - + + if (arg === H5P.Video.PLAYING) { + if (seeking === true) { + extraArg = getSeekedParams(seekedTo); + extraTrigger = 'seeked'; + lastSend = 'seeked'; + seeking = false; + seeking = false; + } else if (lastSend !== 'play') { + extraArg = getPlayParams(); + extraTrigger = 'play'; + lastSend = 'play'; + } } - if( arg === H5P.Video.PAUSED ){ - //put together extraArg for sending to xAPI statement - - if( ! video.seeking && seeking === false) { - extraTrigger = "paused"; - extraArg = getPausedParams(); - lastSend = 'paused'; - } + + if (arg === H5P.Video.PAUSED) { + // Put together extraArg for sending to xAPI statement. + if (!video.seeking && seeking === false) { + extraTrigger = "paused"; + extraArg = getPausedParams(); + lastSend = 'paused'; + } } - //send extra trigger for giving progress on ended call to xAPI - if ( arg === H5P.Video.ENDED ){ - var length = video.duration; - if ( length > 0 ) { - var progress = get_progress(); - if (progress >= 1 ){ - //send statement - extraTrigger = "completed"; - extraArg = getCompletedParams(); - lastSend = 'completed'; - } + + // Send extra trigger for giving progress on ended call to xAPI. + if (arg === H5P.Video.ENDED) { + var length = video.duration; + if (length > 0) { + var progress = get_progress(); + if (progress >= 1) { + // Send statement. + extraTrigger = "completed"; + extraArg = getCompletedParams(); + lastSend = 'completed'; } + } } break; case 'seeked': - return; //seek is tracked differently based on time difference in timeupdate + return; // Seek is tracked differently based on time difference in timeupdate. break; case 'seeking': - return; //just need to store current time for seeked event + return; // Just need to store current time for seeked event. break; case 'volumechange' : arg = getVolumeChangeParams(); lastSend = 'volumechange'; break; - case 'play': - if ( seeking === false && lastSend != h5p ){ - - arg = getPlayParams(); - lastSend = h5p; + case 'play': + if (seeking === false && lastSend != h5p) { + arg = getPlayParams(); + lastSend = h5p; } else { - arg = getSeekedParams( seekedTo ); - lastSend = 'seeked'; - seeking = false; - h5p = 'seeked'; + arg = getSeekedParams(seekedTo); + lastSend = 'seeked'; + seeking = false; + h5p = 'seeked'; } break; case 'fullscreen': @@ -571,12 +564,12 @@ H5P.VideoHtml5 = (function ($) { video.addEventListener('durationchange', andLoaded, false); return; } - - //send extra xAPI statement + + // Send extra xAPI statement. extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); lastSend = 'xAPIloaded'; - + break; case 'error': // Handle error and get message. @@ -602,10 +595,10 @@ H5P.VideoHtml5 = (function ($) { break; } self.trigger(h5p, arg); - - //make extra calls for events with needed values for xAPI statement - if( extraTrigger != null && extraArg != null ){ - self.trigger(extraTrigger, extraArg); + + // Make extra calls for events with needed values for xAPI statement. + if (extraTrigger != null && extraArg != null) { + self.trigger(extraTrigger, extraArg); } }, false); }; @@ -818,7 +811,7 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if(seeking === false) { + if (seeking === false) { previousTime = video.currentTime; } video.currentTime = time; @@ -997,7 +990,6 @@ H5P.VideoHtml5 = (function ($) { mapEvent('timeupdate', 'timeupdate', H5P.Video.PLAYING); mapEvent('volumechange', 'volumechange'); mapEvent('play', 'play', H5P.Video.PLAYING); - //fuscreen events mapEvent('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', 'fullscreen'); if (!video.controls) { @@ -1030,40 +1022,28 @@ H5P.VideoHtml5 = (function ($) { } }); }); - - ////////xAPI extension events for video///// - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); + + // xAPI extension events for video. + self.on('seeked', function (event) { + this.triggerXAPI('seeked', event.data); }); - //catch volumeChanged Event - self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + self.on('volumechange', function (event) { + this.triggerXAPI('interacted', event.data); }); - self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); + self.on('completed', function (event) { + this.triggerXAPI('completed', event.data); }) - //catch fullscreen Event - self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + self.on('fullscreen', function (event) { + this.triggerXAPI('interacted', event.data); }); - //catch play Event - self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); + self.on('play', function (event) { + this.triggerXAPI('played', event.data); }); - self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); + self.on('xAPIloaded', function (event) { + this.triggerXAPI('initialized',event.data); }) - //catch play Event - self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); + self.on('paused', function (event) { + this.triggerXAPI('paused', event.data); }); // Video controls are ready diff --git a/scripts/youtube.js b/scripts/youtube.js index c4490319..d1be60dd 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -92,7 +92,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); - self.trigger('xAPIloaded',getLoadedParams()); + self.trigger('xAPIloaded', getLoadedParams()); }, onApiChange: function () { if (loadCaptionsModule) { @@ -132,33 +132,30 @@ H5P.VideoYouTube = (function ($) { // End IE11 fix self.trigger('stateChange', state.data); - - //calls for xAPI events - if ( state.data == 1 ){ - - //get and send play call when not seeking - if ( seeking === false ) { - self.trigger('play', getPlayParams()); + + // Calls for xAPI events. + if (state.data == 1) { + // Get and send play call when not seeking. + if (seeking === false) { + self.trigger('play', getPlayParams()); } else { - self.trigger('seeked', getSeekedParams( seekedTo )); - seeking = false; + self.trigger('seeked', getSeekedParams(seekedTo)); + seeking = false; } - } - if ( state.data == 2 ) { - //this is a paused event - // execute your code here for paused state - if(seeking === false){ - self.trigger('paused', getPausedParams()); + } else if (state.data == 2) { + // This is a paused event. + if (seeking === false) { + self.trigger('paused', getPausedParams()); } - } else if ( state.data == 0 ) { - //send xapi trigger if video progress indicates completed - var length = player.getDuration(); - if ( length > 0){ - var progress = get_progress(); - if ( progress >= 1 ){ - var arg = getCompletedParams(); - } + } else if (state.data == 0) { + // Send xapi trigger if video progress indicates completed. + var length = player.getDuration(); + if (length > 0) { + var progress = get_progress(); + if (progress >= 1) { + var arg = getCompletedParams(); } + } } } }, @@ -193,315 +190,306 @@ H5P.VideoYouTube = (function ($) { } }); }; - //function used when putting together object to send for xAPI calls - function getWidthOrHeight ( returnType ){ - var quality = player.getPlaybackQuality(); - var width; - var height; - switch (quality) { - case 'small': - width: '320'; - height: '240'; - break; - case 'medium': - width: '640'; - height: '360'; - break; - case 'large': - width: '853'; - height: '480'; - break; - case 'hd720': - width: '640'; - height: '360'; - break; - case 'hd1080': - width: '1920'; - height: '1080'; - break; - case 'highres': - width: '1920'; - height: '1080'; - break; - } - - return (returnType.toLowerCase().trim()=='width')? width : height; + + // Helper to calculate video dimensions (used in xAPI statements). + function getWidthOrHeight (returnType) { + var quality = player.getPlaybackQuality(); + var width; + var height; + switch (quality) { + case 'small': + width: '320'; + height: '240'; + break; + case 'medium': + width: '640'; + height: '360'; + break; + case 'large': + width: '853'; + height: '480'; + break; + case 'hd720': + width: '640'; + height: '360'; + break; + case 'hd1080': + width: '1920'; + height: '1080'; + break; + case 'highres': + width: '1920'; + height: '1080'; + break; + } + return (returnType.toLowerCase().trim()=='width')? width : height; } - + + // function getLoadedParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var quality = player.getPlaybackQuality(); - var height = getWidthOrHeight('height'); - var width = getWidthOrHeight('width'); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; - var volume = player.getVolume(); - var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; - var ccLanguage; - if ( ccEnabled ) { - ccLanguage = player.getOptions('cc', 'track').languageCode; - } - var userAgent = navigator.userAgent; - var playbackRate = player.getPlaybackRate(); - - var arg = { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + var quality = player.getPlaybackQuality(); + var height = getWidthOrHeight('height'); + var width = getWidthOrHeight('width'); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = player.getVolume(); + var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccLanguage; + if ( ccEnabled ) { + ccLanguage = player.getOptions('cc', 'track').languageCode; + } + var userAgent = navigator.userAgent; + var playbackRate = player.getPlaybackRate(); + + var arg = { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + return arg; } + + // function getPlayParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + return arg; } - //paused Params called on pause statement used by xAPI event + + // Paused Params called on pause statement used by xAPI event. function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - previousTime = resultExtTime; - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; + return arg; } + + // function getSeekedParams( time ) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + // Put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + } + return arg; } + + // function getCompletedParams() { - var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var dateTime = new Date(); + end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } - // common math functions - function formatFloat(number) { - if(number == null) - return null; - return +(parseFloat(number).toFixed(3)); + // Format parameter as float (or null if invalid). + var formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); } - //determine video progress + + // Determine video progress function get_progress() { - var arr, arr2; + var arr, arr2; - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } + //get played segments array + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { + return a[0] - b[0]; + }); - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; + //normalize the segments + arr2.forEach(function (v,i) { + if (i > 0) { + //overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; } + + // function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + //don't run if called too closely to each other + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - ////////xAPI extension events for video///// - //catch for seeked event + + // Generate a random GUID string. + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + // xAPI extension events for video. self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); + this.triggerXAPI('seeked', event.data); }); - //catch volumeChanged Event self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + this.triggerXAPI('interacted', event.data); }); self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); + this.triggerXAPI('completed', event.data); }) - //catch fullscreen Event self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + this.triggerXAPI('interacted', event.data); }); - //catch play Event self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); + this.triggerXAPI('played', event.data); }); self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); + this.triggerXAPI('initialized',event.data); }); - //catch play Event self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); + this.triggerXAPI('paused', event.data); }); + /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. @@ -617,10 +605,10 @@ H5P.VideoYouTube = (function ($) { if (!player || !player.seekTo) { return; } - - if( seeking === false ){ + + if (seeking === false) { previousTime = player.getCurrentTime(); - } + } player.seekTo(time, true); seekedTo = time; seeking = true; From 2cb2dce85f0b9755d0595c0c880ed8e794940c93 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 16 Nov 2017 15:49:23 -1000 Subject: [PATCH 018/137] Add documentation and reorganize --- scripts/html5.js | 170 +++++++++++++++++------------ scripts/youtube.js | 266 +++++++++++++++++++++++++-------------------- 2 files changed, 247 insertions(+), 189 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 34979133..46a986dc 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -151,7 +151,11 @@ H5P.VideoHtml5 = (function ($) { } }); - // Format parameter as float (or null if invalid). + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + */ var formatFloat = function (number) { if (number == null) { return null; @@ -159,7 +163,9 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); }; - // Generate a random GUID string. + /** + * Generate a random GUID string. + */ var guid = function () { var s4 = function () { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); @@ -167,7 +173,9 @@ H5P.VideoHtml5 = (function ($) { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; - // Calculate video progress. + /** + * Calculate video progress. + */ var get_progress = function () { var arr, arr2; @@ -215,43 +223,49 @@ H5P.VideoHtml5 = (function ($) { return progress; }; - function end_played_segment(end_time) { + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + var end_played_segment = function (end_time) { var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); + // Need to not push in segments that happen from multiple triggers during scrubbing + if (end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + // Don't run if called too closely to each other. + arr = played_segments == "" ? [] : played_segments.split("[,]"); arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; } - } + }; - function getLoadedParams() { - //variables used in compiling xAPI results + /** + * Create the xAPI object for the 'Loaded' event. + */ + var getLoadedParams = function () { + // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; var screenSize = screen.width + "x" + screen.height; - //playback size var playbackSize = video.videoWidth + "x" + video.videoHeight; - + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = video.videoHeight < video.videoWidth ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; var ccEnabled = false; var ccLanguage; - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ + for (var i = 0; i < video.textTracks.length; i++) { + if (video.textTracks[i].mode === 'showing') { ccEnabled = true; ccLanguage = video.textTracks[i].language; } } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; + return { "context" : { "contextActivities": { @@ -274,15 +288,49 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp": timeStamp }; - } + }; - function getPausedParams() { + /** + * Create xAPI object for the 'Play' event. + */ + var getPlayParams = function () { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + }; + + /** + * Create the xAPI object for the 'Paused' event. + */ + var getPausedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); played_segments_segment_start = resultExtTime; var progress = get_progress(); + return { "result": { "extensions": { @@ -303,18 +351,22 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; - function getSeekedParams( time ) { + /** + * Create the xAPI object for the 'Seeked' event. + * + * @param {int} time Time we are seeking to + */ + var getSeekedParams = function (time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); + var resultExtTime = formatFloat(time); seekStart = resultExtTime; end_played_segment(previousTime); played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { + return { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -332,21 +384,24 @@ H5P.VideoHtml5 = (function ($) { } }, "timestamp" : timeStamp - } - return arg; - } + }; + }; - function getVolumeChangeParams() { + /** + * Create xAPI object for the 'VolumeChange' event. + */ + var getVolumeChangeParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); volume_changed_at = video.currentTime; var isMuted = video.muted; var volumeChange; - if ( isMuted === true ){ + if (isMuted === true) { volumeChange = 0; } else { volumeChange = formatFloat(video.volume); } + return { "result" : { "extensions": { @@ -366,44 +421,17 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } - - function getPlayParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = null; - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - } - } + }; - function getFullScreenParams() { + /** + * Create xAPI object for the 'FullScreen' event. + */ + var getFullScreenParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - //sed xapi statement var screenSize = screen.width + "x" + screen.height; - - //playback size var playbackSize = video.videoWidth + "x" + video.videoHeight; return { @@ -427,14 +455,18 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; - function getCompletedParams() { + /** + * Create xAPI object for the 'Completed' event. + */ + var getCompletedParams = function () { var progress = get_progress(); var resultExtTime = formatFloat(video.currentTime); var dateTime = new Date(); end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); + return { "result": { "extensions": { @@ -455,7 +487,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; /** * Helps registering events. @@ -504,13 +536,12 @@ H5P.VideoHtml5 = (function ($) { } } - // Send extra trigger for giving progress on ended call to xAPI. if (arg === H5P.Video.ENDED) { + // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { var progress = get_progress(); if (progress >= 1) { - // Send statement. extraTrigger = "completed"; extraArg = getCompletedParams(); lastSend = 'completed'; @@ -565,7 +596,6 @@ H5P.VideoHtml5 = (function ($) { return; } - // Send extra xAPI statement. extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); lastSend = 'xAPIloaded'; diff --git a/scripts/youtube.js b/scripts/youtube.js index d1be60dd..7dab5577 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -16,9 +16,10 @@ H5P.VideoYouTube = (function ($) { var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; numInstances++; - /* - * variables to track add extra xAPI statements for video - * @type private + + /** + * Track xAPI statement data for video events. + * @private */ var previousTime = 0; var seekStart = null; @@ -191,11 +192,15 @@ H5P.VideoYouTube = (function ($) { }); }; - // Helper to calculate video dimensions (used in xAPI statements). + /** + * Helper to calculate video dimensions (used in xAPI statements). + * + * @param {string} Which dimension to return ('width' or 'height') + */ function getWidthOrHeight (returnType) { var quality = player.getPlaybackQuality(); - var width; - var height; + var width = ''; + var height = ''; switch (quality) { case 'small': width: '320'; @@ -222,11 +227,102 @@ H5P.VideoYouTube = (function ($) { height: '1080'; break; } - return (returnType.toLowerCase().trim()=='width')? width : height; + return (returnType.toLowerCase().trim()=='width') ? width : height; } - // - function getLoadedParams(){ + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + */ + var formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); + }; + + /** + * Generate a random GUID string. + */ + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Calculate video progress. + */ + var get_progress = function () { + var arr, arr2; + + //get played segments array + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { + return a[0] - b[0]; + }); + + //normalize the segments + arr2.forEach(function (v,i) { + if (i > 0) { + //overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + var end_played_segment = function (end_time) { + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + //don't run if called too closely to each other + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + }; + + /** + * Create the xAPI object for the 'Loaded' event. + */ + var getLoadedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); @@ -235,17 +331,17 @@ H5P.VideoYouTube = (function ($) { var quality = player.getPlaybackQuality(); var height = getWidthOrHeight('height'); var width = getWidthOrHeight('width'); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var playbackSize = width !== undefined ? width + 'x' + height : "undetermined"; var volume = player.getVolume(); - var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccEnabled = player.getOptions().indexOf("cc") !== -1; var ccLanguage; - if ( ccEnabled ) { + if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } var userAgent = navigator.userAgent; var playbackRate = player.getPlaybackRate(); - var arg = { + return { "context" : { "contextActivities": { "category": [{ @@ -267,17 +363,19 @@ H5P.VideoYouTube = (function ($) { }, "timestamp": timeStamp }; - return arg; - } + }; - // - function getPlayParams(){ + /** + * Create xAPI object for the 'Play' event. + */ + var getPlayParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); played_segments_segment_start = resultExtTime; seekStart = null; - var arg = { + + return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, @@ -295,11 +393,12 @@ H5P.VideoYouTube = (function ($) { }, "timestamp": timeStamp }; - return arg; - } + }; - // Paused Params called on pause statement used by xAPI event. - function getPausedParams() { + /** + * Create the xAPI object for the 'Paused' event. + */ + var getPausedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); @@ -307,7 +406,8 @@ H5P.VideoYouTube = (function ($) { end_played_segment(resultExtTime); played_segments_segment_start = resultExtTime; var progress = get_progress(); - var arg = { + + return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, @@ -327,19 +427,22 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp }; - return arg; - } + }; - // - function getSeekedParams( time ) { + /** + * Create the xAPI object for the 'Seeked' event. + * + * @param {int} time Time we are seeking to + */ + var getSeekedParams = function (time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); + var resultExtTime = formatFloat(time); seekStart = resultExtTime; end_played_segment(previousTime); played_segments_segment_start = seekStart; - // Put together data for xAPI statement to be sent with event - var arg = { + + return { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -357,17 +460,19 @@ H5P.VideoYouTube = (function ($) { } }, "timestamp" : timeStamp - } - return arg; - } + }; + }; - // - function getCompletedParams() { + /** + * Create xAPI object for the 'Completed' event. + */ + var getCompletedParams = function () { var progress = get_progress(); var resultExtTime = formatFloat(player.getCurrentTime()); var dateTime = new Date(); end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); + return { "result": { "extensions": { @@ -388,105 +493,28 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp }; - } - - // Format parameter as float (or null if invalid). - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - } - - // Determine video progress - function get_progress() { - var arr, arr2; - - //get played segments array - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { - return a[0] - b[0]; - }); - - //normalize the segments - arr2.forEach(function (v,i) { - if (i > 0) { - //overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; - } - - // - function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - //don't run if called too closely to each other - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - } - - // Generate a random GUID string. - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; // xAPI extension events for video. - self.on('seeked', function(event) { + self.on('seeked', function (event) { this.triggerXAPI('seeked', event.data); }); - self.on('volumechange', function(event) { + self.on('volumechange', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('completed', function(event){ + self.on('completed', function (event){ this.triggerXAPI('completed', event.data); }) - self.on('fullscreen', function(event) { + self.on('fullscreen', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('play', function(event) { + self.on('play', function (event) { this.triggerXAPI('played', event.data); }); - self.on('xAPIloaded', function(event){ + self.on('xAPIloaded', function (event){ this.triggerXAPI('initialized',event.data); }); - self.on('paused', function(event) { + self.on('paused', function (event) { this.triggerXAPI('paused', event.data); }); From 920f80ed27de1f628d61ce588755ecadc6808706 Mon Sep 17 00:00:00 2001 From: kirkj Date: Fri, 17 Nov 2017 13:43:07 -1000 Subject: [PATCH 019/137] Made Code DRY --- scripts/html5.js | 379 ++++----------------------------------------- scripts/video.js | 377 ++++++++++++++++++++++++++++++++++++++++++++ scripts/youtube.js | 293 ++--------------------------------- 3 files changed, 420 insertions(+), 629 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 46a986dc..b995120f 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -39,17 +39,7 @@ H5P.VideoHtml5 = (function ($) { * Track xAPI statement data for video events. * @private */ - var previousTime = 0; - var seekStart = null; - var played_segments = []; - var played_segments_segment_start = 0; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var sessionID = guid(); var lastSend = null; - var seekedTo = 0; /** * Avoids firing the same event twice. @@ -151,111 +141,11 @@ H5P.VideoHtml5 = (function ($) { } }); - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - */ - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - }; - - /** - * Generate a random GUID string. - */ - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Calculate video progress. - */ - var get_progress = function () { - var arr, arr2; - - // Get played segments array. - arr = played_segments == "" ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - // Sort the array. - arr2.sort(function (a,b) { - return a[0] - b[0]; - }); - - // Normalize the segments. - arr2.forEach(function (v,i) { - if (i > 0) { - // Overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - // Calculate progress_length. - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / video.duration).toFixed(2); - - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} end_time When the current played segment ended - */ - var end_played_segment = function (end_time) { - var arr; - // Need to not push in segments that happen from multiple triggers during scrubbing - if (end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - // Don't run if called too closely to each other. - arr = played_segments == "" ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - }; - /** * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = video.videoWidth + "x" + video.videoHeight; - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = video.videoHeight < video.videoWidth ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; + var ccEnabled = false; var ccLanguage; @@ -265,229 +155,18 @@ H5P.VideoHtml5 = (function ($) { ccLanguage = video.textTracks[i].language; } } - - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create xAPI object for the 'Play' event. - */ - var getPlayParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Paused' event. - */ - var getPausedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Seeked' event. - * - * @param {int} time Time we are seeking to - */ - var getSeekedParams = function (time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(time); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'VolumeChange' event. - */ - var getVolumeChangeParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); + + var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if ( isFullScreen === undefined ){ + isFullScreen = false; } + + return H5P.Video.getxAPIInitializedObject( video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage ); - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'FullScreen' event. - */ - var getFullScreenParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'Completed' event. - */ - var getCompletedParams = function () { - var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; }; + + //set duration used for xAPI statements + H5P.Video.duration = video.duration; /** * Helps registering events. @@ -514,14 +193,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (seeking === true) { - extraArg = getSeekedParams(seekedTo); + if ( H5P.Video.seeking === true) { + extraArg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - seeking = false; - seeking = false; + H5P.Video.seeking = false; + H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = getPlayParams(); + extraArg = H5P.Video.getxAPIPlayObject( video.currentTime ); extraTrigger = 'play'; lastSend = 'play'; } @@ -529,9 +208,9 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && seeking === false) { + if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = getPausedParams(); + extraArg = H5P.Video.getxAPIPauseObject( video.currentTime, video.duration ); lastSend = 'paused'; } } @@ -540,10 +219,10 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = get_progress(); + var progress = H5P.Video.get_progress( video.current_time, video.duration ); if (progress >= 1) { extraTrigger = "completed"; - extraArg = getCompletedParams(); + extraArg = H5P.Video.getxAPICompleteObject( video.currentTime, video.duration ); lastSend = 'completed'; } } @@ -556,22 +235,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = getVolumeChangeParams(); + arg = H5P.Video.getxAPIVolumeChangeObject( video.currentTime, video.muted, video.volume ); lastSend = 'volumechange'; break; case 'play': - if (seeking === false && lastSend != h5p) { - arg = getPlayParams(); + if ( H5P.Video.seeking === false && lastSend != h5p) { + arg = H5P.Video.getxAPIPlayObject( video.currentTime ); lastSend = h5p; } else { - arg = getSeekedParams(seekedTo); + arg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); lastSend = 'seeked'; - seeking = false; + H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = getFullScreenParams(); + arg = H5P.Video.getxAPIFullScreenObject( video.currentTime, video.videoWidth, video.videoHeight ); lastSend = h5p; break; case 'loaded': @@ -841,12 +520,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (seeking === false) { - previousTime = video.currentTime; + if ( H5P.Video.seeking === false) { + H5P.Video.previousTime = video.currentTime; } video.currentTime = time; - seeking = true; - seekedTo = time; + H5P.Video.seeking = true; + H5P.Video.seekedTo = time; }; /** diff --git a/scripts/video.js b/scripts/video.js index 2460b05f..ae590e15 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -135,7 +135,384 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } + + /** + * Generate a random GUID string used for seesionID with video xAPI statements. + */ + Video.guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + * used when making arguments sent with video xAPI statments + */ + Video.formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); + }; + + /** + * Track xAPI statement data for video events. + * @private + */ + Video.previousTime = 0; + Video.seekStart = null; + Video.played_segments = []; + Video.played_segments_segment_start =0; + Video.played_segments_segment_end; + Video.volume_changed_on = null; + Video.volume_changed_at = 0; + Video.seeking = false; + Video.sessionID = Video.guid(); + Video.currentTime = 0; + Video.seekedTo = 0; + + /** + * Calculate video progress. + */ + Video.get_progress = function (current_time, duration ) { + var arr, arr2; + + // Get played segments array. + arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); + if (Video.played_segments_segment_start != null) { + arr.push(Video.played_segments_segment_start + "[.]" + Video.formatFloat(current_time)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + // Calculate progress_length. + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / duration ).toFixed(2); + + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + Video.end_played_segment = function (end_time) { + var arr; + // Need to not push in segments that happen from multiple triggers during scrubbing + if (end_time !== Video.played_segments_segment_start && Math.abs(end_time - Video.played_segments_segment_start) > 1 ) { + // Don't run if called too closely to each other. + arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); + arr.push(Video.formatFloat(Video.played_segments_segment_start) + "[.]" + Video.formatFloat(end_time)); + Video.played_segments = arr.join("[,]"); + Video.played_segments_segment_end = end_time; + Video.played_segments_segment_start = null; + } + }; + /** + * Video.getxAPIPauseObject + * + * @param {type} current_time + * @returns {json object} + */ + Video.getxAPIPauseObject = function ( current_time, duration ) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.end_played_segment(resultExtTime); + Video.played_segments_segment_start = resultExtTime; + var progress = Video.get_progress( current_time, duration ); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + /** + * Video.getxAPIPlayObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with event to be triggered by xAPI event + */ + Video.getxAPIPlayObject = function ( current_time ){ + + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.played_segments_segment_start = resultExtTime; + Video.seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + } + + /** + * Video.getxAPIPlayObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with seeked event to be triggered by xAPI event + */ + Video.getxAPISeekedObject = function ( current_time ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.seekStart = resultExtTime; + Video.end_played_segment(Video.previousTime); + Video.played_segments_segment_start = Video.seekStart; + + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, + "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIVolumeChangeObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with volume change event to be triggered by xAPI event + */ + Video.getxAPIVolumeChangeObject = function ( current_time, muted, volume ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + Video.volume_changed_at = Video.formatFloat( current_time ); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = Video.formatFloat(volume); + } + + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPICompleteObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with complete event to be triggered by xAPI event + */ + Video.getxAPICompleteObject = function ( current_time, duration ){ + var progress = Video.get_progress( current_time, duration ); + var resultExtTime = Video.formatFloat(current_time); + var dateTime = new Date(); + Video.end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIFullScreenObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + Video.getxAPIFullScreenObject = function ( current_time, width, height, fullscreen = false ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat( current_time ); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if (state == undefined ){ + state = fullscreen; + } + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIInitializedObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + Video.getxAPIInitializedObject = function ( current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false ){ + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = Video.formatFloat( volume ); + var quality = (quality === false )? (height < width ? height : width) : quality; + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if ( state === undefined ){ + state = false; + } + + + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + } + // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; diff --git a/scripts/youtube.js b/scripts/youtube.js index 7dab5577..16ff2b13 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -17,22 +17,6 @@ H5P.VideoYouTube = (function ($) { var id = 'h5p-youtube-' + numInstances; numInstances++; - /** - * Track xAPI statement data for video events. - * @private - */ - var previousTime = 0; - var seekStart = null; - var played_segments = []; - var played_segments_segment_start =0; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var sessionID = guid(); - var currentTime = 0; - var seekedTo = 0; - var $wrapper = $('
'); var $placeholder = $('
', { id: id, @@ -137,24 +121,24 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data == 1) { // Get and send play call when not seeking. - if (seeking === false) { - self.trigger('play', getPlayParams()); + if (H5P.Video.seeking === false) { + self.trigger('play', H5P.Video.getxAPIPlayObject( player.getCurrentTime()) ); } else { - self.trigger('seeked', getSeekedParams(seekedTo)); - seeking = false; + self.trigger('seeked',H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo) ); + H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. - if (seeking === false) { - self.trigger('paused', getPausedParams()); + if (H5P.Video.seeking === false) { + self.trigger('paused', H5P.Video.getxAPIPauseObject( player.getCurrentTime(), player.getDuration()) ); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = get_progress(); + var progress = H5P.Video.get_progress( player.getCurrentTime(), player.getDuration() ); if (progress >= 1) { - var arg = getCompletedParams(); + var arg = H5P.Video.getxAPICompleteObject( player.getCurrentTime(), player.getDuration() );; } } } @@ -230,269 +214,20 @@ H5P.VideoYouTube = (function ($) { return (returnType.toLowerCase().trim()=='width') ? width : height; } - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - */ - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - }; - - /** - * Generate a random GUID string. - */ - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Calculate video progress. - */ - var get_progress = function () { - var arr, arr2; - - //get played segments array - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { - return a[0] - b[0]; - }); - - //normalize the segments - arr2.forEach(function (v,i) { - if (i > 0) { - //overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} end_time When the current played segment ended - */ - var end_played_segment = function (end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - //don't run if called too closely to each other - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - }; - /** * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var quality = player.getPlaybackQuality(); var height = getWidthOrHeight('height'); var width = getWidthOrHeight('width'); - var playbackSize = width !== undefined ? width + 'x' + height : "undetermined"; - var volume = player.getVolume(); var ccEnabled = player.getOptions().indexOf("cc") !== -1; var ccLanguage; if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - var userAgent = navigator.userAgent; - var playbackRate = player.getPlaybackRate(); + + return H5P.Video.getxAPIInitializedObject( player.getCurrentTime() , width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create xAPI object for the 'Play' event. - */ - var getPlayParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Paused' event. - */ - var getPausedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - previousTime = resultExtTime; - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Seeked' event. - * - * @param {int} time Time we are seeking to - */ - var getSeekedParams = function (time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(time); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'Completed' event. - */ - var getCompletedParams = function () { - var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; }; // xAPI extension events for video. @@ -634,12 +369,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (seeking === false) { - previousTime = player.getCurrentTime(); + if (H5P.Video.seeking === false) { + H5P.Video.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - seekedTo = time; - seeking = true; + H5P.Video.seeking = time; + H5P.Video.seeking = true; }; /** From 3527921944ec965f879d9e48d9e64bc219ec1040 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:46:05 -1000 Subject: [PATCH 020/137] Whitespace --- scripts/html5.js | 37 +++++++++++------------ scripts/video.js | 74 +++++++++++++++++++++++----------------------- scripts/youtube.js | 14 ++++----- 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index b995120f..36d58fa7 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -145,7 +145,6 @@ H5P.VideoHtml5 = (function ($) { * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - var ccEnabled = false; var ccLanguage; @@ -161,11 +160,11 @@ H5P.VideoHtml5 = (function ($) { isFullScreen = false; } - return H5P.Video.getxAPIInitializedObject( video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage ); + return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; - - //set duration used for xAPI statements + + // Set duration used for xAPI statements. H5P.Video.duration = video.duration; /** @@ -193,14 +192,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if ( H5P.Video.seeking === true) { - extraArg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); + if (H5P.Video.seeking === true) { + extraArg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - H5P.Video.seeking = false; - H5P.Video.seeking = false; + H5P.Video.seeking = false; + H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getxAPIPlayObject( video.currentTime ); + extraArg = H5P.Video.getxAPIPlayObject(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -210,7 +209,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getxAPIPauseObject( video.currentTime, video.duration ); + extraArg = H5P.Video.getxAPIPauseObject(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -219,10 +218,10 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = H5P.Video.get_progress( video.current_time, video.duration ); + var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getxAPICompleteObject( video.currentTime, video.duration ); + extraArg = H5P.Video.getxAPICompleteObject(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -235,22 +234,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getxAPIVolumeChangeObject( video.currentTime, video.muted, video.volume ); + arg = H5P.Video.getxAPIVolumeChangeObject(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': - if ( H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getxAPIPlayObject( video.currentTime ); + if (H5P.Video.seeking === false && lastSend != h5p) { + arg = H5P.Video.getxAPIPlayObject(video.currentTime); lastSend = h5p; } else { - arg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); + arg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); lastSend = 'seeked'; H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.Video.getxAPIFullScreenObject( video.currentTime, video.videoWidth, video.videoHeight ); + arg = H5P.Video.getxAPIFullScreenObject(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': @@ -520,11 +519,11 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if ( H5P.Video.seeking === false) { + if (H5P.Video.seeking === false) { H5P.Video.previousTime = video.currentTime; } video.currentTime = time; - H5P.Video.seeking = true; + H5P.Video.seeking = true; H5P.Video.seekedTo = time; }; diff --git a/scripts/video.js b/scripts/video.js index ae590e15..97125bf4 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -135,7 +135,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } - + /** * Generate a random GUID string used for seesionID with video xAPI statements. */ @@ -145,7 +145,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; - + /** * Format parameter as float (or null if invalid). * @@ -158,7 +158,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } return +(parseFloat(number).toFixed(3)); }; - + /** * Track xAPI statement data for video events. * @private @@ -178,7 +178,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Calculate video progress. */ - Video.get_progress = function (current_time, duration ) { + Video.get_progress = function (current_time, duration) { var arr, arr2; // Get played segments array. @@ -244,17 +244,17 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** * Video.getxAPIPauseObject - * + * * @param {type} current_time * @returns {json object} */ - Video.getxAPIPauseObject = function ( current_time, duration ) { + Video.getxAPIPauseObject = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); Video.end_played_segment(resultExtTime); Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress( current_time, duration ); + var progress = Video.get_progress(current_time, duration); return { "result": { @@ -279,13 +279,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } /** * Video.getxAPIPlayObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getxAPIPlayObject = function ( current_time ){ - + Video.getxAPIPlayObject = function (current_time) { + var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -311,15 +311,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp": timeStamp }; } - + /** * Video.getxAPIPlayObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getxAPISeekedObject = function ( current_time ){ + Video.getxAPISeekedObject = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -347,18 +347,18 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPIVolumeChangeObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getxAPIVolumeChangeObject = function ( current_time, muted, volume ){ + Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat( current_time ); + Video.volume_changed_at = Video.formatFloat(current_time); var isMuted = muted; var volumeChange; if (isMuted === true) { @@ -387,16 +387,16 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPICompleteObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getxAPICompleteObject = function ( current_time, duration ){ - var progress = Video.get_progress( current_time, duration ); + Video.getxAPICompleteObject = function (current_time, duration) { + var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); Video.end_played_segment(resultExtTime); @@ -422,19 +422,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }, "timestamp" : timeStamp }; - } - + } + /** * Video.getxAPIFullScreenObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIFullScreenObject = function ( current_time, width, height, fullscreen = false ){ + Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat( current_time ); + var resultExtTime = Video.formatFloat(current_time); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; if (state == undefined ){ state = fullscreen; @@ -464,15 +464,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPIInitializedObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIInitializedObject = function ( current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false ){ + Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); @@ -480,7 +480,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var screenSize = screen.width + "x" + screen.height; var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; - var volume = Video.formatFloat( volume ); + var volume = Video.formatFloat(volume); var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; @@ -512,7 +512,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp": timeStamp }; } - + // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; diff --git a/scripts/youtube.js b/scripts/youtube.js index 16ff2b13..40146623 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -122,23 +122,23 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getxAPIPlayObject( player.getCurrentTime()) ); + self.trigger('play', H5P.Video.getxAPIPlayObject(player.getCurrentTime())); } else { - self.trigger('seeked',H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo) ); + self.trigger('seeked',H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo)); H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getxAPIPauseObject( player.getCurrentTime(), player.getDuration()) ); + self.trigger('paused', H5P.Video.getxAPIPauseObject(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = H5P.Video.get_progress( player.getCurrentTime(), player.getDuration() ); + var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getxAPICompleteObject( player.getCurrentTime(), player.getDuration() );; + var arg = H5P.Video.getxAPICompleteObject(player.getCurrentTime(), player.getDuration()); } } } @@ -225,8 +225,8 @@ H5P.VideoYouTube = (function ($) { if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - - return H5P.Video.getxAPIInitializedObject( player.getCurrentTime() , width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + + return H5P.Video.getxAPIInitializedObject(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; From 847bd71599d7fd42c8c29149eb4de04cb1480471 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:46:33 -1000 Subject: [PATCH 021/137] Cleaner logic --- scripts/html5.js | 9 +++------ scripts/video.js | 11 ++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 36d58fa7..1e08c83d 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -154,12 +154,9 @@ H5P.VideoHtml5 = (function ($) { ccLanguage = video.textTracks[i].language; } } - - var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if ( isFullScreen === undefined ){ - isFullScreen = false; - } - + + var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; diff --git a/scripts/video.js b/scripts/video.js index 97125bf4..ab3f4a78 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -435,10 +435,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if (state == undefined ){ - state = fullscreen; - } + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; @@ -483,11 +480,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var volume = Video.formatFloat(volume); var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if ( state === undefined ){ - state = false; - } - + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; return { "context" : { From 157f527f48df6a30403e18eb1ee3f72dfc0203e6 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:54:20 -1000 Subject: [PATCH 022/137] Whitespace (indentation) --- scripts/video.js | 391 ++++++++++++++++++++++++----------------------- 1 file changed, 196 insertions(+), 195 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index ab3f4a78..de4611b5 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -242,6 +242,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { Video.played_segments_segment_start = null; } }; + /** * Video.getxAPIPauseObject * @@ -249,34 +250,35 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * @returns {json object} */ Video.getxAPIPauseObject = function (current_time, duration) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.end_played_segment(resultExtTime); - Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress(current_time, duration); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.end_played_segment(resultExtTime); + Video.played_segments_segment_start = resultExtTime; + var progress = Video.get_progress(current_time, duration); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; + /** * Video.getxAPIPlayObject * @@ -285,32 +287,31 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with event to be triggered by xAPI event */ Video.getxAPIPlayObject = function (current_time) { - - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.played_segments_segment_start = resultExtTime; - Video.seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.played_segments_segment_start = resultExtTime; + Video.seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp": timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + }; /** * Video.getxAPIPlayObject @@ -320,33 +321,33 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with seeked event to be triggered by xAPI event */ Video.getxAPISeekedObject = function (current_time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.seekStart = resultExtTime; - Video.end_played_segment(Video.previousTime); - Video.played_segments_segment_start = Video.seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, - "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.seekStart = resultExtTime; + Video.end_played_segment(Video.previousTime); + Video.played_segments_segment_start = Video.seekStart; + + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, + "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIVolumeChangeObject @@ -356,37 +357,37 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with volume change event to be triggered by xAPI event */ Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat(current_time); - var isMuted = muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = Video.formatFloat(volume); - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + Video.volume_changed_at = Video.formatFloat(current_time); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = Video.formatFloat(volume); + } - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPICompleteObject @@ -396,33 +397,33 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with complete event to be triggered by xAPI event */ Video.getxAPICompleteObject = function (current_time, duration) { - var progress = Video.get_progress(current_time, duration); - var resultExtTime = Video.formatFloat(current_time); - var dateTime = new Date(); - Video.end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var progress = Video.get_progress(current_time, duration); + var resultExtTime = Video.formatFloat(current_time); + var dateTime = new Date(); + Video.end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIFullScreenObject @@ -432,35 +433,35 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with full screen change event to be triggered by xAPI event */ Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = width + "x" + height; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIInitializedObject @@ -470,41 +471,41 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with full screen change event to be triggered by xAPI event */ Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - var screenSize = screen.width + "x" + screen.height; - var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; - var playbackRate = rate; - var volume = Video.formatFloat(volume); - var quality = (quality === false )? (height < width ? height : width) : quality; - var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = Video.formatFloat(volume); + var quality = (quality === false )? (height < width ? height : width) : quality; + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp": timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + }; // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); From 60015e7b3d8dec22730206223af36ac3b7041c50 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:05:59 -1000 Subject: [PATCH 023/137] Refactor xAPI object function names Use template getArgsXAPI{verb} instead of getxAPI{verb}Object --- scripts/html5.js | 18 +++++++++--------- scripts/video.js | 28 ++++++++++++++-------------- scripts/youtube.js | 10 +++++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1e08c83d..1d9ac6d6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -157,7 +157,7 @@ H5P.VideoHtml5 = (function ($) { var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return H5P.Video.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; @@ -190,13 +190,13 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PLAYING) { if (H5P.Video.seeking === true) { - extraArg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); + extraArg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; H5P.Video.seeking = false; H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getxAPIPlayObject(video.currentTime); + extraArg = H5P.Video.getArgsXAPIPlay(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -206,7 +206,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getxAPIPauseObject(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIPause(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -218,7 +218,7 @@ H5P.VideoHtml5 = (function ($) { var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getxAPICompleteObject(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIComplete(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -231,22 +231,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getxAPIVolumeChangeObject(video.currentTime, video.muted, video.volume); + arg = H5P.Video.getArgsXAPIVolumeChange(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getxAPIPlayObject(video.currentTime); + arg = H5P.Video.getArgsXAPIPlay(video.currentTime); lastSend = h5p; } else { - arg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); + arg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); lastSend = 'seeked'; H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.Video.getxAPIFullScreenObject(video.currentTime, video.videoWidth, video.videoHeight); + arg = H5P.Video.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': diff --git a/scripts/video.js b/scripts/video.js index de4611b5..4e9ecbd8 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -244,12 +244,12 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPauseObject + * Video.getArgsXAPIPause * * @param {type} current_time * @returns {json object} */ - Video.getxAPIPauseObject = function (current_time, duration) { + Video.getArgsXAPIPause = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -280,13 +280,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPlayObject + * Video.getArgsXAPIPlay * * @param { float } current_time time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getxAPIPlayObject = function (current_time) { + Video.getArgsXAPIPlay = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -314,13 +314,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPlayObject + * Video.getArgsXAPISeeked * * @param { float } current_time time of the video currently * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getxAPISeekedObject = function (current_time) { + Video.getArgsXAPISeeked = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -350,13 +350,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIVolumeChangeObject + * Video.getArgsXAPIVolumeChange * * @param { float } current_time time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChange = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); Video.volume_changed_at = Video.formatFloat(current_time); @@ -390,13 +390,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPICompleteObject + * Video.getArgsXAPIComplete * * @param { float } current_time time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getxAPICompleteObject = function (current_time, duration) { + Video.getArgsXAPIComplete = function (current_time, duration) { var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); @@ -426,13 +426,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIFullScreenObject + * Video.getArgsXAPIFullScreen * * @param { float } current_time time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { + Video.getArgsXAPIFullScreen = function (current_time, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -464,13 +464,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIInitializedObject + * Video.getArgsXAPIInitialized * * @param { float } current_time time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); diff --git a/scripts/youtube.js b/scripts/youtube.js index 40146623..9904cb9f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -122,15 +122,15 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getxAPIPlayObject(player.getCurrentTime())); + self.trigger('play', H5P.Video.getArgsXAPIPlay(player.getCurrentTime())); } else { - self.trigger('seeked',H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo)); + self.trigger('seeked', H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo)); H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getxAPIPauseObject(player.getCurrentTime(), player.getDuration())); + self.trigger('paused', H5P.Video.getArgsXAPIPause(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. @@ -138,7 +138,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getxAPICompleteObject(player.getCurrentTime(), player.getDuration()); + var arg = H5P.Video.getArgsXAPIComplete(player.getCurrentTime(), player.getDuration()); } } } @@ -226,7 +226,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return H5P.Video.getxAPIInitializedObject(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return H5P.Video.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; From 871fb29f152c1d557d274c10c253f6e725612974 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:10:06 -1000 Subject: [PATCH 024/137] Refactor xAPI function names xAPI verbs are past tense, so make the function names match. --- scripts/html5.js | 10 +++++----- scripts/video.js | 16 ++++++++-------- scripts/youtube.js | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1d9ac6d6..542783e6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -196,7 +196,7 @@ H5P.VideoHtml5 = (function ($) { H5P.Video.seeking = false; H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getArgsXAPIPlay(video.currentTime); + extraArg = H5P.Video.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -206,7 +206,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getArgsXAPIPause(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -218,7 +218,7 @@ H5P.VideoHtml5 = (function ($) { var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getArgsXAPIComplete(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPICompleted(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -231,12 +231,12 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getArgsXAPIVolumeChange(video.currentTime, video.muted, video.volume); + arg = H5P.Video.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getArgsXAPIPlay(video.currentTime); + arg = H5P.Video.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { arg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); diff --git a/scripts/video.js b/scripts/video.js index 4e9ecbd8..c63b706d 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -244,12 +244,12 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIPause + * Video.getArgsXAPIPaused * * @param {type} current_time * @returns {json object} */ - Video.getArgsXAPIPause = function (current_time, duration) { + Video.getArgsXAPIPaused = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -280,13 +280,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIPlay + * Video.getArgsXAPIPlayed * * @param { float } current_time time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getArgsXAPIPlay = function (current_time) { + Video.getArgsXAPIPlayed = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -350,13 +350,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIVolumeChange + * Video.getArgsXAPIVolumeChanged * * @param { float } current_time time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getArgsXAPIVolumeChange = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChanged = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); Video.volume_changed_at = Video.formatFloat(current_time); @@ -390,13 +390,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIComplete + * Video.getArgsXAPICompleted * * @param { float } current_time time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getArgsXAPIComplete = function (current_time, duration) { + Video.getArgsXAPICompleted = function (current_time, duration) { var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); diff --git a/scripts/youtube.js b/scripts/youtube.js index 9904cb9f..9a116842 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -122,7 +122,7 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getArgsXAPIPlay(player.getCurrentTime())); + self.trigger('play', H5P.Video.getArgsXAPIPlayed(player.getCurrentTime())); } else { self.trigger('seeked', H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo)); H5P.Video.seeking = false; @@ -130,7 +130,7 @@ H5P.VideoYouTube = (function ($) { } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getArgsXAPIPause(player.getCurrentTime(), player.getDuration())); + self.trigger('paused', H5P.Video.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. @@ -138,7 +138,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getArgsXAPIComplete(player.getCurrentTime(), player.getDuration()); + var arg = H5P.Video.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); } } } From e52a8dd252d14366e3abd65ace278f885b641052 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:27:00 -1000 Subject: [PATCH 025/137] Fix for misnamed variable html5 video object has a property called currentTime, not current_time --- scripts/html5.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/html5.js b/scripts/html5.js index 542783e6..7c2d3d5d 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -215,7 +215,7 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = H5P.Video.get_progress(video.current_time, video.duration); + var progress = H5P.Video.getProgress(video.currentTime, video.duration); if (progress >= 1) { extraTrigger = "completed"; extraArg = H5P.Video.getArgsXAPICompleted(video.currentTime, video.duration); From 449df2d829ced418b79915b4181fa7a64dbb55b6 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:32:39 -1000 Subject: [PATCH 026/137] Refactor var names (use camelCase, not snake case) --- scripts/video.js | 106 ++++++++++++++++++++++----------------------- scripts/youtube.js | 2 +- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index c63b706d..e5999227 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -165,11 +165,11 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { */ Video.previousTime = 0; Video.seekStart = null; - Video.played_segments = []; - Video.played_segments_segment_start =0; - Video.played_segments_segment_end; - Video.volume_changed_on = null; - Video.volume_changed_at = 0; + Video.playedSegments = []; + Video.playedSegmentsSegmentStart =0; + Video.playedSegmentsSegmentEnd; + Video.volumeChangedOn = null; + Video.volumeChangedAt = 0; Video.seeking = false; Video.sessionID = Video.guid(); Video.currentTime = 0; @@ -178,13 +178,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Calculate video progress. */ - Video.get_progress = function (current_time, duration) { + Video.getProgress = function (currentTime, duration) { var arr, arr2; // Get played segments array. - arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); - if (Video.played_segments_segment_start != null) { - arr.push(Video.played_segments_segment_start + "[.]" + Video.formatFloat(current_time)); + arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); + if (Video.playedSegmentsSegmentStart != null) { + arr.push(Video.playedSegmentsSegmentStart + "[.]" + Video.formatFloat(currentTime)); } arr2 = []; @@ -212,15 +212,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } }); - // Calculate progress_length. - var progress_length = 0; + // Calculate progress length. + var progressLength = 0; arr2.forEach(function (v,i) { if (v[1] > v[0]) { - progress_length += v[1] - v[0]; + progressLength += v[1] - v[0]; } }); - var progress = 1 * (progress_length / duration ).toFixed(2); + var progress = 1 * (progressLength / duration ).toFixed(2); return progress; }; @@ -228,41 +228,41 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Add a played segment to the array of already played segments. * - * @param {int} end_time When the current played segment ended + * @param {int} endTime When the current played segment ended */ - Video.end_played_segment = function (end_time) { + Video.endPlayedSegment = function (endTime) { var arr; // Need to not push in segments that happen from multiple triggers during scrubbing - if (end_time !== Video.played_segments_segment_start && Math.abs(end_time - Video.played_segments_segment_start) > 1 ) { + if (endTime !== Video.playedSegmentsSegmentStart && Math.abs(endTime - Video.playedSegmentsSegmentStart) > 1 ) { // Don't run if called too closely to each other. - arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); - arr.push(Video.formatFloat(Video.played_segments_segment_start) + "[.]" + Video.formatFloat(end_time)); - Video.played_segments = arr.join("[,]"); - Video.played_segments_segment_end = end_time; - Video.played_segments_segment_start = null; + arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); + arr.push(Video.formatFloat(Video.playedSegmentsSegmentStart) + "[.]" + Video.formatFloat(endTime)); + Video.playedSegments = arr.join("[,]"); + Video.playedSegmentsSegmentEnd = endTime; + Video.playedSegmentsSegmentStart = null; } }; /** * Video.getArgsXAPIPaused * - * @param {type} current_time + * @param {type} currentTime * @returns {json object} */ - Video.getArgsXAPIPaused = function (current_time, duration) { + Video.getArgsXAPIPaused = function (currentTime, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.end_played_segment(resultExtTime); - Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress(current_time, duration); + var resultExtTime = Video.formatFloat(currentTime); + Video.endPlayedSegment(resultExtTime); + Video.playedSegmentsSegmentStart = resultExtTime; + var progress = Video.getProgress(currentTime, duration); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments } }, "context": { @@ -282,15 +282,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIPlayed * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getArgsXAPIPlayed = function (current_time) { + Video.getArgsXAPIPlayed = function (currentTime) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.played_segments_segment_start = resultExtTime; + var resultExtTime = Video.formatFloat(currentTime); + Video.playedSegmentsSegmentStart = resultExtTime; Video.seekStart = null; return { @@ -316,17 +316,17 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPISeeked * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getArgsXAPISeeked = function (current_time) { + Video.getArgsXAPISeeked = function (currentTime) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); Video.seekStart = resultExtTime; - Video.end_played_segment(Video.previousTime); - Video.played_segments_segment_start = Video.seekStart; + Video.endPlayedSegment(Video.previousTime); + Video.playedSegmentsSegmentStart = Video.seekStart; return { "result": { @@ -352,14 +352,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIVolumeChanged * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getArgsXAPIVolumeChanged = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat(current_time); + Video.volumeChangedAt = Video.formatFloat(currentTime); var isMuted = muted; var volumeChange; if (isMuted === true) { @@ -371,7 +371,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { return { "result" : { "extensions": { - "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + "https://w3id.org/xapi/video/extensions/time": Video.volumeChangedAt } }, "context": { @@ -392,15 +392,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPICompleted * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getArgsXAPICompleted = function (current_time, duration) { - var progress = Video.get_progress(current_time, duration); - var resultExtTime = Video.formatFloat(current_time); + Video.getArgsXAPICompleted = function (currentTime, duration) { + var progress = Video.getProgress(currentTime, duration); + var resultExtTime = Video.formatFloat(currentTime); var dateTime = new Date(); - Video.end_played_segment(resultExtTime); + Video.endPlayedSegment(resultExtTime); var timeStamp = dateTime.toISOString(); return { @@ -408,7 +408,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments } }, "context": { @@ -428,14 +428,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIFullScreen * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIFullScreen = function (current_time, width, height, fullscreen = false) { + Video.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; @@ -466,15 +466,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIInitialized * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIInitialized = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); var screenSize = screen.width + "x" + screen.height; var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; diff --git a/scripts/youtube.js b/scripts/youtube.js index 9a116842..c12b2fe5 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -136,7 +136,7 @@ H5P.VideoYouTube = (function ($) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); + var progress = H5P.Video.getProgress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { var arg = H5P.Video.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); } From 4924b66a68f97f256959e282e37cc1c3b6288da3 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:45:32 -1000 Subject: [PATCH 027/137] Set default value of getArgsXAPIInitialized param --- scripts/video.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index e5999227..56b6d109 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -470,7 +470,10 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { + // Set default value for quality. + quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); + // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); @@ -479,7 +482,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; var volume = Video.formatFloat(volume); - var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; From 8c66406cab16eb07fd9f86e4044fc34c10b0de5f Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 08:22:19 -1000 Subject: [PATCH 028/137] Added Seeked Functionailty Added code to handle seeked event and trigger an xAPI statement --- scripts/html5.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 1c01f86f..433fd2b6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -34,7 +34,19 @@ H5P.VideoHtml5 = (function ($) { */ var stateBeforeChangingQuality; var currentTimeBeforeChangingQuality; - + + /* + * tracks last time when paused used for completing seeked action + * also variables for tracking played segments + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; /** * Avoids firing the same event twice. * @private @@ -135,6 +147,22 @@ H5P.VideoHtml5 = (function ($) { video.appendChild(trackElement); } }); + + // common math functions + function formatFloat(number) { + if(number == null) + return null; + + return +(parseFloat(number).toFixed(3)); + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } /** * Helps registering events. @@ -157,9 +185,44 @@ H5P.VideoHtml5 = (function ($) { video.currentTime = options.startAt; delete options.startAt; } - + if( arg === H5P.Video.PLAYING ){ + played_segments_segment_start = video.currentTime; + } + if( arg === H5P.Video.PAUSED ){ + previousTime = video.currentTime; + } break; - + case 'seeking': + if ( lastState == arg ){ + return; + } + previousTime = formatFloat(video.currentTime); + return; //just need to store current time for seeked event + break; + case 'seeked': + if ( lastState == arg ){ + return; + } + seekStart = formatFloat(video.currentTime); + if ( Math.abs( seekStart - previousTime ) < 1 ) { + seekStart = null; + return; //don't send for seeks less than a second + } + dateTime = new Date(); + timeStamp = dateTime.toISOString(); + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + break; case 'loaded': isLoaded = true; @@ -589,6 +652,8 @@ H5P.VideoHtml5 = (function ($) { mapEvent('loadedmetadata', 'loaded'); mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); + mapEvent('seeking','seekeing', H5P.Video.PAUSED); + mapEvent('seeked', 'seeked', H5P.Video.PLAYING); if (!video.controls) { // Disable context menu(right click) to prevent controls. @@ -596,6 +661,12 @@ H5P.VideoHtml5 = (function ($) { event.preventDefault(); }, false); } + + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); // Display throbber when buffering/loading video. self.on('stateChange', function (event) { From 8ef5ba9956b198abbf76c0bc84b5f9ff2f83b04f Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 11:32:06 -1000 Subject: [PATCH 029/137] Added xAPI extending functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit added code for sending following events -seek -volume change -fullscreen -play -pause -Initiated I haven’t actually been able to get the fullscreen event to trigger --- scripts/html5.js | 250 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 19 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 433fd2b6..422f09e8 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -36,8 +36,7 @@ H5P.VideoHtml5 = (function ($) { var currentTimeBeforeChangingQuality; /* - * tracks last time when paused used for completing seeked action - * also variables for tracking played segments + * variables to track add extra xAPI statements for video * @type private */ var previousTime = null; @@ -47,6 +46,8 @@ H5P.VideoHtml5 = (function ($) { var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; /** * Avoids firing the same event twice. * @private @@ -155,6 +156,48 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / video.duration).toFixed(2); + return progress; + } function end_played_segment(end_time) { var arr; arr = (played_segments == "")? []:played_segments.split("[,]"); @@ -174,6 +217,14 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { + + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var extraArg = null; + var extraTrigger = null; + var resultExtTime = formatFloat(video.currentTime); + switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -190,26 +241,58 @@ H5P.VideoHtml5 = (function ($) { } if( arg === H5P.Video.PAUSED ){ previousTime = video.currentTime; + //put together extraArg for sending to xAPI statement + + if( ! video.seeking ) { + + end_played_segment(resultExtTime); + + var progress = get_progress(); + extraTrigger = "paused"; + extraArg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + } + } + //send extra trigger for giving progress on ended call to xAPI + if ( arg === H5P.Video.ENDED ){ + var length = video.duration; + if ( length > 0 ) { + var progress = get_progress(); + + if (progress >= 1 ){ + //send statement + extraTrigger = "completed"; + extraArg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + } + } } break; case 'seeking': - if ( lastState == arg ){ - return; - } previousTime = formatFloat(video.currentTime); return; //just need to store current time for seeked event break; case 'seeked': - if ( lastState == arg ){ - return; - } seekStart = formatFloat(video.currentTime); if ( Math.abs( seekStart - previousTime ) < 1 ) { seekStart = null; return; //don't send for seeks less than a second } - dateTime = new Date(); - timeStamp = dateTime.toISOString(); end_played_segment(previousTime); played_segments_segment_start = seekStart; //put together data for xAPI statement to be sent with event @@ -223,6 +306,60 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp } break; + case 'volumechange' : + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + break; + case 'play': + seekStart = null; + played_segments_segment_start = resultExtTime; + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + } + break; + case 'fullscreen': + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + + if ( state ){ + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + break; case 'loaded': isLoaded = true; @@ -244,8 +381,46 @@ H5P.VideoHtml5 = (function ($) { video.addEventListener('durationchange', andLoaded, false); return; } + + //send extra xAPI statement + extraTrigger = 'xAPIloaded'; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; + } + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + extraArg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + break; - case 'error': // Handle error and get message. arg = error(arguments[0], arguments[1]); @@ -270,6 +445,11 @@ H5P.VideoHtml5 = (function ($) { break; } self.trigger(h5p, arg); + + //make extra calls for events with needed values for xAPI statement + if( extraTrigger != null && extraArg != null ){ + self.trigger(extraTrigger, extraArg); + } }, false); }; @@ -652,8 +832,12 @@ H5P.VideoHtml5 = (function ($) { mapEvent('loadedmetadata', 'loaded'); mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); - mapEvent('seeking','seekeing', H5P.Video.PAUSED); + mapEvent('seeking','seeking', H5P.Video.PAUSED); mapEvent('seeked', 'seeked', H5P.Video.PLAYING); + mapEvent('volumechange', 'volumechange'); + mapEvent('play', 'play', H5P.Video.PLAYING); + //fuscreen events + mapEvent('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', 'fullscreen'); if (!video.controls) { // Disable context menu(right click) to prevent controls. @@ -661,13 +845,6 @@ H5P.VideoHtml5 = (function ($) { event.preventDefault(); }, false); } - - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); - }); - // Display throbber when buffering/loading video. self.on('stateChange', function (event) { var state = event.data; @@ -692,6 +869,41 @@ H5P.VideoHtml5 = (function ($) { } }); }); + + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }) + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); // Video controls are ready nextTick(function () { From e08d9c5a1815dffe6060792bdde48d81bd91e8c9 Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 6 Nov 2017 15:40:09 -1000 Subject: [PATCH 030/137] Added xAPI for Youtube Used Youtube Video API to implement: -Seek -Play -Pause -Initiated This is all that was possible via their API --- scripts/youtube.js | 285 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) diff --git a/scripts/youtube.js b/scripts/youtube.js index 56cd4748..be9d64df 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -16,6 +16,21 @@ H5P.VideoYouTube = (function ($) { var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; numInstances++; + /* + * variables to track add extra xAPI statements for video + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; + var seeking = false; + var lastState; var $wrapper = $('
'); var $placeholder = $('
', { @@ -76,6 +91,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); + self.trigger('xAPIloaded',getLoadedParams()) }, onApiChange: function () { if (loadCaptionsModule) { @@ -115,6 +131,54 @@ H5P.VideoYouTube = (function ($) { // End IE11 fix self.trigger('stateChange', state.data); + + //calls for xAPI events + if ( state.data == 1 ){ + if ( seeking ){ + //call from a seek we run seek command not play + self.trigger('seeked', getSeekParams()); + seeking = false; + } else { + //get and send play call + self.trigger('play', getPlayParams()); + } + } + if ( lastState == 2 && state.data == 3 ){ + seeking = true; + } + + if ( state.data == 2 ) { + //trigger call for xAPI paused but not seek + previousTime = player.getCurrentTime(); + if( ! seeking ) { + //this is a paused event + seeking = false; + // execute your code here for paused state + self.trigger('paused', getPausedParams()); + + } + } else if ( state.data == 0 ) { + //send xapi trigger if video progress indicates completed + var length = player.getDuration(); + if ( length > 0){ + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + if ( progress >= 1 ){ + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + self.trigger('completed',arg); + } + } + } + + lastState = state.data; } }, onPlaybackQualityChange: function (quality) { @@ -148,7 +212,228 @@ H5P.VideoYouTube = (function ($) { } }); }; + + //function used when putting together object to send for xAPI calls + function getWidthOrHeight ( returnType ){ + var quality = player.getPlaybackQuality(); + var width; + var height; + switch (quality) { + case 'small': + width: '320'; + height: '240'; + break; + case 'medium': + width: '640'; + height: '360'; + break; + case 'large': + width: '853'; + height: '480'; + break; + case 'hd720': + width: '640'; + height: '360'; + break; + case 'hd1080': + width: '1920'; + height: '1080'; + break; + case 'highres': + width: '1920'; + height: '1080'; + break; + } + + return (returnType.toLowerCase().trim()=='width')? width : height; + } + + function getLoadedParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + var quality = player.getPlaybackQuality(); + var height = getWidthOrHeight('height'); + var width = getWidthOrHeight('width'); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = player.getVolume(); + var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccLanguage; + if ( ccEnabled ) { + ccLanguage = player.getOptions('cc', 'track').languageCode; + } + var userAgent = navigator.userAgent; + var playbackRate = player.getPlaybackRate(); + + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + return arg; + } + function getPlayParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + played_segments_segment_start = resultExtTime; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + }; + return arg; + } + //paused Params called on pause statement used by xAPI event + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getSeekParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + + return arg; + } + // common math functions + function formatFloat(number) { + if(number == null) + return null; + return +(parseFloat(number).toFixed(3)); + } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }); + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. From 1c102c7b974e604cbe1cfe3519d04ce1ca36ebc4 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 10:00:34 -1000 Subject: [PATCH 031/137] xAPI on Youtube, html5, and videojs --- scripts/html5.js | 268 ++++++++++++++++++++---------------- scripts/video.js | 336 +++++++++++++++++++++++++++++++++++++++++++++ scripts/youtube.js | 2 +- 3 files changed, 484 insertions(+), 122 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 422f09e8..f63c7530 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -48,6 +48,7 @@ H5P.VideoHtml5 = (function ($) { var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; + var seeking = false; /** * Avoids firing the same event twice. * @private @@ -205,8 +206,145 @@ H5P.VideoHtml5 = (function ($) { played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; - } + } + function getLoadedParams() { + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; + } + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + } + }, + "timestamp": timeStamp + }; + } + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + end_played_segment(resultExtTime); + + var progress = get_progress(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + } + function getSeekedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = formatFloat(video.currentTime); + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + seeking = false; + //put together data for xAPI statement to be sent with event + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + }; + } + function getVolumeChangeParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + } + function getPlayParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = null; + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + } + } + function getFullScreenParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } /** * Helps registering events. * @@ -217,14 +355,8 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { - - //variables used in compiling xAPI results - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var extraArg = null; - var extraTrigger = null; - var resultExtTime = formatFloat(video.currentTime); - + var extraArg = null; + var extraTrigger = null; switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -245,20 +377,8 @@ H5P.VideoHtml5 = (function ($) { if( ! video.seeking ) { - end_played_segment(resultExtTime); - - var progress = get_progress(); extraTrigger = "paused"; - extraArg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "timestamp" : timeStamp - }; + extraArg = getPausedParams(); } } //send extra trigger for giving progress on ended call to xAPI @@ -288,77 +408,17 @@ H5P.VideoHtml5 = (function ($) { return; //just need to store current time for seeked event break; case 'seeked': - seekStart = formatFloat(video.currentTime); - if ( Math.abs( seekStart - previousTime ) < 1 ) { - seekStart = null; - return; //don't send for seeks less than a second - } - end_played_segment(previousTime); - played_segments_segment_start = seekStart; //put together data for xAPI statement to be sent with event - arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "timestamp" : timeStamp - } + arg = getSeekedParams(); break; case 'volumechange' : - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); - } - arg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; + arg = getVolumeChangeParams(); break; case 'play': - seekStart = null; - played_segments_segment_start = resultExtTime; - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "timestamp": timeStamp - } + arg = getPlayParams(); break; case 'fullscreen': - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - if ( state ){ - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } - }, - "timestamp" : timeStamp - }; - } + arg = getFullScreenParams(); break; case 'loaded': isLoaded = true; @@ -384,41 +444,7 @@ H5P.VideoHtml5 = (function ($) { //send extra xAPI statement extraTrigger = 'xAPIloaded'; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - var ccEnabled = false; - var ccLanguage; - - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ - ccEnabled = true; - ccLanguage = video.textTracks[i].language; - } - } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; - extraArg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume - - } - }, - "timestamp": timeStamp - }; + extraArg = getLoadedParams(); break; case 'error': diff --git a/scripts/video.js b/scripts/video.js index a1b5f8a5..0660767b 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -154,6 +154,342 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }, parameters.l10n); } } + + + //xapi video profile setup and calls for video.js + /* + * variables to track add extra xAPI statements for video + * @type private + */ + var previousTime = null; + var seekStart = null; + var dateTime; + var timeStamp; + var played_segments = []; + var played_segments_segment_start; + var played_segments_segment_end; + var volume_changed_on = null; + var volume_changed_at = 0; + var seeking = false; + var lastState; + var start = false; + var tracks = Video.textTracks(); + var skipPlayEvent = false; + var currentTime = 0; + var next_completion_check = 0; + var sent_completed = false; + + + function getLoadedParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + var state = Video.isFullscreen(); + var screenSize = screen.width + "x" + screen.height; + var quality = (Video.videoHeight() < Video.videoWidth())? Video.videoHeight():videoWidth(); + var height = Video.currentHeight(); + var width = Video.currentWidth(); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = formatFloat(video.volume()); + var ccEnabled = false; + var ccLanguage = "None Set"; + + //Captions/Subtitles values + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // If it is showing then CC is enabled and determine the language + if (track.mode === 'showing') { + ccEnabled = true; + ccLanguage = track.language; + } + } + var userAgent = navigator.userAgent; + var playbackRate = Video.playbackRate(); + + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume + + } + }, + "timestamp": timeStamp + }; + return arg; + } + function getPlayParams(){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + played_segments_segment_start = resultExtTime; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "timestamp": timeStamp + }; + return arg; + } + //paused Params called on pause statement used by xAPI event + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getSeekParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(Video.currentTime()); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "timestamp" : timeStamp + } + + return arg; + } + + function check_completion() { + if(sent_completed) + { + //console.log("completed statement already sent"); + return; + } + + var currentTimestamp = (new Date()).getTime(); + + if(currentTimestamp < next_completion_check) { + //console.log(new Date(next_completion_check) + " in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); + return; + } + var length = Video.duration(); + //console.log("length: " + length); + if(length <= 0) + return; + + var progress = get_progress(); + if(progress >= 1) { + sent_completed = true; + var resultExtTime = formatFloat(Video.currentTime()); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "timestamp" : timeStamp + }; + self.trigger('completed',arg); + } + var remaining_seconds = (1 - progress) * length; + //console.log("remaining_seconds: " + remaining_seconds); + next_completion_check = currentTimestamp + remaining_seconds.toFixed(3) * 1000; + //console.log("Progress: " + progress + " currentTimestamp: " + currentTimestamp + " next completion check in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); + } + function getVolumeChangeParams() { + volume_changed_at = Video.currentTime(); + var isMuted = Video.muted(); + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(Video.volume()); + } + var arg = { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + return arg; + } + function getFullScreenParams() { + var state = Video.isFullscreen(); + var resultExtTime = formatFloat(Video.currentTime()); + + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = Video.currentWidth() + "x" + Video.currentHeight(); + + arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + // common math functions + function formatFloat(number) { + if(number == null) + return null; + + return +(parseFloat(number).toFixed(3)); + } + //determine video progress + function get_progress() { + var arr, arr2; + + //get played segments array + arr = (played_segments == "")? []:played_segments.split("[,]"); + if(played_segments_segment_start != null){ + arr.push(played_segments_segment_start + "[.]" + formatFloat(Video.currentTime())); + } + + arr2 = []; + arr.forEach(function(v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { return a[0] - b[0];}); + + //normalize the segments + arr2.forEach(function(v,i) { + if(i > 0) { + if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. + //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); + arr2[i][0] = arr2[i-1][1]; + if(arr2[i][0] > arr2[i][1]) + arr2[i][1] = arr2[i][0]; + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function(v,i) { + if(v[1] > v[0]) + progress_length += v[1] - v[0]; + }); + + var progress = 1 * (progress_length / Video.duration()).toFixed(2); + return progress; + } + function end_played_segment(end_time) { + var arr; + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + ////////xAPI extension events for video///// + //catch for seeked event + self.on('seeked', function(event) { + var statement = event.data; + this.triggerXAPI('seeked', statement); + }); + //catch volumeChanged Event + self.on('volumechange', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + self.on('completed', function(event){ + var statement = event.data; + this.triggerXAPI('completed', statement); + }) + //catch fullscreen Event + self.on('fullscreen', function(event) { + var statement = event.data; + this.triggerXAPI('interacted', statement); + }); + //catch play Event + self.on('play', function(event) { + var statement = event.data; + this.triggerXAPI('played', statement); + }); + self.on('xAPIloaded', function(event){ + var statement = event.data; + this.triggerXAPI('initialized',statement); + }); + //catch play Event + self.on('paused', function(event) { + var statement = event.data; + this.triggerXAPI('paused', statement); + }); + + //event listeners + Video.on('play', function() { + if(start == false){ + start = true; + self.trigger('xAPIloaded',getLoadedParams()); + } + + if (skipPlayEvent !== true) { + self.trigger('play',getPlayParams()); + } else { + skipPlayEvent = false; + self.trigger('seeked',getSeekParams()); + } + }); + + Video.on('pause', function() { + if (this.seeking() === false ){ + self.trigger('paused', getPausedParams()); + } else { + skipPlayEvent = true; + } + }); + Video.on("timeupdate", function() { + previousTime = currentTime; + currentTime = formatFloat(Video.currentTime()); + check_completion(); + }); + Video.on("volumechange",function() { + self.trigger('volumechange',getVolumeChangeParams()); + }); + Video.on("fullscreenchange",function(){ + self.trigger('fullscreen',getFullScreenParams()); + }); + } // Extends the event dispatcher diff --git a/scripts/youtube.js b/scripts/youtube.js index be9d64df..8c4a151a 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -91,7 +91,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); - self.trigger('xAPIloaded',getLoadedParams()) + self.trigger('xAPIloaded',getLoadedParams()); }, onApiChange: function () { if (loadCaptionsModule) { From 2b5ed92e68bb5eb22e48a058212502995947af0f Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 13:45:29 -1000 Subject: [PATCH 032/137] changes to get Seek working correctly --- scripts/html5.js | 35 ++++++++++++++++++++++++----------- scripts/video.js | 5 ++--- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index f63c7530..ddd3f2f6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -39,7 +39,7 @@ H5P.VideoHtml5 = (function ($) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -275,7 +275,7 @@ H5P.VideoHtml5 = (function ($) { played_segments_segment_start = seekStart; seeking = false; //put together data for xAPI statement to be sent with event - return { + var arg = { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -284,6 +284,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; + return arg; } function getVolumeChangeParams() { var dateTime = new Date(); @@ -369,14 +370,12 @@ H5P.VideoHtml5 = (function ($) { delete options.startAt; } if( arg === H5P.Video.PLAYING ){ - played_segments_segment_start = video.currentTime; + previousTime = video.currentTime; } if( arg === H5P.Video.PAUSED ){ - previousTime = video.currentTime; //put together extraArg for sending to xAPI statement if( ! video.seeking ) { - extraTrigger = "paused"; extraArg = getPausedParams(); } @@ -386,6 +385,7 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if ( length > 0 ) { var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); if (progress >= 1 ){ //send statement @@ -403,19 +403,32 @@ H5P.VideoHtml5 = (function ($) { } } break; + case 'timeupdate' : + if((Math.abs( previousTime - video.currentTime) > 2) ){ + h5p = 'seeked'; + arg = getSeekedParams(); + played_segments_segment_start = video.currentTime; + } else { + previousTime = video.currentTime; + } + break; + case 'seeked': + return; //seek is tracked differently based on time difference in timeupdate + break; case 'seeking': - previousTime = formatFloat(video.currentTime); return; //just need to store current time for seeked event break; - case 'seeked': - //put together data for xAPI statement to be sent with event - arg = getSeekedParams(); - break; case 'volumechange' : arg = getVolumeChangeParams(); break; case 'play': + if ( Math.abs(previousTime - video.currentTime) > 2 ){ + h5p = 'seeked'; + arg = getSeekedParams(); + played_segments_segment_start = video.currentTime; + } else { arg = getPlayParams(); + } break; case 'fullscreen': arg = getFullScreenParams(); @@ -859,7 +872,7 @@ H5P.VideoHtml5 = (function ($) { mapEvent('error', 'error'); mapEvent('ratechange', 'playbackRateChange'); mapEvent('seeking','seeking', H5P.Video.PAUSED); - mapEvent('seeked', 'seeked', H5P.Video.PLAYING); + mapEvent('timeupdate', 'timeupdate', H5P.Video.PLAYING); mapEvent('volumechange', 'volumechange'); mapEvent('play', 'play', H5P.Video.PLAYING); //fuscreen events diff --git a/scripts/video.js b/scripts/video.js index 0660767b..2828dac8 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -161,7 +161,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -248,7 +248,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(Video.currentTime()); - previousTime = resultExtTime; end_played_segment(resultExtTime); var progress = get_progress(); var arg = { @@ -465,7 +464,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { if (skipPlayEvent !== true) { self.trigger('play',getPlayParams()); - } else { + } else if ( Math.abs(previousTime - Video.currentTime()) > 1 ){ skipPlayEvent = false; self.trigger('seeked',getSeekParams()); } From 9e8aa9b1d35d8480f9f759673c28e7c7e0d766d8 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 7 Nov 2017 14:12:51 -1000 Subject: [PATCH 033/137] Fixed seeked on Youtube player --- scripts/youtube.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index 8c4a151a..77d063f6 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -20,7 +20,7 @@ H5P.VideoYouTube = (function ($) { * variables to track add extra xAPI statements for video * @type private */ - var previousTime = null; + var previousTime = 0; var seekStart = null; var dateTime; var timeStamp; @@ -134,7 +134,7 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( seeking ){ + if ( Math.abs( previousTime - player.getCurrentTime() ) > 1 ){ //call from a seek we run seek command not play self.trigger('seeked', getSeekParams()); seeking = false; @@ -143,20 +143,11 @@ H5P.VideoYouTube = (function ($) { self.trigger('play', getPlayParams()); } } - if ( lastState == 2 && state.data == 3 ){ - seeking = true; - } - if ( state.data == 2 ) { - //trigger call for xAPI paused but not seek - previousTime = player.getCurrentTime(); - if( ! seeking ) { - //this is a paused event - seeking = false; - // execute your code here for paused state - self.trigger('paused', getPausedParams()); - - } + //this is a paused event + seeking = false; + // execute your code here for paused state + self.trigger('paused', getPausedParams()); } else if ( state.data == 0 ) { //send xapi trigger if video progress indicates completed var length = player.getDuration(); @@ -213,6 +204,8 @@ H5P.VideoYouTube = (function ($) { }); }; + //Youtube player has no timeupdate event so need to use setInterval + setInterval(function(){ previousTime = player.getCurrentTime(); }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); From 9f22a59c0ffc4648379a5cb8534316a24ae21e8b Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:02:13 -1000 Subject: [PATCH 034/137] Got Context added to xAPI statements --- scripts/html5.js | 125 +++++++++++++++++++++++++++++++++++++-------- scripts/video.js | 94 +++++++++++++++++++++++++++++++--- scripts/youtube.js | 60 +++++++++++++++++++++- 3 files changed, 250 insertions(+), 29 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index ddd3f2f6..7137846e 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,6 +49,7 @@ H5P.VideoHtml5 = (function ($) { var volume_changed_on = null; var volume_changed_at = 0; var seeking = false; + var sessionID = guid(); /** * Avoids firing the same event twice. * @private @@ -157,6 +158,15 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } //determine video progress function get_progress() { var arr, arr2; @@ -232,22 +242,30 @@ H5P.VideoHtml5 = (function ($) { var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; var userAgent = navigator.userAgent; return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; + } + }, + "timestamp": timeStamp + }; } function getPausedParams() { var dateTime = new Date(); @@ -264,6 +282,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; } @@ -282,6 +313,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -300,8 +344,21 @@ H5P.VideoHtml5 = (function ($) { return { "result" : { "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange + "https://w3id.org/xapi/video/extensions/time": volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } }, "timestamp" : timeStamp @@ -319,6 +376,19 @@ H5P.VideoHtml5 = (function ($) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp } } @@ -337,10 +407,23 @@ H5P.VideoHtml5 = (function ($) { return { "result": { "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } }, "timestamp" : timeStamp diff --git a/scripts/video.js b/scripts/video.js index 2828dac8..0fd9826b 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -178,6 +178,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var currentTime = 0; var next_completion_check = 0; var sent_completed = false; + var sessionID = guid(); function getLoadedParams(){ @@ -208,7 +209,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var playbackRate = Video.playbackRate(); var arg = { - "result" : { + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, "extensions": { "https://w3id.org/xapi/video/extensions/full-screen": state, "https://w3id.org/xapi/video/extensions/screen-size": screenSize, @@ -239,6 +247,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp }; return arg; @@ -258,6 +279,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -277,6 +311,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp } @@ -334,7 +381,20 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "result" : { "extensions": { "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } }, "timestamp" : timeStamp @@ -354,10 +414,23 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { arg = { "result": { "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } }, "timestamp" : timeStamp @@ -370,6 +443,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { return +(parseFloat(number).toFixed(3)); } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } //determine video progress function get_progress() { var arr, arr2; diff --git a/scripts/youtube.js b/scripts/youtube.js index 77d063f6..ef26feeb 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -31,6 +31,7 @@ H5P.VideoYouTube = (function ($) { var volume_changed_at = 0; var seeking = false; var lastState; + var sessionID = guid(); var $wrapper = $('
'); var $placeholder = $('
', { @@ -261,7 +262,14 @@ H5P.VideoYouTube = (function ($) { var playbackRate = player.getPlaybackRate(); var arg = { - "result" : { + "context" : { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, "extensions": { "https://w3id.org/xapi/video/extensions/full-screen": state, "https://w3id.org/xapi/video/extensions/screen-size": screenSize, @@ -292,6 +300,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/time": resultExtTime, } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp": timeStamp }; return arg; @@ -312,6 +333,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp }; return arg; @@ -331,6 +365,19 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/time-to": seekStart } }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, "timestamp" : timeStamp } @@ -392,7 +439,16 @@ H5P.VideoYouTube = (function ($) { played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; - } + } + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } ////////xAPI extension events for video///// //catch for seeked event self.on('seeked', function(event) { From 828611eaeb436149b7c79f8e904733954297fee3 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:20:31 -1000 Subject: [PATCH 035/137] =?UTF-8?q?Adjusted=20Youtube=E2=80=99s=20Seek=20c?= =?UTF-8?q?apabilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/youtube.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index ef26feeb..63d0ca97 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -32,6 +32,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var lastState; var sessionID = guid(); + var currentTime = 0; var $wrapper = $('
'); var $placeholder = $('
', { @@ -135,7 +136,8 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( Math.abs( previousTime - player.getCurrentTime() ) > 1 ){ + + if ( (Math.abs( previousTime - player.getCurrentTime() ) > 1) || seeking ){ //call from a seek we run seek command not play self.trigger('seeked', getSeekParams()); seeking = false; @@ -146,7 +148,6 @@ H5P.VideoYouTube = (function ($) { } if ( state.data == 2 ) { //this is a paused event - seeking = false; // execute your code here for paused state self.trigger('paused', getPausedParams()); } else if ( state.data == 0 ) { @@ -206,7 +207,15 @@ H5P.VideoYouTube = (function ($) { }; //Youtube player has no timeupdate event so need to use setInterval - setInterval(function(){ previousTime = player.getCurrentTime(); }, 1000); + setInterval(function(){ + if( typeof player !== undefined ){ + previousTime = currentTime; + currentTime = formatFloat(player.getCurrentTime()); + if( Math.abs(previousTime - currentTime) > 1){ + seeking = true; + } + } + }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); From c16cc628d8f61e8f6516225326300c971c690fb5 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:24:12 -1000 Subject: [PATCH 036/137] Removed unused variables from Youtube --- scripts/youtube.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index 63d0ca97..90ff5ae1 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -22,15 +22,12 @@ H5P.VideoYouTube = (function ($) { */ var previousTime = 0; var seekStart = null; - var dateTime; - var timeStamp; var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; var seeking = false; - var lastState; var sessionID = guid(); var currentTime = 0; @@ -170,8 +167,6 @@ H5P.VideoYouTube = (function ($) { } } } - - lastState = state.data; } }, onPlaybackQualityChange: function (quality) { From 1988a2d0188669af6a217a71912e64e43bda253a Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:27:54 -1000 Subject: [PATCH 037/137] Ensured Session ID was in every call for each video player --- scripts/video.js | 3 ++- scripts/youtube.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 0fd9826b..50f0797b 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -226,7 +226,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID } }, diff --git a/scripts/youtube.js b/scripts/youtube.js index 90ff5ae1..fe511c54 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -283,8 +283,8 @@ H5P.VideoYouTube = (function ($) { "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume - + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID } }, "timestamp": timeStamp From 6b57fc4db67aa9a1c38fdc81d98a6eed5e18ba84 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 08:51:49 -1000 Subject: [PATCH 038/137] Ensure xAPI played event sent appropriately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Play wasn’t firing with play button was hit all the time so I added functionality in the state change event to evaluate if play event needed to be triggered. --- scripts/html5.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 7137846e..26a583ff 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -41,8 +41,6 @@ H5P.VideoHtml5 = (function ($) { */ var previousTime = 0; var seekStart = null; - var dateTime; - var timeStamp; var played_segments = []; var played_segments_segment_start; var played_segments_segment_end; @@ -50,6 +48,7 @@ H5P.VideoHtml5 = (function ($) { var volume_changed_at = 0; var seeking = false; var sessionID = guid(); + var lastSend = null; /** * Avoids firing the same event twice. * @private @@ -454,6 +453,11 @@ H5P.VideoHtml5 = (function ($) { } if( arg === H5P.Video.PLAYING ){ previousTime = video.currentTime; + if( lastSend != 'play' ){ + extraTrigger = 'play'; + extraArg = getPlayParams(); + lastSend = 'play'; + } } if( arg === H5P.Video.PAUSED ){ //put together extraArg for sending to xAPI statement @@ -461,6 +465,7 @@ H5P.VideoHtml5 = (function ($) { if( ! video.seeking ) { extraTrigger = "paused"; extraArg = getPausedParams(); + lastSend = 'paused'; } } //send extra trigger for giving progress on ended call to xAPI @@ -471,6 +476,8 @@ H5P.VideoHtml5 = (function ($) { var resultExtTime = formatFloat(video.currentTime); if (progress >= 1 ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); //send statement extraTrigger = "completed"; extraArg = { @@ -482,6 +489,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; + lastSend = 'completed'; } } } @@ -491,6 +499,7 @@ H5P.VideoHtml5 = (function ($) { h5p = 'seeked'; arg = getSeekedParams(); played_segments_segment_start = video.currentTime; + lastSend = 'seeked'; } else { previousTime = video.currentTime; } @@ -503,18 +512,22 @@ H5P.VideoHtml5 = (function ($) { break; case 'volumechange' : arg = getVolumeChangeParams(); + lastSend = 'volumechange'; break; case 'play': if ( Math.abs(previousTime - video.currentTime) > 2 ){ h5p = 'seeked'; arg = getSeekedParams(); played_segments_segment_start = video.currentTime; + lastSend = 'seeked'; } else { arg = getPlayParams(); + lastSend = h5p; } break; case 'fullscreen': arg = getFullScreenParams(); + lastSend = h5p; break; case 'loaded': isLoaded = true; @@ -541,6 +554,7 @@ H5P.VideoHtml5 = (function ($) { //send extra xAPI statement extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); + lastSend = 'xAPIloaded'; break; case 'error': From 72454583d9ff6d7308b86c98cfab3a75aed66277 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 15:31:56 -1000 Subject: [PATCH 039/137] Moved Competion Prams into a function Moved completion params into a function --- scripts/html5.js | 61 ++++++++++++++++++++++++++++++---------------- scripts/youtube.js | 57 +++++++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 26a583ff..3db6985b 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -42,7 +42,7 @@ H5P.VideoHtml5 = (function ($) { var previousTime = 0; var seekStart = null; var played_segments = []; - var played_segments_segment_start; + var played_segments_segment_start = 0; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; @@ -210,12 +210,15 @@ H5P.VideoHtml5 = (function ($) { } function end_played_segment(end_time) { var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } + if (end_time !== played_segments_segment_start){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } +} function getLoadedParams() { //variables used in compiling xAPI results var dateTime = new Date(); @@ -271,7 +274,7 @@ H5P.VideoHtml5 = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); - + played_segments_segment_start == resultExtTime; var progress = get_progress(); return { "result": { @@ -428,6 +431,34 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp }; } + function getCompletedParams() { + var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, + "timestamp" : timeStamp + }; + } /** * Helps registering events. * @@ -473,22 +504,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if ( length > 0 ) { var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - if (progress >= 1 ){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); //send statement extraTrigger = "completed"; - extraArg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; + extraArg = getCompletedParams(); lastSend = 'completed'; } } diff --git a/scripts/youtube.js b/scripts/youtube.js index fe511c54..d6628054 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -23,7 +23,7 @@ H5P.VideoYouTube = (function ($) { var previousTime = 0; var seekStart = null; var played_segments = []; - var played_segments_segment_start; + var played_segments_segment_start =0; var played_segments_segment_end; var volume_changed_on = null; var volume_changed_at = 0; @@ -152,18 +152,8 @@ H5P.VideoYouTube = (function ($) { var length = player.getDuration(); if ( length > 0){ var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); if ( progress >= 1 ){ - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; - self.trigger('completed',arg); + var arg = getCompletedParams(); } } } @@ -297,7 +287,6 @@ H5P.VideoYouTube = (function ($) { var resultExtTime = formatFloat(player.getCurrentTime()); played_segments_segment_start = resultExtTime; seekStart = null; - played_segments_segment_start = resultExtTime; var arg = { "result": { "extensions": { @@ -328,6 +317,7 @@ H5P.VideoYouTube = (function ($) { var resultExtTime = formatFloat(player.getCurrentTime()); previousTime = resultExtTime; end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; var progress = get_progress(); var arg = { "result": { @@ -387,6 +377,34 @@ H5P.VideoYouTube = (function ($) { return arg; } + function getCompletedParams() { + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } + }, + "timestamp" : timeStamp + }; + } // common math functions function formatFloat(number) { if(number == null) @@ -438,11 +456,14 @@ H5P.VideoYouTube = (function ($) { } function end_played_segment(end_time) { var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; + if (end_time !== played_segments_segment_start){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(played_segments_segment_start + "[.]" + end_time); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } } function guid() { function s4() { From 25ede9a4ad627ffa530bbefdf2e43e627d096fc4 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 8 Nov 2017 15:34:48 -1000 Subject: [PATCH 040/137] Reverted Changes on Video JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Haven’t been able to get this code to call for testing in any video upload with Wordpress so I am not adding functionality for this. --- scripts/video.js | 418 ----------------------------------------------- 1 file changed, 418 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 50f0797b..a1b5f8a5 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -154,424 +154,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }, parameters.l10n); } } - - - //xapi video profile setup and calls for video.js - /* - * variables to track add extra xAPI statements for video - * @type private - */ - var previousTime = 0; - var seekStart = null; - var dateTime; - var timeStamp; - var played_segments = []; - var played_segments_segment_start; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var lastState; - var start = false; - var tracks = Video.textTracks(); - var skipPlayEvent = false; - var currentTime = 0; - var next_completion_check = 0; - var sent_completed = false; - var sessionID = guid(); - - - function getLoadedParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - var state = Video.isFullscreen(); - var screenSize = screen.width + "x" + screen.height; - var quality = (Video.videoHeight() < Video.videoWidth())? Video.videoHeight():videoWidth(); - var height = Video.currentHeight(); - var width = Video.currentWidth(); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; - var volume = formatFloat(video.volume()); - var ccEnabled = false; - var ccLanguage = "None Set"; - - //Captions/Subtitles values - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - - // If it is showing then CC is enabled and determine the language - if (track.mode === 'showing') { - ccEnabled = true; - ccLanguage = track.language; - } - } - var userAgent = navigator.userAgent; - var playbackRate = Video.playbackRate(); - - var arg = { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; - } - function getPlayParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - played_segments_segment_start = resultExtTime; - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; - } - //paused Params called on pause statement used by xAPI event - function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - end_played_segment(resultExtTime); - var progress = get_progress(); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; - return arg; - } - function getSeekParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(Video.currentTime()); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - - return arg; - } - - function check_completion() { - if(sent_completed) - { - //console.log("completed statement already sent"); - return; - } - - var currentTimestamp = (new Date()).getTime(); - - if(currentTimestamp < next_completion_check) { - //console.log(new Date(next_completion_check) + " in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); - return; - } - var length = Video.duration(); - //console.log("length: " + length); - if(length <= 0) - return; - - var progress = get_progress(); - if(progress >= 1) { - sent_completed = true; - var resultExtTime = formatFloat(Video.currentTime()); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress - } - }, - "timestamp" : timeStamp - }; - self.trigger('completed',arg); - } - var remaining_seconds = (1 - progress) * length; - //console.log("remaining_seconds: " + remaining_seconds); - next_completion_check = currentTimestamp + remaining_seconds.toFixed(3) * 1000; - //console.log("Progress: " + progress + " currentTimestamp: " + currentTimestamp + " next completion check in " + (next_completion_check - currentTimestamp)/1000 + " seconds"); - } - function getVolumeChangeParams() { - volume_changed_at = Video.currentTime(); - var isMuted = Video.muted(); - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(Video.volume()); - } - var arg = { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - - } - }, - "timestamp" : timeStamp - }; - return arg; - } - function getFullScreenParams() { - var state = Video.isFullscreen(); - var resultExtTime = formatFloat(Video.currentTime()); - - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = Video.currentWidth() + "x" + Video.currentHeight(); - - arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - - } - }, - "timestamp" : timeStamp - }; - } - // common math functions - function formatFloat(number) { - if(number == null) - return null; - - return +(parseFloat(number).toFixed(3)); - } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - //determine video progress - function get_progress() { - var arr, arr2; - - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(Video.currentTime())); - } - - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / Video.duration()).toFixed(2); - return progress; - } - function end_played_segment(end_time) { - var arr; - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - ////////xAPI extension events for video///// - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); - }); - //catch volumeChanged Event - self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); - }); - self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); - }) - //catch fullscreen Event - self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); - }); - //catch play Event - self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); - }); - self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); - }); - //catch play Event - self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); - }); - - //event listeners - Video.on('play', function() { - if(start == false){ - start = true; - self.trigger('xAPIloaded',getLoadedParams()); - } - - if (skipPlayEvent !== true) { - self.trigger('play',getPlayParams()); - } else if ( Math.abs(previousTime - Video.currentTime()) > 1 ){ - skipPlayEvent = false; - self.trigger('seeked',getSeekParams()); - } - }); - - Video.on('pause', function() { - if (this.seeking() === false ){ - self.trigger('paused', getPausedParams()); - } else { - skipPlayEvent = true; - } - }); - Video.on("timeupdate", function() { - previousTime = currentTime; - currentTime = formatFloat(Video.currentTime()); - check_completion(); - }); - Video.on("volumechange",function() { - self.trigger('volumechange',getVolumeChangeParams()); - }); - Video.on("fullscreenchange",function(){ - self.trigger('fullscreen',getFullScreenParams()); - }); - } // Extends the event dispatcher From 778330f8fd6450347ce325f94bd513db8d0dd333 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:44:14 -1000 Subject: [PATCH 041/137] Modifying for seek Fine tuned seek event more. It is working very well on HTML5 still need some work on Youtube for the events when a user is scrubbing. --- scripts/html5.js | 116 ++++++++++++++++++++++++--------------------- scripts/youtube.js | 105 +++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 104 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 3db6985b..30802236 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,6 +49,8 @@ H5P.VideoHtml5 = (function ($) { var seeking = false; var sessionID = guid(); var lastSend = null; + var lastSeekedEvent = { seek_from: null, seek_to: null }; + var seekedTo = 0; /** * Avoids firing the same event twice. * @private @@ -208,9 +210,11 @@ H5P.VideoHtml5 = (function ($) { var progress = 1 * (progress_length / video.duration).toFixed(2); return progress; } + function end_played_segment(end_time) { var arr; - if (end_time !== played_segments_segment_start){ + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); arr.push(played_segments_segment_start + "[.]" + end_time); @@ -218,7 +222,7 @@ H5P.VideoHtml5 = (function ($) { played_segments_segment_end = end_time; played_segments_segment_start = null; } -} + } function getLoadedParams() { //variables used in compiling xAPI results var dateTime = new Date(); @@ -274,7 +278,7 @@ H5P.VideoHtml5 = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); - played_segments_segment_start == resultExtTime; + played_segments_segment_start = resultExtTime; var progress = get_progress(); return { "result": { @@ -300,37 +304,38 @@ H5P.VideoHtml5 = (function ($) { "timestamp" : timeStamp }; } - function getSeekedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = formatFloat(video.currentTime); - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - seeking = false; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] + function getSeekedParams( time ) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - return arg; + } + }, + "timestamp" : timeStamp + } + return arg; } function getVolumeChangeParams() { var dateTime = new Date(); @@ -435,12 +440,14 @@ H5P.VideoHtml5 = (function ($) { var progress = get_progress(); var resultExtTime = formatFloat(video.currentTime); var dateTime = new Date(); + end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, "context": { @@ -483,17 +490,23 @@ H5P.VideoHtml5 = (function ($) { delete options.startAt; } if( arg === H5P.Video.PLAYING ){ - previousTime = video.currentTime; - if( lastSend != 'play' ){ - extraTrigger = 'play'; + if ( seeking === true ){ + extraArg = getSeekedParams( seekedTo ); + extraTrigger = 'seeked'; + lastSend = 'seeked'; + seeking = false; + seeking = false; + } else if ( lastSend !== 'play' ){ extraArg = getPlayParams(); + extraTrigger = 'play'; lastSend = 'play'; } + } if( arg === H5P.Video.PAUSED ){ //put together extraArg for sending to xAPI statement - if( ! video.seeking ) { + if( ! video.seeking && seeking === false) { extraTrigger = "paused"; extraArg = getPausedParams(); lastSend = 'paused'; @@ -513,16 +526,6 @@ H5P.VideoHtml5 = (function ($) { } } break; - case 'timeupdate' : - if((Math.abs( previousTime - video.currentTime) > 2) ){ - h5p = 'seeked'; - arg = getSeekedParams(); - played_segments_segment_start = video.currentTime; - lastSend = 'seeked'; - } else { - previousTime = video.currentTime; - } - break; case 'seeked': return; //seek is tracked differently based on time difference in timeupdate break; @@ -534,14 +537,15 @@ H5P.VideoHtml5 = (function ($) { lastSend = 'volumechange'; break; case 'play': - if ( Math.abs(previousTime - video.currentTime) > 2 ){ - h5p = 'seeked'; - arg = getSeekedParams(); - played_segments_segment_start = video.currentTime; - lastSend = 'seeked'; - } else { + if ( seeking === false && lastSend != h5p ){ + arg = getPlayParams(); lastSend = h5p; + } else { + arg = getSeekedParams( seekedTo ); + lastSend = 'seeked'; + seeking = false; + h5p = 'seeked'; } break; case 'fullscreen': @@ -816,8 +820,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - + if(seeking === false) { + previousTime = video.currentTime; + } video.currentTime = time; + seeking = true; + seekedTo = time; }; /** diff --git a/scripts/youtube.js b/scripts/youtube.js index d6628054..d154c0e6 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -30,6 +30,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var sessionID = guid(); var currentTime = 0; + var lastSeekedEvent = { seek_from: null, seek_to: null }; var $wrapper = $('
'); var $placeholder = $('
', { @@ -134,19 +135,18 @@ H5P.VideoYouTube = (function ($) { //calls for xAPI events if ( state.data == 1 ){ - if ( (Math.abs( previousTime - player.getCurrentTime() ) > 1) || seeking ){ - //call from a seek we run seek command not play - self.trigger('seeked', getSeekParams()); - seeking = false; - } else { - //get and send play call - self.trigger('play', getPlayParams()); - } + //get and send play call when not seeking + if ( seeking === false ) { + self.trigger('play', getPlayParams()); + } + seeking = false; } if ( state.data == 2 ) { //this is a paused event // execute your code here for paused state + if(seeking === false){ self.trigger('paused', getPausedParams()); + } } else if ( state.data == 0 ) { //send xapi trigger if video progress indicates completed var length = player.getDuration(); @@ -190,17 +190,6 @@ H5P.VideoYouTube = (function ($) { } }); }; - - //Youtube player has no timeupdate event so need to use setInterval - setInterval(function(){ - if( typeof player !== undefined ){ - previousTime = currentTime; - currentTime = formatFloat(player.getCurrentTime()); - if( Math.abs(previousTime - currentTime) > 1){ - seeking = true; - } - } - }, 1000); //function used when putting together object to send for xAPI calls function getWidthOrHeight ( returnType ){ var quality = player.getPlaybackQuality(); @@ -344,49 +333,61 @@ H5P.VideoYouTube = (function ($) { }; return arg; } - function getSeekParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] + function sendSeeked( time ) { + + //only trigger if first time running or if the last event is different + //this prevents multiple runs of seek when user is scrubbing + if ( (lastSeekedEvent.seek_from == null && lastSeekedEvent.seek_to == null) || + (( Math.abs(time - lastSeekedEvent.seek_to) > 1 && Math.abs(previousTime - lastSeekedEvent.seek_from) > 1) && + Math.abs(previousTime - time) > 1) ){ + lastSeekedEvent.seek_from = previousTime; + lastSeekedEvent.seek_to = time; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [ + { + "id": "https://w3id.org/xapi/video" + } + ] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + + } }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + "timestamp" : timeStamp + } - } - }, - "timestamp" : timeStamp + self.trigger('seeked',arg); } - return arg; } function getCompletedParams() { var progress = get_progress(); var resultExtTime = formatFloat(player.getCurrentTime()); var dateTime = new Date(); + end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments } }, "context": { @@ -456,7 +457,8 @@ H5P.VideoYouTube = (function ($) { } function end_played_segment(end_time) { var arr; - if (end_time !== played_segments_segment_start){ + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); arr.push(played_segments_segment_start + "[.]" + end_time); @@ -623,8 +625,11 @@ H5P.VideoYouTube = (function ($) { if (!player || !player.seekTo) { return; } - + + previousTime = player.getCurrentTime(); player.seekTo(time, true); + sendSeeked( time ); + seeking = true; }; /** From 7561c346549c3ca8f5236a5af7b195609968e0f9 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:51:39 -1000 Subject: [PATCH 042/137] Modified Seek Event for YouTube --- scripts/html5.js | 1 - scripts/youtube.js | 27 ++++++++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 30802236..f425d5eb 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -49,7 +49,6 @@ H5P.VideoHtml5 = (function ($) { var seeking = false; var sessionID = guid(); var lastSend = null; - var lastSeekedEvent = { seek_from: null, seek_to: null }; var seekedTo = 0; /** * Avoids firing the same event twice. diff --git a/scripts/youtube.js b/scripts/youtube.js index d154c0e6..dc61022e 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -30,7 +30,7 @@ H5P.VideoYouTube = (function ($) { var seeking = false; var sessionID = guid(); var currentTime = 0; - var lastSeekedEvent = { seek_from: null, seek_to: null }; + var seekedTo = 0; var $wrapper = $('
'); var $placeholder = $('
', { @@ -138,8 +138,10 @@ H5P.VideoYouTube = (function ($) { //get and send play call when not seeking if ( seeking === false ) { self.trigger('play', getPlayParams()); + } else { + self.trigger('seeked', getSeekedParams( seekedTo )); + seeking = false; } - seeking = false; } if ( state.data == 2 ) { //this is a paused event @@ -333,15 +335,7 @@ H5P.VideoYouTube = (function ($) { }; return arg; } - function sendSeeked( time ) { - - //only trigger if first time running or if the last event is different - //this prevents multiple runs of seek when user is scrubbing - if ( (lastSeekedEvent.seek_from == null && lastSeekedEvent.seek_to == null) || - (( Math.abs(time - lastSeekedEvent.seek_to) > 1 && Math.abs(previousTime - lastSeekedEvent.seek_from) > 1) && - Math.abs(previousTime - time) > 1) ){ - lastSeekedEvent.seek_from = previousTime; - lastSeekedEvent.seek_to = time; + function getSeekedParams( time ) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat( time ); @@ -371,10 +365,7 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp } - - self.trigger('seeked',arg); - } - + return arg; } function getCompletedParams() { var progress = get_progress(); @@ -626,9 +617,11 @@ H5P.VideoYouTube = (function ($) { return; } - previousTime = player.getCurrentTime(); + if( seeking === false ){ + previousTime = player.getCurrentTime(); + } player.seekTo(time, true); - sendSeeked( time ); + seekedTo = time; seeking = true; }; From a6404401d8830342f0df94e5859cc51f7c765556 Mon Sep 17 00:00:00 2001 From: kirkj Date: Tue, 14 Nov 2017 13:55:11 -1000 Subject: [PATCH 043/137] Format Float on played segments Made sure all floats in played segments were formatted to correct precision --- scripts/html5.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index f425d5eb..726d4304 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -216,7 +216,7 @@ H5P.VideoHtml5 = (function ($) { if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; diff --git a/scripts/youtube.js b/scripts/youtube.js index dc61022e..39809717 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -452,7 +452,7 @@ H5P.VideoYouTube = (function ($) { if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ //don't run if called too closely to each other arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(played_segments_segment_start + "[.]" + end_time); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; From dd3cefc581ecd73e4300c349614ac743ebcf60f8 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 16 Nov 2017 14:38:57 -1000 Subject: [PATCH 044/137] Fix whitespace maintainer uses 2 spaces for indentation, make sure to follow this style. --- scripts/html5.js | 740 ++++++++++++++++++++++----------------------- scripts/youtube.js | 594 ++++++++++++++++++------------------ 2 files changed, 651 insertions(+), 683 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 726d4304..269445f9 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -34,10 +34,10 @@ H5P.VideoHtml5 = (function ($) { */ var stateBeforeChangingQuality; var currentTimeBeforeChangingQuality; - - /* - * variables to track add extra xAPI statements for video - * @type private + + /** + * Track xAPI statement data for video events. + * @private */ var previousTime = 0; var seekStart = null; @@ -50,6 +50,7 @@ H5P.VideoHtml5 = (function ($) { var sessionID = guid(); var lastSend = null; var seekedTo = 0; + /** * Avoids firing the same event twice. * @private @@ -150,321 +151,313 @@ H5P.VideoHtml5 = (function ($) { video.appendChild(trackElement); } }); - - // common math functions - function formatFloat(number) { - if(number == null) - return null; - return +(parseFloat(number).toFixed(3)); - } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); + // Format parameter as float (or null if invalid). + var formatFloat = function (number) { + if (number == null) { + return null; } - //determine video progress - function get_progress() { - var arr, arr2; + return +(parseFloat(number).toFixed(3)); + }; - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); - } + // Generate a random GUID string. + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / video.duration).toFixed(2); - return progress; - } - - function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; + // Calculate video progress. + var get_progress = function () { + var arr, arr2; + + // Get played segments array. + arr = played_segments == "" ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + // Calculate progress_length. + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; } - } + }); + + var progress = 1 * (progress_length / video.duration).toFixed(2); + + return progress; + }; + + function end_played_segment(end_time) { + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ + //don't run if called too closely to each other + arr = (played_segments == "")? []:played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + } + function getLoadedParams() { - //variables used in compiling xAPI results - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - var ccEnabled = false; - var ccLanguage; - - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ - ccEnabled = true; - ccLanguage = video.textTracks[i].language; - } + //variables used in compiling xAPI results + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + var ccEnabled = false; + var ccLanguage; + + for( var i = 0; i < video.textTracks.length; i++ ){ + if( video.textTracks[i].mode === 'showing' ){ + ccEnabled = true; + ccLanguage = video.textTracks[i].language; } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; - return { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; + } + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; } - function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; + function getPausedParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; + var progress = get_progress(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } + function getSeekedParams( time ) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + + //put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + } + return arg; } - function getVolumeChangeParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if ( isMuted === true ){ - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); - } - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; + function getVolumeChangeParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + volume_changed_at = video.currentTime; + var isMuted = video.muted; + var volumeChange; + if ( isMuted === true ){ + volumeChange = 0; + } else { + volumeChange = formatFloat(video.volume); + } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; } - function getPlayParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = null; - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - } + function getPlayParams() { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + seekStart = null; + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + } } + function getFullScreenParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - //sed xapi statement - var screenSize = screen.width + "x" + screen.height; - - //playback size - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - } - }, - "timestamp" : timeStamp - }; + //sed xapi statement + var screenSize = screen.width + "x" + screen.height; + + //playback size + var playbackSize = video.videoWidth + "x" + video.videoHeight; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; } + function getCompletedParams() { - var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; + var progress = get_progress(); + var resultExtTime = formatFloat(video.currentTime); + var dateTime = new Date(); + end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } + /** * Helps registering events. * @@ -475,8 +468,8 @@ H5P.VideoHtml5 = (function ($) { */ var mapEvent = function (native, h5p, arg) { video.addEventListener(native, function () { - var extraArg = null; - var extraTrigger = null; + var extraArg = null; + var extraTrigger = null; switch (h5p) { case 'stateChange': if (lastState === arg) { @@ -488,63 +481,63 @@ H5P.VideoHtml5 = (function ($) { video.currentTime = options.startAt; delete options.startAt; } - if( arg === H5P.Video.PLAYING ){ - if ( seeking === true ){ - extraArg = getSeekedParams( seekedTo ); - extraTrigger = 'seeked'; - lastSend = 'seeked'; - seeking = false; - seeking = false; - } else if ( lastSend !== 'play' ){ - extraArg = getPlayParams(); - extraTrigger = 'play'; - lastSend = 'play'; - } - + + if (arg === H5P.Video.PLAYING) { + if (seeking === true) { + extraArg = getSeekedParams(seekedTo); + extraTrigger = 'seeked'; + lastSend = 'seeked'; + seeking = false; + seeking = false; + } else if (lastSend !== 'play') { + extraArg = getPlayParams(); + extraTrigger = 'play'; + lastSend = 'play'; + } } - if( arg === H5P.Video.PAUSED ){ - //put together extraArg for sending to xAPI statement - - if( ! video.seeking && seeking === false) { - extraTrigger = "paused"; - extraArg = getPausedParams(); - lastSend = 'paused'; - } + + if (arg === H5P.Video.PAUSED) { + // Put together extraArg for sending to xAPI statement. + if (!video.seeking && seeking === false) { + extraTrigger = "paused"; + extraArg = getPausedParams(); + lastSend = 'paused'; + } } - //send extra trigger for giving progress on ended call to xAPI - if ( arg === H5P.Video.ENDED ){ - var length = video.duration; - if ( length > 0 ) { - var progress = get_progress(); - if (progress >= 1 ){ - //send statement - extraTrigger = "completed"; - extraArg = getCompletedParams(); - lastSend = 'completed'; - } + + // Send extra trigger for giving progress on ended call to xAPI. + if (arg === H5P.Video.ENDED) { + var length = video.duration; + if (length > 0) { + var progress = get_progress(); + if (progress >= 1) { + // Send statement. + extraTrigger = "completed"; + extraArg = getCompletedParams(); + lastSend = 'completed'; } + } } break; case 'seeked': - return; //seek is tracked differently based on time difference in timeupdate + return; // Seek is tracked differently based on time difference in timeupdate. break; case 'seeking': - return; //just need to store current time for seeked event + return; // Just need to store current time for seeked event. break; case 'volumechange' : arg = getVolumeChangeParams(); lastSend = 'volumechange'; break; - case 'play': - if ( seeking === false && lastSend != h5p ){ - - arg = getPlayParams(); - lastSend = h5p; + case 'play': + if (seeking === false && lastSend != h5p) { + arg = getPlayParams(); + lastSend = h5p; } else { - arg = getSeekedParams( seekedTo ); - lastSend = 'seeked'; - seeking = false; - h5p = 'seeked'; + arg = getSeekedParams(seekedTo); + lastSend = 'seeked'; + seeking = false; + h5p = 'seeked'; } break; case 'fullscreen': @@ -572,12 +565,12 @@ H5P.VideoHtml5 = (function ($) { video.addEventListener('durationchange', andLoaded, false); return; } - - //send extra xAPI statement + + // Send extra xAPI statement. extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); lastSend = 'xAPIloaded'; - + break; case 'error': // Handle error and get message. @@ -603,10 +596,10 @@ H5P.VideoHtml5 = (function ($) { break; } self.trigger(h5p, arg); - - //make extra calls for events with needed values for xAPI statement - if( extraTrigger != null && extraArg != null ){ - self.trigger(extraTrigger, extraArg); + + // Make extra calls for events with needed values for xAPI statement. + if (extraTrigger != null && extraArg != null) { + self.trigger(extraTrigger, extraArg); } }, false); }; @@ -819,7 +812,7 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if(seeking === false) { + if (seeking === false) { previousTime = video.currentTime; } video.currentTime = time; @@ -998,7 +991,6 @@ H5P.VideoHtml5 = (function ($) { mapEvent('timeupdate', 'timeupdate', H5P.Video.PLAYING); mapEvent('volumechange', 'volumechange'); mapEvent('play', 'play', H5P.Video.PLAYING); - //fuscreen events mapEvent('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', 'fullscreen'); if (!video.controls) { @@ -1031,40 +1023,28 @@ H5P.VideoHtml5 = (function ($) { } }); }); - - ////////xAPI extension events for video///// - //catch for seeked event - self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); + + // xAPI extension events for video. + self.on('seeked', function (event) { + this.triggerXAPI('seeked', event.data); }); - //catch volumeChanged Event - self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + self.on('volumechange', function (event) { + this.triggerXAPI('interacted', event.data); }); - self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); + self.on('completed', function (event) { + this.triggerXAPI('completed', event.data); }) - //catch fullscreen Event - self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + self.on('fullscreen', function (event) { + this.triggerXAPI('interacted', event.data); }); - //catch play Event - self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); + self.on('play', function (event) { + this.triggerXAPI('played', event.data); }); - self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); + self.on('xAPIloaded', function (event) { + this.triggerXAPI('initialized',event.data); }) - //catch play Event - self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); + self.on('paused', function (event) { + this.triggerXAPI('paused', event.data); }); // Video controls are ready diff --git a/scripts/youtube.js b/scripts/youtube.js index 39809717..42e708fe 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -91,7 +91,7 @@ H5P.VideoYouTube = (function ($) { onReady: function () { self.trigger('ready'); self.trigger('loaded'); - self.trigger('xAPIloaded',getLoadedParams()); + self.trigger('xAPIloaded', getLoadedParams()); }, onApiChange: function () { if (loadCaptionsModule) { @@ -131,33 +131,30 @@ H5P.VideoYouTube = (function ($) { // End IE11 fix self.trigger('stateChange', state.data); - - //calls for xAPI events - if ( state.data == 1 ){ - - //get and send play call when not seeking - if ( seeking === false ) { - self.trigger('play', getPlayParams()); + + // Calls for xAPI events. + if (state.data == 1) { + // Get and send play call when not seeking. + if (seeking === false) { + self.trigger('play', getPlayParams()); } else { - self.trigger('seeked', getSeekedParams( seekedTo )); - seeking = false; + self.trigger('seeked', getSeekedParams(seekedTo)); + seeking = false; } - } - if ( state.data == 2 ) { - //this is a paused event - // execute your code here for paused state - if(seeking === false){ - self.trigger('paused', getPausedParams()); + } else if (state.data == 2) { + // This is a paused event. + if (seeking === false) { + self.trigger('paused', getPausedParams()); } - } else if ( state.data == 0 ) { - //send xapi trigger if video progress indicates completed - var length = player.getDuration(); - if ( length > 0){ - var progress = get_progress(); - if ( progress >= 1 ){ - var arg = getCompletedParams(); - } + } else if (state.data == 0) { + // Send xapi trigger if video progress indicates completed. + var length = player.getDuration(); + if (length > 0) { + var progress = get_progress(); + if (progress >= 1) { + var arg = getCompletedParams(); } + } } } }, @@ -192,315 +189,306 @@ H5P.VideoYouTube = (function ($) { } }); }; - //function used when putting together object to send for xAPI calls - function getWidthOrHeight ( returnType ){ - var quality = player.getPlaybackQuality(); - var width; - var height; - switch (quality) { - case 'small': - width: '320'; - height: '240'; - break; - case 'medium': - width: '640'; - height: '360'; - break; - case 'large': - width: '853'; - height: '480'; - break; - case 'hd720': - width: '640'; - height: '360'; - break; - case 'hd1080': - width: '1920'; - height: '1080'; - break; - case 'highres': - width: '1920'; - height: '1080'; - break; - } - - return (returnType.toLowerCase().trim()=='width')? width : height; + + // Helper to calculate video dimensions (used in xAPI statements). + function getWidthOrHeight (returnType) { + var quality = player.getPlaybackQuality(); + var width; + var height; + switch (quality) { + case 'small': + width: '320'; + height: '240'; + break; + case 'medium': + width: '640'; + height: '360'; + break; + case 'large': + width: '853'; + height: '480'; + break; + case 'hd720': + width: '640'; + height: '360'; + break; + case 'hd1080': + width: '1920'; + height: '1080'; + break; + case 'highres': + width: '1920'; + height: '1080'; + break; + } + return (returnType.toLowerCase().trim()=='width')? width : height; } - + + // function getLoadedParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var quality = player.getPlaybackQuality(); - var height = getWidthOrHeight('height'); - var width = getWidthOrHeight('width'); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; - var volume = player.getVolume(); - var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; - var ccLanguage; - if ( ccEnabled ) { - ccLanguage = player.getOptions('cc', 'track').languageCode; - } - var userAgent = navigator.userAgent; - var playbackRate = player.getPlaybackRate(); - - var arg = { - "context" : { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + var screenSize = screen.width + "x" + screen.height; + var quality = player.getPlaybackQuality(); + var height = getWidthOrHeight('height'); + var width = getWidthOrHeight('width'); + var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var volume = player.getVolume(); + var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccLanguage; + if ( ccEnabled ) { + ccLanguage = player.getOptions('cc', 'track').languageCode; + } + var userAgent = navigator.userAgent; + var playbackRate = player.getPlaybackRate(); + + var arg = { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + return arg; } + + // function getPlayParams(){ - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp": timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + played_segments_segment_start = resultExtTime; + seekStart = null; + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + return arg; } - //paused Params called on pause statement used by xAPI event + + // Paused Params called on pause statement used by xAPI event. function getPausedParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - previousTime = resultExtTime; - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - var arg = { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(player.getCurrentTime()); + previousTime = resultExtTime; + end_played_segment(resultExtTime); + played_segments_segment_start = resultExtTime; + var progress = get_progress(); + var arg = { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; + return arg; } + + // function getSeekedParams( time ) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - } - return arg; + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat( time ); + seekStart = resultExtTime; + end_played_segment(previousTime); + played_segments_segment_start = seekStart; + // Put together data for xAPI statement to be sent with event + var arg = { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": previousTime, + "https://w3id.org/xapi/video/extensions/time-to": seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + } + return arg; } + + // function getCompletedParams() { - var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [ - { - "id": "https://w3id.org/xapi/video" - } - ] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - - } - }, - "timestamp" : timeStamp - }; + var progress = get_progress(); + var resultExtTime = formatFloat(player.getCurrentTime()); + var dateTime = new Date(); + end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp" : timeStamp + }; } - // common math functions - function formatFloat(number) { - if(number == null) - return null; - return +(parseFloat(number).toFixed(3)); + // Format parameter as float (or null if invalid). + var formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); } - //determine video progress + + // Determine video progress function get_progress() { - var arr, arr2; + var arr, arr2; - //get played segments array - arr = (played_segments == "")? []:played_segments.split("[,]"); - if(played_segments_segment_start != null){ - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } + //get played segments array + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { + return a[0] - b[0]; + }); - arr2 = []; - arr.forEach(function(v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { return a[0] - b[0];}); - - //normalize the segments - arr2.forEach(function(v,i) { - if(i > 0) { - if(arr2[i][0] < arr2[i-1][1]) { //overlapping segments: this segment's starting point is less than last segment's end point. - //console.log(arr2[i][0] + " < " + arr2[i-1][1] + " : " + arr2[i][0] +" = " +arr2[i-1][1] ); - arr2[i][0] = arr2[i-1][1]; - if(arr2[i][0] > arr2[i][1]) - arr2[i][1] = arr2[i][0]; - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function(v,i) { - if(v[1] > v[0]) - progress_length += v[1] - v[0]; - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; + //normalize the segments + arr2.forEach(function (v,i) { + if (i > 0) { + //overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; } + + // function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + //don't run if called too closely to each other + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } } - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - ////////xAPI extension events for video///// - //catch for seeked event + + // Generate a random GUID string. + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + // xAPI extension events for video. self.on('seeked', function(event) { - var statement = event.data; - this.triggerXAPI('seeked', statement); + this.triggerXAPI('seeked', event.data); }); - //catch volumeChanged Event self.on('volumechange', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + this.triggerXAPI('interacted', event.data); }); self.on('completed', function(event){ - var statement = event.data; - this.triggerXAPI('completed', statement); + this.triggerXAPI('completed', event.data); }) - //catch fullscreen Event self.on('fullscreen', function(event) { - var statement = event.data; - this.triggerXAPI('interacted', statement); + this.triggerXAPI('interacted', event.data); }); - //catch play Event self.on('play', function(event) { - var statement = event.data; - this.triggerXAPI('played', statement); + this.triggerXAPI('played', event.data); }); self.on('xAPIloaded', function(event){ - var statement = event.data; - this.triggerXAPI('initialized',statement); + this.triggerXAPI('initialized',event.data); }); - //catch play Event self.on('paused', function(event) { - var statement = event.data; - this.triggerXAPI('paused', statement); + this.triggerXAPI('paused', event.data); }); + /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. @@ -616,10 +604,10 @@ H5P.VideoYouTube = (function ($) { if (!player || !player.seekTo) { return; } - - if( seeking === false ){ + + if (seeking === false) { previousTime = player.getCurrentTime(); - } + } player.seekTo(time, true); seekedTo = time; seeking = true; From 18be43b8e6750446bcb6d01d60d6ba3360a00227 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 16 Nov 2017 15:49:23 -1000 Subject: [PATCH 045/137] Add documentation and reorganize --- scripts/html5.js | 170 +++++++++++++++++------------ scripts/youtube.js | 266 +++++++++++++++++++++++++-------------------- 2 files changed, 247 insertions(+), 189 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 269445f9..86ad31d0 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -152,7 +152,11 @@ H5P.VideoHtml5 = (function ($) { } }); - // Format parameter as float (or null if invalid). + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + */ var formatFloat = function (number) { if (number == null) { return null; @@ -160,7 +164,9 @@ H5P.VideoHtml5 = (function ($) { return +(parseFloat(number).toFixed(3)); }; - // Generate a random GUID string. + /** + * Generate a random GUID string. + */ var guid = function () { var s4 = function () { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); @@ -168,7 +174,9 @@ H5P.VideoHtml5 = (function ($) { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; - // Calculate video progress. + /** + * Calculate video progress. + */ var get_progress = function () { var arr, arr2; @@ -216,43 +224,49 @@ H5P.VideoHtml5 = (function ($) { return progress; }; - function end_played_segment(end_time) { + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + var end_played_segment = function (end_time) { var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( (end_time !== played_segments_segment_start) && (Math.abs(end_time - played_segments_segment_start) > 1 ) ){ - //don't run if called too closely to each other - arr = (played_segments == "")? []:played_segments.split("[,]"); + // Need to not push in segments that happen from multiple triggers during scrubbing + if (end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + // Don't run if called too closely to each other. + arr = played_segments == "" ? [] : played_segments.split("[,]"); arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); played_segments = arr.join("[,]"); played_segments_segment_end = end_time; played_segments_segment_start = null; } - } + }; - function getLoadedParams() { - //variables used in compiling xAPI results + /** + * Create the xAPI object for the 'Loaded' event. + */ + var getLoadedParams = function () { + // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; var screenSize = screen.width + "x" + screen.height; - //playback size var playbackSize = video.videoWidth + "x" + video.videoHeight; - + var playbackRate = video.playbackRate; + var volume = formatFloat(video.volume); + var quality = video.videoHeight < video.videoWidth ? video.videoHeight : video.videoWidth; + var userAgent = navigator.userAgent; var ccEnabled = false; var ccLanguage; - for( var i = 0; i < video.textTracks.length; i++ ){ - if( video.textTracks[i].mode === 'showing' ){ + for (var i = 0; i < video.textTracks.length; i++) { + if (video.textTracks[i].mode === 'showing') { ccEnabled = true; ccLanguage = video.textTracks[i].language; } } - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = (video.videoHeight < video.videoWidth ) ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; + return { "context" : { "contextActivities": { @@ -275,15 +289,49 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp": timeStamp }; - } + }; - function getPausedParams() { + /** + * Create xAPI object for the 'Play' event. + */ + var getPlayParams = function () { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(video.currentTime); + played_segments_segment_start = resultExtTime; + seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID + } + }, + "timestamp": timeStamp + }; + }; + + /** + * Create the xAPI object for the 'Paused' event. + */ + var getPausedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); end_played_segment(resultExtTime); played_segments_segment_start = resultExtTime; var progress = get_progress(); + return { "result": { "extensions": { @@ -304,18 +352,22 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; - function getSeekedParams( time ) { + /** + * Create the xAPI object for the 'Seeked' event. + * + * @param {int} time Time we are seeking to + */ + var getSeekedParams = function (time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); + var resultExtTime = formatFloat(time); seekStart = resultExtTime; end_played_segment(previousTime); played_segments_segment_start = seekStart; - //put together data for xAPI statement to be sent with event - var arg = { + return { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -333,21 +385,24 @@ H5P.VideoHtml5 = (function ($) { } }, "timestamp" : timeStamp - } - return arg; - } + }; + }; - function getVolumeChangeParams() { + /** + * Create xAPI object for the 'VolumeChange' event. + */ + var getVolumeChangeParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); volume_changed_at = video.currentTime; var isMuted = video.muted; var volumeChange; - if ( isMuted === true ){ + if (isMuted === true) { volumeChange = 0; } else { volumeChange = formatFloat(video.volume); } + return { "result" : { "extensions": { @@ -367,44 +422,17 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } - - function getPlayParams() { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - seekStart = null; - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - } - } + }; - function getFullScreenParams() { + /** + * Create xAPI object for the 'FullScreen' event. + */ + var getFullScreenParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(video.currentTime); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - - //sed xapi statement var screenSize = screen.width + "x" + screen.height; - - //playback size var playbackSize = video.videoWidth + "x" + video.videoHeight; return { @@ -428,14 +456,18 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; - function getCompletedParams() { + /** + * Create xAPI object for the 'Completed' event. + */ + var getCompletedParams = function () { var progress = get_progress(); var resultExtTime = formatFloat(video.currentTime); var dateTime = new Date(); end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); + return { "result": { "extensions": { @@ -456,7 +488,7 @@ H5P.VideoHtml5 = (function ($) { }, "timestamp" : timeStamp }; - } + }; /** * Helps registering events. @@ -505,13 +537,12 @@ H5P.VideoHtml5 = (function ($) { } } - // Send extra trigger for giving progress on ended call to xAPI. if (arg === H5P.Video.ENDED) { + // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { var progress = get_progress(); if (progress >= 1) { - // Send statement. extraTrigger = "completed"; extraArg = getCompletedParams(); lastSend = 'completed'; @@ -566,7 +597,6 @@ H5P.VideoHtml5 = (function ($) { return; } - // Send extra xAPI statement. extraTrigger = 'xAPIloaded'; extraArg = getLoadedParams(); lastSend = 'xAPIloaded'; diff --git a/scripts/youtube.js b/scripts/youtube.js index 42e708fe..12e6fce6 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -16,9 +16,10 @@ H5P.VideoYouTube = (function ($) { var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; numInstances++; - /* - * variables to track add extra xAPI statements for video - * @type private + + /** + * Track xAPI statement data for video events. + * @private */ var previousTime = 0; var seekStart = null; @@ -190,11 +191,15 @@ H5P.VideoYouTube = (function ($) { }); }; - // Helper to calculate video dimensions (used in xAPI statements). + /** + * Helper to calculate video dimensions (used in xAPI statements). + * + * @param {string} Which dimension to return ('width' or 'height') + */ function getWidthOrHeight (returnType) { var quality = player.getPlaybackQuality(); - var width; - var height; + var width = ''; + var height = ''; switch (quality) { case 'small': width: '320'; @@ -221,11 +226,102 @@ H5P.VideoYouTube = (function ($) { height: '1080'; break; } - return (returnType.toLowerCase().trim()=='width')? width : height; + return (returnType.toLowerCase().trim()=='width') ? width : height; } - // - function getLoadedParams(){ + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + */ + var formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); + }; + + /** + * Generate a random GUID string. + */ + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Calculate video progress. + */ + var get_progress = function () { + var arr, arr2; + + //get played segments array + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + if (played_segments_segment_start != null) { + arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + //sort the array + arr2.sort(function(a,b) { + return a[0] - b[0]; + }); + + //normalize the segments + arr2.forEach(function (v,i) { + if (i > 0) { + //overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + //calculate progress_length + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / player.getDuration()).toFixed(2); + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + var end_played_segment = function (end_time) { + var arr; + //need to not push in segments that happen from multiple triggers during scrubbing + if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { + //don't run if called too closely to each other + arr = (played_segments == "") ? [] : played_segments.split("[,]"); + arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); + played_segments = arr.join("[,]"); + played_segments_segment_end = end_time; + played_segments_segment_start = null; + } + }; + + /** + * Create the xAPI object for the 'Loaded' event. + */ + var getLoadedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); @@ -234,17 +330,17 @@ H5P.VideoYouTube = (function ($) { var quality = player.getPlaybackQuality(); var height = getWidthOrHeight('height'); var width = getWidthOrHeight('width'); - var playbackSize = ( width !== undefined )? width + 'x' + height : "undetermined"; + var playbackSize = width !== undefined ? width + 'x' + height : "undetermined"; var volume = player.getVolume(); - var ccEnabled = ( player.getOptions().indexOf("cc") !== -1) ? true : false; + var ccEnabled = player.getOptions().indexOf("cc") !== -1; var ccLanguage; - if ( ccEnabled ) { + if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } var userAgent = navigator.userAgent; var playbackRate = player.getPlaybackRate(); - var arg = { + return { "context" : { "contextActivities": { "category": [{ @@ -266,17 +362,19 @@ H5P.VideoYouTube = (function ($) { }, "timestamp": timeStamp }; - return arg; - } + }; - // - function getPlayParams(){ + /** + * Create xAPI object for the 'Play' event. + */ + var getPlayParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); played_segments_segment_start = resultExtTime; seekStart = null; - var arg = { + + return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, @@ -294,11 +392,12 @@ H5P.VideoYouTube = (function ($) { }, "timestamp": timeStamp }; - return arg; - } + }; - // Paused Params called on pause statement used by xAPI event. - function getPausedParams() { + /** + * Create the xAPI object for the 'Paused' event. + */ + var getPausedParams = function () { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(player.getCurrentTime()); @@ -306,7 +405,8 @@ H5P.VideoYouTube = (function ($) { end_played_segment(resultExtTime); played_segments_segment_start = resultExtTime; var progress = get_progress(); - var arg = { + + return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, @@ -326,19 +426,22 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp }; - return arg; - } + }; - // - function getSeekedParams( time ) { + /** + * Create the xAPI object for the 'Seeked' event. + * + * @param {int} time Time we are seeking to + */ + var getSeekedParams = function (time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat( time ); + var resultExtTime = formatFloat(time); seekStart = resultExtTime; end_played_segment(previousTime); played_segments_segment_start = seekStart; - // Put together data for xAPI statement to be sent with event - var arg = { + + return { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": previousTime, @@ -356,17 +459,19 @@ H5P.VideoYouTube = (function ($) { } }, "timestamp" : timeStamp - } - return arg; - } + }; + }; - // - function getCompletedParams() { + /** + * Create xAPI object for the 'Completed' event. + */ + var getCompletedParams = function () { var progress = get_progress(); var resultExtTime = formatFloat(player.getCurrentTime()); var dateTime = new Date(); end_played_segment(resultExtTime); var timeStamp = dateTime.toISOString(); + return { "result": { "extensions": { @@ -387,105 +492,28 @@ H5P.VideoYouTube = (function ($) { }, "timestamp" : timeStamp }; - } - - // Format parameter as float (or null if invalid). - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - } - - // Determine video progress - function get_progress() { - var arr, arr2; - - //get played segments array - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { - return a[0] - b[0]; - }); - - //normalize the segments - arr2.forEach(function (v,i) { - if (i > 0) { - //overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; - } - - // - function end_played_segment(end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - //don't run if called too closely to each other - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - } - - // Generate a random GUID string. - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; // xAPI extension events for video. - self.on('seeked', function(event) { + self.on('seeked', function (event) { this.triggerXAPI('seeked', event.data); }); - self.on('volumechange', function(event) { + self.on('volumechange', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('completed', function(event){ + self.on('completed', function (event){ this.triggerXAPI('completed', event.data); }) - self.on('fullscreen', function(event) { + self.on('fullscreen', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('play', function(event) { + self.on('play', function (event) { this.triggerXAPI('played', event.data); }); - self.on('xAPIloaded', function(event){ + self.on('xAPIloaded', function (event){ this.triggerXAPI('initialized',event.data); }); - self.on('paused', function(event) { + self.on('paused', function (event) { this.triggerXAPI('paused', event.data); }); From c4e2861991a38af7963743c26763dfbe549003b7 Mon Sep 17 00:00:00 2001 From: kirkj Date: Fri, 17 Nov 2017 13:43:07 -1000 Subject: [PATCH 046/137] Made Code DRY --- scripts/html5.js | 379 ++++----------------------------------------- scripts/video.js | 377 ++++++++++++++++++++++++++++++++++++++++++++ scripts/youtube.js | 293 ++--------------------------------- 3 files changed, 420 insertions(+), 629 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 86ad31d0..8136cd91 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -39,17 +39,7 @@ H5P.VideoHtml5 = (function ($) { * Track xAPI statement data for video events. * @private */ - var previousTime = 0; - var seekStart = null; - var played_segments = []; - var played_segments_segment_start = 0; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var sessionID = guid(); var lastSend = null; - var seekedTo = 0; /** * Avoids firing the same event twice. @@ -152,111 +142,11 @@ H5P.VideoHtml5 = (function ($) { } }); - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - */ - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - }; - - /** - * Generate a random GUID string. - */ - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Calculate video progress. - */ - var get_progress = function () { - var arr, arr2; - - // Get played segments array. - arr = played_segments == "" ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(video.currentTime)); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - // Sort the array. - arr2.sort(function (a,b) { - return a[0] - b[0]; - }); - - // Normalize the segments. - arr2.forEach(function (v,i) { - if (i > 0) { - // Overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - // Calculate progress_length. - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / video.duration).toFixed(2); - - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} end_time When the current played segment ended - */ - var end_played_segment = function (end_time) { - var arr; - // Need to not push in segments that happen from multiple triggers during scrubbing - if (end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - // Don't run if called too closely to each other. - arr = played_segments == "" ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - }; - /** * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = video.videoWidth + "x" + video.videoHeight; - var playbackRate = video.playbackRate; - var volume = formatFloat(video.volume); - var quality = video.videoHeight < video.videoWidth ? video.videoHeight : video.videoWidth; - var userAgent = navigator.userAgent; + var ccEnabled = false; var ccLanguage; @@ -266,229 +156,18 @@ H5P.VideoHtml5 = (function ($) { ccLanguage = video.textTracks[i].language; } } - - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create xAPI object for the 'Play' event. - */ - var getPlayParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - played_segments_segment_start = resultExtTime; - seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Paused' event. - */ - var getPausedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Seeked' event. - * - * @param {int} time Time we are seeking to - */ - var getSeekedParams = function (time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(time); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'VolumeChange' event. - */ - var getVolumeChangeParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - volume_changed_at = video.currentTime; - var isMuted = video.muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = formatFloat(video.volume); + + var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if ( isFullScreen === undefined ){ + isFullScreen = false; } + + return H5P.Video.getxAPIInitializedObject( video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage ); - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'FullScreen' event. - */ - var getFullScreenParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(video.currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = video.videoWidth + "x" + video.videoHeight; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'Completed' event. - */ - var getCompletedParams = function () { - var progress = get_progress(); - var resultExtTime = formatFloat(video.currentTime); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; }; + + //set duration used for xAPI statements + H5P.Video.duration = video.duration; /** * Helps registering events. @@ -515,14 +194,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (seeking === true) { - extraArg = getSeekedParams(seekedTo); + if ( H5P.Video.seeking === true) { + extraArg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - seeking = false; - seeking = false; + H5P.Video.seeking = false; + H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = getPlayParams(); + extraArg = H5P.Video.getxAPIPlayObject( video.currentTime ); extraTrigger = 'play'; lastSend = 'play'; } @@ -530,9 +209,9 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && seeking === false) { + if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = getPausedParams(); + extraArg = H5P.Video.getxAPIPauseObject( video.currentTime, video.duration ); lastSend = 'paused'; } } @@ -541,10 +220,10 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = get_progress(); + var progress = H5P.Video.get_progress( video.current_time, video.duration ); if (progress >= 1) { extraTrigger = "completed"; - extraArg = getCompletedParams(); + extraArg = H5P.Video.getxAPICompleteObject( video.currentTime, video.duration ); lastSend = 'completed'; } } @@ -557,22 +236,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = getVolumeChangeParams(); + arg = H5P.Video.getxAPIVolumeChangeObject( video.currentTime, video.muted, video.volume ); lastSend = 'volumechange'; break; case 'play': - if (seeking === false && lastSend != h5p) { - arg = getPlayParams(); + if ( H5P.Video.seeking === false && lastSend != h5p) { + arg = H5P.Video.getxAPIPlayObject( video.currentTime ); lastSend = h5p; } else { - arg = getSeekedParams(seekedTo); + arg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); lastSend = 'seeked'; - seeking = false; + H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = getFullScreenParams(); + arg = H5P.Video.getxAPIFullScreenObject( video.currentTime, video.videoWidth, video.videoHeight ); lastSend = h5p; break; case 'loaded': @@ -842,12 +521,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (seeking === false) { - previousTime = video.currentTime; + if ( H5P.Video.seeking === false) { + H5P.Video.previousTime = video.currentTime; } video.currentTime = time; - seeking = true; - seekedTo = time; + H5P.Video.seeking = true; + H5P.Video.seekedTo = time; }; /** diff --git a/scripts/video.js b/scripts/video.js index a1b5f8a5..d191771e 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -155,7 +155,384 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } + + /** + * Generate a random GUID string used for seesionID with video xAPI statements. + */ + Video.guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + * used when making arguments sent with video xAPI statments + */ + Video.formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); + }; + + /** + * Track xAPI statement data for video events. + * @private + */ + Video.previousTime = 0; + Video.seekStart = null; + Video.played_segments = []; + Video.played_segments_segment_start =0; + Video.played_segments_segment_end; + Video.volume_changed_on = null; + Video.volume_changed_at = 0; + Video.seeking = false; + Video.sessionID = Video.guid(); + Video.currentTime = 0; + Video.seekedTo = 0; + + /** + * Calculate video progress. + */ + Video.get_progress = function (current_time, duration ) { + var arr, arr2; + + // Get played segments array. + arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); + if (Video.played_segments_segment_start != null) { + arr.push(Video.played_segments_segment_start + "[.]" + Video.formatFloat(current_time)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + // Calculate progress_length. + var progress_length = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progress_length += v[1] - v[0]; + } + }); + + var progress = 1 * (progress_length / duration ).toFixed(2); + + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @param {int} end_time When the current played segment ended + */ + Video.end_played_segment = function (end_time) { + var arr; + // Need to not push in segments that happen from multiple triggers during scrubbing + if (end_time !== Video.played_segments_segment_start && Math.abs(end_time - Video.played_segments_segment_start) > 1 ) { + // Don't run if called too closely to each other. + arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); + arr.push(Video.formatFloat(Video.played_segments_segment_start) + "[.]" + Video.formatFloat(end_time)); + Video.played_segments = arr.join("[,]"); + Video.played_segments_segment_end = end_time; + Video.played_segments_segment_start = null; + } + }; + /** + * Video.getxAPIPauseObject + * + * @param {type} current_time + * @returns {json object} + */ + Video.getxAPIPauseObject = function ( current_time, duration ) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.end_played_segment(resultExtTime); + Video.played_segments_segment_start = resultExtTime; + var progress = Video.get_progress( current_time, duration ); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + /** + * Video.getxAPIPlayObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with event to be triggered by xAPI event + */ + Video.getxAPIPlayObject = function ( current_time ){ + + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.played_segments_segment_start = resultExtTime; + Video.seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + } + + /** + * Video.getxAPIPlayObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with seeked event to be triggered by xAPI event + */ + Video.getxAPISeekedObject = function ( current_time ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.seekStart = resultExtTime; + Video.end_played_segment(Video.previousTime); + Video.played_segments_segment_start = Video.seekStart; + + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, + "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIVolumeChangeObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with volume change event to be triggered by xAPI event + */ + Video.getxAPIVolumeChangeObject = function ( current_time, muted, volume ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + Video.volume_changed_at = Video.formatFloat( current_time ); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = Video.formatFloat(volume); + } + + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPICompleteObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with complete event to be triggered by xAPI event + */ + Video.getxAPICompleteObject = function ( current_time, duration ){ + var progress = Video.get_progress( current_time, duration ); + var resultExtTime = Video.formatFloat(current_time); + var dateTime = new Date(); + Video.end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIFullScreenObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + Video.getxAPIFullScreenObject = function ( current_time, width, height, fullscreen = false ){ + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat( current_time ); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if (state == undefined ){ + state = fullscreen; + } + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + } + + /** + * Video.getxAPIInitializedObject + * + * @param { float } current_time time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + Video.getxAPIInitializedObject = function ( current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false ){ + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = Video.formatFloat( volume ); + var quality = (quality === false )? (height < width ? height : width) : quality; + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; + if ( state === undefined ){ + state = false; + } + + + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + } + // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; diff --git a/scripts/youtube.js b/scripts/youtube.js index 12e6fce6..d5787305 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -17,22 +17,6 @@ H5P.VideoYouTube = (function ($) { var id = 'h5p-youtube-' + numInstances; numInstances++; - /** - * Track xAPI statement data for video events. - * @private - */ - var previousTime = 0; - var seekStart = null; - var played_segments = []; - var played_segments_segment_start =0; - var played_segments_segment_end; - var volume_changed_on = null; - var volume_changed_at = 0; - var seeking = false; - var sessionID = guid(); - var currentTime = 0; - var seekedTo = 0; - var $wrapper = $('
'); var $placeholder = $('
', { id: id, @@ -136,24 +120,24 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data == 1) { // Get and send play call when not seeking. - if (seeking === false) { - self.trigger('play', getPlayParams()); + if (H5P.Video.seeking === false) { + self.trigger('play', H5P.Video.getxAPIPlayObject( player.getCurrentTime()) ); } else { - self.trigger('seeked', getSeekedParams(seekedTo)); - seeking = false; + self.trigger('seeked',H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo) ); + H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. - if (seeking === false) { - self.trigger('paused', getPausedParams()); + if (H5P.Video.seeking === false) { + self.trigger('paused', H5P.Video.getxAPIPauseObject( player.getCurrentTime(), player.getDuration()) ); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = get_progress(); + var progress = H5P.Video.get_progress( player.getCurrentTime(), player.getDuration() ); if (progress >= 1) { - var arg = getCompletedParams(); + var arg = H5P.Video.getxAPICompleteObject( player.getCurrentTime(), player.getDuration() );; } } } @@ -229,269 +213,20 @@ H5P.VideoYouTube = (function ($) { return (returnType.toLowerCase().trim()=='width') ? width : height; } - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - */ - var formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - }; - - /** - * Generate a random GUID string. - */ - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Calculate video progress. - */ - var get_progress = function () { - var arr, arr2; - - //get played segments array - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - if (played_segments_segment_start != null) { - arr.push(played_segments_segment_start + "[.]" + formatFloat(player.getCurrentTime())); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - //sort the array - arr2.sort(function(a,b) { - return a[0] - b[0]; - }); - - //normalize the segments - arr2.forEach(function (v,i) { - if (i > 0) { - //overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - //calculate progress_length - var progress_length = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progress_length += v[1] - v[0]; - } - }); - - var progress = 1 * (progress_length / player.getDuration()).toFixed(2); - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} end_time When the current played segment ended - */ - var end_played_segment = function (end_time) { - var arr; - //need to not push in segments that happen from multiple triggers during scrubbing - if ( end_time !== played_segments_segment_start && Math.abs(end_time - played_segments_segment_start) > 1 ) { - //don't run if called too closely to each other - arr = (played_segments == "") ? [] : played_segments.split("[,]"); - arr.push(formatFloat(played_segments_segment_start) + "[.]" + formatFloat(end_time)); - played_segments = arr.join("[,]"); - played_segments_segment_end = end_time; - played_segments_segment_start = null; - } - }; - /** * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - var screenSize = screen.width + "x" + screen.height; - var quality = player.getPlaybackQuality(); var height = getWidthOrHeight('height'); var width = getWidthOrHeight('width'); - var playbackSize = width !== undefined ? width + 'x' + height : "undetermined"; - var volume = player.getVolume(); var ccEnabled = player.getOptions().indexOf("cc") !== -1; var ccLanguage; if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - var userAgent = navigator.userAgent; - var playbackRate = player.getPlaybackRate(); + + return H5P.Video.getxAPIInitializedObject( player.getCurrentTime() , width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create xAPI object for the 'Play' event. - */ - var getPlayParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - played_segments_segment_start = resultExtTime; - seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Paused' event. - */ - var getPausedParams = function () { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(player.getCurrentTime()); - previousTime = resultExtTime; - end_played_segment(resultExtTime); - played_segments_segment_start = resultExtTime; - var progress = get_progress(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create the xAPI object for the 'Seeked' event. - * - * @param {int} time Time we are seeking to - */ - var getSeekedParams = function (time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(time); - seekStart = resultExtTime; - end_played_segment(previousTime); - played_segments_segment_start = seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": previousTime, - "https://w3id.org/xapi/video/extensions/time-to": seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Create xAPI object for the 'Completed' event. - */ - var getCompletedParams = function () { - var progress = get_progress(); - var resultExtTime = formatFloat(player.getCurrentTime()); - var dateTime = new Date(); - end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID - } - }, - "timestamp" : timeStamp - }; }; // xAPI extension events for video. @@ -633,12 +368,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (seeking === false) { - previousTime = player.getCurrentTime(); + if (H5P.Video.seeking === false) { + H5P.Video.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - seekedTo = time; - seeking = true; + H5P.Video.seeking = time; + H5P.Video.seeking = true; }; /** From 3c6e0a9997f9a990821195e851fbb373e0980b4a Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:46:05 -1000 Subject: [PATCH 047/137] Whitespace --- scripts/html5.js | 37 +++++++++++------------ scripts/video.js | 74 +++++++++++++++++++++++----------------------- scripts/youtube.js | 14 ++++----- 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 8136cd91..ada0b580 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -146,7 +146,6 @@ H5P.VideoHtml5 = (function ($) { * Create the xAPI object for the 'Loaded' event. */ var getLoadedParams = function () { - var ccEnabled = false; var ccLanguage; @@ -162,11 +161,11 @@ H5P.VideoHtml5 = (function ($) { isFullScreen = false; } - return H5P.Video.getxAPIInitializedObject( video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage ); + return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; - - //set duration used for xAPI statements + + // Set duration used for xAPI statements. H5P.Video.duration = video.duration; /** @@ -194,14 +193,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if ( H5P.Video.seeking === true) { - extraArg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); + if (H5P.Video.seeking === true) { + extraArg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - H5P.Video.seeking = false; - H5P.Video.seeking = false; + H5P.Video.seeking = false; + H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getxAPIPlayObject( video.currentTime ); + extraArg = H5P.Video.getxAPIPlayObject(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -211,7 +210,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getxAPIPauseObject( video.currentTime, video.duration ); + extraArg = H5P.Video.getxAPIPauseObject(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -220,10 +219,10 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = H5P.Video.get_progress( video.current_time, video.duration ); + var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getxAPICompleteObject( video.currentTime, video.duration ); + extraArg = H5P.Video.getxAPICompleteObject(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -236,22 +235,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getxAPIVolumeChangeObject( video.currentTime, video.muted, video.volume ); + arg = H5P.Video.getxAPIVolumeChangeObject(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': - if ( H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getxAPIPlayObject( video.currentTime ); + if (H5P.Video.seeking === false && lastSend != h5p) { + arg = H5P.Video.getxAPIPlayObject(video.currentTime); lastSend = h5p; } else { - arg = H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo); + arg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); lastSend = 'seeked'; H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.Video.getxAPIFullScreenObject( video.currentTime, video.videoWidth, video.videoHeight ); + arg = H5P.Video.getxAPIFullScreenObject(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': @@ -521,11 +520,11 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if ( H5P.Video.seeking === false) { + if (H5P.Video.seeking === false) { H5P.Video.previousTime = video.currentTime; } video.currentTime = time; - H5P.Video.seeking = true; + H5P.Video.seeking = true; H5P.Video.seekedTo = time; }; diff --git a/scripts/video.js b/scripts/video.js index d191771e..b3fac434 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -155,7 +155,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } } - + /** * Generate a random GUID string used for seesionID with video xAPI statements. */ @@ -165,7 +165,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; - + /** * Format parameter as float (or null if invalid). * @@ -178,7 +178,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } return +(parseFloat(number).toFixed(3)); }; - + /** * Track xAPI statement data for video events. * @private @@ -198,7 +198,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Calculate video progress. */ - Video.get_progress = function (current_time, duration ) { + Video.get_progress = function (current_time, duration) { var arr, arr2; // Get played segments array. @@ -264,17 +264,17 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** * Video.getxAPIPauseObject - * + * * @param {type} current_time * @returns {json object} */ - Video.getxAPIPauseObject = function ( current_time, duration ) { + Video.getxAPIPauseObject = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); Video.end_played_segment(resultExtTime); Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress( current_time, duration ); + var progress = Video.get_progress(current_time, duration); return { "result": { @@ -299,13 +299,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } /** * Video.getxAPIPlayObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getxAPIPlayObject = function ( current_time ){ - + Video.getxAPIPlayObject = function (current_time) { + var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -331,15 +331,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp": timeStamp }; } - + /** * Video.getxAPIPlayObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getxAPISeekedObject = function ( current_time ){ + Video.getxAPISeekedObject = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -367,18 +367,18 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPIVolumeChangeObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getxAPIVolumeChangeObject = function ( current_time, muted, volume ){ + Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat( current_time ); + Video.volume_changed_at = Video.formatFloat(current_time); var isMuted = muted; var volumeChange; if (isMuted === true) { @@ -407,16 +407,16 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPICompleteObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getxAPICompleteObject = function ( current_time, duration ){ - var progress = Video.get_progress( current_time, duration ); + Video.getxAPICompleteObject = function (current_time, duration) { + var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); Video.end_played_segment(resultExtTime); @@ -442,19 +442,19 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }, "timestamp" : timeStamp }; - } - + } + /** * Video.getxAPIFullScreenObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIFullScreenObject = function ( current_time, width, height, fullscreen = false ){ + Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat( current_time ); + var resultExtTime = Video.formatFloat(current_time); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; if (state == undefined ){ state = fullscreen; @@ -484,15 +484,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp" : timeStamp }; } - + /** * Video.getxAPIInitializedObject - * + * * @param { float } current_time time of the video currently - * + * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIInitializedObject = function ( current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false ){ + Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); @@ -500,7 +500,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var screenSize = screen.width + "x" + screen.height; var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; - var volume = Video.formatFloat( volume ); + var volume = Video.formatFloat(volume); var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; @@ -532,7 +532,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "timestamp": timeStamp }; } - + // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; diff --git a/scripts/youtube.js b/scripts/youtube.js index d5787305..91d8cf3c 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -121,23 +121,23 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getxAPIPlayObject( player.getCurrentTime()) ); + self.trigger('play', H5P.Video.getxAPIPlayObject(player.getCurrentTime())); } else { - self.trigger('seeked',H5P.Video.getxAPISeekedObject( H5P.Video.seekedTo) ); + self.trigger('seeked',H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo)); H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getxAPIPauseObject( player.getCurrentTime(), player.getDuration()) ); + self.trigger('paused', H5P.Video.getxAPIPauseObject(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = H5P.Video.get_progress( player.getCurrentTime(), player.getDuration() ); + var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getxAPICompleteObject( player.getCurrentTime(), player.getDuration() );; + var arg = H5P.Video.getxAPICompleteObject(player.getCurrentTime(), player.getDuration()); } } } @@ -224,8 +224,8 @@ H5P.VideoYouTube = (function ($) { if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - - return H5P.Video.getxAPIInitializedObject( player.getCurrentTime() , width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + + return H5P.Video.getxAPIInitializedObject(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; From 1434112b0c6280cc42a16a67baf7b50225c1960d Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:46:33 -1000 Subject: [PATCH 048/137] Cleaner logic --- scripts/html5.js | 9 +++------ scripts/video.js | 11 ++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index ada0b580..8f73f8ad 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -155,12 +155,9 @@ H5P.VideoHtml5 = (function ($) { ccLanguage = video.textTracks[i].language; } } - - var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if ( isFullScreen === undefined ){ - isFullScreen = false; - } - + + var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; diff --git a/scripts/video.js b/scripts/video.js index b3fac434..2ef10e8e 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -455,10 +455,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if (state == undefined ){ - state = fullscreen; - } + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; @@ -503,11 +500,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var volume = Video.formatFloat(volume); var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; - if ( state === undefined ){ - state = false; - } - + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; return { "context" : { From 129d56e0f56dfac81bb51a20688d6b115929e8f8 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 10:54:20 -1000 Subject: [PATCH 049/137] Whitespace (indentation) --- scripts/video.js | 391 ++++++++++++++++++++++++----------------------- 1 file changed, 196 insertions(+), 195 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 2ef10e8e..55ef5d7c 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -262,6 +262,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { Video.played_segments_segment_start = null; } }; + /** * Video.getxAPIPauseObject * @@ -269,34 +270,35 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * @returns {json object} */ Video.getxAPIPauseObject = function (current_time, duration) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.end_played_segment(resultExtTime); - Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress(current_time, duration); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.end_played_segment(resultExtTime); + Video.played_segments_segment_start = resultExtTime; + var progress = Video.get_progress(current_time, duration); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; + /** * Video.getxAPIPlayObject * @@ -305,32 +307,31 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with event to be triggered by xAPI event */ Video.getxAPIPlayObject = function (current_time) { - - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.played_segments_segment_start = resultExtTime; - Video.seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.played_segments_segment_start = resultExtTime; + Video.seekStart = null; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp": timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + }; /** * Video.getxAPIPlayObject @@ -340,33 +341,33 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with seeked event to be triggered by xAPI event */ Video.getxAPISeekedObject = function (current_time) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.seekStart = resultExtTime; - Video.end_played_segment(Video.previousTime); - Video.played_segments_segment_start = Video.seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, - "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + Video.seekStart = resultExtTime; + Video.end_played_segment(Video.previousTime); + Video.played_segments_segment_start = Video.seekStart; + + return { + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, + "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIVolumeChangeObject @@ -376,37 +377,37 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with volume change event to be triggered by xAPI event */ Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat(current_time); - var isMuted = muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = Video.formatFloat(volume); - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + Video.volume_changed_at = Video.formatFloat(current_time); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = Video.formatFloat(volume); + } - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } + return { + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPICompleteObject @@ -416,33 +417,33 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with complete event to be triggered by xAPI event */ Video.getxAPICompleteObject = function (current_time, duration) { - var progress = Video.get_progress(current_time, duration); - var resultExtTime = Video.formatFloat(current_time); - var dateTime = new Date(); - Video.end_played_segment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + var progress = Video.get_progress(current_time, duration); + var resultExtTime = Video.formatFloat(current_time); + var dateTime = new Date(); + Video.end_played_segment(resultExtTime); + var timeStamp = dateTime.toISOString(); + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIFullScreenObject @@ -452,35 +453,35 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with full screen change event to be triggered by xAPI event */ Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = width + "x" + height; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + return { + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp" : timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + }; /** * Video.getxAPIInitializedObject @@ -490,41 +491,41 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * used to retun json object sent with full screen change event to be triggered by xAPI event */ Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - var screenSize = screen.width + "x" + screen.height; - var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; - var playbackRate = rate; - var volume = Video.formatFloat(volume); - var quality = (quality === false )? (height < width ? height : width) : quality; - var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = Video.formatFloat(current_time); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = Video.formatFloat(volume); + var quality = (quality === false )? (height < width ? height : width) : quality; + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + + return { + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] }, - "timestamp": timeStamp - }; - } + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID + } + }, + "timestamp": timeStamp + }; + }; // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); From ebadcc9766d0978d154412a2052c5b715471cb96 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:05:59 -1000 Subject: [PATCH 050/137] Refactor xAPI object function names Use template getArgsXAPI{verb} instead of getxAPI{verb}Object --- scripts/html5.js | 18 +++++++++--------- scripts/video.js | 28 ++++++++++++++-------------- scripts/youtube.js | 10 +++++----- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 8f73f8ad..8bd6d0ab 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -158,7 +158,7 @@ H5P.VideoHtml5 = (function ($) { var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return H5P.Video.getxAPIInitializedObject(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return H5P.Video.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; @@ -191,13 +191,13 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PLAYING) { if (H5P.Video.seeking === true) { - extraArg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); + extraArg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; H5P.Video.seeking = false; H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getxAPIPlayObject(video.currentTime); + extraArg = H5P.Video.getArgsXAPIPlay(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -207,7 +207,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getxAPIPauseObject(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIPause(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -219,7 +219,7 @@ H5P.VideoHtml5 = (function ($) { var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getxAPICompleteObject(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIComplete(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -232,22 +232,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getxAPIVolumeChangeObject(video.currentTime, video.muted, video.volume); + arg = H5P.Video.getArgsXAPIVolumeChange(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getxAPIPlayObject(video.currentTime); + arg = H5P.Video.getArgsXAPIPlay(video.currentTime); lastSend = h5p; } else { - arg = H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo); + arg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); lastSend = 'seeked'; H5P.Video.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.Video.getxAPIFullScreenObject(video.currentTime, video.videoWidth, video.videoHeight); + arg = H5P.Video.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': diff --git a/scripts/video.js b/scripts/video.js index 55ef5d7c..73940f09 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -264,12 +264,12 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPauseObject + * Video.getArgsXAPIPause * * @param {type} current_time * @returns {json object} */ - Video.getxAPIPauseObject = function (current_time, duration) { + Video.getArgsXAPIPause = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -300,13 +300,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPlayObject + * Video.getArgsXAPIPlay * * @param { float } current_time time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getxAPIPlayObject = function (current_time) { + Video.getArgsXAPIPlay = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -334,13 +334,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIPlayObject + * Video.getArgsXAPISeeked * * @param { float } current_time time of the video currently * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getxAPISeekedObject = function (current_time) { + Video.getArgsXAPISeeked = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -370,13 +370,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIVolumeChangeObject + * Video.getArgsXAPIVolumeChange * * @param { float } current_time time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getxAPIVolumeChangeObject = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChange = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); Video.volume_changed_at = Video.formatFloat(current_time); @@ -410,13 +410,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPICompleteObject + * Video.getArgsXAPIComplete * * @param { float } current_time time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getxAPICompleteObject = function (current_time, duration) { + Video.getArgsXAPIComplete = function (current_time, duration) { var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); @@ -446,13 +446,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIFullScreenObject + * Video.getArgsXAPIFullScreen * * @param { float } current_time time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIFullScreenObject = function (current_time, width, height, fullscreen = false) { + Video.getArgsXAPIFullScreen = function (current_time, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -484,13 +484,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getxAPIInitializedObject + * Video.getArgsXAPIInitialized * * @param { float } current_time time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getxAPIInitializedObject = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); diff --git a/scripts/youtube.js b/scripts/youtube.js index 91d8cf3c..d664f831 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -121,15 +121,15 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getxAPIPlayObject(player.getCurrentTime())); + self.trigger('play', H5P.Video.getArgsXAPIPlay(player.getCurrentTime())); } else { - self.trigger('seeked',H5P.Video.getxAPISeekedObject(H5P.Video.seekedTo)); + self.trigger('seeked', H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo)); H5P.Video.seeking = false; } } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getxAPIPauseObject(player.getCurrentTime(), player.getDuration())); + self.trigger('paused', H5P.Video.getArgsXAPIPause(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. @@ -137,7 +137,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getxAPICompleteObject(player.getCurrentTime(), player.getDuration()); + var arg = H5P.Video.getArgsXAPIComplete(player.getCurrentTime(), player.getDuration()); } } } @@ -225,7 +225,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return H5P.Video.getxAPIInitializedObject(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return H5P.Video.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; From c16d0fda2314c31bc868c99768642dc55d498a02 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:10:06 -1000 Subject: [PATCH 051/137] Refactor xAPI function names xAPI verbs are past tense, so make the function names match. --- scripts/html5.js | 10 +++++----- scripts/video.js | 16 ++++++++-------- scripts/youtube.js | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 8bd6d0ab..b4038ebc 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -197,7 +197,7 @@ H5P.VideoHtml5 = (function ($) { H5P.Video.seeking = false; H5P.Video.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getArgsXAPIPlay(video.currentTime); + extraArg = H5P.Video.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -207,7 +207,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && H5P.Video.seeking === false) { extraTrigger = "paused"; - extraArg = H5P.Video.getArgsXAPIPause(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -219,7 +219,7 @@ H5P.VideoHtml5 = (function ($) { var progress = H5P.Video.get_progress(video.current_time, video.duration); if (progress >= 1) { extraTrigger = "completed"; - extraArg = H5P.Video.getArgsXAPIComplete(video.currentTime, video.duration); + extraArg = H5P.Video.getArgsXAPICompleted(video.currentTime, video.duration); lastSend = 'completed'; } } @@ -232,12 +232,12 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getArgsXAPIVolumeChange(video.currentTime, video.muted, video.volume); + arg = H5P.Video.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getArgsXAPIPlay(video.currentTime); + arg = H5P.Video.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { arg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); diff --git a/scripts/video.js b/scripts/video.js index 73940f09..3b027192 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -264,12 +264,12 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIPause + * Video.getArgsXAPIPaused * * @param {type} current_time * @returns {json object} */ - Video.getArgsXAPIPause = function (current_time, duration) { + Video.getArgsXAPIPaused = function (current_time, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -300,13 +300,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIPlay + * Video.getArgsXAPIPlayed * * @param { float } current_time time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getArgsXAPIPlay = function (current_time) { + Video.getArgsXAPIPlayed = function (current_time) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = Video.formatFloat(current_time); @@ -370,13 +370,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIVolumeChange + * Video.getArgsXAPIVolumeChanged * * @param { float } current_time time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getArgsXAPIVolumeChange = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChanged = function (current_time, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); Video.volume_changed_at = Video.formatFloat(current_time); @@ -410,13 +410,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPIComplete + * Video.getArgsXAPICompleted * * @param { float } current_time time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getArgsXAPIComplete = function (current_time, duration) { + Video.getArgsXAPICompleted = function (current_time, duration) { var progress = Video.get_progress(current_time, duration); var resultExtTime = Video.formatFloat(current_time); var dateTime = new Date(); diff --git a/scripts/youtube.js b/scripts/youtube.js index d664f831..8e070162 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -121,7 +121,7 @@ H5P.VideoYouTube = (function ($) { if (state.data == 1) { // Get and send play call when not seeking. if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getArgsXAPIPlay(player.getCurrentTime())); + self.trigger('play', H5P.Video.getArgsXAPIPlayed(player.getCurrentTime())); } else { self.trigger('seeked', H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo)); H5P.Video.seeking = false; @@ -129,7 +129,7 @@ H5P.VideoYouTube = (function ($) { } else if (state.data == 2) { // This is a paused event. if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getArgsXAPIPause(player.getCurrentTime(), player.getDuration())); + self.trigger('paused', H5P.Video.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates completed. @@ -137,7 +137,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getArgsXAPIComplete(player.getCurrentTime(), player.getDuration()); + var arg = H5P.Video.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); } } } From a95ae2ce0d727cc406d4340760f3942fdcc4e965 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:27:00 -1000 Subject: [PATCH 052/137] Fix for misnamed variable html5 video object has a property called currentTime, not current_time --- scripts/html5.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/html5.js b/scripts/html5.js index b4038ebc..4625c25b 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -216,7 +216,7 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = H5P.Video.get_progress(video.current_time, video.duration); + var progress = H5P.Video.getProgress(video.currentTime, video.duration); if (progress >= 1) { extraTrigger = "completed"; extraArg = H5P.Video.getArgsXAPICompleted(video.currentTime, video.duration); From b85a5ecf0dfec1755b2f2b2d74d281163090ead2 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:32:39 -1000 Subject: [PATCH 053/137] Refactor var names (use camelCase, not snake case) --- scripts/video.js | 106 ++++++++++++++++++++++----------------------- scripts/youtube.js | 2 +- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 3b027192..872b185c 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -185,11 +185,11 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { */ Video.previousTime = 0; Video.seekStart = null; - Video.played_segments = []; - Video.played_segments_segment_start =0; - Video.played_segments_segment_end; - Video.volume_changed_on = null; - Video.volume_changed_at = 0; + Video.playedSegments = []; + Video.playedSegmentsSegmentStart =0; + Video.playedSegmentsSegmentEnd; + Video.volumeChangedOn = null; + Video.volumeChangedAt = 0; Video.seeking = false; Video.sessionID = Video.guid(); Video.currentTime = 0; @@ -198,13 +198,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Calculate video progress. */ - Video.get_progress = function (current_time, duration) { + Video.getProgress = function (currentTime, duration) { var arr, arr2; // Get played segments array. - arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); - if (Video.played_segments_segment_start != null) { - arr.push(Video.played_segments_segment_start + "[.]" + Video.formatFloat(current_time)); + arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); + if (Video.playedSegmentsSegmentStart != null) { + arr.push(Video.playedSegmentsSegmentStart + "[.]" + Video.formatFloat(currentTime)); } arr2 = []; @@ -232,15 +232,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } }); - // Calculate progress_length. - var progress_length = 0; + // Calculate progress length. + var progressLength = 0; arr2.forEach(function (v,i) { if (v[1] > v[0]) { - progress_length += v[1] - v[0]; + progressLength += v[1] - v[0]; } }); - var progress = 1 * (progress_length / duration ).toFixed(2); + var progress = 1 * (progressLength / duration ).toFixed(2); return progress; }; @@ -248,41 +248,41 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Add a played segment to the array of already played segments. * - * @param {int} end_time When the current played segment ended + * @param {int} endTime When the current played segment ended */ - Video.end_played_segment = function (end_time) { + Video.endPlayedSegment = function (endTime) { var arr; // Need to not push in segments that happen from multiple triggers during scrubbing - if (end_time !== Video.played_segments_segment_start && Math.abs(end_time - Video.played_segments_segment_start) > 1 ) { + if (endTime !== Video.playedSegmentsSegmentStart && Math.abs(endTime - Video.playedSegmentsSegmentStart) > 1 ) { // Don't run if called too closely to each other. - arr = Video.played_segments == "" ? [] : Video.played_segments.split("[,]"); - arr.push(Video.formatFloat(Video.played_segments_segment_start) + "[.]" + Video.formatFloat(end_time)); - Video.played_segments = arr.join("[,]"); - Video.played_segments_segment_end = end_time; - Video.played_segments_segment_start = null; + arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); + arr.push(Video.formatFloat(Video.playedSegmentsSegmentStart) + "[.]" + Video.formatFloat(endTime)); + Video.playedSegments = arr.join("[,]"); + Video.playedSegmentsSegmentEnd = endTime; + Video.playedSegmentsSegmentStart = null; } }; /** * Video.getArgsXAPIPaused * - * @param {type} current_time + * @param {type} currentTime * @returns {json object} */ - Video.getArgsXAPIPaused = function (current_time, duration) { + Video.getArgsXAPIPaused = function (currentTime, duration) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.end_played_segment(resultExtTime); - Video.played_segments_segment_start = resultExtTime; - var progress = Video.get_progress(current_time, duration); + var resultExtTime = Video.formatFloat(currentTime); + Video.endPlayedSegment(resultExtTime); + Video.playedSegmentsSegmentStart = resultExtTime; + var progress = Video.getProgress(currentTime, duration); return { "result": { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments } }, "context": { @@ -302,15 +302,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIPlayed * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with event to be triggered by xAPI event */ - Video.getArgsXAPIPlayed = function (current_time) { + Video.getArgsXAPIPlayed = function (currentTime) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); - Video.played_segments_segment_start = resultExtTime; + var resultExtTime = Video.formatFloat(currentTime); + Video.playedSegmentsSegmentStart = resultExtTime; Video.seekStart = null; return { @@ -336,17 +336,17 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPISeeked * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with seeked event to be triggered by xAPI event */ - Video.getArgsXAPISeeked = function (current_time) { + Video.getArgsXAPISeeked = function (currentTime) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); Video.seekStart = resultExtTime; - Video.end_played_segment(Video.previousTime); - Video.played_segments_segment_start = Video.seekStart; + Video.endPlayedSegment(Video.previousTime); + Video.playedSegmentsSegmentStart = Video.seekStart; return { "result": { @@ -372,14 +372,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIVolumeChanged * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with volume change event to be triggered by xAPI event */ - Video.getArgsXAPIVolumeChanged = function (current_time, muted, volume) { + Video.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - Video.volume_changed_at = Video.formatFloat(current_time); + Video.volumeChangedAt = Video.formatFloat(currentTime); var isMuted = muted; var volumeChange; if (isMuted === true) { @@ -391,7 +391,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { return { "result" : { "extensions": { - "https://w3id.org/xapi/video/extensions/time": Video.volume_changed_at + "https://w3id.org/xapi/video/extensions/time": Video.volumeChangedAt } }, "context": { @@ -412,15 +412,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPICompleted * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getArgsXAPICompleted = function (current_time, duration) { - var progress = Video.get_progress(current_time, duration); - var resultExtTime = Video.formatFloat(current_time); + Video.getArgsXAPICompleted = function (currentTime, duration) { + var progress = Video.getProgress(currentTime, duration); + var resultExtTime = Video.formatFloat(currentTime); var dateTime = new Date(); - Video.end_played_segment(resultExtTime); + Video.endPlayedSegment(resultExtTime); var timeStamp = dateTime.toISOString(); return { @@ -428,7 +428,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { "extensions": { "https://w3id.org/xapi/video/extensions/time": resultExtTime, "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.played_segments + "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments } }, "context": { @@ -448,14 +448,14 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIFullScreen * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIFullScreen = function (current_time, width, height, fullscreen = false) { + Video.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; @@ -486,15 +486,15 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { /** * Video.getArgsXAPIInitialized * - * @param { float } current_time time of the video currently + * @param { float } currentTime time of the video currently * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIInitialized = function (current_time, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(current_time); + var resultExtTime = Video.formatFloat(currentTime); var screenSize = screen.width + "x" + screen.height; var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; diff --git a/scripts/youtube.js b/scripts/youtube.js index 8e070162..ef3dfed1 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -135,7 +135,7 @@ H5P.VideoYouTube = (function ($) { // Send xapi trigger if video progress indicates completed. var length = player.getDuration(); if (length > 0) { - var progress = H5P.Video.get_progress(player.getCurrentTime(), player.getDuration()); + var progress = H5P.Video.getProgress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { var arg = H5P.Video.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); } From 7100eb68bec9e6fee40ad7fc9d7fecc8a0343e4a Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 20 Nov 2017 11:45:32 -1000 Subject: [PATCH 054/137] Set default value of getArgsXAPIInitialized param --- scripts/video.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 872b185c..240d6a84 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -490,7 +490,10 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { * * used to retun json object sent with full screen change event to be triggered by xAPI event */ - Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality = false) { + Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { + // Set default value for quality. + quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); + // Variables used in compiling xAPI results. var dateTime = new Date(); var timeStamp = dateTime.toISOString(); @@ -499,7 +502,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; var playbackRate = rate; var volume = Video.formatFloat(volume); - var quality = (quality === false )? (height < width ? height : width) : quality; var userAgent = navigator.userAgent; var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; From 954f51f9a4671ea8c5028a7d2cd9507640271da9 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 22 Nov 2017 10:22:19 -1000 Subject: [PATCH 055/137] Change Completed to Finished --- scripts/html5.js | 10 +++++----- scripts/video.js | 4 ++-- scripts/youtube.js | 11 ++++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 4625c25b..f7c7d2c8 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -218,9 +218,9 @@ H5P.VideoHtml5 = (function ($) { if (length > 0) { var progress = H5P.Video.getProgress(video.currentTime, video.duration); if (progress >= 1) { - extraTrigger = "completed"; - extraArg = H5P.Video.getArgsXAPICompleted(video.currentTime, video.duration); - lastSend = 'completed'; + extraTrigger = "finished"; + extraArg = H5P.Video.getArgsXAPIFinished(video.currentTime, video.duration); + lastSend = 'finished'; } } } @@ -736,8 +736,8 @@ H5P.VideoHtml5 = (function ($) { self.on('volumechange', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('completed', function (event) { - this.triggerXAPI('completed', event.data); + self.on('finished', function (event) { + this.triggerXAPI('finished', event.data); }) self.on('fullscreen', function (event) { this.triggerXAPI('interacted', event.data); diff --git a/scripts/video.js b/scripts/video.js index 240d6a84..39dc5a23 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -410,13 +410,13 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { }; /** - * Video.getArgsXAPICompleted + * Video.getArgsXAPIFinished * * @param { float } currentTime time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - Video.getArgsXAPICompleted = function (currentTime, duration) { + Video.getArgsXAPIFinished = function (currentTime, duration) { var progress = Video.getProgress(currentTime, duration); var resultExtTime = Video.formatFloat(currentTime); var dateTime = new Date(); diff --git a/scripts/youtube.js b/scripts/youtube.js index ef3dfed1..31b6b989 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -132,12 +132,13 @@ H5P.VideoYouTube = (function ($) { self.trigger('paused', H5P.Video.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { - // Send xapi trigger if video progress indicates completed. + // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { var progress = H5P.Video.getProgress(player.getCurrentTime(), player.getDuration()); if (progress >= 1) { - var arg = H5P.Video.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); + var arg = H5P.Video.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); + self.trigger('finished', arg); } } } @@ -233,11 +234,11 @@ H5P.VideoYouTube = (function ($) { self.on('seeked', function (event) { this.triggerXAPI('seeked', event.data); }); - self.on('volumechange', function (event) { + self.on('volumechange', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('completed', function (event){ - this.triggerXAPI('completed', event.data); + self.on('finished', function (event){ + this.triggerXAPI('finished', event.data); }) self.on('fullscreen', function (event) { this.triggerXAPI('interacted', event.data); From f82c2cbbe44018f5425d925726214908e06a55dc Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 22 Nov 2017 10:42:44 -1000 Subject: [PATCH 056/137] Got finished working --- scripts/html5.js | 3 ++- scripts/video.js | 3 ++- scripts/youtube.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index f7c7d2c8..8db90236 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -216,7 +216,8 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - var progress = H5P.Video.getProgress(video.currentTime, video.duration); + //length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop + var progress = H5P.Video.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; extraArg = H5P.Video.getArgsXAPIFinished(video.currentTime, video.duration); diff --git a/scripts/video.js b/scripts/video.js index 39dc5a23..5379b96e 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -200,7 +200,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { */ Video.getProgress = function (currentTime, duration) { var arr, arr2; - + Video.endPlayedSegment(currentTime); + Video.playedSegmentsSegmentStart = currentTime; // Get played segments array. arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); if (Video.playedSegmentsSegmentStart != null) { diff --git a/scripts/youtube.js b/scripts/youtube.js index 31b6b989..1522aa9a 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -135,7 +135,8 @@ H5P.VideoYouTube = (function ($) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { - var progress = H5P.Video.getProgress(player.getCurrentTime(), player.getDuration()); + //length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop + var progress = H5P.Video.getProgress( length, length ); if (progress >= 1) { var arg = H5P.Video.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); self.trigger('finished', arg); From a8e7b3f87e0dce65eecd293ca16fe8dfd48f1c5a Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 22 Nov 2017 10:56:54 -1000 Subject: [PATCH 057/137] Fix whitespace --- scripts/html5.js | 2 +- scripts/youtube.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 8db90236..ef40fac2 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -216,7 +216,7 @@ H5P.VideoHtml5 = (function ($) { // Send extra trigger for giving progress on ended call to xAPI. var length = video.duration; if (length > 0) { - //length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop + // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = H5P.Video.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; diff --git a/scripts/youtube.js b/scripts/youtube.js index 1522aa9a..d83563b0 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -135,7 +135,7 @@ H5P.VideoYouTube = (function ($) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { - //length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop + // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = H5P.Video.getProgress( length, length ); if (progress >= 1) { var arg = H5P.Video.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); @@ -235,12 +235,12 @@ H5P.VideoYouTube = (function ($) { self.on('seeked', function (event) { this.triggerXAPI('seeked', event.data); }); - self.on('volumechange', function (event) { + self.on('volumechange', function (event) { this.triggerXAPI('interacted', event.data); }); - self.on('finished', function (event){ + self.on('finished', function (event) { this.triggerXAPI('finished', event.data); - }) + }); self.on('fullscreen', function (event) { this.triggerXAPI('interacted', event.data); }); From 215da893c8a276ea8782fb9670ae5d77cca142fc Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 22 Nov 2017 14:43:04 -1000 Subject: [PATCH 058/137] Whitespace --- scripts/html5.js | 2 +- scripts/youtube.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index ef40fac2..50a99a75 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -747,7 +747,7 @@ H5P.VideoHtml5 = (function ($) { this.triggerXAPI('played', event.data); }); self.on('xAPIloaded', function (event) { - this.triggerXAPI('initialized',event.data); + this.triggerXAPI('initialized', event.data); }) self.on('paused', function (event) { this.triggerXAPI('paused', event.data); diff --git a/scripts/youtube.js b/scripts/youtube.js index d83563b0..e42b3802 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -233,25 +233,25 @@ H5P.VideoYouTube = (function ($) { // xAPI extension events for video. self.on('seeked', function (event) { - this.triggerXAPI('seeked', event.data); + this.triggerXAPI('seeked', event.data); }); self.on('volumechange', function (event) { - this.triggerXAPI('interacted', event.data); + this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { - this.triggerXAPI('finished', event.data); + this.triggerXAPI('finished', event.data); }); self.on('fullscreen', function (event) { - this.triggerXAPI('interacted', event.data); + this.triggerXAPI('interacted', event.data); }); self.on('play', function (event) { - this.triggerXAPI('played', event.data); + this.triggerXAPI('played', event.data); }); self.on('xAPIloaded', function (event){ - this.triggerXAPI('initialized',event.data); + this.triggerXAPI('initialized',event.data); }); self.on('paused', function (event) { - this.triggerXAPI('paused', event.data); + this.triggerXAPI('paused', event.data); }); /** From 52256ccec42143080f75b94ec5a86cdf2e4f9958 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 22 Nov 2017 14:43:21 -1000 Subject: [PATCH 059/137] Mark verbs not currently working --- scripts/html5.js | 1 + scripts/youtube.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/html5.js b/scripts/html5.js index 50a99a75..eae544f0 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -741,6 +741,7 @@ H5P.VideoHtml5 = (function ($) { this.triggerXAPI('finished', event.data); }) self.on('fullscreen', function (event) { + // @todo: Not currently used. this.triggerXAPI('interacted', event.data); }); self.on('play', function (event) { diff --git a/scripts/youtube.js b/scripts/youtube.js index e42b3802..126bd5c0 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -236,12 +236,14 @@ H5P.VideoYouTube = (function ($) { this.triggerXAPI('seeked', event.data); }); self.on('volumechange', function (event) { + // @todo: Not currently used. this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { this.triggerXAPI('finished', event.data); }); self.on('fullscreen', function (event) { + // @todo: Not currently used. this.triggerXAPI('interacted', event.data); }); self.on('play', function (event) { From 15473c20b6a50f80ce265bf2f502d8a544eb57a4 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 22 Nov 2017 14:43:43 -1000 Subject: [PATCH 060/137] Fix html5 player firing paused before finished --- scripts/html5.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/html5.js b/scripts/html5.js index eae544f0..23b24777 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -205,7 +205,7 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && H5P.Video.seeking === false) { + if (!video.seeking && H5P.Video.seeking === false && video.currentTime !== video.duration) { extraTrigger = "paused"; extraArg = H5P.Video.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; From 15a58c8d63baa753a378014713e59d62e4cdc8ed Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 12 Feb 2018 13:04:01 -1000 Subject: [PATCH 061/137] Moved statement creation to separate file Moved all statement creation of xAPI video objects to a separate file x-api.js --- library.json | 3 + scripts/html5.js | 42 ++--- scripts/x-api.js | 430 +++++++++++++++++++++++++++++++++++++++++++++ scripts/youtube.js | 26 +-- 4 files changed, 467 insertions(+), 34 deletions(-) create mode 100644 scripts/x-api.js diff --git a/library.json b/library.json index c7daa6e2..b2470717 100644 --- a/library.json +++ b/library.json @@ -14,6 +14,9 @@ "minorVersion": 4 }, "preloadedJs": [ + { + "path": "scripts/x-api.js" + }, { "path": "scripts/youtube.js" }, diff --git a/scripts/html5.js b/scripts/html5.js index 23b24777..490da259 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -158,12 +158,12 @@ H5P.VideoHtml5 = (function ($) { var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return H5P.Video.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return H5P.VideoXapi.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; // Set duration used for xAPI statements. - H5P.Video.duration = video.duration; + H5P.VideoXapi.duration = video.duration; /** * Helps registering events. @@ -190,14 +190,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (H5P.Video.seeking === true) { - extraArg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); + if (H5P.VideoXapi.seeking === true) { + extraArg = H5P.VideoXapi.getArgsXAPISeeked(H5P.VideoXapi.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - H5P.Video.seeking = false; - H5P.Video.seeking = false; + H5P.VideoXapi.seeking = false; + H5P.VideoXapi.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.Video.getArgsXAPIPlayed(video.currentTime); + extraArg = H5P.VideoXapi.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -205,9 +205,9 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && H5P.Video.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && H5P.VideoXapi.seeking === false && video.currentTime !== video.duration) { extraTrigger = "paused"; - extraArg = H5P.Video.getArgsXAPIPaused(video.currentTime, video.duration); + extraArg = H5P.VideoXapi.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -217,10 +217,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.Video.getProgress(length, length); + var progress = H5P.VideoXapi.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; - extraArg = H5P.Video.getArgsXAPIFinished(video.currentTime, video.duration); + extraArg = H5P.VideoXapi.getArgsXAPIFinished(video.currentTime, video.duration); lastSend = 'finished'; } } @@ -233,22 +233,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.Video.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); + arg = H5P.VideoXapi.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': - if (H5P.Video.seeking === false && lastSend != h5p) { - arg = H5P.Video.getArgsXAPIPlayed(video.currentTime); + if (H5P.VideoXapi.seeking === false && lastSend != h5p) { + arg = H5P.VideoXapi.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo); + arg = H5P.VideoXapi.getArgsXAPISeeked(H5P.VideoXapi.seekedTo); lastSend = 'seeked'; - H5P.Video.seeking = false; + H5P.VideoXapi.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.Video.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); + arg = H5P.VideoXapi.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': @@ -518,12 +518,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (H5P.Video.seeking === false) { - H5P.Video.previousTime = video.currentTime; + if (H5P.VideoXapi.seeking === false) { + H5P.VideoXapi.previousTime = video.currentTime; } video.currentTime = time; - H5P.Video.seeking = true; - H5P.Video.seekedTo = time; + H5P.VideoXapi.seeking = true; + H5P.VideoXapi.seekedTo = time; }; /** diff --git a/scripts/x-api.js b/scripts/x-api.js new file mode 100644 index 00000000..44d9afc1 --- /dev/null +++ b/scripts/x-api.js @@ -0,0 +1,430 @@ +/** @namespace H5P */ +H5P.VideoXapi = (function ($) { + + var self = this; + /** + * Xapi video statement generator for H5P. + * + * @class + */ + + /** + * Generate a random GUID string used for seesionID with video xAPI statements. + */ + self.guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Format parameter as float (or null if invalid). + * + * @param {string} number Number to convert to float + * used when making arguments sent with video xAPI statments + */ + self.formatFloat = function (number) { + if (number == null) { + return null; + } + return +(parseFloat(number).toFixed(3)); + }; + + /** + * Track xAPI statement data for video events. + * @private + */ + self.previousTime = 0; + self.seekStart = null; + self.playedSegments = []; + self.playedSegmentsSegmentStart =0; + self.playedSegmentsSegmentEnd; + self.volumeChangedOn = null; + self.volumeChangedAt = 0; + self.seeking = false; + self.sessionID = this.guid(); + self.currentTime = 0; + self.seekedTo = 0; + self.duration = 0; + + /** + * Calculate video progress. + */ + self.getProgress = function (currentTime, duration) { + var arr, arr2; + self.endPlayedSegment(currentTime); + self.playedSegmentsSegmentStart = currentTime; + // Get played segments array. + arr = self.playedSegments == "" ? [] : self.playedSegments.split("[,]"); + if (self.playedSegmentsSegmentStart != null) { + arr.push(self.playedSegmentsSegmentStart + "[.]" + self.formatFloat(currentTime)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } + } + } + }); + + // Calculate progress length. + var progressLength = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progressLength += v[1] - v[0]; + } + }); + + var progress = 1 * (progressLength / duration ).toFixed(2); + + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @param {int} endTime When the current played segment ended + */ + self.endPlayedSegment = function (endTime) { + var arr; + // Need to not push in segments that happen from multiple triggers during scrubbing + if (endTime !== self.playedSegmentsSegmentStart && Math.abs(endTime - self.playedSegmentsSegmentStart) > 1 ) { + // Don't run if called too closely to each other. + arr = self.playedSegments == "" ? [] : self.playedSegments.split("[,]"); + arr.push(self.formatFloat(self.playedSegmentsSegmentStart) + "[.]" + self.formatFloat(endTime)); + self.playedSegments = arr.join("[,]"); + self.playedSegmentsSegmentEnd = endTime; + self.playedSegmentsSegmentStart = null; + } + }; + + /** + * self.getArgsXAPIPaused + * + * @param {type} currentTime + * @returns {json object} + */ + self.getArgsXAPIPaused = function (currentTime, duration) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = self.formatFloat(currentTime); + self.endPlayedSegment(resultExtTime); + self.playedSegmentsSegmentStart = resultExtTime; + var progress = self.getProgress(currentTime, duration); + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/paused", + "display": { + "en-US": "paused" + } + }, + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": self.playedSegments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID + } + }, + "timestamp" : timeStamp + }; + }; + + /** + * self.getArgsXAPIPlayed + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with event to be triggered by xAPI event + */ + self.getArgsXAPIPlayed = function (currentTime) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = self.formatFloat(currentTime); + self.playedSegmentsSegmentStart = resultExtTime; + self.seekStart = null; + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/played", + "display": { + "en-US": "played" + } + }, + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID + } + }, + "timestamp": timeStamp + }; + }; + + /** + * self.getArgsXAPISeeked + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with seeked event to be triggered by xAPI event + */ + self.getArgsXAPISeeked = function (currentTime) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = self.formatFloat(currentTime); + self.seekStart = resultExtTime; + self.endPlayedSegment(self.previousTime); + self.playedSegmentsSegmentStart = self.seekStart; + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/seeked", + "display": { + "en-US": "seeked" + } + }, + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": self.previousTime, + "https://w3id.org/xapi/video/extensions/time-to": self.seekStart + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID + } + }, + "timestamp" : timeStamp + }; + }; + + /** + * self.getArgsXAPIVolumeChanged + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with volume change event to be triggered by xAPI event + */ + self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + self.volumeChangedAt = self.formatFloat(currentTime); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = self.formatFloat(volume); + } + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/interacted", + "display": { + "en-US": "interacted" + } + }, + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": self.volumeChangedAt + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange + } + }, + "timestamp" : timeStamp + }; + }; + + /** + * self.getArgsXAPIFinished + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with complete event to be triggered by xAPI event + */ + self.getArgsXAPIFinished = function (currentTime, duration) { + var progress = self.getProgress(currentTime, duration); + var resultExtTime = self.formatFloat(currentTime); + var dateTime = new Date(); + self.endPlayedSegment(resultExtTime); + var timeStamp = dateTime.toISOString(); + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/finished", + "display": { + "en-US": "finished" + } + }, + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, + "https://w3id.org/xapi/video/extensions/progress": progress, + "https://w3id.org/xapi/video/extensions/played-segments": self.playedSegments + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID + } + }, + "timestamp" : timeStamp + }; + }; + + /** + * self.getArgsXAPIFullScreen + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = self.formatFloat(currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/interacted", + "display": { + "en-US": "interacted" + } + }, + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime + } + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID, + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize + } + }, + "timestamp" : timeStamp + }; + }; + + /** + * self.getArgsXAPIInitialized + * + * @param { float } currentTime time of the video currently + * + * used to retun json object sent with full screen change event to be triggered by xAPI event + */ + self.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { + // Set default value for quality. + quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); + + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = self.formatFloat(currentTime); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = self.formatFloat(volume); + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/initialized", + "display": { + "en-US": "initialized" + } + }, + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/full-screen": state, + "https://w3id.org/xapi/video/extensions/screen-size": screenSize, + "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, + "https://w3id.org/xapi/video/extensions/quality": quality, + "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, + "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, + "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", + "https://w3id.org/xapi/video/extensions/user-agent": userAgent, + "https://w3id.org/xapi/video/extensions/volume": volume, + "https://w3id.org/xapi/video/extensions/session-id": self.sessionID + } + }, + "timestamp": timeStamp + }; + }; + + + return self; +})(H5P.jQuery ); diff --git a/scripts/youtube.js b/scripts/youtube.js index 126bd5c0..41db2253 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -120,25 +120,25 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data == 1) { // Get and send play call when not seeking. - if (H5P.Video.seeking === false) { - self.trigger('play', H5P.Video.getArgsXAPIPlayed(player.getCurrentTime())); + if (H5P.VideoXapi.xAPI.seeking === false) { + self.trigger('play', H5P.VideoXapi.xAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', H5P.Video.getArgsXAPISeeked(H5P.Video.seekedTo)); - H5P.Video.seeking = false; + self.trigger('seeked', H5P.VideoXapi.xAPI.getArgsXAPISeeked(H5P.VideoXapi.xAPI.seekedTo)); + H5P.VideoXapi.xAPI.seeking = false; } } else if (state.data == 2) { // This is a paused event. - if (H5P.Video.seeking === false) { - self.trigger('paused', H5P.Video.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); + if (H5P.VideoXapi.xAPI.seeking === false) { + self.trigger('paused', H5P.VideoXapi.xAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.Video.getProgress( length, length ); + var progress = H5P.VideoXapi.xAPI.getProgress( length, length ); if (progress >= 1) { - var arg = H5P.Video.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); + var arg = H5P.VideoXapi.xAPI.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); self.trigger('finished', arg); } } @@ -227,7 +227,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return H5P.Video.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return H5P.VideoXapi.xAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; @@ -372,12 +372,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (H5P.Video.seeking === false) { - H5P.Video.previousTime = player.getCurrentTime(); + if (H5P.VideoXapi.xAPI.seeking === false) { + H5P.VideoXapi.xAPI.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - H5P.Video.seeking = time; - H5P.Video.seeking = true; + H5P.VideoXapi.xAPI.seeking = time; + H5P.VideoXapi.xAPI.seeking = true; }; /** From b1e9a27129a410b610d11e986364160184635db0 Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 12 Feb 2018 14:07:19 -1000 Subject: [PATCH 062/137] Move of xapi statement to new file This should have been commited with the last commit. The statement generation was moved out of this file to the x-api.js file. --- scripts/video.js | 373 ----------------------------------------------- 1 file changed, 373 deletions(-) diff --git a/scripts/video.js b/scripts/video.js index 5379b96e..119054f4 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -156,379 +156,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } - /** - * Generate a random GUID string used for seesionID with video xAPI statements. - */ - Video.guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - * used when making arguments sent with video xAPI statments - */ - Video.formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(3)); - }; - - /** - * Track xAPI statement data for video events. - * @private - */ - Video.previousTime = 0; - Video.seekStart = null; - Video.playedSegments = []; - Video.playedSegmentsSegmentStart =0; - Video.playedSegmentsSegmentEnd; - Video.volumeChangedOn = null; - Video.volumeChangedAt = 0; - Video.seeking = false; - Video.sessionID = Video.guid(); - Video.currentTime = 0; - Video.seekedTo = 0; - - /** - * Calculate video progress. - */ - Video.getProgress = function (currentTime, duration) { - var arr, arr2; - Video.endPlayedSegment(currentTime); - Video.playedSegmentsSegmentStart = currentTime; - // Get played segments array. - arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); - if (Video.playedSegmentsSegmentStart != null) { - arr.push(Video.playedSegmentsSegmentStart + "[.]" + Video.formatFloat(currentTime)); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - // Sort the array. - arr2.sort(function (a,b) { - return a[0] - b[0]; - }); - - // Normalize the segments. - arr2.forEach(function (v,i) { - if (i > 0) { - // Overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - // Calculate progress length. - var progressLength = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progressLength += v[1] - v[0]; - } - }); - - var progress = 1 * (progressLength / duration ).toFixed(2); - - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} endTime When the current played segment ended - */ - Video.endPlayedSegment = function (endTime) { - var arr; - // Need to not push in segments that happen from multiple triggers during scrubbing - if (endTime !== Video.playedSegmentsSegmentStart && Math.abs(endTime - Video.playedSegmentsSegmentStart) > 1 ) { - // Don't run if called too closely to each other. - arr = Video.playedSegments == "" ? [] : Video.playedSegments.split("[,]"); - arr.push(Video.formatFloat(Video.playedSegmentsSegmentStart) + "[.]" + Video.formatFloat(endTime)); - Video.playedSegments = arr.join("[,]"); - Video.playedSegmentsSegmentEnd = endTime; - Video.playedSegmentsSegmentStart = null; - } - }; - - /** - * Video.getArgsXAPIPaused - * - * @param {type} currentTime - * @returns {json object} - */ - Video.getArgsXAPIPaused = function (currentTime, duration) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(currentTime); - Video.endPlayedSegment(resultExtTime); - Video.playedSegmentsSegmentStart = resultExtTime; - var progress = Video.getProgress(currentTime, duration); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Video.getArgsXAPIPlayed - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with event to be triggered by xAPI event - */ - Video.getArgsXAPIPlayed = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(currentTime); - Video.playedSegmentsSegmentStart = resultExtTime; - Video.seekStart = null; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * Video.getArgsXAPISeeked - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with seeked event to be triggered by xAPI event - */ - Video.getArgsXAPISeeked = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(currentTime); - Video.seekStart = resultExtTime; - Video.endPlayedSegment(Video.previousTime); - Video.playedSegmentsSegmentStart = Video.seekStart; - - return { - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": Video.previousTime, - "https://w3id.org/xapi/video/extensions/time-to": Video.seekStart - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Video.getArgsXAPIVolumeChanged - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with volume change event to be triggered by xAPI event - */ - Video.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - Video.volumeChangedAt = Video.formatFloat(currentTime); - var isMuted = muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = Video.formatFloat(volume); - } - - return { - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": Video.volumeChangedAt - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Video.getArgsXAPIFinished - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with complete event to be triggered by xAPI event - */ - Video.getArgsXAPIFinished = function (currentTime, duration) { - var progress = Video.getProgress(currentTime, duration); - var resultExtTime = Video.formatFloat(currentTime); - var dateTime = new Date(); - Video.endPlayedSegment(resultExtTime); - var timeStamp = dateTime.toISOString(); - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": Video.playedSegments - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Video.getArgsXAPIFullScreen - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with full screen change event to be triggered by xAPI event - */ - Video.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = width + "x" + height; - - return { - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * Video.getArgsXAPIInitialized - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with full screen change event to be triggered by xAPI event - */ - Video.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { - // Set default value for quality. - quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); - - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = Video.formatFloat(currentTime); - var screenSize = screen.width + "x" + screen.height; - var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; - var playbackRate = rate; - var volume = Video.formatFloat(volume); - var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - - return { - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": Video.sessionID - } - }, - "timestamp": timeStamp - }; - }; // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); From 28b9654cca9f0e4576fb29f200da018e3613230b Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 21 Feb 2018 11:49:16 -1000 Subject: [PATCH 063/137] Rename class VideoXapi to VideoXAPI to match capitalization rules --- scripts/html5.js | 42 +++++++++++++++++++++--------------------- scripts/x-api.js | 12 ++++++------ scripts/youtube.js | 26 +++++++++++++------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 490da259..611bf1f2 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -158,12 +158,12 @@ H5P.VideoHtml5 = (function ($) { var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return H5P.VideoXapi.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return H5P.VideoXAPI.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; // Set duration used for xAPI statements. - H5P.VideoXapi.duration = video.duration; + H5P.VideoXAPI.duration = video.duration; /** * Helps registering events. @@ -190,14 +190,14 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (H5P.VideoXapi.seeking === true) { - extraArg = H5P.VideoXapi.getArgsXAPISeeked(H5P.VideoXapi.seekedTo); + if (H5P.VideoXAPI.seeking === true) { + extraArg = H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - H5P.VideoXapi.seeking = false; - H5P.VideoXapi.seeking = false; + H5P.VideoXAPI.seeking = false; + H5P.VideoXAPI.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.VideoXapi.getArgsXAPIPlayed(video.currentTime); + extraArg = H5P.VideoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -205,9 +205,9 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && H5P.VideoXapi.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && H5P.VideoXAPI.seeking === false && video.currentTime !== video.duration) { extraTrigger = "paused"; - extraArg = H5P.VideoXapi.getArgsXAPIPaused(video.currentTime, video.duration); + extraArg = H5P.VideoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -217,10 +217,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.VideoXapi.getProgress(length, length); + var progress = H5P.VideoXAPI.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; - extraArg = H5P.VideoXapi.getArgsXAPIFinished(video.currentTime, video.duration); + extraArg = H5P.VideoXAPI.getArgsXAPIFinished(video.currentTime, video.duration); lastSend = 'finished'; } } @@ -233,22 +233,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.VideoXapi.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); + arg = H5P.VideoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': - if (H5P.VideoXapi.seeking === false && lastSend != h5p) { - arg = H5P.VideoXapi.getArgsXAPIPlayed(video.currentTime); + if (H5P.VideoXAPI.seeking === false && lastSend != h5p) { + arg = H5P.VideoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = H5P.VideoXapi.getArgsXAPISeeked(H5P.VideoXapi.seekedTo); + arg = H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo); lastSend = 'seeked'; - H5P.VideoXapi.seeking = false; + H5P.VideoXAPI.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.VideoXapi.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); + arg = H5P.VideoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': @@ -518,12 +518,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (H5P.VideoXapi.seeking === false) { - H5P.VideoXapi.previousTime = video.currentTime; + if (H5P.VideoXAPI.seeking === false) { + H5P.VideoXAPI.previousTime = video.currentTime; } video.currentTime = time; - H5P.VideoXapi.seeking = true; - H5P.VideoXapi.seekedTo = time; + H5P.VideoXAPI.seeking = true; + H5P.VideoXAPI.seekedTo = time; }; /** diff --git a/scripts/x-api.js b/scripts/x-api.js index 44d9afc1..cbe172cf 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -1,13 +1,13 @@ /** @namespace H5P */ -H5P.VideoXapi = (function ($) { - +H5P.VideoXAPI = (function ($) { + var self = this; /** * Xapi video statement generator for H5P. * * @class */ - + /** * Generate a random GUID string used for seesionID with video xAPI statements. */ @@ -242,7 +242,7 @@ H5P.VideoXapi = (function ($) { }; /** - * self.getArgsXAPIVolumeChanged + * self.getArgsXAPIVolumeChanged * * @param { float } currentTime time of the video currently * @@ -424,7 +424,7 @@ H5P.VideoXapi = (function ($) { "timestamp": timeStamp }; }; - - + + return self; })(H5P.jQuery ); diff --git a/scripts/youtube.js b/scripts/youtube.js index 41db2253..02e9029f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -120,25 +120,25 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data == 1) { // Get and send play call when not seeking. - if (H5P.VideoXapi.xAPI.seeking === false) { - self.trigger('play', H5P.VideoXapi.xAPI.getArgsXAPIPlayed(player.getCurrentTime())); + if (H5P.VideoXAPI.seeking === false) { + self.trigger('play', H5P.VideoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', H5P.VideoXapi.xAPI.getArgsXAPISeeked(H5P.VideoXapi.xAPI.seekedTo)); - H5P.VideoXapi.xAPI.seeking = false; + self.trigger('seeked', H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo)); + H5P.VideoXAPI.seeking = false; } } else if (state.data == 2) { // This is a paused event. - if (H5P.VideoXapi.xAPI.seeking === false) { - self.trigger('paused', H5P.VideoXapi.xAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); + if (H5P.VideoXAPI.seeking === false) { + self.trigger('paused', H5P.VideoXAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.VideoXapi.xAPI.getProgress( length, length ); + var progress = H5P.VideoXAPI.getProgress( length, length ); if (progress >= 1) { - var arg = H5P.VideoXapi.xAPI.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); + var arg = H5P.VideoXAPI.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); self.trigger('finished', arg); } } @@ -227,7 +227,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return H5P.VideoXapi.xAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return H5P.VideoXAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; @@ -372,12 +372,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (H5P.VideoXapi.xAPI.seeking === false) { - H5P.VideoXapi.xAPI.previousTime = player.getCurrentTime(); + if (H5P.VideoXAPI.seeking === false) { + H5P.VideoXAPI.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - H5P.VideoXapi.xAPI.seeking = time; - H5P.VideoXapi.xAPI.seeking = true; + H5P.VideoXAPI.seeking = time; + H5P.VideoXAPI.seeking = true; }; /** From 5eb74929ec25d9f8815013f579baf658182d0f5e Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 21 Feb 2018 13:38:11 -1000 Subject: [PATCH 064/137] Bugfix in seekedTo time calculation --- scripts/html5.js | 1 - scripts/youtube.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 611bf1f2..64c0124a 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -195,7 +195,6 @@ H5P.VideoHtml5 = (function ($) { extraTrigger = 'seeked'; lastSend = 'seeked'; H5P.VideoXAPI.seeking = false; - H5P.VideoXAPI.seeking = false; } else if (lastSend !== 'play') { extraArg = H5P.VideoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; diff --git a/scripts/youtube.js b/scripts/youtube.js index 02e9029f..f8e4774a 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -376,7 +376,7 @@ H5P.VideoYouTube = (function ($) { H5P.VideoXAPI.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - H5P.VideoXAPI.seeking = time; + H5P.VideoXAPI.seekedTo = time; H5P.VideoXAPI.seeking = true; }; From 9fe5f95fb20afefe04f6f705fb33e6c58c5fa811 Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 22 Feb 2018 09:30:56 -1000 Subject: [PATCH 065/137] =?UTF-8?q?Don=E2=80=99t=20include=20extensions=20?= =?UTF-8?q?for=20non=20set=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/x-api.js | 99 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index cbe172cf..d508334c 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -130,7 +130,16 @@ H5P.VideoXAPI = (function ($) { self.endPlayedSegment(resultExtTime); self.playedSegmentsSegmentStart = resultExtTime; var progress = self.getProgress(currentTime, duration); - + var extensions = {}; + if ( typeof resultExtTime != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + } + if ( typeof progress != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + } + if ( typeof self.playedSegments != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/progress"] = self.playedSegments + } return { "verb": { "id": "https://w3id.org/xapi/video/verbs/paused", @@ -139,11 +148,7 @@ H5P.VideoXAPI = (function ($) { } }, "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": self.playedSegments - } + "extensions": extensions }, "context": { "contextActivities": { @@ -300,6 +305,17 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); self.endPlayedSegment(resultExtTime); var timeStamp = dateTime.toISOString(); + //make sure extensions exist + var extensions = {}; + if ( typeof resultExtTime != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + } + if ( typeof progress != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + } + if ( typeof self.playedSegments != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = self.playedSegments + } return { "verb": { @@ -309,11 +325,7 @@ H5P.VideoXAPI = (function ($) { } }, "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - "https://w3id.org/xapi/video/extensions/progress": progress, - "https://w3id.org/xapi/video/extensions/played-segments": self.playedSegments - } + "extensions": extensions }, "context": { "contextActivities": { @@ -343,7 +355,20 @@ H5P.VideoXAPI = (function ($) { var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; - + //make sure extensions exist + var extensions = {}; + if ( typeof self.sessionID != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/session-id"] = self.sessionID + } + if ( typeof state != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + } + if ( typeof screenSize != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + } + if ( typeof playbackSize != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + } return { "verb": { "id": "http://adlnet.gov/expapi/verbs/interacted", @@ -362,12 +387,7 @@ H5P.VideoXAPI = (function ($) { "id": "https://w3id.org/xapi/video" }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID, - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize - } + "extensions": extensions }, "timestamp" : timeStamp }; @@ -394,7 +414,35 @@ H5P.VideoXAPI = (function ($) { var volume = self.formatFloat(volume); var userAgent = navigator.userAgent; var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - + //make sure extensions exist + var extensions = {}; + if ( typeof state != "undefined" && state != false){ + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + } + if ( typeof screenSize != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + } + if ( typeof playbackSize != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + } + if ( typeof self.sessionID != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/session-id"] = self.sessionID + } + if ( typeof quality != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/quality"] = quality + } + if ( typeof ccEnabled != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/cc-enabled"] = ccEnabled + } + if ( typeof playbackRate != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/speed"] = playbackRate + } + if ( typeof userAgent != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/user-agent"] = userAgent + } + if ( typeof volume != "undefined" ){ + extensions["https://w3id.org/xapi/video/extensions/volume"] = volume + } return { "verb": { "id": "http://adlnet.gov/expapi/verbs/initialized", @@ -408,18 +456,7 @@ H5P.VideoXAPI = (function ($) { "id": "https://w3id.org/xapi/video" }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/full-screen": state, - "https://w3id.org/xapi/video/extensions/screen-size": screenSize, - "https://w3id.org/xapi/video/extensions/video-playback-size": playbackSize, - "https://w3id.org/xapi/video/extensions/quality": quality, - "https://w3id.org/xapi/video/extensions/cc-enabled": ccEnabled, - "https://w3id.org/xapi/video/extensions/cc-subtitle-lang": ccLanguage, - "https://w3id.org/xapi/video/extensions/speed": playbackRate + "x", - "https://w3id.org/xapi/video/extensions/user-agent": userAgent, - "https://w3id.org/xapi/video/extensions/volume": volume, - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID - } + "extensions": extensions }, "timestamp": timeStamp }; From 58d5638c41a0a22d0b322a42f67b3b2dadbfc8a2 Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 22 Feb 2018 10:45:29 -1000 Subject: [PATCH 066/137] Changed Finished to Completed Changed to match video profile. Completed statement can be differentiated from H5P interactive video completed statement due to object.id. --- scripts/html5.js | 6 ++++-- scripts/x-api.js | 8 ++++---- scripts/youtube.js | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 64c0124a..65b61313 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -219,7 +219,7 @@ H5P.VideoHtml5 = (function ($) { var progress = H5P.VideoXAPI.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; - extraArg = H5P.VideoXAPI.getArgsXAPIFinished(video.currentTime, video.duration); + extraArg = H5P.VideoXAPI.getArgsXAPICompleted(video.currentTime, video.duration); lastSend = 'finished'; } } @@ -737,7 +737,9 @@ H5P.VideoHtml5 = (function ($) { this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { - this.triggerXAPI('finished', event.data); + //triggered as finished to be seperate from H5Ps completed, + //but statement is sent as completed and differentiated by object.id + this.triggerXAPI('completed', event.data); }) self.on('fullscreen', function (event) { // @todo: Not currently used. diff --git a/scripts/x-api.js b/scripts/x-api.js index d508334c..668efb9e 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -293,13 +293,13 @@ H5P.VideoXAPI = (function ($) { }; /** - * self.getArgsXAPIFinished + * self.getArgsXAPICompleted * * @param { float } currentTime time of the video currently * * used to retun json object sent with complete event to be triggered by xAPI event */ - self.getArgsXAPIFinished = function (currentTime, duration) { + self.getArgsXAPICompleted = function (currentTime, duration) { var progress = self.getProgress(currentTime, duration); var resultExtTime = self.formatFloat(currentTime); var dateTime = new Date(); @@ -319,9 +319,9 @@ H5P.VideoXAPI = (function ($) { return { "verb": { - "id": "http://adlnet.gov/expapi/verbs/finished", + "id": "http://adlnet.gov/expapi/verbs/completed", "display": { - "en-US": "finished" + "en-US": "completed" } }, "result": { diff --git a/scripts/youtube.js b/scripts/youtube.js index f8e4774a..f31495d4 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -138,7 +138,7 @@ H5P.VideoYouTube = (function ($) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = H5P.VideoXAPI.getProgress( length, length ); if (progress >= 1) { - var arg = H5P.VideoXAPI.getArgsXAPIFinished(player.getCurrentTime(), player.getDuration()); + var arg = H5P.VideoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); self.trigger('finished', arg); } } @@ -240,7 +240,9 @@ H5P.VideoYouTube = (function ($) { this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { - this.triggerXAPI('finished', event.data); + //triggered as finished to be seperate from H5Ps completed, + //but statement is sent as completed and differentiated by object.id + this.triggerXAPI('completed', event.data); }); self.on('fullscreen', function (event) { // @todo: Not currently used. From 02607f0733be65fb3d9d7cea1f522feb0ac39743 Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 22 Feb 2018 13:09:29 -1000 Subject: [PATCH 067/137] Adjustments to xapi statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added some items to completed per Jon’s request and ensured decimals in seeked were being fixed to a decimal count. --- scripts/x-api.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 668efb9e..c8b01334 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -28,7 +28,7 @@ H5P.VideoXAPI = (function ($) { if (number == null) { return null; } - return +(parseFloat(number).toFixed(3)); + return +(parseFloat(number).toFixed(2)); }; /** @@ -138,7 +138,7 @@ H5P.VideoXAPI = (function ($) { extensions["https://w3id.org/xapi/video/extensions/progress"] = progress } if ( typeof self.playedSegments != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/progress"] = self.playedSegments + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = self.playedSegments } return { "verb": { @@ -216,8 +216,8 @@ H5P.VideoXAPI = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = self.formatFloat(currentTime); self.seekStart = resultExtTime; - self.endPlayedSegment(self.previousTime); - self.playedSegmentsSegmentStart = self.seekStart; + self.endPlayedSegment(self.previousTime.toFixed(2)); + self.playedSegmentsSegmentStart = self.seekStart.toFixed(2); return { "verb": { @@ -228,8 +228,8 @@ H5P.VideoXAPI = (function ($) { }, "result": { "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": self.previousTime, - "https://w3id.org/xapi/video/extensions/time-to": self.seekStart + "https://w3id.org/xapi/video/extensions/time-from": self.previousTime.toFixed(2), + "https://w3id.org/xapi/video/extensions/time-to": self.seekStart.toFixed(2) } }, "context": { @@ -325,7 +325,9 @@ H5P.VideoXAPI = (function ($) { } }, "result": { - "extensions": extensions + "extensions": extensions, + "success" : true, + "duration" : duration }, "context": { "contextActivities": { From a3d024cc15320b51478a012d16bdace56ff0db91 Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 22 Feb 2018 13:48:12 -1000 Subject: [PATCH 068/137] Convert duration in completed to ISO8601 --- scripts/x-api.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index c8b01334..348d78b9 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -30,6 +30,42 @@ H5P.VideoXAPI = (function ($) { } return +(parseFloat(number).toFixed(2)); }; + /** + * Format time to ISO8601 time + * + * @param {float} time Number to convert to ISO8601 string + */ + self.time_to_iso8601_duration = function( time ) { + var units = { + "Y" : (365*24*3600), + "D" : (24*3600), + "H" : 3600, + "M" : 60, + "S" : 1, + } + var time_names = [ "H", "M", "S" ]; + + var str = "P"; + var istime = false; + + for(var unitName in units ) { + if( units.hasOwnProperty(unitName)) { + var unit = units[unitName]; + var quot = parseInt( time / unit); + var time = time - (quot * unit); + unit = quot; + if (unit > 0) { + if (!istime && (time_names.indexOf(unitName) > -1) ) { // There may be a better way to do this + str += "T"; + istime = true; + } + str += ''+unit+'' + unitName; + } + } + } + + return str; + } /** * Track xAPI statement data for video events. @@ -327,7 +363,7 @@ H5P.VideoXAPI = (function ($) { "result": { "extensions": extensions, "success" : true, - "duration" : duration + "duration" : self.time_to_iso8601_duration( duration ) }, "context": { "contextActivities": { From c516b82186937b9a16608d2adc0d11a36819e649 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 22 Feb 2018 16:50:25 -1000 Subject: [PATCH 069/137] Refactor VideoXAPI to a helper object --- scripts/html5.js | 46 +- scripts/x-api.js | 1039 ++++++++++++++++++++++++-------------------- scripts/youtube.js | 40 +- 3 files changed, 615 insertions(+), 510 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 65b61313..63a22fe6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -12,6 +12,12 @@ H5P.VideoHtml5 = (function ($) { function Html5(sources, options, l10n) { var self = this; + /** + * xAPI Helper. + * @private + */ + var videoXAPI = new H5P.VideoXAPI(self); + /** * Displayed when the video is buffering * @private @@ -158,12 +164,12 @@ H5P.VideoHtml5 = (function ($) { var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return H5P.VideoXAPI.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return videoXAPI.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; // Set duration used for xAPI statements. - H5P.VideoXAPI.duration = video.duration; + videoXAPI.duration = video.duration; /** * Helps registering events. @@ -190,13 +196,13 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (H5P.VideoXAPI.seeking === true) { - extraArg = H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo); + if (videoXAPI.seeking === true) { + extraArg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - H5P.VideoXAPI.seeking = false; + videoXAPI.seeking = false; } else if (lastSend !== 'play') { - extraArg = H5P.VideoXAPI.getArgsXAPIPlayed(video.currentTime); + extraArg = videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -204,9 +210,9 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && H5P.VideoXAPI.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && videoXAPI.seeking === false && video.currentTime !== video.duration) { extraTrigger = "paused"; - extraArg = H5P.VideoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); + extraArg = videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -216,10 +222,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.VideoXAPI.getProgress(length, length); + var progress = videoXAPI.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; - extraArg = H5P.VideoXAPI.getArgsXAPICompleted(video.currentTime, video.duration); + extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration); lastSend = 'finished'; } } @@ -232,22 +238,22 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = H5P.VideoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); + arg = videoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': - if (H5P.VideoXAPI.seeking === false && lastSend != h5p) { - arg = H5P.VideoXAPI.getArgsXAPIPlayed(video.currentTime); + if (videoXAPI.seeking === false && lastSend != h5p) { + arg = videoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo); + arg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); lastSend = 'seeked'; - H5P.VideoXAPI.seeking = false; + videoXAPI.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = H5P.VideoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); + arg = videoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': @@ -517,12 +523,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (H5P.VideoXAPI.seeking === false) { - H5P.VideoXAPI.previousTime = video.currentTime; + if (videoXAPI.seeking === false) { + videoXAPI.previousTime = video.currentTime; } video.currentTime = time; - H5P.VideoXAPI.seeking = true; - H5P.VideoXAPI.seekedTo = time; + videoXAPI.seeking = true; + videoXAPI.seekedTo = time; }; /** diff --git a/scripts/x-api.js b/scripts/x-api.js index 348d78b9..2396eb18 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -1,505 +1,594 @@ /** @namespace H5P */ H5P.VideoXAPI = (function ($) { - var self = this; - /** + /** * Xapi video statement generator for H5P. * * @class + * @param {Object} instance Parent H5P.Video{YouTube|Html5|Flash} + * video object generating xAPI statements */ + function XAPI(instance) { + var self = this; - /** - * Generate a random GUID string used for seesionID with video xAPI statements. - */ - self.guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - - /** - * Format parameter as float (or null if invalid). - * - * @param {string} number Number to convert to float - * used when making arguments sent with video xAPI statments - */ - self.formatFloat = function (number) { - if (number == null) { - return null; - } - return +(parseFloat(number).toFixed(2)); - }; - /** - * Format time to ISO8601 time - * - * @param {float} time Number to convert to ISO8601 string - */ - self.time_to_iso8601_duration = function( time ) { - var units = { - "Y" : (365*24*3600), - "D" : (24*3600), - "H" : 3600, - "M" : 60, - "S" : 1, - } - var time_names = [ "H", "M", "S" ]; - - var str = "P"; - var istime = false; - - for(var unitName in units ) { - if( units.hasOwnProperty(unitName)) { - var unit = units[unitName]; - var quot = parseInt( time / unit); - var time = time - (quot * unit); - unit = quot; - if (unit > 0) { - if (!istime && (time_names.indexOf(unitName) > -1) ) { // There may be a better way to do this - str += "T"; - istime = true; - } - str += ''+unit+'' + unitName; - } - } - } + /** + * Variables to track time values from the video player. + * + * @public + */ + self.previousTime = 0; + self.seeking = false; + self.seekedTo = 0; + self.duration = 0; - return str; - } + /** + * Variables to track internal video state. + * + * @private + */ + var videoInstance = instance; + var seekStart = null; + var playedSegments = []; + var playedSegmentsSegmentStart =0; + var playedSegmentsSegmentEnd; + var volumeChangedOn = null; + var volumeChangedAt = 0; + var sessionID = guid(); + var currentTime = 0; - /** - * Track xAPI statement data for video events. - * @private - */ - self.previousTime = 0; - self.seekStart = null; - self.playedSegments = []; - self.playedSegmentsSegmentStart =0; - self.playedSegmentsSegmentEnd; - self.volumeChangedOn = null; - self.volumeChangedAt = 0; - self.seeking = false; - self.sessionID = this.guid(); - self.currentTime = 0; - self.seekedTo = 0; - self.duration = 0; - - /** - * Calculate video progress. - */ - self.getProgress = function (currentTime, duration) { - var arr, arr2; - self.endPlayedSegment(currentTime); - self.playedSegmentsSegmentStart = currentTime; - // Get played segments array. - arr = self.playedSegments == "" ? [] : self.playedSegments.split("[,]"); - if (self.playedSegmentsSegmentStart != null) { - arr.push(self.playedSegmentsSegmentStart + "[.]" + self.formatFloat(currentTime)); - } - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); - - // Sort the array. - arr2.sort(function (a,b) { - return a[0] - b[0]; - }); - - // Normalize the segments. - arr2.forEach(function (v,i) { - if (i > 0) { - // Overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } - }); - - // Calculate progress length. - var progressLength = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progressLength += v[1] - v[0]; - } - }); - - var progress = 1 * (progressLength / duration ).toFixed(2); - - return progress; - }; - - /** - * Add a played segment to the array of already played segments. - * - * @param {int} endTime When the current played segment ended - */ - self.endPlayedSegment = function (endTime) { - var arr; - // Need to not push in segments that happen from multiple triggers during scrubbing - if (endTime !== self.playedSegmentsSegmentStart && Math.abs(endTime - self.playedSegmentsSegmentStart) > 1 ) { - // Don't run if called too closely to each other. - arr = self.playedSegments == "" ? [] : self.playedSegments.split("[,]"); - arr.push(self.formatFloat(self.playedSegmentsSegmentStart) + "[.]" + self.formatFloat(endTime)); - self.playedSegments = arr.join("[,]"); - self.playedSegmentsSegmentEnd = endTime; - self.playedSegmentsSegmentStart = null; - } - }; - - /** - * self.getArgsXAPIPaused - * - * @param {type} currentTime - * @returns {json object} - */ - self.getArgsXAPIPaused = function (currentTime, duration) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = self.formatFloat(currentTime); - self.endPlayedSegment(resultExtTime); - self.playedSegmentsSegmentStart = resultExtTime; - var progress = self.getProgress(currentTime, duration); - var extensions = {}; - if ( typeof resultExtTime != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + + /** + * Generates "initialized" xAPI statement (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#231-initialized + * + * @public + * @param {Number} currentTime time of the video currently + * @param {Number} width [description] + * @param {Number} height [description] + * @param {Number} rate [description] + * @param {number} volume [description] + * @param {Boolean} ccEnabled [description] + * @param {String} ccLanguage [description] + * @param {String} quality [description] + * @returns {Object} JSON xAPI statement + * + */ + self.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { + // Set default value for quality. + quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); + + // Variables used in compiling xAPI results. + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(currentTime); + var screenSize = screen.width + "x" + screen.height; + var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var playbackRate = rate; + var volume = formatFloat(volume); + var userAgent = navigator.userAgent; + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + + var extensions = {}; + if (typeof state !== "undefined" && state != false) { + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + } + if (typeof screenSize !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + } + if (typeof playbackSize !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + } + if (typeof sessionID !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/session-id"] = sessionID + } + if (typeof quality !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/quality"] = quality + } + if (typeof ccEnabled !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/cc-enabled"] = ccEnabled + } + if (typeof playbackRate !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/speed"] = playbackRate + } + if (typeof userAgent !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/user-agent"] = userAgent + } + if (typeof volume !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/volume"] = volume + } + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/initialized", + "display": { + "en-US": "initialized" } - if ( typeof progress != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + }, + "object": getXAPIObject(), + "context" : { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": extensions + }, + "timestamp": timeStamp + }; + }; + + /** + * Generates "played" xAPI statement. + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#232-played + * + * @public + * @param {Number} currentTime time of the video currently + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPIPlayed = function (currentTime) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + + var resultExtTime = formatFloat(currentTime); + + playedSegmentsSegmentStart = resultExtTime; + seekStart = null; + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/played", + "display": { + "en-US": "played" } - if ( typeof self.playedSegments != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = self.playedSegments + }, + "object": getXAPIObject(), + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime, } - return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/paused", - "display": { - "en-US": "paused" - } - }, - "result": { - "extensions": extensions - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * self.getArgsXAPIPlayed - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with event to be triggered by xAPI event - */ - self.getArgsXAPIPlayed = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = self.formatFloat(currentTime); - self.playedSegmentsSegmentStart = resultExtTime; - self.seekStart = null; - - return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/played", - "display": { - "en-US": "played" - } - }, - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID - } - }, - "timestamp": timeStamp - }; - }; - - /** - * self.getArgsXAPISeeked - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with seeked event to be triggered by xAPI event - */ - self.getArgsXAPISeeked = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = self.formatFloat(currentTime); - self.seekStart = resultExtTime; - self.endPlayedSegment(self.previousTime.toFixed(2)); - self.playedSegmentsSegmentStart = self.seekStart.toFixed(2); - - return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/seeked", - "display": { - "en-US": "seeked" - } - }, - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": self.previousTime.toFixed(2), - "https://w3id.org/xapi/video/extensions/time-to": self.seekStart.toFixed(2) - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * self.getArgsXAPIVolumeChanged - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with volume change event to be triggered by xAPI event - */ - self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - self.volumeChangedAt = self.formatFloat(currentTime); - var isMuted = muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = self.formatFloat(volume); + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID } + }, + "timestamp": timeStamp + }; + }; - return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/interacted", - "display": { - "en-US": "interacted" - } - }, - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": self.volumeChangedAt - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * self.getArgsXAPICompleted - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with complete event to be triggered by xAPI event - */ - self.getArgsXAPICompleted = function (currentTime, duration) { - var progress = self.getProgress(currentTime, duration); - var resultExtTime = self.formatFloat(currentTime); - var dateTime = new Date(); - self.endPlayedSegment(resultExtTime); - var timeStamp = dateTime.toISOString(); - //make sure extensions exist - var extensions = {}; - if ( typeof resultExtTime != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime - } - if ( typeof progress != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + /** + * Generates "paused" xAPI statement (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#233-paused + * + * @public + * @param {Number} currentTime time of the video currently + * @param {Number} duration [description] + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPIPaused = function (currentTime, duration) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + + var resultExtTime = formatFloat(currentTime); + + var progress = self.getProgress(currentTime, duration); + + playedSegmentsSegmentStart = resultExtTime; + endPlayedSegment(resultExtTime); + + var extensions = {}; + if (typeof resultExtTime !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + } + if (typeof progress !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + } + if (typeof playedSegments !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = playedSegments + } + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/paused", + "display": { + "en-US": "paused" } - if ( typeof self.playedSegments != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = self.playedSegments + }, + "object": getXAPIObject(), + "result": { + "extensions": extensions + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID } + }, + "timestamp" : timeStamp + }; + }; - return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/completed", - "display": { - "en-US": "completed" - } - }, - "result": { - "extensions": extensions, - "success" : true, - "duration" : self.time_to_iso8601_duration( duration ) - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": self.sessionID - } - }, - "timestamp" : timeStamp - }; - }; - - /** - * self.getArgsXAPIFullScreen - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with full screen change event to be triggered by xAPI event - */ - self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = self.formatFloat(currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = width + "x" + height; - //make sure extensions exist - var extensions = {}; - if ( typeof self.sessionID != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/session-id"] = self.sessionID - } - if ( typeof state != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + /** + * Generates "seeked" xAPI statement (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#234-seeked + * + * @public + * @param {Number} currentTime time of the video currently + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPISeeked = function (currentTime) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(currentTime); + seekStart = resultExtTime; + endPlayedSegment(self.previousTime.toFixed(2)); + playedSegmentsSegmentStart = seekStart.toFixed(2); + + return { + "verb": { + "id": "https://w3id.org/xapi/video/verbs/seeked", + "display": { + "en-US": "seeked" } - if ( typeof screenSize != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + }, + "object": getXAPIObject(), + "result": { + "extensions" : { + "https://w3id.org/xapi/video/extensions/time-from": self.previousTime.toFixed(2), + "https://w3id.org/xapi/video/extensions/time-to": seekStart.toFixed(2) } - if ( typeof playbackSize != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID } - return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/interacted", - "display": { - "en-US": "interacted" - } - }, - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime - } - }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": extensions - }, - "timestamp" : timeStamp - }; - }; - - /** - * self.getArgsXAPIInitialized - * - * @param { float } currentTime time of the video currently - * - * used to retun json object sent with full screen change event to be triggered by xAPI event - */ - self.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { - // Set default value for quality. - quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); - - // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = self.formatFloat(currentTime); - var screenSize = screen.width + "x" + screen.height; - var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; - var playbackRate = rate; - var volume = self.formatFloat(volume); - var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - //make sure extensions exist - var extensions = {}; - if ( typeof state != "undefined" && state != false){ - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + }, + "timestamp" : timeStamp + }; + }; + + /** + * Generates "interacted" xAPI statement when volume changes (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#235-interacted + * + * @public + * @param {Number} currentTime time of the video currently + * @param {Boolean} muted [description] + * @param {Number} volume [description] + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + volumeChangedAt = formatFloat(currentTime); + var isMuted = muted; + var volumeChange; + if (isMuted === true) { + volumeChange = 0; + } else { + volumeChange = formatFloat(volume); + } + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/interacted", + "display": { + "en-US": "interacted" } - if ( typeof screenSize != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + }, + "object": getXAPIObject(), + "result" : { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": volumeChangedAt } - if ( typeof playbackSize != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID, + "https://w3id.org/xapi/video/extensions/volume": volumeChange } - if ( typeof self.sessionID != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/session-id"] = self.sessionID + }, + "timestamp" : timeStamp + }; + }; + + /** + * Generates "interacted" xAPI statement when fullscreen entered (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#235-interacted + * + * @public + * @param {Number} currentTime time of the video currently + * @param {Number} width [description] + * @param {Number} height [description] + * @param {Boolean} fullscreen [description] + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + var resultExtTime = formatFloat(currentTime); + var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var screenSize = screen.width + "x" + screen.height; + var playbackSize = width + "x" + height; + + var extensions = {}; + if (typeof sessionID !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/session-id"] = sessionID + } + if (typeof state !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + } + if (typeof screenSize !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + } + if (typeof playbackSize !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + } + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/interacted", + "display": { + "en-US": "interacted" } - if ( typeof quality != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/quality"] = quality + }, + "object": getXAPIObject(), + "result": { + "extensions": { + "https://w3id.org/xapi/video/extensions/time": resultExtTime } - if ( typeof ccEnabled != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/cc-enabled"] = ccEnabled + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": extensions + }, + "timestamp" : timeStamp + }; + }; + + /** + * Generates "completed" xAPI statement (Video Profile). + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#236-completed + * + * @public + * @param {Number} currentTime time of the video currently + * @param {Number} duration [description] + * @returns {Object} JSON xAPI statement + */ + self.getArgsXAPICompleted = function (currentTime, duration) { + var progress = self.getProgress(currentTime, duration); + var resultExtTime = formatFloat(currentTime); + var dateTime = new Date(); + endPlayedSegment(resultExtTime); + var timeStamp = dateTime.toISOString(); + + var extensions = {}; + if (typeof resultExtTime !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + } + if (typeof progress !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + } + if (typeof playedSegments !== "undefined") { + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = playedSegments + } + + return { + "verb": { + "id": "http://adlnet.gov/expapi/verbs/completed", + "display": { + "en-US": "completed" } - if ( typeof playbackRate != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/speed"] = playbackRate + }, + "object": getXAPIObject(), + "result": { + "extensions": extensions, + "success" : true, + "duration" : secondsToISO8601Duration(duration) + }, + "context": { + "contextActivities": { + "category": [{ + "id": "https://w3id.org/xapi/video" + }] + }, + "extensions": { + "https://w3id.org/xapi/video/extensions/session-id": sessionID } - if ( typeof userAgent != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/user-agent"] = userAgent + }, + "timestamp" : timeStamp + }; + }; + + /** + * Calculate video progress. + * + * @public + * @param {Number} currentTime [description] + * @param {Number} duration [description] + * @returns {Number} Progress between 0..1 + */ + self.getProgress = function (currentTime, duration) { + var arr, arr2; + endPlayedSegment(currentTime); + playedSegmentsSegmentStart = currentTime; + // Get played segments array. + arr = playedSegments == "" ? [] : playedSegments.split("[,]"); + if (playedSegmentsSegmentStart != null) { + arr.push(playedSegmentsSegmentStart + "[.]" + formatFloat(currentTime)); + } + + arr2 = []; + arr.forEach(function (v,i) { + arr2[i] = v.split("[.]"); + arr2[i][0] *= 1; + arr2[i][1] *= 1; + }); + + // Sort the array. + arr2.sort(function (a,b) { + return a[0] - b[0]; + }); + + // Normalize the segments. + arr2.forEach(function (v,i) { + if (i > 0) { + // Overlapping segments: this segment's starting point is less than last segment's end point. + if (arr2[i][0] < arr2[i-1][1]) { + arr2[i][0] = arr2[i-1][1]; + if (arr2[i][0] > arr2[i][1]) { + arr2[i][1] = arr2[i][0]; + } } - if ( typeof volume != "undefined" ){ - extensions["https://w3id.org/xapi/video/extensions/volume"] = volume + } + }); + + // Calculate progress length. + var progressLength = 0; + arr2.forEach(function (v,i) { + if (v[1] > v[0]) { + progressLength += v[1] - v[0]; + } + }); + + var progress = 1 * (progressLength / duration ).toFixed(2); + + return progress; + }; + + /** + * Add a played segment to the array of already played segments. + * + * @private + * @param {Number} endTime When the current played segment ended + */ + var endPlayedSegment = function (endTime) { + var arr; + // Need to not push in segments that happen from multiple triggers during scrubbing + if (endTime !== playedSegmentsSegmentStart && Math.abs(endTime - playedSegmentsSegmentStart) > 1 ) { + // Don't run if called too closely to each other. + arr = playedSegments == "" ? [] : playedSegments.split("[,]"); + arr.push(formatFloat(playedSegmentsSegmentStart) + "[.]" + formatFloat(endTime)); + playedSegments = arr.join("[,]"); + playedSegmentsSegmentEnd = endTime; + playedSegmentsSegmentStart = null; + } + }; + + /** + * Append extra data to the XAPI statement's default object definition. + * + * @private + * @returns {Object} "Object" portion of JSON xAPI statement + */ + var getXAPIObject = function () { + var event = new H5P.XAPIEvent(); + event.setObject(videoInstance); + var xAPIObject = event.data.statement.object; + + // Add definition type (required by xAPI Video Profile). + // @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#241-definition + xAPIObject.definition.type = "https://w3id.org/xapi/video/activity-type/video"; + + // Add definition description (if video has a description). + if (H5PIntegration && H5PIntegration.contents && videoInstance.contentId && H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { + var videoData = JSON.parse(H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent); + if (videoData.shortStartDescription) { + xAPIObject.description = videoData.shortStartDescription; + } + } + + return xAPIObject; + }; + } + + /** + * Generate a random GUID string used for seesionID with video xAPI statements. + * + * @private + */ + var guid = function () { + var s4 = function () { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + }; + + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); + }; + + /** + * Format parameter as float (or null if invalid). + * Used when making arguments sent with video xAPI statments. + * + * @private + * @param {string} Number to convert to float + */ + var formatFloat = function (number) { + if (number == null) { + return null; + } + + return +(parseFloat(number).toFixed(2)); + }; + + /** + * Convert duration in seconds to an ISO8601 duration string. + * + * @private + * @param {Number} time Duration in seconds + * @returns {String} Duration in ISO8601 duration format + */ + var secondsToISO8601Duration = function (time) { + var units = { + "Y" : (365*24*3600), + "D" : (24*3600), + "H" : 3600, + "M" : 60, + "S" : 1, + } + var timeUnits = [ "H", "M", "S" ]; + var iso8601Duration = "P"; + var isTime = false; + for (var unitName in units ) { + if (units.hasOwnProperty(unitName)) { + var unit = units[unitName]; + var quot = parseInt(time / unit); + var time = time - (quot * unit); + unit = quot; + if (unit > 0) { + if (!isTime && (timeUnits.indexOf(unitName) > -1)) { + iso8601Duration += "T"; + isTime = true; } - return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/initialized", - "display": { - "en-US": "initialized" - } - }, - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" - }] - }, - "extensions": extensions - }, - "timestamp": timeStamp - }; - }; - - - return self; -})(H5P.jQuery ); + iso8601Duration += '' + unit + '' + unitName; + } + } + } + + return iso8601Duration; + } + + return XAPI; +})(H5P.jQuery); diff --git a/scripts/youtube.js b/scripts/youtube.js index f31495d4..9f299ee9 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -12,6 +12,12 @@ H5P.VideoYouTube = (function ($) { function YouTube(sources, options, l10n) { var self = this; + /** + * xAPI Helper. + * @private + */ + var videoXAPI = new H5P.VideoXAPI(self); + var player; var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; @@ -120,25 +126,25 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data == 1) { // Get and send play call when not seeking. - if (H5P.VideoXAPI.seeking === false) { - self.trigger('play', H5P.VideoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); + if (videoXAPI.seeking === false) { + self.trigger('play', videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', H5P.VideoXAPI.getArgsXAPISeeked(H5P.VideoXAPI.seekedTo)); - H5P.VideoXAPI.seeking = false; + self.trigger('seeked', videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo)); + videoXAPI.seeking = false; } } else if (state.data == 2) { // This is a paused event. - if (H5P.VideoXAPI.seeking === false) { - self.trigger('paused', H5P.VideoXAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); + if (videoXAPI.seeking === false) { + self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } } else if (state.data == 0) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = H5P.VideoXAPI.getProgress( length, length ); + var progress = videoXAPI.getProgress( length, length ); if (progress >= 1) { - var arg = H5P.VideoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); + var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); self.trigger('finished', arg); } } @@ -180,9 +186,10 @@ H5P.VideoYouTube = (function ($) { /** * Helper to calculate video dimensions (used in xAPI statements). * + * @private * @param {string} Which dimension to return ('width' or 'height') */ - function getWidthOrHeight (returnType) { + var getWidthOrHeight = function (returnType) { var quality = player.getPlaybackQuality(); var width = ''; var height = ''; @@ -212,11 +219,14 @@ H5P.VideoYouTube = (function ($) { height: '1080'; break; } + return (returnType.toLowerCase().trim()=='width') ? width : height; - } + }; /** * Create the xAPI object for the 'Loaded' event. + * + * @private */ var getLoadedParams = function () { var height = getWidthOrHeight('height'); @@ -227,7 +237,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return H5P.VideoXAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return videoXAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; @@ -374,12 +384,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (H5P.VideoXAPI.seeking === false) { - H5P.VideoXAPI.previousTime = player.getCurrentTime(); + if (videoXAPI.seeking === false) { + videoXAPI.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - H5P.VideoXAPI.seekedTo = time; - H5P.VideoXAPI.seeking = true; + videoXAPI.seekedTo = time; + videoXAPI.seeking = true; }; /** From a35b66cb24a55677309b47c25a95c0dc7185fba5 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 23 Feb 2018 14:31:24 -1000 Subject: [PATCH 070/137] Fix for video description in xAPI statement --- scripts/x-api.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 2396eb18..f0c9f97d 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -514,10 +514,12 @@ H5P.VideoXAPI = (function ($) { xAPIObject.definition.type = "https://w3id.org/xapi/video/activity-type/video"; // Add definition description (if video has a description). - if (H5PIntegration && H5PIntegration.contents && videoInstance.contentId && H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { + if (videoInstance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { var videoData = JSON.parse(H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent); - if (videoData.shortStartDescription) { - xAPIObject.description = videoData.shortStartDescription; + if (videoData && videoData.interactiveVideo && videoData.interactiveVideo.video && videoData.interactiveVideo.video.startScreenOptions && videoData.interactiveVideo.video.startScreenOptions.shortStartDescription) { + xAPIObject.definition.description = { + "en-US": videoData.interactiveVideo.video.startScreenOptions.shortStartDescription + }; } } From 56d8f0dd33b017171a8d4ee30fc585911ad7f56f Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 26 Feb 2018 09:30:37 -1000 Subject: [PATCH 071/137] Add comments Add description comments to input variables --- scripts/x-api.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index f0c9f97d..a884ba9c 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -43,13 +43,13 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime time of the video currently - * @param {Number} width [description] - * @param {Number} height [description] - * @param {Number} rate [description] - * @param {number} volume [description] - * @param {Boolean} ccEnabled [description] - * @param {String} ccLanguage [description] - * @param {String} quality [description] + * @param {Number} width width of the current screen + * @param {Number} height height of the current video screen + * @param {Number} rate playback rate + * @param {number} volume level of volume + * @param {Boolean} ccEnabled boolean whether closed captions are enabled + * @param {String} ccLanguage language of closed captions + * @param {String} quality quality rating of resolution * @returns {Object} JSON xAPI statement * */ @@ -167,7 +167,7 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime time of the video currently - * @param {Number} duration [description] + * @param {Number} duration length of the video in seconds * @returns {Object} JSON xAPI statement */ self.getArgsXAPIPaused = function (currentTime, duration) { @@ -267,8 +267,8 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime time of the video currently - * @param {Boolean} muted [description] - * @param {Number} volume [description] + * @param {Boolean} muted indicates whether video is currently muted + * @param {Number} volume indicates the volume level * @returns {Object} JSON xAPI statement */ self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { @@ -317,9 +317,9 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime time of the video currently - * @param {Number} width [description] - * @param {Number} height [description] - * @param {Boolean} fullscreen [description] + * @param {Number} width width of the current video screen (pixels) + * @param {Number} height height of the current video screen (pixels) + * @param {Boolean} fullscreen indicates whether user is watching in full screen mode or not * @returns {Object} JSON xAPI statement */ self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { @@ -375,7 +375,7 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime time of the video currently - * @param {Number} duration [description] + * @param {Number} duration length of the current video in seconds * @returns {Object} JSON xAPI statement */ self.getArgsXAPICompleted = function (currentTime, duration) { @@ -427,8 +427,8 @@ H5P.VideoXAPI = (function ($) { * Calculate video progress. * * @public - * @param {Number} currentTime [description] - * @param {Number} duration [description] + * @param {Number} currentTime current time of the video in seconds + * @param {Number} duration legnth of the video in seconds * @returns {Number} Progress between 0..1 */ self.getProgress = function (currentTime, duration) { From 35daa0a9d7f75dbde38b2b06db3ba21e8eeb193b Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 26 Feb 2018 14:23:39 -1000 Subject: [PATCH 072/137] Fix bug with small video durations parseInt() fails on small numbers. Video durations less than 32 seconds will use scientific notation when doing the year calculation (e.g., 8 seconds => parseInt(8*(365*24*3600)) == 2.53e-7), and parseInt() will convert this to 2 instead of 0. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Description --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index a884ba9c..b5d45f5c 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -576,7 +576,7 @@ H5P.VideoXAPI = (function ($) { for (var unitName in units ) { if (units.hasOwnProperty(unitName)) { var unit = units[unitName]; - var quot = parseInt(time / unit); + var quot = Math.floor(time / unit); var time = time - (quot * unit); unit = quot; if (unit > 0) { From 57f9e7504d1ff78a65e098750a7273ad59ec412f Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 27 Feb 2018 11:06:19 -1000 Subject: [PATCH 073/137] Whitespace --- scripts/video.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/video.js b/scripts/video.js index 119054f4..a1b5f8a5 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -156,7 +156,6 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { } } - // Extends the event dispatcher Video.prototype = Object.create(H5P.EventDispatcher.prototype); Video.prototype.constructor = Video; From d292a6f7aa1017279bcd4c98c866e712388d83de Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 14:16:32 -1000 Subject: [PATCH 074/137] Remove irrelevant hasOwnProperty() check @see https://github.com/h5p/h5p-video/pull/17#discussion_r172256930 --- scripts/x-api.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index b5d45f5c..cf73a36c 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -574,18 +574,16 @@ H5P.VideoXAPI = (function ($) { var iso8601Duration = "P"; var isTime = false; for (var unitName in units ) { - if (units.hasOwnProperty(unitName)) { - var unit = units[unitName]; - var quot = Math.floor(time / unit); - var time = time - (quot * unit); - unit = quot; - if (unit > 0) { - if (!isTime && (timeUnits.indexOf(unitName) > -1)) { - iso8601Duration += "T"; - isTime = true; - } - iso8601Duration += '' + unit + '' + unitName; + var unit = units[unitName]; + var quot = Math.floor(time / unit); + var time = time - (quot * unit); + unit = quot; + if (unit > 0) { + if (!isTime && (timeUnits.indexOf(unitName) > -1)) { + iso8601Duration += "T"; + isTime = true; } + iso8601Duration += '' + unit + '' + unitName; } } From 4086a798a4e9a3b92bd26c4eca113967ebf3681e Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 14:46:39 -1000 Subject: [PATCH 075/137] guid() is unneeded because of H5P.createUUID() https://github.com/h5p/h5p-video/pull/17#discussion_r172256640 --- scripts/x-api.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index cf73a36c..4c03c8c3 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -33,7 +33,7 @@ H5P.VideoXAPI = (function ($) { var playedSegmentsSegmentEnd; var volumeChangedOn = null; var volumeChangedAt = 0; - var sessionID = guid(); + var sessionID = H5P.createUUID(); var currentTime = 0; @@ -527,19 +527,6 @@ H5P.VideoXAPI = (function ($) { }; } - /** - * Generate a random GUID string used for seesionID with video xAPI statements. - * - * @private - */ - var guid = function () { - var s4 = function () { - return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); - }; - - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); - }; - /** * Format parameter as float (or null if invalid). * Used when making arguments sent with video xAPI statments. From 66a7928a6620009fa059b34e5da3546d42998316 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 14:50:36 -1000 Subject: [PATCH 076/137] Remove redundant logic --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 4c03c8c3..7f022157 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -488,7 +488,7 @@ H5P.VideoXAPI = (function ($) { var endPlayedSegment = function (endTime) { var arr; // Need to not push in segments that happen from multiple triggers during scrubbing - if (endTime !== playedSegmentsSegmentStart && Math.abs(endTime - playedSegmentsSegmentStart) > 1 ) { + if (Math.abs(endTime - playedSegmentsSegmentStart) > 1) { // Don't run if called too closely to each other. arr = playedSegments == "" ? [] : playedSegments.split("[,]"); arr.push(formatFloat(playedSegmentsSegmentStart) + "[.]" + formatFloat(endTime)); From 735bf0995d2d37995a9ce1aa44f709518317024f Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 15:02:11 -1000 Subject: [PATCH 077/137] Fix typo --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 7f022157..a2c56225 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -428,7 +428,7 @@ H5P.VideoXAPI = (function ($) { * * @public * @param {Number} currentTime current time of the video in seconds - * @param {Number} duration legnth of the video in seconds + * @param {Number} duration length of the video in seconds * @returns {Number} Progress between 0..1 */ self.getProgress = function (currentTime, duration) { From 745ab2ab4bd7f42f99d3bdc5ba24cf02d1332548 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 15:23:16 -1000 Subject: [PATCH 078/137] Remove unecessary class variable seekStart --- scripts/x-api.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index a2c56225..a2fd37de 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -27,7 +27,6 @@ H5P.VideoXAPI = (function ($) { * @private */ var videoInstance = instance; - var seekStart = null; var playedSegments = []; var playedSegmentsSegmentStart =0; var playedSegmentsSegmentEnd; @@ -132,7 +131,6 @@ H5P.VideoXAPI = (function ($) { var resultExtTime = formatFloat(currentTime); playedSegmentsSegmentStart = resultExtTime; - seekStart = null; return { "verb": { @@ -229,9 +227,8 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - seekStart = resultExtTime; endPlayedSegment(self.previousTime.toFixed(2)); - playedSegmentsSegmentStart = seekStart.toFixed(2); + playedSegmentsSegmentStart = resultExtTime; return { "verb": { @@ -244,7 +241,7 @@ H5P.VideoXAPI = (function ($) { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": self.previousTime.toFixed(2), - "https://w3id.org/xapi/video/extensions/time-to": seekStart.toFixed(2) + "https://w3id.org/xapi/video/extensions/time-to": playedSegmentsSegmentStart } }, "context": { From 44a36df1e9e84589bd9a2a4cfe6fd758043237aa Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 15:36:22 -1000 Subject: [PATCH 079/137] Replace obsolete HTML feature Document.fullscreen See https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreen --- scripts/x-api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index a2fd37de..622744e5 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -65,11 +65,11 @@ H5P.VideoXAPI = (function ($) { var playbackRate = rate; var volume = formatFloat(volume); var userAgent = navigator.userAgent; - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; + var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; var extensions = {}; - if (typeof state !== "undefined" && state != false) { - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + if (typeof isFullscreen !== "undefined" && isFullscreen) { + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = isFullscreen } if (typeof screenSize !== "undefined") { extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize From c9a68cfc73401eb2ccb8c7b86fa5cf1028eed1b3 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Mon, 5 Mar 2018 16:00:35 -1000 Subject: [PATCH 080/137] Whitespace --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 622744e5..735216e6 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -28,7 +28,7 @@ H5P.VideoXAPI = (function ($) { */ var videoInstance = instance; var playedSegments = []; - var playedSegmentsSegmentStart =0; + var playedSegmentsSegmentStart = 0; var playedSegmentsSegmentEnd; var volumeChangedOn = null; var volumeChangedAt = 0; From 5ab4d281f7d6d7327b765ae6ca656b4d59aa8d5f Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 11:53:51 -1000 Subject: [PATCH 081/137] Update completed verb to match change in spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xAPI Video Profile spec was updated to use “completion” instead of “success” in the result object. See: https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#252-completion --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 735216e6..794023ad 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -403,7 +403,7 @@ H5P.VideoXAPI = (function ($) { "object": getXAPIObject(), "result": { "extensions": extensions, - "success" : true, + "completion" : true, "duration" : secondsToISO8601Duration(duration) }, "context": { From cb1b90ebcb635b3113b9164293d55ac9ed3af852 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 12:00:11 -1000 Subject: [PATCH 082/137] Annotate spec format for progress extension also include note about avoiding parseInt() because it cannot handle exponential notation. --- scripts/x-api.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/x-api.js b/scripts/x-api.js index 794023ad..c12eb8e2 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -471,6 +471,11 @@ H5P.VideoXAPI = (function ($) { } }); + // Progress (percentage) is encoded as a decimal between 0.00 and 1.00. + // @see: https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2544-progress + // Note: We avoid parseInt() here because it fails when the representation + // of a number in JavaScript is expressed in exponential notation (used + // for very small or very large numbers). var progress = 1 * (progressLength / duration ).toFixed(2); return progress; From 0ef32d9a17e16209379089338ff4dec4f9c8aec7 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 14:51:24 -1000 Subject: [PATCH 083/137] Standardize on 3-decimal precision for floats --- scripts/x-api.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index c12eb8e2..9d31b98e 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -227,7 +227,7 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - endPlayedSegment(self.previousTime.toFixed(2)); + endPlayedSegment(formatFloat(self.previousTime)); playedSegmentsSegmentStart = resultExtTime; return { @@ -240,7 +240,7 @@ H5P.VideoXAPI = (function ($) { "object": getXAPIObject(), "result": { "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": self.previousTime.toFixed(2), + "https://w3id.org/xapi/video/extensions/time-from": formatFloat(self.previousTime), "https://w3id.org/xapi/video/extensions/time-to": playedSegmentsSegmentStart } }, @@ -471,12 +471,9 @@ H5P.VideoXAPI = (function ($) { } }); - // Progress (percentage) is encoded as a decimal between 0.00 and 1.00. + // Progress (percentage) is encoded as a decimal between 0.000 and 1.000. // @see: https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2544-progress - // Note: We avoid parseInt() here because it fails when the representation - // of a number in JavaScript is expressed in exponential notation (used - // for very small or very large numbers). - var progress = 1 * (progressLength / duration ).toFixed(2); + var progress = formatFloat(progressLength / duration); return progress; }; @@ -530,18 +527,19 @@ H5P.VideoXAPI = (function ($) { } /** - * Format parameter as float (or null if invalid). - * Used when making arguments sent with video xAPI statments. + * Returns a floating point value with up to 3 decimals of precision (or null + * if invalid). Used when making arguments sent with video xAPI statments. * * @private * @param {string} Number to convert to float + * @returns {Number} Floating point with up to 3 decimals of precision */ var formatFloat = function (number) { if (number == null) { return null; } - return +(parseFloat(number).toFixed(2)); + return +(parseFloat(number).toFixed(3)); }; /** From f210eba346c48a50e0379665ff0a920db9ae17f2 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 14:59:01 -1000 Subject: [PATCH 084/137] Remove unecessary local variables --- scripts/x-api.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 9d31b98e..c74ba20b 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -272,13 +272,7 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); volumeChangedAt = formatFloat(currentTime); - var isMuted = muted; - var volumeChange; - if (isMuted === true) { - volumeChange = 0; - } else { - volumeChange = formatFloat(volume); - } + volume = muted ? 0 : formatFloat(volume); return { "verb": { @@ -301,7 +295,7 @@ H5P.VideoXAPI = (function ($) { }, "extensions": { "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volumeChange + "https://w3id.org/xapi/video/extensions/volume": volume } }, "timestamp" : timeStamp From 467ee4c3c391569ca7d126a24fc609514797c6f4 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 14:59:10 -1000 Subject: [PATCH 085/137] Whitespace --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index c74ba20b..690e26ea 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -269,7 +269,7 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement */ self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { - var dateTime = new Date(); + var dateTime = new Date(); var timeStamp = dateTime.toISOString(); volumeChangedAt = formatFloat(currentTime); volume = muted ? 0 : formatFloat(volume); From 4cf86022a79685a8f8713555a7bcd66f56bde384 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 15:22:42 -1000 Subject: [PATCH 086/137] Replace obsolete HTML feature Document.fullscreen --- scripts/x-api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 690e26ea..bf5179eb 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -317,7 +317,7 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + "x" + screen.height; var playbackSize = width + "x" + height; @@ -325,8 +325,8 @@ H5P.VideoXAPI = (function ($) { if (typeof sessionID !== "undefined") { extensions["https://w3id.org/xapi/video/extensions/session-id"] = sessionID } - if (typeof state !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = state + if (typeof isFullscreen !== "undefined" && isFullscreen) { + extensions["https://w3id.org/xapi/video/extensions/full-screen"] = isFullscreen } if (typeof screenSize !== "undefined") { extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize From 2012f5b51d1692757ff7a9f8aa974623c96ddd4c Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Tue, 6 Mar 2018 15:22:56 -1000 Subject: [PATCH 087/137] Remove unused local variable --- scripts/html5.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 63a22fe6..99ffc10a 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -149,7 +149,7 @@ H5P.VideoHtml5 = (function ($) { }); /** - * Create the xAPI object for the 'Loaded' event. + * Create the xAPI object for the 'Initialized' event. */ var getLoadedParams = function () { var ccEnabled = false; @@ -162,8 +162,6 @@ H5P.VideoHtml5 = (function ($) { } } - var isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || false; - return videoXAPI.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; From ca9a1966c943baadca5c515d6ab9691ae23c85c1 Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 7 Mar 2018 09:31:27 -1000 Subject: [PATCH 088/137] Adjusting getProgress() In getProgress a current segment is ended. I removed an uneccessary if statement below this that made a segment from the current time to the current time. getProgress is also called in html5.js and youtube.js before determining if getArgsXAPICopmleted should be called. getArgsXAPICompleted was also calling getProgess which results in another segment of current time to current time which is meaningless so I made progress an input to getArgsXAPICompleted so this call is only made once and the segment is ended without a meaningless one being added to the segment string. --- scripts/html5.js | 2 +- scripts/x-api.js | 7 ++----- scripts/youtube.js | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 63a22fe6..1065155b 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -225,7 +225,7 @@ H5P.VideoHtml5 = (function ($) { var progress = videoXAPI.getProgress(length, length); if (progress >= 1) { extraTrigger = "finished"; - extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration); + extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; } } diff --git a/scripts/x-api.js b/scripts/x-api.js index 690e26ea..fada9e3f 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -367,10 +367,10 @@ H5P.VideoXAPI = (function ($) { * @public * @param {Number} currentTime time of the video currently * @param {Number} duration length of the current video in seconds + * @param {Number} number between 0-1 indicating percentage of video watched * @returns {Object} JSON xAPI statement */ - self.getArgsXAPICompleted = function (currentTime, duration) { - var progress = self.getProgress(currentTime, duration); + self.getArgsXAPICompleted = function (currentTime, duration, progress) { var resultExtTime = formatFloat(currentTime); var dateTime = new Date(); endPlayedSegment(resultExtTime); @@ -428,9 +428,6 @@ H5P.VideoXAPI = (function ($) { playedSegmentsSegmentStart = currentTime; // Get played segments array. arr = playedSegments == "" ? [] : playedSegments.split("[,]"); - if (playedSegmentsSegmentStart != null) { - arr.push(playedSegmentsSegmentStart + "[.]" + formatFloat(currentTime)); - } arr2 = []; arr.forEach(function (v,i) { diff --git a/scripts/youtube.js b/scripts/youtube.js index 9f299ee9..9bf23533 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -144,7 +144,7 @@ H5P.VideoYouTube = (function ($) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = videoXAPI.getProgress( length, length ); if (progress >= 1) { - var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration()); + var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration(), progress); self.trigger('finished', arg); } } From ca6cd81b1cf1e738c55fb7e74d925dc5fcde3342 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 11:36:56 -1000 Subject: [PATCH 089/137] Remove unused variable playedSegmentsSegmentEnd --- scripts/x-api.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 0bcaeae9..64958543 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -29,7 +29,6 @@ H5P.VideoXAPI = (function ($) { var videoInstance = instance; var playedSegments = []; var playedSegmentsSegmentStart = 0; - var playedSegmentsSegmentEnd; var volumeChangedOn = null; var volumeChangedAt = 0; var sessionID = H5P.createUUID(); @@ -483,7 +482,6 @@ H5P.VideoXAPI = (function ($) { arr = playedSegments == "" ? [] : playedSegments.split("[,]"); arr.push(formatFloat(playedSegmentsSegmentStart) + "[.]" + formatFloat(endTime)); playedSegments = arr.join("[,]"); - playedSegmentsSegmentEnd = endTime; playedSegmentsSegmentStart = null; } }; From 080f2ceaf8172ebe7cf48be3aa186291606c4d55 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 11:39:01 -1000 Subject: [PATCH 090/137] Rename local variable playedSegmentsSegmentStart to playingSegmentStart --- scripts/x-api.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 64958543..7de7a0b7 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -28,7 +28,7 @@ H5P.VideoXAPI = (function ($) { */ var videoInstance = instance; var playedSegments = []; - var playedSegmentsSegmentStart = 0; + var playingSegmentStart = 0; var volumeChangedOn = null; var volumeChangedAt = 0; var sessionID = H5P.createUUID(); @@ -128,8 +128,7 @@ H5P.VideoXAPI = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - - playedSegmentsSegmentStart = resultExtTime; + playingSegmentStart = resultExtTime; return { "verb": { @@ -227,7 +226,7 @@ H5P.VideoXAPI = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); endPlayedSegment(formatFloat(self.previousTime)); - playedSegmentsSegmentStart = resultExtTime; + playingSegmentStart = resultExtTime; return { "verb": { @@ -240,7 +239,7 @@ H5P.VideoXAPI = (function ($) { "result": { "extensions" : { "https://w3id.org/xapi/video/extensions/time-from": formatFloat(self.previousTime), - "https://w3id.org/xapi/video/extensions/time-to": playedSegmentsSegmentStart + "https://w3id.org/xapi/video/extensions/time-to": playingSegmentStart } }, "context": { @@ -373,6 +372,7 @@ H5P.VideoXAPI = (function ($) { var resultExtTime = formatFloat(currentTime); var dateTime = new Date(); endPlayedSegment(resultExtTime); + playingSegmentStart = 0; var timeStamp = dateTime.toISOString(); var extensions = {}; From 09583ab5f91afd5b34c8e9b99960c60647cc4861 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 11:40:03 -1000 Subject: [PATCH 091/137] Docs update and whitespace --- scripts/x-api.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 7de7a0b7..e776e933 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -365,7 +365,7 @@ H5P.VideoXAPI = (function ($) { * @public * @param {Number} currentTime time of the video currently * @param {Number} duration length of the current video in seconds - * @param {Number} number between 0-1 indicating percentage of video watched + * @param {Number} progress Number between 0.000 and 1.000 indicating percentage of video watched * @returns {Object} JSON xAPI statement */ self.getArgsXAPICompleted = function (currentTime, duration, progress) { diff --git a/scripts/youtube.js b/scripts/youtube.js index 9bf23533..d59200f7 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -142,7 +142,7 @@ H5P.VideoYouTube = (function ($) { var length = player.getDuration(); if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = videoXAPI.getProgress( length, length ); + var progress = videoXAPI.getProgress(length, length); if (progress >= 1) { var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration(), progress); self.trigger('finished', arg); From a00b8b5a25a5f38f372508836d413ff8d5e765c5 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 11:43:09 -1000 Subject: [PATCH 092/137] Refactor endPlayedSegment(), getProgress(), and playedSegments playedSegments was being stored as a string, requiring parsing to array and manipulating whenever progress was queried. Refactor playedSegments to an array, and only stringify it when sending an xAPI statement out. --- scripts/x-api.js | 108 ++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index e776e933..3968e73c 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -174,8 +174,7 @@ H5P.VideoXAPI = (function ($) { var progress = self.getProgress(currentTime, duration); - playedSegmentsSegmentStart = resultExtTime; - endPlayedSegment(resultExtTime); + endPlayingSegment(resultExtTime); var extensions = {}; if (typeof resultExtTime !== "undefined") { @@ -185,7 +184,7 @@ H5P.VideoXAPI = (function ($) { extensions["https://w3id.org/xapi/video/extensions/progress"] = progress } if (typeof playedSegments !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = playedSegments + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = stringifyPlayedSegments() } return { @@ -225,7 +224,7 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - endPlayedSegment(formatFloat(self.previousTime)); + endPlayingSegment(formatFloat(self.previousTime)); playingSegmentStart = resultExtTime; return { @@ -371,7 +370,7 @@ H5P.VideoXAPI = (function ($) { self.getArgsXAPICompleted = function (currentTime, duration, progress) { var resultExtTime = formatFloat(currentTime); var dateTime = new Date(); - endPlayedSegment(resultExtTime); + endPlayingSegment(resultExtTime); playingSegmentStart = 0; var timeStamp = dateTime.toISOString(); @@ -383,7 +382,7 @@ H5P.VideoXAPI = (function ($) { extensions["https://w3id.org/xapi/video/extensions/progress"] = progress } if (typeof playedSegments !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = playedSegments + extensions["https://w3id.org/xapi/video/extensions/played-segments"] = stringifyPlayedSegments() } return { @@ -422,70 +421,75 @@ H5P.VideoXAPI = (function ($) { * @returns {Number} Progress between 0..1 */ self.getProgress = function (currentTime, duration) { - var arr, arr2; - endPlayedSegment(currentTime); - playedSegmentsSegmentStart = currentTime; - // Get played segments array. - arr = playedSegments == "" ? [] : playedSegments.split("[,]"); - - arr2 = []; - arr.forEach(function (v,i) { - arr2[i] = v.split("[.]"); - arr2[i][0] *= 1; - arr2[i][1] *= 1; - }); + // If we're currently playing a segment, end it so it's included in our + // calculations below. + endPlayingSegment(currentTime); - // Sort the array. - arr2.sort(function (a,b) { - return a[0] - b[0]; - }); + // Create a copy of the played segments array so we can manipulate it. + var parsedPlayedSegments = JSON.parse(JSON.stringify(playedSegments)); - // Normalize the segments. - arr2.forEach(function (v,i) { - if (i > 0) { - // Overlapping segments: this segment's starting point is less than last segment's end point. - if (arr2[i][0] < arr2[i-1][1]) { - arr2[i][0] = arr2[i-1][1]; - if (arr2[i][0] > arr2[i][1]) { - arr2[i][1] = arr2[i][0]; - } - } - } + // Sort the array (so we can detect overlapping segments). + parsedPlayedSegments.sort(function (a, b) { + return a.start - b.start; }); - // Calculate progress length. - var progressLength = 0; - arr2.forEach(function (v,i) { - if (v[1] > v[0]) { - progressLength += v[1] - v[0]; + // Calculate total time watched from played segments. + var timePlayed = 0; + parsedPlayedSegments.forEach(function (currentValue, i, segments) { + // If a segment overlaps, discard the overlap (otherwise our progress + // count would be artificially inflated). + if (i > 0 && segments[i].start < segments[i-1].end) { + segments[i].start = segments[i-1].end; + // This segment may have been inside the previous segment, so be sure + // to update its end timestamp so we don't have a negative range). + segments[i].end = Math.max(segments[i].start, segments[i].end); } + // Add this segment's length to our cumulative progress counter. + timePlayed += segments[i].end - segments[i].start; }); // Progress (percentage) is encoded as a decimal between 0.000 and 1.000. // @see: https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2544-progress - var progress = formatFloat(progressLength / duration); - - return progress; + return formatFloat(timePlayed / duration); }; /** - * Add a played segment to the array of already played segments. + * Adds a played segment to the array of already played segments. * * @private - * @param {Number} endTime When the current played segment ended + * @param {Number} endTime When the currently playing segment ended */ - var endPlayedSegment = function (endTime) { - var arr; - // Need to not push in segments that happen from multiple triggers during scrubbing - if (Math.abs(endTime - playedSegmentsSegmentStart) > 1) { - // Don't run if called too closely to each other. - arr = playedSegments == "" ? [] : playedSegments.split("[,]"); - arr.push(formatFloat(playedSegmentsSegmentStart) + "[.]" + formatFloat(endTime)); - playedSegments = arr.join("[,]"); - playedSegmentsSegmentStart = null; + var endPlayingSegment = function (endTime) { + // Scrubbing the video will fire this function many times, so only record + // segments 1 second or longer (ignore any segments less than 1 second). + if (Math.abs(endTime - playingSegmentStart) > 1) { + playedSegments.push({ + start: formatFloat(playingSegmentStart), + end: formatFloat(endTime) + }); + playingSegmentStart = endTime; } }; + /** + * Converts an array of played segments to the string representation defined + * in the xAPI Video Profile spec. + * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2545-played-segments + * @return {String} Played segments string, e.g., "0.000[.]12.000[,]14.000[.]21.000" + */ + var stringifyPlayedSegments = function () { + var stringPlayedSegments = ''; + if (playedSegments.length > 0) { + stringPlayedSegments = playedSegments.map(function (segment) { + return segment.start.toFixed(3) + '[.]' + segment.end.toFixed(3); + }).reduce(function (accumulator, segment) { + return accumulator + '[,]' + segment; + }); + } + + return stringPlayedSegments; + } + /** * Append extra data to the XAPI statement's default object definition. * From a4e7dd6dd2ef83ec0ad5f22886aac9f1f8e7caec Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 13:44:08 -1000 Subject: [PATCH 093/137] Mark optional params in function headers --- scripts/x-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 3968e73c..212dc40d 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -47,7 +47,7 @@ H5P.VideoXAPI = (function ($) { * @param {number} volume level of volume * @param {Boolean} ccEnabled boolean whether closed captions are enabled * @param {String} ccLanguage language of closed captions - * @param {String} quality quality rating of resolution + * @param {String} [quality] quality rating of resolution * @returns {Object} JSON xAPI statement * */ @@ -307,7 +307,7 @@ H5P.VideoXAPI = (function ($) { * @param {Number} currentTime time of the video currently * @param {Number} width width of the current video screen (pixels) * @param {Number} height height of the current video screen (pixels) - * @param {Boolean} fullscreen indicates whether user is watching in full screen mode or not + * @param {Boolean} [fullscreen] indicates whether user is watching in full screen mode or not * @returns {Object} JSON xAPI statement */ self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { From ff3ffb86a2bb877520136f81e6ba737d322389b8 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 14:09:53 -1000 Subject: [PATCH 094/137] Formatting (match H5P code guidelines) strings wrapped in single quotes; add missing semicolons; strict comparisons; else starts its own line. See: https://h5p.org/documentation/for-developers/coding-guidelines --- scripts/html5.js | 14 +- scripts/x-api.js | 334 ++++++++++++++++++++++----------------------- scripts/youtube.js | 13 +- 3 files changed, 183 insertions(+), 178 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index cb3bdc07..95a0d3a8 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -199,7 +199,8 @@ H5P.VideoHtml5 = (function ($) { extraTrigger = 'seeked'; lastSend = 'seeked'; videoXAPI.seeking = false; - } else if (lastSend !== 'play') { + } + else if (lastSend !== 'play') { extraArg = videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; @@ -209,7 +210,7 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && videoXAPI.seeking === false && video.currentTime !== video.duration) { - extraTrigger = "paused"; + extraTrigger = 'paused'; extraArg = videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } @@ -222,7 +223,7 @@ H5P.VideoHtml5 = (function ($) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = videoXAPI.getProgress(length, length); if (progress >= 1) { - extraTrigger = "finished"; + extraTrigger = 'finished'; extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; } @@ -240,10 +241,11 @@ H5P.VideoHtml5 = (function ($) { lastSend = 'volumechange'; break; case 'play': - if (videoXAPI.seeking === false && lastSend != h5p) { + if (videoXAPI.seeking === false && lastSend !== h5p) { arg = videoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; - } else { + } + else { arg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); lastSend = 'seeked'; videoXAPI.seeking = false; @@ -307,7 +309,7 @@ H5P.VideoHtml5 = (function ($) { self.trigger(h5p, arg); // Make extra calls for events with needed values for xAPI statement. - if (extraTrigger != null && extraArg != null) { + if (extraTrigger !== null && extraArg !== null) { self.trigger(extraTrigger, extraArg); } }, false); diff --git a/scripts/x-api.js b/scripts/x-api.js index 212dc40d..25d04f43 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -36,7 +36,7 @@ H5P.VideoXAPI = (function ($) { /** - * Generates "initialized" xAPI statement (Video Profile). + * Generates 'initialized' xAPI statement (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#231-initialized * * @public @@ -59,64 +59,64 @@ H5P.VideoXAPI = (function ($) { var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); - var screenSize = screen.width + "x" + screen.height; - var playbackSize = (width !== undefined && width !== '' ) ? width + "x" + height : "undetermined"; + var screenSize = screen.width + 'x' + screen.height; + var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : 'undetermined'; var playbackRate = rate; var volume = formatFloat(volume); var userAgent = navigator.userAgent; var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; var extensions = {}; - if (typeof isFullscreen !== "undefined" && isFullscreen) { - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = isFullscreen + if (typeof isFullscreen !== 'undefined' && isFullscreen) { + extensions['https://w3id.org/xapi/video/extensions/full-screen'] = isFullscreen; } - if (typeof screenSize !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + if (typeof screenSize !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/screen-size'] = screenSize; } - if (typeof playbackSize !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + if (typeof playbackSize !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/video-playback-size'] = playbackSize; } - if (typeof sessionID !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/session-id"] = sessionID + if (typeof sessionID !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/session-id'] = sessionID; } - if (typeof quality !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/quality"] = quality + if (typeof quality !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/quality'] = quality; } - if (typeof ccEnabled !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/cc-enabled"] = ccEnabled + if (typeof ccEnabled !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/cc-enabled'] = ccEnabled; } - if (typeof playbackRate !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/speed"] = playbackRate + if (typeof playbackRate !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/speed'] = playbackRate; } - if (typeof userAgent !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/user-agent"] = userAgent + if (typeof userAgent !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/user-agent'] = userAgent; } - if (typeof volume !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/volume"] = volume + if (typeof volume !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/volume'] = volume; } return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/initialized", - "display": { - "en-US": "initialized" + 'verb': { + 'id': 'http://adlnet.gov/expapi/verbs/initialized', + 'display': { + 'en-US': 'initialized' } }, - "object": getXAPIObject(), - "context" : { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'object': getXAPIObject(), + 'context' : { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": extensions + 'extensions': extensions }, - "timestamp": timeStamp + 'timestamp': timeStamp }; }; /** - * Generates "played" xAPI statement. + * Generates 'played' xAPI statement. * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#232-played * * @public @@ -131,34 +131,34 @@ H5P.VideoXAPI = (function ($) { playingSegmentStart = resultExtTime; return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/played", - "display": { - "en-US": "played" + 'verb': { + 'id': 'https://w3id.org/xapi/video/verbs/played', + 'display': { + 'en-US': 'played' } }, - "object": getXAPIObject(), - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime, + 'object': getXAPIObject(), + 'result': { + 'extensions': { + 'https://w3id.org/xapi/video/extensions/time': resultExtTime, } }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + 'extensions': { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID } }, - "timestamp": timeStamp + 'timestamp': timeStamp }; }; /** - * Generates "paused" xAPI statement (Video Profile). + * Generates 'paused' xAPI statement (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#233-paused * * @public @@ -177,43 +177,43 @@ H5P.VideoXAPI = (function ($) { endPlayingSegment(resultExtTime); var extensions = {}; - if (typeof resultExtTime !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + if (typeof resultExtTime !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/time'] = resultExtTime; } - if (typeof progress !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + if (typeof progress !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/progress'] = progress; } - if (typeof playedSegments !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = stringifyPlayedSegments() + if (typeof playedSegments !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/played-segments'] = stringifyPlayedSegments(); } return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/paused", - "display": { - "en-US": "paused" + 'verb': { + 'id': 'https://w3id.org/xapi/video/verbs/paused', + 'display': { + 'en-US': 'paused' } }, - "object": getXAPIObject(), - "result": { - "extensions": extensions + 'object': getXAPIObject(), + 'result': { + 'extensions': extensions }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + 'extensions': { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID } }, - "timestamp" : timeStamp + 'timestamp' : timeStamp }; }; /** - * Generates "seeked" xAPI statement (Video Profile). + * Generates 'seeked' xAPI statement (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#234-seeked * * @public @@ -228,35 +228,35 @@ H5P.VideoXAPI = (function ($) { playingSegmentStart = resultExtTime; return { - "verb": { - "id": "https://w3id.org/xapi/video/verbs/seeked", - "display": { - "en-US": "seeked" + 'verb': { + 'id': 'https://w3id.org/xapi/video/verbs/seeked', + 'display': { + 'en-US': 'seeked' } }, - "object": getXAPIObject(), - "result": { - "extensions" : { - "https://w3id.org/xapi/video/extensions/time-from": formatFloat(self.previousTime), - "https://w3id.org/xapi/video/extensions/time-to": playingSegmentStart + 'object': getXAPIObject(), + 'result': { + 'extensions' : { + 'https://w3id.org/xapi/video/extensions/time-from': formatFloat(self.previousTime), + 'https://w3id.org/xapi/video/extensions/time-to': playingSegmentStart } }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + 'extensions': { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID } }, - "timestamp" : timeStamp + 'timestamp' : timeStamp }; }; /** - * Generates "interacted" xAPI statement when volume changes (Video Profile). + * Generates 'interacted' xAPI statement when volume changes (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#235-interacted * * @public @@ -272,35 +272,35 @@ H5P.VideoXAPI = (function ($) { volume = muted ? 0 : formatFloat(volume); return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/interacted", - "display": { - "en-US": "interacted" + 'verb': { + 'id': 'http://adlnet.gov/expapi/verbs/interacted', + 'display': { + 'en-US': 'interacted' } }, - "object": getXAPIObject(), - "result" : { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": volumeChangedAt + 'object': getXAPIObject(), + 'result' : { + 'extensions': { + 'https://w3id.org/xapi/video/extensions/time': volumeChangedAt } }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID, - "https://w3id.org/xapi/video/extensions/volume": volume + 'extensions': { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID, + 'https://w3id.org/xapi/video/extensions/volume': volume } }, - "timestamp" : timeStamp + 'timestamp' : timeStamp }; }; /** - * Generates "interacted" xAPI statement when fullscreen entered (Video Profile). + * Generates 'interacted' xAPI statement when fullscreen entered (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#235-interacted * * @public @@ -315,50 +315,50 @@ H5P.VideoXAPI = (function ($) { var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; - var screenSize = screen.width + "x" + screen.height; - var playbackSize = width + "x" + height; + var screenSize = screen.width + 'x' + screen.height; + var playbackSize = width + 'x' + height; var extensions = {}; - if (typeof sessionID !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/session-id"] = sessionID + if (typeof sessionID !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/session-id'] = sessionID; } - if (typeof isFullscreen !== "undefined" && isFullscreen) { - extensions["https://w3id.org/xapi/video/extensions/full-screen"] = isFullscreen + if (typeof isFullscreen !== 'undefined' && isFullscreen) { + extensions['https://w3id.org/xapi/video/extensions/full-screen'] = isFullscreen; } - if (typeof screenSize !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/screen-size"] = screenSize + if (typeof screenSize !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/screen-size'] = screenSize; } - if (typeof playbackSize !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/video-playback-size"] = playbackSize + if (typeof playbackSize !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/video-playback-size'] = playbackSize; } return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/interacted", - "display": { - "en-US": "interacted" + 'verb': { + 'id': 'http://adlnet.gov/expapi/verbs/interacted', + 'display': { + 'en-US': 'interacted' } }, - "object": getXAPIObject(), - "result": { - "extensions": { - "https://w3id.org/xapi/video/extensions/time": resultExtTime + 'object': getXAPIObject(), + 'result': { + 'extensions': { + 'https://w3id.org/xapi/video/extensions/time': resultExtTime } }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": extensions + 'extensions': extensions }, - "timestamp" : timeStamp + 'timestamp' : timeStamp }; }; /** - * Generates "completed" xAPI statement (Video Profile). + * Generates 'completed' xAPI statement (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#236-completed * * @public @@ -375,40 +375,40 @@ H5P.VideoXAPI = (function ($) { var timeStamp = dateTime.toISOString(); var extensions = {}; - if (typeof resultExtTime !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/time"] = resultExtTime + if (typeof resultExtTime !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/time'] = resultExtTime; } - if (typeof progress !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/progress"] = progress + if (typeof progress !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/progress'] = progress; } - if (typeof playedSegments !== "undefined") { - extensions["https://w3id.org/xapi/video/extensions/played-segments"] = stringifyPlayedSegments() + if (typeof playedSegments !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/played-segments'] = stringifyPlayedSegments(); } return { - "verb": { - "id": "http://adlnet.gov/expapi/verbs/completed", - "display": { - "en-US": "completed" + 'verb': { + 'id': 'http://adlnet.gov/expapi/verbs/completed', + 'display': { + 'en-US': 'completed' } }, - "object": getXAPIObject(), - "result": { - "extensions": extensions, - "completion" : true, - "duration" : secondsToISO8601Duration(duration) + 'object': getXAPIObject(), + 'result': { + 'extensions': extensions, + 'completion' : true, + 'duration' : secondsToISO8601Duration(duration) }, - "context": { - "contextActivities": { - "category": [{ - "id": "https://w3id.org/xapi/video" + 'context': { + 'contextActivities': { + 'category': [{ + 'id': 'https://w3id.org/xapi/video' }] }, - "extensions": { - "https://w3id.org/xapi/video/extensions/session-id": sessionID + 'extensions': { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID } }, - "timestamp" : timeStamp + 'timestamp' : timeStamp }; }; @@ -475,7 +475,7 @@ H5P.VideoXAPI = (function ($) { * Converts an array of played segments to the string representation defined * in the xAPI Video Profile spec. * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2545-played-segments - * @return {String} Played segments string, e.g., "0.000[.]12.000[,]14.000[.]21.000" + * @return {String} Played segments string, e.g., '0.000[.]12.000[,]14.000[.]21.000' */ var stringifyPlayedSegments = function () { var stringPlayedSegments = ''; @@ -488,13 +488,13 @@ H5P.VideoXAPI = (function ($) { } return stringPlayedSegments; - } + }; /** * Append extra data to the XAPI statement's default object definition. * * @private - * @returns {Object} "Object" portion of JSON xAPI statement + * @returns {Object} 'Object' portion of JSON xAPI statement */ var getXAPIObject = function () { var event = new H5P.XAPIEvent(); @@ -503,14 +503,14 @@ H5P.VideoXAPI = (function ($) { // Add definition type (required by xAPI Video Profile). // @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#241-definition - xAPIObject.definition.type = "https://w3id.org/xapi/video/activity-type/video"; + xAPIObject.definition.type = 'https://w3id.org/xapi/video/activity-type/video'; // Add definition description (if video has a description). if (videoInstance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { var videoData = JSON.parse(H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent); if (videoData && videoData.interactiveVideo && videoData.interactiveVideo.video && videoData.interactiveVideo.video.startScreenOptions && videoData.interactiveVideo.video.startScreenOptions.shortStartDescription) { xAPIObject.definition.description = { - "en-US": videoData.interactiveVideo.video.startScreenOptions.shortStartDescription + 'en-US': videoData.interactiveVideo.video.startScreenOptions.shortStartDescription }; } } @@ -528,7 +528,7 @@ H5P.VideoXAPI = (function ($) { * @returns {Number} Floating point with up to 3 decimals of precision */ var formatFloat = function (number) { - if (number == null) { + if (number === null) { return null; } @@ -544,23 +544,23 @@ H5P.VideoXAPI = (function ($) { */ var secondsToISO8601Duration = function (time) { var units = { - "Y" : (365*24*3600), - "D" : (24*3600), - "H" : 3600, - "M" : 60, - "S" : 1, - } - var timeUnits = [ "H", "M", "S" ]; - var iso8601Duration = "P"; + 'Y': (365*24*3600), + 'D': (24*3600), + 'H': 3600, + 'M': 60, + 'S': 1, + }; + var timeUnits = ['H', 'M', 'S']; + var iso8601Duration = 'P'; var isTime = false; - for (var unitName in units ) { + for (var unitName in units) { var unit = units[unitName]; var quot = Math.floor(time / unit); var time = time - (quot * unit); unit = quot; if (unit > 0) { if (!isTime && (timeUnits.indexOf(unitName) > -1)) { - iso8601Duration += "T"; + iso8601Duration += 'T'; isTime = true; } iso8601Duration += '' + unit + '' + unitName; diff --git a/scripts/youtube.js b/scripts/youtube.js index d59200f7..551fe3f4 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -124,20 +124,23 @@ H5P.VideoYouTube = (function ($) { self.trigger('stateChange', state.data); // Calls for xAPI events. - if (state.data == 1) { + if (state.data === 1) { // Get and send play call when not seeking. if (videoXAPI.seeking === false) { self.trigger('play', videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); - } else { + } + else { self.trigger('seeked', videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo)); videoXAPI.seeking = false; } - } else if (state.data == 2) { + } + else if (state.data === 2) { // This is a paused event. if (videoXAPI.seeking === false) { self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); } - } else if (state.data == 0) { + } + else if (state.data === 0) { // Send xapi trigger if video progress indicates finished. var length = player.getDuration(); if (length > 0) { @@ -231,7 +234,7 @@ H5P.VideoYouTube = (function ($) { var getLoadedParams = function () { var height = getWidthOrHeight('height'); var width = getWidthOrHeight('width'); - var ccEnabled = player.getOptions().indexOf("cc") !== -1; + var ccEnabled = player.getOptions().indexOf('cc') !== -1; var ccLanguage; if (ccEnabled) { ccLanguage = player.getOptions('cc', 'track').languageCode; From af8e98571eaae8741f605fa1faa628599ddcb9c1 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 14:12:29 -1000 Subject: [PATCH 095/137] Set quality param to height if not provided --- scripts/x-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 25d04f43..fa1f8021 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -52,8 +52,8 @@ H5P.VideoXAPI = (function ($) { * */ self.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { - // Set default value for quality. - quality = typeof quality !== 'undefined' ? quality : Math.min(width, height); + // If quality isn't provided, set it to the height of the video. + quality = typeof quality !== 'undefined' ? quality : height; // Variables used in compiling xAPI results. var dateTime = new Date(); From 5558d8fa1e1464bf1735d05673e4d18c111d767d Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 15:57:27 -1000 Subject: [PATCH 096/137] Lint (redeclaration) --- scripts/x-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index fa1f8021..6e891582 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -62,9 +62,9 @@ H5P.VideoXAPI = (function ($) { var screenSize = screen.width + 'x' + screen.height; var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : 'undetermined'; var playbackRate = rate; - var volume = formatFloat(volume); var userAgent = navigator.userAgent; var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; + volume = formatFloat(volume); var extensions = {}; if (typeof isFullscreen !== 'undefined' && isFullscreen) { @@ -556,7 +556,7 @@ H5P.VideoXAPI = (function ($) { for (var unitName in units) { var unit = units[unitName]; var quot = Math.floor(time / unit); - var time = time - (quot * unit); + time = time - (quot * unit); unit = quot; if (unit > 0) { if (!isTime && (timeUnits.indexOf(unitName) > -1)) { From ff9f1c0bd2d6be2f03dd5db098991555eae9fcb5 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 15:58:01 -1000 Subject: [PATCH 097/137] Lint (whitespace) --- scripts/html5.js | 6 +++--- scripts/x-api.js | 2 +- scripts/youtube.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 95a0d3a8..24fd4d9f 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -248,7 +248,7 @@ H5P.VideoHtml5 = (function ($) { else { arg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); lastSend = 'seeked'; - videoXAPI.seeking = false; + videoXAPI.seeking = false; h5p = 'seeked'; } break; @@ -743,8 +743,8 @@ H5P.VideoHtml5 = (function ($) { this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { - //triggered as finished to be seperate from H5Ps completed, - //but statement is sent as completed and differentiated by object.id + // Triggered as finished to be seperate from H5Ps completed, + // but statement is sent as completed and differentiated by object.id this.triggerXAPI('completed', event.data); }) self.on('fullscreen', function (event) { diff --git a/scripts/x-api.js b/scripts/x-api.js index 6e891582..6309488e 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -568,7 +568,7 @@ H5P.VideoXAPI = (function ($) { } return iso8601Duration; - } + }; return XAPI; })(H5P.jQuery); diff --git a/scripts/youtube.js b/scripts/youtube.js index 551fe3f4..e364cf53 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -253,8 +253,8 @@ H5P.VideoYouTube = (function ($) { this.triggerXAPI('interacted', event.data); }); self.on('finished', function (event) { - //triggered as finished to be seperate from H5Ps completed, - //but statement is sent as completed and differentiated by object.id + // Triggered as finished to be seperate from H5Ps completed, + // but statement is sent as completed and differentiated by object.id this.triggerXAPI('completed', event.data); }); self.on('fullscreen', function (event) { From ec75b45c2d37f7bca4812bc7aeef77823dec37e6 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 15:58:20 -1000 Subject: [PATCH 098/137] Lint (semicolons) --- scripts/html5.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 24fd4d9f..eaaa5883 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -746,7 +746,7 @@ H5P.VideoHtml5 = (function ($) { // Triggered as finished to be seperate from H5Ps completed, // but statement is sent as completed and differentiated by object.id this.triggerXAPI('completed', event.data); - }) + }); self.on('fullscreen', function (event) { // @todo: Not currently used. this.triggerXAPI('interacted', event.data); @@ -756,7 +756,7 @@ H5P.VideoHtml5 = (function ($) { }); self.on('xAPIloaded', function (event) { this.triggerXAPI('initialized', event.data); - }) + }); self.on('paused', function (event) { this.triggerXAPI('paused', event.data); }); From 719add40376b355b92130904b2407695d3a600bd Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 15:58:41 -1000 Subject: [PATCH 099/137] Fix syntax in getWidthOrHeight() function --- scripts/youtube.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/youtube.js b/scripts/youtube.js index e364cf53..93610605 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -198,28 +198,28 @@ H5P.VideoYouTube = (function ($) { var height = ''; switch (quality) { case 'small': - width: '320'; - height: '240'; + width = '320'; + height = '240'; break; case 'medium': - width: '640'; - height: '360'; + width = '640'; + height = '360'; break; case 'large': - width: '853'; - height: '480'; + width = '853'; + height = '480'; break; case 'hd720': - width: '640'; - height: '360'; + width = '640'; + height = '360'; break; case 'hd1080': - width: '1920'; - height: '1080'; + width = '1920'; + height = '1080'; break; case 'highres': - width: '1920'; - height: '1080'; + width = '1920'; + height = '1080'; break; } From ef6aef416a10ff9a00a5e705cd9a1c1f2563cfed Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 15:59:06 -1000 Subject: [PATCH 100/137] Lint (remove ES6 syntax) --- scripts/x-api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 6309488e..8cf7eff2 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -310,7 +310,9 @@ H5P.VideoXAPI = (function ($) { * @param {Boolean} [fullscreen] indicates whether user is watching in full screen mode or not * @returns {Object} JSON xAPI statement */ - self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { + self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen) { + fullscreen = typeof fullscreen !== 'undefined' ? fullscreen : false; + var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); From 4995735579758108e797e2e71e1ded875abeb05b Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 7 Mar 2018 16:09:58 -1000 Subject: [PATCH 101/137] Fix for played segments could have garbage data with lots of timeline scrubbing, or miss some segments if user played/paused in less than 1 second intervals. --- scripts/x-api.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 8cf7eff2..9380208d 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -463,8 +463,9 @@ H5P.VideoXAPI = (function ($) { */ var endPlayingSegment = function (endTime) { // Scrubbing the video will fire this function many times, so only record - // segments 1 second or longer (ignore any segments less than 1 second). - if (Math.abs(endTime - playingSegmentStart) > 1) { + // segments 500ms or longer (ignore any segments less than 500ms and + // negative play segments). + if (endTime - playingSegmentStart > 0.5) { playedSegments.push({ start: formatFloat(playingSegmentStart), end: formatFloat(endTime) From 2eb31e9e2ed5d61bf17572d458adc3603545a72d Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 8 Mar 2018 07:14:53 -1000 Subject: [PATCH 102/137] Lowered threshold of time for endPlayedSegment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was only pushing segments if the time was greater than 1 second. The reason being is this would get called multiple times when a user scrubbed the play bar for a seeked event, and we used the time to determine state. We lowered this to 1/2 a second and the results seem to go well. Seek isn’t fired multiple times when scrubbing and play and paused are recorded acurately even with short intervals. --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index fa1f8021..5e4bb127 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -462,7 +462,7 @@ H5P.VideoXAPI = (function ($) { var endPlayingSegment = function (endTime) { // Scrubbing the video will fire this function many times, so only record // segments 1 second or longer (ignore any segments less than 1 second). - if (Math.abs(endTime - playingSegmentStart) > 1) { + if (Math.abs(endTime - playingSegmentStart) > 0.5) { playedSegments.push({ start: formatFloat(playingSegmentStart), end: formatFloat(endTime) From 7fc4d188ddfa11a6e7c718acc7fb4c11e6f8bde1 Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 8 Mar 2018 07:29:50 -1000 Subject: [PATCH 103/137] Revert to previous version to accomodate pull MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Had made changes and committed before my git tool recognized changes had already been pushed. I’m changing to match pushed changes since they incorporate the work I was going to do anyways. --- scripts/x-api.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 5e4bb127..7ef387e4 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -62,9 +62,9 @@ H5P.VideoXAPI = (function ($) { var screenSize = screen.width + 'x' + screen.height; var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : 'undetermined'; var playbackRate = rate; - var volume = formatFloat(volume); var userAgent = navigator.userAgent; var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; + volume = formatFloat(volume); var extensions = {}; if (typeof isFullscreen !== 'undefined' && isFullscreen) { @@ -310,7 +310,9 @@ H5P.VideoXAPI = (function ($) { * @param {Boolean} [fullscreen] indicates whether user is watching in full screen mode or not * @returns {Object} JSON xAPI statement */ - self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen = false) { + self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen) { + fullscreen = typeof fullscreen !== 'undefined' ? fullscreen : false; + var dateTime = new Date(); var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); @@ -461,8 +463,9 @@ H5P.VideoXAPI = (function ($) { */ var endPlayingSegment = function (endTime) { // Scrubbing the video will fire this function many times, so only record - // segments 1 second or longer (ignore any segments less than 1 second). - if (Math.abs(endTime - playingSegmentStart) > 0.5) { + // segments 500ms or longer (ignore any segments less than 500ms and + // negative play segments). + if (endTime - playingSegmentStart > 0.5) { playedSegments.push({ start: formatFloat(playingSegmentStart), end: formatFloat(endTime) @@ -556,7 +559,7 @@ H5P.VideoXAPI = (function ($) { for (var unitName in units) { var unit = units[unitName]; var quot = Math.floor(time / unit); - var time = time - (quot * unit); + time = time - (quot * unit); unit = quot; if (unit > 0) { if (!isTime && (timeUnits.indexOf(unitName) > -1)) { @@ -568,7 +571,7 @@ H5P.VideoXAPI = (function ($) { } return iso8601Duration; - } + }; return XAPI; -})(H5P.jQuery); +})(H5P.jQuery); \ No newline at end of file From c8940b23b65a4ed6522b067255e317cd92fe8d6f Mon Sep 17 00:00:00 2001 From: kirkj Date: Thu, 8 Mar 2018 13:26:39 -1000 Subject: [PATCH 104/137] Re-Add EOF line deleted in last commit --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 7ef387e4..da2ddd8f 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -574,4 +574,4 @@ H5P.VideoXAPI = (function ($) { }; return XAPI; -})(H5P.jQuery); \ No newline at end of file +})(H5P.jQuery); From a88c17b84c9a7e6e4ffffcdb39eca4d24c156082 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 14 Mar 2018 10:38:42 +0100 Subject: [PATCH 105/137] HFP-1779 Refactor xAPI statement building There was some duplicate code that was used in several functions that could also be supported by a builder function. Will be more relevant as new xAPI statements will get added. Cmp. https://github.com/h5p/h5p-video/pull/17#discussion_r173470361 --- scripts/html5.js | 2 +- scripts/x-api.js | 267 +++++++++++++++------------------------------ scripts/youtube.js | 2 +- 3 files changed, 88 insertions(+), 183 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index eaaa5883..e2405d3f 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -162,7 +162,7 @@ H5P.VideoHtml5 = (function ($) { } } - return videoXAPI.getArgsXAPIInitialized(video.currentTime, video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; diff --git a/scripts/x-api.js b/scripts/x-api.js index da2ddd8f..c349066a 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -35,12 +35,41 @@ H5P.VideoXAPI = (function ($) { var currentTime = 0; + /** + * Generate common xAPI statement elements (Video Profile). + + * @param {Object} params - Parameters. + * @param {string} params.verb - Verb for the xAPI statement. + * @param {Object} [params.result] - Extensions for the object. + * @param {Object} [params.extensionsContext] - Extensions for the context. + * @return {Object} JSON xAPI statement + */ + self.getArgsXAPI = function (params) { + params.extensionsContext = params.extensionsContext || {}; + + var dateTime = new Date(); + var timeStamp = dateTime.toISOString(); + + return { + 'verb': { + 'id': 'http://adlnet.gov/expapi/verbs/' + params.verb, + 'display': {'en-US': params.verb} + }, + 'object': getXAPIObject(), + 'result': params.result, + 'context': { + 'contextActivities': {'category': [{'id': 'https://w3id.org/xapi/video'}]}, + 'extensions': params.extensionsContext + }, + 'timestamp': timeStamp + }; + }; + /** * Generates 'initialized' xAPI statement (Video Profile). * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#231-initialized * * @public - * @param {Number} currentTime time of the video currently * @param {Number} width width of the current screen * @param {Number} height height of the current video screen * @param {Number} rate playback rate @@ -51,14 +80,12 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement * */ - self.getArgsXAPIInitialized = function (currentTime, width, height, rate, volume, ccEnabled, ccLanguage, quality) { + self.getArgsXAPIInitialized = function (width, height, rate, volume, ccEnabled, ccLanguage, quality) { // If quality isn't provided, set it to the height of the video. quality = typeof quality !== 'undefined' ? quality : height; // Variables used in compiling xAPI results. - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(currentTime); + var screenSize = screen.width + 'x' + screen.height; var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : 'undetermined'; var playbackRate = rate; @@ -95,24 +122,10 @@ H5P.VideoXAPI = (function ($) { extensions['https://w3id.org/xapi/video/extensions/volume'] = volume; } - return { - 'verb': { - 'id': 'http://adlnet.gov/expapi/verbs/initialized', - 'display': { - 'en-US': 'initialized' - } - }, - 'object': getXAPIObject(), - 'context' : { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': extensions - }, - 'timestamp': timeStamp - }; + return self.getArgsXAPI({ + verb: 'initialized', + extensionsContext: extensions + }); }; /** @@ -124,37 +137,18 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement */ self.getArgsXAPIPlayed = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(currentTime); playingSegmentStart = resultExtTime; - return { - 'verb': { - 'id': 'https://w3id.org/xapi/video/verbs/played', - 'display': { - 'en-US': 'played' - } + return self.getArgsXAPI({ + verb: 'played', + result: {extensions: { + 'https://w3id.org/xapi/video/extensions/time': resultExtTime} }, - 'object': getXAPIObject(), - 'result': { - 'extensions': { - 'https://w3id.org/xapi/video/extensions/time': resultExtTime, - } - }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': { - 'https://w3id.org/xapi/video/extensions/session-id': sessionID - } - }, - 'timestamp': timeStamp - }; + extensionsContext: { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID + } + }); }; /** @@ -167,13 +161,8 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement */ self.getArgsXAPIPaused = function (currentTime, duration) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(currentTime); - var progress = self.getProgress(currentTime, duration); - endPlayingSegment(resultExtTime); var extensions = {}; @@ -187,29 +176,13 @@ H5P.VideoXAPI = (function ($) { extensions['https://w3id.org/xapi/video/extensions/played-segments'] = stringifyPlayedSegments(); } - return { - 'verb': { - 'id': 'https://w3id.org/xapi/video/verbs/paused', - 'display': { - 'en-US': 'paused' - } - }, - 'object': getXAPIObject(), - 'result': { - 'extensions': extensions - }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': { - 'https://w3id.org/xapi/video/extensions/session-id': sessionID - } - }, - 'timestamp' : timeStamp - }; + return self.getArgsXAPI({ + verb: 'paused', + result: {extensions: extensions}, + extensionsContext: { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID + } + }); }; /** @@ -221,38 +194,22 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement */ self.getArgsXAPISeeked = function (currentTime) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); var resultExtTime = formatFloat(currentTime); endPlayingSegment(formatFloat(self.previousTime)); playingSegmentStart = resultExtTime; - return { - 'verb': { - 'id': 'https://w3id.org/xapi/video/verbs/seeked', - 'display': { - 'en-US': 'seeked' - } - }, - 'object': getXAPIObject(), - 'result': { - 'extensions' : { + return self.getArgsXAPI({ + verb: 'seeked', + result: { + extensions: { 'https://w3id.org/xapi/video/extensions/time-from': formatFloat(self.previousTime), 'https://w3id.org/xapi/video/extensions/time-to': playingSegmentStart } }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': { - 'https://w3id.org/xapi/video/extensions/session-id': sessionID - } - }, - 'timestamp' : timeStamp - }; + extensionsContext: { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID + } + }); }; /** @@ -266,37 +223,19 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement */ self.getArgsXAPIVolumeChanged = function (currentTime, muted, volume) { - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); volumeChangedAt = formatFloat(currentTime); volume = muted ? 0 : formatFloat(volume); - return { - 'verb': { - 'id': 'http://adlnet.gov/expapi/verbs/interacted', - 'display': { - 'en-US': 'interacted' - } + return self.getArgsXAPI({ + verb: 'interacted', + result: {extensions: { + 'https://w3id.org/xapi/video/extensions/time': volumeChangedAt} }, - 'object': getXAPIObject(), - 'result' : { - 'extensions': { - 'https://w3id.org/xapi/video/extensions/time': volumeChangedAt - } - }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': { - 'https://w3id.org/xapi/video/extensions/session-id': sessionID, - 'https://w3id.org/xapi/video/extensions/volume': volume - } - }, - 'timestamp' : timeStamp - }; + extensionsContext: { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID, + 'https://w3id.org/xapi/video/extensions/volume': volume + } + }); }; /** @@ -312,14 +251,12 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen) { fullscreen = typeof fullscreen !== 'undefined' ? fullscreen : false; - - var dateTime = new Date(); - var timeStamp = dateTime.toISOString(); - var resultExtTime = formatFloat(currentTime); var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + 'x' + screen.height; var playbackSize = width + 'x' + height; + var resultExtTime = formatFloat(currentTime); + var extensions = {}; if (typeof sessionID !== 'undefined') { extensions['https://w3id.org/xapi/video/extensions/session-id'] = sessionID; @@ -334,29 +271,13 @@ H5P.VideoXAPI = (function ($) { extensions['https://w3id.org/xapi/video/extensions/video-playback-size'] = playbackSize; } - return { - 'verb': { - 'id': 'http://adlnet.gov/expapi/verbs/interacted', - 'display': { - 'en-US': 'interacted' - } - }, - 'object': getXAPIObject(), - 'result': { - 'extensions': { - 'https://w3id.org/xapi/video/extensions/time': resultExtTime - } - }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': extensions + return self.getArgsXAPI({ + verb: 'interacted', + result: {extensions: { + 'https://w3id.org/xapi/video/extensions/time': resultExtTime} }, - 'timestamp' : timeStamp - }; + extensionsContext: extensions + }); }; /** @@ -371,10 +292,8 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPICompleted = function (currentTime, duration, progress) { var resultExtTime = formatFloat(currentTime); - var dateTime = new Date(); endPlayingSegment(resultExtTime); playingSegmentStart = 0; - var timeStamp = dateTime.toISOString(); var extensions = {}; if (typeof resultExtTime !== 'undefined') { @@ -387,31 +306,17 @@ H5P.VideoXAPI = (function ($) { extensions['https://w3id.org/xapi/video/extensions/played-segments'] = stringifyPlayedSegments(); } - return { - 'verb': { - 'id': 'http://adlnet.gov/expapi/verbs/completed', - 'display': { - 'en-US': 'completed' - } - }, - 'object': getXAPIObject(), - 'result': { + return self.getArgsXAPI({ + verb: 'completed', + result: { 'extensions': extensions, - 'completion' : true, - 'duration' : secondsToISO8601Duration(duration) - }, - 'context': { - 'contextActivities': { - 'category': [{ - 'id': 'https://w3id.org/xapi/video' - }] - }, - 'extensions': { - 'https://w3id.org/xapi/video/extensions/session-id': sessionID - } + 'completion': true, + 'duration': secondsToISO8601Duration(duration) }, - 'timestamp' : timeStamp - }; + extensionsContext: { + 'https://w3id.org/xapi/video/extensions/session-id': sessionID + } + }); }; /** @@ -574,4 +479,4 @@ H5P.VideoXAPI = (function ($) { }; return XAPI; -})(H5P.jQuery); +})(H5P.jQuery); diff --git a/scripts/youtube.js b/scripts/youtube.js index 93610605..d00a3d0f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -240,7 +240,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(player.getCurrentTime(), width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return videoXAPI.getArgsXAPIInitialized(width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); }; From f3581ac056513578ed7b8876cc8c2131854a8d11 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 14 Mar 2018 10:41:09 +0100 Subject: [PATCH 106/137] HFP-1779 Allow false for full-screen extension on init The Video CoP does allow the full-screen extension to be false. Cmp. https://github.com/liveaspankaj/xapi-video-cop/blob/master/statement_data_model.md --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index c349066a..ba03f620 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -94,7 +94,7 @@ H5P.VideoXAPI = (function ($) { volume = formatFloat(volume); var extensions = {}; - if (typeof isFullscreen !== 'undefined' && isFullscreen) { + if (typeof isFullscreen !== 'undefined') { extensions['https://w3id.org/xapi/video/extensions/full-screen'] = isFullscreen; } if (typeof screenSize !== 'undefined') { From 0585bbb4316454c15d7e88e87ca6bf528f17c7c9 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 14 Mar 2018 11:23:08 +0100 Subject: [PATCH 107/137] HFP-1779 Build xAPIObject with base information only once --- scripts/x-api.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index ba03f620..9de71135 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -33,7 +33,7 @@ H5P.VideoXAPI = (function ($) { var volumeChangedAt = 0; var sessionID = H5P.createUUID(); var currentTime = 0; - + var xAPIBase; /** * Generate common xAPI statement elements (Video Profile). @@ -405,8 +405,13 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} 'Object' portion of JSON xAPI statement */ var getXAPIObject = function () { + if (xAPIBase !== undefined) { + return xAPIBase; + } + var event = new H5P.XAPIEvent(); event.setObject(videoInstance); + var xAPIObject = event.data.statement.object; // Add definition type (required by xAPI Video Profile). @@ -422,7 +427,7 @@ H5P.VideoXAPI = (function ($) { }; } } - + xAPIBase = xAPIObject; return xAPIObject; }; } From 94aa8c20ae92ce62d897987b7da89acdfcda0cb2 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 14 Mar 2018 11:29:31 +0100 Subject: [PATCH 108/137] HFP-1779 Move xAPI trigger to video.js --- scripts/html5.js | 26 -------------------------- scripts/video.js | 24 ++++++++++++++++++++++++ scripts/youtube.js | 27 --------------------------- 3 files changed, 24 insertions(+), 53 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index e2405d3f..5e885dd6 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -735,32 +735,6 @@ H5P.VideoHtml5 = (function ($) { }); }); - // xAPI extension events for video. - self.on('seeked', function (event) { - this.triggerXAPI('seeked', event.data); - }); - self.on('volumechange', function (event) { - this.triggerXAPI('interacted', event.data); - }); - self.on('finished', function (event) { - // Triggered as finished to be seperate from H5Ps completed, - // but statement is sent as completed and differentiated by object.id - this.triggerXAPI('completed', event.data); - }); - self.on('fullscreen', function (event) { - // @todo: Not currently used. - this.triggerXAPI('interacted', event.data); - }); - self.on('play', function (event) { - this.triggerXAPI('played', event.data); - }); - self.on('xAPIloaded', function (event) { - this.triggerXAPI('initialized', event.data); - }); - self.on('paused', function (event) { - this.triggerXAPI('paused', event.data); - }); - // Video controls are ready nextTick(function () { self.trigger('ready'); diff --git a/scripts/video.js b/scripts/video.js index a1b5f8a5..a33423ab 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -115,6 +115,30 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { self.on('loaded', function () { self.trigger('resize'); }); + // xAPI extension events for video. + self.on('seeked', function (event) { + self.triggerXAPI('seeked', event.data); + }); + self.on('volumechange', function (event) { + self.triggerXAPI('interacted', event.data); + }); + self.on('finished', function (event) { + // Triggered as finished to be seperate from H5Ps completed, + // but statement is sent as completed and differentiated by object.id + self.triggerXAPI('completed', event.data); + }); + self.on('fullscreen', function (event) { + self.triggerXAPI('interacted', event.data); + }); + self.on('play', function (event) { + self.triggerXAPI('played', event.data); + }); + self.on('xAPIloaded', function (event){ + self.triggerXAPI('initialized',event.data); + }); + self.on('paused', function (event) { + self.triggerXAPI('paused', event.data); + }); // Find player for video sources if (sources.length) { diff --git a/scripts/youtube.js b/scripts/youtube.js index d00a3d0f..ec18d5e6 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -244,33 +244,6 @@ H5P.VideoYouTube = (function ($) { }; - // xAPI extension events for video. - self.on('seeked', function (event) { - this.triggerXAPI('seeked', event.data); - }); - self.on('volumechange', function (event) { - // @todo: Not currently used. - this.triggerXAPI('interacted', event.data); - }); - self.on('finished', function (event) { - // Triggered as finished to be seperate from H5Ps completed, - // but statement is sent as completed and differentiated by object.id - this.triggerXAPI('completed', event.data); - }); - self.on('fullscreen', function (event) { - // @todo: Not currently used. - this.triggerXAPI('interacted', event.data); - }); - self.on('play', function (event) { - this.triggerXAPI('played', event.data); - }); - self.on('xAPIloaded', function (event){ - this.triggerXAPI('initialized',event.data); - }); - self.on('paused', function (event) { - this.triggerXAPI('paused', event.data); - }); - /** * Indicates if the video must be clicked for it to start playing. * For instance YouTube videos on iPad must be pressed to start playing. From 40db1c34aef0f5837246666f862582b85922774d Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Wed, 14 Mar 2018 14:30:59 +0100 Subject: [PATCH 109/137] HFP-1779 Move video state variables to video object Some video state variables were stored with the xAPI object, not with the the video object. This was step 1 in moving some of the duplicate logic within the video handlers to video.js --- scripts/html5.js | 24 ++++++++++++------------ scripts/video.js | 9 ++++++++- scripts/x-api.js | 8 ++------ scripts/youtube.js | 26 +++++++++++++------------- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 5e885dd6..99d84a00 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -167,7 +167,7 @@ H5P.VideoHtml5 = (function ($) { }; // Set duration used for xAPI statements. - videoXAPI.duration = video.duration; + self.duration = video.duration; /** * Helps registering events. @@ -194,11 +194,11 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (videoXAPI.seeking === true) { - extraArg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); + if (self.seeking === true) { + extraArg = videoXAPI.getArgsXAPISeeked(self.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; - videoXAPI.seeking = false; + self.seeking = false; } else if (lastSend !== 'play') { extraArg = videoXAPI.getArgsXAPIPlayed(video.currentTime); @@ -209,7 +209,7 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && videoXAPI.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && self.seeking === false && video.currentTime !== video.duration) { extraTrigger = 'paused'; extraArg = videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; @@ -241,14 +241,14 @@ H5P.VideoHtml5 = (function ($) { lastSend = 'volumechange'; break; case 'play': - if (videoXAPI.seeking === false && lastSend !== h5p) { + if (self.seeking === false && lastSend !== h5p) { arg = videoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo); + arg = videoXAPI.getArgsXAPISeeked(self.seekedTo); lastSend = 'seeked'; - videoXAPI.seeking = false; + self.seeking = false; h5p = 'seeked'; } break; @@ -523,12 +523,12 @@ H5P.VideoHtml5 = (function ($) { video.play(); video.pause(); } - if (videoXAPI.seeking === false) { - videoXAPI.previousTime = video.currentTime; + if (self.seeking === false) { + self.previousTime = video.currentTime; } video.currentTime = time; - videoXAPI.seeking = true; - videoXAPI.seekedTo = time; + self.seeking = true; + self.seekedTo = time; }; /** diff --git a/scripts/video.js b/scripts/video.js index a33423ab..ba32057e 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -18,6 +18,12 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { // Ref youtube.js - ipad & youtube - issue self.pressToPlay = false; + // Values needed for xAPI triggering + self.previousTime = 0; + self.seeking = false; + self.seekedTo = 0; + self.duration = 0; + // Initialize event inheritance H5P.EventDispatcher.call(self); @@ -134,7 +140,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { self.triggerXAPI('played', event.data); }); self.on('xAPIloaded', function (event){ - self.triggerXAPI('initialized',event.data); + self.duration = self.getDuration(); + self.triggerXAPI('initialized', event.data); }); self.on('paused', function (event) { self.triggerXAPI('paused', event.data); diff --git a/scripts/x-api.js b/scripts/x-api.js index 9de71135..9a753769 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -16,10 +16,6 @@ H5P.VideoXAPI = (function ($) { * * @public */ - self.previousTime = 0; - self.seeking = false; - self.seekedTo = 0; - self.duration = 0; /** * Variables to track internal video state. @@ -195,14 +191,14 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPISeeked = function (currentTime) { var resultExtTime = formatFloat(currentTime); - endPlayingSegment(formatFloat(self.previousTime)); + endPlayingSegment(formatFloat(instance.previousTime)); playingSegmentStart = resultExtTime; return self.getArgsXAPI({ verb: 'seeked', result: { extensions: { - 'https://w3id.org/xapi/video/extensions/time-from': formatFloat(self.previousTime), + 'https://w3id.org/xapi/video/extensions/time-from': formatFloat(instance.previousTime), 'https://w3id.org/xapi/video/extensions/time-to': playingSegmentStart } }, diff --git a/scripts/youtube.js b/scripts/youtube.js index ec18d5e6..a53fd725 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -126,28 +126,28 @@ H5P.VideoYouTube = (function ($) { // Calls for xAPI events. if (state.data === 1) { // Get and send play call when not seeking. - if (videoXAPI.seeking === false) { + if (self.seeking === false) { self.trigger('play', videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', videoXAPI.getArgsXAPISeeked(videoXAPI.seekedTo)); - videoXAPI.seeking = false; + self.trigger('seeked', videoXAPI.getArgsXAPISeeked(self.seekedTo)); + self.seeking = false; } } else if (state.data === 2) { // This is a paused event. - if (videoXAPI.seeking === false) { - self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), player.getDuration())); + if (self.seeking === false) { + self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } else if (state.data === 0) { // Send xapi trigger if video progress indicates finished. - var length = player.getDuration(); + var length = self.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = videoXAPI.getProgress(length, length); if (progress >= 1) { - var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), player.getDuration(), progress); + var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } } @@ -193,7 +193,7 @@ H5P.VideoYouTube = (function ($) { * @param {string} Which dimension to return ('width' or 'height') */ var getWidthOrHeight = function (returnType) { - var quality = player.getPlaybackQuality(); + var quality = self.getQuality(); var width = ''; var height = ''; switch (quality) { @@ -240,7 +240,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(width, height, player.getPlaybackRate(), player.getVolume(), ccEnabled, ccLanguage, player.getPlaybackQuality()); + return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality()); }; @@ -360,12 +360,12 @@ H5P.VideoYouTube = (function ($) { return; } - if (videoXAPI.seeking === false) { - videoXAPI.previousTime = player.getCurrentTime(); + if (self.seeking === false) { + self.previousTime = player.getCurrentTime(); } player.seekTo(time, true); - videoXAPI.seekedTo = time; - videoXAPI.seeking = true; + self.seekedTo = time; + self.seeking = true; }; /** From 4346b9257ca5211c776ea14fc0291495aed8a11b Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 14 Mar 2018 13:13:36 -1000 Subject: [PATCH 110/137] Fix for uncaught error when editing a new video --- scripts/x-api.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index da2ddd8f..835ec925 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -501,20 +501,23 @@ H5P.VideoXAPI = (function ($) { */ var getXAPIObject = function () { var event = new H5P.XAPIEvent(); - event.setObject(videoInstance); - var xAPIObject = event.data.statement.object; - - // Add definition type (required by xAPI Video Profile). - // @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#241-definition - xAPIObject.definition.type = 'https://w3id.org/xapi/video/activity-type/video'; - - // Add definition description (if video has a description). - if (videoInstance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { - var videoData = JSON.parse(H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent); - if (videoData && videoData.interactiveVideo && videoData.interactiveVideo.video && videoData.interactiveVideo.video.startScreenOptions && videoData.interactiveVideo.video.startScreenOptions.shortStartDescription) { - xAPIObject.definition.description = { - 'en-US': videoData.interactiveVideo.video.startScreenOptions.shortStartDescription - }; + + if (videoInstance && videoInstance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + videoInstance.contentId]) { + event.setObject(videoInstance); + xAPIObject = event.data.statement.object; + + // Add definition type (required by xAPI Video Profile). + // @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#241-definition + xAPIObject.definition.type = 'https://w3id.org/xapi/video/activity-type/video'; + + // Add definition description (if video has a description). + if (H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent) { + var videoData = JSON.parse(H5PIntegration.contents['cid-' + videoInstance.contentId].jsonContent); + if (videoData && videoData.interactiveVideo && videoData.interactiveVideo.video && videoData.interactiveVideo.video.startScreenOptions && videoData.interactiveVideo.video.startScreenOptions.shortStartDescription) { + xAPIObject.definition.description = { + 'en-US': videoData.interactiveVideo.video.startScreenOptions.shortStartDescription + }; + } } } From 6c0dbf9a4f22c99b4d9a363eb09552e37737af46 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 14 Mar 2018 13:14:51 -1000 Subject: [PATCH 111/137] Cache result of getXAPIObject (optimization) --- scripts/x-api.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/x-api.js b/scripts/x-api.js index 835ec925..41398ed6 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -33,6 +33,7 @@ H5P.VideoXAPI = (function ($) { var volumeChangedAt = 0; var sessionID = H5P.createUUID(); var currentTime = 0; + var xAPIObject = null; /** @@ -500,6 +501,10 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} 'Object' portion of JSON xAPI statement */ var getXAPIObject = function () { + if (xAPIObject !== null) { + return xAPIObject; + } + var event = new H5P.XAPIEvent(); if (videoInstance && videoInstance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + videoInstance.contentId]) { From 2224eddfb4543dad6608c32b9dd55a4d63d7e3e7 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Wed, 14 Mar 2018 13:15:05 -1000 Subject: [PATCH 112/137] Whitespace --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 41398ed6..06c72ff6 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -582,4 +582,4 @@ H5P.VideoXAPI = (function ($) { }; return XAPI; -})(H5P.jQuery); +})(H5P.jQuery); From d0bc373b00d13b9083d6bf17aa1960fcc5552c87 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 18 Mar 2018 16:04:21 +0100 Subject: [PATCH 113/137] HFP-1779 Allow different verb definition origins --- scripts/x-api.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 9a753769..0dd2c018 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -48,8 +48,8 @@ H5P.VideoXAPI = (function ($) { return { 'verb': { - 'id': 'http://adlnet.gov/expapi/verbs/' + params.verb, - 'display': {'en-US': params.verb} + 'id': params.verb, + 'display': {'en-US': params.verb.substr(params.verb.lastIndexOf('/') + 1)} }, 'object': getXAPIObject(), 'result': params.result, @@ -119,7 +119,7 @@ H5P.VideoXAPI = (function ($) { } return self.getArgsXAPI({ - verb: 'initialized', + verb: 'http://adlnet.gov/expapi/verbs/initialized', extensionsContext: extensions }); }; @@ -137,7 +137,7 @@ H5P.VideoXAPI = (function ($) { playingSegmentStart = resultExtTime; return self.getArgsXAPI({ - verb: 'played', + verb: 'https://w3id.org/xapi/video/verbs/played', result: {extensions: { 'https://w3id.org/xapi/video/extensions/time': resultExtTime} }, @@ -173,7 +173,7 @@ H5P.VideoXAPI = (function ($) { } return self.getArgsXAPI({ - verb: 'paused', + verb: 'https://w3id.org/xapi/video/verbs/paused', result: {extensions: extensions}, extensionsContext: { 'https://w3id.org/xapi/video/extensions/session-id': sessionID @@ -195,7 +195,7 @@ H5P.VideoXAPI = (function ($) { playingSegmentStart = resultExtTime; return self.getArgsXAPI({ - verb: 'seeked', + verb: 'https://w3id.org/xapi/video/verbs/seeked', result: { extensions: { 'https://w3id.org/xapi/video/extensions/time-from': formatFloat(instance.previousTime), @@ -223,7 +223,7 @@ H5P.VideoXAPI = (function ($) { volume = muted ? 0 : formatFloat(volume); return self.getArgsXAPI({ - verb: 'interacted', + verb: 'http://adlnet.gov/expapi/verbs/interacted', result: {extensions: { 'https://w3id.org/xapi/video/extensions/time': volumeChangedAt} }, @@ -268,7 +268,7 @@ H5P.VideoXAPI = (function ($) { } return self.getArgsXAPI({ - verb: 'interacted', + verb: 'http://adlnet.gov/expapi/verbs/interacted', result: {extensions: { 'https://w3id.org/xapi/video/extensions/time': resultExtTime} }, @@ -303,7 +303,7 @@ H5P.VideoXAPI = (function ($) { } return self.getArgsXAPI({ - verb: 'completed', + verb: 'http://adlnet.gov/expapi/verbs/completed', result: { 'extensions': extensions, 'completion': true, From f3ed616fbfce7572f5f7279006dcee7dab3cf143 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 23 Mar 2018 10:08:39 -1000 Subject: [PATCH 114/137] Implement volumechange event in youtube.js --- scripts/youtube.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/youtube.js b/scripts/youtube.js index a53fd725..f2802e8f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -420,6 +420,8 @@ H5P.VideoYouTube = (function ($) { return; } + self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), true, player.getVolume())); + player.mute(); }; @@ -433,6 +435,8 @@ H5P.VideoYouTube = (function ($) { return; } + self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), false, player.getVolume())); + player.unMute(); }; @@ -475,6 +479,8 @@ H5P.VideoYouTube = (function ($) { return; } + self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), player.isMuted(), level)); + player.setVolume(level); }; From d6e4f247a88a8ea5d874df5f463bd7059eb45d41 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 23 Mar 2018 10:27:43 -1000 Subject: [PATCH 115/137] Whitespace --- scripts/video.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/video.js b/scripts/video.js index ba32057e..afc6fe44 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -139,7 +139,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { self.on('play', function (event) { self.triggerXAPI('played', event.data); }); - self.on('xAPIloaded', function (event){ + self.on('xAPIloaded', function (event) { self.duration = self.getDuration(); self.triggerXAPI('initialized', event.data); }); From 91936f3926777f7927267324679b8dea6caa85f9 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 23 Mar 2018 10:28:07 -1000 Subject: [PATCH 116/137] Add note about fullscreen event not implemented --- scripts/video.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/video.js b/scripts/video.js index afc6fe44..42e50cfa 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -134,6 +134,7 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { self.triggerXAPI('completed', event.data); }); self.on('fullscreen', function (event) { + // Note: youtube.js and html5.js players do not fire this event. self.triggerXAPI('interacted', event.data); }); self.on('play', function (event) { From 166c8d302952e4800ffad92b9d31de935304ee9a Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 23 Mar 2018 10:38:01 -1000 Subject: [PATCH 117/137] Trigger completed statement at 95% or more watched When video ends, if the watched segments cover at least 95% of the total video time, then fire the xAPI completed statement. This covers edge cases where small imprecisions in play/pause timecodes cause getProgress() to report less-than-100% completion, even if the entire video was watched. --- scripts/html5.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 99d84a00..a16cc442 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -222,7 +222,7 @@ H5P.VideoHtml5 = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = videoXAPI.getProgress(length, length); - if (progress >= 1) { + if (progress >= 0.95) { extraTrigger = 'finished'; extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; diff --git a/scripts/youtube.js b/scripts/youtube.js index f2802e8f..8c0f134d 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -146,7 +146,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = videoXAPI.getProgress(length, length); - if (progress >= 1) { + if (progress >= 0.95) { var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } From 5c1f9959d34dcb0003d4c3f6ea6095ace5790959 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 16:22:05 +0200 Subject: [PATCH 118/137] HFP-1779 Move videoXAPI variable to video.js --- scripts/html5.js | 26 ++++++++++---------------- scripts/video.js | 2 ++ scripts/youtube.js | 24 +++++++++--------------- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index a16cc442..7869095a 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -12,12 +12,6 @@ H5P.VideoHtml5 = (function ($) { function Html5(sources, options, l10n) { var self = this; - /** - * xAPI Helper. - * @private - */ - var videoXAPI = new H5P.VideoXAPI(self); - /** * Displayed when the video is buffering * @private @@ -162,7 +156,7 @@ H5P.VideoHtml5 = (function ($) { } } - return videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return self.videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); }; @@ -195,13 +189,13 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PLAYING) { if (self.seeking === true) { - extraArg = videoXAPI.getArgsXAPISeeked(self.seekedTo); + extraArg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; self.seeking = false; } else if (lastSend !== 'play') { - extraArg = videoXAPI.getArgsXAPIPlayed(video.currentTime); + extraArg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -211,7 +205,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && self.seeking === false && video.currentTime !== video.duration) { extraTrigger = 'paused'; - extraArg = videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); + extraArg = self.videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -221,10 +215,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = videoXAPI.getProgress(length, length); + var progress = self.videoXAPI.getProgress(length, length); if (progress >= 0.95) { extraTrigger = 'finished'; - extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); + extraArg = self.videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; } } @@ -237,23 +231,23 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = videoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); + arg = self.videoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (self.seeking === false && lastSend !== h5p) { - arg = videoXAPI.getArgsXAPIPlayed(video.currentTime); + arg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = videoXAPI.getArgsXAPISeeked(self.seekedTo); + arg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); lastSend = 'seeked'; self.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = videoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); + arg = self.videoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': diff --git a/scripts/video.js b/scripts/video.js index 42e50cfa..13c40c1c 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -15,6 +15,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { function Video(parameters, id) { var self = this; + self.videoXAPI = new H5P.VideoXAPI(self); + // Ref youtube.js - ipad & youtube - issue self.pressToPlay = false; diff --git a/scripts/youtube.js b/scripts/youtube.js index 8c0f134d..6b9c3508 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -12,12 +12,6 @@ H5P.VideoYouTube = (function ($) { function YouTube(sources, options, l10n) { var self = this; - /** - * xAPI Helper. - * @private - */ - var videoXAPI = new H5P.VideoXAPI(self); - var player; var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; @@ -127,17 +121,17 @@ H5P.VideoYouTube = (function ($) { if (state.data === 1) { // Get and send play call when not seeking. if (self.seeking === false) { - self.trigger('play', videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); + self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', videoXAPI.getArgsXAPISeeked(self.seekedTo)); + self.trigger('seeked', self.videoXAPI.getArgsXAPISeeked(self.seekedTo)); self.seeking = false; } } else if (state.data === 2) { // This is a paused event. if (self.seeking === false) { - self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); + self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } else if (state.data === 0) { @@ -145,9 +139,9 @@ H5P.VideoYouTube = (function ($) { var length = self.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = videoXAPI.getProgress(length, length); + var progress = self.videoXAPI.getProgress(length, length); if (progress >= 0.95) { - var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); + var arg = self.videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } } @@ -240,7 +234,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality()); + return self.videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality()); }; @@ -420,7 +414,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), true, player.getVolume())); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), true, player.getVolume())); player.mute(); }; @@ -435,7 +429,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), false, player.getVolume())); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), false, player.getVolume())); player.unMute(); }; @@ -479,7 +473,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), player.isMuted(), level)); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), player.isMuted(), level)); player.setVolume(level); }; From 1f18bdb9324a1137d2bef99c6c75af6f48f6f5c2 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 19:41:25 +0200 Subject: [PATCH 119/137] HFP-1779 Prevent triggering paused before seeked The current implementation of H5P.Video was not designed to track the experience of the user or video state as perceived by the user. This job is left to the content type using H5P.Video, which forces the implementation of new xAPI verbs to be quirky. Also, it might not be possible to get rid of some unneccesary xAPI statements that are fired unless there's some refactoring which would also include e.g. Interactive Video. --- scripts/html5.js | 11 +++-------- scripts/video.js | 24 ++++++++++++++++++++++-- scripts/youtube.js | 7 ++----- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 7869095a..58b88e33 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -188,13 +188,7 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (self.seeking === true) { - extraArg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); - extraTrigger = 'seeked'; - lastSend = 'seeked'; - self.seeking = false; - } - else if (lastSend !== 'play') { + if (lastSend !== 'play') { extraArg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; @@ -203,7 +197,7 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && self.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && self.seeking === false && video.currentTime !== video.duration && self.previousState !== H5P.Video.BUFFERING) { extraTrigger = 'paused'; extraArg = self.videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; @@ -300,6 +294,7 @@ H5P.VideoHtml5 = (function ($) { arg = self.getPlaybackRate(); break; } + self.previousState = arg; self.trigger(h5p, arg); // Make extra calls for events with needed values for xAPI statement. diff --git a/scripts/video.js b/scripts/video.js index 13c40c1c..a158442d 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -20,11 +20,28 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { // Ref youtube.js - ipad & youtube - issue self.pressToPlay = false; - // Values needed for xAPI triggering + // Values needed for xAPI triggering, set by handlers self.previousTime = 0; self.seeking = false; self.seekedTo = 0; self.duration = 0; + self.previousState = -1; + self.mousedown = false; + + /* + * Used to distinguish seeking from pausing + * TODO: This might be much cleaner with refactoring IV, video and the handlers + */ + document.addEventListener('mousedown', function() { + self.mousedown = true; + }); + document.addEventListener('mouseup', function() { + if (self.seeking) { + self.trigger('seeked', self.videoXAPI.getArgsXAPISeeked(self.seekedTo)); + self.seeking = false; + } + self.mousedown = false; + }); // Initialize event inheritance H5P.EventDispatcher.call(self); @@ -147,7 +164,10 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { self.triggerXAPI('initialized', event.data); }); self.on('paused', function (event) { - self.triggerXAPI('paused', event.data); + // if mouse button is down, we're seeking + if (self.mousedown === false) { + self.triggerXAPI('paused', event.data); + } }); // Find player for video sources diff --git a/scripts/youtube.js b/scripts/youtube.js index 6b9c3508..7c5b2a8e 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -123,14 +123,10 @@ H5P.VideoYouTube = (function ($) { if (self.seeking === false) { self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } - else { - self.trigger('seeked', self.videoXAPI.getArgsXAPISeeked(self.seekedTo)); - self.seeking = false; - } } else if (state.data === 2) { // This is a paused event. - if (self.seeking === false) { + if (self.seeking === false && self.previousState !== 3) { self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } @@ -147,6 +143,7 @@ H5P.VideoYouTube = (function ($) { } } } + self.previousState = state.data; }, onPlaybackQualityChange: function (quality) { self.trigger('qualityChange', quality.data); From f977a6450fa5a80a4516245bdcc026d5bb0a7c8b Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 19:58:28 +0200 Subject: [PATCH 120/137] HFP-1779 Clean up code Use identity operator instead of equality operator Use constants for video state --- scripts/html5.js | 3 ++- scripts/video.js | 1 + scripts/youtube.js | 14 ++++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 58b88e33..5970f269 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -1,5 +1,6 @@ /** @namespace H5P */ H5P.VideoHtml5 = (function ($) { + 'use strict'; /** * HTML5 video player for H5P. @@ -283,7 +284,7 @@ H5P.VideoHtml5 = (function ($) { skipRateChange = false; return; // Avoid firing event when changing back } - if (H5P.Video.IE11_PLAYBACK_RATE_FIX && playbackRate != video.playbackRate) { // Intentional + if (H5P.Video.IE11_PLAYBACK_RATE_FIX && playbackRate !== video.playbackRate) { // Intentional // Prevent change in playback rate not triggered by the user video.playbackRate = playbackRate; skipRateChange = true; diff --git a/scripts/video.js b/scripts/video.js index a158442d..e9ab971d 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -1,5 +1,6 @@ /** @namespace H5P */ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { + 'use strict'; /** * The ultimate H5P video player! diff --git a/scripts/youtube.js b/scripts/youtube.js index 7c5b2a8e..52a9e562 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -1,5 +1,7 @@ /** @namespace H5P */ + H5P.VideoYouTube = (function ($) { +'use strict' /** * YouTube video player for H5P. @@ -105,7 +107,7 @@ H5P.VideoYouTube = (function ($) { } }, onStateChange: function (state) { - if (state.data > -1 && state.data < 4) { + if (state.data >= H5P.Video.ENDED && state.data <= H5P.Video.BUFFERING) { // Fix for keeping playback rate in IE11 if (H5P.Video.IE11_PLAYBACK_RATE_FIX && state.data === H5P.Video.PLAYING && playbackRate !== 1) { @@ -118,19 +120,19 @@ H5P.VideoYouTube = (function ($) { self.trigger('stateChange', state.data); // Calls for xAPI events. - if (state.data === 1) { + if (state.data === H5P.Video.PLAYING) { // Get and send play call when not seeking. if (self.seeking === false) { self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } } - else if (state.data === 2) { + else if (state.data === H5P.Video.PAUSED) { // This is a paused event. - if (self.seeking === false && self.previousState !== 3) { + if (self.seeking === false && self.previousState !== H5P.Video.BUFFERING) { self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } - else if (state.data === 0) { + else if (state.data === H5P.Video.ENDED) { // Send xapi trigger if video progress indicates finished. var length = self.duration; if (length > 0) { @@ -214,7 +216,7 @@ H5P.VideoYouTube = (function ($) { break; } - return (returnType.toLowerCase().trim()=='width') ? width : height; + return (returnType.toLowerCase().trim() === 'width') ? width : height; }; /** From 283f2e37f63cd532c569acbc31b807cdbd4b9eff Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 20:44:24 +0200 Subject: [PATCH 121/137] HFP-1779 Clean code --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 169a39ec..817fd2bf 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -1,5 +1,6 @@ /** @namespace H5P */ H5P.VideoXAPI = (function ($) { + 'use strict'; /** * Xapi video statement generator for H5P. @@ -425,7 +426,6 @@ H5P.VideoXAPI = (function ($) { } } } - xAPIBase = xAPIObject; return xAPIObject; }; } From 6dd68ac38c3062766dc1ec15ac2d082398d2d970 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 20:46:03 +0200 Subject: [PATCH 122/137] HFP-1779 Stop setting segment start time on play event --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 817fd2bf..a648d633 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -135,7 +135,7 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPIPlayed = function (currentTime) { var resultExtTime = formatFloat(currentTime); - playingSegmentStart = resultExtTime; + //playingSegmentStart = resultExtTime; return self.getArgsXAPI({ verb: 'https://w3id.org/xapi/video/verbs/played', From 8223c20f3b0f192e41d23681c53fffc971eb92b4 Mon Sep 17 00:00:00 2001 From: Oliver Tacke Date: Sun, 1 Apr 2018 21:31:39 +0200 Subject: [PATCH 123/137] HFP-1779 Move finished threshold to variable --- scripts/html5.js | 2 +- scripts/video.js | 2 ++ scripts/youtube.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 5970f269..6e54d391 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -211,7 +211,7 @@ H5P.VideoHtml5 = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = self.videoXAPI.getProgress(length, length); - if (progress >= 0.95) { + if (progress >= self.finishedThreshold) { extraTrigger = 'finished'; extraArg = self.videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; diff --git a/scripts/video.js b/scripts/video.js index e9ab971d..c3f0975f 100644 --- a/scripts/video.js +++ b/scripts/video.js @@ -21,6 +21,8 @@ H5P.Video = (function ($, ContentCopyrights, MediaCopyright, handlers) { // Ref youtube.js - ipad & youtube - issue self.pressToPlay = false; + self.finishedThreshold = 0.95; + // Values needed for xAPI triggering, set by handlers self.previousTime = 0; self.seeking = false; diff --git a/scripts/youtube.js b/scripts/youtube.js index 52a9e562..6ef7efd3 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -138,7 +138,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = self.videoXAPI.getProgress(length, length); - if (progress >= 0.95) { + if (progress >= self.finishedThreshold) { var arg = self.videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } From 12912a8f282e20f8af1446e3d348d1a9d51081fc Mon Sep 17 00:00:00 2001 From: kirkj Date: Wed, 4 Apr 2018 09:08:58 -1000 Subject: [PATCH 124/137] Add Completion Threshold to initializes extensions --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 169a39ec..45597b22 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -117,7 +117,7 @@ H5P.VideoXAPI = (function ($) { if (typeof volume !== 'undefined') { extensions['https://w3id.org/xapi/video/extensions/volume'] = volume; } - + extensions['https://w3id.org/xapi/video/extensions/completion-threshold'] = instance.finishedThreshold; return self.getArgsXAPI({ verb: 'http://adlnet.gov/expapi/verbs/initialized', extensionsContext: extensions From 4f70c16568c6f5fc75e8ffbca0df3184309b5394 Mon Sep 17 00:00:00 2001 From: kirkj Date: Mon, 23 Apr 2018 13:32:20 -1000 Subject: [PATCH 125/137] Add video length to initialized statement Was asked by Jon to add video length to the extensions sent by the intialized verb. --- scripts/html5.js | 2 +- scripts/x-api.js | 3 ++- scripts/youtube.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index a16cc442..b5508349 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -162,7 +162,7 @@ H5P.VideoHtml5 = (function ($) { } } - return videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage); + return videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage, video.videoHeight, video.duration); }; diff --git a/scripts/x-api.js b/scripts/x-api.js index 45597b22..140d3c89 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -76,7 +76,7 @@ H5P.VideoXAPI = (function ($) { * @returns {Object} JSON xAPI statement * */ - self.getArgsXAPIInitialized = function (width, height, rate, volume, ccEnabled, ccLanguage, quality) { + self.getArgsXAPIInitialized = function (width, height, rate, volume, ccEnabled, ccLanguage, quality, videoLength) { // If quality isn't provided, set it to the height of the video. quality = typeof quality !== 'undefined' ? quality : height; @@ -90,6 +90,7 @@ H5P.VideoXAPI = (function ($) { volume = formatFloat(volume); var extensions = {}; + extensions['https://w3id.org/xapi/video/extensions/length'] = videoLength; if (typeof isFullscreen !== 'undefined') { extensions['https://w3id.org/xapi/video/extensions/full-screen'] = isFullscreen; } diff --git a/scripts/youtube.js b/scripts/youtube.js index 8c0f134d..20d1984c 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -240,7 +240,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality()); + return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality(), self.duration); }; From 035d23a0fe2eb951e050b04bda2cfb0d395eefb3 Mon Sep 17 00:00:00 2001 From: COE User Date: Tue, 24 Apr 2018 17:58:40 -1000 Subject: [PATCH 126/137] Use Youtube API to pass correct duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Youtube was passing 0 in for video duration so I changed this to use the api’s method to get video length. --- scripts/x-api.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index a49271cd..fae40eeb 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -80,7 +80,7 @@ H5P.VideoXAPI = (function ($) { self.getArgsXAPIInitialized = function (width, height, rate, volume, ccEnabled, ccLanguage, quality, videoLength) { // If quality isn't provided, set it to the height of the video. quality = typeof quality !== 'undefined' ? quality : height; - + videoLength = typeof videoLength !== 'undefined' ? parseFloat(videoLength).toFixed(3) : videoLength; // Variables used in compiling xAPI results. var screenSize = screen.width + 'x' + screen.height; diff --git a/scripts/youtube.js b/scripts/youtube.js index 20d1984c..854f1263 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -240,7 +240,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality(), self.duration); + return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality(), self.getDuration()); }; From 3707be24116f1f7f0510b75600c9e3e36ac54dc4 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 26 Apr 2018 13:31:09 -1000 Subject: [PATCH 127/137] =?UTF-8?q?Don=E2=80=99t=20report=20video-playback?= =?UTF-8?q?-size=20if=20not=20defined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/x-api.js | 2 +- scripts/youtube.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index fae40eeb..c202227a 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -84,7 +84,7 @@ H5P.VideoXAPI = (function ($) { // Variables used in compiling xAPI results. var screenSize = screen.width + 'x' + screen.height; - var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : 'undetermined'; + var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : undefined; var playbackRate = rate; var userAgent = navigator.userAgent; var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; diff --git a/scripts/youtube.js b/scripts/youtube.js index 854f1263..69930f5f 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -194,8 +194,9 @@ H5P.VideoYouTube = (function ($) { */ var getWidthOrHeight = function (returnType) { var quality = self.getQuality(); - var width = ''; - var height = ''; + var width; + var height; + switch (quality) { case 'small': width = '320'; From d243bb12332c240c1ae866cdcecd9c92173f15f8 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 26 Apr 2018 13:31:55 -1000 Subject: [PATCH 128/137] Fix logic error in determining isFullscreen --- scripts/x-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index c202227a..75cab362 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -87,7 +87,7 @@ H5P.VideoXAPI = (function ($) { var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : undefined; var playbackRate = rate; var userAgent = navigator.userAgent; - var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || false; + var isFullscreen = document.fullscreenElement || document.mozFullScreen || document.webkitIsFullScreen || false; volume = formatFloat(volume); var extensions = {}; @@ -249,7 +249,7 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPIFullScreen = function (currentTime, width, height, fullscreen) { fullscreen = typeof fullscreen !== 'undefined' ? fullscreen : false; - var isFullscreen = Document.fullscreenElement !== null || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; + var isFullscreen = document.fullscreenElement || document.mozFullScreen || document.webkitIsFullScreen || fullscreen; var screenSize = screen.width + 'x' + screen.height; var playbackSize = width + 'x' + height; From 550157968053f785d322c5295ef9cfb4046bd2a0 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 26 Apr 2018 13:32:19 -1000 Subject: [PATCH 129/137] Whitespace --- scripts/x-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 75cab362..2a98755d 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -80,9 +80,9 @@ H5P.VideoXAPI = (function ($) { self.getArgsXAPIInitialized = function (width, height, rate, volume, ccEnabled, ccLanguage, quality, videoLength) { // If quality isn't provided, set it to the height of the video. quality = typeof quality !== 'undefined' ? quality : height; - videoLength = typeof videoLength !== 'undefined' ? parseFloat(videoLength).toFixed(3) : videoLength; - // Variables used in compiling xAPI results. + videoLength = typeof videoLength !== 'undefined' ? parseFloat(videoLength).toFixed(3) : videoLength; + // Variables used in compiling xAPI results. var screenSize = screen.width + 'x' + screen.height; var playbackSize = (width !== undefined && width !== '') ? width + 'x' + height : undefined; var playbackRate = rate; From 7ab090bc32649c3296b3d1925d049cb6aceab290 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Thu, 26 Apr 2018 13:32:35 -1000 Subject: [PATCH 130/137] =?UTF-8?q?Don=E2=80=99t=20report=20video=20length?= =?UTF-8?q?=20if=20not=20defined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/x-api.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 2a98755d..0da20394 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -91,8 +91,11 @@ H5P.VideoXAPI = (function ($) { volume = formatFloat(volume); var extensions = {}; - extensions['https://w3id.org/xapi/video/extensions/length'] = videoLength; - if (typeof isFullscreen !== 'undefined') { + + if (typeof videoLength !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/length'] = videoLength; + } + if (typeof isFullscreen !== 'undefined' && isFullscreen) { extensions['https://w3id.org/xapi/video/extensions/full-screen'] = isFullscreen; } if (typeof screenSize !== 'undefined') { From e1af0c158d33fe7041714d7d6d7e192de6921f47 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 09:56:29 -1000 Subject: [PATCH 131/137] Replay 5c1f995 due to incorrect merge conflict resolution in 94ff7b3 --- scripts/html5.js | 26 ++++++++++---------------- scripts/youtube.js | 24 +++++++++--------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index b5508349..20e997e8 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -12,12 +12,6 @@ H5P.VideoHtml5 = (function ($) { function Html5(sources, options, l10n) { var self = this; - /** - * xAPI Helper. - * @private - */ - var videoXAPI = new H5P.VideoXAPI(self); - /** * Displayed when the video is buffering * @private @@ -162,7 +156,7 @@ H5P.VideoHtml5 = (function ($) { } } - return videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage, video.videoHeight, video.duration); + return self.videoXAPI.getArgsXAPIInitialized(video.videoWidth, video.videoHeight, video.playbackRate, video.volume, ccEnabled, ccLanguage, video.videoHeight, video.duration); }; @@ -195,13 +189,13 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PLAYING) { if (self.seeking === true) { - extraArg = videoXAPI.getArgsXAPISeeked(self.seekedTo); + extraArg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); extraTrigger = 'seeked'; lastSend = 'seeked'; self.seeking = false; } else if (lastSend !== 'play') { - extraArg = videoXAPI.getArgsXAPIPlayed(video.currentTime); + extraArg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; } @@ -211,7 +205,7 @@ H5P.VideoHtml5 = (function ($) { // Put together extraArg for sending to xAPI statement. if (!video.seeking && self.seeking === false && video.currentTime !== video.duration) { extraTrigger = 'paused'; - extraArg = videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); + extraArg = self.videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; } } @@ -221,10 +215,10 @@ H5P.VideoHtml5 = (function ($) { var length = video.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = videoXAPI.getProgress(length, length); + var progress = self.videoXAPI.getProgress(length, length); if (progress >= 0.95) { extraTrigger = 'finished'; - extraArg = videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); + extraArg = self.videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; } } @@ -237,23 +231,23 @@ H5P.VideoHtml5 = (function ($) { return; // Just need to store current time for seeked event. break; case 'volumechange' : - arg = videoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); + arg = self.videoXAPI.getArgsXAPIVolumeChanged(video.currentTime, video.muted, video.volume); lastSend = 'volumechange'; break; case 'play': if (self.seeking === false && lastSend !== h5p) { - arg = videoXAPI.getArgsXAPIPlayed(video.currentTime); + arg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); lastSend = h5p; } else { - arg = videoXAPI.getArgsXAPISeeked(self.seekedTo); + arg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); lastSend = 'seeked'; self.seeking = false; h5p = 'seeked'; } break; case 'fullscreen': - arg = videoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); + arg = self.videoXAPI.getArgsXAPIFullScreen(video.currentTime, video.videoWidth, video.videoHeight); lastSend = h5p; break; case 'loaded': diff --git a/scripts/youtube.js b/scripts/youtube.js index 69930f5f..a27f6627 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -12,12 +12,6 @@ H5P.VideoYouTube = (function ($) { function YouTube(sources, options, l10n) { var self = this; - /** - * xAPI Helper. - * @private - */ - var videoXAPI = new H5P.VideoXAPI(self); - var player; var playbackRate = 1; var id = 'h5p-youtube-' + numInstances; @@ -127,17 +121,17 @@ H5P.VideoYouTube = (function ($) { if (state.data === 1) { // Get and send play call when not seeking. if (self.seeking === false) { - self.trigger('play', videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); + self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } else { - self.trigger('seeked', videoXAPI.getArgsXAPISeeked(self.seekedTo)); + self.trigger('seeked', self.videoXAPI.getArgsXAPISeeked(self.seekedTo)); self.seeking = false; } } else if (state.data === 2) { // This is a paused event. if (self.seeking === false) { - self.trigger('paused', videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); + self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } else if (state.data === 0) { @@ -145,9 +139,9 @@ H5P.VideoYouTube = (function ($) { var length = self.duration; if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop - var progress = videoXAPI.getProgress(length, length); + var progress = self.videoXAPI.getProgress(length, length); if (progress >= 0.95) { - var arg = videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); + var arg = self.videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } } @@ -241,7 +235,7 @@ H5P.VideoYouTube = (function ($) { ccLanguage = player.getOptions('cc', 'track').languageCode; } - return videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality(), self.getDuration()); + return self.videoXAPI.getArgsXAPIInitialized(width, height, self.getPlaybackRate(), self.getVolume(), ccEnabled, ccLanguage, self.getQuality(), self.getDuration()); }; @@ -421,7 +415,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), true, player.getVolume())); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), true, player.getVolume())); player.mute(); }; @@ -436,7 +430,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), false, player.getVolume())); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), false, player.getVolume())); player.unMute(); }; @@ -480,7 +474,7 @@ H5P.VideoYouTube = (function ($) { return; } - self.trigger('volumechange', videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), player.isMuted(), level)); + self.trigger('volumechange', self.videoXAPI.getArgsXAPIVolumeChanged(player.getCurrentTime(), player.isMuted(), level)); player.setVolume(level); }; From 9d612b7a4338207a0a4e08db35eeedccd93a79ec Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 10:02:06 -1000 Subject: [PATCH 132/137] Replay 1f18bdb due to incorrect merge conflict resolution in 94ff7b3 --- scripts/html5.js | 11 +++-------- scripts/youtube.js | 7 ++----- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 20e997e8..002892b9 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -188,13 +188,7 @@ H5P.VideoHtml5 = (function ($) { } if (arg === H5P.Video.PLAYING) { - if (self.seeking === true) { - extraArg = self.videoXAPI.getArgsXAPISeeked(self.seekedTo); - extraTrigger = 'seeked'; - lastSend = 'seeked'; - self.seeking = false; - } - else if (lastSend !== 'play') { + if (lastSend !== 'play') { extraArg = self.videoXAPI.getArgsXAPIPlayed(video.currentTime); extraTrigger = 'play'; lastSend = 'play'; @@ -203,7 +197,7 @@ H5P.VideoHtml5 = (function ($) { if (arg === H5P.Video.PAUSED) { // Put together extraArg for sending to xAPI statement. - if (!video.seeking && self.seeking === false && video.currentTime !== video.duration) { + if (!video.seeking && self.seeking === false && video.currentTime !== video.duration && self.previousState !== H5P.Video.BUFFERING) { extraTrigger = 'paused'; extraArg = self.videoXAPI.getArgsXAPIPaused(video.currentTime, video.duration); lastSend = 'paused'; @@ -300,6 +294,7 @@ H5P.VideoHtml5 = (function ($) { arg = self.getPlaybackRate(); break; } + self.previousState = arg; self.trigger(h5p, arg); // Make extra calls for events with needed values for xAPI statement. diff --git a/scripts/youtube.js b/scripts/youtube.js index a27f6627..287a1787 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -123,14 +123,10 @@ H5P.VideoYouTube = (function ($) { if (self.seeking === false) { self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } - else { - self.trigger('seeked', self.videoXAPI.getArgsXAPISeeked(self.seekedTo)); - self.seeking = false; - } } else if (state.data === 2) { // This is a paused event. - if (self.seeking === false) { + if (self.seeking === false && self.previousState !== 3) { self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } @@ -147,6 +143,7 @@ H5P.VideoYouTube = (function ($) { } } } + self.previousState = state.data; }, onPlaybackQualityChange: function (quality) { self.trigger('qualityChange', quality.data); From 3e6a58437ea41888ff3e2b424337f3e539ccf00d Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 10:05:10 -1000 Subject: [PATCH 133/137] Replay f977a64 due to incorrect merge conflict resolution in 94ff7b3 --- scripts/html5.js | 1 + scripts/youtube.js | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 002892b9..0609207d 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -1,5 +1,6 @@ /** @namespace H5P */ H5P.VideoHtml5 = (function ($) { + 'use strict'; /** * HTML5 video player for H5P. diff --git a/scripts/youtube.js b/scripts/youtube.js index 287a1787..b04e3429 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -1,5 +1,6 @@ /** @namespace H5P */ H5P.VideoYouTube = (function ($) { + 'use strict'; /** * YouTube video player for H5P. @@ -105,7 +106,7 @@ H5P.VideoYouTube = (function ($) { } }, onStateChange: function (state) { - if (state.data > -1 && state.data < 4) { + if (state.data >= H5P.Video.ENDED && state.data <= H5P.Video.BUFFERING) { // Fix for keeping playback rate in IE11 if (H5P.Video.IE11_PLAYBACK_RATE_FIX && state.data === H5P.Video.PLAYING && playbackRate !== 1) { @@ -118,19 +119,19 @@ H5P.VideoYouTube = (function ($) { self.trigger('stateChange', state.data); // Calls for xAPI events. - if (state.data === 1) { + if (state.data === H5P.Video.PLAYING) { // Get and send play call when not seeking. if (self.seeking === false) { self.trigger('play', self.videoXAPI.getArgsXAPIPlayed(player.getCurrentTime())); } } - else if (state.data === 2) { + else if (state.data === H5P.Video.PAUSED) { // This is a paused event. - if (self.seeking === false && self.previousState !== 3) { + if (self.seeking === false && self.previousState !== H5P.Video.BUFFERING) { self.trigger('paused', self.videoXAPI.getArgsXAPIPaused(player.getCurrentTime(), self.duration)); } } - else if (state.data === 0) { + else if (state.data === H5P.Video.ENDED) { // Send xapi trigger if video progress indicates finished. var length = self.duration; if (length > 0) { @@ -215,7 +216,7 @@ H5P.VideoYouTube = (function ($) { break; } - return (returnType.toLowerCase().trim()=='width') ? width : height; + return (returnType.toLowerCase().trim() === 'width') ? width : height; }; /** From 85b0b8685686fff85efb8200b6f1e36c19a43dad Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 10:07:13 -1000 Subject: [PATCH 134/137] Replay 8223c20 due to incorrect merge conflict resolution in 94ff7b3 --- scripts/html5.js | 2 +- scripts/youtube.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/html5.js b/scripts/html5.js index 0609207d..9bd25285 100644 --- a/scripts/html5.js +++ b/scripts/html5.js @@ -211,7 +211,7 @@ H5P.VideoHtml5 = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = self.videoXAPI.getProgress(length, length); - if (progress >= 0.95) { + if (progress >= self.finishedThreshold) { extraTrigger = 'finished'; extraArg = self.videoXAPI.getArgsXAPICompleted(video.currentTime, video.duration, progress); lastSend = 'finished'; diff --git a/scripts/youtube.js b/scripts/youtube.js index b04e3429..b51c7a16 100644 --- a/scripts/youtube.js +++ b/scripts/youtube.js @@ -137,7 +137,7 @@ H5P.VideoYouTube = (function ($) { if (length > 0) { // Length passed in as current time, because at end of video when this is fired currentTime reset to 0 if on loop var progress = self.videoXAPI.getProgress(length, length); - if (progress >= 0.95) { + if (progress >= self.finishedThreshold) { var arg = self.videoXAPI.getArgsXAPICompleted(player.getCurrentTime(), self.duration, progress); self.trigger('finished', arg); } From ef6d166c78d564b8412e38f20195a26d39063441 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 10:08:50 -1000 Subject: [PATCH 135/137] Add existence check to completion-threshold extension --- scripts/x-api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 0da20394..b7b9ae2f 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -122,7 +122,10 @@ H5P.VideoXAPI = (function ($) { if (typeof volume !== 'undefined') { extensions['https://w3id.org/xapi/video/extensions/volume'] = volume; } - extensions['https://w3id.org/xapi/video/extensions/completion-threshold'] = instance.finishedThreshold; + if (typeof instance.finishedThreshold !== 'undefined') { + extensions['https://w3id.org/xapi/video/extensions/completion-threshold'] = instance.finishedThreshold; + } + return self.getArgsXAPI({ verb: 'http://adlnet.gov/expapi/verbs/initialized', extensionsContext: extensions From 4f595f75bcd687ee528222a55429a3d85268c237 Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 10:09:25 -1000 Subject: [PATCH 136/137] Comments (update function doc header) --- scripts/x-api.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/x-api.js b/scripts/x-api.js index b7b9ae2f..6a7b4191 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -387,6 +387,8 @@ H5P.VideoXAPI = (function ($) { * Converts an array of played segments to the string representation defined * in the xAPI Video Profile spec. * @see https://liveaspankaj.gitbooks.io/xapi-video-profile/content/statement_data_model.html#2545-played-segments + * + * @private * @return {String} Played segments string, e.g., '0.000[.]12.000[,]14.000[.]21.000' */ var stringifyPlayedSegments = function () { From 26b8f940247678acc7898b61ef39de1003b4608e Mon Sep 17 00:00:00 2001 From: Paul Ryan Date: Fri, 25 May 2018 11:12:20 -1000 Subject: [PATCH 137/137] Revert 6dd68ac MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updating the currently playing segment start time when the video starts playing is required in certain scenarios. For example, If a user revisits a page with a youtube video that they had previously partially watched, the video will resume from where they left off. If we don’t set `playingSegmentStart` on the play event, the first playing segment recorded will start at 0 (the beginning of the video) rather than the timecode the video resumed from. --- scripts/x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/x-api.js b/scripts/x-api.js index 6a7b4191..7a012a79 100644 --- a/scripts/x-api.js +++ b/scripts/x-api.js @@ -142,7 +142,7 @@ H5P.VideoXAPI = (function ($) { */ self.getArgsXAPIPlayed = function (currentTime) { var resultExtTime = formatFloat(currentTime); - //playingSegmentStart = resultExtTime; + playingSegmentStart = resultExtTime; return self.getArgsXAPI({ verb: 'https://w3id.org/xapi/video/verbs/played',