Skip to content

Commit

Permalink
Merge pull request #4 from THEOplayer/nielsen
Browse files Browse the repository at this point in the history
Nielsen
  • Loading branch information
georgechoustoulakis authored Feb 28, 2024
2 parents d0cd382 + 76bdc9e commit 8bea253
Show file tree
Hide file tree
Showing 27 changed files with 506 additions and 121 deletions.
5 changes: 5 additions & 0 deletions .changeset/lucky-wolves-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@theoplayer/nielsen-connector-web": minor
---

Updated to be compatible with THEOplayer `6.X`.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules/

# Generated by MacOS
.DS_Store
1 change: 1 addition & 0 deletions .idea/web-connectors.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 0 additions & 59 deletions conviva/BUILD.md

This file was deleted.

File renamed without changes.
1 change: 1 addition & 0 deletions conviva/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dist/",
"CHANGELOG.md",
"README.md",
"LICENSE.md",
"package.json"
],
"dependencies": {
Expand Down
10 changes: 10 additions & 0 deletions nielsen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Node artifact files
node_modules/
lib/
dist/

# Generated by MacOS
.DS_Store

# Generated by Windows
Thumbs.db
7 changes: 7 additions & 0 deletions nielsen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @theoplayer/nielsen-connector-web

## 1.0.0

### ✨ Features

- Initial release
File renamed without changes.
50 changes: 50 additions & 0 deletions nielsen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Nielsen Web Connector

A connector implementing Nielsen with THEOplayer.

## Installation

```sh
npm install @theoplayer/nielsen-connector-web
```

## Usage

### 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.

```js
import {NielsenConnector} from "../../dist/THEOplayerNielsenConnector";

const appId = '<your app ID>';
const channelName = '<your channel name>';
// Optional
const options: NielsenOptions = {
containerId: 'THEOplayer',
optout: false
}
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. |
| ` nol_sdkDebug` | Enables Debug Mode which allows output to be viewed in console. |
| ` optout ` | Whether to opt-out of Nielsen Measurement. |

### Passing metadata dynamically

The connector allows updating the current asset's metadata at any time:

```js
const metadata = {
['channelName']: 'newChannelName',
['customTag1']: 'customValue1',
['customTag2']: 'customValue2'
}
nielsenConnector.updateMetadata(metadata);
```
4 changes: 4 additions & 0 deletions nielsen/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node"
};
37 changes: 37 additions & 0 deletions nielsen/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@theoplayer/nielsen-connector-web",
"version": "1.0.0",
"description": "A connector implementing Nielsen with THEOplayer.",
"main": "dist/THEOplayerNielsenConnector.umd.js",
"module": "dist/THEOplayerNielsenConnector.esm.js",
"types": "dist/THEOplayerNielsenConnector.d.ts",
"exports": {
".": {
"types": "./dist/THEOplayerNielsenConnector.d.ts",
"import": "./dist/THEOplayerNielsenConnector.esm.js",
"require": "./dist/THEOplayerNielsenConnector.umd.js"
},
"./dist/*": "./dist/*",
"./package": "./package.json",
"./package.json": "./package.json"
},
"scripts": {
"clean": "rimraf lib dist",
"bundle": "rollup -c rollup.config.mjs",
"build": "npm run clean && npm run bundle",
"serve": "http-server",
"test": "jest"
},
"author": "THEO Technologies NV",
"license": "MIT",
"files": [
"dist/",
"CHANGELOG.md",
"README.md",
"LICENSE.md",
"package.json"
],
"peerDependencies": {
"theoplayer": "^5.0.0 || ^6.0.0"
}
}
14 changes: 14 additions & 0 deletions nielsen/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {getSharedBuildConfiguration} from "../tools/build.mjs";
import fs from "node:fs";

const {version} = JSON.parse(fs.readFileSync("./package.json", "utf8"));

const fileName = "nielsen-connector";
const globalName = "THEOplayerNielsenConnector";

const banner = `
/**
* Nielsen Web Connector v${version}
*/`.trim();

export default getSharedBuildConfiguration(fileName, globalName, banner);
2 changes: 2 additions & 0 deletions nielsen/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { NielsenConnector } from './integration/NielsenConnector';
export { NielsenOptions } from './nielsen/Types';
27 changes: 27 additions & 0 deletions nielsen/src/integration/NielsenConnector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ChromelessPlayer } from 'theoplayer';
import { NielsenOptions } from '../nielsen/Types';
import { NielsenHandler } from './NielsenHandler';

export class NielsenConnector {
private nielsenHandler: NielsenHandler;

/**
* Create NielsenConnector
*
* @param player THEOplayer instance.
* @param appId UniqueID assigned to player/site.
* @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);
}

updateMetadata(metadata: { [key: string]: string }): void {
this.nielsenHandler.updateMetadata(metadata);
}

destroy() {
this.nielsenHandler.destroy();
}
}
151 changes: 151 additions & 0 deletions nielsen/src/integration/NielsenHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Ad, AddTrackEvent, ChromelessPlayer, TextTrack, TextTrackEnterCueEvent, VolumeChangeEvent } from 'theoplayer';
import { loadNielsenLibrary } from '../nielsen/NOLBUNDLE';
import { AdMetadata, ContentMetadata, NielsenOptions } from '../nielsen/Types';
import { getAdType } from '../utils/Util';

export class NielsenHandler {
private player: ChromelessPlayer;

private nSdkInstance: any;

private sessionInProgress: boolean = false;

private duration: number = NaN;

constructor(player: ChromelessPlayer, appId: string, instanceName: string, options?: NielsenOptions) {
this.player = player;
this.nSdkInstance = loadNielsenLibrary(appId, instanceName, options);

this.addEventListeners();
}

updateMetadata(metadata: { [key: string]: string }): void {
this.nSdkInstance.ggPM('updateMetadata', metadata);
}

private addEventListeners(): void {
this.player.addEventListener('play', this.onPlay);
this.player.addEventListener('ended', this.onEnd);
this.player.addEventListener('sourcechange', this.onSourceChange);
this.player.addEventListener('volumechange', this.onVolumeChange);
this.player.addEventListener('loadedmetadata', this.onLoadMetadata);
this.player.addEventListener('durationchange', this.onDurationChange);

this.player.textTracks.addEventListener('addtrack', this.onAddTrack);

if (this.player.ads) {
this.player.ads.addEventListener('adbegin', this.onAdBegin);
}

window.addEventListener('beforeunload', this.onEnd);
}

private removeEventListeners(): void {
this.player.removeEventListener('play', this.onPlay);
this.player.removeEventListener('ended', this.onEnd);
this.player.removeEventListener('sourcechange', this.onSourceChange);
this.player.removeEventListener('volumechange', this.onVolumeChange);
this.player.removeEventListener('loadedmetadata', this.onLoadMetadata);
this.player.removeEventListener('durationchange', this.onDurationChange);

this.player.textTracks.removeEventListener('addtrack', this.onAddTrack);

if (this.player.ads) {
this.player.ads.removeEventListener('adbegin', this.onAdBegin);
}

window.removeEventListener('beforeunload', this.onEnd);
}

private onPlay = () => {
this.maybeSendPlayEvent();
};

private onEnd = () => {
this.endSession();
};

private onSourceChange = () => {
this.duration = NaN;
this.endSession();
};

private onVolumeChange = (event: VolumeChangeEvent) => {
const volumeLevel = this.player.muted ? 0 : event.volume * 100;
this.nSdkInstance.ggPM('setVolume', volumeLevel);
};

private onDurationChange = () => {
this.duration = this.player.duration;
this.maybeSendPlayEvent();
};

private onLoadMetadata = () => {
const data: ContentMetadata = {
type: 'content',
adModel: '1' // Always '1' for DTVR
};
this.nSdkInstance.ggPM('loadMetadata', data);
};

private onAddTrack = (event: AddTrackEvent) => {
if (event.track.kind === 'metadata') {
const track = event.track as TextTrack;
if (track.type === 'id3') {
// || track.type === 'emsg') {
if (track.mode === 'disabled') {
track.mode = 'hidden';
}
track.addEventListener('entercue', this.onEnterCue);
}
}
};

private onEnterCue = (event: TextTrackEnterCueEvent) => {
const { cue } = event;
if (cue.track.type === 'id3') {
if (cue.content.id === 'PRIV' && cue.content.ownerIdentifier.indexOf('www.nielsen.com') !== -1) {
this.nSdkInstance.ggPM('sendID3', cue.content.ownerIdentifier);
}
} else {
// TODO emsg is not supported for now
}
};

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);
};

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);
}
}

private endSession(): void {
if (this.sessionInProgress) {
this.sessionInProgress = false;
this.nSdkInstance.ggPM('end', this.getPlayHeadPosition());
}
}

private getPlayHeadPosition(): string {
return Math.floor(this.player.currentTime).toString();
}

destroy() {
this.removeEventListeners();
this.endSession();
}
}
Loading

0 comments on commit 8bea253

Please sign in to comment.