Skip to content
6 changes: 6 additions & 0 deletions integrations/adobe-analytics/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.16.2 / 2020-07-13
===================
* Reads session playhead values from `window._segHBPlayheads` if it exists. This solves an issue where the playhead is only updated when 'Video Content Playing' (+ various others) events are tracked. To get around this, we allow video implementations to set the playhead value as often as possible without the need to trigger an event.
* Removes trackComplete from Video Content Completed events and only calls chapterComplete on these events. It also adds trackComplete to Video Playback Completed events. This is in line with Adobe's documentation and also allows for parity between iOS, a.js, and Android.
* Stringifies context data values which are booleans. The AA SDK tends to reject false boolean values when setting them on the window object. This does not break existing behavior since booleans are stringified when they're sent in the query string.

1.16.1 / 2020-05-15
===================
* Supports sending top level `event` as `prop`, `eVar`, `lVar`, `hVar`, or Context Data Variable.
Expand Down
44 changes: 35 additions & 9 deletions integrations/adobe-analytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ AdobeAnalytics.prototype.initialize = function() {
// In case this has been defined already
window.s_account = window.s_account || options.reportSuiteId;

// Initialize a window object that can be used to update the playhead value of a session
// WITHOUT sending several 'Video Content Playing' events. (see line 1242)
window._segHBPlayheads = {};

// Load the larger Heartbeat script only if the customer has it enabled in settings.
// This file is considerably bigger, so this check is necessary.
if (options.heartbeatTrackingServerUrl) {
Expand Down Expand Up @@ -620,6 +624,14 @@ function updateContextData(facade, options) {
return;
}

// If context data values are booleans then stringify them.
// Adobe's SDK seems to reject a false boolean value. Stringifying is
// acceptable since these values are appended as query strings anyway.
if (typeof value === 'boolean') {
addContextDatum(key, value.toString());
return;
}

addContextDatum(key, value);
}, contextProperties);
}
Expand Down Expand Up @@ -1236,7 +1248,17 @@ function initHeartbeat(track) {
mediaHeartbeatConfig.debugLogging = !!window._enableHeartbeatDebugLogging; // Optional beta flag for seeing debug output.

mediaHeartbeatDelegate.getCurrentPlaybackTime = function() {
return self.playhead || 0; // TODO: Bind to the Heartbeat events we have specced.
var playhead = self.playhead || 0;

// We allow implementions to set the playhead value of a video session on a shared
// window object. This allows us to relay the playhead to AA's heartbeat SDK several
// times a second, without relying on a 'Video Content Playing' event to update the position.
var sessions = window._segHBPlayheads || {};
if (sessions[props.session_id]) {
playhead = sessions[props.session_id];
}

return playhead;
};

mediaHeartbeatDelegate.getQoSObject = function() {
Expand Down Expand Up @@ -1317,9 +1339,7 @@ function heartbeatVideoStart(track) {
chapterObj,
chapterCustomMetadata
);
this.mediaHeartbeats[
props.session_id || 'default'
].chapterInProgress = true;
this.mediaHeartbeats[props.session_id || 'default'].chapterInProgress = true;
}
}

Expand All @@ -1332,8 +1352,6 @@ function heartbeatVideoComplete(track) {
videoAnalytics.MediaHeartbeat.Event.ChapterComplete
);
this.mediaHeartbeats[props.session_id || 'default'].chapterInProgress = false;

this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackComplete();
}

function heartbeatVideoPaused(track) {
Expand All @@ -1347,9 +1365,8 @@ function heartbeatSessionEnd(track) {
populateHeartbeat.call(this, track);

var props = track.properties();
this.mediaHeartbeats[
props.session_id || 'default'
].heartbeat.trackSessionEnd();
this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackComplete();
this.mediaHeartbeats[props.session_id || 'default'].heartbeat.trackSessionEnd();

// Remove the session from memory when it's all over.
delete this.mediaHeartbeats[props.session_id || 'default'];
Expand Down Expand Up @@ -1514,6 +1531,15 @@ function createCustomVideoMetadataContext(track, options) {
if (!key || value === undefined || value === null || value === '') {
return;
}

// If context data values are booleans then stringify them.
// Adobe's SDK seems to reject a false boolean value. Stringifying is
// acceptable since these values are appended as query strings anyway.
if (typeof value === 'boolean') {
contextData[key] = value.toString();
return;
}

contextData[key] = value;
}, extractedProperties);
return contextData;
Expand Down
2 changes: 1 addition & 1 deletion integrations/adobe-analytics/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@segment/analytics.js-integration-adobe-analytics",
"description": "The Adobe Analytics analytics.js integration.",
"version": "1.16.1",
"version": "1.16.2",
"keywords": [
"analytics.js",
"analytics.js-integration",
Expand Down
61 changes: 56 additions & 5 deletions integrations/adobe-analytics/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,30 @@ describe('Adobe Analytics', function() {
});
analytics.equal(window.s.events, 'prodView,event1,event38');
});

