@newrelic/video-core 3.1.1 → 3.2.0-beta-1

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.
@@ -0,0 +1,574 @@
1
+ import Chrono from "./chrono";
2
+ import Log from "./log";
3
+
4
+ /**
5
+ * State machine for a VideoTracker and its monitored video.
6
+ */
7
+ class VideoTrackerState {
8
+ /** Constructor */
9
+ constructor() {
10
+ this.reset();
11
+
12
+ //this.setupNetworkListeners();
13
+
14
+ /**
15
+ * Time when the VideoTrackerState was initializated.
16
+ * @private
17
+ */
18
+ this._createdAt = Date.now();
19
+ this._hb = true;
20
+ this._acc = 0;
21
+ this._bufferAcc = 0;
22
+ }
23
+
24
+ /** Resets all flags and chronos. */
25
+ reset() {
26
+ /**
27
+ * Unique identifier of the view.
28
+ * @private
29
+ */
30
+ this._viewSession = null;
31
+
32
+ /**
33
+ * Number of views seen.
34
+ * @private
35
+ */
36
+ this._viewCount = 0;
37
+
38
+ /**
39
+ * True if it is tracking ads.
40
+ * @private
41
+ */
42
+ this._isAd = false;
43
+
44
+ /**
45
+ * Number of errors fired. 'End' resets it.
46
+ */
47
+ this.numberOfErrors = 0;
48
+
49
+ /**
50
+ * Number of ads shown.
51
+ */
52
+ this.numberOfAds = 0;
53
+
54
+ /**
55
+ * Number of videos played.
56
+ */
57
+ this.numberOfVideos = 0;
58
+
59
+ /**
60
+ * The amount of ms the user has been watching content (not paused, not buffering, not ads...)
61
+ */
62
+ this.totalPlaytime = 0;
63
+
64
+ /**
65
+ * The amount of ms the user has been watching ads during an ad break.
66
+ */
67
+ this.totalAdPlaytime = 0;
68
+
69
+ /** True if you are in the middle of an ad break. */
70
+ this.isAdBreak = false;
71
+
72
+ /** True if initial buffering event already happened. */
73
+ this.initialBufferingHappened = false;
74
+
75
+ this.resetFlags();
76
+ this.resetChronos();
77
+ }
78
+
79
+ /** Resets flags. */
80
+ resetFlags() {
81
+ /** True once the player has finished loading. */
82
+ this.isPlayerReady = false;
83
+
84
+ /** True if the video has been user-requested to play. ie: user cicks play. */
85
+ this.isRequested = false;
86
+
87
+ /** True if the video has starting playing. ie: actual images/audio showing in screen. */
88
+ this.isStarted = false;
89
+
90
+ /** True if the video is paused. */
91
+ this.isPaused = false;
92
+
93
+ /** True if the video is performing a seek action. */
94
+ this.isSeeking = false;
95
+
96
+ /** True if the video is currently buffering. */
97
+ this.isBuffering = false;
98
+
99
+ /** True if the video is currently playing (not buffering, not paused...) */
100
+ this.isPlaying = false;
101
+ }
102
+
103
+ /** Resets chronos. */
104
+ resetChronos() {
105
+ /** Chrono that counts time since last requested event. */
106
+ this.timeSinceRequested = new Chrono();
107
+
108
+ /** Chrono that counts time since last start event. */
109
+ this.timeSinceStarted = new Chrono();
110
+
111
+ /** Chrono that counts time since last pause event. */
112
+ this.timeSincePaused = new Chrono();
113
+
114
+ /** Chrono that counts time since last seeking start event. */
115
+ this.timeSinceSeekBegin = new Chrono();
116
+
117
+ /** Chrono that counts time since last buffering start event. */
118
+ this.timeSinceBufferBegin = new Chrono();
119
+
120
+ /** Chrono that counts time since last ad break start event. */
121
+ this.timeSinceAdBreakStart = new Chrono();
122
+
123
+ /** Chrono that counts time since last download event. */
124
+ this.timeSinceLastDownload = new Chrono();
125
+
126
+ /** Chrono that counts time since last heartbeat. */
127
+ this.timeSinceLastHeartbeat = new Chrono();
128
+
129
+ /** Chrono that counts time since last rendition change. */
130
+ this.timeSinceLastRenditionChange = new Chrono();
131
+
132
+ /** Ads only. Chrono that counts time since last ad quartile. */
133
+ this.timeSinceLastAdQuartile = new Chrono();
134
+
135
+ /** Content only. Chrono that counts time since last AD_END. */
136
+ this.timeSinceLastAd = new Chrono();
137
+
138
+ /** Chrono that counts time since last *_RESUME. Only for buffering events. */
139
+ this.timeSinceResumed = new Chrono();
140
+
141
+ /** Chrono that counts time since last *_SEEK_END. Only for buffering events. */
142
+ this.timeSinceSeekEnd = new Chrono();
143
+
144
+ /** Chrono that counts the ammount of time the video have been playing since the last event. */
145
+ this.playtimeSinceLastEvent = new Chrono();
146
+
147
+ /** A dictionary containing the custom timeSince attributes. */
148
+ this.customTimeSinceAttributes = {};
149
+
150
+ /** This are used to collect the time of buffred and pause resume between two heartbeats */
151
+ this.elapsedTime = new Chrono();
152
+ this.bufferElapsedTime = new Chrono();
153
+ }
154
+
155
+ /** Returns true if the tracker is currently on ads. */
156
+ isAd() {
157
+ return this._isAd;
158
+ }
159
+
160
+ /** Sets if the tracker is currenlty tracking ads */
161
+ setIsAd(isAd) {
162
+ this._isAd = isAd;
163
+ }
164
+
165
+ /**
166
+ * Set the Chrono for the custom attribute
167
+ *
168
+ * @param {object} name Time since attribute name.
169
+ */
170
+ setTimeSinceAttribute(name) {
171
+ this.customTimeSinceAttributes[name] = new Chrono();
172
+ this.customTimeSinceAttributes[name].start();
173
+ }
174
+
175
+ /**
176
+ * Delete a time since attribute
177
+ *
178
+ * @param {object} name Time since attribute name.
179
+ */
180
+ removeTimeSinceAttribute(name) {
181
+ delete this.customTimeSinceAttributes[name];
182
+ }
183
+
184
+ /**
185
+ * Returns a random-generated view Session ID, useful to sort by views.
186
+ */
187
+ getViewSession() {
188
+ if (!this._viewSession) {
189
+ let time = new Date().getTime();
190
+ let random =
191
+ Math.random().toString(36).substring(2) +
192
+ Math.random().toString(36).substring(2);
193
+
194
+ this._viewSession = time + "-" + random;
195
+ }
196
+
197
+ return this._viewSession;
198
+ }
199
+
200
+ /**
201
+ * Returns a random-generated view Session ID, plus a view count, allowing you to distinguish
202
+ * between two videos played in the same session.
203
+ */
204
+ getViewId() {
205
+ return this.getViewSession() + "-" + this._viewCount;
206
+ }
207
+
208
+ /**
209
+ * Fills given object with state-based attributes.
210
+ *
211
+ * @param {object} att Collection fo key value attributes
212
+ * @return {object} Filled attributes
213
+ */
214
+ getStateAttributes(att) {
215
+ att = att || {};
216
+
217
+ if (this.isAd()) {
218
+ // Ads only
219
+ if (this.isRequested) {
220
+ att.timeSinceAdRequested = this.timeSinceRequested.getDeltaTime();
221
+ att.timeSinceLastAdHeartbeat =
222
+ this.timeSinceLastHeartbeat.getDeltaTime();
223
+ }
224
+ if (this.isStarted)
225
+ att.timeSinceAdStarted = this.timeSinceStarted.getDeltaTime();
226
+ if (this.isPaused)
227
+ att.timeSinceAdPaused = this.timeSincePaused.getDeltaTime();
228
+ if (this.isBuffering)
229
+ att.timeSinceAdBufferBegin = this.timeSinceBufferBegin.getDeltaTime();
230
+ if (this.isSeeking)
231
+ att.timeSinceAdSeekBegin = this.timeSinceSeekBegin.getDeltaTime();
232
+ if (this.isAdBreak)
233
+ att.timeSinceAdBreakBegin = this.timeSinceAdBreakStart.getDeltaTime();
234
+ att.numberOfAds = this.numberOfAds;
235
+ } else {
236
+ // Content only
237
+ if (this.isRequested) {
238
+ att.timeSinceRequested = this.timeSinceRequested.getDeltaTime();
239
+ att.timeSinceLastHeartbeat = this.timeSinceLastHeartbeat.getDeltaTime();
240
+ }
241
+ if (this.isStarted)
242
+ att.timeSinceStarted = this.timeSinceStarted.getDeltaTime();
243
+ if (this.isPaused)
244
+ att.timeSincePaused = this.timeSincePaused.getDeltaTime();
245
+ if (this.isBuffering)
246
+ att.timeSinceBufferBegin = this.timeSinceBufferBegin.getDeltaTime();
247
+ if (this.isSeeking)
248
+ att.timeSinceSeekBegin = this.timeSinceSeekBegin.getDeltaTime();
249
+ att.timeSinceLastAd = this.timeSinceLastAd.getDeltaTime();
250
+ att.numberOfVideos = this.numberOfVideos;
251
+ }
252
+ att.numberOfErrors = this.numberOfErrors;
253
+
254
+ // Playtime
255
+ if (!this.isAd()) {
256
+ // Content only
257
+ if (this.playtimeSinceLastEvent.startTime > 0) {
258
+ att.playtimeSinceLastEvent = this.playtimeSinceLastEvent.getDeltaTime();
259
+ } else {
260
+ att.playtimeSinceLastEvent = 0;
261
+ }
262
+ if (this.isPlaying) {
263
+ this.playtimeSinceLastEvent.start();
264
+ } else {
265
+ this.playtimeSinceLastEvent.reset();
266
+ }
267
+ this.totalPlaytime += att.playtimeSinceLastEvent;
268
+ att.totalPlaytime = this.totalPlaytime;
269
+ }
270
+
271
+ for (const [key, value] of Object.entries(this.customTimeSinceAttributes)) {
272
+ att[key] = value.getDeltaTime();
273
+ }
274
+
275
+ return att;
276
+ }
277
+
278
+ /**
279
+ * Calculate the bufferType attribute.
280
+ *
281
+ * @param {boolean} isInitialBuffering Is initial buffering event.
282
+ */
283
+ calculateBufferType(isInitialBuffering) {
284
+ let bufferType = "";
285
+ if (isInitialBuffering) {
286
+ bufferType = "initial";
287
+ } else if (this.isSeeking) {
288
+ bufferType = "seek";
289
+ } else if (this.isPaused) {
290
+ bufferType = "pause";
291
+ } else {
292
+ // If none of the above is true, it is a connection buffering
293
+ bufferType = "connection";
294
+ }
295
+ Log.debug("Buffer Type = " + bufferType);
296
+
297
+ return bufferType;
298
+ }
299
+
300
+ /**
301
+ * Augments view count. This will be called with each *_START and *_END.
302
+ */
303
+ goViewCountUp() {
304
+ this._viewCount++;
305
+ }
306
+
307
+ /**
308
+ * Checks flags and changes state.
309
+ * @returns {boolean} True if the state changed.
310
+ */
311
+ goPlayerReady() {
312
+ if (!this.isPlayerReady) {
313
+ this.isPlayerReady = true;
314
+ return true;
315
+ } else {
316
+ return false;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Checks flags and changes state
322
+ * @returns {boolean} True if the state changed.
323
+ */
324
+ goRequest() {
325
+ if (!this.isRequested) {
326
+ this.isRequested = true;
327
+
328
+ this.timeSinceLastAd.reset();
329
+ this.timeSinceRequested.start();
330
+ return true;
331
+ } else {
332
+ return false;
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Checks flags and changes state
338
+ * @returns {boolean} True if the state changed.
339
+ */
340
+ goStart() {
341
+ if (this.isRequested && !this.isStarted) {
342
+ if (this.isAd()) {
343
+ this.numberOfAds++;
344
+ } else {
345
+ this.numberOfVideos++;
346
+ }
347
+ this.isStarted = true;
348
+ this.isPlaying = true;
349
+ this.timeSinceStarted.start();
350
+ this.playtimeSinceLastEvent.start();
351
+ return true;
352
+ } else {
353
+ return false;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Checks flags and changes state
359
+ * @returns {boolean} True if the state changed.
360
+ */
361
+ goEnd() {
362
+ if (this.isRequested) {
363
+ this.numberOfErrors = 0;
364
+ this.resetFlags();
365
+ this.timeSinceRequested.stop();
366
+ this.timeSinceStarted.stop();
367
+ this.playtimeSinceLastEvent.stop();
368
+ return true;
369
+ } else {
370
+ return false;
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Checks flags and changes state
376
+ * @returns {boolean} True if the state changed.
377
+ */
378
+ goPause() {
379
+ if (this.isStarted && !this.isPaused) {
380
+ this.isPaused = true;
381
+ this.isPlaying = false;
382
+ this.timeSincePaused.start();
383
+ this.playtimeSinceLastEvent.stop();
384
+ this.timeSinceResumed.reset();
385
+ if (this.isBuffering) {
386
+ this._bufferAcc += this.bufferElapsedTime.getDeltaTime();
387
+ }
388
+ this.elapsedTime.start();
389
+ return true;
390
+ } else {
391
+ return false;
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Checks flags and changes state
397
+ * @returns {boolean} True if the state changed.
398
+ */
399
+ goResume() {
400
+ if (this.isStarted && this.isPaused) {
401
+ this.isPaused = false;
402
+ this.isPlaying = true;
403
+ this.timeSincePaused.stop();
404
+ this.timeSinceResumed.start();
405
+ if (this._hb) {
406
+ this._acc = this.elapsedTime.getDeltaTime();
407
+ this._hb = false;
408
+ } else {
409
+ if (this.isBuffering) {
410
+ this.bufferElapsedTime.start();
411
+ }
412
+ this._acc += this.elapsedTime.getDeltaTime();
413
+ }
414
+ return true;
415
+ } else {
416
+ return false;
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Checks flags and changes state
422
+ * @returns {boolean} True if the state changed.
423
+ */
424
+ goBufferStart() {
425
+ if (this.isRequested && !this.isBuffering) {
426
+ this.isBuffering = true;
427
+ this.isPlaying = false;
428
+ this.timeSinceBufferBegin.start();
429
+ this.bufferElapsedTime.start();
430
+
431
+ return true;
432
+ } else {
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Checks flags and changes state
439
+ * @returns {boolean} True if the state changed.
440
+ */
441
+ goBufferEnd() {
442
+ if (this.isRequested && this.isBuffering) {
443
+ this.isBuffering = false;
444
+ this.isPlaying = true;
445
+ this.timeSinceBufferBegin.stop();
446
+ if (this._hb) {
447
+ this._bufferAcc = this.bufferElapsedTime.getDeltaTime();
448
+ this._hb = false;
449
+ } else {
450
+ this._bufferAcc += this.bufferElapsedTime.getDeltaTime();
451
+ }
452
+
453
+ return true;
454
+ } else {
455
+ return false;
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Checks flags and changes state
461
+ * @returns {boolean} True if the state changed.
462
+ */
463
+ goSeekStart() {
464
+ if (this.isStarted && !this.isSeeking) {
465
+ this.isSeeking = true;
466
+ this.isPlaying = false;
467
+ this.timeSinceSeekBegin.start();
468
+ this.timeSinceSeekEnd.reset();
469
+
470
+ //new
471
+ // this.seekStartTime = Date.now();
472
+
473
+ return true;
474
+ } else {
475
+ return false;
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Checks flags and changes state
481
+ * @returns {boolean} True if the state changed.
482
+ */
483
+ goSeekEnd() {
484
+ if (this.isStarted && this.isSeeking) {
485
+ this.isSeeking = false;
486
+ this.isPlaying = true;
487
+ this.timeSinceSeekBegin.stop();
488
+ this.timeSinceSeekEnd.start();
489
+
490
+ //new
491
+ // this.seekEndTime = Date.now();
492
+ // this.seekDuration = this.seekEndTime - this.seekStartTime;
493
+
494
+ return true;
495
+ } else {
496
+ return false;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Checks flags and changes state
502
+ * @returns {boolean} True if the state changed.
503
+ */
504
+ goAdBreakStart() {
505
+ if (!this.isAdBreak) {
506
+ this.isAdBreak = true;
507
+ this.timeSinceAdBreakStart.start();
508
+ return true;
509
+ } else {
510
+ return false;
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Checks flags and changes state
516
+ * @returns {boolean} True if the state changed.
517
+ */
518
+ goAdBreakEnd() {
519
+ if (this.isAdBreak) {
520
+ this.isRequested = false;
521
+ this.isAdBreak = false;
522
+ this.totalAdPlaytime = this.timeSinceAdBreakStart.getDeltaTime();
523
+ this.timeSinceAdBreakStart.stop();
524
+ return true;
525
+ } else {
526
+ return false;
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Restarts download chrono.
532
+ */
533
+ goDownload() {
534
+ this.timeSinceLastDownload.start();
535
+ }
536
+
537
+ /**
538
+ * Restarts heartbeat chrono.
539
+ */
540
+ goHeartbeat() {
541
+ this.timeSinceLastHeartbeat.start();
542
+ }
543
+
544
+ /**
545
+ * Restarts rendition change chrono.
546
+ */
547
+ goRenditionChange() {
548
+ this.timeSinceLastRenditionChange.start();
549
+ }
550
+
551
+ /**
552
+ * Restarts ad quartile chrono.
553
+ */
554
+ goAdQuartile() {
555
+ this.timeSinceLastAdQuartile.start();
556
+ }
557
+
558
+ /**
559
+ * Increments error counter.
560
+ */
561
+ goError() {
562
+ this.isError = true;
563
+ this.numberOfErrors++;
564
+ }
565
+
566
+ /**
567
+ * Restarts last ad chrono.
568
+ */
569
+ goLastAd() {
570
+ this.timeSinceLastAd.start();
571
+ }
572
+ }
573
+
574
+ export default VideoTrackerState;