@newrelic/video-core 4.1.7 → 4.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/video-core",
3
- "version": "4.1.7",
3
+ "version": "4.1.8",
4
4
  "description": "New Relic video tracking core library",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -289,7 +289,7 @@ class VideoTracker extends Tracker {
289
289
  * saving the current rendition and thus preventing interferences with RENDITION_CHANGE events.
290
290
  */
291
291
  getRenditionShift(saveNewRendition) {
292
- let current = this.getRenditionBitrate();
292
+ let current = this.getManifestBitrate();
293
293
  let last;
294
294
  if (this.isAd()) {
295
295
  last = this._lastAdRendition;
@@ -544,26 +544,24 @@ class VideoTracker extends Tracker {
544
544
 
545
545
  if(this.state.isStarted && !this.isAd()) {
546
546
  this.state.trackContentBitrateState(att.contentBitrate);
547
+ this.state.trackDownloadRate(att.contentNetworkDownloadBitrate);
548
+ this.state.addPlayedRendition(att.contentRenditionHeight, att.contentRenditionWidth);
547
549
  }
548
550
 
549
551
  for (let key in this.customData) {
550
552
  att[key] = this.customData[key];
551
553
  }
552
554
 
553
- /**
554
- * Adds all the attributes and custom attributes for qoe event
555
- */
556
- this.addQoeAttributes(att);
557
-
558
- return att;
559
- }
560
-
561
- addQoeAttributes(att) {
562
- att = this.state.getQoeAttributes(att);
555
+ // Add QOE v1.1 attributes (for content only, not ads)
556
+ if (!this.isAd()) {
557
+ this.state.getQoeAttributes(att);
563
558
  const qoe = att.qoe;
564
559
  for (let key in this.customData) {
565
- qoe[key] = this.customData[key];
560
+ qoe[key] = this.customData[key];
566
561
  }
562
+ }
563
+
564
+ return att;
567
565
  }
568
566
 
569
567
  /**
@@ -669,7 +667,10 @@ class VideoTracker extends Tracker {
669
667
  ev = VideoTracker.Events.AD_END;
670
668
  att.timeSinceAdRequested = this.state.timeSinceRequested.getDeltaTime();
671
669
  att.timeSinceAdStarted = this.state.timeSinceStarted.getDeltaTime();
672
- if (this.parentTracker) this.parentTracker.state.isPlaying = true;
670
+ if (this.parentTracker) {
671
+ this.parentTracker.state.isPlaying = true;
672
+ this.parentTracker.state.timeSincePaused.reset();
673
+ }
673
674
  this.state.stopAdsTime();
674
675
  } else {
675
676
  ev = VideoTracker.Events.CONTENT_END;
@@ -713,8 +714,6 @@ class VideoTracker extends Tracker {
713
714
  this.isAd()
714
715
  ? this.sendVideoAdAction(ev, att)
715
716
  : this.sendVideoAction(ev, att);
716
-
717
- //this.send(ev, att);
718
717
  }
719
718
  }
720
719
 
@@ -899,7 +898,25 @@ class VideoTracker extends Tracker {
899
898
  att = att || {};
900
899
  att.timeSinceLastRenditionChange =
901
900
  this.state.timeSinceLastRenditionChange.getDeltaTime();
902
- att.shift = this.getRenditionShift(true);
901
+
902
+ const {oldBitrate, newBitrate} = att;
903
+
904
+ if (!this.isAd() && oldBitrate !== undefined && newBitrate !== undefined) {
905
+ if (newBitrate > oldBitrate) {
906
+ att.shift = "up";
907
+ this.state.totalSwitchUps += 1;
908
+ } else if (newBitrate < oldBitrate) {
909
+ att.shift = "down";
910
+ this.state.totalSwitchDowns += 1;
911
+ } else {
912
+ att.shift = null;
913
+ }
914
+ }
915
+
916
+ // Remove internal tracking fields before sending to analytics
917
+ delete att.oldBitrate;
918
+ delete att.newBitrate;
919
+
903
920
  let ev;
904
921
  if (this.isAd()) {
905
922
  ev = VideoTracker.Events.AD_RENDITION_CHANGE;
@@ -89,24 +89,45 @@ class VideoTrackerState {
89
89
  this.peakBitrate = 0;
90
90
 
91
91
  /**
92
- * Last tracked bitrate
92
+ * Time-weighted bitrate calculation accumulators
93
93
  */
94
+ this.partialAverageBitrate = 0;
95
+ this._totalBitrateDuration = 0;
96
+ this.weightedBitrate = 0;
94
97
  this._lastBitrate = null;
98
+ this._lastBitrateChangeTimestamp = null;
95
99
 
96
100
  /**
97
- * Tracks the last updated timestamp for bitrate
98
- * */
99
- this._lastBitrateChangeTimestamp = null;
101
+ * Array of changed network download bitrate values (only values that differ from previous).
102
+ * Used for calculating simple mean, min, max (not time-weighted).
103
+ */
104
+ this._downloadBitrates = [];
100
105
 
101
106
  /**
102
- * total bitrate partial value for average weighted average bitrate
107
+ * Previous download bitrate value to detect changes.
103
108
  */
104
- this.partialAverageBitrate = 0;
109
+ this._lastDownloadBitrate = null;
105
110
 
106
111
  /**
107
- * Total duration (ms) of all closed bitrate segments for weighted average
112
+ * QOE v1.1: Total number of rendition/quality switches where bitrate increased.
108
113
  */
109
- this._totalBitrateDuration = 0;
114
+ this.totalSwitchUps = 0;
115
+
116
+ /**
117
+ * QOE v1.1: Total number of rendition/quality switches where bitrate decreased.
118
+ */
119
+ this.totalSwitchDowns = 0;
120
+
121
+ /**
122
+ * QOE v1.1: Total time in milliseconds spent in paused state (intentional pauses only).
123
+ * Calculated from timeSincePaused chrono accumulation.
124
+ */
125
+ this.totalPauseTime = 0;
126
+
127
+ /**
128
+ * QOE v1.1: Set of distinct renditions played, keyed by "heightxwidth" (e.g., "1080x1920").
129
+ */
130
+ this._playedRenditions = new Set();
110
131
 
111
132
  /**
112
133
  * Had Startup Error: TRUE if CONTENT_ERROR occurs before CONTENT_START.
@@ -352,7 +373,7 @@ class VideoTrackerState {
352
373
  const kpi = {};
353
374
 
354
375
  try {
355
- // QoE KPIs - Content only
376
+ // QoE v1 KPIs
356
377
  if (this.startupTime !== null) {
357
378
  kpi["startupTime"] = this.startupTime;
358
379
  }
@@ -362,13 +383,35 @@ class VideoTrackerState {
362
383
  kpi["hadStartupError"] = this.hadStartupError;
363
384
  kpi["hadPlaybackError"] = this.hadPlaybackError;
364
385
  kpi["totalRebufferingTime"] = this.totalRebufferingTime;
365
- // Calculate rebuffering ratio as percentage (avoid division by zero)
366
386
  kpi["rebufferingRatio"] = this.totalPlaytime > 0
367
387
  ? (this.totalRebufferingTime / this.totalPlaytime) * 100
368
388
  : 0;
369
389
  kpi["totalPlaytime"] = this.totalPlaytime;
370
390
  kpi["averageBitrate"] = this.weightedBitrate;
371
391
  kpi["numberOfErrors"] = this.numberOfErrors;
392
+
393
+ // QoE v1.1 KPIs - Download rate (simple mean of changed values)
394
+ if (this._downloadBitrates.length > 0) {
395
+ const sum = this._downloadBitrates.reduce((a, b) => a + b, 0);
396
+ kpi["avgDownloadRate"] = Math.round(sum / this._downloadBitrates.length);
397
+ kpi["minDownloadRate"] = Math.min(...this._downloadBitrates);
398
+ kpi["maxDownloadRate"] = Math.max(...this._downloadBitrates);
399
+ }
400
+
401
+ // QoE v1.1 KPIs - Rendition switching
402
+ kpi["totalSwitchUps"] = this.totalSwitchUps;
403
+ kpi["totalSwitchDowns"] = this.totalSwitchDowns;
404
+
405
+ // QoE v1.1 KPIs - Pause and session timing
406
+ kpi["totalPauseTime"] = this.timeSincePaused.getDuration();
407
+ kpi["totalViewSessionTime"] = this.totalPlaytime;
408
+
409
+ // QoE v1.1 KPIs - Renditions
410
+ kpi["totalRenditions"] = this._playedRenditions.size;
411
+
412
+ // Version tag
413
+ kpi["qoeAggregateVersion"] = "1.1.0";
414
+
372
415
  } catch (error) {
373
416
  Log.error("Failed to add attributes for QOE KPIs", error.message);
374
417
  }
@@ -696,7 +739,7 @@ class VideoTrackerState {
696
739
  }
697
740
 
698
741
  /**
699
- * Updates peak bitrate with current bitrate value (content only).
742
+ * Updates peak bitrate and time-weighted average bitrate (content only).
700
743
  * @param {number} bitrate Current content bitrate in bps.
701
744
  */
702
745
  trackContentBitrateState(bitrate) {
@@ -757,16 +800,47 @@ class VideoTrackerState {
757
800
  }
758
801
  }
759
802
 
803
+ /**
804
+ * Records network download bitrate observation. Only tracks values that differ from previous.
805
+ * @param {number} bps Network throughput in bits per second.
806
+ */
807
+ trackDownloadRate(bps) {
808
+ if (!bps || typeof bps !== "number" || bps <= 0) return;
809
+ if (bps !== this._lastDownloadBitrate) {
810
+ this._downloadBitrates.push(bps);
811
+ this._lastDownloadBitrate = bps;
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Records a rendition play event (height x width combination).
817
+ * @param {number} height Rendition height in pixels.
818
+ * @param {number} width Rendition width in pixels.
819
+ */
820
+ addPlayedRendition(height, width) {
821
+ if (height && width) {
822
+ this._playedRenditions.add(`${height}x${width}`);
823
+ }
824
+ }
825
+
826
+
760
827
  /**
761
828
  * Resets tracked variable for view id change
762
829
  * */
763
830
  resetViewIdTrackedState() {
764
831
  this.peakBitrate = 0;
832
+ this.startupTime = null;
765
833
  this.partialAverageBitrate = 0;
766
834
  this._totalBitrateDuration = 0;
767
- this.startupTime = null;
835
+ this.weightedBitrate = 0;
768
836
  this._lastBitrate = null;
769
837
  this._lastBitrateChangeTimestamp = null;
838
+ this._downloadBitrates = [];
839
+ this._lastDownloadBitrate = null;
840
+ this.totalSwitchUps = 0;
841
+ this.totalSwitchDowns = 0;
842
+ this.timeSincePaused.reset();
843
+ this._playedRenditions = new Set();
770
844
  }
771
845
 
772
846
  /** Methods to manage total ads time chrono */