it('should stringify bool context data', function() {
adobeAnalytics.options.contextValues = {
'page.referrer': 'page.referrer',
'page.url': 'page.title',
'page.bickenBack': 'page.bickenBack'
};
analytics.track(
'Drank Some Milk',
{ foo: 'bar' },
{ page: { bickenBack: false } }
);
analytics.equal(
window.s.contextData['page.referrer'],
window.document.referrer
);
analytics.equal(
window.s.contextData['page.title'],
window.location.href
);
analytics.equal(window.s.contextData['page.bickenBack'], 'false');
analytics.equal(window.s.contextData.foo, 'bar');
analytics.called(window.s.tl);
});
});
});

Expand Down Expand Up @@ -1592,7 +1616,7 @@ describe('Adobe Analytics', function() {
);
});

it('should call trackComplete when a video completes', function() {
it('should set chapterInProgress when a video completes', function() {
analytics.track('Video Playback Started', {
session_id: sessionId,
channel: 'Black Mesa',
Expand All @@ -1606,7 +1630,7 @@ describe('Adobe Analytics', function() {

analytics.stub(
adobeAnalytics.mediaHeartbeats[sessionId].heartbeat,
'trackComplete'
'trackEvent'
);

analytics.track('Video Content Completed', {
Expand All @@ -1621,7 +1645,12 @@ describe('Adobe Analytics', function() {
});

analytics.called(
adobeAnalytics.mediaHeartbeats[sessionId].heartbeat.trackComplete
adobeAnalytics.mediaHeartbeats[sessionId].heartbeat.trackEvent,
window.ADB.va.MediaHeartbeat.Event.ChapterComplete
);
analytics.equal(
false,
adobeAnalytics.mediaHeartbeats[sessionId].chapterInProgress
);
});

Expand Down Expand Up @@ -1658,7 +1687,7 @@ describe('Adobe Analytics', function() {
);
});

it('should delete the instance when the session is over', function() {
it('should call final hb methods and delete the instance when the session is over', function() {
analytics.track('Video Playback Started', {
session_id: sessionId,
channel: 'Black Mesa',
Expand All @@ -1674,6 +1703,7 @@ describe('Adobe Analytics', function() {

// We need to save this reference for the upcoming check, since we delete the higher property after the next call.
var heartbeatRef = adobeAnalytics.mediaHeartbeats[sessionId].heartbeat;
analytics.stub(heartbeatRef, 'trackComplete');
analytics.stub(heartbeatRef, 'trackSessionEnd');

analytics.track('Video Playback Completed', {
Expand All @@ -1687,8 +1717,9 @@ describe('Adobe Analytics', function() {
livestream: false
});

analytics.assert(!adobeAnalytics.mediaHeartbeats[sessionId]);
analytics.called(heartbeatRef.trackComplete);
analytics.called(heartbeatRef.trackSessionEnd);
analytics.assert(!adobeAnalytics.mediaHeartbeats[sessionId]);
});

it('should start an Ad Break and Ad Tracking when an ad starts', function() {
Expand Down Expand Up @@ -1833,6 +1864,26 @@ describe('Adobe Analytics', function() {
'trackPause'
);
});

it('should return the playhead value from the window object', function() {
analytics.track('Video Playback Started', {
session_id: sessionId,
channel: 'Black Mesa',
video_player: 'Transit Announcement System',
playhead: 5,
asset_id: 'Gordon Freeman',
title: 'Half-Life',
total_length: 1260,
livestream: false
});

window._segHBPlayheads[sessionId] = 5.111;

var actual = adobeAnalytics.mediaHeartbeats[
sessionId
].delegate.getCurrentPlaybackTime();
analytics.assert(actual, 5.111);
});
});
});
});
Expand Down