From 8ff3ca1e8f87fa27b23a7180107a0a7d9c887a2f Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 16:58:55 +0200 Subject: [PATCH 01/55] Add tested countries --- nielsen/src/nielsen/Types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index f2ffe88a..012d6299 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -23,3 +23,11 @@ export type AdMetadata = { type: AdType; assetid: any; // TODO string? or can be anything? } & { [key: string]: string }; + +/* + * Countries for which (1) Nielsen provides DCR Browser SDKs and (2) the corresponding SDK was tested with this integration. + */ +export enum NielsenCountry { + US = 'US', + CZ = 'CZ' +} From 7fbdeb71d253d953b7bc0bc661bdf7682334b460 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 16:59:19 +0200 Subject: [PATCH 02/55] Load separate static queue snippet for different country configurations --- nielsen/src/nielsen/NOLBUNDLE.ts | 130 +++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 34 deletions(-) diff --git a/nielsen/src/nielsen/NOLBUNDLE.ts b/nielsen/src/nielsen/NOLBUNDLE.ts index 87d67c1a..38c02d91 100644 --- a/nielsen/src/nielsen/NOLBUNDLE.ts +++ b/nielsen/src/nielsen/NOLBUNDLE.ts @@ -1,40 +1,102 @@ /* eslint-disable */ -import { NielsenOptions } from './Types'; +import { NielsenCountry, NielsenOptions } from './Types'; -export function loadNielsenLibrary(appId: string, instanceName: string, options?: NielsenOptions) { - // https://engineeringportal.nielsen.com/docs/DTVR_Browser_SDK - // Add Static Queue Snippet to initialize a Nielsen SDK Instance. - // @ts-ignore - !(function (t: Window, n: any) { - t[n] = t[n] || { - nlsQ: function (e: any, o: any, c?: any, r?: any, s?: any, i?: any) { - // @ts-ignore - return ( - (s = t.document), - (r = s.createElement('script')), - (r.async = 1), - (r.src = - ('http:' === t.location.protocol ? 'http:' : 'https:') + - '//cdn-gl.imrworldwide.com/conf/' + - e + - '.js#name=' + - o + - '&ns=' + - n), - (i = s.getElementsByTagName('script')[0]), - i.parentNode.insertBefore(r, i), - (t[n][o] = t[n][o] || { - g: c || {}, - ggPM: function (e: any, c: any, r?: any, s?: any, i?: any) { - // @ts-ignore - (t[n][o].q = t[n][o].q || []).push([e, c, r, s, i]); - } - }), - t[n][o] - ); +export function loadNielsenLibrary( + appId: string, + instanceName: string, + options?: NielsenOptions, + country?: NielsenCountry +) { + if (country == NielsenCountry.CZ) { + // @ts-ignore + !(function (e: any, n: any) { + function t(e: any) { + return 'object' == typeof e ? JSON.parse(JSON.stringify(e)) : e; } - }; - })(window, 'NOLBUNDLE'); + e[n] = e[n] || { + nlsQ: function (o: any, r: any, c: any) { + //@ts-ignore + var s = e.document, + a = s.createElement('script'); + (a.async = 1), + (a.src = + ('http:' === e.location.protocol ? 'http:' : 'https:') + + '//cdn-gl.imrworldwide.com/conf/' + + o + + '.js#name=' + + r + + '&ns=' + + n); + var i = s.getElementsByTagName('script')[0]; + return ( + i.parentNode.insertBefore(a, i), + (e[n][r] = e[n][r] || { + g: c || {}, + ggPM: function (o: any, c: any, s: any, a: any, i: any) { + // @ts-ignore + e[n][r].q = e[n][r].q || []; + try { + var l = t([o, c, s, a, i]); + e[n][r].q.push(l); + } catch (e) { + console && + console.log && + console.log('Error: Cannot register event in Nielsen SDK queue.'); + } + }, + trackEvent: function (o: any) { + // @ts-ignore + e[n][r].te = e[n][r].te || []; + try { + var c = t(o); + e[n][r].te.push(c); + } catch (e) { + console && + console.log && + console.log('Error: Cannot register event in Nielsen SDK queue.'); + } + } + }), + e[n][r] + ); + } + }; + })(window, 'NOLBUNDLE'); + } else { + // https://engineeringportal.nielsen.com/docs/DTVR_Browser_SDK + // Add Static Queue Snippet to initialize a Nielsen SDK Instance. + // @ts-ignore + !(function (t: Window, n: any) { + t[n] = t[n] || { + nlsQ: function (e: any, o: any, c?: any, r?: any, s?: any, i?: any) { + // @ts-ignore + return ( + (s = t.document), + (r = s.createElement('script')), + (r.async = 1), + (r.src = + ('http:' === t.location.protocol ? 'http:' : 'https:') + + '//cdn-gl.imrworldwide.com/conf/' + + e + + '.js#name=' + + o + + '&ns=' + + n), + (i = s.getElementsByTagName('script')[0]), + i.parentNode.insertBefore(r, i), + (t[n][o] = t[n][o] || { + g: c || {}, + ggPM: function (e: any, c: any, r?: any, s?: any, i?: any) { + // @ts-ignore + (t[n][o].q = t[n][o].q || []).push([e, c, r, s, i]); + } + }), + t[n][o] + ); + } + }; + })(window, 'NOLBUNDLE'); + } if (options) { return (window as any).NOLBUNDLE.nlsQ(appId, instanceName, options); From b9208da0d9a61cd93a29ad4d85473de484846884 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 16:59:44 +0200 Subject: [PATCH 03/55] appId placeholder string --- nielsen/test/pages/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index 910e458c..039cc5f4 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -21,7 +21,7 @@ // // Add your connector setup logic here // - const appId = "P77E3B909-D4B5-4E5C-9B5F-77B0E8FE27F5"; + const appId = "XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX"; const instanceName = "instanceName"; const options = { // containerId: 'THEOplayer', From 8541e1b1e7c4977823c9c4f768cb4d812761e3c3 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 17:01:30 +0200 Subject: [PATCH 04/55] Reference to CZ docs --- nielsen/src/nielsen/NOLBUNDLE.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nielsen/src/nielsen/NOLBUNDLE.ts b/nielsen/src/nielsen/NOLBUNDLE.ts index 38c02d91..83a2fa5c 100644 --- a/nielsen/src/nielsen/NOLBUNDLE.ts +++ b/nielsen/src/nielsen/NOLBUNDLE.ts @@ -8,6 +8,8 @@ export function loadNielsenLibrary( country?: NielsenCountry ) { if (country == NielsenCountry.CZ) { + // https://engineeringportal.nielsen.com/wiki/DCR_Czech_Video_Browser_SDK + // Step 1: Configure SDK // @ts-ignore !(function (e: any, n: any) { function t(e: any) { From 9516b276b44ffc7186012f68933b69bc22c1e1c8 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 17:10:56 +0200 Subject: [PATCH 05/55] Rename DTVR Content Metadata --- nielsen/src/integration/NielsenHandler.ts | 4 ++-- nielsen/src/nielsen/Types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 335b432c..29b42df1 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -7,7 +7,7 @@ import type { VolumeChangeEvent } from 'theoplayer'; import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; -import { AdMetadata, ContentMetadata, NielsenOptions } from '../nielsen/Types'; +import { AdMetadata, DTVRContentMetadata, NielsenOptions } from '../nielsen/Types'; import { getAdType } from '../utils/Util'; const EMSG_PRIV_SUFFIX = 'PRIV{'; @@ -93,7 +93,7 @@ export class NielsenHandler { }; private onLoadMetadata = () => { - const data: ContentMetadata = { + const data: DTVRContentMetadata = { type: 'content', adModel: '1' // Always '1' for DTVR }; diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 012d6299..0d021377 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -14,7 +14,7 @@ export type NielsenOptions = { /** * adModel: 1) - Linear – matches TV ad load * 2) Dynamic – Dynamic Ad Insertion (DAI) */ -export type ContentMetadata = { +export type DTVRContentMetadata = { type: 'content'; adModel: '1' | '2'; } & { [key: string]: string }; From 9352600d9d94c7afdd797a33a03af53d4ef79985 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 18:47:47 +0200 Subject: [PATCH 06/55] Add example content metadata object for CZ DCR --- nielsen/test/pages/main.html | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index 039cc5f4..e63b7dbb 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -23,6 +23,22 @@ // const appId = "XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX"; const instanceName = "instanceName"; + const contentMetadataObjectCZ = { + "assetid": "cz-500358-98731568435405", + "type": "content", + "program": "Animated Test Content", + "title": "Big Buck Bunny", + "length": "596", + "airdate": "20230620 20:00:00", + "isfullepisode": "y", + "crossId1": "915 954 39504", + "nol_c1": "p1,", + "nol_c2": "p2, 651678089925925", + "segB": "011", + "segC": "", + "adloadtype": "2", + "hasAds": "1" + }; const options = { // containerId: 'THEOplayer', nol_sdkDebug: "debug" From 04bdf84be520fa797da6fee556641758a6db6668 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Mon, 19 Aug 2024 18:58:49 +0200 Subject: [PATCH 07/55] Add ContentMetadata types split up in generic, US and CZ specific properties --- nielsen/src/nielsen/Types.ts | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 0d021377..5c68426f 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -11,6 +11,98 @@ export type NielsenOptions = { optout?: boolean; }; +export type DCRContentMetadata = { + /* + * A fixed dial specifying the type of measured content + */ + type: 'content'; + /* + * Unique identifier for the video content (video file). Any label according to the needs of the TV company, which will ensure the identification of the same content across platforms. It can also be used for chaining several pieces of information from internal systems. At the beginning, the possibility of adding a client ID (to ensure uniqueness across clients) + */ + assetid: string; + /* + * Content description (name of the show, channel name, etc.) + */ + program: string; + /* + * Detailed description of content. + * [VOD] episode title + * [LIVE] name of the program (if it is available and can be dynamically changed along with the change of the program. Otherwise, fill in only the name of the channel)) + */ + title: string; + /* + * The length of the broadcast video content. It is also used to uniquely distinguish VOD and live broadcasts. This is the length of the currently playing content/file. If the content is, for example, only a part of the program, the length of this played part of the program is indicated. Use 86400 for live content. + */ + length: string; + /* + * Broadcast date, if you cannot fill in the correct value, please use the constant "19700101 00:00:01". + * [VOD] The date and time the video content was exposed online YYYYMMDD HH24:MI:SS. + * [LIVE] Midnight of the current day in YYYYMMDD 00:00:00 format + */ + airdate: string; + /* + * Indication of whether the video content being played is the entire episode or only part of it. Always use 'y' for live. + */ + isfullepisode: 'y' | 'n'; + /* + * CMS tag helper item. The method of recording ads insertion: 1. Linear – corresponds to TV insertion of ads 2. Dynamic – Dynamic Ad Insertion (DAI) + */ + adloadtype: '1' | '2'; +}; + +export type DCRContentMetadataCZ = DCRContentMetadata & { + /* + * IDEC type identifier + */ + crossId1: string; + /* + * More detailed categorization of video content. + * [VOD] Program type (codes according to the TV code list). + * [LIVE] The program type of the content being played, if available and can be changed dynamically with the program change. Otherwise, send an empty string. + */ + segB: string; + /* + * More detailed categorization of video content. Should always be an empty string. + */ + segC: ''; + /* + * Live TV station code + * [VOD] Keep this empty and pass "nol_c1":"p1,". + * [LIVE] implementation : "nol_c1":"p1,value" where value = Live station code used in the output data of the TV audience measurement project. see current appendix of the reference manual "Appendix RP 13 - List of stations.xlsx" If the live broadcast does not correspond to any TV station, use the code 9999. + */ + c1: string; + /* + * TV Identity for VOD. + * [VOD] Pass "nol_c2" : "p2,value" where value = To ensure the best possible harmonization of PEM D online measurement data with the data of the TV meter project, it is recommended to use TV IDENT as another online content identifier. If TVIDENT is not available at the time the content is brought online, c2 remains blank (and can be filled in later). TVIdent - same as in broadcast protocols of TV stations. + * [LIVE] Keep empty : "nol_c2":"p2," + */ + c2?: string; + /* + * CMS tag helper item. Indication of whether the content being played supports the insertion of advertisements. “0” – No ads “1” – Supports ads “2” – Don't know (default). + */ + hasAds: '0' | '1' | '2'; +}; + +export type DCRContentMetadataUS = DCRContentMetadata & { + /* + * Gracenote TMS ID (If available) should be passed for all telecasted content for clients using the Gracenote solution for proper matching purposes + * Note: The TMS ID will be a 14 character string. Normally leading with 2 alpha characters ('EP', 'MV', 'SH' or 'SP'), followed by 12 numbers. This should be provided to you by Nielsen + */ + crossId1?: string; + /* + * Populated by content distributor to contribute viewing from that distributor to the given content originator. For a full list of acceptable values, please contact your Nielsen representative. + */ + crossId2?: string; + /* + * One of two custom segments for clients' granular reporting within a brand. (Examples: Genre - horror, comedy, etc. ; Timeslot - primetime, daytime, etc. ; News type - breakingnews, weather, etc.) + */ + segB?: string; + /* + * One of two custom segments for clients' granular reporting within a brand. (Examples: Genre - horror, comedy, etc. ; Timeslot - primetime, daytime, etc. ; News type - breakingnews, weather, etc.) + */ + segC?: string; +}; + /** * adModel: 1) - Linear – matches TV ad load * 2) Dynamic – Dynamic Ad Insertion (DAI) */ From 1d5dc5a33e1d5f9412ad9672c078ee612f49f548 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Tue, 20 Aug 2024 12:11:30 +0200 Subject: [PATCH 08/55] Add CZ Ad metadata type --- nielsen/src/nielsen/Types.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 5c68426f..63d6854c 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -116,6 +116,29 @@ export type AdMetadata = { assetid: any; // TODO string? or can be anything? } & { [key: string]: string }; +export type DCRAdMetadataCZ = AdMetadata & { + /* + * An item in the CMS tag reserved for an identifier enabling the connection of an advertisement description from the RTVK system, similarly to PEM TV data. + */ + c4: string; + /* + * More detailed categorization of video content + */ + c5: string; + /* + * Ad type (same value as in the "type" item) + */ + c6: string; + /* + * Ad description. Possible use for cases where the AKA code is not available. RTB - designation of the advertising supplier (e.g. if there is no AKA code or more detailed description of the advertisement). + */ + title?: string; + /* + * Length of broadcast ad in seconds. (So that the length indicator is available even in cases where the AKA code is not available.) + */ + length: number; +}; + /* * Countries for which (1) Nielsen provides DCR Browser SDKs and (2) the corresponding SDK was tested with this integration. */ From 38c5740bc982de23b76ee59f800d91fc7dffe36a Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Tue, 20 Aug 2024 12:19:39 +0200 Subject: [PATCH 09/55] Add configuration type --- nielsen/src/nielsen/Types.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 63d6854c..0701f77b 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -1,5 +1,11 @@ export type AdType = 'preroll' | 'midroll' | 'postroll' | 'ad'; +export type NielsenConfiguration = { + country: NielsenCountry; + enableDTVR: boolean; + enableDCR: boolean; +}; + export type NielsenOptions = { // HTML DOM element id of the player container containerId?: string; From 083b2ca438f82e4b00fa627b5d6c1d3bab4583ae Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Tue, 20 Aug 2024 12:20:07 +0200 Subject: [PATCH 10/55] pass country configuration to static queue snippet loading function --- nielsen/src/integration/NielsenHandler.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 29b42df1..3b135dc1 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -7,7 +7,7 @@ import type { VolumeChangeEvent } from 'theoplayer'; import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; -import { AdMetadata, DTVRContentMetadata, NielsenOptions } from '../nielsen/Types'; +import { AdMetadata, DTVRContentMetadata, NielsenConfiguration, NielsenOptions } from '../nielsen/Types'; import { getAdType } from '../utils/Util'; const EMSG_PRIV_SUFFIX = 'PRIV{'; @@ -24,9 +24,15 @@ export class NielsenHandler { private decoder = new TextDecoder('utf-8'); - constructor(player: ChromelessPlayer, appId: string, instanceName: string, options?: NielsenOptions) { + constructor( + player: ChromelessPlayer, + appId: string, + instanceName: string, + configuration: NielsenConfiguration, + options?: NielsenOptions + ) { this.player = player; - this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options); + this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options, configuration.country); this.addEventListeners(); } From 9b1d992ec5f8ad88591ba4521fb6aef45c048bf1 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Tue, 20 Aug 2024 21:41:14 +0200 Subject: [PATCH 11/55] store configuration parameters in handler --- nielsen/src/integration/NielsenHandler.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 3b135dc1..69b69932 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -7,7 +7,13 @@ import type { VolumeChangeEvent } from 'theoplayer'; import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; -import { AdMetadata, DTVRContentMetadata, NielsenConfiguration, NielsenOptions } from '../nielsen/Types'; +import { + AdMetadata, + DTVRContentMetadata, + NielsenConfiguration, + NielsenCountry, + NielsenOptions +} from '../nielsen/Types'; import { getAdType } from '../utils/Util'; const EMSG_PRIV_SUFFIX = 'PRIV{'; @@ -16,6 +22,10 @@ const EMSG_PAYLOAD_SUFFIX = 'payload='; export class NielsenHandler { private player: ChromelessPlayer; + private dcrEnabled: boolean; + private dtvrEnabled: boolean; + private country: NielsenCountry = NielsenCountry.US; + private nSdkInstance: any; private sessionInProgress: boolean = false; @@ -28,11 +38,14 @@ export class NielsenHandler { player: ChromelessPlayer, appId: string, instanceName: string, - configuration: NielsenConfiguration, - options?: NielsenOptions + options?: NielsenOptions, + configuration?: NielsenConfiguration ) { this.player = player; - this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options, configuration.country); + this.dcrEnabled = configuration?.enableDCR ?? false; + this.dtvrEnabled = configuration?.enableDTVR ?? true; + this.country = configuration?.country ?? NielsenCountry.US; + this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options, this.country); this.addEventListeners(); } From 6d052eef7ce322a58a6ed4d65f6537d3a53ac595 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:20:14 +0200 Subject: [PATCH 12/55] updateMetadata improvements --- nielsen/src/integration/NielsenHandler.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 69b69932..60bba064 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -51,7 +51,22 @@ export class NielsenHandler { } updateMetadata(metadata: { [key: string]: string }): void { - this.nSdkInstance.ggPM('updateMetadata', metadata); + switch (this.country) { + case NielsenCountry.US: { + const { type, vidtype, assetid, ...updateableParameters } = metadata; + console.log(`[NIELSEN] updateMetadata: ${{ type, vidtype, assetid }} will not be updated`); + this.nSdkInstance.ggPM('updateMetadata', updateableParameters); + // ex + break; + } + case NielsenCountry.CZ: + default: + // + } + } + + updateDCRContentMetadata(metadata: DCRContentMetadata): void { + if (this.dcrEnabled) this.metadata = metadata; } private addEventListeners(): void { From ca2de345d50c474e24a89d4b04599a8464921fe7 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:21:49 +0200 Subject: [PATCH 13/55] introduce DCRContentMetadata field --- nielsen/src/integration/NielsenHandler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 60bba064..998a46e2 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -9,6 +9,7 @@ import type { import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; import { AdMetadata, + DCRContentMetadata, DTVRContentMetadata, NielsenConfiguration, NielsenCountry, @@ -26,6 +27,8 @@ export class NielsenHandler { private dtvrEnabled: boolean; private country: NielsenCountry = NielsenCountry.US; + private metadata: DCRContentMetadata | undefined; + private nSdkInstance: any; private sessionInProgress: boolean = false; From b6bc9a6704731ffafb1d5ee69c9bfb9bdb63c094 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:31:17 +0200 Subject: [PATCH 14/55] tweak if statement --- nielsen/src/integration/NielsenHandler.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 998a46e2..55e10d25 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -212,14 +212,13 @@ export class NielsenHandler { }; private maybeSendPlayEvent(): void { - if (!this.sessionInProgress && !Number.isNaN(this.duration)) { - this.sessionInProgress = true; - const metadataObject = { - channelName: this.player.src, - length: this.duration - }; - this.nSdkInstance.ggPM('play', metadataObject); - } + if (this.sessionInProgress || Number.isNaN(this.duration)) return; + this.sessionInProgress = true; + const metadataObject = { + channelName: this.player.src, + length: this.duration + }; + this.nSdkInstance.ggPM('play', metadataObject); } private endSession(): void { From e81ae1a8934b96d8123fd2c7d40f719ff2e90889 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:40:14 +0200 Subject: [PATCH 15/55] only send onLoadMetadata with dtvr metadata if dtvr is enabled --- nielsen/src/integration/NielsenHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 55e10d25..8e4f3e24 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -130,6 +130,7 @@ export class NielsenHandler { }; private onLoadMetadata = () => { + if (!this.dtvrEnabled) return; const data: DTVRContentMetadata = { type: 'content', adModel: '1' // Always '1' for DTVR From 059f61fb55635fca9f3a4709f89d90e157a0a762 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:40:34 +0200 Subject: [PATCH 16/55] only send play event if dtvr is enabled --- nielsen/src/integration/NielsenHandler.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 8e4f3e24..f445e431 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -214,12 +214,14 @@ export class NielsenHandler { private maybeSendPlayEvent(): void { if (this.sessionInProgress || Number.isNaN(this.duration)) return; - this.sessionInProgress = true; - const metadataObject = { - channelName: this.player.src, - length: this.duration - }; - this.nSdkInstance.ggPM('play', metadataObject); + if (this.dtvrEnabled) { + this.sessionInProgress = true; + const metadataObject = { + channelName: this.player.src, + length: this.duration + }; + this.nSdkInstance.ggPM('play', metadataObject); + } } private endSession(): void { From 8cc288e4674cd2e0692cc716d5144932a1b38994 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 21:43:51 +0200 Subject: [PATCH 17/55] send loadMetadata event for DCR on first play event --- nielsen/src/integration/NielsenHandler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index f445e431..5db286f2 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -222,6 +222,9 @@ export class NielsenHandler { }; this.nSdkInstance.ggPM('play', metadataObject); } + if (this.dcrEnabled) { + this.nSdkInstance.ggPM('loadMetadata', this.metadata); + } } private endSession(): void { From a12d29f03ea8c0adba5845d6233c3867ab88d1f7 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 22:21:38 +0200 Subject: [PATCH 18/55] adjust types --- nielsen/src/nielsen/Types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 0701f77b..8e1505c0 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -126,15 +126,15 @@ export type DCRAdMetadataCZ = AdMetadata & { /* * An item in the CMS tag reserved for an identifier enabling the connection of an advertisement description from the RTVK system, similarly to PEM TV data. */ - c4: string; + nol_c4: string; /* * More detailed categorization of video content */ - c5: string; + nol_c5: string; /* * Ad type (same value as in the "type" item) */ - c6: string; + nol_c6: string; /* * Ad description. Possible use for cases where the AKA code is not available. RTB - designation of the advertising supplier (e.g. if there is no AKA code or more detailed description of the advertisement). */ @@ -142,7 +142,7 @@ export type DCRAdMetadataCZ = AdMetadata & { /* * Length of broadcast ad in seconds. (So that the length indicator is available even in cases where the AKA code is not available.) */ - length: number; + length: string; }; /* From 0487a5f71115c7f1734ac4373055e2c071f602df Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Wed, 21 Aug 2024 22:22:14 +0200 Subject: [PATCH 19/55] separate ad metadata reporting logic for dcr and dtvr --- nielsen/src/integration/NielsenHandler.ts | 16 ++++++++---- nielsen/src/utils/Util.ts | 30 +++++++++++++++++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 5db286f2..7075eaaf 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -205,11 +205,17 @@ export class NielsenHandler { private onAdBegin = () => { const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); const type = getAdType(this.player.ads!.currentAdBreak!); - const adMetadata: AdMetadata = { - type, - assetid: currentAd[0].id! - }; - this.nSdkInstance.ggPM('loadMetadata', adMetadata); + if (this.dtvrEnabled) { + const dtvrAdMetadata: AdMetadata = { + type, + assetid: currentAd[0].id! + }; + this.nSdkInstance.ggPM('loadMetadata', dtvrAdMetadata); + } + if (this.dcrEnabled) { + const dcrAdMetadata = buildDCRAdMetadata(currentAd); + this.nSdkInstance.ggPM('loadMetadata', dcrAdMetadata); + } }; private maybeSendPlayEvent(): void { diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index 56006989..c097cd7d 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -1,5 +1,5 @@ -import type { AdBreak } from 'theoplayer'; -import { AdType } from '../nielsen/Types'; +import type { Ad, AdBreak, GoogleImaAd } from 'theoplayer'; +import { AdMetadata, AdType, DCRAdMetadataCZ, NielsenCountry } from '../nielsen/Types'; export function getAdType(adBreak: AdBreak): AdType { const currentAdBreakTimeOffset = adBreak.timeOffset; @@ -13,3 +13,29 @@ export function getAdType(adBreak: AdBreak): AdType { } return currentAdBreakPosition; } + +export function buildDCRAdMetadata(ad: Ad, country: NielsenCountry): AdMetadata { + const adMetadata = { + assetid: ad.id ?? '', + type: getAdType(ad.adBreak) + }; + if (country == NielsenCountry.US) { + return adMetadata; + } + if (country == NielsenCountry.CZ) { + const dcrAdMetadataCZ: DCRAdMetadataCZ = { + ...adMetadata, + ['nol_c4']: 'PLACEHOLDER', + ['nol_c5']: '2', // 1. regular show, 2. advertising, 3. trailer, 4. divide, 5. komerce (teleshopping,sponsor) TODO: provide API to control this + ['nol_c6']: `p6,${adMetadata.type}`, + title: (ad as GoogleImaAd).title ?? '', + length: ad.duration?.toString() ?? '0' + }; + return dcrAdMetadataCZ; + } + console.error('[NIELSEN - Error] Failed to extract Ad Metadata - sending placeholders instead'); + return { + type: 'ad', + assetid: '0000' + }; +} From a95ac850c817edfd36b4ed2596d26b017a4c0a3c Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 10:47:50 +0200 Subject: [PATCH 20/55] set initial adMetadata io placeholder values --- nielsen/src/utils/Util.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index c097cd7d..cf29239c 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -33,9 +33,6 @@ export function buildDCRAdMetadata(ad: Ad, country: NielsenCountry): AdMetadata }; return dcrAdMetadataCZ; } - console.error('[NIELSEN - Error] Failed to extract Ad Metadata - sending placeholders instead'); - return { - type: 'ad', - assetid: '0000' - }; + console.error('[NIELSEN - Error] No NielsenCountry was provided - sending only assetid and type'); + return adMetadata; } From 05c7477d674cdfae572a3569178c75dcb0700da7 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 10:48:30 +0200 Subject: [PATCH 21/55] take first array item of filtered currentAds and add TODO note --- nielsen/src/integration/NielsenHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 7075eaaf..88b66c66 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -203,7 +203,7 @@ export class NielsenHandler { }; private onAdBegin = () => { - const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); + const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); // TODO check why we chose to not use the ad from the event payload const type = getAdType(this.player.ads!.currentAdBreak!); if (this.dtvrEnabled) { const dtvrAdMetadata: AdMetadata = { @@ -213,7 +213,7 @@ export class NielsenHandler { this.nSdkInstance.ggPM('loadMetadata', dtvrAdMetadata); } if (this.dcrEnabled) { - const dcrAdMetadata = buildDCRAdMetadata(currentAd); + const dcrAdMetadata = buildDCRAdMetadata(currentAd[0], this.country); this.nSdkInstance.ggPM('loadMetadata', dcrAdMetadata); } }; From 50f7b9fa487d08b32e78ca7386b07edb2a3a21d6 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 10:48:43 +0200 Subject: [PATCH 22/55] import --- nielsen/src/integration/NielsenHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 88b66c66..1d945684 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -15,7 +15,7 @@ import { NielsenCountry, NielsenOptions } from '../nielsen/Types'; -import { getAdType } from '../utils/Util'; +import { buildDCRAdMetadata, getAdType } from '../utils/Util'; const EMSG_PRIV_SUFFIX = 'PRIV{'; const EMSG_PAYLOAD_SUFFIX = 'payload='; From 23cf5c7df1179756d312369c1b1f3cdcf2626005 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 11:04:09 +0200 Subject: [PATCH 23/55] Report playhead position --- nielsen/src/integration/NielsenHandler.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 1d945684..4b4f38c7 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -4,6 +4,7 @@ import type { ChromelessPlayer, TextTrack, TextTrackEnterCueEvent, + TimeUpdateEvent, VolumeChangeEvent } from 'theoplayer'; import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE'; @@ -28,6 +29,7 @@ export class NielsenHandler { private country: NielsenCountry = NielsenCountry.US; private metadata: DCRContentMetadata | undefined; + private lastReportedPlayheadPosition: number | undefined; private nSdkInstance: any; @@ -79,6 +81,7 @@ export class NielsenHandler { this.player.addEventListener('volumechange', this.onVolumeChange); this.player.addEventListener('loadedmetadata', this.onLoadMetadata); this.player.addEventListener('durationchange', this.onDurationChange); + this.player.addEventListener('timeupdate', this.onTimeUpdate); this.player.textTracks.addEventListener('addtrack', this.onAddTrack); @@ -96,6 +99,7 @@ export class NielsenHandler { this.player.removeEventListener('volumechange', this.onVolumeChange); this.player.removeEventListener('loadedmetadata', this.onLoadMetadata); this.player.removeEventListener('durationchange', this.onDurationChange); + this.player.removeEventListener('timeupdate', this.onTimeUpdate); this.player.textTracks.removeEventListener('addtrack', this.onAddTrack); @@ -129,6 +133,13 @@ export class NielsenHandler { this.maybeSendPlayEvent(); }; + private onTimeUpdate = ({ currentTime }: TimeUpdateEvent) => { + const currentTimeFloor = Math.floor(currentTime); + if (currentTimeFloor === this.lastReportedPlayheadPosition) return; + this.lastReportedPlayheadPosition = currentTimeFloor; + this.nSdkInstance.ggPM('setPlayheadPosition', currentTimeFloor); + }; + private onLoadMetadata = () => { if (!this.dtvrEnabled) return; const data: DTVRContentMetadata = { From b4a7ca419d8a92bae88556d4c6b4aee33994a933 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 11:05:02 +0200 Subject: [PATCH 24/55] only report playhead position when DCR is enabled --- nielsen/src/integration/NielsenHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 4b4f38c7..2432502d 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -134,6 +134,7 @@ export class NielsenHandler { }; private onTimeUpdate = ({ currentTime }: TimeUpdateEvent) => { + if (!this.dcrEnabled) return; const currentTimeFloor = Math.floor(currentTime); if (currentTimeFloor === this.lastReportedPlayheadPosition) return; this.lastReportedPlayheadPosition = currentTimeFloor; From bdae3eb1b1096b3338ee05d38475d71ccc3d5faa Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 11:06:52 +0200 Subject: [PATCH 25/55] dont listen for timed metadata if DTVR is not enabled --- nielsen/src/integration/NielsenHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 2432502d..c073a670 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -151,6 +151,7 @@ export class NielsenHandler { }; private onAddTrack = (event: AddTrackEvent) => { + if (!this.dtvrEnabled) return; if (event.track.kind === 'metadata') { const track = event.track as TextTrack; if (track.type === 'id3' || track.type === 'emsg') { From 0713d95c9291d5e878c3017b19db88516c835682 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 11:15:37 +0200 Subject: [PATCH 26/55] send stop event when an ad ends --- nielsen/src/integration/NielsenHandler.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index c073a670..fa19c6e3 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -87,6 +87,7 @@ export class NielsenHandler { if (this.player.ads) { this.player.ads.addEventListener('adbegin', this.onAdBegin); + this.player.ads.addEventListener('adend', this.onAdEnd); } window.addEventListener('beforeunload', this.onEnd); @@ -105,6 +106,7 @@ export class NielsenHandler { if (this.player.ads) { this.player.ads.removeEventListener('adbegin', this.onAdBegin); + this.player.ads.removeEventListener('adend', this.onAdEnd); } window.removeEventListener('beforeunload', this.onEnd); @@ -231,6 +233,11 @@ export class NielsenHandler { } }; + private onAdEnd = () => { + if (!this.dcrEnabled) return; + this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); + }; + private maybeSendPlayEvent(): void { if (this.sessionInProgress || Number.isNaN(this.duration)) return; if (this.dtvrEnabled) { From 4e67802c79c3d8282e818c0e56147a5ffd6af276 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:07:20 +0200 Subject: [PATCH 27/55] report end before post roll starts --- nielsen/src/integration/NielsenHandler.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index fa19c6e3..b5c5ff0c 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -1,5 +1,6 @@ import type { Ad, + AdBreakEvent, AddTrackEvent, ChromelessPlayer, TextTrack, @@ -238,6 +239,13 @@ export class NielsenHandler { this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); }; + private onAdBreakBegin = ({ adBreak }: AdBreakEvent<'begin'>) => { + if (!this.dcrEnabled) return; + const isPostroll = getAdType(adBreak) === 'postroll'; + if (!isPostroll) return; + this.endSession(); + }; + private maybeSendPlayEvent(): void { if (this.sessionInProgress || Number.isNaN(this.duration)) return; if (this.dtvrEnabled) { From 8a9fc8e3b897a79902c489cd9f400c9032d9ed8e Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:14:42 +0200 Subject: [PATCH 28/55] typo --- nielsen/src/integration/NielsenHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index b5c5ff0c..2a839ce6 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -239,7 +239,7 @@ export class NielsenHandler { this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); }; - private onAdBreakBegin = ({ adBreak }: AdBreakEvent<'begin'>) => { + private onAdBreakBegin = ({ adBreak }: AdBreakEvent<'adbreakbegin'>) => { if (!this.dcrEnabled) return; const isPostroll = getAdType(adBreak) === 'postroll'; if (!isPostroll) return; From 235f9ae8668c9a131f867b54b316e0232eba2376 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:14:55 +0200 Subject: [PATCH 29/55] add and remove listener --- nielsen/src/integration/NielsenHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 2a839ce6..2d484be2 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -89,6 +89,7 @@ export class NielsenHandler { if (this.player.ads) { this.player.ads.addEventListener('adbegin', this.onAdBegin); this.player.ads.addEventListener('adend', this.onAdEnd); + this.player.ads.addEventListener('adbreakbegin', this.onAdBreakBegin); } window.addEventListener('beforeunload', this.onEnd); @@ -108,6 +109,7 @@ export class NielsenHandler { if (this.player.ads) { this.player.ads.removeEventListener('adbegin', this.onAdBegin); this.player.ads.removeEventListener('adend', this.onAdEnd); + this.player.ads.removeEventListener('adbreakbegin', this.onAdBreakBegin); } window.removeEventListener('beforeunload', this.onEnd); From 3a7f85768c9e3794d5ecdfde64d3357889728bc1 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:17:24 +0200 Subject: [PATCH 30/55] rewrite --- nielsen/src/integration/NielsenHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 2d484be2..8444f580 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -72,7 +72,8 @@ export class NielsenHandler { } updateDCRContentMetadata(metadata: DCRContentMetadata): void { - if (this.dcrEnabled) this.metadata = metadata; + if (!this.dcrEnabled) return; + this.metadata = metadata; } private addEventListeners(): void { From 62a787b8ee29e838914945ea8a87287be9028050 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:17:43 +0200 Subject: [PATCH 31/55] only report setVolume for DTVR --- nielsen/src/integration/NielsenHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 8444f580..f07ead0c 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -130,6 +130,7 @@ export class NielsenHandler { }; private onVolumeChange = (event: VolumeChangeEvent) => { + if (!this.dtvrEnabled) return; const volumeLevel = this.player.muted ? 0 : event.volume * 100; this.nSdkInstance.ggPM('setVolume', volumeLevel); }; From 520838c6e08cd2fb1d3cacee4e2bbe941c02f744 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:17:50 +0200 Subject: [PATCH 32/55] remove comments --- nielsen/src/integration/NielsenHandler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index f07ead0c..da9d253d 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -62,12 +62,10 @@ export class NielsenHandler { const { type, vidtype, assetid, ...updateableParameters } = metadata; console.log(`[NIELSEN] updateMetadata: ${{ type, vidtype, assetid }} will not be updated`); this.nSdkInstance.ggPM('updateMetadata', updateableParameters); - // ex break; } case NielsenCountry.CZ: default: - // } } From 5918b31b2d083909832c5601d42c1490adc4879b Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:20:42 +0200 Subject: [PATCH 33/55] ignore NaN duration change events --- nielsen/src/integration/NielsenHandler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index da9d253d..f3d344ec 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -3,6 +3,7 @@ import type { AdBreakEvent, AddTrackEvent, ChromelessPlayer, + DurationChangeEvent, TextTrack, TextTrackEnterCueEvent, TimeUpdateEvent, @@ -133,7 +134,8 @@ export class NielsenHandler { this.nSdkInstance.ggPM('setVolume', volumeLevel); }; - private onDurationChange = () => { + private onDurationChange = ({ duration }: DurationChangeEvent) => { + if (isNaN(duration)) return; this.duration = this.player.duration; this.maybeSendPlayEvent(); }; From 24473232257c0c297fe0974ddc5077ae4b8db0be Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 12:50:54 +0200 Subject: [PATCH 34/55] detect post rolls for DAI sources too --- nielsen/src/integration/NielsenHandler.ts | 16 ++++++++++++---- nielsen/src/utils/Util.ts | 11 ++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index f3d344ec..17e4f4f3 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -1,6 +1,7 @@ import type { Ad, AdBreakEvent, + AdEvent, AddTrackEvent, ChromelessPlayer, DurationChangeEvent, @@ -222,13 +223,17 @@ export class NielsenHandler { } }; - private onAdBegin = () => { + private onAdBegin = ({ ad }: AdEvent<'adbegin'>) => { const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); // TODO check why we chose to not use the ad from the event payload - const type = getAdType(this.player.ads!.currentAdBreak!); + const { adBreak } = ad; + const { timeOffset } = adBreak; + const offset = this.player.ads?.dai?.contentTimeForStreamTime(timeOffset) ?? timeOffset; + const duration = this.player.ads?.dai?.contentTimeForStreamTime(this.duration) ?? this.duration; + const type = getAdType(offset, duration); if (this.dtvrEnabled) { const dtvrAdMetadata: AdMetadata = { type, - assetid: currentAd[0].id! + assetid: ad.id! }; this.nSdkInstance.ggPM('loadMetadata', dtvrAdMetadata); } @@ -245,7 +250,10 @@ export class NielsenHandler { private onAdBreakBegin = ({ adBreak }: AdBreakEvent<'adbreakbegin'>) => { if (!this.dcrEnabled) return; - const isPostroll = getAdType(adBreak) === 'postroll'; + const { timeOffset } = adBreak; + const offset = this.player.ads?.dai?.contentTimeForStreamTime(timeOffset) ?? timeOffset; + const duration = this.player.ads?.dai?.contentTimeForStreamTime(this.duration) ?? this.duration; + const isPostroll = getAdType(offset, duration) === 'postroll'; if (!isPostroll) return; this.endSession(); }; diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index cf29239c..d341813a 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -1,14 +1,15 @@ import type { Ad, AdBreak, GoogleImaAd } from 'theoplayer'; import { AdMetadata, AdType, DCRAdMetadataCZ, NielsenCountry } from '../nielsen/Types'; -export function getAdType(adBreak: AdBreak): AdType { - const currentAdBreakTimeOffset = adBreak.timeOffset; +export function getAdType(offset: number, duration: number): AdType { let currentAdBreakPosition: AdType = 'ad'; - if (currentAdBreakTimeOffset === 0) { + if (offset === 0) { currentAdBreakPosition = 'preroll'; - } else if (currentAdBreakTimeOffset < 0) { + } else if (offset < 0) { currentAdBreakPosition = 'postroll'; - } else if (currentAdBreakTimeOffset > 0) { + } else if (duration - offset < 1) { + currentAdBreakPosition = 'postroll'; // For DAI ads, the offset and duration will be converted from stream time to content time and coincide more or less + } else if (offset > 0) { currentAdBreakPosition = 'midroll'; } return currentAdBreakPosition; From 85b4c30115b8a292ae7bf737714aefcbe6fac3be Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 15:04:03 +0200 Subject: [PATCH 35/55] report stop if an ad was playing in the onEnd handler --- nielsen/src/integration/NielsenHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 17e4f4f3..7d3a0ca4 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -121,6 +121,7 @@ export class NielsenHandler { }; private onEnd = () => { + if (this.dcrEnabled && this.player.ads?.playing) this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); this.endSession(); }; From 62ef2fc963153fd14ef138fdd144b631b005c917 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 15:04:22 +0200 Subject: [PATCH 36/55] report stop on more interrupt scenarios --- nielsen/src/integration/NielsenHandler.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 7d3a0ca4..f040687e 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -78,6 +78,8 @@ export class NielsenHandler { private addEventListeners(): void { this.player.addEventListener('play', this.onPlay); + this.player.addEventListener('pause', this.onInterrupt); + this.player.addEventListener('waiting', this.onInterrupt); this.player.addEventListener('ended', this.onEnd); this.player.addEventListener('sourcechange', this.onSourceChange); this.player.addEventListener('volumechange', this.onVolumeChange); @@ -98,6 +100,8 @@ export class NielsenHandler { private removeEventListeners(): void { this.player.removeEventListener('play', this.onPlay); + this.player.removeEventListener('pause', this.onInterrupt); + this.player.removeEventListener('waiting', this.onInterrupt); this.player.removeEventListener('ended', this.onEnd); this.player.removeEventListener('sourcechange', this.onSourceChange); this.player.removeEventListener('volumechange', this.onVolumeChange); @@ -120,6 +124,11 @@ export class NielsenHandler { this.maybeSendPlayEvent(); }; + private onInterrupt = () => { + if (!this.dcrEnabled) return; + this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); + }; + private onEnd = () => { if (this.dcrEnabled && this.player.ads?.playing) this.nSdkInstance.ggPM('stop', this.getPlayHeadPosition()); this.endSession(); From 824079aae4dc44ca49c73a28408a019da6b5283f Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 16:35:05 +0200 Subject: [PATCH 37/55] adapt build ad metadata helper to new getAdType implementation --- nielsen/src/integration/NielsenHandler.ts | 2 +- nielsen/src/utils/Util.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index f040687e..45a053e0 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -248,7 +248,7 @@ export class NielsenHandler { this.nSdkInstance.ggPM('loadMetadata', dtvrAdMetadata); } if (this.dcrEnabled) { - const dcrAdMetadata = buildDCRAdMetadata(currentAd[0], this.country); + const dcrAdMetadata = buildDCRAdMetadata(currentAd[0], this.country, this.duration); this.nSdkInstance.ggPM('loadMetadata', dcrAdMetadata); } }; diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index d341813a..0d3672c2 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -15,10 +15,10 @@ export function getAdType(offset: number, duration: number): AdType { return currentAdBreakPosition; } -export function buildDCRAdMetadata(ad: Ad, country: NielsenCountry): AdMetadata { +export function buildDCRAdMetadata(ad: Ad, country: NielsenCountry, duration: number): AdMetadata { const adMetadata = { assetid: ad.id ?? '', - type: getAdType(ad.adBreak) + type: getAdType(ad.adBreak.timeOffset, duration) }; if (country == NielsenCountry.US) { return adMetadata; From 2f8dec374f8a6f8cd2a87871ee5238bb448d5e03 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 16:35:48 +0200 Subject: [PATCH 38/55] Add public DCR metadata types --- nielsen/src/index.ts | 2 +- nielsen/src/nielsen/Types.ts | 85 ++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/nielsen/src/index.ts b/nielsen/src/index.ts index ee3d9c68..f2cadfba 100644 --- a/nielsen/src/index.ts +++ b/nielsen/src/index.ts @@ -1,2 +1,2 @@ export { NielsenConnector } from './integration/NielsenConnector'; -export { NielsenOptions } from './nielsen/Types'; +export { NielsenOptions, NielsenDCRContentMetadata, NielsenDCRContentMetadataCZ } from './nielsen/Types'; diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index 8e1505c0..e60d15e9 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -56,6 +56,85 @@ export type DCRContentMetadata = { adloadtype: '1' | '2'; }; +export enum AdLoadType { + linear = '1', + dynamic = '2' +} + +export type NielsenDCRContentMetadata = { + /* + * Unique identifier for the video content (video file). Any label according to the needs of the TV company, which will ensure the identification of the same content across platforms. It can also be used for chaining several pieces of information from internal systems. At the beginning, the possibility of adding a client ID (to ensure uniqueness across clients) + */ + assetid: string; + /* + * Content description (name of the show, channel name, etc.) + */ + program: string; + /* + * Detailed description of content. + * [VOD] episode title + * [LIVE] name of the program (if it is available and can be dynamically changed along with the change of the program. Otherwise, fill in only the name of the channel)) + */ + title: string; + /* + * The length of the broadcast video content. It is also used to uniquely distinguish VOD and live broadcasts. This is the length of the currently playing content/file. If the content is, for example, only a part of the program, the length of this played part of the program is indicated. Use 86400 for live content. + */ + length: string; + /* + * Broadcast date, if left empty, "19700101 00:00:01" will be reported. + * [VOD] The date and time the video content was exposed online YYYYMMDD HH24:MI:SS. + * [LIVE] Midnight of the current day in YYYYMMDD 00:00:00 format + */ + airdate?: string; + /* + * Indication of whether the video content being played is the entire episode or only part of it. Always reported as true for live. + */ + isfullepisode: boolean; + /* + * CMS tag helper item. The method of recording ads insertion: 1. Linear – corresponds to TV insertion of ads 2. Dynamic – Dynamic Ad Insertion (DAI) + */ + adloadtype: AdLoadType; +}; + +export enum HasAds { + no_ads = '0', + supports_ads = '1', + unknown = '2' +} + +export type NielsenDCRContentMetadataCZ = NielsenDCRContentMetadata & { + /* + * IDEC type identifier + */ + crossId1: string; + /* + * More detailed categorization of video content. + * [VOD] Program type (codes according to the TV code list). + * [LIVE] The program type of the content being played, if available and can be changed dynamically with the program change. Otherwise, send an empty string. + */ + segB: string; + /* + * More detailed categorization of video content. + */ + segC?: ''; + /* + * Live TV station code + * [VOD] Keep this empty and pass "nol_c1":"p1,". + * [LIVE] implementation : "nol_c1":"p1,value" where value = Live station code used in the output data of the TV audience measurement project. see current appendix of the reference manual "Appendix RP 13 - List of stations.xlsx" If the live broadcast does not correspond to any TV station, use the code 9999. + */ + c1?: string; + /* + * TV Identity for VOD. + * [VOD] Pass "nol_c2" : "p2,value" where value = To ensure the best possible harmonization of PEM D online measurement data with the data of the TV meter project, it is recommended to use TV IDENT as another online content identifier. If TVIDENT is not available at the time the content is brought online, c2 remains blank (and can be filled in later). TVIdent - same as in broadcast protocols of TV stations. + * [LIVE] Keep empty : "nol_c2":"p2," + */ + c2?: string; + /* + * CMS tag helper item. Indication of whether the content being played supports the insertion of advertisements. “0” – No ads “1” – Supports ads “2” – Don't know (default). + */ + hasAds: HasAds; +}; + export type DCRContentMetadataCZ = DCRContentMetadata & { /* * IDEC type identifier @@ -68,7 +147,7 @@ export type DCRContentMetadataCZ = DCRContentMetadata & { */ segB: string; /* - * More detailed categorization of video content. Should always be an empty string. + * More detailed categorization of video content. */ segC: ''; /* @@ -76,13 +155,13 @@ export type DCRContentMetadataCZ = DCRContentMetadata & { * [VOD] Keep this empty and pass "nol_c1":"p1,". * [LIVE] implementation : "nol_c1":"p1,value" where value = Live station code used in the output data of the TV audience measurement project. see current appendix of the reference manual "Appendix RP 13 - List of stations.xlsx" If the live broadcast does not correspond to any TV station, use the code 9999. */ - c1: string; + nol_c1?: string; /* * TV Identity for VOD. * [VOD] Pass "nol_c2" : "p2,value" where value = To ensure the best possible harmonization of PEM D online measurement data with the data of the TV meter project, it is recommended to use TV IDENT as another online content identifier. If TVIDENT is not available at the time the content is brought online, c2 remains blank (and can be filled in later). TVIdent - same as in broadcast protocols of TV stations. * [LIVE] Keep empty : "nol_c2":"p2," */ - c2?: string; + nol_c2?: string; /* * CMS tag helper item. Indication of whether the content being played supports the insertion of advertisements. “0” – No ads “1” – Supports ads “2” – Don't know (default). */ From 5fce7b8e33e27d313ac3ceb044bc2b0f7e816e7b Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 17:17:17 +0200 Subject: [PATCH 39/55] convert provided metadata to types imposed by nielsen dcr docs --- nielsen/src/integration/NielsenHandler.ts | 7 +-- nielsen/src/nielsen/Types.ts | 26 +++++++++-- nielsen/src/utils/Util.ts | 54 ++++++++++++++++++++++- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 45a053e0..dfbe10a3 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -17,9 +17,10 @@ import { DTVRContentMetadata, NielsenConfiguration, NielsenCountry, + NielsenDCRContentMetadata, NielsenOptions } from '../nielsen/Types'; -import { buildDCRAdMetadata, getAdType } from '../utils/Util'; +import { buildDCRAdMetadata, buildDCRContentMetadata, getAdType } from '../utils/Util'; const EMSG_PRIV_SUFFIX = 'PRIV{'; const EMSG_PAYLOAD_SUFFIX = 'payload='; @@ -71,9 +72,9 @@ export class NielsenHandler { } } - updateDCRContentMetadata(metadata: DCRContentMetadata): void { + updateDCRContentMetadata(metadata: NielsenDCRContentMetadata): void { if (!this.dcrEnabled) return; - this.metadata = metadata; + this.metadata = buildDCRContentMetadata(metadata, this.country); } private addEventListeners(): void { diff --git a/nielsen/src/nielsen/Types.ts b/nielsen/src/nielsen/Types.ts index e60d15e9..049fba98 100644 --- a/nielsen/src/nielsen/Types.ts +++ b/nielsen/src/nielsen/Types.ts @@ -21,7 +21,7 @@ export type DCRContentMetadata = { /* * A fixed dial specifying the type of measured content */ - type: 'content'; + type: string; /* * Unique identifier for the video content (video file). Any label according to the needs of the TV company, which will ensure the identification of the same content across platforms. It can also be used for chaining several pieces of information from internal systems. At the beginning, the possibility of adding a client ID (to ensure uniqueness across clients) */ @@ -49,11 +49,11 @@ export type DCRContentMetadata = { /* * Indication of whether the video content being played is the entire episode or only part of it. Always use 'y' for live. */ - isfullepisode: 'y' | 'n'; + isfullepisode: string; /* * CMS tag helper item. The method of recording ads insertion: 1. Linear – corresponds to TV insertion of ads 2. Dynamic – Dynamic Ad Insertion (DAI) */ - adloadtype: '1' | '2'; + adloadtype: string; }; export enum AdLoadType { @@ -168,6 +168,26 @@ export type DCRContentMetadataCZ = DCRContentMetadata & { hasAds: '0' | '1' | '2'; }; +export type NielsenDCRContentMetadataUS = NielsenDCRContentMetadata & { + /* + * Gracenote TMS ID (If available) should be passed for all telecasted content for clients using the Gracenote solution for proper matching purposes + * Note: The TMS ID will be a 14 character string. Normally leading with 2 alpha characters ('EP', 'MV', 'SH' or 'SP'), followed by 12 numbers. This should be provided to you by Nielsen + */ + crossId1?: string; + /* + * Populated by content distributor to contribute viewing from that distributor to the given content originator. For a full list of acceptable values, please contact your Nielsen representative. + */ + crossId2?: string; + /* + * One of two custom segments for clients' granular reporting within a brand. (Examples: Genre - horror, comedy, etc. ; Timeslot - primetime, daytime, etc. ; News type - breakingnews, weather, etc.) + */ + segB?: string; + /* + * One of two custom segments for clients' granular reporting within a brand. (Examples: Genre - horror, comedy, etc. ; Timeslot - primetime, daytime, etc. ; News type - breakingnews, weather, etc.) + */ + segC?: string; +}; + export type DCRContentMetadataUS = DCRContentMetadata & { /* * Gracenote TMS ID (If available) should be passed for all telecasted content for clients using the Gracenote solution for proper matching purposes diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index 0d3672c2..a7b70a15 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -1,5 +1,16 @@ import type { Ad, AdBreak, GoogleImaAd } from 'theoplayer'; -import { AdMetadata, AdType, DCRAdMetadataCZ, NielsenCountry } from '../nielsen/Types'; +import { + AdMetadata, + AdType, + DCRAdMetadataCZ, + DCRContentMetadata, + DCRContentMetadataCZ, + DCRContentMetadataUS, + NielsenCountry, + NielsenDCRContentMetadata, + NielsenDCRContentMetadataCZ, + NielsenDCRContentMetadataUS +} from '../nielsen/Types'; export function getAdType(offset: number, duration: number): AdType { let currentAdBreakPosition: AdType = 'ad'; @@ -15,6 +26,47 @@ export function getAdType(offset: number, duration: number): AdType { return currentAdBreakPosition; } +export function buildDCRContentMetadata( + metadata: NielsenDCRContentMetadata, + country: NielsenCountry +): DCRContentMetadata | DCRContentMetadataUS | DCRContentMetadataCZ { + const dcrContentMetadata = { + type: 'content', + assetid: metadata.assetid, + program: metadata.program, + title: metadata.title, + length: metadata.length, + airdate: metadata?.airdate ?? '19700101 00:00:01', + isfullepisode: metadata.isfullepisode ? 'y' : 'n', + adloadtype: metadata.adloadtype + }; + if (country === NielsenCountry.CZ) { + const { crossId1, segB, segC, c1, c2, hasAds } = metadata as NielsenDCRContentMetadataCZ; + const dcrContentMetadataCZ: DCRContentMetadataCZ = { + ...dcrContentMetadata, + ['crossId1']: crossId1, + segB: segB, + segC: segC ?? '', + hasAds: hasAds + }; + if (c1) dcrContentMetadataCZ['nol_c1'] = `p1,${c1}`; + if (c2) dcrContentMetadataCZ['nol_c2'] = `p2,${c2}`; + return dcrContentMetadataCZ; + } + if (country === NielsenCountry.US) { + const { crossId1, crossId2, segB, segC } = metadata as NielsenDCRContentMetadataUS; + const dcrContentMetadataUS: DCRContentMetadataUS = { + ...dcrContentMetadata + }; + if (crossId1) dcrContentMetadataUS['crossId1'] = crossId1; + if (crossId2) dcrContentMetadataUS['crossId2'] = crossId2; + if (segB) dcrContentMetadataUS['segB'] = segB; + if (segC) dcrContentMetadataUS['segC'] = segC; + return dcrContentMetadataUS; + } + return dcrContentMetadata; +} + export function buildDCRAdMetadata(ad: Ad, country: NielsenCountry, duration: number): AdMetadata { const adMetadata = { assetid: ad.id ?? '', From 66b72c528f84b4058d6a60b6be38028c46deba6e Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:41:31 +0200 Subject: [PATCH 40/55] expose updateDCRContentMetadata --- nielsen/src/integration/NielsenConnector.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nielsen/src/integration/NielsenConnector.ts b/nielsen/src/integration/NielsenConnector.ts index 371816bc..a912ec74 100644 --- a/nielsen/src/integration/NielsenConnector.ts +++ b/nielsen/src/integration/NielsenConnector.ts @@ -21,6 +21,10 @@ export class NielsenConnector { this.nielsenHandler.updateMetadata(metadata); } + updateDCRContentMetadata(metadata: NielsenDCRContentMetadata): void { + this.nielsenHandler.updateDCRContentMetadata(metadata); + } + destroy() { this.nielsenHandler.destroy(); } From d0edda94ecb96043c00b0739cbc93376a9c6f8cd Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:42:11 +0200 Subject: [PATCH 41/55] add configuration parameter --- nielsen/src/integration/NielsenConnector.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nielsen/src/integration/NielsenConnector.ts b/nielsen/src/integration/NielsenConnector.ts index a912ec74..a53022c3 100644 --- a/nielsen/src/integration/NielsenConnector.ts +++ b/nielsen/src/integration/NielsenConnector.ts @@ -13,8 +13,14 @@ export class NielsenConnector { * @param instanceName User-defined string value for describing the player/site. * @param options Additional options. */ - constructor(player: ChromelessPlayer, appId: string, instanceName: string, options?: NielsenOptions) { - this.nielsenHandler = new NielsenHandler(player, appId, instanceName, options); + constructor( + player: ChromelessPlayer, + appId: string, + instanceName: string, + options?: NielsenOptions, + configuration?: NielsenConfiguration + ) { + this.nielsenHandler = new NielsenHandler(player, appId, instanceName, options, configuration); } updateMetadata(metadata: { [key: string]: string }): void { From 674e01a59983d3b2201c8efd4c41eecf9e8acebd Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:42:37 +0200 Subject: [PATCH 42/55] add import --- nielsen/src/integration/NielsenConnector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenConnector.ts b/nielsen/src/integration/NielsenConnector.ts index a53022c3..23c4177b 100644 --- a/nielsen/src/integration/NielsenConnector.ts +++ b/nielsen/src/integration/NielsenConnector.ts @@ -1,5 +1,5 @@ import type { ChromelessPlayer } from 'theoplayer'; -import { NielsenOptions } from '../nielsen/Types'; +import { NielsenConfiguration, NielsenDCRContentMetadata, NielsenOptions } from '../nielsen/Types'; import { NielsenHandler } from './NielsenHandler'; export class NielsenConnector { From f1cae07426972393dcd3e0c7c9b4e6680e9f3f5f Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:43:10 +0200 Subject: [PATCH 43/55] edit paths --- nielsen/test/pages/main.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index e63b7dbb..b9ae454d 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -3,9 +3,9 @@ Connector test page - - - + + +
From 0e5d9bd44add7726c12f61777577723a1d4d3588 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:43:20 +0200 Subject: [PATCH 44/55] edit librarylocation path --- nielsen/test/pages/main.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index b9ae454d..f2b5f077 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -15,7 +15,7 @@ ui: { fluid: true }, - libraryLocation: "/node_modules/theoplayer/" + libraryLocation: "./../../../node_modules/theoplayer/" }); // From 0f55a077ddcb795674c49f2ddf0d2ad51d6b70a2 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 19:44:43 +0200 Subject: [PATCH 45/55] edit serve script --- nielsen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/package.json b/nielsen/package.json index f1fe65b0..da1ccbd4 100644 --- a/nielsen/package.json +++ b/nielsen/package.json @@ -20,7 +20,7 @@ "bundle": "rollup -c rollup.config.mjs", "watch": "npm run bundle -- --watch", "build": "npm run clean && npm run bundle", - "serve": "http-server", + "serve": "http-server ./.. -o /nielsen/test/pages/main.html", "test": "jest" }, "author": "THEO Technologies NV", From 84afa8d922bc48d5e67f2355b1e0254355535c91 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 20:13:54 +0200 Subject: [PATCH 46/55] set sessionInProgress to true regardless of DCR/DTVR config --- nielsen/src/integration/NielsenHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index dfbe10a3..758c61e6 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -271,8 +271,8 @@ export class NielsenHandler { private maybeSendPlayEvent(): void { if (this.sessionInProgress || Number.isNaN(this.duration)) return; + this.sessionInProgress = true; if (this.dtvrEnabled) { - this.sessionInProgress = true; const metadataObject = { channelName: this.player.src, length: this.duration From e3f66731f99b6dc28c20c1709a958d97adb34673 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 20:26:28 +0200 Subject: [PATCH 47/55] use Ad from AdBeginEvent --- nielsen/src/integration/NielsenHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 758c61e6..08ee663b 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -235,7 +235,7 @@ export class NielsenHandler { }; private onAdBegin = ({ ad }: AdEvent<'adbegin'>) => { - const currentAd = this.player.ads!.currentAds.filter((ad: Ad) => ad.type === 'linear'); // TODO check why we chose to not use the ad from the event payload + if (ad.type !== 'linear') return; const { adBreak } = ad; const { timeOffset } = adBreak; const offset = this.player.ads?.dai?.contentTimeForStreamTime(timeOffset) ?? timeOffset; @@ -249,7 +249,7 @@ export class NielsenHandler { this.nSdkInstance.ggPM('loadMetadata', dtvrAdMetadata); } if (this.dcrEnabled) { - const dcrAdMetadata = buildDCRAdMetadata(currentAd[0], this.country, this.duration); + const dcrAdMetadata = buildDCRAdMetadata(ad, this.country, this.duration); this.nSdkInstance.ggPM('loadMetadata', dcrAdMetadata); } }; From 2448e81a8d514480c7789d08de512cc614af6a1c Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:14:47 +0200 Subject: [PATCH 48/55] update README --- nielsen/README.md | 66 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/nielsen/README.md b/nielsen/README.md index d598a24f..2a7cfdfc 100644 --- a/nielsen/README.md +++ b/nielsen/README.md @@ -12,39 +12,79 @@ npm install @theoplayer/nielsen-connector-web ### Configuring the connector -Create the connector by providing the `THEOplayer` instance, the Nielsen App ID, the channelName for the asset -and optionally some Nielsen configuration. +Create the connector by providing the following mandatory parameters: + +- The `THEOplayer` instance +- the Nielsen App ID +- the channelName for the asset + +and optionally the following parameters: + +- a `NielsenOptions` object +- a `NielsenConfiguration` object (if none is provided, the default configuration disables DCR, enables DTVR and sets the country to US) ```js -import {NielsenConnector} from "../../dist/THEOplayerNielsenConnector"; +import { NielsenConnector } from '../../dist/THEOplayerNielsenConnector'; const appId = ''; const channelName = ''; -// Optional + +// Non-mandatory options const options: NielsenOptions = { - containerId: 'THEOplayer', + containerId: 'THEOplayer', optout: false -} +}; + +// Non-mandatory configuration (e.g. for DCR tracking with the Czech Republic SDK) +const configuration: NielsenConfiguration = { + country: NielsenCountry.CZ, + enableDTVR: false, + enableDCR: true +}; const nielsenConnector = new NielsenConnector(player, appId, channelName, options); ``` The `NielsenOptions` can have the following fields: | Key | Value | -|-----------------|-----------------------------------------------------------------| -| ` containerId ` | HTML DOM element id of the player container. | +| --------------- | --------------------------------------------------------------- | +| `containerId` | HTML DOM element id of the player container. | | ` nol_sdkDebug` | Enables Debug Mode which allows output to be viewed in console. | -| ` optout ` | Whether to opt-out of Nielsen Measurement. | +| `optout` | Whether to opt-out of Nielsen Measurement. | -### Passing metadata dynamically +### Passing metadata dynamically (DTVR) -The connector allows updating the current asset's metadata at any time: +The connector allows updating the current asset's metadata at any time. Note that Nielsen's [documentation]() prohibits updating of the values for `type`, `vidtype` or `assetid` parameters ```js const metadata = { ['channelName']: 'newChannelName', ['customTag1']: 'customValue1', - ['customTag2']: 'customValue2' -} + ['customTag2']: 'customValue2' +}; nielsenConnector.updateMetadata(metadata); ``` + +### Passing metadata when setting a source to the player (DCR) + +This can be achieved through the `updateDCRContentMetadata` method, e.g.: + +```js +const metadata: NielsenDCRContentMetadataCZ = { + assetid: 'cz-500358-98731568435405', + program: 'Animated Test Content', + title: 'Big Buck Bunny', + length: '596', + airdate: '20230620 20:00:00', + isfullepisode: true, + crossId1: '915 954 39504', + c2: 'p2, 651678089925925', + segB: '011', + adloadtype: AdLoadType.linear, + hasAds: HasAds.supports_ads +}; + +nielsenConnector.updateDCRContentMetadata(metadata); +``` + +Note that types are included in the package: `NielsenDCRContentMetadataUS`, `NielsenDCRContentMetadataCZ`. Please contact your THEO Technologies representative if you need support for another International DCR SDK. From 7f6391cddacb4cc190f8362b7a381883833710b9 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:15:24 +0200 Subject: [PATCH 49/55] typo --- nielsen/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/README.md b/nielsen/README.md index 2a7cfdfc..3b97a5a2 100644 --- a/nielsen/README.md +++ b/nielsen/README.md @@ -78,7 +78,7 @@ const metadata: NielsenDCRContentMetadataCZ = { airdate: '20230620 20:00:00', isfullepisode: true, crossId1: '915 954 39504', - c2: 'p2, 651678089925925', + c2: '651678089925925', segB: '011', adloadtype: AdLoadType.linear, hasAds: HasAds.supports_ads From 1005a44c1f47f5f517177ac29e872f7db5d52412 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:19:12 +0200 Subject: [PATCH 50/55] add necessary type exports --- nielsen/src/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nielsen/src/index.ts b/nielsen/src/index.ts index f2cadfba..9cf6bf32 100644 --- a/nielsen/src/index.ts +++ b/nielsen/src/index.ts @@ -1,2 +1,11 @@ export { NielsenConnector } from './integration/NielsenConnector'; -export { NielsenOptions, NielsenDCRContentMetadata, NielsenDCRContentMetadataCZ } from './nielsen/Types'; +export { + NielsenOptions, + NielsenDCRContentMetadata, + NielsenDCRContentMetadataCZ, + NielsenDCRContentMetadataUS, + NielsenConfiguration, + NielsenCountry, + AdLoadType, + HasAds +} from './nielsen/Types'; From 8abc60c7a1475bfb5b39d222841062302750ac1a Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:20:33 +0200 Subject: [PATCH 51/55] finish example page --- nielsen/test/pages/main.html | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index f2b5f077..edfba02f 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -39,21 +39,33 @@ "adloadtype": "2", "hasAds": "1" }; + const instanceName = "exampleInstanceName"; const options = { // containerId: 'THEOplayer', nol_sdkDebug: "debug" }; + const configuration = { + country: "CZ", + enableDTVR: false, + enableDCR: true + } const nielsenConnector = new THEOplayerNielsenConnector.NielsenConnector( player, appId, instanceName, - options + options, + configuration ); // Set a source that works with your connector + // player.source = {sources: [{ src: "https://www.nielseninternet.com/DTVR/RTVOD_(PC-FD)_C3/prog_index.m3u8" }]} + player.source = { - sources: [{ src: "https://www.nielseninternet.com/DTVR/RTVOD_(PC-FD)_C3/prog_index.m3u8" }] + sources: [{ src: "https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8" }] }; + + nielsenConnector.updateDCRContentMetadata(contentMetadataObjectCZ) + From e0e5fd45f7d55c76ca638b93b1a06e50d1631339 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:23:28 +0200 Subject: [PATCH 52/55] Add changeset --- .changeset/chilly-yaks-bake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilly-yaks-bake.md diff --git a/.changeset/chilly-yaks-bake.md b/.changeset/chilly-yaks-bake.md new file mode 100644 index 00000000..8c9db543 --- /dev/null +++ b/.changeset/chilly-yaks-bake.md @@ -0,0 +1,5 @@ +--- +"@theoplayer/nielsen-connector-web": minor +--- + +Add DCR support (CZ and US). From f1cbb0dfb5fc08662f7d5538d2a401a42b750a4b Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:36:52 +0200 Subject: [PATCH 53/55] edit example metadata --- nielsen/test/pages/main.html | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/nielsen/test/pages/main.html b/nielsen/test/pages/main.html index edfba02f..8cb95a65 100644 --- a/nielsen/test/pages/main.html +++ b/nielsen/test/pages/main.html @@ -24,21 +24,18 @@ const appId = "XXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX"; const instanceName = "instanceName"; const contentMetadataObjectCZ = { - "assetid": "cz-500358-98731568435405", - "type": "content", - "program": "Animated Test Content", - "title": "Big Buck Bunny", - "length": "596", - "airdate": "20230620 20:00:00", - "isfullepisode": "y", - "crossId1": "915 954 39504", - "nol_c1": "p1,", - "nol_c2": "p2, 651678089925925", - "segB": "011", - "segC": "", - "adloadtype": "2", - "hasAds": "1" - }; + assetid: 'cz-500358-98731568435405', + program: 'Animated Test Content', + title: 'Big Buck Bunny', + length: '596', + airdate: '20230620 20:00:00', + isfullepisode: true, + crossId1: '915 954 39504', + c2: '651678089925925', + segB: '011', + adloadtype: "2", + hasAds: "2" + }; const instanceName = "exampleInstanceName"; const options = { // containerId: 'THEOplayer', From ecaa04bec63774a44b3c74ab7752c86fb3829e15 Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 22 Aug 2024 23:39:26 +0200 Subject: [PATCH 54/55] always include nol_c2 property --- nielsen/src/utils/Util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nielsen/src/utils/Util.ts b/nielsen/src/utils/Util.ts index a7b70a15..a0d5da2c 100644 --- a/nielsen/src/utils/Util.ts +++ b/nielsen/src/utils/Util.ts @@ -45,12 +45,12 @@ export function buildDCRContentMetadata( const dcrContentMetadataCZ: DCRContentMetadataCZ = { ...dcrContentMetadata, ['crossId1']: crossId1, + ['nol_c2']: `p2,${c2 ?? ''}`, segB: segB, segC: segC ?? '', hasAds: hasAds }; if (c1) dcrContentMetadataCZ['nol_c1'] = `p1,${c1}`; - if (c2) dcrContentMetadataCZ['nol_c2'] = `p2,${c2}`; return dcrContentMetadataCZ; } if (country === NielsenCountry.US) { From 44bc3e530aa0656a7a5794be5887d266c16e964a Mon Sep 17 00:00:00 2001 From: Wonne Joosen Date: Thu, 29 Aug 2024 13:42:03 +0200 Subject: [PATCH 55/55] only print logs if nol_sdkDebug === "debug" --- nielsen/src/integration/NielsenHandler.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nielsen/src/integration/NielsenHandler.ts b/nielsen/src/integration/NielsenHandler.ts index 08ee663b..9d14a7c8 100644 --- a/nielsen/src/integration/NielsenHandler.ts +++ b/nielsen/src/integration/NielsenHandler.ts @@ -28,6 +28,8 @@ const EMSG_PAYLOAD_SUFFIX = 'payload='; export class NielsenHandler { private player: ChromelessPlayer; + private debug: boolean; + private dcrEnabled: boolean; private dtvrEnabled: boolean; private country: NielsenCountry = NielsenCountry.US; @@ -51,6 +53,7 @@ export class NielsenHandler { configuration?: NielsenConfiguration ) { this.player = player; + this.debug = options?.nol_sdkDebug === 'debug' ? true : false; this.dcrEnabled = configuration?.enableDCR ?? false; this.dtvrEnabled = configuration?.enableDTVR ?? true; this.country = configuration?.country ?? NielsenCountry.US; @@ -63,7 +66,8 @@ export class NielsenHandler { switch (this.country) { case NielsenCountry.US: { const { type, vidtype, assetid, ...updateableParameters } = metadata; - console.log(`[NIELSEN] updateMetadata: ${{ type, vidtype, assetid }} will not be updated`); + if (this.debug) + console.log(`[NIELSEN] updateMetadata: ${{ type, vidtype, assetid }} will not be updated`); this.nSdkInstance.ggPM('updateMetadata', updateableParameters); break; }