@newrelic/video-core 4.1.1-beta → 4.1.1
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 +9 -7
- package/README.md +13 -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/esm/index.js +1 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -1
- 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 +5 -3
- package/src/constants.js +5 -1
- package/src/core.js +1 -1
- package/src/eventAggregator.js +0 -1
- package/src/harvestScheduler.js +18 -3
- package/src/recordEvent.js +1 -1
- package/src/videoConfiguration.js +58 -8
- package/src/videotracker.js +37 -10
- package/src/videotrackerstate.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/video-core",
|
|
3
|
-
"version": "4.1.1
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "New Relic video tracking core library",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"watch": "webpack --mode production --progress --color --watch",
|
|
11
11
|
"watch:dev": "webpack --progress --color --watch",
|
|
12
12
|
"clean": "rm -rf dist coverage doc",
|
|
13
|
-
"test": "
|
|
13
|
+
"test": "jest --coverage",
|
|
14
14
|
"doc": "jsdoc -c jsdoc.json -d documentation",
|
|
15
15
|
"deploy": "node scripts/deploy.js",
|
|
16
16
|
"third-party-updates": "oss third-party manifest --includeOptDeps && oss third-party notices --includeOptDeps && git add THIRD_PARTY_NOTICES.md third_party_manifest.json"
|
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
"@babel/preset-env": "^7.24.5",
|
|
34
34
|
"@babel/register": "^7.24.6",
|
|
35
35
|
"aws-sdk": "^2.920.0",
|
|
36
|
+
"babel-jest": "^30.2.0",
|
|
36
37
|
"babel-loader": "^9.1.3",
|
|
37
|
-
"chai": "^4.3.4",
|
|
38
38
|
"diff": "^5.0.0",
|
|
39
|
+
"jest": "^30.2.0",
|
|
40
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
39
41
|
"jsdom": "^25.0.1",
|
|
40
42
|
"mocha": "^10.4.0",
|
|
41
43
|
"nyc": "^15.1.0",
|
package/src/constants.js
CHANGED
|
@@ -45,7 +45,11 @@ Constants.INTERVAL = 10000; //10 seconds
|
|
|
45
45
|
Constants.QOE_AGGREGATE_KEYS = [
|
|
46
46
|
"coreVersion", "instrumentation.name",
|
|
47
47
|
"instrumentation.provider", "instrumentation.version", "isBackgroundEvent", "playerName", "playerVersion",
|
|
48
|
-
"src", "viewId", "viewSession", "contentIsAutoplayed"
|
|
48
|
+
"src", "viewId", "viewSession", "contentIsAutoplayed", "contentIsMuted", "contentRenditionHeight", "contentRenditionWidth",
|
|
49
|
+
"contentSrc", "numberOfVideos", "pageUrl", "trackerName", "trackerVersion", "contentDuration", "contentPlayrate", "contentPlayhead",
|
|
50
|
+
"contentPreload", "elapsedTime", "contentTitle", "contentId", "contentIsLive", "deviceType", "deviceGroup", "deviceManufacturer",
|
|
51
|
+
"deviceModel", "deviceName", "deviceSize", "deviceUuid", "contentRenditionName", "contentIsFullscreen", "contentCdn",
|
|
52
|
+
"contentFps", "asnOrganization", "asnLongitude", "asnLatitude", "asn", "timeSinceRequested", "timeSinceStarted"
|
|
49
53
|
]
|
|
50
54
|
|
|
51
55
|
export default Constants;
|
package/src/core.js
CHANGED
package/src/eventAggregator.js
CHANGED
package/src/harvestScheduler.js
CHANGED
|
@@ -31,6 +31,7 @@ export class HarvestScheduler {
|
|
|
31
31
|
this.currentTimerId = null;
|
|
32
32
|
this.harvestCycle = Constants.INTERVAL;
|
|
33
33
|
this.isHarvesting = false;
|
|
34
|
+
this.qoeCycleCount = 1;
|
|
34
35
|
|
|
35
36
|
// Page lifecycle handling
|
|
36
37
|
this.setupPageLifecycleHandlers();
|
|
@@ -237,15 +238,29 @@ export class HarvestScheduler {
|
|
|
237
238
|
/**
|
|
238
239
|
* Drains events from the event buffer and optionally includes retry queue data.
|
|
239
240
|
* Uses fresh-events-first approach with payload limits.
|
|
241
|
+
* Filters out QOE_AGGREGATE events based on the harvest interval multiplier,
|
|
242
|
+
* always including them on the first and final harvest cycles.
|
|
240
243
|
* @param {object} options - Harvest options
|
|
241
244
|
* @returns {Array} Drained events
|
|
242
245
|
* @private
|
|
243
246
|
*/
|
|
244
|
-
drainEvents() {
|
|
247
|
+
drainEvents(options = {}) {
|
|
248
|
+
// Determine if this cycle should include the QOE_AGGREGATE event
|
|
249
|
+
const multiplier = window.NRVIDEO?.config?.qoeIntervalFactor ?? 1;
|
|
250
|
+
const isQoeCycle =
|
|
251
|
+
(this.qoeCycleCount - 1) % multiplier === 0 ||
|
|
252
|
+
!!options.isFinalHarvest;
|
|
253
|
+
|
|
245
254
|
// Always drain fresh events first (priority approach)
|
|
246
255
|
const freshEvents = this.eventBuffer.drain();
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
const filteredFreshEvents = isQoeCycle
|
|
257
|
+
? freshEvents
|
|
258
|
+
: freshEvents.filter((e) => e.actionName !== "QOE_AGGREGATE");
|
|
259
|
+
|
|
260
|
+
this.qoeCycleCount++;
|
|
261
|
+
|
|
262
|
+
let events = [...filteredFreshEvents];
|
|
263
|
+
let currentPayloadSize = dataSize(filteredFreshEvents);
|
|
249
264
|
|
|
250
265
|
// Always check retry queue if it has data - no flags needed
|
|
251
266
|
if (this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0) {
|
package/src/recordEvent.js
CHANGED
|
@@ -57,7 +57,7 @@ export function recordEvent(eventType, attributes = {}) {
|
|
|
57
57
|
// Send to video analytics harvester
|
|
58
58
|
const success = videoAnalyticsHarvester.addEvent(eventObject);
|
|
59
59
|
|
|
60
|
-
if(qoeEventObject) {
|
|
60
|
+
if(qoeEventObject && window?.NRVIDEO?.config?.qoeAggregate) {
|
|
61
61
|
const successQoe = videoAnalyticsHarvester.addEvent(qoeEventObject);
|
|
62
62
|
return success && successQoe;
|
|
63
63
|
}
|
|
@@ -10,12 +10,19 @@ const { COLLECTOR } = Constants;
|
|
|
10
10
|
class VideoConfiguration {
|
|
11
11
|
/**
|
|
12
12
|
* Validates and sets the video analytics configuration.
|
|
13
|
-
* @param {object}
|
|
13
|
+
* @param {object} userInfo - User provided configuration
|
|
14
|
+
* @param {object} [config] - Optional configuration object
|
|
14
15
|
* @returns {boolean} True if configuration is valid and set
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
setConfiguration(userInfo) {
|
|
18
|
-
this.
|
|
18
|
+
setConfiguration(userInfo, config) {
|
|
19
|
+
if (!this.validateRequiredFields(userInfo)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (!this.validateConfigFields(config)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
this.initializeGlobalConfig(userInfo, config);
|
|
19
26
|
Log.notice("Video analytics configuration initialized successfully");
|
|
20
27
|
return true;
|
|
21
28
|
}
|
|
@@ -70,11 +77,49 @@ class VideoConfiguration {
|
|
|
70
77
|
return true;
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Validates optional config fields.
|
|
82
|
+
* @param {object} config - Config to validate
|
|
83
|
+
* @returns {boolean} True if valid
|
|
84
|
+
*/
|
|
85
|
+
validateConfigFields(config) {
|
|
86
|
+
if (config === null || config === undefined) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
91
|
+
Log.error("config must be an object");
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { qoeAggregate } = config;
|
|
96
|
+
|
|
97
|
+
if (qoeAggregate !== undefined && typeof qoeAggregate !== "boolean") {
|
|
98
|
+
Log.error("qoeAggregate must be a boolean");
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sanitizes qoeIntervalFactor, defaulting to 1 if the value is not a positive integer.
|
|
107
|
+
* @param {*} value
|
|
108
|
+
* @returns {number}
|
|
109
|
+
*/
|
|
110
|
+
sanitizeQoeIntervalFactor(value) {
|
|
111
|
+
if (value === undefined || value === null) return 1;
|
|
112
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
|
|
113
|
+
console.warn(`[nrvideo] Invalid qoeIntervalFactor "${value}" — must be a positive integer. Defaulting to 1.`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
73
117
|
/**
|
|
74
118
|
* Initializes the global NRVIDEO configuration object.
|
|
119
|
+
* @param {object} userInfo - User provided configuration
|
|
120
|
+
* @param {object} [config] - Optional configuration object
|
|
75
121
|
*/
|
|
76
|
-
initializeGlobalConfig(userInfo) {
|
|
77
|
-
if (!this.validateRequiredFields(userInfo)) return;
|
|
122
|
+
initializeGlobalConfig(userInfo, config) {
|
|
78
123
|
|
|
79
124
|
let { licenseKey, appName, region, beacon, applicationID } = userInfo;
|
|
80
125
|
|
|
@@ -93,6 +138,10 @@ class VideoConfiguration {
|
|
|
93
138
|
applicationID,
|
|
94
139
|
...(applicationID ? {} : { appName }), // Only include appName when no applicationID
|
|
95
140
|
},
|
|
141
|
+
config: {
|
|
142
|
+
qoeAggregate: config?.qoeAggregate ?? false,
|
|
143
|
+
qoeIntervalFactor: this.sanitizeQoeIntervalFactor(config?.qoeIntervalFactor),
|
|
144
|
+
}
|
|
96
145
|
};
|
|
97
146
|
}
|
|
98
147
|
}
|
|
@@ -102,11 +151,12 @@ const videoConfiguration = new VideoConfiguration();
|
|
|
102
151
|
|
|
103
152
|
/**
|
|
104
153
|
* Sets the video analytics configuration.
|
|
105
|
-
* @param {object}
|
|
154
|
+
* @param {object} info - Info configuration object
|
|
155
|
+
* @param {object} [config] - Optional configuration object
|
|
106
156
|
* @returns {boolean} True if configuration was set successfully
|
|
107
157
|
*/
|
|
108
|
-
export function setVideoConfig(info) {
|
|
109
|
-
return videoConfiguration.setConfiguration(info);
|
|
158
|
+
export function setVideoConfig(info, config) {
|
|
159
|
+
return videoConfiguration.setConfiguration(info, config);
|
|
110
160
|
}
|
|
111
161
|
|
|
112
162
|
export { videoConfiguration };
|
package/src/videotracker.js
CHANGED
|
@@ -239,6 +239,21 @@ class VideoTracker extends Tracker {
|
|
|
239
239
|
return null;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/** Override to return the manifest-declared bitrate in bps (Indicated Bitrate). */
|
|
243
|
+
getManifestBitrate() {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Override to return the measured network bitrate in bps (Observed Bitrate). */
|
|
248
|
+
getMeasuredBitrate() {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Override to return the download throughput in bps. */
|
|
253
|
+
getDownloadBitrate() {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
242
257
|
/** Calculates consumed bitrate using webkitVideoDecodedByteCount. */
|
|
243
258
|
getWebkitBitrate() {
|
|
244
259
|
if (this.tag && this.tag.webkitVideoDecodedByteCount) {
|
|
@@ -465,12 +480,17 @@ class VideoTracker extends Tracker {
|
|
|
465
480
|
att.adTitle = this.getTitle();
|
|
466
481
|
att.adSrc = this.getSrc();
|
|
467
482
|
att.adCdn = this.getCdn();
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
483
|
+
|
|
484
|
+
// Only add bitrate attributes after ad has started
|
|
485
|
+
if (this.state.isStarted) {
|
|
486
|
+
att.adBitrate =
|
|
487
|
+
this.getBitrate() ||
|
|
488
|
+
this.getWebkitBitrate() ||
|
|
489
|
+
this.getRenditionBitrate();
|
|
490
|
+
att.adRenditionBitrate = this.getRenditionBitrate();
|
|
491
|
+
}
|
|
492
|
+
|
|
472
493
|
att.adRenditionName = this.getRenditionName();
|
|
473
|
-
att.adRenditionBitrate = this.getRenditionBitrate();
|
|
474
494
|
att.adRenditionHeight = this.getRenditionHeight();
|
|
475
495
|
att.adRenditionWidth = this.getRenditionWidth();
|
|
476
496
|
att.adDuration = this.getDuration();
|
|
@@ -492,13 +512,20 @@ class VideoTracker extends Tracker {
|
|
|
492
512
|
att.contentPlayhead = this.getPlayhead();
|
|
493
513
|
|
|
494
514
|
att.contentIsLive = this.isLive();
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
515
|
+
|
|
516
|
+
// Only add bitrate attributes after content has started
|
|
517
|
+
if (this.state.isStarted) {
|
|
518
|
+
att.contentBitrate =
|
|
519
|
+
this.getBitrate() ||
|
|
520
|
+
this.getWebkitBitrate() ||
|
|
521
|
+
this.getRenditionBitrate();
|
|
522
|
+
att.contentRenditionBitrate = this.getRenditionBitrate();
|
|
523
|
+
att.contentManifestBitrate = this.getManifestBitrate();
|
|
524
|
+
att.contentMeasuredBitrate = this.getMeasuredBitrate();
|
|
525
|
+
att.contentDownloadBitrate = this.getDownloadBitrate();
|
|
526
|
+
}
|
|
499
527
|
|
|
500
528
|
att.contentRenditionName = this.getRenditionName();
|
|
501
|
-
att.contentRenditionBitrate = this.getRenditionBitrate();
|
|
502
529
|
att.contentRenditionHeight = this.getRenditionHeight();
|
|
503
530
|
att.contentRenditionWidth = this.getRenditionWidth();
|
|
504
531
|
att.contentDuration = this.getDuration();
|
package/src/videotrackerstate.js
CHANGED
|
@@ -363,6 +363,7 @@ class VideoTrackerState {
|
|
|
363
363
|
: 0;
|
|
364
364
|
kpi["totalPlaytime"] = this.totalPlaytime;
|
|
365
365
|
kpi["averageBitrate"] = this.weightedBitrate;
|
|
366
|
+
kpi["numberOfErrors"] = this.numberOfErrors;
|
|
366
367
|
} catch (error) {
|
|
367
368
|
Log.error("Failed to add attributes for QOE KPIs", error.message);
|
|
368
369
|
}
|