@newrelic/video-core 4.1.8-beta → 5.0.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/CHANGELOG.md +65 -15
- package/README.md +628 -45
- package/dist/cjs/browser/index.js +3 -0
- package/dist/cjs/browser/index.js.LICENSE.txt +6 -0
- package/dist/cjs/browser/index.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.LICENSE.txt +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/vega/index.js +3 -0
- package/dist/cjs/vega/index.js.LICENSE.txt +6 -0
- package/dist/cjs/vega/index.js.map +1 -0
- package/dist/esm/browser/index.js +3 -0
- package/dist/esm/browser/index.js.LICENSE.txt +6 -0
- package/dist/esm/browser/index.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/vega/index.js +3 -0
- package/dist/esm/vega/index.js.LICENSE.txt +6 -0
- package/dist/esm/vega/index.js.map +1 -0
- package/dist/umd/nrvideo.min.js +1 -1
- package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
- package/dist/umd/nrvideo.min.js.map +1 -1
- package/package.json +31 -6
- package/src/{agent.js → browser/agent.js} +43 -8
- package/src/{harvestScheduler.js → browser/harvestScheduler.js} +87 -86
- package/src/browser/index.js +58 -0
- package/src/connectedDevice/connectedDeviceAgent.js +116 -0
- package/src/connectedDevice/connectedDeviceConstants.js +121 -0
- package/src/connectedDevice/connectedDeviceHarvester.js +522 -0
- package/src/connectedDevice/index.js +49 -0
- package/src/constants.js +8 -0
- package/src/core.js +4 -18
- package/src/eventAggregator.js +48 -0
- package/src/index.js +9 -10
- package/src/obfuscate.js +33 -0
- package/src/optimizedHttpClient.js +6 -2
- package/src/recordEvent.js +56 -49
- package/src/tracker.js +14 -4
- package/src/utils/eventBuilder.js +126 -0
- package/src/utils/harvestTimer.js +109 -0
- package/src/{utils.js → utils/index.js} +2 -2
- package/src/utils/qoeFilters.js +149 -0
- package/src/videoConfiguration.js +90 -7
- package/src/videotracker.js +100 -32
- package/src/videotrackerstate.js +156 -31
package/package.json
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/video-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "New Relic video tracking core library",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/esm/index.js",
|
|
10
|
+
"require": "./dist/cjs/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./browser": {
|
|
13
|
+
"import": "./dist/esm/browser/index.js",
|
|
14
|
+
"require": "./dist/cjs/browser/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./vega": {
|
|
17
|
+
"import": "./dist/esm/vega/index.js",
|
|
18
|
+
"require": "./dist/cjs/vega/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
7
22
|
"scripts": {
|
|
8
23
|
"build": "webpack --mode production",
|
|
9
24
|
"build:dev": "webpack --mode development",
|
|
@@ -26,21 +41,28 @@
|
|
|
26
41
|
"Bharat Sarsawat"
|
|
27
42
|
],
|
|
28
43
|
"license": "Apache-2.0",
|
|
44
|
+
"sideEffects": false,
|
|
29
45
|
"devDependencies": {
|
|
30
46
|
"@babel/core": "^7.24.5",
|
|
31
47
|
"@babel/plugin-transform-modules-commonjs": "^7.24.1",
|
|
32
|
-
"@newrelic/newrelic-oss-cli": "^0.1.2",
|
|
33
48
|
"@babel/preset-env": "^7.24.5",
|
|
34
49
|
"@babel/register": "^7.24.6",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
50
|
+
"@newrelic/newrelic-oss-cli": "^0.1.2",
|
|
51
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
52
|
+
"@semantic-release/commit-analyzer": "^12.0.0",
|
|
53
|
+
"@semantic-release/git": "^10.0.1",
|
|
54
|
+
"@semantic-release/github": "^10.0.3",
|
|
55
|
+
"@semantic-release/npm": "^13.1.5",
|
|
56
|
+
"@semantic-release/release-notes-generator": "^13.0.0",
|
|
57
|
+
"babel-jest": "^30.0.0",
|
|
37
58
|
"babel-loader": "^9.1.3",
|
|
38
59
|
"diff": "^5.0.0",
|
|
39
60
|
"jest": "^30.2.0",
|
|
40
61
|
"jest-environment-jsdom": "^30.2.0",
|
|
41
62
|
"jsdom": "^25.0.1",
|
|
42
|
-
"mocha": "^
|
|
43
|
-
"nyc": "^
|
|
63
|
+
"mocha": "^11.0.0",
|
|
64
|
+
"nyc": "^18.0.0",
|
|
65
|
+
"semantic-release": "^25.0.8",
|
|
44
66
|
"sinon": "^2.4.1",
|
|
45
67
|
"webpack": "^5.91.0",
|
|
46
68
|
"webpack-cli": "^4.9.2"
|
|
@@ -58,5 +80,8 @@
|
|
|
58
80
|
],
|
|
59
81
|
"publishConfig": {
|
|
60
82
|
"access": "public"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"terser-webpack-plugin": "^5.6.1"
|
|
61
86
|
}
|
|
62
87
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { HarvestScheduler } from "./harvestScheduler.js";
|
|
2
|
-
import { NrVideoEventAggregator } from "
|
|
3
|
-
import Log from "
|
|
4
|
-
import
|
|
2
|
+
import { NrVideoEventAggregator } from "../eventAggregator.js";
|
|
3
|
+
import Log from "../log.js";
|
|
4
|
+
import { registerHarvester } from "../recordEvent.js";
|
|
5
|
+
import {
|
|
6
|
+
bufferEventWithQoeDedup,
|
|
7
|
+
refreshQoeKpisInBuffer,
|
|
8
|
+
} from "../utils/qoeFilters";
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Enhanced video analytics agent with HarvestScheduler only.
|
|
@@ -48,11 +52,8 @@ class VideoAnalyticsAgent {
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, eventObject);
|
|
54
|
-
}
|
|
55
|
-
return this.eventBuffer.add(eventObject);
|
|
55
|
+
// QOE_AGGREGATE dedup + plain append, shared with the Vega pipeline.
|
|
56
|
+
return bufferEventWithQoeDedup(this.eventBuffer, eventObject);
|
|
56
57
|
} catch (error) {
|
|
57
58
|
Log.error("Failed to add event to harvesting system:", error.message);
|
|
58
59
|
return false;
|
|
@@ -71,10 +72,44 @@ class VideoAnalyticsAgent {
|
|
|
71
72
|
|
|
72
73
|
this.harvestScheduler.updateHarvestInterval(interval);
|
|
73
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Forces the next harvest cycle to include QOE_AGGREGATE events.
|
|
78
|
+
* Called at CONTENT_END to ensure final QoE is sent.
|
|
79
|
+
*/
|
|
80
|
+
forceNextQoeCycle() {
|
|
81
|
+
if (this.harvestScheduler) {
|
|
82
|
+
this.harvestScheduler.forceNextQoeCycle = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sets a callback to be called before each drain to refresh QoE KPIs.
|
|
88
|
+
* @param {Function|null} callback - Function that refreshes QoE data in the buffer, or null to clear
|
|
89
|
+
*/
|
|
90
|
+
setBeforeDrainCallback(callback) {
|
|
91
|
+
if (this.harvestScheduler) {
|
|
92
|
+
this.harvestScheduler.beforeDrainCallback = callback;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Updates QoE KPI fields on the existing QOE_AGGREGATE event in the buffer.
|
|
98
|
+
* Scoped to a specific viewId to support multiple players on the same page.
|
|
99
|
+
* @param {object} freshKpis - Object with latest KPI values
|
|
100
|
+
* @param {string} [viewId] - The viewId of the player whose QoE event to update
|
|
101
|
+
*/
|
|
102
|
+
refreshQoeKpis(freshKpis, viewId) {
|
|
103
|
+
refreshQoeKpisInBuffer(this.eventBuffer, freshKpis, viewId);
|
|
104
|
+
}
|
|
74
105
|
}
|
|
75
106
|
|
|
76
107
|
// Create singleton instance
|
|
77
108
|
const videoAnalyticsAgent = new VideoAnalyticsAgent();
|
|
78
109
|
|
|
110
|
+
// Self-register for the 'Browser' routing key. Importing this module is what
|
|
111
|
+
// makes the Browser pipeline reachable in the consumer's bundle.
|
|
112
|
+
registerHarvester("Browser", videoAnalyticsAgent);
|
|
113
|
+
|
|
79
114
|
// Enhanced video analytics harvester
|
|
80
115
|
export const videoAnalyticsHarvester = videoAnalyticsAgent;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { NrVideoEventAggregator } from "
|
|
2
|
-
import { RetryQueueHandler } from "
|
|
3
|
-
import { OptimizedHttpClient } from "
|
|
4
|
-
import { buildUrl, dataSize } from "
|
|
5
|
-
import
|
|
6
|
-
|
|
1
|
+
import { NrVideoEventAggregator } from "../eventAggregator";
|
|
2
|
+
import { RetryQueueHandler } from "../retryQueueHandler";
|
|
3
|
+
import { OptimizedHttpClient } from "../optimizedHttpClient";
|
|
4
|
+
import { buildUrl, dataSize } from "../utils";
|
|
5
|
+
import {
|
|
6
|
+
partitionByQoeCycle,
|
|
7
|
+
applyQoeDirtyFilter,
|
|
8
|
+
} from "../utils/qoeFilters";
|
|
9
|
+
import { createHarvestTimer } from "../utils/harvestTimer";
|
|
10
|
+
import Constants from "../constants";
|
|
11
|
+
import Tracker from "../tracker";
|
|
12
|
+
import Log from "../log";
|
|
7
13
|
|
|
8
14
|
/**
|
|
9
15
|
* Enhanced harvest scheduler that orchestrates the video analytics data collection,
|
|
@@ -27,49 +33,55 @@ export class HarvestScheduler {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
// Scheduler state
|
|
30
|
-
this.isStarted = false;
|
|
31
|
-
this.currentTimerId = null;
|
|
32
36
|
this.harvestCycle = Constants.INTERVAL;
|
|
33
37
|
this.isHarvesting = false;
|
|
38
|
+
this.qoeCycleCount = 1;
|
|
39
|
+
this.forceNextQoeCycle = false;
|
|
40
|
+
this.beforeDrainCallback = null;
|
|
41
|
+
this._lastSentQoeKpis = {};
|
|
42
|
+
|
|
43
|
+
// Periodic harvest timer. Chained-setTimeout under the hood
|
|
44
|
+
this.timer = createHarvestTimer({
|
|
45
|
+
interval: this.harvestCycle,
|
|
46
|
+
onTick: () => this.onHarvestInterval(),
|
|
47
|
+
errorLabel: "HarvestScheduler",
|
|
48
|
+
});
|
|
34
49
|
|
|
35
50
|
// Page lifecycle handling
|
|
36
51
|
this.setupPageLifecycleHandlers();
|
|
37
52
|
}
|
|
38
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Whether the scheduler is currently running. Backed by the shared timer.
|
|
56
|
+
* @returns {boolean}
|
|
57
|
+
*/
|
|
58
|
+
get isStarted() {
|
|
59
|
+
return this.timer.isRunning();
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
/**
|
|
40
63
|
* Starts the harvest scheduler.
|
|
41
64
|
*/
|
|
42
65
|
startScheduler() {
|
|
43
|
-
if (this.
|
|
66
|
+
if (this.timer.isRunning()) {
|
|
44
67
|
Log.warn("Harvest scheduler is already started");
|
|
45
68
|
return;
|
|
46
69
|
}
|
|
47
70
|
|
|
48
|
-
this.isStarted = true;
|
|
49
|
-
|
|
50
71
|
Log.notice("Starting harvest scheduler", {
|
|
51
72
|
harvestCycle: this.harvestCycle,
|
|
52
73
|
eventBufferSize: this.eventBuffer ? this.eventBuffer.size() : 0,
|
|
53
74
|
});
|
|
54
75
|
|
|
55
|
-
this.
|
|
76
|
+
this.timer.start();
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
/**
|
|
59
80
|
* Stops the harvest scheduler.
|
|
60
81
|
*/
|
|
61
82
|
stopScheduler() {
|
|
62
|
-
if (!this.
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this.isStarted = false;
|
|
67
|
-
|
|
68
|
-
if (this.currentTimerId) {
|
|
69
|
-
clearTimeout(this.currentTimerId);
|
|
70
|
-
this.currentTimerId = null;
|
|
71
|
-
}
|
|
72
|
-
|
|
83
|
+
if (!this.timer.isRunning()) return;
|
|
84
|
+
this.timer.stop();
|
|
73
85
|
Log.notice("Harvest scheduler stopped");
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -87,53 +99,30 @@ export class HarvestScheduler {
|
|
|
87
99
|
// If buffer is empty, abort harvest
|
|
88
100
|
if (!this.eventBuffer || this.eventBuffer.isEmpty()) return;
|
|
89
101
|
|
|
90
|
-
// Clear existing timer to prevent redundant harvests
|
|
91
|
-
if (this.currentTimerId) {
|
|
92
|
-
clearTimeout(this.currentTimerId);
|
|
93
|
-
this.currentTimerId = null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
102
|
try {
|
|
97
103
|
await this.triggerHarvest({});
|
|
98
104
|
} catch (error) {
|
|
99
105
|
Log.error(`${type} harvest failed:`, error.message);
|
|
100
106
|
} finally {
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
107
|
+
// Reset the periodic clock — next periodic tick happens `harvestCycle`
|
|
108
|
+
// after this smart drain, not at the originally-scheduled time.
|
|
109
|
+
this.timer.cancelAndReschedule();
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
|
109
|
-
*
|
|
110
|
-
* @private
|
|
111
|
-
*/
|
|
112
|
-
scheduleNextHarvest() {
|
|
113
|
-
if (!this.isStarted) return;
|
|
114
|
-
|
|
115
|
-
const interval = this.harvestCycle;
|
|
116
|
-
this.currentTimerId = setTimeout(() => this.onHarvestInterval(), interval);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Handles the harvest interval timer.
|
|
114
|
+
* Periodic-tick callback. Invoked by the shared harvest timer on each cycle.
|
|
121
115
|
* @private
|
|
122
116
|
*/
|
|
123
117
|
async onHarvestInterval() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
} catch (error) {
|
|
133
|
-
Log.error("Error during scheduled harvest:", error.message);
|
|
134
|
-
} finally {
|
|
135
|
-
this.scheduleNextHarvest();
|
|
136
|
-
}
|
|
118
|
+
// Check if there's any data to harvest (buffer or retry queue) before
|
|
119
|
+
// starting the harvest process — avoids unnecessary network calls when
|
|
120
|
+
// there's nothing to ship.
|
|
121
|
+
const hasBufferData = this.eventBuffer && !this.eventBuffer.isEmpty();
|
|
122
|
+
const hasRetryData =
|
|
123
|
+
this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0;
|
|
124
|
+
if (!hasBufferData && !hasRetryData) return;
|
|
125
|
+
await this.triggerHarvest({});
|
|
137
126
|
}
|
|
138
127
|
|
|
139
128
|
/**
|
|
@@ -180,7 +169,6 @@ export class HarvestScheduler {
|
|
|
180
169
|
return {
|
|
181
170
|
success: false,
|
|
182
171
|
error: error.message,
|
|
183
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
184
172
|
};
|
|
185
173
|
} finally {
|
|
186
174
|
this.isHarvesting = false;
|
|
@@ -237,15 +225,47 @@ export class HarvestScheduler {
|
|
|
237
225
|
/**
|
|
238
226
|
* Drains events from the event buffer and optionally includes retry queue data.
|
|
239
227
|
* Uses fresh-events-first approach with payload limits.
|
|
228
|
+
* Filters out QOE_AGGREGATE events based on the harvest interval multiplier,
|
|
229
|
+
* always including them on the first and final harvest cycles.
|
|
240
230
|
* @param {object} options - Harvest options
|
|
241
231
|
* @returns {Array} Drained events
|
|
242
232
|
* @private
|
|
243
233
|
*/
|
|
244
|
-
drainEvents() {
|
|
234
|
+
drainEvents(options = {}) {
|
|
235
|
+
// Determine if this cycle should include the QOE_AGGREGATE event
|
|
236
|
+
const multiplier = window.NRVIDEO?.config?.qoeIntervalFactor ?? 1;
|
|
237
|
+
const isForced = !!options.isFinalHarvest || this.forceNextQoeCycle;
|
|
238
|
+
const isQoeCycle =
|
|
239
|
+
(this.qoeCycleCount - 1) % multiplier === 0 || isForced;
|
|
240
|
+
|
|
241
|
+
// Reset force flag after use
|
|
242
|
+
if (this.forceNextQoeCycle) this.forceNextQoeCycle = false;
|
|
243
|
+
|
|
244
|
+
// Refresh QoE KPIs with latest tracker state before draining
|
|
245
|
+
if (this.beforeDrainCallback && typeof this.beforeDrainCallback === 'function') {
|
|
246
|
+
try {
|
|
247
|
+
this.beforeDrainCallback();
|
|
248
|
+
} catch (e) {
|
|
249
|
+
Log.error("Before drain callback failed:", e.message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
245
253
|
// Always drain fresh events first (priority approach)
|
|
246
254
|
const freshEvents = this.eventBuffer.drain();
|
|
247
|
-
let
|
|
248
|
-
|
|
255
|
+
let filteredFreshEvents = partitionByQoeCycle(
|
|
256
|
+
freshEvents,
|
|
257
|
+
isQoeCycle,
|
|
258
|
+
this.eventBuffer
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
this.qoeCycleCount++;
|
|
262
|
+
|
|
263
|
+
// Cross-cycle dirty check: skip QoE if KPIs unchanged since last send.
|
|
264
|
+
// Forced cycles (CONTENT_END, page unload) always send QoE.
|
|
265
|
+
applyQoeDirtyFilter(filteredFreshEvents, this._lastSentQoeKpis, isForced);
|
|
266
|
+
|
|
267
|
+
let events = [...filteredFreshEvents];
|
|
268
|
+
let currentPayloadSize = dataSize(filteredFreshEvents);
|
|
249
269
|
|
|
250
270
|
// Always check retry queue if it has data - no flags needed
|
|
251
271
|
if (this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0) {
|
|
@@ -336,12 +356,7 @@ export class HarvestScheduler {
|
|
|
336
356
|
* @private
|
|
337
357
|
*/
|
|
338
358
|
handleHarvestFailure(error) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
Log.warn("Harvest failure handled", {
|
|
342
|
-
error: error.message,
|
|
343
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
344
|
-
});
|
|
359
|
+
Log.warn("Harvest failure handled", { error: error.message });
|
|
345
360
|
}
|
|
346
361
|
|
|
347
362
|
/**
|
|
@@ -351,7 +366,7 @@ export class HarvestScheduler {
|
|
|
351
366
|
*/
|
|
352
367
|
|
|
353
368
|
updateHarvestInterval(newInterval) {
|
|
354
|
-
if (typeof newInterval !== "number"
|
|
369
|
+
if (typeof newInterval !== "number" || isNaN(newInterval)) {
|
|
355
370
|
Log.warn("Invalid newInterval provided to updateHarvestInterval");
|
|
356
371
|
return;
|
|
357
372
|
}
|
|
@@ -362,26 +377,11 @@ export class HarvestScheduler {
|
|
|
362
377
|
}
|
|
363
378
|
|
|
364
379
|
// Check if the interval has actually changed to avoid unnecessary actions
|
|
365
|
-
if (this.harvestCycle === newInterval)
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
380
|
+
if (this.harvestCycle === newInterval) return;
|
|
368
381
|
|
|
369
|
-
// 1. Update the harvestCycle property with the new interval
|
|
370
382
|
this.harvestCycle = newInterval;
|
|
371
383
|
Log.notice("Updated harvestCycle:", this.harvestCycle);
|
|
372
|
-
|
|
373
|
-
// 2. Clear the existing timer
|
|
374
|
-
if (this.currentTimerId) {
|
|
375
|
-
clearTimeout(this.currentTimerId);
|
|
376
|
-
this.currentTimerId = null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// 3. Schedule a new timer with the updated interval
|
|
380
|
-
if (this.isStarted) {
|
|
381
|
-
this.scheduleNextHarvest();
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return;
|
|
384
|
+
this.timer.updateInterval(newInterval);
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
/**
|
|
@@ -413,4 +413,5 @@ export class HarvestScheduler {
|
|
|
413
413
|
triggerFinalHarvest();
|
|
414
414
|
});
|
|
415
415
|
}
|
|
416
|
+
|
|
416
417
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Core from "../core";
|
|
2
|
+
import Constants from "../constants";
|
|
3
|
+
import Chrono from "../chrono";
|
|
4
|
+
import Log from "../log";
|
|
5
|
+
import Emitter from "../emitter";
|
|
6
|
+
import Tracker from "../tracker";
|
|
7
|
+
import VideoTracker from "../videotracker";
|
|
8
|
+
import VideoTrackerState from "../videotrackerstate";
|
|
9
|
+
import { NrVideoEventAggregator } from "../eventAggregator";
|
|
10
|
+
import { RetryQueueHandler } from "../retryQueueHandler";
|
|
11
|
+
import { OptimizedHttpClient } from "../optimizedHttpClient";
|
|
12
|
+
import { HarvestScheduler } from "./harvestScheduler";
|
|
13
|
+
import { videoAnalyticsHarvester } from "./agent";
|
|
14
|
+
import { getRegisteredHarvester } from "../recordEvent";
|
|
15
|
+
// recordEvent is the single shared implementation from `src/recordEvent.js`
|
|
16
|
+
// — handles both Browser and Vega pipelines via the registry.
|
|
17
|
+
import { recordEvent } from "../recordEvent";
|
|
18
|
+
import { version } from "../../package.json";
|
|
19
|
+
|
|
20
|
+
const nrvideo = {
|
|
21
|
+
Constants,
|
|
22
|
+
Chrono,
|
|
23
|
+
Log,
|
|
24
|
+
Emitter,
|
|
25
|
+
Tracker,
|
|
26
|
+
VideoTracker,
|
|
27
|
+
VideoTrackerState,
|
|
28
|
+
Core,
|
|
29
|
+
version,
|
|
30
|
+
|
|
31
|
+
NrVideoEventAggregator,
|
|
32
|
+
RetryQueueHandler,
|
|
33
|
+
OptimizedHttpClient,
|
|
34
|
+
HarvestScheduler,
|
|
35
|
+
|
|
36
|
+
recordEvent,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
Core,
|
|
41
|
+
Constants,
|
|
42
|
+
Chrono,
|
|
43
|
+
Log,
|
|
44
|
+
Emitter,
|
|
45
|
+
Tracker,
|
|
46
|
+
VideoTracker,
|
|
47
|
+
VideoTrackerState,
|
|
48
|
+
NrVideoEventAggregator,
|
|
49
|
+
RetryQueueHandler,
|
|
50
|
+
OptimizedHttpClient,
|
|
51
|
+
HarvestScheduler,
|
|
52
|
+
videoAnalyticsHarvester,
|
|
53
|
+
getRegisteredHarvester,
|
|
54
|
+
recordEvent,
|
|
55
|
+
version,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default nrvideo;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import ConnectedDeviceHarvester from "./connectedDeviceHarvester";
|
|
2
|
+
import Log from "../log";
|
|
3
|
+
import { registerHarvester } from "../recordEvent";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vega-side analytics agent. Mirror of `VideoAnalyticsAgent` in `agent.js`.
|
|
7
|
+
*/
|
|
8
|
+
class ConnectedDeviceAnalyticsAgent {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.isInitialized = false;
|
|
11
|
+
this.harvester = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lazy initialization. Reads `info` from `globalThis.__NRVIDEO_CD__` and
|
|
16
|
+
* constructs the wrapped `ConnectedDeviceHarvester`. If `info` is not yet populated,
|
|
17
|
+
* returns without flipping `isInitialized` so the next `addEvent` retries.
|
|
18
|
+
*/
|
|
19
|
+
initialize() {
|
|
20
|
+
if (this.isInitialized) return;
|
|
21
|
+
|
|
22
|
+
const info = globalThis.__NRVIDEO_CD__?.info;
|
|
23
|
+
if (!info) {
|
|
24
|
+
Log.warn(
|
|
25
|
+
"ConnectedDeviceAnalyticsAgent.initialize: globalThis.__NRVIDEO_CD__.info is missing"
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
this.harvester = new ConnectedDeviceHarvester(info);
|
|
32
|
+
this.isInitialized = true;
|
|
33
|
+
Log.notice("ConnectedDeviceAnalyticsAgent initialized");
|
|
34
|
+
} catch (err) {
|
|
35
|
+
Log.error("ConnectedDeviceAnalyticsAgent.initialize failed:", err.message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Buffers an event for the wrapped ConnectedDeviceHarvester. Triggers lazy init on
|
|
41
|
+
* the first call. Drops events silently if init has not yet succeeded
|
|
42
|
+
* (defensive — under normal flow, `setVideoConfig` runs synchronously
|
|
43
|
+
* inside `super()` before any player event can fire).
|
|
44
|
+
*
|
|
45
|
+
*
|
|
46
|
+
* @param {object} eventObject
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
addEvent(eventObject) {
|
|
50
|
+
if (!this.isInitialized) this.initialize();
|
|
51
|
+
if (!this.isInitialized) return false;
|
|
52
|
+
return this.harvester.addEvent(eventObject);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Forwards to the underlying ConnectedDeviceHarvester. No-op if not yet initialized.
|
|
57
|
+
* @see ConnectedDeviceHarvester#forceQoeNextHarvest
|
|
58
|
+
*/
|
|
59
|
+
forceQoeNextHarvest() {
|
|
60
|
+
if (this.harvester) this.harvester.forceQoeNextHarvest();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* API-parity alias for `forceQoeNextHarvest`. Lets `videotracker.js` call a
|
|
65
|
+
* single method name on whichever harvester `getHarvester()` returns —
|
|
66
|
+
* Browser exposes `forceNextQoeCycle`, Vega exposes both names.
|
|
67
|
+
*/
|
|
68
|
+
forceNextQoeCycle() {
|
|
69
|
+
this.forceQoeNextHarvest();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Forwards to the underlying ConnectedDeviceHarvester. No-op if not yet initialized.
|
|
74
|
+
* @see ConnectedDeviceHarvester#setBeforeDrainCallback
|
|
75
|
+
* @param {Function|null} cb
|
|
76
|
+
*/
|
|
77
|
+
setBeforeDrainCallback(cb) {
|
|
78
|
+
if (this.harvester) this.harvester.setBeforeDrainCallback(cb);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Forwards to the underlying ConnectedDeviceHarvester. Triggers lazy init so
|
|
83
|
+
* `videotracker.js`'s drain callback (registered in sendStart) can refresh
|
|
84
|
+
* QoE on the buffered event before send.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} freshKpis
|
|
87
|
+
* @param {string} [viewId]
|
|
88
|
+
*/
|
|
89
|
+
refreshQoeKpis(freshKpis, viewId) {
|
|
90
|
+
if (!this.isInitialized) this.initialize();
|
|
91
|
+
if (this.harvester) this.harvester.refreshQoeKpis(freshKpis, viewId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Forwards to the underlying ConnectedDeviceHarvester. Triggers lazy init so
|
|
96
|
+
* `tracker.setHarvestInterval(...)` works for VegaTracker the same way it
|
|
97
|
+
* does for other trackers.
|
|
98
|
+
*
|
|
99
|
+
* @param {number} interval
|
|
100
|
+
*/
|
|
101
|
+
setHarvestInterval(interval) {
|
|
102
|
+
if (!this.isInitialized) this.initialize();
|
|
103
|
+
if (this.harvester) this.harvester.setHarvestInterval(interval);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const connectedDeviceAnalyticsAgent = new ConnectedDeviceAnalyticsAgent();
|
|
108
|
+
|
|
109
|
+
// Self-register for the 'Vega' routing key. Importing this module is what
|
|
110
|
+
// makes the Vega pipeline reachable in the consumer's bundle. If no consumer
|
|
111
|
+
// imports this module (e.g., Html5Tracker-only build), the entire Vega chain
|
|
112
|
+
// — connectedDeviceHarvester, connectedDeviceConstants, and indirectly
|
|
113
|
+
// retryQueueHandler/optimizedHttpClient — is tree-shaken.
|
|
114
|
+
registerHarvester("Vega", connectedDeviceAnalyticsAgent);
|
|
115
|
+
|
|
116
|
+
export const connectedDeviceAnalyticsHarvester = connectedDeviceAnalyticsAgent;
|