@newrelic/video-core 4.1.6-beta → 4.1.6

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 CHANGED
@@ -1,79 +1,557 @@
1
- [![Community Project header](https://github.com/newrelic/open-source-office/raw/master/examples/categories/images/Community_Project.png)](https://github.com/newrelic/open-source-office/blob/master/examples/categories/index.md#community-project)
2
-
3
1
  # New Relic Video Core - JavaScript
4
2
 
5
- The New Relic video tracking core library is the base for all video trackers in the browser platform. It contains the classes and core mechanisms used by the player specific trackers.
6
- It segregates the events into different event types based on action, such as video-related events going to `VideoAction`, ad-related events to `VideoAdAction`, errors to `VideoErrorAction`, and custom actions to `VideoCustomAction`.
3
+ [![npm version](https://img.shields.io/npm/v/@newrelic/video-core.svg)](https://www.npmjs.com/package/@newrelic/video-core)
4
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
5
+
6
+ The **New Relic Video Core** library (`@newrelic/video-core`) is the foundational framework for all browser-based video trackers in the New Relic ecosystem. It provides the core classes, state management, event harvesting, and data transmission pipeline that player-specific trackers extend.
7
+
8
+ Events are categorized into four distinct types:
9
+
10
+ | Event Type | Description |
11
+ |---|---|
12
+ | `VideoAction` | Content playback events (play, pause, seek, buffer, etc.) |
13
+ | `VideoAdAction` | Ad-related events (ad start, end, quartile, break, etc.) |
14
+ | `VideoErrorAction` | Error events (content errors, ad errors, crashes) |
15
+ | `VideoCustomAction` | Custom events defined by the integrator |
16
+
17
+ ---
18
+
19
+ ## Table of Contents
20
+
21
+ - [Installation](#installation)
22
+ - [Quick Start](#quick-start)
23
+ - [Configuration](#configuration)
24
+ - [Info Object (Required)](#info-object-required)
25
+ - [Config Object (Optional)](#config-object-optional)
26
+ - [Exposed API](#exposed-api)
27
+ - [Core](#core)
28
+ - [VideoTracker](#videotracker)
29
+ - [Tracker](#tracker)
30
+ - [Emitter](#emitter)
31
+ - [VideoTrackerState](#videotrackerstate)
32
+ - [Chrono](#chrono)
33
+ - [Log](#log)
34
+ - [Constants](#constants)
35
+ - [Building a Custom Tracker](#building-a-custom-tracker)
36
+ - [Tracker Methods Reference](#tracker-methods-reference)
37
+ - [Getter Methods (Override These)](#getter-methods-override-these)
38
+ - [Quality of Experience (QoE)](#quality-of-experience-qoe)
39
+ - [Obfuscation Rules](#obfuscation-rules)
40
+ - [Build & Development](#build--development)
41
+ - [Distribution Formats](#distribution-formats)
42
+ - [Testing](#testing)
43
+ - [Data Model](#data-model)
44
+ - [License](#license)
45
+
46
+ ---
7
47
 
8
- ## Registering Trackers
48
+ ## Installation
9
49
 
10
- Any browser-based video tracker can extend the `VideoTracker` class and use its core functionality.
50
+ ```bash
51
+ npm install @newrelic/video-core
52
+ ```
53
+
54
+ Or include directly via UMD:
55
+
56
+ ```html
57
+ <script src="dist/umd/nrvideo.min.js"></script>
58
+ ```
11
59
 
12
- To initialize a tracker, create an instance of your specific tracker class:
60
+ ## Quick Start
13
61
 
14
62
  ```javascript
63
+ import nrvideo from '@newrelic/video-core';
64
+
65
+ // 1. Define a custom tracker by extending VideoTracker
66
+ class MyPlayerTracker extends nrvideo.VideoTracker {
67
+ getTrackerName() { return 'my-player'; }
68
+ getSrc() { return this.player.currentSrc; }
69
+ getDuration() { return this.player.duration; }
70
+ getPlayhead() { return this.player.currentTime; }
71
+ getRenditionWidth() { return this.player.videoWidth; }
72
+ getRenditionHeight() { return this.player.videoHeight; }
73
+
74
+ registerListeners() {
75
+ this.player.addEventListener('play', () => this.sendRequest());
76
+ this.player.addEventListener('playing', () => this.sendStart());
77
+ this.player.addEventListener('pause', () => this.sendPause());
78
+ this.player.addEventListener('ended', () => this.sendEnd());
79
+ this.player.addEventListener('waiting', () => this.sendBufferStart());
80
+ this.player.addEventListener('seeking', () => this.sendSeekStart());
81
+ this.player.addEventListener('seeked', () => this.sendSeekEnd());
82
+ this.player.addEventListener('error', () => this.sendError({
83
+ errorName: this.player.error?.message,
84
+ errorCode: this.player.error?.code
85
+ }));
86
+ }
87
+
88
+ unregisterListeners() {
89
+ // Remove all listeners added in registerListeners
90
+ }
91
+ }
92
+
93
+ // 2. Configure and register the tracker
15
94
  const options = {
16
95
  info: {
17
- licenseKey: "xxxxxxxxxxx",
18
- beacon: "xxxxxxxxxx",
19
- applicationId: "xxxxxxx",
96
+ licenseKey: 'YOUR_LICENSE_KEY',
97
+ beacon: 'bam.nr-data.net',
98
+ applicationID: 'YOUR_APPLICATION_ID',
99
+ },
100
+ config: {
101
+ qoeAggregate: true,
20
102
  },
21
103
  };
22
104
 
23
- // User can get the `info` object by completing the onboarding process on New Relic.
24
- const tracker = new VideoSpecificTracker(player, options);
105
+ const player = document.getElementById('myPlayer');
106
+ const tracker = new MyPlayerTracker(player, options);
107
+
108
+ // 3. Add tracker to Core to begin reporting
109
+ nrvideo.Core.addTracker(tracker, options);
25
110
  ```
26
111
 
27
- Some of the APIs exposed and commonly used are:
112
+ ## Configuration
113
+
114
+ The `options` object passed to `Core.addTracker()` consists of two parts:
115
+
116
+ ### Getting Your Configuration
28
117
 
29
- - `tracker.setUserId("userId")` &mdash; Set the user ID.
30
- - `tracker.setHarvestInterval(30000)` &mdash; Set the harvest interval time (in milliseconds).
31
- - `tracker.setOptions({ customData: { key: value } })` &mdash; Set custom options or data.
32
- - `tracker.sendCustom("CustomEvent", { data: "custom-test" })` &mdash; Send a custom event.
118
+ Before initializing the tracker, obtain your New Relic configuration:
33
119
 
34
- Any event emitted by the tracker will be sent to New Relic and processed according to its type.
120
+ 1. Log in to [one.newrelic.com](https://one.newrelic.com)
121
+ 2. Navigate to the video agent onboarding flow
122
+ 3. Copy your credentials: `licenseKey`, `beacon`, and `applicationId`
35
123
 
36
- Once the tracker is added, any event it emits will be sent to New Relic and processed by the following functions:
124
+ ### Info Object (Required)
125
+
126
+ Obtained through the New Relic onboarding process. Two configuration modes are supported:
127
+
128
+ **Mode 1 — With Application ID and Beacon:**
37
129
 
38
130
  ```javascript
39
- tracker.sendVideoAction("VideoEvent", { data: 1 });
40
- tracker.sendVideoAdAction("AdEvent", { data: "test-1" });
41
- tracker.sendVideoErrorAction("ErrorEvent", { data: "error-test" });
42
- tracker.sendVideoCustomAction("CustomEvent", { data: "custom-test" });
131
+ info: {
132
+ licenseKey: 'YOUR_LICENSE_KEY', // Required
133
+ applicationID: 'YOUR_APPLICATION_ID', // Required
134
+ beacon: 'bam.nr-data.net', // Required when applicationID is provided
135
+ }
43
136
  ```
44
137
 
138
+ **Mode 2 — With App Name and Region:**
139
+
140
+ ```javascript
141
+ info: {
142
+ licenseKey: 'YOUR_LICENSE_KEY', // Required
143
+ appName: 'My Video App', // Required when no applicationID
144
+ region: 'US', // Required when no applicationID
145
+ }
146
+ ```
147
+
148
+ **Valid Beacon Endpoints:**
149
+
150
+ | Region | Beacon |
151
+ |---|---|
152
+ | US | `bam.nr-data.net`, `bam-cell.nr-data.net` |
153
+ | EU | `bam.eu01.nr-data.net` |
154
+ | Staging | `staging-bam-cell.nr-data.net` |
155
+ | GOV | `gov-bam.nr-data.net` |
156
+
157
+ ### Config Object (Optional)
158
+
159
+ | Option | Type | Default | Description |
160
+ |---|---|---|---|
161
+ | `qoeAggregate` | `boolean` | `false` | Enable Quality of Experience event aggregation. |
162
+ | `qoeIntervalFactor` | `number` | `1` | Include QoE aggregate events once every N harvest cycles. Must be a positive integer. QoE events are always sent on the first and final harvest cycles. |
163
+ | `obfuscate` | `array` | `[]` | Regex-based rules to mask sensitive data before transmission. See [Obfuscation Rules](#obfuscation-rules). |
164
+
165
+ ---
166
+
167
+ ## Exposed API
168
+
169
+ All exports are available under the `nrvideo` namespace (UMD) or as named imports:
170
+
171
+ ```javascript
172
+ import nrvideo from '@newrelic/video-core';
173
+
174
+ // Available: nrvideo.Core, nrvideo.VideoTracker, nrvideo.Tracker,
175
+ // nrvideo.Emitter, nrvideo.VideoTrackerState, nrvideo.Chrono,
176
+ // nrvideo.Log, nrvideo.Constants, nrvideo.version,
177
+ // nrvideo.NrVideoEventAggregator, nrvideo.RetryQueueHandler,
178
+ // nrvideo.OptimizedHttpClient, nrvideo.HarvestScheduler,
179
+ // nrvideo.recordEvent
180
+ ```
181
+
182
+ ### Core
183
+
184
+ Static class managing tracker registration and event dispatch.
185
+
186
+ | Method | Description |
187
+ |---|---|
188
+ | `Core.addTracker(tracker, options)` | Registers a tracker and initializes video analytics config. Starts event reporting. |
189
+ | `Core.removeTracker(tracker)` | Disposes and removes a tracker. Stops its event reporting. |
190
+ | `Core.getTrackers()` | Returns the array of currently registered trackers. |
191
+ | `Core.send(eventType, actionName, data)` | Sends an event to the collector. Called internally by event handlers. |
192
+ | `Core.sendError(att)` | Sends a `VideoErrorAction` with `actionName: "ERROR"`. For external/app-level errors. |
193
+ | `Core.forceHarvest()` | Forces an immediate harvest of all pending events. Returns a `Promise`. |
194
+
195
+ ### VideoTracker
196
+
197
+ Base class for video player trackers. Extends `Tracker`.
198
+
199
+ | Method | Description |
200
+ |---|---|
201
+ | `constructor(player, options)` | Initializes the tracker. Lifecycle: constructor → `setOptions` → `setPlayer` → `registerListeners`. |
202
+ | `setPlayer(player, tag)` | Sets the player and optional DOM element. Calls `registerListeners()`. |
203
+ | `setOptions(options)` | Configures tracker options (heartbeat, customData, adsTracker, isAd, parentTracker). |
204
+ | `setAdsTracker(tracker)` | Sets a child ad tracker. Ad events are funneled through the parent. |
205
+ | `setUserId(userId)` | Sets a user identifier included as `enduser.id` in all events. |
206
+ | `setHarvestInterval(interval)` | Updates the harvest cycle interval in milliseconds. |
207
+ | `dispose()` | Stops heartbeat, disposes ad tracker, unregisters listeners, clears references. |
208
+ | `registerListeners()` | **Override this.** Attach player event listeners and map to `send*()` methods. |
209
+ | `unregisterListeners()` | **Override this.** Detach player event listeners. |
210
+ | `getAttributes(att)` | **Do NOT override.** Collects all video/ad attributes. Use getter methods instead. |
211
+
212
+ **State-changing methods** (call these from `registerListeners`):
213
+
214
+ | Method | Event Emitted (Content / Ad) | Description |
215
+ |---|---|---|
216
+ | `sendPlayerReady(att)` | `PLAYER_READY` | Player is initialized and ready. |
217
+ | `sendRequest(att)` | `CONTENT_REQUEST` / `AD_REQUEST` | Playback has been requested. |
218
+ | `sendStart(att)` | `CONTENT_START` / `AD_START` | First frame rendered. Starts heartbeat. |
219
+ | `sendEnd(att)` | `CONTENT_END` / `AD_END` | Playback ended. Stops heartbeat. |
220
+ | `sendPause(att)` | `CONTENT_PAUSE` / `AD_PAUSE` | Playback paused. |
221
+ | `sendResume(att)` | `CONTENT_RESUME` / `AD_RESUME` | Playback resumed. |
222
+ | `sendBufferStart(att)` | `CONTENT_BUFFER_START` / `AD_BUFFER_START` | Buffering started. |
223
+ | `sendBufferEnd(att)` | `CONTENT_BUFFER_END` / `AD_BUFFER_END` | Buffering ended. |
224
+ | `sendSeekStart(att)` | `CONTENT_SEEK_START` / `AD_SEEK_START` | Seek started. |
225
+ | `sendSeekEnd(att)` | `CONTENT_SEEK_END` / `AD_SEEK_END` | Seek ended. |
226
+ | `sendError(att)` | `CONTENT_ERROR` / `AD_ERROR` | Error occurred during playback. |
227
+ | `sendRenditionChanged(att)` | `CONTENT_RENDITION_CHANGE` / `AD_RENDITION_CHANGE` | Stream quality changed. |
228
+ | `sendDownload(att)` | `DOWNLOAD` | Download event. Requires `att.state`. |
229
+ | `sendHeartbeat(att)` | `CONTENT_HEARTBEAT` / `AD_HEARTBEAT` | Sent automatically every 30s (2s for ads). |
230
+ | `sendAdBreakStart(att)` | `AD_BREAK_START` | Ad break started (ads only). |
231
+ | `sendAdBreakEnd(att)` | `AD_BREAK_END` | Ad break ended (ads only). |
232
+ | `sendAdQuartile(att)` | `AD_QUARTILE` | Ad quartile reached. Requires `att.quartile`. |
233
+ | `sendAdClick(att)` | `AD_CLICK` | Ad clicked. Requires `att.url`. |
234
+ | `sendCustom(actionName, timeSinceAttName, att)` | Custom `VideoCustomAction` | Sends a custom event with a `timeSince` attribute. |
235
+
236
+ ### Tracker
237
+
238
+ Base class providing heartbeat, custom data, and attribute management. Extends `Emitter`.
239
+
240
+ | Method | Description |
241
+ |---|---|
242
+ | `setOptions(options)` | Set heartbeat interval, customData, and parentTracker. |
243
+ | `getHeartbeat()` | Returns heartbeat interval in ms. Default: 30000 (content), 2000 (ads). |
244
+ | `startHeartbeat()` | Starts the heartbeat interval. Called automatically on `sendStart`. |
245
+ | `stopHeartbeat()` | Stops the heartbeat interval. Called automatically on `sendEnd`. |
246
+ | `getAttributes(att)` | Returns base attributes (trackerName, coreVersion, isBackgroundEvent, etc.). |
247
+ | `sendVideoAction(event, att)` | Emits a `VideoAction` event. |
248
+ | `sendVideoAdAction(event, att)` | Emits a `VideoAdAction` event. |
249
+ | `sendVideoErrorAction(event, att)` | Emits a `VideoErrorAction` event. |
250
+ | `sendVideoCustomAction(event, att)` | Emits a `VideoCustomAction` event. |
251
+
252
+ ### Emitter
253
+
254
+ Event system base class.
255
+
256
+ | Method | Description |
257
+ |---|---|
258
+ | `on(event, callback)` | Subscribe to an event. Use `'*'` to listen to all events. |
259
+ | `off(event, callback)` | Unsubscribe from an event. |
260
+ | `emit(eventType, event, data)` | Emit an event to all subscribers. |
261
+
262
+ ### VideoTrackerState
263
+
264
+ Internal state machine managing view lifecycle, QoE KPIs, and timing.
265
+
266
+ | Property | Description |
267
+ |---|---|
268
+ | `numberOfErrors` | Error count for current view. |
269
+ | `numberOfAds` | Total ads shown. |
270
+ | `numberOfVideos` | Total videos played. |
271
+ | `totalPlaytime` | Content viewing time in ms (excludes pausing, buffering, ads). |
272
+ | `totalAdPlaytime` | Ad viewing time in ms. |
273
+ | `startupTime` | Time from `CONTENT_REQUEST` to `CONTENT_START` in ms. |
274
+ | `peakBitrate` | Maximum bitrate observed during playback. |
275
+
276
+ ### Chrono
277
+
278
+ Utility class for measuring time lapses.
279
+
280
+ | Method | Description |
281
+ |---|---|
282
+ | `start()` | Start the timer. |
283
+ | `stop()` | Stop the timer and return delta. |
284
+ | `getDeltaTime()` | Get elapsed time since `start()` in ms. |
285
+ | `getDuration()` | Get accumulated duration across multiple start/stop cycles. |
286
+ | `reset()` | Reset all values. |
287
+ | `clone()` | Create a copy of the chrono. |
288
+
289
+ ### Log
290
+
291
+ Static logging utility with configurable levels.
292
+
293
+ | Method | Description |
294
+ |---|---|
295
+ | `Log.error(...msg)` | Log an error. |
296
+ | `Log.warn(...msg)` | Log a warning. |
297
+ | `Log.notice(...msg)` | Log a notice. |
298
+ | `Log.debug(...msg)` | Log a debug message. |
299
+ | `Log.debugCommonVideoEvents(player, extraEvents, report)` | Attach debug listeners for common HTML5 video events. |
300
+
301
+ **Log Levels** (set via `Log.level`):
302
+
303
+ ```javascript
304
+ nrvideo.Log.level = nrvideo.Log.Levels.DEBUG; // ALL, DEBUG, NOTICE, WARNING, ERROR, SILENT
305
+ ```
306
+
307
+ ### Constants
308
+
309
+ | Constant | Value | Description |
310
+ |---|---|---|
311
+ | `Constants.AdPositions` | `{ PRE, MID, POST }` | Ad position enum. |
312
+ | `Constants.COLLECTOR` | Object | Beacon endpoint URLs by region. |
313
+ | `Constants.VALID_EVENT_TYPES` | Array | `['VideoAction', 'VideoAdAction', 'VideoErrorAction', 'VideoCustomAction']` |
314
+ | `Constants.MAX_PAYLOAD_SIZE` | `1048576` | Maximum payload size (1 MB). |
315
+ | `Constants.MAX_BEACON_SIZE` | `61440` | Maximum beacon size (60 KB). |
316
+ | `Constants.MAX_EVENTS_PER_BATCH` | `1000` | Maximum events per batch. |
317
+ | `Constants.INTERVAL` | `10000` | Default harvest interval (10s). |
318
+
319
+ ---
320
+
321
+ ## Building a Custom Tracker
322
+
323
+ Extend `VideoTracker` and override getter methods and listener registration:
324
+
325
+ ```javascript
326
+ class MyPlayerTracker extends nrvideo.VideoTracker {
327
+ // Required: identify your tracker
328
+ getTrackerName() { return 'my-custom-player'; }
329
+ getTrackerVersion() { return '1.0.0'; }
330
+
331
+ // Override getters to return player metadata
332
+ getVideoId() { return this.player.getContentId(); }
333
+ getTitle() { return this.player.getTitle(); }
334
+ getSrc() { return this.player.getSrc(); }
335
+ getDuration() { return this.player.getDuration() * 1000; } // in ms
336
+ getPlayhead() { return this.player.getCurrentTime() * 1000; }
337
+ getBitrate() { return this.player.getCurrentBitrate(); }
338
+ getRenditionName() { return this.player.getQualityLabel(); }
339
+ getRenditionHeight() { return this.player.getVideoHeight(); }
340
+ getRenditionWidth() { return this.player.getVideoWidth(); }
341
+ isLive() { return this.player.isLive(); }
342
+ isMuted() { return this.player.isMuted(); }
343
+ isFullscreen() { return this.player.isFullscreen(); }
344
+ getPlayrate() { return this.player.getPlaybackRate(); }
345
+ getPlayerName() { return 'My Player'; }
346
+ getPlayerVersion() { return this.player.version; }
347
+
348
+ // Map player events to tracker methods
349
+ registerListeners() {
350
+ this.player.on('play', () => this.sendRequest());
351
+ this.player.on('playing', () => this.sendStart());
352
+ this.player.on('pause', () => this.sendPause());
353
+ this.player.on('ended', () => this.sendEnd());
354
+ this.player.on('waiting', () => this.sendBufferStart());
355
+ this.player.on('canplay', () => this.sendBufferEnd());
356
+ this.player.on('seeking', () => this.sendSeekStart());
357
+ this.player.on('seeked', () => this.sendSeekEnd());
358
+ this.player.on('error', (e) => this.sendError({
359
+ errorName: e.message,
360
+ errorCode: e.code
361
+ }));
362
+ }
363
+
364
+ unregisterListeners() {
365
+ this.player.off('play');
366
+ this.player.off('playing');
367
+ // ... remove all listeners
368
+ }
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Getter Methods (Override These)
375
+
376
+ These methods return `null` by default. Override them in your tracker to provide player-specific data:
377
+
378
+ | Getter | Attribute Set | Description |
379
+ |---|---|---|
380
+ | `getVideoId()` | `contentId` / `adId` | Content or ad identifier. |
381
+ | `getTitle()` | `contentTitle` / `adTitle` | Content or ad title. |
382
+ | `getSrc()` | `contentSrc` / `adSrc` | Media source URL. |
383
+ | `getDuration()` | `contentDuration` / `adDuration` | Duration in ms. |
384
+ | `getPlayhead()` | `contentPlayhead` / `adPlayhead` | Current playback position in ms. |
385
+ | `getBitrate()` | `contentBitrate` / `adBitrate` | Current bitrate in bits/s. |
386
+ | `getManifestBitrate()` | `contentManifestBitrate` | Manifest/playlist declared bitrate in bps. |
387
+ | `getSegmentDownloadBitrate()` | `contentSegmentDownloadBitrate` | Measured bitrate from segment download in bps. |
388
+ | `getNetworkDownloadBitrate()` | `contentNetworkDownloadBitrate` | Network download throughput in bps. |
389
+ | `getRenditionName()` | `contentRenditionName` / `adRenditionName` | Quality label (e.g., "1080p"). |
390
+ | `getRenditionHeight()` | `contentRenditionHeight` / `adRenditionHeight` | Rendition height in px. |
391
+ | `getRenditionWidth()` | `contentRenditionWidth` / `adRenditionWidth` | Rendition width in px. |
392
+ | `isLive()` | `contentIsLive` | `true` if live stream. |
393
+ | `isMuted()` | `contentIsMuted` / `adIsMuted` | `true` if muted. |
394
+ | `isFullscreen()` | `contentIsFullscreen` | `true` if fullscreen. |
395
+ | `getPlayrate()` | `contentPlayrate` | Playback speed (1.0 = normal). |
396
+ | `getLanguage()` | `contentLanguage` / `adLanguage` | Language in locale notation (e.g., `en_US`). |
397
+ | `getCdn()` | `contentCdn` / `adCdn` | CDN serving the content. |
398
+ | `getFps()` | `contentFps` / `adFps` | Current frames per second. |
399
+ | `isAutoplayed()` | `contentIsAutoplayed` | `true` if autoplayed. |
400
+ | `getPreload()` | `contentPreload` | Preload attribute value. |
401
+ | `getPlayerName()` | `playerName` | Player name. |
402
+ | `getPlayerVersion()` | `playerVersion` | Player version. |
403
+ | `getAdQuartile()` | `adQuartile` | Ad quartile (0–4). |
404
+ | `getAdPosition()` | `adPosition` | Ad position (`pre`, `mid`, `post`). |
405
+ | `getAdPartner()` | `adPartner` | Ad partner (e.g., `ima`, `freewheel`). |
406
+ | `getAdCreativeId()` | `adCreativeId` | Ad creative identifier. |
407
+
408
+ ---
409
+
410
+ ## Quality of Experience (QoE)
411
+
412
+ When `qoeAggregate` is enabled, the library tracks and reports QoE KPI metrics as `QOE_AGGREGATE` events:
413
+
414
+ | KPI | Description |
415
+ |---|---|
416
+ | `startupTime` | Time from `CONTENT_REQUEST` to `CONTENT_START` in ms. |
417
+ | `peakBitrate` | Maximum bitrate observed during playback. |
418
+ | `averageBitrate` | Playtime-weighted average bitrate in bps. |
419
+ | `totalPlaytime` | Total content viewing time in ms (excludes pausing, buffering, ads). |
420
+ | `totalRebufferingTime` | Total ms spent rebuffering (excludes initial buffering). |
421
+ | `rebufferingRatio` | `(totalRebufferingTime / totalPlaytime) × 100`. |
422
+ | `hadStartupError` | `true` if error occurred before `CONTENT_START`. |
423
+ | `hadPlaybackError` | `true` if error occurred at any time during playback. |
424
+
425
+ QoE KPIs are refreshed before each harvest drain and always included in the final harvest at `CONTENT_END`.
426
+
427
+ ---
428
+
429
+ ## Obfuscation Rules
430
+
431
+ Mask sensitive data in event payloads before transmission using regex-based rules:
432
+
433
+ ```javascript
434
+ config: {
435
+ obfuscate: [
436
+ { regex: /\/user\/[^\/?"]+/, replacement: '/user/[REDACTED]' },
437
+ { regex: /token=[^&"]+/, replacement: 'token=[MASKED]' },
438
+ ]
439
+ }
440
+ ```
441
+
442
+ | Field | Type | Description |
443
+ |---|---|---|
444
+ | `regex` | `string` \| `RegExp` | Pattern to match in the serialized JSON payload. |
445
+ | `replacement` | `string` | Replacement string for each match. |
446
+
447
+ Rules are applied sequentially — the output of one rule feeds into the next. Invalid regex patterns are skipped with a warning logged via `Log.warn`.
448
+
449
+ ---
450
+
451
+ ## Build & Development
452
+
453
+ ```bash
454
+ # Install dependencies
455
+ npm install
456
+
457
+ # Production build
458
+ npm run build
459
+
460
+ # Development build (unminified)
461
+ npm run build:dev
462
+
463
+ # Watch mode (production)
464
+ npm run watch
465
+
466
+ # Watch mode (development)
467
+ npm run watch:dev
468
+
469
+ # Clean build artifacts
470
+ npm run clean
471
+ ```
472
+
473
+ ## Distribution Formats
474
+
475
+ The build produces three output formats:
476
+
477
+ | Format | Path | Usage |
478
+ |---|---|---|
479
+ | **UMD** | `dist/umd/nrvideo.min.js` | `<script>` tag → `window.nrvideo` |
480
+ | **CommonJS** | `dist/cjs/index.js` | `require('@newrelic/video-core')` |
481
+ | **ES Module** | `dist/esm/index.js` | `import nrvideo from '@newrelic/video-core'` |
482
+
483
+ ## Testing
484
+
485
+ ```bash
486
+ # Run tests with coverage
487
+ npm test
488
+ ```
489
+
490
+ Tests are written using [Jest](https://jestjs.io/) and located in the `test/` directory.
491
+
492
+ ---
493
+
45
494
  ## Data Model
46
495
 
47
- To understand which actions and attributes are captured and emitted by the tracker under different event types, see [DataModel.md](DATAMODEL.md).
496
+ For a comprehensive reference of all event types, attributes, and action names, see [DATAMODEL.md](DATAMODEL.md).
497
+
498
+ ---
499
+
500
+ ## License
501
+
502
+ This project is licensed under the [Apache 2.0 License](LICENSE).
503
+
504
+ ---
505
+
506
+ ## Support
507
+
508
+ Should you need assistance with New Relic products, you are in good hands with several support channels.
509
+
510
+ If the issue has been confirmed as a bug or is a feature request, please file a [GitHub issue](../../issues).
511
+
512
+ **Support Channels:**
513
+
514
+ - [New Relic Documentation](https://docs.newrelic.com): Comprehensive guidance for using our platform
515
+ - [New Relic Community](https://discuss.newrelic.com): The best place to engage in troubleshooting questions
516
+ - [New Relic University](https://learn.newrelic.com): A range of online training for New Relic users of every level
517
+ - [New Relic Technical Support](https://support.newrelic.com): 24/7/365 ticketed support. Read more about our Technical Support Offerings
518
+
519
+ **Additional Resources:**
520
+
521
+ - [DATAMODEL.md](DATAMODEL.md) — Complete event and attribute reference
522
+ - [DEVELOPING.md](DEVELOPING.md) — Building and testing instructions
523
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — Contribution guidelines
524
+
525
+ ---
48
526
 
49
- ## Documentation
527
+ ## Contributing
50
528
 
51
- All classes are documented using autodocs. The documents, generated with [jsdoc](https://github.com/jsdoc/jsdoc), can be found in the `documentation` directory of the current repo.
529
+ We encourage your contributions to improve the Video Core JS library! Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project.
52
530
 
53
- # Support
531
+ If you have any questions, or to execute our corporate CLA (which is required if your contribution is on behalf of a company), drop us an email at opensource@newrelic.com.
54
532
 
55
- New Relic has open-sourced this project. This project is provided AS-IS WITHOUT WARRANTY OR DEDICATED SUPPORT. Issues and contributions should be reported to the project here on GitHub.
533
+ For more details on how best to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md).
56
534
 
57
- We encourage you to bring your experiences and questions to the [Explorers Hub](https://discuss.newrelic.com) where our community members collaborate on solutions and new ideas.
535
+ ---
58
536
 
59
- ## Community
537
+ ## A Note About Vulnerabilities
60
538
 
61
- New Relic hosts and moderates an online forum where customers can interact with New Relic employees as well as other customers to get help and share best practices. Like all official New Relic open source projects, there's a related Community topic in the New Relic Explorers Hub. You can find this project's topic/threads here:
539
+ As noted in our [security policy](../../security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals.
62
540
 
63
- https://discuss.newrelic.com/t/video-core-js-tracker/100303
541
+ If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through our [bug bounty program](https://docs.newrelic.com/docs/security/security-privacy/information-security/report-security-vulnerabilities/).
64
542
 
65
- ## Issues / enhancement requests
543
+ ---
66
544
 
67
- Issues and enhancement requests can be submitted in the [Issues tab of this repository](../../issues). Please search for and review the existing open issues before submitting a new issue.
545
+ ## Acknowledgments
68
546
 
69
- # Contributing
547
+ If you would like to contribute to this project, review [these guidelines](CONTRIBUTING.md).
70
548
 
71
- Contributions are encouraged! If you submit an enhancement request, we'll invite you to contribute the change yourself. Please review our [Contributors Guide](CONTRIBUTING.md).
549
+ To all contributors, we thank you! Without your contribution, this project would not be what it is today.
72
550
 
73
- Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource+videoagent@newrelic.com.
551
+ ---
74
552
 
75
- # License
553
+ ## License
76
554
 
77
- This project is distributed under the [Apache 2.0](https://apache.org/licenses/LICENSE-2.0.txt) License.
555
+ The Video Core JS library is licensed under the [Apache 2.0 License](LICENSE).
78
556
 
79
- The video-core also uses source code from third-party libraries. Full details on which libraries are used and the terms under which they are licensed can be found in the [third-party notices document](THIRD_PARTY_NOTICES.md).
557
+ The Video Core JS library also uses source code from third-party libraries. Full details on which libraries are used and the terms under which they are licensed can be found in the [third-party notices document](THIRD_PARTY_NOTICES.md).