@newrelic/video-core 4.1.8-beta → 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/CHANGELOG.md +65 -15
- package/README.md +514 -44
- 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 +9 -2
- package/src/agent.js +57 -1
- package/src/constants.js +6 -0
- package/src/eventAggregator.js +48 -0
- package/src/harvestScheduler.js +86 -3
- package/src/obfuscate.js +33 -0
- package/src/optimizedHttpClient.js +6 -2
- package/src/videoConfiguration.js +38 -1
- package/src/videotracker.js +80 -29
- package/src/videotrackerstate.js +156 -31
package/src/videotrackerstate.js
CHANGED
|
@@ -89,29 +89,55 @@ class VideoTrackerState {
|
|
|
89
89
|
this.peakBitrate = 0;
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
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
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
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
|
-
*
|
|
107
|
+
* Previous download bitrate value to detect changes.
|
|
103
108
|
*/
|
|
104
|
-
this.
|
|
109
|
+
this._lastDownloadBitrate = null;
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
|
-
*
|
|
112
|
+
* QOE v1.1: Total number of rendition/quality switches where bitrate increased.
|
|
108
113
|
*/
|
|
109
|
-
this.
|
|
114
|
+
this.totalSwitchUps = 0;
|
|
110
115
|
|
|
111
116
|
/**
|
|
112
|
-
*
|
|
117
|
+
* QOE v1.1: Total number of rendition/quality switches where bitrate decreased.
|
|
113
118
|
*/
|
|
114
|
-
this.
|
|
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();
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Had Startup Error: TRUE if CONTENT_ERROR occurs before CONTENT_START.
|
|
134
|
+
*/
|
|
135
|
+
this.hadStartupError = false;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Had Playback Error: TRUE if CONTENT_ERROR occurs during content playback.
|
|
139
|
+
*/
|
|
140
|
+
this.hadPlaybackError = false;
|
|
115
141
|
|
|
116
142
|
/**
|
|
117
143
|
* The amount of ms the user has been rebuffering during content playback.
|
|
@@ -347,23 +373,45 @@ class VideoTrackerState {
|
|
|
347
373
|
const kpi = {};
|
|
348
374
|
|
|
349
375
|
try {
|
|
350
|
-
// QoE KPIs
|
|
376
|
+
// QoE v1 KPIs
|
|
351
377
|
if (this.startupTime !== null) {
|
|
352
378
|
kpi["startupTime"] = this.startupTime;
|
|
353
379
|
}
|
|
354
380
|
if (this.peakBitrate > 0) {
|
|
355
381
|
kpi["peakBitrate"] = this.peakBitrate;
|
|
356
382
|
}
|
|
357
|
-
kpi["
|
|
358
|
-
kpi["
|
|
383
|
+
kpi["hadStartupError"] = this.hadStartupError;
|
|
384
|
+
kpi["hadPlaybackError"] = this.hadPlaybackError;
|
|
359
385
|
kpi["totalRebufferingTime"] = this.totalRebufferingTime;
|
|
360
|
-
// Calculate rebuffering ratio as percentage (avoid division by zero)
|
|
361
386
|
kpi["rebufferingRatio"] = this.totalPlaytime > 0
|
|
362
387
|
? (this.totalRebufferingTime / this.totalPlaytime) * 100
|
|
363
388
|
: 0;
|
|
364
389
|
kpi["totalPlaytime"] = this.totalPlaytime;
|
|
365
390
|
kpi["averageBitrate"] = this.weightedBitrate;
|
|
366
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
|
+
|
|
367
415
|
} catch (error) {
|
|
368
416
|
Log.error("Failed to add attributes for QOE KPIs", error.message);
|
|
369
417
|
}
|
|
@@ -549,8 +597,10 @@ class VideoTrackerState {
|
|
|
549
597
|
}
|
|
550
598
|
|
|
551
599
|
// Accumulate total rebuffering time for content only
|
|
600
|
+
// Use exact stopped duration (stopTime - startTime) instead of live getDeltaTime()
|
|
552
601
|
if (!this.isAd() && this.initialBufferingHappened) {
|
|
553
|
-
this.totalRebufferingTime +=
|
|
602
|
+
this.totalRebufferingTime +=
|
|
603
|
+
(this.timeSinceBufferBegin.stopTime - this.timeSinceBufferBegin.startTime);
|
|
554
604
|
}
|
|
555
605
|
|
|
556
606
|
return true;
|
|
@@ -670,13 +720,13 @@ class VideoTrackerState {
|
|
|
670
720
|
} else {
|
|
671
721
|
this.timeSinceLastError.start();
|
|
672
722
|
|
|
673
|
-
// Track
|
|
674
|
-
// Had Startup
|
|
723
|
+
// Track error flags for content errors only
|
|
724
|
+
// Had Startup Error: error before content started
|
|
675
725
|
if (!this.isStarted) {
|
|
676
|
-
this.
|
|
726
|
+
this.hadStartupError = true;
|
|
677
727
|
} else {
|
|
678
|
-
// Had Playback
|
|
679
|
-
this.
|
|
728
|
+
// Had Playback Error: any content error during playback
|
|
729
|
+
this.hadPlaybackError = true;
|
|
680
730
|
}
|
|
681
731
|
}
|
|
682
732
|
}
|
|
@@ -689,38 +739,113 @@ class VideoTrackerState {
|
|
|
689
739
|
}
|
|
690
740
|
|
|
691
741
|
/**
|
|
692
|
-
* Updates peak bitrate
|
|
742
|
+
* Updates peak bitrate and time-weighted average bitrate (content only).
|
|
693
743
|
* @param {number} bitrate Current content bitrate in bps.
|
|
694
744
|
*/
|
|
695
745
|
trackContentBitrateState(bitrate) {
|
|
696
746
|
if (bitrate && typeof bitrate === "number") {
|
|
697
747
|
this.peakBitrate = Math.max(this.peakBitrate, bitrate);
|
|
698
748
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
749
|
+
const now = Date.now();
|
|
750
|
+
|
|
751
|
+
// If not playing (buffering, paused, etc.), close the current segment
|
|
752
|
+
// so non-play time is excluded from the weighted average
|
|
753
|
+
if (!this.isPlaying) {
|
|
754
|
+
if (this._lastBitrate !== null && this._lastBitrateChangeTimestamp !== null) {
|
|
755
|
+
const segmentDuration = now - this._lastBitrateChangeTimestamp;
|
|
756
|
+
if (segmentDuration > 0) {
|
|
757
|
+
this.partialAverageBitrate += this._lastBitrate * segmentDuration;
|
|
758
|
+
this._totalBitrateDuration += segmentDuration;
|
|
759
|
+
}
|
|
760
|
+
this._lastBitrateChangeTimestamp = null; // Mark as paused
|
|
761
|
+
}
|
|
704
762
|
this._lastBitrate = bitrate;
|
|
705
|
-
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Playing: restart timestamp if returning from non-play state
|
|
767
|
+
if (this._lastBitrateChangeTimestamp === null && this._lastBitrate !== null) {
|
|
768
|
+
this._lastBitrateChangeTimestamp = now;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Close the PREVIOUS segment when bitrate changes
|
|
772
|
+
if (this._lastBitrate !== null && this._lastBitrate !== bitrate
|
|
773
|
+
&& this._lastBitrateChangeTimestamp !== null) {
|
|
774
|
+
const segmentDuration = now - this._lastBitrateChangeTimestamp;
|
|
775
|
+
if (segmentDuration > 0) {
|
|
776
|
+
this.partialAverageBitrate += this._lastBitrate * segmentDuration;
|
|
777
|
+
this._totalBitrateDuration += segmentDuration;
|
|
778
|
+
}
|
|
779
|
+
this._lastBitrateChangeTimestamp = now;
|
|
706
780
|
}
|
|
781
|
+
|
|
782
|
+
// Initialize timestamp on first observation
|
|
783
|
+
if (this._lastBitrateChangeTimestamp === null) {
|
|
784
|
+
this._lastBitrateChangeTimestamp = now;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
this._lastBitrate = bitrate;
|
|
788
|
+
|
|
789
|
+
// Compute weighted average including current in-progress segment
|
|
790
|
+
let totalWeighted = this.partialAverageBitrate;
|
|
791
|
+
let totalDuration = this._totalBitrateDuration;
|
|
792
|
+
const currentSegment = now - this._lastBitrateChangeTimestamp;
|
|
793
|
+
if (currentSegment > 0) {
|
|
794
|
+
totalWeighted += bitrate * currentSegment;
|
|
795
|
+
totalDuration += currentSegment;
|
|
796
|
+
}
|
|
797
|
+
this.weightedBitrate = totalDuration > 0
|
|
798
|
+
? Math.round(totalWeighted / totalDuration)
|
|
799
|
+
: bitrate;
|
|
707
800
|
}
|
|
708
801
|
}
|
|
709
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
|
+
|
|
710
827
|
/**
|
|
711
828
|
* Resets tracked variable for view id change
|
|
712
829
|
* */
|
|
713
830
|
resetViewIdTrackedState() {
|
|
714
831
|
this.peakBitrate = 0;
|
|
715
|
-
this.partialAverageBitrate = 0;
|
|
716
832
|
this.startupTime = null;
|
|
833
|
+
this.partialAverageBitrate = 0;
|
|
834
|
+
this._totalBitrateDuration = 0;
|
|
835
|
+
this.weightedBitrate = 0;
|
|
717
836
|
this._lastBitrate = null;
|
|
718
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();
|
|
719
844
|
}
|
|
720
845
|
|
|
721
846
|
/** Methods to manage total ads time chrono */
|
|
722
847
|
clearTotalAdsTime() {
|
|
723
|
-
|
|
848
|
+
Log.debug("clear total ads time", this.totalAdTime);
|
|
724
849
|
this._totalAdPlaytime.reset();
|
|
725
850
|
}
|
|
726
851
|
|
|
@@ -729,12 +854,12 @@ class VideoTrackerState {
|
|
|
729
854
|
}
|
|
730
855
|
|
|
731
856
|
startAdsTime() {
|
|
732
|
-
|
|
857
|
+
Log.debug("startAdsTime");
|
|
733
858
|
return this._totalAdPlaytime.start();
|
|
734
859
|
}
|
|
735
860
|
|
|
736
861
|
stopAdsTime() {
|
|
737
|
-
|
|
862
|
+
Log.debug("stopAdsTime");
|
|
738
863
|
return this._totalAdPlaytime.stop();
|
|
739
864
|
}
|
|
740
865
|
|