@newrelic/video-core 4.0.1 → 4.1.0-beta
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 +13 -0
- package/__mock__.js +82 -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 +2 -1
- package/src/agent.js +5 -0
- package/src/chrono.js +15 -0
- package/src/constants.js +6 -0
- package/src/eventAggregator.js +38 -5
- package/src/recordEvent.js +34 -6
- package/src/tracker.js +1 -0
- package/src/utils.js +24 -0
- package/src/videotracker.js +37 -0
- package/src/videotrackerstate.js +144 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/video-core",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0-beta",
|
|
4
4
|
"description": "New Relic video tracking core library",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"LICENSE",
|
|
52
52
|
"README.md",
|
|
53
53
|
"src",
|
|
54
|
+
"__mock__.js",
|
|
54
55
|
"!test"
|
|
55
56
|
],
|
|
56
57
|
"publishConfig": {
|
package/src/agent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HarvestScheduler } from "./harvestScheduler.js";
|
|
2
2
|
import { NrVideoEventAggregator } from "./eventAggregator.js";
|
|
3
3
|
import Log from "./log.js";
|
|
4
|
+
import Tracker from "./tracker";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Enhanced video analytics agent with HarvestScheduler only.
|
|
@@ -47,6 +48,10 @@ class VideoAnalyticsAgent {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
try {
|
|
51
|
+
if(eventObject.actionName && eventObject.actionName === Tracker.Events.QOE_AGGREGATE) {
|
|
52
|
+
// This makes sure that there is only one QOE aggregate event for a harvest cycle
|
|
53
|
+
return this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, eventObject);
|
|
54
|
+
}
|
|
50
55
|
return this.eventBuffer.add(eventObject);
|
|
51
56
|
} catch (error) {
|
|
52
57
|
Log.error("Failed to add event to harvesting system:", error.message);
|
package/src/chrono.js
CHANGED
|
@@ -17,6 +17,9 @@ class Chrono {
|
|
|
17
17
|
/** Stop time */
|
|
18
18
|
this.stopTime = 0;
|
|
19
19
|
|
|
20
|
+
/** accumulation of all the start and stop intervals */
|
|
21
|
+
this.accumulator = 0;
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* If you set an offset in a chrono, its value will be added getDeltaTime and stop.
|
|
22
25
|
*
|
|
@@ -59,9 +62,20 @@ class Chrono {
|
|
|
59
62
|
*/
|
|
60
63
|
stop() {
|
|
61
64
|
this.stopTime = new Date().getTime();
|
|
65
|
+
if(this.startTime < this.stopTime) {
|
|
66
|
+
this.accumulator += (this.stopTime - this.startTime);
|
|
67
|
+
}
|
|
62
68
|
return this.getDeltaTime();
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
getDuration() {
|
|
72
|
+
if(this.stopTime) {
|
|
73
|
+
return this.accumulator + this.offset;
|
|
74
|
+
} else {
|
|
75
|
+
return this.accumulator + (this.getDeltaTime() ?? 0)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
/**
|
|
66
80
|
* Creates a copy of the chrono.
|
|
67
81
|
* @returns {Chrono} Cloned chrono
|
|
@@ -71,6 +85,7 @@ class Chrono {
|
|
|
71
85
|
chrono.startTime = this.startTime;
|
|
72
86
|
chrono.stopTime = this.stopTime;
|
|
73
87
|
chrono.offset = this.offset;
|
|
88
|
+
chrono.accumulator = this.accumulator;
|
|
74
89
|
return chrono;
|
|
75
90
|
}
|
|
76
91
|
}
|
package/src/constants.js
CHANGED
|
@@ -42,4 +42,10 @@ Constants.MAX_BEACON_SIZE = 61440; // 60KB = 60 × 1024 bytes
|
|
|
42
42
|
Constants.MAX_EVENTS_PER_BATCH = 1000;
|
|
43
43
|
Constants.INTERVAL = 10000; //10 seconds
|
|
44
44
|
|
|
45
|
+
Constants.QOE_AGGREGATE_KEYS = [
|
|
46
|
+
"coreVersion", "instrumentation.name",
|
|
47
|
+
"instrumentation.provider", "instrumentation.version", "isBackgroundEvent", "playerName", "playerVersion",
|
|
48
|
+
"src", "viewId", "viewSession", "contentIsAutoplayed"
|
|
49
|
+
]
|
|
50
|
+
|
|
45
51
|
export default Constants;
|
package/src/eventAggregator.js
CHANGED
|
@@ -32,12 +32,37 @@ export class NrVideoEventAggregator {
|
|
|
32
32
|
this.onSmartHarvestTrigger = null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* If an event with the specified actionName already exists in the buffer, it will be replaced.
|
|
37
|
+
* Otherwise, the event will be added as a new entry.
|
|
38
|
+
* @param {string} actionName - The actionName to search for in the buffer
|
|
39
|
+
* @param {object} eventObject - The event object to add or use as replacement. Should contain an actionName property.
|
|
40
|
+
* @returns {boolean} True if the operation succeeded, false if an error occurred
|
|
41
|
+
*/
|
|
42
|
+
addOrReplaceByActionName(actionName, eventObject) {
|
|
43
|
+
const i = this.buffer.findIndex(e => e.actionName === actionName);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if(i === -1) {
|
|
47
|
+
this.add(eventObject);
|
|
48
|
+
} else {
|
|
49
|
+
this.add(eventObject, i);
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
Log.error("Failed to set or replace the event to buffer:", error.message);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
/**
|
|
36
60
|
* Adds an event to the unified buffer.
|
|
37
61
|
* All events are treated equally in FIFO order.
|
|
38
62
|
* @param {object} eventObject - The event to add
|
|
63
|
+
* @param {number} index - index at which the event should be replaced with
|
|
39
64
|
*/
|
|
40
|
-
add(eventObject) {
|
|
65
|
+
add(eventObject, index) {
|
|
41
66
|
try {
|
|
42
67
|
// Calculate event payload size
|
|
43
68
|
const eventSize = dataSize(eventObject);
|
|
@@ -52,10 +77,18 @@ export class NrVideoEventAggregator {
|
|
|
52
77
|
this.makeRoom(eventSize);
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
80
|
+
if(index !== undefined && index !== null && index > -1) {
|
|
81
|
+
// replace in unified buffer
|
|
82
|
+
const previousPayloadSize = dataSize(this.buffer[index]);
|
|
83
|
+
this.buffer[index] = eventObject;
|
|
84
|
+
// Updating the payload size for the replaced event
|
|
85
|
+
this.currentPayloadSize += eventSize - previousPayloadSize;
|
|
86
|
+
} else {
|
|
87
|
+
// Add to unified buffer
|
|
88
|
+
this.buffer.push(eventObject);
|
|
89
|
+
this.totalEvents++;
|
|
90
|
+
this.currentPayloadSize += eventSize;
|
|
91
|
+
}
|
|
59
92
|
|
|
60
93
|
// Check if smart harvest should be triggered
|
|
61
94
|
this.checkSmartHarvestTrigger();
|
package/src/recordEvent.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { videoAnalyticsHarvester } from "./agent.js";
|
|
2
2
|
import Constants from "./constants.js";
|
|
3
3
|
import Log from "./log.js";
|
|
4
|
+
import Tracker from "./tracker";
|
|
5
|
+
import {getObjectEntriesForKeys} from "./utils";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Enhanced record event function with validation, enrichment, and unified handling.
|
|
@@ -21,18 +23,44 @@ export function recordEvent(eventType, attributes = {}) {
|
|
|
21
23
|
|
|
22
24
|
const { appName, applicationID } = window.NRVIDEO.info;
|
|
23
25
|
|
|
26
|
+
const { qoe, ...eventAttributes } = attributes;
|
|
27
|
+
const qoeAttrs = qoe ? { ...qoe } : {};
|
|
28
|
+
|
|
29
|
+
const otherAttrs = {
|
|
30
|
+
...(applicationID ? {} : { appName }), // Only include appName when no applicationID
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
timeSinceLoad: window.performance
|
|
33
|
+
? window.performance.now() / 1000
|
|
34
|
+
: null,
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
const eventObject = {
|
|
25
|
-
...
|
|
38
|
+
...eventAttributes,
|
|
26
39
|
eventType,
|
|
27
|
-
...
|
|
28
|
-
timestamp: Date.now(),
|
|
29
|
-
timeSinceLoad: window.performance
|
|
30
|
-
? window.performance.now() / 1000
|
|
31
|
-
: null,
|
|
40
|
+
...otherAttrs,
|
|
32
41
|
};
|
|
33
42
|
|
|
43
|
+
const metadataAttributes = getObjectEntriesForKeys(Constants.QOE_AGGREGATE_KEYS, attributes)
|
|
44
|
+
|
|
45
|
+
let qoeEventObject = null;
|
|
46
|
+
if(eventType === "VideoAction") {
|
|
47
|
+
qoeEventObject = {
|
|
48
|
+
eventType: "VideoAction",
|
|
49
|
+
actionName: Tracker.Events.QOE_AGGREGATE,
|
|
50
|
+
...qoeAttrs,
|
|
51
|
+
...metadataAttributes,
|
|
52
|
+
...otherAttrs,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
// Send to video analytics harvester
|
|
35
57
|
const success = videoAnalyticsHarvester.addEvent(eventObject);
|
|
58
|
+
|
|
59
|
+
if(qoeEventObject) {
|
|
60
|
+
const successQoe = videoAnalyticsHarvester.addEvent(qoeEventObject);
|
|
61
|
+
return success && successQoe;
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
return success;
|
|
37
65
|
} catch (error) {
|
|
38
66
|
Log.error("Failed to record event:", error.message);
|
package/src/tracker.js
CHANGED
package/src/utils.js
CHANGED
|
@@ -160,3 +160,27 @@ export async function decompressPayload(compressedData) {
|
|
|
160
160
|
throw new Error(`Failed to decompress payload: ${error.message}`);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Filters an object to include only the specified keys.
|
|
166
|
+
* Creates a new object containing only the key-value pairs from the source object
|
|
167
|
+
* that match the provided keys array.
|
|
168
|
+
* @param {string[]} keys - Array of keys to extract from the object. If empty, null, or not an array, returns the original object.
|
|
169
|
+
* @param {object} obj - The source object to extract entries from.
|
|
170
|
+
* @returns {object} A new object containing only the entries that match the specified keys. Returns an empty object if obj is invalid.
|
|
171
|
+
* @example
|
|
172
|
+
* const data = { name: 'John', age: 30, city: 'NYC', country: 'USA' };
|
|
173
|
+
* const filtered = getObjectEntriesForKeys(['name', 'city'], data);
|
|
174
|
+
* // Returns: { name: 'John', city: 'NYC' }
|
|
175
|
+
*/
|
|
176
|
+
export function getObjectEntriesForKeys(keys, obj) {
|
|
177
|
+
if(!keys || !Array.isArray(keys) || keys.length === 0) return obj;
|
|
178
|
+
if(!obj || typeof obj !== 'object') return {};
|
|
179
|
+
|
|
180
|
+
return keys.reduce((result, key) => {
|
|
181
|
+
if(key in obj) {
|
|
182
|
+
result[key] = obj[key];
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}, {});
|
|
186
|
+
}
|
package/src/videotracker.js
CHANGED
|
@@ -496,6 +496,7 @@ class VideoTracker extends Tracker {
|
|
|
496
496
|
this.getBitrate() ||
|
|
497
497
|
this.getWebkitBitrate() ||
|
|
498
498
|
this.getRenditionBitrate();
|
|
499
|
+
|
|
499
500
|
att.contentRenditionName = this.getRenditionName();
|
|
500
501
|
att.contentRenditionBitrate = this.getRenditionBitrate();
|
|
501
502
|
att.contentRenditionHeight = this.getRenditionHeight();
|
|
@@ -520,13 +521,30 @@ class VideoTracker extends Tracker {
|
|
|
520
521
|
|
|
521
522
|
this.state.getStateAttributes(att);
|
|
522
523
|
|
|
524
|
+
if(this.state.isStarted && !this.isAd()) {
|
|
525
|
+
this.state.trackContentBitrateState(att.contentBitrate);
|
|
526
|
+
}
|
|
527
|
+
|
|
523
528
|
for (let key in this.customData) {
|
|
524
529
|
att[key] = this.customData[key];
|
|
525
530
|
}
|
|
526
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Adds all the attributes and custom attributes for qoe event
|
|
534
|
+
*/
|
|
535
|
+
this.addQoeAttributes(att);
|
|
536
|
+
|
|
527
537
|
return att;
|
|
528
538
|
}
|
|
529
539
|
|
|
540
|
+
addQoeAttributes(att) {
|
|
541
|
+
att = this.state.getQoeAttributes(att);
|
|
542
|
+
const qoe = att.qoe;
|
|
543
|
+
for (let key in this.customData) {
|
|
544
|
+
qoe[key] = this.customData[key];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
530
548
|
/**
|
|
531
549
|
* Sends custom event and registers a timeSince attribute.
|
|
532
550
|
* @param {Object} [actionName] Custom action name.
|
|
@@ -585,8 +603,21 @@ class VideoTracker extends Tracker {
|
|
|
585
603
|
ev = VideoTracker.Events.AD_START;
|
|
586
604
|
if (this.parentTracker) this.parentTracker.state.isPlaying = false;
|
|
587
605
|
this.sendVideoAdAction(ev, att);
|
|
606
|
+
this.state.startAdsTime();
|
|
588
607
|
} else {
|
|
589
608
|
ev = VideoTracker.Events.CONTENT_START;
|
|
609
|
+
let totalAdsTime = 0;
|
|
610
|
+
if(this.adsTracker) {
|
|
611
|
+
// If ads state is set to playing (ad error) after content start, reset the ad state.
|
|
612
|
+
if(this.adsTracker.state.isPlaying || this.adsTracker.state.isBuffering) {
|
|
613
|
+
totalAdsTime = this.adsTracker.state.stopAdsTime();
|
|
614
|
+
this.adsTracker.state.isPlaying = false;
|
|
615
|
+
this.adsTracker.state.isBuffering = false;
|
|
616
|
+
} else {
|
|
617
|
+
totalAdsTime = this.adsTracker.state.totalAdTime() ?? 0;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
this.state.setStartupTime(totalAdsTime)
|
|
590
621
|
this.sendVideoAction(ev, att);
|
|
591
622
|
}
|
|
592
623
|
//this.send(ev, att);
|
|
@@ -610,6 +641,7 @@ class VideoTracker extends Tracker {
|
|
|
610
641
|
att.timeSinceAdRequested = this.state.timeSinceRequested.getDeltaTime();
|
|
611
642
|
att.timeSinceAdStarted = this.state.timeSinceStarted.getDeltaTime();
|
|
612
643
|
if (this.parentTracker) this.parentTracker.state.isPlaying = true;
|
|
644
|
+
this.state.stopAdsTime();
|
|
613
645
|
} else {
|
|
614
646
|
ev = VideoTracker.Events.CONTENT_END;
|
|
615
647
|
att.timeSinceRequested = this.state.timeSinceRequested.getDeltaTime();
|
|
@@ -625,6 +657,11 @@ class VideoTracker extends Tracker {
|
|
|
625
657
|
this.parentTracker.state.goLastAd();
|
|
626
658
|
this.state.goViewCountUp();
|
|
627
659
|
this.state.totalPlaytime = 0;
|
|
660
|
+
if(!this.isAd()) {
|
|
661
|
+
// reset the states after the view count is up
|
|
662
|
+
if(this.adsTracker) this.adsTracker.state.clearTotalAdsTime();
|
|
663
|
+
this.state.resetViewIdTrackedState();
|
|
664
|
+
}
|
|
628
665
|
}
|
|
629
666
|
}
|
|
630
667
|
|
package/src/videotrackerstate.js
CHANGED
|
@@ -61,6 +61,8 @@ class VideoTrackerState {
|
|
|
61
61
|
*/
|
|
62
62
|
this.totalPlaytime = 0;
|
|
63
63
|
|
|
64
|
+
this.weightedAverageBitrate = 0;
|
|
65
|
+
|
|
64
66
|
/**
|
|
65
67
|
* The amount of ms the user has been watching ads during an ad break.
|
|
66
68
|
*/
|
|
@@ -72,6 +74,45 @@ class VideoTrackerState {
|
|
|
72
74
|
/** True if initial buffering event already happened. */
|
|
73
75
|
this.initialBufferingHappened = false;
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* New QoE KPIs - Content only
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Startup Time: Time from CONTENT_REQUEST to CONTENT_START in milliseconds.
|
|
83
|
+
*/
|
|
84
|
+
this.startupTime = null;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Peak Bitrate: Maximum contentBitrate observed across all content playback.
|
|
88
|
+
*/
|
|
89
|
+
this.peakBitrate = 0;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Last tracked bitrate
|
|
93
|
+
*/
|
|
94
|
+
this._lastBitrate = null;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* total bitrate partial value for average weighted average bitrate
|
|
98
|
+
*/
|
|
99
|
+
this.partialAverageBitrate = 0;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Had Startup Failure: TRUE if CONTENT_ERROR occurs before CONTENT_START.
|
|
103
|
+
*/
|
|
104
|
+
this.hadStartupFailure = false;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Had Playback Failure: TRUE if CONTENT_ERROR occurs during content playback.
|
|
108
|
+
*/
|
|
109
|
+
this.hadPlaybackFailure = false;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The amount of ms the user has been rebuffering during content playback.
|
|
113
|
+
*/
|
|
114
|
+
this.totalRebufferingTime = 0;
|
|
115
|
+
|
|
75
116
|
this.resetFlags();
|
|
76
117
|
this.resetChronos();
|
|
77
118
|
}
|
|
@@ -153,9 +194,12 @@ class VideoTrackerState {
|
|
|
153
194
|
/** A dictionary containing the custom timeSince attributes. */
|
|
154
195
|
this.customTimeSinceAttributes = {};
|
|
155
196
|
|
|
156
|
-
/** This are used to collect the time of
|
|
197
|
+
/** This are used to collect the time of buffered and pause resume between two heartbeats */
|
|
157
198
|
this.elapsedTime = new Chrono();
|
|
158
199
|
this.bufferElapsedTime = new Chrono();
|
|
200
|
+
|
|
201
|
+
/** tracks total ad play time */
|
|
202
|
+
this._totalAdPlaytime = new Chrono();
|
|
159
203
|
}
|
|
160
204
|
|
|
161
205
|
/** Returns true if the tracker is currently on ads. */
|
|
@@ -293,6 +337,35 @@ class VideoTrackerState {
|
|
|
293
337
|
return att;
|
|
294
338
|
}
|
|
295
339
|
|
|
340
|
+
getQoeAttributes(att) {
|
|
341
|
+
att = att || {};
|
|
342
|
+
const kpi = {};
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
// QoE KPIs - Content only
|
|
346
|
+
if (this.startupTime !== null) {
|
|
347
|
+
kpi["startupTime"] = this.startupTime;
|
|
348
|
+
}
|
|
349
|
+
if (this.peakBitrate > 0) {
|
|
350
|
+
kpi["peakBitrate"] = this.peakBitrate;
|
|
351
|
+
}
|
|
352
|
+
kpi["hadStartupFailure"] = this.hadStartupFailure;
|
|
353
|
+
kpi["hadPlaybackFailure"] = this.hadPlaybackFailure;
|
|
354
|
+
kpi["totalRebufferingTime"] = this.totalRebufferingTime;
|
|
355
|
+
// Calculate rebuffering ratio as percentage (avoid division by zero)
|
|
356
|
+
kpi["rebufferingRatio"] = this.totalPlaytime > 0
|
|
357
|
+
? (this.totalRebufferingTime / this.totalPlaytime) * 100
|
|
358
|
+
: 0;
|
|
359
|
+
kpi["totalPlaytime"] = this.totalPlaytime;
|
|
360
|
+
kpi["averageBitrate"] = this.weightedBitrate;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
Log.error("Failed to add attributes for QOE KPIs", error.message);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
att.qoe = kpi;
|
|
366
|
+
return att;
|
|
367
|
+
}
|
|
368
|
+
|
|
296
369
|
/**
|
|
297
370
|
* Calculate the bufferType attribute.
|
|
298
371
|
*
|
|
@@ -383,6 +456,7 @@ class VideoTrackerState {
|
|
|
383
456
|
this.timeSinceRequested.stop();
|
|
384
457
|
this.timeSinceStarted.stop();
|
|
385
458
|
this.playtimeSinceLastEvent.stop();
|
|
459
|
+
this.isPlaying = false;
|
|
386
460
|
return true;
|
|
387
461
|
} else {
|
|
388
462
|
return false;
|
|
@@ -468,6 +542,11 @@ class VideoTrackerState {
|
|
|
468
542
|
this._bufferAcc += this.bufferElapsedTime.getDeltaTime();
|
|
469
543
|
}
|
|
470
544
|
|
|
545
|
+
// Accumulate total rebuffering time for content only
|
|
546
|
+
if (!this.isAd() && this.initialBufferingHappened) {
|
|
547
|
+
this.totalRebufferingTime += this.timeSinceBufferBegin.getDeltaTime();
|
|
548
|
+
}
|
|
549
|
+
|
|
471
550
|
return true;
|
|
472
551
|
} else {
|
|
473
552
|
return false;
|
|
@@ -584,6 +663,15 @@ class VideoTrackerState {
|
|
|
584
663
|
this.timeSinceLastAdError.start();
|
|
585
664
|
} else {
|
|
586
665
|
this.timeSinceLastError.start();
|
|
666
|
+
|
|
667
|
+
// Track failure flags for content errors only
|
|
668
|
+
// Had Startup Failure: error before content started
|
|
669
|
+
if (!this.isStarted) {
|
|
670
|
+
this.hadStartupFailure = true;
|
|
671
|
+
} else {
|
|
672
|
+
// Had Playback Failure: any content error
|
|
673
|
+
this.hadPlaybackFailure = true;
|
|
674
|
+
}
|
|
587
675
|
}
|
|
588
676
|
}
|
|
589
677
|
|
|
@@ -593,6 +681,61 @@ class VideoTrackerState {
|
|
|
593
681
|
goLastAd() {
|
|
594
682
|
this.timeSinceLastAd.start();
|
|
595
683
|
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Updates peak bitrate with current bitrate value (content only).
|
|
687
|
+
* @param {number} bitrate Current content bitrate in bps.
|
|
688
|
+
*/
|
|
689
|
+
trackContentBitrateState(bitrate) {
|
|
690
|
+
if (bitrate && typeof bitrate === "number") {
|
|
691
|
+
this.peakBitrate = Math.max(this.peakBitrate, bitrate);
|
|
692
|
+
|
|
693
|
+
if(this._lastBitrate === null || this._lastBitrate !== bitrate) {
|
|
694
|
+
const totalPlaytime = this.timeSinceLastRenditionChange.getDeltaTime() || this.totalPlaytime;
|
|
695
|
+
const currentWeightedBitrate = (bitrate * totalPlaytime);
|
|
696
|
+
this.partialAverageBitrate += currentWeightedBitrate;
|
|
697
|
+
this.weightedBitrate = currentWeightedBitrate / totalPlaytime;
|
|
698
|
+
this._lastBitrate = bitrate;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Resets tracked variable for view id change
|
|
705
|
+
* */
|
|
706
|
+
resetViewIdTrackedState() {
|
|
707
|
+
this.peakBitrate = 0;
|
|
708
|
+
this.partialAverageBitrate = 0;
|
|
709
|
+
this.startupTime = null;
|
|
710
|
+
this._lastBitrate = null;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** Methods to manage total ads time chrono */
|
|
714
|
+
clearTotalAdsTime() {
|
|
715
|
+
console.log("clear total ads time", this.totalAdTime);
|
|
716
|
+
this._totalAdPlaytime.reset();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
totalAdTime() {
|
|
720
|
+
return this._totalAdPlaytime.getDuration();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
startAdsTime() {
|
|
724
|
+
console.log("startAdsTime");
|
|
725
|
+
return this._totalAdPlaytime.start();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
stopAdsTime() {
|
|
729
|
+
console.log("stopAdsTime");
|
|
730
|
+
return this._totalAdPlaytime.stop();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
setStartupTime(totalAdTime) {
|
|
734
|
+
if (this.startupTime === null) {
|
|
735
|
+
this.startupTime = Math.max(this.timeSinceRequested.getDeltaTime() - totalAdTime, 0)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
596
739
|
}
|
|
597
740
|
|
|
598
741
|
export default VideoTrackerState;
|