@newrelic/video-videojs 4.1.2 → 4.2.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.
- package/CHANGELOG.md +62 -1
- package/README.md +170 -40
- 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/newrelic-video-videojs.min.js +1 -1
- package/dist/umd/newrelic-video-videojs.min.js.LICENSE.txt +1 -1
- package/dist/umd/newrelic-video-videojs.min.js.map +1 -1
- package/package.json +1 -1
- package/src/ads/media-tailor.js +126 -91
- package/src/ads/utils/mt-constants.js +2 -1
- package/src/ads/utils/mt.js +44 -22
- package/src/register-plugin.js +2 -2
- package/src/tracker.js +150 -16
package/src/ads/media-tailor.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
parseHlsManifestForAdBreaks,
|
|
24
24
|
parseDashManifestForAdBreaks,
|
|
25
25
|
detectAdBreaksFromVhsPlaylist,
|
|
26
|
+
whichAdSegmentMarker,
|
|
26
27
|
enrichAdScheduleWithTrackingMetadata,
|
|
27
28
|
extractHlsTargetDurationSeconds,
|
|
28
29
|
extractDashMinimumUpdatePeriodSeconds,
|
|
@@ -51,12 +52,10 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
51
52
|
/**
|
|
52
53
|
* Checks if tracker should be used for this player source
|
|
53
54
|
*/
|
|
54
|
-
static isUsing(player) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
player.currentSrc().includes(MEDIATAILOR_HOST_MARKER)
|
|
59
|
-
);
|
|
55
|
+
static isUsing(player, options = {}) {
|
|
56
|
+
// Opt-in flag is the single source of truth.
|
|
57
|
+
// Accepts { mediatailor: true } or { mediatailor: { trackingUrl, adSegmentPrefix } }.
|
|
58
|
+
return Boolean(options && options.mediatailor);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
@@ -87,11 +86,32 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
87
86
|
constructor(player, options = {}) {
|
|
88
87
|
super(player);
|
|
89
88
|
|
|
89
|
+
// Normalize mediatailor option: true | { trackingUrl, adSegmentPrefix } | undefined
|
|
90
|
+
const mtOptions =
|
|
91
|
+
options.mediatailor === true ? {} : options.mediatailor || {};
|
|
92
|
+
|
|
90
93
|
// Initialize state
|
|
91
94
|
this.streamType = null; // 'vod' or 'live'
|
|
92
95
|
this.manifestFormat = null; // 'hls' or 'dash'
|
|
93
96
|
this.playbackManifestUrl = player.currentSrc();
|
|
94
97
|
|
|
98
|
+
// trackingUrl: optional override for the MediaTailor tracking endpoint.
|
|
99
|
+
// When not provided, the tracker derives it automatically from the playback URL.
|
|
100
|
+
this.explicitTrackingUrl = mtOptions.trackingUrl || null;
|
|
101
|
+
|
|
102
|
+
// adSegmentPrefix: only needed when the customer configured a CDN ad-segment
|
|
103
|
+
// prefix in AWS MediaTailor that does NOT follow the AWS-recommended /tm/ path.
|
|
104
|
+
// Most setups (default AWS hostname or CDN following AWS conventions) are
|
|
105
|
+
// detected automatically via MT_DEFAULT_AD_SEGMENT_PATH ('/tm/') and do not
|
|
106
|
+
// need this override.
|
|
107
|
+
this.adSegmentPrefix = mtOptions.adSegmentPrefix || null;
|
|
108
|
+
|
|
109
|
+
if (this.adSegmentPrefix) {
|
|
110
|
+
Log.debug(`[MT] ad segment detection: custom prefix "${this.adSegmentPrefix}"`);
|
|
111
|
+
} else {
|
|
112
|
+
Log.debug('[MT] ad segment detection: default (segments.mediatailor hostname or /tm/ path)');
|
|
113
|
+
}
|
|
114
|
+
|
|
95
115
|
// Ad tracking state
|
|
96
116
|
this.adSchedule = [];
|
|
97
117
|
this.currentAdBreak = null;
|
|
@@ -120,19 +140,19 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
120
140
|
this.mediaPlaylistUrl = null;
|
|
121
141
|
this.lastMediaPlaylistText = null;
|
|
122
142
|
|
|
123
|
-
|
|
143
|
+
Log.debug(`[MT - ${getTimestamp()}] MediaTailorAdsTracker initialized`, {
|
|
124
144
|
endpoint: this.playbackManifestUrl,
|
|
125
145
|
trackingAPITimeout: TRACKING_API_TIMEOUT_MS,
|
|
126
146
|
});
|
|
127
147
|
|
|
128
148
|
this.manifestFormat = detectManifestFormatFromUrl(this.playbackManifestUrl);
|
|
129
|
-
|
|
149
|
+
Log.debug(
|
|
130
150
|
`[MT - ${getTimestamp()}] Manifest type: ${this.manifestFormat.toUpperCase()}`,
|
|
131
151
|
);
|
|
132
152
|
|
|
133
153
|
this.player.one('loadedmetadata', () => {
|
|
134
154
|
this.streamType = detectPlaybackStreamType(this.player.duration());
|
|
135
|
-
|
|
155
|
+
Log.debug(
|
|
136
156
|
`[MT - ${getTimestamp()}] Stream type: ${this.streamType.toUpperCase()}`,
|
|
137
157
|
);
|
|
138
158
|
this.initializeTracking();
|
|
@@ -143,20 +163,22 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
143
163
|
* Initializes tracking based on detected stream type
|
|
144
164
|
*/
|
|
145
165
|
initializeTracking() {
|
|
146
|
-
|
|
166
|
+
Log.debug(
|
|
147
167
|
`[MT - ${getTimestamp()}] Initializing ${this.manifestFormat.toUpperCase()} ${this.streamType.toUpperCase()} tracking`,
|
|
148
168
|
);
|
|
149
169
|
|
|
150
|
-
//
|
|
151
|
-
this.trackingEndpointUrl =
|
|
170
|
+
// Prefer explicit tracking URL (explicit POST session) over derived one
|
|
171
|
+
this.trackingEndpointUrl =
|
|
172
|
+
this.explicitTrackingUrl ||
|
|
173
|
+
buildTrackingEndpointUrl(this.playbackManifestUrl);
|
|
152
174
|
if (this.trackingEndpointUrl) {
|
|
153
|
-
|
|
175
|
+
Log.debug(
|
|
154
176
|
`[MT - ${getTimestamp()}] Tracking URL extracted:`,
|
|
155
177
|
this.trackingEndpointUrl,
|
|
156
178
|
);
|
|
157
179
|
} else {
|
|
158
|
-
|
|
159
|
-
`[MT - ${getTimestamp()}]
|
|
180
|
+
Log.warn(
|
|
181
|
+
`[MT - ${getTimestamp()}] Could not derive tracking URL from playback URL — pass trackingUrl in mediatailor options if needed`,
|
|
160
182
|
);
|
|
161
183
|
}
|
|
162
184
|
|
|
@@ -191,7 +213,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
191
213
|
this.player.on('waiting', this.onWaiting);
|
|
192
214
|
this.player.on('ended', this.onEnded);
|
|
193
215
|
this.player.on('timeupdate', this.onTimeUpdate);
|
|
194
|
-
|
|
216
|
+
Log.debug(`[MT - ${getTimestamp()}] Event listeners registered`);
|
|
195
217
|
}
|
|
196
218
|
|
|
197
219
|
/**
|
|
@@ -228,7 +250,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
228
250
|
* Sets up VOD tracking (single parse, no polling)
|
|
229
251
|
*/
|
|
230
252
|
setupVODTracking() {
|
|
231
|
-
|
|
253
|
+
Log.debug(`[MT - ${getTimestamp()}] VOD mode: Single manifest parse`);
|
|
232
254
|
this.hookPlayerManifest();
|
|
233
255
|
}
|
|
234
256
|
|
|
@@ -236,7 +258,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
236
258
|
* Sets up Live tracking (continuous polling)
|
|
237
259
|
*/
|
|
238
260
|
setupLiveTracking() {
|
|
239
|
-
|
|
261
|
+
Log.debug(`[MT - ${getTimestamp()}] Live mode: Continuous polling`);
|
|
240
262
|
this.hookPlayerManifest();
|
|
241
263
|
|
|
242
264
|
const pollingInterval = this.getLiveRefreshIntervalMs();
|
|
@@ -250,7 +272,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
250
272
|
this.getAndProcessTrackingMetadata();
|
|
251
273
|
}, pollingInterval);
|
|
252
274
|
|
|
253
|
-
|
|
275
|
+
Log.debug(`[MT - ${getTimestamp()}] Live polling started`, {
|
|
254
276
|
pollingInterval,
|
|
255
277
|
});
|
|
256
278
|
}
|
|
@@ -271,7 +293,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
271
293
|
if (!this.liveRefreshIntervalSeconds) return;
|
|
272
294
|
|
|
273
295
|
const newInterval = this.getLiveRefreshIntervalMs();
|
|
274
|
-
|
|
296
|
+
Log.debug(
|
|
275
297
|
`[MT - ${getTimestamp()}] Updating live polling interval: ${this.liveRefreshIntervalSeconds}s`,
|
|
276
298
|
);
|
|
277
299
|
|
|
@@ -295,7 +317,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
295
317
|
hookPlayerManifest() {
|
|
296
318
|
const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
|
|
297
319
|
if (!tech) {
|
|
298
|
-
|
|
320
|
+
Log.debug(`[MT - ${getTimestamp()}] No tech - using fallback fetch`);
|
|
299
321
|
this.getManifestDirectly();
|
|
300
322
|
return;
|
|
301
323
|
}
|
|
@@ -316,7 +338,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
316
338
|
}
|
|
317
339
|
|
|
318
340
|
// Fallback: Direct manifest fetch
|
|
319
|
-
|
|
341
|
+
Log.debug(
|
|
320
342
|
`[MT - ${getTimestamp()}] Using fallback: direct manifest fetch`,
|
|
321
343
|
);
|
|
322
344
|
this.getManifestDirectly();
|
|
@@ -328,7 +350,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
328
350
|
hookHLSViaVHS(tech) {
|
|
329
351
|
if (!tech.vhs || !tech.vhs.playlists) return false;
|
|
330
352
|
|
|
331
|
-
|
|
353
|
+
Log.debug(`[MT - ${getTimestamp()}] Hooked: VHS`);
|
|
332
354
|
|
|
333
355
|
// Parse already-loaded playlist
|
|
334
356
|
const currentPlaylist = tech.vhs.playlists.media();
|
|
@@ -337,7 +359,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
337
359
|
currentPlaylist.segments &&
|
|
338
360
|
currentPlaylist.segments.length > 0
|
|
339
361
|
) {
|
|
340
|
-
|
|
362
|
+
Log.debug(`[MT - ${getTimestamp()}] Parsing existing playlist`);
|
|
341
363
|
this.parseVhsPlaylistForAdBreaks(currentPlaylist);
|
|
342
364
|
}
|
|
343
365
|
|
|
@@ -362,7 +384,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
362
384
|
tech.el_.canPlayType &&
|
|
363
385
|
tech.el_.canPlayType(HLS_MIME_TYPE)
|
|
364
386
|
) {
|
|
365
|
-
|
|
387
|
+
Log.debug(
|
|
366
388
|
`[MT - ${getTimestamp()}] Native HLS detected - using fallback`,
|
|
367
389
|
);
|
|
368
390
|
this.getManifestDirectly();
|
|
@@ -377,7 +399,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
377
399
|
hookHLSViaContribHls(tech) {
|
|
378
400
|
if (!tech.hls || !tech.hls.playlists) return false;
|
|
379
401
|
|
|
380
|
-
|
|
402
|
+
Log.debug(`[MT - ${getTimestamp()}] Hooked: contrib-hls (legacy)`);
|
|
381
403
|
|
|
382
404
|
// Parse already-loaded playlist
|
|
383
405
|
const currentPlaylist = tech.hls.playlists.media();
|
|
@@ -406,7 +428,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
406
428
|
hookDASHViaShaka(tech) {
|
|
407
429
|
if (!tech.shakaPlayer) return false;
|
|
408
430
|
|
|
409
|
-
|
|
431
|
+
Log.debug(`[MT - ${getTimestamp()}] Hooked: Shaka Player`);
|
|
410
432
|
tech.shakaPlayer.addEventListener('emsg', (event) => {
|
|
411
433
|
this.handleDASHEmsgEvent(event);
|
|
412
434
|
});
|
|
@@ -420,7 +442,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
420
442
|
hookDASHViaDashJs(tech) {
|
|
421
443
|
if (!tech.dash || !tech.dash.on) return false;
|
|
422
444
|
|
|
423
|
-
|
|
445
|
+
Log.debug(`[MT - ${getTimestamp()}] Hooked: dash.js`);
|
|
424
446
|
tech.dash.on('EVENT_MODE_ON_RECEIVE', (event) => {
|
|
425
447
|
this.handleDASHEventStream(event);
|
|
426
448
|
});
|
|
@@ -432,7 +454,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
432
454
|
* Fetches manifest directly (fallback when hooks unavailable)
|
|
433
455
|
*/
|
|
434
456
|
async getManifestDirectly() {
|
|
435
|
-
|
|
457
|
+
Log.debug(
|
|
436
458
|
`[MT - ${getTimestamp()}] Fallback: fetching manifest directly`,
|
|
437
459
|
);
|
|
438
460
|
|
|
@@ -445,7 +467,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
445
467
|
await this.fetchAndParseDashManifest(manifestUrl);
|
|
446
468
|
}
|
|
447
469
|
} catch (error) {
|
|
448
|
-
|
|
470
|
+
Log.debug(`[MT - ${getTimestamp()}] Fallback fetch error:`, error);
|
|
449
471
|
}
|
|
450
472
|
}
|
|
451
473
|
|
|
@@ -454,17 +476,17 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
454
476
|
*/
|
|
455
477
|
async fetchAndParseHlsManifest(manifestUrl) {
|
|
456
478
|
try {
|
|
457
|
-
|
|
479
|
+
Log.debug(`[MT - ${getTimestamp()}] Fetching HLS master manifest`);
|
|
458
480
|
|
|
459
481
|
// Fetch master manifest
|
|
460
482
|
const { mediaPlaylistUrl } = await fetchHlsMasterManifest(manifestUrl);
|
|
461
483
|
|
|
462
484
|
if (!mediaPlaylistUrl) {
|
|
463
|
-
|
|
485
|
+
Log.debug(`[MT - ${getTimestamp()}] No media playlist found`);
|
|
464
486
|
return;
|
|
465
487
|
}
|
|
466
488
|
|
|
467
|
-
|
|
489
|
+
Log.debug(`[MT - ${getTimestamp()}] Fetching media playlist`);
|
|
468
490
|
|
|
469
491
|
// Fetch media playlist
|
|
470
492
|
const mediaText = await fetchHlsMediaPlaylist(mediaPlaylistUrl);
|
|
@@ -478,13 +500,13 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
478
500
|
// Parse for ads
|
|
479
501
|
const ads = parseHlsManifestForAdBreaks(mediaText);
|
|
480
502
|
if (ads.length > 0) {
|
|
481
|
-
|
|
503
|
+
Log.debug(
|
|
482
504
|
`[MT - ${getTimestamp()}] Detected ${ads.length} ad break(s)`,
|
|
483
505
|
);
|
|
484
506
|
this.mergeNewAds(ads);
|
|
485
507
|
}
|
|
486
508
|
} catch (error) {
|
|
487
|
-
|
|
509
|
+
Log.debug(`[MT - ${getTimestamp()}] HLS fetch error:`, error);
|
|
488
510
|
}
|
|
489
511
|
}
|
|
490
512
|
|
|
@@ -493,7 +515,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
493
515
|
*/
|
|
494
516
|
async fetchAndParseDashManifest(manifestUrl) {
|
|
495
517
|
try {
|
|
496
|
-
|
|
518
|
+
Log.debug(`[MT - ${getTimestamp()}] Fetching DASH manifest`);
|
|
497
519
|
|
|
498
520
|
// Fetch DASH manifest
|
|
499
521
|
const xmlText = await fetchDashManifest(manifestUrl);
|
|
@@ -508,7 +530,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
508
530
|
// Parse for ads
|
|
509
531
|
const ads = parseDashManifestForAdBreaks(xmlText);
|
|
510
532
|
|
|
511
|
-
|
|
533
|
+
Log.debug(
|
|
512
534
|
`[MT - ${getTimestamp()}] DASH: ${ads.length} ad break(s) found`,
|
|
513
535
|
);
|
|
514
536
|
|
|
@@ -520,14 +542,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
520
542
|
!this.hasAttemptedTrackingFetch
|
|
521
543
|
) {
|
|
522
544
|
// No SCTE-35 markers in manifest - fall back to tracking API directly
|
|
523
|
-
|
|
545
|
+
Log.debug(
|
|
524
546
|
`[MT - ${getTimestamp()}] DASH: no manifest cues, fetching tracking API`,
|
|
525
547
|
);
|
|
526
548
|
this.hasAttemptedTrackingFetch = true;
|
|
527
549
|
this.getAndProcessTrackingMetadata();
|
|
528
550
|
}
|
|
529
551
|
} catch (error) {
|
|
530
|
-
|
|
552
|
+
Log.debug(`[MT - ${getTimestamp()}] DASH fetch error:`, error);
|
|
531
553
|
}
|
|
532
554
|
}
|
|
533
555
|
|
|
@@ -535,14 +557,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
535
557
|
* Handles DASH emsg events from Shaka Player
|
|
536
558
|
*/
|
|
537
559
|
handleDASHEmsgEvent(event) {
|
|
538
|
-
|
|
560
|
+
Log.debug(`[MT - ${getTimestamp()}] DASH emsg event:`, event);
|
|
539
561
|
|
|
540
562
|
try {
|
|
541
563
|
// Shaka Player emits emsg events with event.detail containing the emsg box data
|
|
542
564
|
const emsgData = event.detail;
|
|
543
565
|
|
|
544
566
|
if (!emsgData) {
|
|
545
|
-
|
|
567
|
+
Log.debug(`[MT - ${getTimestamp()}] No emsg data in event`);
|
|
546
568
|
return;
|
|
547
569
|
}
|
|
548
570
|
|
|
@@ -551,14 +573,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
551
573
|
const schemeIdUri = emsgData.schemeIdUri || '';
|
|
552
574
|
|
|
553
575
|
if (!schemeIdUri.includes(SCTE35_SCHEME_MARKER)) {
|
|
554
|
-
|
|
576
|
+
Log.debug(
|
|
555
577
|
`[MT - ${getTimestamp()}] Non-SCTE-35 emsg, skipping:`,
|
|
556
578
|
schemeIdUri,
|
|
557
579
|
);
|
|
558
580
|
return;
|
|
559
581
|
}
|
|
560
582
|
|
|
561
|
-
|
|
583
|
+
Log.debug(`[MT - ${getTimestamp()}] SCTE-35 emsg detected:`, {
|
|
562
584
|
schemeIdUri: emsgData.schemeIdUri,
|
|
563
585
|
value: emsgData.value,
|
|
564
586
|
timescale: emsgData.timescale,
|
|
@@ -592,14 +614,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
592
614
|
pods: [],
|
|
593
615
|
};
|
|
594
616
|
|
|
595
|
-
|
|
617
|
+
Log.debug(
|
|
596
618
|
`[MT - ${getTimestamp()}] Adding ad break from DASH emsg:`,
|
|
597
619
|
adBreak,
|
|
598
620
|
);
|
|
599
621
|
this.mergeNewAds([adBreak]);
|
|
600
622
|
}
|
|
601
623
|
} catch (error) {
|
|
602
|
-
|
|
624
|
+
Log.debug(
|
|
603
625
|
`[MT - ${getTimestamp()}] Error parsing DASH emsg event:`,
|
|
604
626
|
error,
|
|
605
627
|
);
|
|
@@ -610,14 +632,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
610
632
|
* Handles DASH event stream from dash.js
|
|
611
633
|
*/
|
|
612
634
|
handleDASHEventStream(event) {
|
|
613
|
-
|
|
635
|
+
Log.debug(`[MT - ${getTimestamp()}] DASH event stream:`, event);
|
|
614
636
|
|
|
615
637
|
try {
|
|
616
638
|
// dash.js emits events with different structure
|
|
617
639
|
const eventData = event.event || event;
|
|
618
640
|
|
|
619
641
|
if (!eventData) {
|
|
620
|
-
|
|
642
|
+
Log.debug(`[MT - ${getTimestamp()}] No event data`);
|
|
621
643
|
return;
|
|
622
644
|
}
|
|
623
645
|
|
|
@@ -625,14 +647,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
625
647
|
const schemeIdUri = eventData.schemeIdUri || '';
|
|
626
648
|
|
|
627
649
|
if (!schemeIdUri.includes(SCTE35_SCHEME_MARKER)) {
|
|
628
|
-
|
|
650
|
+
Log.debug(
|
|
629
651
|
`[MT - ${getTimestamp()}] Non-SCTE-35 event, skipping:`,
|
|
630
652
|
schemeIdUri,
|
|
631
653
|
);
|
|
632
654
|
return;
|
|
633
655
|
}
|
|
634
656
|
|
|
635
|
-
|
|
657
|
+
Log.debug(`[MT - ${getTimestamp()}] SCTE-35 event stream detected:`, {
|
|
636
658
|
id: eventData.id,
|
|
637
659
|
schemeIdUri: eventData.schemeIdUri,
|
|
638
660
|
presentationTime: eventData.presentationTime,
|
|
@@ -658,14 +680,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
658
680
|
pods: [],
|
|
659
681
|
};
|
|
660
682
|
|
|
661
|
-
|
|
683
|
+
Log.debug(
|
|
662
684
|
`[MT - ${getTimestamp()}] Adding ad break from DASH event stream:`,
|
|
663
685
|
adBreak,
|
|
664
686
|
);
|
|
665
687
|
this.mergeNewAds([adBreak]);
|
|
666
688
|
}
|
|
667
689
|
} catch (error) {
|
|
668
|
-
|
|
690
|
+
Log.debug(
|
|
669
691
|
`[MT - ${getTimestamp()}] Error parsing DASH event stream:`,
|
|
670
692
|
error,
|
|
671
693
|
);
|
|
@@ -676,14 +698,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
676
698
|
* Parses VHS playlist object for ads
|
|
677
699
|
*/
|
|
678
700
|
parseVhsPlaylistForAdBreaks(playlist) {
|
|
679
|
-
|
|
701
|
+
Log.debug(
|
|
680
702
|
`[MT - ${getTimestamp()}] Parsing VHS playlist (${
|
|
681
703
|
playlist.segments?.length || 0
|
|
682
704
|
} segments)`,
|
|
683
705
|
);
|
|
684
706
|
|
|
685
707
|
if (!playlist.segments || playlist.segments.length === 0) {
|
|
686
|
-
|
|
708
|
+
Log.debug(`[MT - ${getTimestamp()}] No segments in playlist`);
|
|
687
709
|
return;
|
|
688
710
|
}
|
|
689
711
|
|
|
@@ -693,10 +715,23 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
693
715
|
);
|
|
694
716
|
|
|
695
717
|
// VHS strips CUE tags - detect via discontinuityStarts + MediaTailor segments
|
|
696
|
-
const ads = detectAdBreaksFromVhsPlaylist(playlist
|
|
718
|
+
const ads = detectAdBreaksFromVhsPlaylist(playlist, {
|
|
719
|
+
adSegmentPrefix: this.adSegmentPrefix,
|
|
720
|
+
});
|
|
697
721
|
|
|
698
722
|
if (ads.length > 0) {
|
|
699
|
-
|
|
723
|
+
// Log which detection path matched — only on first detection to avoid noise
|
|
724
|
+
if (!this._detectionPathLogged) {
|
|
725
|
+
const firstSeg = playlist.segments && playlist.segments.find(s =>
|
|
726
|
+
whichAdSegmentMarker(s, { adSegmentPrefix: this.adSegmentPrefix })
|
|
727
|
+
);
|
|
728
|
+
if (firstSeg) {
|
|
729
|
+
const path = whichAdSegmentMarker(firstSeg, { adSegmentPrefix: this.adSegmentPrefix });
|
|
730
|
+
Log.debug(`[MT] ad segment detection matched via: ${path}`);
|
|
731
|
+
this._detectionPathLogged = true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
Log.debug(
|
|
700
735
|
`[MT - ${getTimestamp()}] VHS detected ${ads.length} ad break(s), ${ads.reduce(
|
|
701
736
|
(sum, ab) => sum + ab.pods.length,
|
|
702
737
|
0,
|
|
@@ -704,7 +739,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
704
739
|
);
|
|
705
740
|
this.mergeNewAds(ads);
|
|
706
741
|
} else {
|
|
707
|
-
|
|
742
|
+
Log.debug(`[MT - ${getTimestamp()}] No ads detected in VHS playlist`);
|
|
708
743
|
}
|
|
709
744
|
}
|
|
710
745
|
|
|
@@ -716,7 +751,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
716
751
|
if (this.streamType !== STREAM_TYPE.LIVE) return;
|
|
717
752
|
|
|
718
753
|
if (this.isFetchingManifest) {
|
|
719
|
-
|
|
754
|
+
Log.debug(
|
|
720
755
|
`[MT - ${getTimestamp()}] Manifest fetch already in progress, skipping`,
|
|
721
756
|
);
|
|
722
757
|
return;
|
|
@@ -735,7 +770,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
735
770
|
await this.fetchAndParseDashManifest(this.playbackManifestUrl);
|
|
736
771
|
}
|
|
737
772
|
} catch (error) {
|
|
738
|
-
|
|
773
|
+
Log.debug(`[MT - ${getTimestamp()}] Manifest poll error:`, error);
|
|
739
774
|
} finally {
|
|
740
775
|
this.isFetchingManifest = false;
|
|
741
776
|
}
|
|
@@ -747,7 +782,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
747
782
|
mergeNewAds(newAds) {
|
|
748
783
|
this.adSchedule = mergeAdSchedules(this.adSchedule, newAds);
|
|
749
784
|
|
|
750
|
-
|
|
785
|
+
Log.debug(
|
|
751
786
|
`[MT - ${getTimestamp()}] Ad schedule: ${this.adSchedule.length} ad break(s)`,
|
|
752
787
|
);
|
|
753
788
|
|
|
@@ -758,7 +793,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
758
793
|
!this.hasAttemptedTrackingFetch
|
|
759
794
|
) {
|
|
760
795
|
this.hasAttemptedTrackingFetch = true;
|
|
761
|
-
|
|
796
|
+
Log.debug(
|
|
762
797
|
`[MT - ${getTimestamp()}] Fetching tracking metadata (first manifest parse)`,
|
|
763
798
|
);
|
|
764
799
|
this.getAndProcessTrackingMetadata();
|
|
@@ -782,7 +817,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
782
817
|
}
|
|
783
818
|
|
|
784
819
|
this.liveRefreshIntervalSeconds = intervalSeconds;
|
|
785
|
-
|
|
820
|
+
Log.debug(
|
|
786
821
|
`[MT - ${getTimestamp()}] Derived live polling interval from ${source}: ${intervalSeconds}s`,
|
|
787
822
|
);
|
|
788
823
|
|
|
@@ -798,7 +833,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
798
833
|
if (this.isDisposed || !this.trackingEndpointUrl) return;
|
|
799
834
|
|
|
800
835
|
if (this.isFetchingTracking) {
|
|
801
|
-
|
|
836
|
+
Log.debug(
|
|
802
837
|
`[MT - ${getTimestamp()}] Tracking fetch already in progress, skipping`,
|
|
803
838
|
);
|
|
804
839
|
return;
|
|
@@ -807,7 +842,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
807
842
|
this.isFetchingTracking = true;
|
|
808
843
|
|
|
809
844
|
try {
|
|
810
|
-
|
|
845
|
+
Log.debug(`[MT - ${getTimestamp()}] Fetching tracking metadata`);
|
|
811
846
|
|
|
812
847
|
this.trackingAbortController = new AbortController();
|
|
813
848
|
|
|
@@ -818,35 +853,35 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
818
853
|
);
|
|
819
854
|
|
|
820
855
|
if (this.isDisposed) {
|
|
821
|
-
|
|
856
|
+
Log.debug(
|
|
822
857
|
`[MT - ${getTimestamp()}] Disposed during tracking fetch, ignoring result`,
|
|
823
858
|
);
|
|
824
859
|
return;
|
|
825
860
|
}
|
|
826
861
|
|
|
827
862
|
if (data.avails && data.avails.length > 0) {
|
|
828
|
-
|
|
863
|
+
Log.debug(
|
|
829
864
|
`[MT - ${getTimestamp()}] Enriching with ${data.avails.length} avail(s)`,
|
|
830
865
|
);
|
|
831
866
|
this.enrichWithTrackingMetadata(data.avails);
|
|
832
867
|
this.trackingFetchRetries = 0;
|
|
833
868
|
} else {
|
|
834
|
-
|
|
869
|
+
Log.debug(`[MT - ${getTimestamp()}] Tracking API returned 0 avails`);
|
|
835
870
|
}
|
|
836
871
|
} catch (error) {
|
|
837
872
|
if (error.name === 'AbortError' || this.isDisposed) {
|
|
838
|
-
|
|
873
|
+
Log.debug(`[MT - ${getTimestamp()}] Tracking fetch aborted`);
|
|
839
874
|
return;
|
|
840
875
|
}
|
|
841
876
|
|
|
842
|
-
|
|
877
|
+
Log.debug(
|
|
843
878
|
`[MT - ${getTimestamp()}] Tracking API error: ${error.message}`,
|
|
844
879
|
error,
|
|
845
880
|
);
|
|
846
881
|
|
|
847
882
|
if (this.trackingFetchRetries < this.maxTrackingRetries) {
|
|
848
883
|
this.trackingFetchRetries++;
|
|
849
|
-
|
|
884
|
+
Log.debug(
|
|
850
885
|
`[MT - ${getTimestamp()}] Retrying tracking fetch (${this.trackingFetchRetries}/${this.maxTrackingRetries})`,
|
|
851
886
|
);
|
|
852
887
|
this.isFetchingTracking = false;
|
|
@@ -854,7 +889,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
854
889
|
return;
|
|
855
890
|
}
|
|
856
891
|
|
|
857
|
-
|
|
892
|
+
Log.debug(
|
|
858
893
|
`[MT - ${getTimestamp()}] Max retries reached, continuing with manifest data only`,
|
|
859
894
|
);
|
|
860
895
|
} finally {
|
|
@@ -875,14 +910,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
875
910
|
this.adSchedule.sort((a, b) => a.startTime - b.startTime);
|
|
876
911
|
}
|
|
877
912
|
|
|
878
|
-
|
|
913
|
+
Log.debug(
|
|
879
914
|
`[MT - ${getTimestamp()}] Enrichment complete: ${
|
|
880
915
|
this.adSchedule.length
|
|
881
916
|
} ad break(s)`,
|
|
882
917
|
);
|
|
883
918
|
|
|
884
919
|
// Log enriched schedule with full details
|
|
885
|
-
|
|
920
|
+
Log.debug(
|
|
886
921
|
`[MT - ${getTimestamp()}] Enriched schedule:`,
|
|
887
922
|
this.adSchedule.map((ab) => ({
|
|
888
923
|
id: ab.id,
|
|
@@ -901,7 +936,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
901
936
|
);
|
|
902
937
|
|
|
903
938
|
// Log current player time for debugging
|
|
904
|
-
|
|
939
|
+
Log.debug(
|
|
905
940
|
`[MT - ${getTimestamp()}] Current player time: ${this.player.currentTime()}s`,
|
|
906
941
|
);
|
|
907
942
|
}
|
|
@@ -919,7 +954,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
919
954
|
});
|
|
920
955
|
|
|
921
956
|
quartilesToFire.forEach(({ quartile, key }) => {
|
|
922
|
-
|
|
957
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_QUARTILE ${quartile * 25}%`);
|
|
923
958
|
this.sendAdQuartile({ quartile });
|
|
924
959
|
adObject[`hasFired${key.toUpperCase()}`] = true;
|
|
925
960
|
});
|
|
@@ -938,7 +973,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
938
973
|
Math.floor(currentTime) % 5 === 0 &&
|
|
939
974
|
Math.floor(currentTime * 10) % 10 === 0
|
|
940
975
|
) {
|
|
941
|
-
|
|
976
|
+
Log.debug(
|
|
942
977
|
`[MT - ${getTimestamp()}] TimeUpdate: ${currentTime.toFixed(2)}s, Active break: ${
|
|
943
978
|
activeAdBreak ? activeAdBreak.id : 'none'
|
|
944
979
|
}, Schedule count: ${this.adSchedule.length}`,
|
|
@@ -952,7 +987,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
952
987
|
if (!activeAdBreak.hasFiredStart) {
|
|
953
988
|
this.currentAdBreak = activeAdBreak;
|
|
954
989
|
this.setIsAd(true); // Switch to ad mode
|
|
955
|
-
|
|
990
|
+
Log.debug(
|
|
956
991
|
`[MT - ${getTimestamp()}] setIsAd(true) - Entering ad break`,
|
|
957
992
|
);
|
|
958
993
|
|
|
@@ -969,7 +1004,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
969
1004
|
// Store position on the ad break for reuse
|
|
970
1005
|
activeAdBreak.adPosition = adPosition;
|
|
971
1006
|
|
|
972
|
-
|
|
1007
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_BREAK_START`, {
|
|
973
1008
|
startTime: activeAdBreak.startTime,
|
|
974
1009
|
duration: activeAdBreak.duration,
|
|
975
1010
|
podCount: activeAdBreak.pods?.length || 0,
|
|
@@ -990,14 +1025,14 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
990
1025
|
if (!this.currentAdPod || this.currentAdPod !== activePod) {
|
|
991
1026
|
// End previous pod
|
|
992
1027
|
if (this.currentAdPod) {
|
|
993
|
-
|
|
1028
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_END (pod transition)`);
|
|
994
1029
|
this.sendEnd();
|
|
995
1030
|
}
|
|
996
1031
|
|
|
997
1032
|
// Start new pod
|
|
998
1033
|
this.currentAdPod = activePod;
|
|
999
1034
|
|
|
1000
|
-
|
|
1035
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_START (new pod)`, {
|
|
1001
1036
|
startTime: activePod.startTime,
|
|
1002
1037
|
duration: activePod.duration,
|
|
1003
1038
|
position: activeAdBreak.adPosition,
|
|
@@ -1030,7 +1065,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1030
1065
|
} else {
|
|
1031
1066
|
// No pods - treat entire break as single ad
|
|
1032
1067
|
if (!activeAdBreak.hasFiredAdStart) {
|
|
1033
|
-
|
|
1068
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_START (no pods)`, {
|
|
1034
1069
|
startTime: activeAdBreak.startTime,
|
|
1035
1070
|
duration: activeAdBreak.duration,
|
|
1036
1071
|
position: activeAdBreak.adPosition,
|
|
@@ -1059,25 +1094,25 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1059
1094
|
|
|
1060
1095
|
// End last pod
|
|
1061
1096
|
if (this.currentAdPod) {
|
|
1062
|
-
|
|
1097
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_END (final pod)`);
|
|
1063
1098
|
this.sendEnd();
|
|
1064
1099
|
this.currentAdPod = null;
|
|
1065
1100
|
}
|
|
1066
1101
|
|
|
1067
1102
|
// End ad break
|
|
1068
1103
|
if (!this.currentAdBreak.hasFiredEnd) {
|
|
1069
|
-
|
|
1104
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_BREAK_END`);
|
|
1070
1105
|
this.sendAdBreakEnd();
|
|
1071
1106
|
this.currentAdBreak.hasFiredEnd = true;
|
|
1072
1107
|
}
|
|
1073
1108
|
|
|
1074
1109
|
this.currentAdBreak = null;
|
|
1075
1110
|
this.setIsAd(false); // Switch back to content mode
|
|
1076
|
-
|
|
1111
|
+
Log.debug(`[MT - ${getTimestamp()}] setIsAd(false) - Exiting ad break`);
|
|
1077
1112
|
|
|
1078
1113
|
// Check if video has ended after exiting last ad break
|
|
1079
1114
|
if (this.player.ended() && !this.hasEndedContent) {
|
|
1080
|
-
|
|
1115
|
+
Log.debug(
|
|
1081
1116
|
`[MT - ${getTimestamp()}] Video ended after last ad → CONTENT_END`,
|
|
1082
1117
|
);
|
|
1083
1118
|
this.sendContentEnd();
|
|
@@ -1104,7 +1139,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1104
1139
|
*/
|
|
1105
1140
|
handleAdEvent(eventName, sendMethod) {
|
|
1106
1141
|
if (this.isAd()) {
|
|
1107
|
-
|
|
1142
|
+
Log.debug(`[MT - ${getTimestamp()}] → ${eventName}`);
|
|
1108
1143
|
sendMethod.call(this);
|
|
1109
1144
|
}
|
|
1110
1145
|
}
|
|
@@ -1121,7 +1156,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1121
1156
|
*/
|
|
1122
1157
|
onPlaying() {
|
|
1123
1158
|
if (this.isAd()) {
|
|
1124
|
-
|
|
1159
|
+
Log.debug(`[MT - ${getTimestamp()}] → AD_RESUME`);
|
|
1125
1160
|
this.sendResume();
|
|
1126
1161
|
this.sendBufferEnd(); // Playing event also ends any buffering
|
|
1127
1162
|
}
|
|
@@ -1153,7 +1188,7 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1153
1188
|
*/
|
|
1154
1189
|
onEnded() {
|
|
1155
1190
|
if (!this.hasEndedContent) {
|
|
1156
|
-
|
|
1191
|
+
Log.debug(`[MT - ${getTimestamp()}] Video ended → CONTENT_END`);
|
|
1157
1192
|
this.sendContentEnd();
|
|
1158
1193
|
this.hasEndedContent = true;
|
|
1159
1194
|
}
|
|
@@ -1216,25 +1251,25 @@ export default class MediaTailorAdsTracker extends VideojsAdsTracker {
|
|
|
1216
1251
|
this.trackingPollTimer = null;
|
|
1217
1252
|
}
|
|
1218
1253
|
|
|
1219
|
-
|
|
1254
|
+
Log.debug(`[MT - ${getTimestamp()}] Polling stopped`);
|
|
1220
1255
|
}
|
|
1221
1256
|
|
|
1222
1257
|
/**
|
|
1223
1258
|
* Cleanup when tracker is destroyed
|
|
1224
1259
|
*/
|
|
1225
1260
|
dispose() {
|
|
1226
|
-
|
|
1261
|
+
Log.debug(`[MT - ${getTimestamp()}] Disposing MediaTailorAdsTracker`);
|
|
1227
1262
|
|
|
1228
1263
|
this.isDisposed = true;
|
|
1229
1264
|
|
|
1230
1265
|
if (this.trackingAbortController) {
|
|
1231
|
-
|
|
1266
|
+
Log.debug(`[MT - ${getTimestamp()}] Aborting in-flight tracking fetch`);
|
|
1232
1267
|
this.trackingAbortController.abort();
|
|
1233
1268
|
this.trackingAbortController = null;
|
|
1234
1269
|
}
|
|
1235
1270
|
|
|
1236
1271
|
if (this.manifestAbortController) {
|
|
1237
|
-
|
|
1272
|
+
Log.debug(`[MT - ${getTimestamp()}] Aborting in-flight manifest fetch`);
|
|
1238
1273
|
this.manifestAbortController.abort();
|
|
1239
1274
|
this.manifestAbortController = null;
|
|
1240
1275
|
}
|