@newrelic/video-core 4.0.2 → 4.1.1-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 -1
- 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 +1 -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 +35 -6
- package/src/tracker.js +1 -0
- package/src/utils.js +24 -0
- package/src/videotracker.js +37 -0
- package/src/videotrackerstate.js +151 -1
package/package.json
CHANGED
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,45 @@ 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
|
+
qoeAggregateVersion: '1.0.0',
|
|
51
|
+
...qoeAttrs,
|
|
52
|
+
...metadataAttributes,
|
|
53
|
+
...otherAttrs,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
// Send to video analytics harvester
|
|
35
58
|
const success = videoAnalyticsHarvester.addEvent(eventObject);
|
|
59
|
+
|
|
60
|
+
if(qoeEventObject) {
|
|
61
|
+
const successQoe = videoAnalyticsHarvester.addEvent(qoeEventObject);
|
|
62
|
+
return success && successQoe;
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
return success;
|
|
37
66
|
} catch (error) {
|
|
38
67
|
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,50 @@ 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
|
+
* Tracks the last updated timestamp for bitrate
|
|
98
|
+
* */
|
|
99
|
+
this._lastBitrateChangeTimestamp = null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* total bitrate partial value for average weighted average bitrate
|
|
103
|
+
*/
|
|
104
|
+
this.partialAverageBitrate = 0;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Had Startup Failure: TRUE if CONTENT_ERROR occurs before CONTENT_START.
|
|
108
|
+
*/
|
|
109
|
+
this.hadStartupFailure = false;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Had Playback Failure: TRUE if CONTENT_ERROR occurs during content playback.
|
|
113
|
+
*/
|
|
114
|
+
this.hadPlaybackFailure = false;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The amount of ms the user has been rebuffering during content playback.
|
|
118
|
+
*/
|
|
119
|
+
this.totalRebufferingTime = 0;
|
|
120
|
+
|
|
75
121
|
this.resetFlags();
|
|
76
122
|
this.resetChronos();
|
|
77
123
|
}
|
|
@@ -153,9 +199,12 @@ class VideoTrackerState {
|
|
|
153
199
|
/** A dictionary containing the custom timeSince attributes. */
|
|
154
200
|
this.customTimeSinceAttributes = {};
|
|
155
201
|
|
|
156
|
-
/** This are used to collect the time of
|
|
202
|
+
/** This are used to collect the time of buffered and pause resume between two heartbeats */
|
|
157
203
|
this.elapsedTime = new Chrono();
|
|
158
204
|
this.bufferElapsedTime = new Chrono();
|
|
205
|
+
|
|
206
|
+
/** tracks total ad play time */
|
|
207
|
+
this._totalAdPlaytime = new Chrono();
|
|
159
208
|
}
|
|
160
209
|
|
|
161
210
|
/** Returns true if the tracker is currently on ads. */
|
|
@@ -293,6 +342,35 @@ class VideoTrackerState {
|
|
|
293
342
|
return att;
|
|
294
343
|
}
|
|
295
344
|
|
|
345
|
+
getQoeAttributes(att) {
|
|
346
|
+
att = att || {};
|
|
347
|
+
const kpi = {};
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
// QoE KPIs - Content only
|
|
351
|
+
if (this.startupTime !== null) {
|
|
352
|
+
kpi["startupTime"] = this.startupTime;
|
|
353
|
+
}
|
|
354
|
+
if (this.peakBitrate > 0) {
|
|
355
|
+
kpi["peakBitrate"] = this.peakBitrate;
|
|
356
|
+
}
|
|
357
|
+
kpi["hadStartupFailure"] = this.hadStartupFailure;
|
|
358
|
+
kpi["hadPlaybackFailure"] = this.hadPlaybackFailure;
|
|
359
|
+
kpi["totalRebufferingTime"] = this.totalRebufferingTime;
|
|
360
|
+
// Calculate rebuffering ratio as percentage (avoid division by zero)
|
|
361
|
+
kpi["rebufferingRatio"] = this.totalPlaytime > 0
|
|
362
|
+
? (this.totalRebufferingTime / this.totalPlaytime) * 100
|
|
363
|
+
: 0;
|
|
364
|
+
kpi["totalPlaytime"] = this.totalPlaytime;
|
|
365
|
+
kpi["averageBitrate"] = this.weightedBitrate;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
Log.error("Failed to add attributes for QOE KPIs", error.message);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
att.qoe = kpi;
|
|
371
|
+
return att;
|
|
372
|
+
}
|
|
373
|
+
|
|
296
374
|
/**
|
|
297
375
|
* Calculate the bufferType attribute.
|
|
298
376
|
*
|
|
@@ -383,6 +461,7 @@ class VideoTrackerState {
|
|
|
383
461
|
this.timeSinceRequested.stop();
|
|
384
462
|
this.timeSinceStarted.stop();
|
|
385
463
|
this.playtimeSinceLastEvent.stop();
|
|
464
|
+
this.isPlaying = false;
|
|
386
465
|
return true;
|
|
387
466
|
} else {
|
|
388
467
|
return false;
|
|
@@ -468,6 +547,11 @@ class VideoTrackerState {
|
|
|
468
547
|
this._bufferAcc += this.bufferElapsedTime.getDeltaTime();
|
|
469
548
|
}
|
|
470
549
|
|
|
550
|
+
// Accumulate total rebuffering time for content only
|
|
551
|
+
if (!this.isAd() && this.initialBufferingHappened) {
|
|
552
|
+
this.totalRebufferingTime += this.timeSinceBufferBegin.getDeltaTime();
|
|
553
|
+
}
|
|
554
|
+
|
|
471
555
|
return true;
|
|
472
556
|
} else {
|
|
473
557
|
return false;
|
|
@@ -584,6 +668,15 @@ class VideoTrackerState {
|
|
|
584
668
|
this.timeSinceLastAdError.start();
|
|
585
669
|
} else {
|
|
586
670
|
this.timeSinceLastError.start();
|
|
671
|
+
|
|
672
|
+
// Track failure flags for content errors only
|
|
673
|
+
// Had Startup Failure: error before content started
|
|
674
|
+
if (!this.isStarted) {
|
|
675
|
+
this.hadStartupFailure = true;
|
|
676
|
+
} else {
|
|
677
|
+
// Had Playback Failure: any content error
|
|
678
|
+
this.hadPlaybackFailure = true;
|
|
679
|
+
}
|
|
587
680
|
}
|
|
588
681
|
}
|
|
589
682
|
|
|
@@ -593,6 +686,63 @@ class VideoTrackerState {
|
|
|
593
686
|
goLastAd() {
|
|
594
687
|
this.timeSinceLastAd.start();
|
|
595
688
|
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Updates peak bitrate with current bitrate value (content only).
|
|
692
|
+
* @param {number} bitrate Current content bitrate in bps.
|
|
693
|
+
*/
|
|
694
|
+
trackContentBitrateState(bitrate) {
|
|
695
|
+
if (bitrate && typeof bitrate === "number") {
|
|
696
|
+
this.peakBitrate = Math.max(this.peakBitrate, bitrate);
|
|
697
|
+
|
|
698
|
+
if(this._lastBitrate === null || this._lastBitrate !== bitrate) {
|
|
699
|
+
const deltaPlaytime = this._lastBitrateChangeTimestamp === null ? this.totalPlaytime : Date.now() - this._lastBitrateChangeTimestamp;
|
|
700
|
+
const currentWeightedBitrate = (bitrate * deltaPlaytime);
|
|
701
|
+
this.partialAverageBitrate += currentWeightedBitrate;
|
|
702
|
+
this.weightedBitrate = currentWeightedBitrate / deltaPlaytime;
|
|
703
|
+
this._lastBitrate = bitrate;
|
|
704
|
+
this._lastBitrateChangeTimestamp = Date.now();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Resets tracked variable for view id change
|
|
711
|
+
* */
|
|
712
|
+
resetViewIdTrackedState() {
|
|
713
|
+
this.peakBitrate = 0;
|
|
714
|
+
this.partialAverageBitrate = 0;
|
|
715
|
+
this.startupTime = null;
|
|
716
|
+
this._lastBitrate = null;
|
|
717
|
+
this._lastBitrateChangeTimestamp = null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Methods to manage total ads time chrono */
|
|
721
|
+
clearTotalAdsTime() {
|
|
722
|
+
console.log("clear total ads time", this.totalAdTime);
|
|
723
|
+
this._totalAdPlaytime.reset();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
totalAdTime() {
|
|
727
|
+
return this._totalAdPlaytime.getDuration();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
startAdsTime() {
|
|
731
|
+
console.log("startAdsTime");
|
|
732
|
+
return this._totalAdPlaytime.start();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
stopAdsTime() {
|
|
736
|
+
console.log("stopAdsTime");
|
|
737
|
+
return this._totalAdPlaytime.stop();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
setStartupTime(totalAdTime) {
|
|
741
|
+
if (this.startupTime === null) {
|
|
742
|
+
this.startupTime = Math.max(this.timeSinceRequested.getDeltaTime() - totalAdTime, 0)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
596
746
|
}
|
|
597
747
|
|
|
598
748
|
export default VideoTrackerState;
|