@glomex/integration-analytics 1.1480.0 → 1.1481.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -11
- package/dist/base-event-mapper.d.ts +56 -0
- package/dist/base-event-mapper.js +313 -0
- package/dist/comscore/comscore-event-mapper.d.ts +26 -0
- package/dist/comscore/comscore-event-mapper.js +218 -0
- package/dist/comscore/comscore-metadata.d.ts +15 -0
- package/dist/comscore/comscore-metadata.js +58 -0
- package/dist/comscore/comscore-sdk-loader.d.ts +12 -0
- package/dist/comscore/comscore-sdk-loader.js +35 -0
- package/dist/comscore/comscore-types.d.ts +11 -0
- package/dist/comscore/comscore-types.js +6 -0
- package/dist/comscore/index.d.ts +72 -0
- package/dist/comscore/index.js +101 -0
- package/dist/nielsen/index.d.ts +70 -0
- package/dist/nielsen/index.js +87 -0
- package/dist/nielsen/nielsen-event-mapper.d.ts +23 -0
- package/dist/nielsen/nielsen-event-mapper.js +167 -0
- package/dist/nielsen/nielsen-metadata.d.ts +35 -0
- package/dist/nielsen/nielsen-metadata.js +134 -0
- package/dist/nielsen/nielsen-sdk-loader.d.ts +13 -0
- package/dist/nielsen/nielsen-sdk-loader.js +70 -0
- package/dist/nielsen/nielsen-types.d.ts +128 -0
- package/dist/nielsen/nielsen-types.js +4 -0
- package/dist/npaw/index.d.ts +7 -291
- package/dist/npaw/index.js +32 -313
- package/dist/npaw/npaw-turbo-player-ad-adapter.d.ts +43 -0
- package/dist/npaw/npaw-turbo-player-ad-adapter.js +142 -0
- package/dist/npaw/npaw-turbo-player-adapter.d.ts +47 -0
- package/dist/npaw/npaw-turbo-player-adapter.js +136 -0
- package/dist/npaw/npaw-types.d.ts +202 -0
- package/dist/npaw/npaw-types.js +6 -0
- package/dist/npaw/package-info.d.ts +2 -0
- package/dist/npaw/package-info.js +3 -0
- package/dist/sensic/index.d.ts +89 -0
- package/dist/sensic/index.js +103 -0
- package/dist/sensic/sensic-event-mapper.d.ts +25 -0
- package/dist/sensic/sensic-event-mapper.js +147 -0
- package/dist/sensic/sensic-metadata.d.ts +34 -0
- package/dist/sensic/sensic-metadata.js +102 -0
- package/dist/sensic/sensic-sdk-loader.d.ts +16 -0
- package/dist/sensic/sensic-sdk-loader.js +102 -0
- package/dist/sensic/sensic-types.d.ts +80 -0
- package/dist/sensic/sensic-types.js +6 -0
- package/package.json +19 -8
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { PlaybackMode } from '@glomex/integration-web-component';
|
|
2
|
+
import { BaseEventMapper } from '../base-event-mapper.js';
|
|
3
|
+
export class ComscoreEventMapper extends BaseEventMapper {
|
|
4
|
+
#analytics;
|
|
5
|
+
#streamingAnalytics;
|
|
6
|
+
#contentMetadata;
|
|
7
|
+
#wasInAd = false;
|
|
8
|
+
constructor(integration, sdkReadyPromise, onWarn) {
|
|
9
|
+
super(integration, onWarn);
|
|
10
|
+
sdkReadyPromise.then((result) => {
|
|
11
|
+
if (this.isStopped)
|
|
12
|
+
return;
|
|
13
|
+
if (!result)
|
|
14
|
+
return;
|
|
15
|
+
this.#analytics = result.analytics;
|
|
16
|
+
this.#contentMetadata = result.contentMetadata;
|
|
17
|
+
const mediaType = this.#determineContentMediaType();
|
|
18
|
+
this.#contentMetadata.setMediaType(mediaType);
|
|
19
|
+
this.markSdkReady();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
onContentStart() {
|
|
23
|
+
if (!this.#analytics)
|
|
24
|
+
return;
|
|
25
|
+
this.#streamingAnalytics = new this.#analytics.StreamingAnalytics();
|
|
26
|
+
this.#streamingAnalytics.createPlaybackSession();
|
|
27
|
+
this.#streamingAnalytics.setMediaPlayerName('turbo-player');
|
|
28
|
+
this.#streamingAnalytics.setMediaPlayerVersion(this.integration.version);
|
|
29
|
+
if (this.#contentMetadata) {
|
|
30
|
+
this.#streamingAnalytics.setMetadata(this.#contentMetadata);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
onContentImpression() {
|
|
34
|
+
if (!this.#analytics || !this.#streamingAnalytics)
|
|
35
|
+
return;
|
|
36
|
+
// After preroll ends, we need to restore content metadata
|
|
37
|
+
if (this.#wasInAd) {
|
|
38
|
+
if (this.#contentMetadata) {
|
|
39
|
+
this.#streamingAnalytics.setMetadata(this.#contentMetadata);
|
|
40
|
+
}
|
|
41
|
+
this.#wasInAd = false;
|
|
42
|
+
}
|
|
43
|
+
this.#updatePlaybackPosition();
|
|
44
|
+
this.#streamingAnalytics.notifyPlay();
|
|
45
|
+
}
|
|
46
|
+
onContentPlay() {
|
|
47
|
+
if (!this.#analytics || !this.#streamingAnalytics)
|
|
48
|
+
return;
|
|
49
|
+
if (this.integration.seeking)
|
|
50
|
+
return;
|
|
51
|
+
// After ad ends, we need to restore content metadata
|
|
52
|
+
if (this.#wasInAd) {
|
|
53
|
+
if (this.#contentMetadata) {
|
|
54
|
+
this.#streamingAnalytics.setMetadata(this.#contentMetadata);
|
|
55
|
+
}
|
|
56
|
+
this.#updatePlaybackPosition();
|
|
57
|
+
this.#wasInAd = false;
|
|
58
|
+
}
|
|
59
|
+
this.#streamingAnalytics.notifyPlay();
|
|
60
|
+
}
|
|
61
|
+
onContentPause() {
|
|
62
|
+
if (!this.#streamingAnalytics)
|
|
63
|
+
return;
|
|
64
|
+
if (this.integration.seeking)
|
|
65
|
+
return;
|
|
66
|
+
this.#streamingAnalytics.notifyPause();
|
|
67
|
+
}
|
|
68
|
+
onContentSeeking() {
|
|
69
|
+
if (!this.#streamingAnalytics)
|
|
70
|
+
return;
|
|
71
|
+
this.#streamingAnalytics.notifySeekStart();
|
|
72
|
+
}
|
|
73
|
+
onContentSeeked() {
|
|
74
|
+
if (!this.#streamingAnalytics)
|
|
75
|
+
return;
|
|
76
|
+
this.#updatePlaybackPosition();
|
|
77
|
+
if (!this.integration.paused) {
|
|
78
|
+
this.#streamingAnalytics.notifyPlay();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
onContentBufferingStart() {
|
|
82
|
+
if (!this.#streamingAnalytics)
|
|
83
|
+
return;
|
|
84
|
+
if (this.integration.seeking)
|
|
85
|
+
return;
|
|
86
|
+
this.#streamingAnalytics.notifyBufferStart();
|
|
87
|
+
}
|
|
88
|
+
onContentBufferingEnd() {
|
|
89
|
+
if (!this.#streamingAnalytics)
|
|
90
|
+
return;
|
|
91
|
+
if (this.integration.seeking)
|
|
92
|
+
return;
|
|
93
|
+
this.#streamingAnalytics.notifyBufferStop();
|
|
94
|
+
}
|
|
95
|
+
onContentEnded() {
|
|
96
|
+
if (!this.#streamingAnalytics)
|
|
97
|
+
return;
|
|
98
|
+
this.#streamingAnalytics.notifyEnd();
|
|
99
|
+
}
|
|
100
|
+
onAdImpression() {
|
|
101
|
+
if (!this.#analytics || !this.#streamingAnalytics)
|
|
102
|
+
return;
|
|
103
|
+
const ad = this.integration.currentAd;
|
|
104
|
+
if (!ad)
|
|
105
|
+
return;
|
|
106
|
+
const mediaType = this.#determineAdMediaType(ad.breakName);
|
|
107
|
+
const adMetadata = new this.#analytics.StreamingAnalytics.AdvertisementMetadata();
|
|
108
|
+
const adId = ad.universalAdIds?.[0]?.id || ad.id;
|
|
109
|
+
if (adId) {
|
|
110
|
+
adMetadata.setUniqueId(adId);
|
|
111
|
+
}
|
|
112
|
+
if (ad.duration) {
|
|
113
|
+
adMetadata.setLength(ad.duration);
|
|
114
|
+
}
|
|
115
|
+
adMetadata.setMediaType(mediaType);
|
|
116
|
+
if (this.#contentMetadata) {
|
|
117
|
+
adMetadata.setRelatedContentMetadata(this.#contentMetadata);
|
|
118
|
+
}
|
|
119
|
+
this.#streamingAnalytics.setMetadata(adMetadata);
|
|
120
|
+
this.#streamingAnalytics.notifyPlay();
|
|
121
|
+
}
|
|
122
|
+
onAdPaused() {
|
|
123
|
+
if (!this.#streamingAnalytics || !this.isInAd)
|
|
124
|
+
return;
|
|
125
|
+
this.#streamingAnalytics.notifyPause();
|
|
126
|
+
}
|
|
127
|
+
onAdResumed() {
|
|
128
|
+
if (!this.#streamingAnalytics || !this.isInAd)
|
|
129
|
+
return;
|
|
130
|
+
this.#streamingAnalytics.notifyPlay();
|
|
131
|
+
}
|
|
132
|
+
onAdBufferingStart() {
|
|
133
|
+
if (!this.#streamingAnalytics || !this.isInAd)
|
|
134
|
+
return;
|
|
135
|
+
this.#streamingAnalytics.notifyBufferStart();
|
|
136
|
+
}
|
|
137
|
+
onAdBufferingEnd() {
|
|
138
|
+
if (!this.#streamingAnalytics || !this.isInAd)
|
|
139
|
+
return;
|
|
140
|
+
this.#streamingAnalytics.notifyBufferStop();
|
|
141
|
+
}
|
|
142
|
+
onAdEnd() {
|
|
143
|
+
if (!this.#streamingAnalytics)
|
|
144
|
+
return;
|
|
145
|
+
this.#streamingAnalytics.notifyEnd();
|
|
146
|
+
this.#wasInAd = true;
|
|
147
|
+
}
|
|
148
|
+
#updatePlaybackPosition() {
|
|
149
|
+
if (!this.#streamingAnalytics)
|
|
150
|
+
return;
|
|
151
|
+
if (this.#isLive) {
|
|
152
|
+
this.#updateDvrWindow();
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.#streamingAnalytics.startFromPosition(this.#currentTimeInMilliseconds);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
#updateDvrWindow() {
|
|
159
|
+
if (!this.#streamingAnalytics)
|
|
160
|
+
return;
|
|
161
|
+
const { start, end } = this.integration.seekRange;
|
|
162
|
+
if (start === undefined || end === undefined)
|
|
163
|
+
return;
|
|
164
|
+
const dvrWindowLengthMs = (end - start) * 1000;
|
|
165
|
+
const currentTime = this.integration.currentTime;
|
|
166
|
+
const dvrWindowOffsetMs = (end - currentTime) * 1000;
|
|
167
|
+
this.#streamingAnalytics.setDvrWindowLength(dvrWindowLengthMs);
|
|
168
|
+
this.#streamingAnalytics.startFromDvrWindowOffset(dvrWindowOffsetMs);
|
|
169
|
+
}
|
|
170
|
+
get #isLive() {
|
|
171
|
+
return this.integration.source?.playbackMode === PlaybackMode.LIVE;
|
|
172
|
+
}
|
|
173
|
+
get #content() {
|
|
174
|
+
return this.integration.content;
|
|
175
|
+
}
|
|
176
|
+
get #duration() {
|
|
177
|
+
const content = this.#content;
|
|
178
|
+
return Math.floor(content?.duration === undefined ? this.integration.duration * 1000 : content.duration * 1000);
|
|
179
|
+
}
|
|
180
|
+
get #currentTimeInMilliseconds() {
|
|
181
|
+
if (this.#isLive) {
|
|
182
|
+
return this.integration.wallClockTime * 1000;
|
|
183
|
+
}
|
|
184
|
+
return this.integration.currentTime * 1000;
|
|
185
|
+
}
|
|
186
|
+
#determineContentMediaType() {
|
|
187
|
+
// biome-ignore lint/style/noNonNullAssertion: #analytics is guaranteed to exist when this method is called
|
|
188
|
+
const ContentType = this.#analytics.StreamingAnalytics.ContentMetadata.ContentType;
|
|
189
|
+
if (this.#isLive) {
|
|
190
|
+
return ContentType.LIVE;
|
|
191
|
+
}
|
|
192
|
+
// Content is longer than 600000 ms => 600 seconds => 10 minutes
|
|
193
|
+
if (this.#duration >= 600000) {
|
|
194
|
+
return ContentType.LONG_FORM_ON_DEMAND;
|
|
195
|
+
}
|
|
196
|
+
return ContentType.SHORT_FORM_ON_DEMAND;
|
|
197
|
+
}
|
|
198
|
+
#determineAdMediaType(adBreakName) {
|
|
199
|
+
const AdvertisementType =
|
|
200
|
+
// biome-ignore lint/style/noNonNullAssertion: #analytics is guaranteed to exist when this method is called
|
|
201
|
+
this.#analytics.StreamingAnalytics.AdvertisementMetadata.AdvertisementType;
|
|
202
|
+
if (this.#isLive) {
|
|
203
|
+
return AdvertisementType.LIVE;
|
|
204
|
+
}
|
|
205
|
+
if (adBreakName === 'preroll') {
|
|
206
|
+
return AdvertisementType.ON_DEMAND_PRE_ROLL;
|
|
207
|
+
}
|
|
208
|
+
if (adBreakName === 'postroll') {
|
|
209
|
+
return AdvertisementType.ON_DEMAND_POST_ROLL;
|
|
210
|
+
}
|
|
211
|
+
return AdvertisementType.ON_DEMAND_MID_ROLL;
|
|
212
|
+
}
|
|
213
|
+
onDestroy() {
|
|
214
|
+
if (this.#streamingAnalytics && this.contentStarted) {
|
|
215
|
+
this.#streamingAnalytics.notifyEnd();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
import type { Analytics, ContentMetadataInstance } from './comscore-types.js';
|
|
3
|
+
export interface ContentMetadataConfig {
|
|
4
|
+
integration: IntegrationElement;
|
|
5
|
+
publisherName: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build Comscore content metadata from integration data.
|
|
9
|
+
* Sets up all the standard metadata fields based on the content and source information.
|
|
10
|
+
*
|
|
11
|
+
* @param analytics - The Comscore Analytics instance
|
|
12
|
+
* @param config - Configuration containing the integration element
|
|
13
|
+
* @returns The configured ContentMetadata instance
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildContentMetadata(analytics: Analytics, config: ContentMetadataConfig): ContentMetadataInstance;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { PlaybackMode } from '@glomex/integration-web-component';
|
|
2
|
+
/**
|
|
3
|
+
* Build Comscore content metadata from integration data.
|
|
4
|
+
* Sets up all the standard metadata fields based on the content and source information.
|
|
5
|
+
*
|
|
6
|
+
* @param analytics - The Comscore Analytics instance
|
|
7
|
+
* @param config - Configuration containing the integration element
|
|
8
|
+
* @returns The configured ContentMetadata instance
|
|
9
|
+
*/
|
|
10
|
+
export function buildContentMetadata(analytics, config) {
|
|
11
|
+
const { integration } = config;
|
|
12
|
+
const contentMetadata = new analytics.StreamingAnalytics.ContentMetadata();
|
|
13
|
+
const content = integration.content;
|
|
14
|
+
const source = integration.source;
|
|
15
|
+
const isLive = source?.playbackMode === PlaybackMode.LIVE;
|
|
16
|
+
const contentId = content?.additionalIds?.originId || content?.id || '';
|
|
17
|
+
const mediaId = isLive ? contentId : source?.id || contentId;
|
|
18
|
+
const livestreamId = isLive ? contentId : content?.liveOnDemandChannel?.id;
|
|
19
|
+
const programId = content?.show?.id ||
|
|
20
|
+
content?.competition?.id ||
|
|
21
|
+
content?.compilation?.id ||
|
|
22
|
+
content?.additionalIds?.originId ||
|
|
23
|
+
content?.id;
|
|
24
|
+
const programTitle = content?.show?.name ||
|
|
25
|
+
content?.competition?.name ||
|
|
26
|
+
content?.compilation?.name ||
|
|
27
|
+
content?.title;
|
|
28
|
+
const duration = Math.floor(content?.duration === undefined ? integration.duration * 1000 : content.duration * 1000);
|
|
29
|
+
contentMetadata.setUniqueId(isLive ? livestreamId || mediaId : mediaId);
|
|
30
|
+
contentMetadata.setLength(isLive ? 0 : duration);
|
|
31
|
+
if (!isLive && programTitle) {
|
|
32
|
+
contentMetadata.setProgramTitle(programTitle);
|
|
33
|
+
}
|
|
34
|
+
if (isLive) {
|
|
35
|
+
contentMetadata.setProgramId('1');
|
|
36
|
+
}
|
|
37
|
+
else if (programId) {
|
|
38
|
+
contentMetadata.setProgramId(programId);
|
|
39
|
+
}
|
|
40
|
+
if (!isLive && content?.title) {
|
|
41
|
+
contentMetadata.setEpisodeTitle(content.title);
|
|
42
|
+
}
|
|
43
|
+
if (!isLive && content?.show?.seasonNumber) {
|
|
44
|
+
contentMetadata.setEpisodeSeasonNumber(String(content.show.seasonNumber));
|
|
45
|
+
}
|
|
46
|
+
if (!isLive && content?.show?.episodeNumber) {
|
|
47
|
+
contentMetadata.setEpisodeNumber(String(content.show.episodeNumber));
|
|
48
|
+
}
|
|
49
|
+
if (!isLive && content?.additionalIds?.externalId) {
|
|
50
|
+
contentMetadata.setEpisodeId(content.additionalIds.externalId);
|
|
51
|
+
}
|
|
52
|
+
if (content?.channel?.name) {
|
|
53
|
+
contentMetadata.setStationTitle(content.channel.name);
|
|
54
|
+
}
|
|
55
|
+
contentMetadata.setPublisherName(config.publisherName);
|
|
56
|
+
contentMetadata.classifyAsCompleteEpisode(!isLive);
|
|
57
|
+
return contentMetadata;
|
|
58
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Analytics } from './comscore-types.js';
|
|
2
|
+
export interface InitializeComscoreOptions {
|
|
3
|
+
publisherId: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Initialize Comscore analytics SDK.
|
|
8
|
+
* Dynamically imports the SDK and configures it with the provided publisher ID.
|
|
9
|
+
*
|
|
10
|
+
* @returns The Comscore Analytics instance, or undefined if initialization failed
|
|
11
|
+
*/
|
|
12
|
+
export declare function initializeComscore(options: InitializeComscoreOptions): Promise<Analytics | undefined>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the Comscore namespace from globalThis
|
|
3
|
+
*/
|
|
4
|
+
function getComscoreNamespace() {
|
|
5
|
+
return globalThis.ns_;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Initialize Comscore analytics SDK.
|
|
9
|
+
* Dynamically imports the SDK and configures it with the provided publisher ID.
|
|
10
|
+
*
|
|
11
|
+
* @returns The Comscore Analytics instance, or undefined if initialization failed
|
|
12
|
+
*/
|
|
13
|
+
export async function initializeComscore(options) {
|
|
14
|
+
const { publisherId, debug } = options;
|
|
15
|
+
// Dynamic import - bundler will handle code splitting
|
|
16
|
+
const importedAnalytics = (await import('@comscore/analytics'));
|
|
17
|
+
const ns = getComscoreNamespace();
|
|
18
|
+
const analytics = importedAnalytics || ns?.analytics;
|
|
19
|
+
if (!analytics) {
|
|
20
|
+
throw new Error('Comscore analytics not available after dynamic import');
|
|
21
|
+
}
|
|
22
|
+
// Ensure to reuse existing partner configuration
|
|
23
|
+
if (analytics.configuration.getPartnerConfigurations().length === 0) {
|
|
24
|
+
analytics.PlatformApi.setPlatformApi(analytics.PlatformApi.PlatformApis.WebBrowser);
|
|
25
|
+
const publisherConfig = new analytics.configuration.PublisherConfiguration({
|
|
26
|
+
publisherId
|
|
27
|
+
});
|
|
28
|
+
analytics.configuration.addClient(publisherConfig);
|
|
29
|
+
if (debug) {
|
|
30
|
+
analytics.configuration.enableImplementationValidationMode();
|
|
31
|
+
}
|
|
32
|
+
analytics.start();
|
|
33
|
+
}
|
|
34
|
+
return analytics;
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comscore Streaming Analytics TypeScript Definitions
|
|
3
|
+
* Types imported from @comscore/analytics package
|
|
4
|
+
* @see https://www.npmjs.com/package/@comscore/analytics
|
|
5
|
+
*/
|
|
6
|
+
export type Analytics = typeof import('@comscore/analytics')['default'];
|
|
7
|
+
export type StreamingAnalyticsInstance = InstanceType<Analytics['StreamingAnalytics']>;
|
|
8
|
+
export type ContentMetadataInstance = InstanceType<Analytics['StreamingAnalytics']['ContentMetadata']>;
|
|
9
|
+
export interface ComscoreNamespace {
|
|
10
|
+
analytics: Analytics;
|
|
11
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
import type { ContentMetadataInstance } from './comscore-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for Comscore analytics integration
|
|
5
|
+
*/
|
|
6
|
+
export interface ComscoreOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Comscore publisher ID
|
|
9
|
+
* @example 'abc123'
|
|
10
|
+
*/
|
|
11
|
+
publisherId: string;
|
|
12
|
+
/**
|
|
13
|
+
* Publisher name (e.g. Joyn)
|
|
14
|
+
* @example 'Joyn'
|
|
15
|
+
*/
|
|
16
|
+
publisherName: string;
|
|
17
|
+
/**
|
|
18
|
+
* Enable debug mode for implementation validation
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Optional callback for warning messages. If not provided, warnings are logged to console.
|
|
24
|
+
*/
|
|
25
|
+
warnCallback?: (error: Error) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Configure content metadata before tracking.
|
|
28
|
+
* Called once per content when playback starts.
|
|
29
|
+
* Use `integration.content` to access content data.
|
|
30
|
+
*
|
|
31
|
+
* @param metadata - The pre-configured Comscore content metadata instance with default values
|
|
32
|
+
* @returns The metadata instance to use for tracking, or `null`/`undefined` to skip tracking this content
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* configureContentMetadata: (metadata) => {
|
|
37
|
+
* metadata.setGenreName('Drama');
|
|
38
|
+
* return metadata;
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
configureContentMetadata?: (metadata: ContentMetadataInstance) => ContentMetadataInstance | null | undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Connects Comscore Streaming Analytics to a turbo player integration.
|
|
46
|
+
*
|
|
47
|
+
* This function sets up automatic tracking of playback events including:
|
|
48
|
+
* - Content playback (play, pause, seek, buffer, end)
|
|
49
|
+
* - Advertisement playback (pre-roll, mid-roll, post-roll)
|
|
50
|
+
* - DVR window tracking for live content
|
|
51
|
+
*
|
|
52
|
+
* Tracking only occurs when the user has given consent (IAB TCF purpose 1 and vendor 77).
|
|
53
|
+
*
|
|
54
|
+
* @param integration - The turbo player integration element
|
|
55
|
+
* @param options - Comscore configuration options
|
|
56
|
+
* @returns A cleanup function to disconnect the analytics integration
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { connectToComscore } from '@glomex/integration-analytics/comscore';
|
|
61
|
+
*
|
|
62
|
+
* const disconnect = connectToComscore(integration, {
|
|
63
|
+
* publisherId: 'your-publisher-id',
|
|
64
|
+
* publisherName: 'Your Publisher Name',
|
|
65
|
+
* debug: true
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // When done, call disconnect to clean up
|
|
69
|
+
* disconnect();
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare const connectToComscore: (integration: IntegrationElement, options: ComscoreOptions) => (() => void);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { IntegrationEvent } from '@glomex/integration-web-component';
|
|
2
|
+
import { ComscoreEventMapper } from './comscore-event-mapper.js';
|
|
3
|
+
import { buildContentMetadata } from './comscore-metadata.js';
|
|
4
|
+
import { initializeComscore } from './comscore-sdk-loader.js';
|
|
5
|
+
/** IAB TCF vendor ID for Comscore */
|
|
6
|
+
const IAB_VENDOR_COMSCORE = '77';
|
|
7
|
+
/**
|
|
8
|
+
* Connects Comscore Streaming Analytics to a turbo player integration.
|
|
9
|
+
*
|
|
10
|
+
* This function sets up automatic tracking of playback events including:
|
|
11
|
+
* - Content playback (play, pause, seek, buffer, end)
|
|
12
|
+
* - Advertisement playback (pre-roll, mid-roll, post-roll)
|
|
13
|
+
* - DVR window tracking for live content
|
|
14
|
+
*
|
|
15
|
+
* Tracking only occurs when the user has given consent (IAB TCF purpose 1 and vendor 77).
|
|
16
|
+
*
|
|
17
|
+
* @param integration - The turbo player integration element
|
|
18
|
+
* @param options - Comscore configuration options
|
|
19
|
+
* @returns A cleanup function to disconnect the analytics integration
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { connectToComscore } from '@glomex/integration-analytics/comscore';
|
|
24
|
+
*
|
|
25
|
+
* const disconnect = connectToComscore(integration, {
|
|
26
|
+
* publisherId: 'your-publisher-id',
|
|
27
|
+
* publisherName: 'Your Publisher Name',
|
|
28
|
+
* debug: true
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // When done, call disconnect to clean up
|
|
32
|
+
* disconnect();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export const connectToComscore = (integration, options) => {
|
|
36
|
+
const { publisherId, debug, warnCallback, configureContentMetadata } = options;
|
|
37
|
+
let initializationPromise;
|
|
38
|
+
let currentMapper;
|
|
39
|
+
const onWarn = (error) => {
|
|
40
|
+
if (warnCallback) {
|
|
41
|
+
warnCallback(error);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
// biome-ignore lint: suspicious/noConsole
|
|
46
|
+
console.error(error);
|
|
47
|
+
}, 0);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const initializeComscoreSdk = () => {
|
|
51
|
+
if (initializationPromise)
|
|
52
|
+
return initializationPromise;
|
|
53
|
+
initializationPromise = initializeComscore({ publisherId, debug }).catch((error) => {
|
|
54
|
+
onWarn(error instanceof Error ? error : new Error(String(error)));
|
|
55
|
+
return undefined;
|
|
56
|
+
});
|
|
57
|
+
return initializationPromise;
|
|
58
|
+
};
|
|
59
|
+
const hasConsent = () => {
|
|
60
|
+
const hasPurpose1Consent = Boolean(integration.consent?.purposeConsents?.['1']);
|
|
61
|
+
const hasVendorConsent = Boolean(integration.consent?.vendorConsents?.[IAB_VENDOR_COMSCORE]);
|
|
62
|
+
return hasPurpose1Consent && hasVendorConsent;
|
|
63
|
+
};
|
|
64
|
+
const onContentSelect = () => {
|
|
65
|
+
if (currentMapper) {
|
|
66
|
+
currentMapper.destroy();
|
|
67
|
+
currentMapper = undefined;
|
|
68
|
+
}
|
|
69
|
+
if (!hasConsent())
|
|
70
|
+
return;
|
|
71
|
+
const { content } = integration;
|
|
72
|
+
if (!content)
|
|
73
|
+
return;
|
|
74
|
+
// Start initialization if not already done, but don't await - events will be queued
|
|
75
|
+
const sdkReadyPromise = initializeComscoreSdk();
|
|
76
|
+
currentMapper = new ComscoreEventMapper(integration, sdkReadyPromise.then((analytics) => {
|
|
77
|
+
if (!analytics)
|
|
78
|
+
return undefined;
|
|
79
|
+
let contentMetadata = buildContentMetadata(analytics, {
|
|
80
|
+
integration,
|
|
81
|
+
publisherName: options.publisherName
|
|
82
|
+
});
|
|
83
|
+
if (configureContentMetadata) {
|
|
84
|
+
contentMetadata = configureContentMetadata(contentMetadata);
|
|
85
|
+
}
|
|
86
|
+
if (contentMetadata == null) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
analytics,
|
|
91
|
+
contentMetadata
|
|
92
|
+
};
|
|
93
|
+
}), onWarn);
|
|
94
|
+
};
|
|
95
|
+
integration.addEventListener(IntegrationEvent.CONTENT_SELECT, onContentSelect);
|
|
96
|
+
return () => {
|
|
97
|
+
integration.removeEventListener(IntegrationEvent.CONTENT_SELECT, onContentSelect);
|
|
98
|
+
currentMapper?.destroy();
|
|
99
|
+
currentMapper = undefined;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type IntegrationElement } from '@glomex/integration-web-component';
|
|
2
|
+
export type { NielsenAdMetadata, NielsenAdMetadataBase, NielsenAdMetadataDE, NielsenContentMetadata, NielsenContentMetadataBase, NielsenContentMetadataDE } from './nielsen-types.js';
|
|
3
|
+
import type { NielsenAdMetadata, NielsenAdMetadataBase, NielsenContentMetadata, NielsenContentMetadataBase } from './nielsen-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for Nielsen provider
|
|
6
|
+
*/
|
|
7
|
+
export interface NielsenOptions {
|
|
8
|
+
/** Nielsen App ID */
|
|
9
|
+
appId: string;
|
|
10
|
+
/**
|
|
11
|
+
* Two-letter country code for loading the appropriate Nielsen data mapping.
|
|
12
|
+
* Country-specific metadata builders are used automatically.
|
|
13
|
+
* @example 'de' - Germany (uses AGF-specific custom variables)
|
|
14
|
+
*/
|
|
15
|
+
country: string;
|
|
16
|
+
/** Enable Nielsen SDK debug mode */
|
|
17
|
+
debug?: boolean;
|
|
18
|
+
/** Optional callback for warning messages */
|
|
19
|
+
warnCallback?: (error: Error) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Callback to extend or modify content metadata before tracking.
|
|
22
|
+
* Called once per content when playback starts.
|
|
23
|
+
* Use `integration.content` to access additional content data if needed.
|
|
24
|
+
*
|
|
25
|
+
* When `country` is specified, receives country-specific metadata as input.
|
|
26
|
+
* When `country` is not specified, receives base metadata.
|
|
27
|
+
*
|
|
28
|
+
* @param baseMetadata - Base metadata built from integration data containing `assetid`, `type`, `program`, `title`, and `length`
|
|
29
|
+
* @returns Extended metadata object with additional Nielsen-required fields, or `null`/`undefined` to skip tracking this content
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* configureContentMetadata: (baseMetadata) => ({
|
|
34
|
+
* ...baseMetadata,
|
|
35
|
+
* program: 'My Program',
|
|
36
|
+
* segB: 'category',
|
|
37
|
+
* segC: 'subcategory'
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
configureContentMetadata?: (baseMetadata: NielsenContentMetadataBase) => NielsenContentMetadata | null | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Callback to extend or modify ad metadata before tracking.
|
|
44
|
+
* Called on each linear ad impression (preroll, midroll, postroll).
|
|
45
|
+
* Use `integration.currentAd` to access additional ad data if needed.
|
|
46
|
+
*
|
|
47
|
+
* When `country` is specified, receives country-specific metadata as input.
|
|
48
|
+
* When `country` is not specified, receives base metadata.
|
|
49
|
+
*
|
|
50
|
+
* @param baseMetadata - Base metadata built from integration data containing `assetid` and `type` (preroll/midroll/postroll)
|
|
51
|
+
* @returns Extended metadata object with additional Nielsen-required fields
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* configureAdMetadata: (baseMetadata) => ({
|
|
56
|
+
* ...baseMetadata,
|
|
57
|
+
* title: 'Ad Title'
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
configureAdMetadata?: (baseMetadata: NielsenAdMetadataBase) => NielsenAdMetadata;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Connect to Nielsen tracking.
|
|
65
|
+
* Returns a cleanup function that should be called when tracking is no longer needed.
|
|
66
|
+
*
|
|
67
|
+
* Nielsen does not require consent checks and uses a synchronous stub pattern,
|
|
68
|
+
* so events can be sent immediately and are queued until the SDK loads.
|
|
69
|
+
*/
|
|
70
|
+
export declare const connectToNielsen: (integration: IntegrationElement, options: NielsenOptions) => (() => void);
|