edX Log Catching: Video - dan7davis/Lambda GitHub Wiki
Catching edX Video Events
This page outlines our method of logging edX video interaction data in real time.
Approach
By default, edX logs all learner activity in the entire platform and sends it to their own database (where TU Delft receives their weekly data dumps from). The logging approach outlined here has three steps:
- Listen for all XHR (XML HTTP Requests) from the current page
- Read and parse that request
- Send the original request along to its intended destination
1. Listening for XHR
The following function listens for XHR and sends along the original request (Steps 1 and 3 above):
(function() {
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data) {
origSend.call(this,data);
};
})();
2. Reading and Parsing the XHR
In the above function, we don't do anything with the XHR which we listened for and heard. But we want to know what was inside of that request, so we need to parse it. To do so, fe first create a function to parse XHRs with the format used by edX:
function splitParamString(input) {
if(input == null){
return {};
}
var params = input.split("&");
var paramObject = {};
params.forEach(function(element) {
var keyValue = element.split("=");
paramObject[keyValue[0]]=keyValue[1];
});
return paramObject;
}
Now that we have the parsing function, we can properly read the edX XHRs. Before the origSend.call(this,data);
line in the Listener function, we add our parsing function to parse the data within the XHR.
let parsedContent = splitParamString(data);
The value of parsedContent
will look something like this:
{
"event_type": "play_video",
"event": "%7B%22id%22%3A%228e2b63481ff14f0e8140c509ac74b1f4%22%2C%22code%22%3A%223_yD_cEKoCk%22%2C%22currentTime%22%3A11.667956020980835%7D",
"page": "https%3A%2F%2Fpreview.edge.edx.org%2Fcourses%2Fcourse-v1%3ADelftX%2BDD.002x%2B2017%2Fcourseware%2F4c59ffa0c5824722877cdf837f87469d%2Fc251bdf981d64978b4afd8ce952f7b8a%2F7%3Factivate_block_id%3Dblock-v1%253ADelftX%252BDD.002x%252B2017%252Btype%2540vertical%252Bblock%25405297a0c4bf6548adb07e931f264f8506",
}
Keep in mind that not all XHR data will be related to video. So in order to isolate certain events, you can specify then with the following if
statement to run after the data has been parsed:
if (vidlog.event_type === "play_video") {
// Do stuff here
}
3. Send XHR to Original Destination
To ensure that the XHR reaches its original destination safe and sound, the
origSend.call(this,data);
line you saw in Step 1 does the trick. Do not forget this line!!
Example
Important: edX Studio Issue
It is important to note that this method causes problems in edX studio, the backend development environment for creating and editing edX courses.
To mitigate this, we check to see if the current page is in studio or not and wrap the entire function in an if
statement.
The Code
The following code tracks the total amount of time a learner watches video on a given page.
We first define the splitParamString
function like in Step 2 above, then define the XHR listen and send function (with the edX studio if
statement included), and finally use an addUp
function which takes the arrays comprised of all pause and play events and finds the total duration by subtracting the time of all play events from the time of all pause events.
function splitParamString(input) {
if(input == null){
return {};
}
var params = input.split("&");
var paramObject = {};
params.forEach(function(element) {
var keyValue = element.split("=");
paramObject[keyValue[0]]=keyValue[1];
});
return paramObject;
}
//Goal: log all data that is sent in xhr post requests to console before they are sent
//Code snippet inspired by: http://stackoverflow.com/questions/25335648/how-to-intercept-all-ajax-requests-made-by-different-js-libraries
var vidlog;
var plays = [];
var pauses = [];
var durVid;
var vidSum = [];
var url = document.URL;
// This will break studio if it runs inside of it, so this IF statement make it so the XHR intercept only fires if not in studio
if ( url.includes('studio') === false) {
(function() {
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data) {
//console.log("[CAUGHT XHR DATA] :"+" "+data);
vidlog = splitParamString(data);
vidlog.time = new Date();
if (vidlog.event_type === "play_video") {
plays.push(vidlog.time);
addUp();
} else if (vidlog.event_type === "pause_video") {
pauses.push(vidlog.time);
addUp();
}
origSend.call(this,data);
};
})();
} else {
console.log("You're in studio.")
};
// Takes the Pauses and Plays arrays and finds total duration between play and pause events
function addUp() {
for (i=0; i<= pauses.length-1; i++) {
vidSum[i] = pauses[i] - plays[i];
};
durVid = vidSum.reduce(add, 0) / 1000;
if (durVid >= 30 ) { // Under the assumption that "completing" a video is to watch 30 seconds of it
// datas.vidID = vidlog.event.split("%")[5];
// Stores the video ID of this video now that is has been "completed"
}
};
// Returns the Sum of an Array
function add(a, b) {
return a + b;
};