Skip to content

Commit 8af674d

Browse files
brennanBriebriemcnally
authored
[DEST-1704][Nielsen DCR] Map events only if load_type property is dynamic. (#421)
* [Nielsen DCR] Map events only if load_type property is dynamic. * Add support for new setting and update offset logic * Add unit test * Update HISTORY.md and package.json Co-authored-by: Brie <brienne.mcnally@segment.com> Co-authored-by: Brie McNally <31782219+briemcnally@users.noreply.github.com>
1 parent 254bd7e commit 8af674d

File tree

4 files changed

+133
-38
lines changed

4 files changed

+133
-38
lines changed

integrations/nielsen-dcr/HISTORY.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
2.0.1 / 2020-05-15
2+
==================
3+
4+
* Adds new setting `sendCurrentTimeLivestream` that when enabled will default to sending current time in seconds to Nielsen for livestream videos. If a position is passed and this setting is disabled we will calculate an offset of `currentTime` + `position` from properties object.
5+
6+
2.0.0 / 2020-02-06
7+
==================
8+
* Events are mapped to Nielsen DCR only when Segment `load_type` property is set to 'dynamic'.
9+
10+
1.5.0 / 2020-01-27
11+
==================
12+
13+
* Supports tracking videos in SPA apps.
14+
15+
1.4.0 / 2020-01-17
16+
==================
17+
18+
* Change approach to persisting `content_asset_id` to better keep track of session-level info.
19+
20+
1.3.5 / 2020-01-06
21+
==================
22+
23+
* Send unix timestamp in seconds (rather than milliseconds) when livestreams end.
24+
25+
1.3.4 / 2019-10-23
26+
==================
27+
28+
* Default content length to `0` if not explicitly defined.
29+
130
1.3.3 / 2019-10-11
231
==================
332

integrations/nielsen-dcr/lib/index.js

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var NielsenDCR = (module.exports = integration('Nielsen DCR')
2222
.option('adAssetIdPropertyName', '')
2323
.option('subbrandPropertyName', '')
2424
.option('clientIdPropertyName', '')
25+
.option('sendCurrentTimeLivestream', false)
2526
.option('contentLengthPropertyName', 'total_length')
2627
.option('optout', false)
2728
.tag(
@@ -74,6 +75,7 @@ NielsenDCR.prototype.initialize = function() {
7475
// for the currently viewing ad or content so that we can handle video switches in the same session
7576
this.currentAssetId = null;
7677
this.currentPosition = 0;
78+
this.isDCRStream = false;
7779
this.heartbeatId = null; // reference to setTimeout
7880
this.load(protocol, this.ready);
7981
};
@@ -135,12 +137,11 @@ NielsenDCR.prototype.heartbeat = function(assetId, position, livestream) {
135137

136138
if (livestream) {
137139
// for livestream events, we calculate a unix timestamp based on the current time an offset value, which should be passed in properties.position
138-
this.currentPosition = getOffsetTime(position);
140+
this.currentPosition = getOffsetTime(position, self.options);
139141
} else if (position >= 0) {
140142
// this.currentPosition defaults to 0 upon initialization
141143
this.currentPosition = position;
142144
}
143-
144145
this.heartbeatId = setInterval(function() {
145146
self._client.ggPM('setPlayheadPosition', self.currentPosition);
146147
self.currentPosition++;
@@ -157,6 +158,14 @@ NielsenDCR.prototype.getContentMetadata = function(track, type) {
157158
var propertiesPath = 'properties.';
158159
if (type && type === 'contentEvent') propertiesPath = 'properties.content.';
159160

161+
var integrationOpts = track.options(this.name);
162+
var adLoadType = formatLoadType(integrationOpts, track, propertiesPath);
163+
// adLoadType will be falsy if the `load_type` is NOT 'dynamic' (i.e. it IS 'linear' instead)
164+
// we bubble up false to the calling content/playback method so we can early return, as
165+
// DCR should not be tracking ratings for content of `load_type` 'linear'
166+
if (!adLoadType) return;
167+
this.isDCRStream = true;
168+
160169
var customAssetId;
161170
if (this.options.contentAssetIdPropertyName) {
162171
customAssetId = track.proxy(
@@ -167,7 +176,6 @@ NielsenDCR.prototype.getContentMetadata = function(track, type) {
167176
track.proxy(propertiesPath + 'content_asset_id') ||
168177
track.proxy(propertiesPath + 'asset_id');
169178
var assetId = customAssetId || assetIdProp;
170-
var integrationOpts = track.options(this.name);
171179
var contentMetadata = {
172180
type: 'content',
173181
assetid: assetId,
@@ -176,8 +184,7 @@ NielsenDCR.prototype.getContentMetadata = function(track, type) {
176184
isfullepisode: track.proxy(propertiesPath + 'full_episode') ? 'y' : 'n',
177185
mediaURL: track.proxy('context.page.url'),
178186
airdate: formatAirdate(track.proxy(propertiesPath + 'airdate')),
179-
// `adLoadType` may be set in int opts, falling back to `load_type` property per our video spec
180-
adloadtype: formatLoadType(integrationOpts, track, propertiesPath),
187+
adloadtype: adLoadType,
181188
// below metadata fields must all be set in event's integrations opts object
182189
crossId1: find(integrationOpts, 'crossId1'),
183190
crossId2: find(integrationOpts, 'crossId2'),
@@ -219,8 +226,11 @@ NielsenDCR.prototype.getContentMetadata = function(track, type) {
219226
*/
220227

221228
NielsenDCR.prototype.getAdMetadata = function(track) {
222-
var type = track.proxy('properties.type');
229+
var integrationOpts = track.options(this.name);
230+
var adLoadType = formatLoadType(integrationOpts, track, 'properties');
231+
if (!adLoadType) return;
223232

233+
var type = track.proxy('properties.type');
224234
if (typeof type === 'string') type = type.replace('-', '');
225235

226236
var customAssetId;
@@ -250,6 +260,7 @@ NielsenDCR.prototype.videoContentStarted = function(track) {
250260
clearInterval(this.heartbeatId);
251261

252262
var contentMetadata = this.getContentMetadata(track);
263+
if (!contentMetadata) return;
253264
var position = track.proxy('properties.position');
254265
var livestream = track.proxy('properties.livestream');
255266

@@ -276,6 +287,7 @@ NielsenDCR.prototype.videoContentStarted = function(track) {
276287

277288
NielsenDCR.prototype.videoContentPlaying = function(track) {
278289
clearInterval(this.heartbeatId);
290+
if (!this.isDCRStream) return;
279291

280292
var assetId = this.options.contentAssetIdPropertyName
281293
? track.proxy('properties.' + this.options.contentAssetIdPropertyName)
@@ -293,12 +305,15 @@ NielsenDCR.prototype.videoContentPlaying = function(track) {
293305
*/
294306

295307
NielsenDCR.prototype.videoContentCompleted = function(track) {
308+
var self = this;
309+
296310
clearInterval(this.heartbeatId);
311+
if (!this.isDCRStream) return;
297312

298313
var position = track.proxy('properties.position');
299314
var livestream = track.proxy('properties.livestream');
300315

301-
if (livestream) position = getOffsetTime(position);
316+
if (livestream) position = getOffsetTime(position, self.options);
302317

303318
this._client.ggPM('setPlayheadPosition', position);
304319
this._client.ggPM('stop', position);
@@ -313,21 +328,24 @@ NielsenDCR.prototype.videoContentCompleted = function(track) {
313328
NielsenDCR.prototype.videoAdStarted = function(track) {
314329
clearInterval(this.heartbeatId);
315330

316-
var contentMetadata = {};
317-
var adAssetId = this.options.adAssetIdPropertyName
318-
? track.proxy('properties.' + this.options.adAssetIdPropertyName)
319-
: track.proxy('properties.asset_id');
320-
var position = track.proxy('properties.position');
321331
var type = track.proxy('properties.type');
322-
323332
if (typeof type === 'string') type = type.replace('-', '');
324333
// edge case: if pre-roll, you must load the content metadata first
325334
// because nielsen ties ad attribution to the content not playback session
335+
var contentMetadata = {};
326336
if (type === 'preroll') {
327337
contentMetadata = this.getContentMetadata(track, 'contentEvent');
338+
if (!contentMetadata) return;
328339
this._client.ggPM('loadMetadata', contentMetadata);
329340
}
330341

342+
if (!this.isDCRStream) return;
343+
344+
var adAssetId = this.options.adAssetIdPropertyName
345+
? track.proxy('properties.' + this.options.adAssetIdPropertyName)
346+
: track.proxy('properties.asset_id');
347+
var position = track.proxy('properties.position');
348+
331349
var adMetadata = {
332350
assetid: adAssetId,
333351
type: type || 'ad'
@@ -348,6 +366,7 @@ NielsenDCR.prototype.videoAdStarted = function(track) {
348366

349367
NielsenDCR.prototype.videoAdPlaying = function(track) {
350368
clearInterval(this.heartbeatId);
369+
if (!this.isDCRStream) return;
351370

352371
var position = track.proxy('properties.position');
353372
// first argument below is "null" b/c `heartbeat` doesn't need to keep track of ad asset ids
@@ -363,6 +382,7 @@ NielsenDCR.prototype.videoAdPlaying = function(track) {
363382

364383
NielsenDCR.prototype.videoAdCompleted = function(track) {
365384
clearInterval(this.heartbeatId);
385+
if (!this.isDCRStream) return;
366386

367387
var position = track.proxy('properties.position');
368388

@@ -381,12 +401,14 @@ NielsenDCR.prototype.videoAdCompleted = function(track) {
381401
NielsenDCR.prototype.videoPlaybackPaused = NielsenDCR.prototype.videoPlaybackSeekStarted = NielsenDCR.prototype.videoPlaybackBufferStarted = function(
382402
track
383403
) {
404+
var self = this;
384405
clearInterval(this.heartbeatId);
406+
if (!this.isDCRStream) return;
385407

386408
var position = track.proxy('properties.position');
387409
var livestream = track.proxy('properties.livestream');
388410

389-
if (livestream) position = getOffsetTime(position);
411+
if (livestream) position = getOffsetTime(position, self.options);
390412

391413
this._client.ggPM('stop', position);
392414
};
@@ -403,6 +425,7 @@ NielsenDCR.prototype.videoPlaybackResumed = NielsenDCR.prototype.videoPlaybackSe
403425
track
404426
) {
405427
clearInterval(this.heartbeatId);
428+
if (!this.isDCRStream) return;
406429

407430
var contentAssetId = this.options.contentAssetIdPropertyName
408431
? track.proxy('properties.' + this.options.contentAssetIdPropertyName)
@@ -422,12 +445,13 @@ NielsenDCR.prototype.videoPlaybackResumed = NielsenDCR.prototype.videoPlaybackSe
422445
this._client.ggPM('end', this.currentPosition);
423446

424447
if (type === 'ad') {
425-
this._client.ggPM('loadMetadata', this.getAdMetadata(track));
448+
var adMetadata = this.getAdMetadata(track);
449+
if (!adMetadata) return;
450+
this._client.ggPM('loadMetadata', adMetadata);
426451
} else if (type === 'content') {
427-
this._client.ggPM(
428-
'loadMetadata',
429-
this.getContentMetadata(track, 'contentEvent')
430-
);
452+
var contentMetadata = this.getContentMetadata(track, 'contentEvent');
453+
if (!contentMetadata) return;
454+
this._client.ggPM('loadMetadata', contentMetadata);
431455
}
432456
}
433457

@@ -438,25 +462,31 @@ NielsenDCR.prototype.videoPlaybackResumed = NielsenDCR.prototype.videoPlaybackSe
438462
* Video Playback Completed
439463
* Video Playback Interrupted
440464
*
465+
*
441466
* @api public
442467
*/
443468

444469
NielsenDCR.prototype.videoPlaybackCompleted = NielsenDCR.prototype.videoPlaybackInterrupted = function(
445470
track
446471
) {
472+
var self = this;
447473
clearInterval(this.heartbeatId);
474+
if (!this.isDCRStream) return;
448475

449476
var position = track.proxy('properties.position');
450477
var livestream = track.proxy('properties.livestream');
451478

452-
if (livestream) position = getOffsetTime(position);
479+
if (livestream) position = getOffsetTime(position, self.options);
453480

454481
this._client.ggPM('setPlayheadPosition', position);
455482
this._client.ggPM('end', position);
456483

457-
// reset state
484+
// reset state because "Video Playback Completed/Interrupted" are "non-recoverable events"
485+
// e.g. they should always be followed by the start of a new video session with either
486+
// "Video Content Started" or "Video Ad Started" events
458487
this.currentPosition = 0;
459488
this.currentAssetId = null;
489+
this.isDCRStream = false;
460490
this.heartbeatId = null;
461491
};
462492

@@ -488,10 +518,10 @@ function formatLoadType(integrationOpts, track, propertiesPath) {
488518
var loadType =
489519
find(integrationOpts, 'ad_load_type') ||
490520
track.proxy(propertiesPath + 'load_type') ||
491-
track.proxy('properties.load_type');
492-
// linear or dynamic
493-
// linear means original ads that were broadcasted with tv airing. much less common use case
494-
loadType = loadType === 'dynamic' ? '2' : '1';
521+
track.proxy(propertiesPath + 'loadType');
522+
// DCR is meant to track videos with ad `load_type` dynamic only
523+
// otherwise, we return `false` so we can easily early return in the calling method
524+
loadType = loadType === 'dynamic' ? '2' : false;
495525
return loadType;
496526
}
497527

@@ -501,14 +531,15 @@ function formatLoadType(integrationOpts, track, propertiesPath) {
501531
* handles offsets for livestreams, if applicable.
502532
*
503533
* @param {*} position Should be negative int representing livestream offset
534+
* @param {*} options Integration settings
504535
* @returns {Number} Unix timestamp in seconds
505536
*
506537
* @api private
507538
*/
508539

509-
function getOffsetTime(position) {
540+
function getOffsetTime(position, options) {
510541
var date = Math.floor(Date.now() / 1000);
511-
if (!position) return date;
542+
if (!position || options.sendCurrentTimeLivestream) return date;
512543

513544
try {
514545
if (typeof position !== 'number') {

integrations/nielsen-dcr/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@segment/analytics.js-integration-nielsen-dcr",
33
"description": "The Nielsen DCR analytics.js integration.",
4-
"version": "1.5.0",
4+
"version": "2.0.1",
55
"keywords": [
66
"analytics.js",
77
"analytics.js-integration",

0 commit comments

Comments
 (0)