Skip to content

Commit

Permalink
fix(analytics): fix watch duration by filtering seek events
Browse files Browse the repository at this point in the history
- clear timeout
- renaming
  • Loading branch information
AntonLantukh committed Apr 3, 2023
1 parent 3861ee6 commit 22cfa26
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 40 deletions.
4 changes: 3 additions & 1 deletion docs/features/video-analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ The event trigger implementation for the ott web app can be found at [jwpltx.js]

Note that `navigator.sendBeacon()` is used to call the endpoints. The browser will not do CORS checks on this operation. It furthermore minimizes performance impact as the browser doesn't wait for the response of the server.

It also lets us to use `beforeunload` event in order to send remaining data to analytics server when closing the window during watching in progress.

## Analytics ID

A special data parameter is the Analytics ID (`aid`). It determines to which JW Player account & property the events belong. Each property has its unique analytics ID and is provided by a JW PLayer Solution Engineer or Account manager.
Expand Down Expand Up @@ -181,7 +183,7 @@ Any decimal values are truncated to an integer.

### Live Streams

For live streams, this quantile distribution is ignored and time ticks are sent at pre-set intervals. We recommend 20 seconds. The following values are sent:
For live streams, this quantile distribution is ignored and time ticks are sent at pre-set intervals of 20 seconds. The following values are sent:

- pw (Quantile watched): -1
- q (Quantiles): 0
Expand Down
84 changes: 45 additions & 39 deletions public/jwpltx.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,40 @@ window.jwpltx = window.jwpltx || {};
let timeWatched = 0;
// Last progress of the video stored
let lastVp = 0;
// Last quantile reached
let lastQ;
// Next quantile to send t event
let nextQuantile;
// Seeking state, gets reset after 1 sec of the latest seeked event
let isSeeking = null;
// Timeout for seeking state
let isSeeking = false;
// Interval for Live Streams seeking state
let liveInterval = null;
// Timeout for seeking state
let seekingTimeout = null;

// How many quantiles have we passed (whether we need to send new t event or not)
function getLastVODQuantile(progress, duration, numberOfQuantiles) {
return Math.floor(progress / (duration / numberOfQuantiles));
// Here we convert seconds watched to quantiles, the units accepted by the analytics service (res is 0 - 128)
function getCurrentProgressQuantile(progress, duration) {
return Math.floor(MAX_DURATION_IN_QUANTILES * (progress / duration));
}

// Here we convert seconds watched to the unites accepted by analytics service (res is 0 - 128)
function getProgressWatched(progress, duration) {
return Math.floor(MAX_DURATION_IN_QUANTILES * (progress / duration));
// We convert `q` metric to quantiles (0 - 128) to define next breakpoint for `t` event
function getNextTriggerQuantile(progress, duration) {
return (Math.ceil((progress / duration) * uri.q) * MAX_DURATION_IN_QUANTILES) / uri.q;
}

// Here we convert seconds watched to quantiles, the units accepted by the analytics service (res is 0 - 128)
function sendRemainingData() {
if (uri.pw === -1) {
clearLiveInterval();
} else {
const pw = getCurrentProgressQuantile(lastVp, uri.vd);
uri.pw = pw;
}

if (timeWatched) {
uri.ti = Math.floor(timeWatched);
timeWatched = 0;

sendData('t');
}
}

// We set interval for sending t events for live streams where we can't get progress info
Expand Down Expand Up @@ -71,20 +90,21 @@ window.jwpltx = window.jwpltx || {};
// Process seek event
o.seek = function (offset, duration) {
isSeeking = true;
clearTimeout(seekingTimeout);
// Clear interval in case of a live stream not to update time watched while seeking
if (uri.pw === -1) {
clearLiveInterval();
} else {
// We need to rewrite progress of the video when seeking to have a valid ti param
lastVp = offset;
lastQ = getLastVODQuantile(offset, duration, uri.q);
nextQuantile = getNextTriggerQuantile(offset, duration);
}
};

// Process seeked event
o.seeked = function () {
// There is currently a 1 sec debounce surrounding this event in order to logically group multiple `seeked` events
window.setTimeout(() => {
seekingTimeout = setTimeout(() => {
isSeeking = false;
// Set new timeout when seeked event reached for live events
if (uri.pw === -1 && !liveInterval) {
Expand All @@ -94,21 +114,16 @@ window.jwpltx = window.jwpltx || {};
}, 1000);
};

// When player is disconnected from the page -> we send remove event and update analytics with recent playback changes
// When player is disconnected from the page -> send the rest of the data and cancel possible intervals
o.remove = function () {
if (uri.pw === -1) {
clearLiveInterval();
} else {
const pw = getProgressWatched(lastVp, uri.vd);
uri.pw = pw;
}

uri.ti = Math.floor(timeWatched);
timeWatched = 0;

sendData('t');
sendRemainingData();
};

// Send the rest of the data and cancel possible intervals in case a web page is closed while watching
window.addEventListener('beforeunload', () => {
sendRemainingData();
});

// Process a time tick event
o.time = function (vp, vd) {
if (isSeeking) {
Expand Down Expand Up @@ -155,27 +170,27 @@ window.jwpltx = window.jwpltx || {};
uri.pw = 0;

// Initialize latest quantile to compare further quantiles with
lastQ = getLastVODQuantile(vp, vd, uri.q);
nextQuantile = getNextTriggerQuantile(vp, vd);
// Initial values to compare watched progress
lastVp = vp;

sendData('s');
// monitor ticks for entering new quantile
} else {
const pw = getProgressWatched(vp, vd);
const passedQ = getLastVODQuantile(vp, vd, uri.q);
const pw = getCurrentProgressQuantile(vp, vd);
const quantile = getNextTriggerQuantile(vp, vd);

// Total time watched since last t event.
timeWatched = timeWatched + (vp - lastVp);
lastVp = vp;

if (passedQ > lastQ) {
uri.ti = Math.floor(timeWatched);
if (pw >= nextQuantile) {
uri.ti = Math.round(timeWatched);
uri.pw = pw;

sendData('t');

lastQ = passedQ;
nextQuantile = quantile;
timeWatched = 0;
}
}
Expand Down Expand Up @@ -233,13 +248,4 @@ window.jwpltx = window.jwpltx || {};
console.log(url + str);
}
}

// Send the rest of the data and cancel possible intervals in case a web page is closed while watching
window.addEventListener('beforeunload', () => {
clearLiveInterval();
if (timeWatched) {
uri.ti = Math.floor(timeWatched);
sendData('t');
}
});
})(window.jwpltx);

0 comments on commit 22cfa26

Please sign in to comment.