@newrelic/video-core 4.1.8 → 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/README.md +140 -27
- 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 +25 -7
- package/src/{agent.js → browser/agent.js} +14 -35
- package/src/{harvestScheduler.js → browser/harvestScheduler.js} +55 -137
- 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 +2 -0
- package/src/core.js +4 -18
- package/src/index.js +9 -10
- 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 +56 -10
- package/src/videotracker.js +26 -9
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,6 +41,7 @@
|
|
|
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",
|
|
@@ -36,18 +52,17 @@
|
|
|
36
52
|
"@semantic-release/commit-analyzer": "^12.0.0",
|
|
37
53
|
"@semantic-release/git": "^10.0.1",
|
|
38
54
|
"@semantic-release/github": "^10.0.3",
|
|
39
|
-
"@semantic-release/npm": "^
|
|
55
|
+
"@semantic-release/npm": "^13.1.5",
|
|
40
56
|
"@semantic-release/release-notes-generator": "^13.0.0",
|
|
41
|
-
"
|
|
42
|
-
"babel-jest": "^30.2.0",
|
|
57
|
+
"babel-jest": "^30.0.0",
|
|
43
58
|
"babel-loader": "^9.1.3",
|
|
44
59
|
"diff": "^5.0.0",
|
|
45
60
|
"jest": "^30.2.0",
|
|
46
61
|
"jest-environment-jsdom": "^30.2.0",
|
|
47
62
|
"jsdom": "^25.0.1",
|
|
48
|
-
"mocha": "^
|
|
49
|
-
"nyc": "^
|
|
50
|
-
"semantic-release": "^
|
|
63
|
+
"mocha": "^11.0.0",
|
|
64
|
+
"nyc": "^18.0.0",
|
|
65
|
+
"semantic-release": "^25.0.8",
|
|
51
66
|
"sinon": "^2.4.1",
|
|
52
67
|
"webpack": "^5.91.0",
|
|
53
68
|
"webpack-cli": "^4.9.2"
|
|
@@ -65,5 +80,8 @@
|
|
|
65
80
|
],
|
|
66
81
|
"publishConfig": {
|
|
67
82
|
"access": "public"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"terser-webpack-plugin": "^5.6.1"
|
|
68
86
|
}
|
|
69
87
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { HarvestScheduler } from "./harvestScheduler.js";
|
|
2
|
-
import { NrVideoEventAggregator } from "
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
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";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Enhanced video analytics agent with HarvestScheduler only.
|
|
@@ -49,20 +52,8 @@ class VideoAnalyticsAgent {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// Each player has a unique viewId, so multi-player scenarios are handled correctly.
|
|
55
|
-
// Dirty-check dedup happens at drain time in HarvestScheduler._qoeKpisUnchanged().
|
|
56
|
-
if (eventObject.viewId) {
|
|
57
|
-
return this.eventBuffer.addOrReplaceByActionNameAndViewId(
|
|
58
|
-
Tracker.Events.QOE_AGGREGATE,
|
|
59
|
-
eventObject.viewId,
|
|
60
|
-
eventObject
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
return this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, eventObject);
|
|
64
|
-
}
|
|
65
|
-
return this.eventBuffer.add(eventObject);
|
|
55
|
+
// QOE_AGGREGATE dedup + plain append, shared with the Vega pipeline.
|
|
56
|
+
return bufferEventWithQoeDedup(this.eventBuffer, eventObject);
|
|
66
57
|
} catch (error) {
|
|
67
58
|
Log.error("Failed to add event to harvesting system:", error.message);
|
|
68
59
|
return false;
|
|
@@ -109,28 +100,16 @@ class VideoAnalyticsAgent {
|
|
|
109
100
|
* @param {string} [viewId] - The viewId of the player whose QoE event to update
|
|
110
101
|
*/
|
|
111
102
|
refreshQoeKpis(freshKpis, viewId) {
|
|
112
|
-
|
|
113
|
-
const existing = viewId
|
|
114
|
-
? this.eventBuffer.findByActionNameAndViewId(Tracker.Events.QOE_AGGREGATE, viewId)
|
|
115
|
-
: this.eventBuffer.findByActionName(Tracker.Events.QOE_AGGREGATE);
|
|
116
|
-
if (existing) {
|
|
117
|
-
const updated = { ...existing };
|
|
118
|
-
for (const key of Constants.QOE_KPI_KEYS) {
|
|
119
|
-
if (key in freshKpis) {
|
|
120
|
-
updated[key] = freshKpis[key];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (viewId) {
|
|
124
|
-
this.eventBuffer.addOrReplaceByActionNameAndViewId(Tracker.Events.QOE_AGGREGATE, viewId, updated);
|
|
125
|
-
} else {
|
|
126
|
-
this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, updated);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
103
|
+
refreshQoeKpisInBuffer(this.eventBuffer, freshKpis, viewId);
|
|
129
104
|
}
|
|
130
105
|
}
|
|
131
106
|
|
|
132
107
|
// Create singleton instance
|
|
133
108
|
const videoAnalyticsAgent = new VideoAnalyticsAgent();
|
|
134
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
|
+
|
|
135
114
|
// Enhanced video analytics harvester
|
|
136
115
|
export const videoAnalyticsHarvester = videoAnalyticsAgent;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { NrVideoEventAggregator } from "
|
|
2
|
-
import { RetryQueueHandler } from "
|
|
3
|
-
import { OptimizedHttpClient } from "
|
|
4
|
-
import { buildUrl, dataSize } from "
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
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";
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* Enhanced harvest scheduler that orchestrates the video analytics data collection,
|
|
@@ -28,8 +33,6 @@ export class HarvestScheduler {
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
// Scheduler state
|
|
31
|
-
this.isStarted = false;
|
|
32
|
-
this.currentTimerId = null;
|
|
33
36
|
this.harvestCycle = Constants.INTERVAL;
|
|
34
37
|
this.isHarvesting = false;
|
|
35
38
|
this.qoeCycleCount = 1;
|
|
@@ -37,44 +40,48 @@ export class HarvestScheduler {
|
|
|
37
40
|
this.beforeDrainCallback = null;
|
|
38
41
|
this._lastSentQoeKpis = {};
|
|
39
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
|
+
});
|
|
49
|
+
|
|
40
50
|
// Page lifecycle handling
|
|
41
51
|
this.setupPageLifecycleHandlers();
|
|
42
52
|
}
|
|
43
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
|
+
|
|
44
62
|
/**
|
|
45
63
|
* Starts the harvest scheduler.
|
|
46
64
|
*/
|
|
47
65
|
startScheduler() {
|
|
48
|
-
if (this.
|
|
66
|
+
if (this.timer.isRunning()) {
|
|
49
67
|
Log.warn("Harvest scheduler is already started");
|
|
50
68
|
return;
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
this.isStarted = true;
|
|
54
|
-
|
|
55
71
|
Log.notice("Starting harvest scheduler", {
|
|
56
72
|
harvestCycle: this.harvestCycle,
|
|
57
73
|
eventBufferSize: this.eventBuffer ? this.eventBuffer.size() : 0,
|
|
58
74
|
});
|
|
59
75
|
|
|
60
|
-
this.
|
|
76
|
+
this.timer.start();
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
/**
|
|
64
80
|
* Stops the harvest scheduler.
|
|
65
81
|
*/
|
|
66
82
|
stopScheduler() {
|
|
67
|
-
if (!this.
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.isStarted = false;
|
|
72
|
-
|
|
73
|
-
if (this.currentTimerId) {
|
|
74
|
-
clearTimeout(this.currentTimerId);
|
|
75
|
-
this.currentTimerId = null;
|
|
76
|
-
}
|
|
77
|
-
|
|
83
|
+
if (!this.timer.isRunning()) return;
|
|
84
|
+
this.timer.stop();
|
|
78
85
|
Log.notice("Harvest scheduler stopped");
|
|
79
86
|
}
|
|
80
87
|
|
|
@@ -92,53 +99,30 @@ export class HarvestScheduler {
|
|
|
92
99
|
// If buffer is empty, abort harvest
|
|
93
100
|
if (!this.eventBuffer || this.eventBuffer.isEmpty()) return;
|
|
94
101
|
|
|
95
|
-
// Clear existing timer to prevent redundant harvests
|
|
96
|
-
if (this.currentTimerId) {
|
|
97
|
-
clearTimeout(this.currentTimerId);
|
|
98
|
-
this.currentTimerId = null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
102
|
try {
|
|
102
103
|
await this.triggerHarvest({});
|
|
103
104
|
} catch (error) {
|
|
104
105
|
Log.error(`${type} harvest failed:`, error.message);
|
|
105
106
|
} finally {
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
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();
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
* @private
|
|
116
|
-
*/
|
|
117
|
-
scheduleNextHarvest() {
|
|
118
|
-
if (!this.isStarted) return;
|
|
119
|
-
|
|
120
|
-
const interval = this.harvestCycle;
|
|
121
|
-
this.currentTimerId = setTimeout(() => this.onHarvestInterval(), interval);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Handles the harvest interval timer.
|
|
114
|
+
* Periodic-tick callback. Invoked by the shared harvest timer on each cycle.
|
|
126
115
|
* @private
|
|
127
116
|
*/
|
|
128
117
|
async onHarvestInterval() {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} catch (error) {
|
|
138
|
-
Log.error("Error during scheduled harvest:", error.message);
|
|
139
|
-
} finally {
|
|
140
|
-
this.scheduleNextHarvest();
|
|
141
|
-
}
|
|
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({});
|
|
142
126
|
}
|
|
143
127
|
|
|
144
128
|
/**
|
|
@@ -185,7 +169,6 @@ export class HarvestScheduler {
|
|
|
185
169
|
return {
|
|
186
170
|
success: false,
|
|
187
171
|
error: error.message,
|
|
188
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
189
172
|
};
|
|
190
173
|
} finally {
|
|
191
174
|
this.isHarvesting = false;
|
|
@@ -269,35 +252,17 @@ export class HarvestScheduler {
|
|
|
269
252
|
|
|
270
253
|
// Always drain fresh events first (priority approach)
|
|
271
254
|
const freshEvents = this.eventBuffer.drain();
|
|
272
|
-
let filteredFreshEvents
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
filteredFreshEvents = [];
|
|
278
|
-
for (const e of freshEvents) {
|
|
279
|
-
if (e.actionName === Tracker.Events.QOE_AGGREGATE) {
|
|
280
|
-
this.eventBuffer.add(e);
|
|
281
|
-
} else {
|
|
282
|
-
filteredFreshEvents.push(e);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
255
|
+
let filteredFreshEvents = partitionByQoeCycle(
|
|
256
|
+
freshEvents,
|
|
257
|
+
isQoeCycle,
|
|
258
|
+
this.eventBuffer
|
|
259
|
+
);
|
|
286
260
|
|
|
287
261
|
this.qoeCycleCount++;
|
|
288
262
|
|
|
289
|
-
// Cross-cycle dirty check: skip QoE if KPIs unchanged since last send
|
|
290
|
-
// Forced cycles (CONTENT_END, page unload) always send QoE
|
|
291
|
-
|
|
292
|
-
const e = filteredFreshEvents[i];
|
|
293
|
-
if (e.actionName === Tracker.Events.QOE_AGGREGATE) {
|
|
294
|
-
if (!isForced && this._qoeKpisUnchanged(e)) {
|
|
295
|
-
filteredFreshEvents.splice(i, 1);
|
|
296
|
-
} else {
|
|
297
|
-
this._saveQoeKpis(e);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
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);
|
|
301
266
|
|
|
302
267
|
let events = [...filteredFreshEvents];
|
|
303
268
|
let currentPayloadSize = dataSize(filteredFreshEvents);
|
|
@@ -391,12 +356,7 @@ export class HarvestScheduler {
|
|
|
391
356
|
* @private
|
|
392
357
|
*/
|
|
393
358
|
handleHarvestFailure(error) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
Log.warn("Harvest failure handled", {
|
|
397
|
-
error: error.message,
|
|
398
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
399
|
-
});
|
|
359
|
+
Log.warn("Harvest failure handled", { error: error.message });
|
|
400
360
|
}
|
|
401
361
|
|
|
402
362
|
/**
|
|
@@ -406,7 +366,7 @@ export class HarvestScheduler {
|
|
|
406
366
|
*/
|
|
407
367
|
|
|
408
368
|
updateHarvestInterval(newInterval) {
|
|
409
|
-
if (typeof newInterval !== "number"
|
|
369
|
+
if (typeof newInterval !== "number" || isNaN(newInterval)) {
|
|
410
370
|
Log.warn("Invalid newInterval provided to updateHarvestInterval");
|
|
411
371
|
return;
|
|
412
372
|
}
|
|
@@ -417,26 +377,11 @@ export class HarvestScheduler {
|
|
|
417
377
|
}
|
|
418
378
|
|
|
419
379
|
// Check if the interval has actually changed to avoid unnecessary actions
|
|
420
|
-
if (this.harvestCycle === newInterval)
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
380
|
+
if (this.harvestCycle === newInterval) return;
|
|
423
381
|
|
|
424
|
-
// 1. Update the harvestCycle property with the new interval
|
|
425
382
|
this.harvestCycle = newInterval;
|
|
426
383
|
Log.notice("Updated harvestCycle:", this.harvestCycle);
|
|
427
|
-
|
|
428
|
-
// 2. Clear the existing timer
|
|
429
|
-
if (this.currentTimerId) {
|
|
430
|
-
clearTimeout(this.currentTimerId);
|
|
431
|
-
this.currentTimerId = null;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// 3. Schedule a new timer with the updated interval
|
|
435
|
-
if (this.isStarted) {
|
|
436
|
-
this.scheduleNextHarvest();
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return;
|
|
384
|
+
this.timer.updateInterval(newInterval);
|
|
440
385
|
}
|
|
441
386
|
|
|
442
387
|
/**
|
|
@@ -469,31 +414,4 @@ export class HarvestScheduler {
|
|
|
469
414
|
});
|
|
470
415
|
}
|
|
471
416
|
|
|
472
|
-
/**
|
|
473
|
-
* Checks if QoE KPIs are unchanged since last send.
|
|
474
|
-
* @param {object} event - QoE event to compare
|
|
475
|
-
* @returns {boolean} True if KPIs are identical to last sent
|
|
476
|
-
* @private
|
|
477
|
-
*/
|
|
478
|
-
_qoeKpisUnchanged(event) {
|
|
479
|
-
const snapshot = this._lastSentQoeKpis[event.viewId];
|
|
480
|
-
if (!snapshot) return false;
|
|
481
|
-
for (const key of Constants.QOE_KPI_KEYS) {
|
|
482
|
-
if (event[key] !== snapshot[key]) return false;
|
|
483
|
-
}
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Saves QoE KPI values after sending, keyed by viewId to support multiple players.
|
|
489
|
-
* @param {object} event - QoE event that was sent
|
|
490
|
-
* @private
|
|
491
|
-
*/
|
|
492
|
-
_saveQoeKpis(event) {
|
|
493
|
-
const snapshot = {};
|
|
494
|
-
for (const key of Constants.QOE_KPI_KEYS) {
|
|
495
|
-
snapshot[key] = event[key];
|
|
496
|
-
}
|
|
497
|
-
this._lastSentQoeKpis[event.viewId] = snapshot;
|
|
498
|
-
}
|
|
499
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;
|