UNPKG

32.2 kBJavaScriptView Raw
1/*!
2 * YouTube tracking for Snowplow v3.2.3 (http://bit.ly/sp-js)
3 * Copyright 2022 Snowplow Analytics Ltd
4 * Licensed under BSD-3-Clause
5 */
6
7(function (global, factory) {
8 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
9 typeof define === 'function' && define.amd ? define(['exports'], factory) :
10 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.snowplowYouTubeTracking = {}));
11}(this, (function (exports) { 'use strict';
12
13 /*! *****************************************************************************
14 Copyright (c) Microsoft Corporation.
15
16 Permission to use, copy, modify, and/or distribute this software for any
17 purpose with or without fee is hereby granted.
18
19 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
20 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
21 AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
22 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
23 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
24 OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
25 PERFORMANCE OF THIS SOFTWARE.
26 ***************************************************************************** */
27
28 var __assign = function() {
29 __assign = Object.assign || function __assign(t) {
30 for (var s, i = 1, n = arguments.length; i < n; i++) {
31 s = arguments[i];
32 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
33 }
34 return t;
35 };
36 return __assign.apply(this, arguments);
37 };
38
39 function __spreadArray(to, from, pack) {
40 if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
41 if (ar || !(i in from)) {
42 if (!ar) ar = Array.prototype.slice.call(from, 0, i);
43 ar[i] = from[i];
44 }
45 }
46 return to.concat(ar || from);
47 }
48
49 var SnowplowEvent;
50 (function (SnowplowEvent) {
51 SnowplowEvent["PERCENTPROGRESS"] = "percentprogress";
52 SnowplowEvent["SEEK"] = "seek";
53 SnowplowEvent["VOLUMECHANGE"] = "volumechange";
54 })(SnowplowEvent || (SnowplowEvent = {}));
55
56 var YouTubeIFrameAPIURL = 'https://www.youtube.com/iframe_api';
57 // The payload a YouTube player event emits has no identifier of what event it is
58 // Some payloads can emit the same data
59 // i.e. onError and onPlaybackRateChange can both emit '{data: 2}'
60 var YTPlayerEvent;
61 (function (YTPlayerEvent) {
62 YTPlayerEvent["ONSTATECHANGE"] = "onStateChange";
63 YTPlayerEvent["ONPLAYBACKQUALITYCHANGE"] = "onPlaybackQualityChange";
64 YTPlayerEvent["ONERROR"] = "onError";
65 YTPlayerEvent["ONAPICHANGE"] = "onApiChange";
66 YTPlayerEvent["ONPLAYBACKRATECHANGE"] = "onPlaybackRateChange";
67 YTPlayerEvent["ONREADY"] = "onReady";
68 })(YTPlayerEvent || (YTPlayerEvent = {}));
69 var YTStateEvent = {
70 '-1': 'unstarted',
71 '0': 'ended',
72 '1': 'play',
73 '2': 'pause',
74 '3': 'buffering',
75 '5': 'cued'
76 };
77 var CaptureEventToYouTubeEvent = {
78 ready: YTPlayerEvent.ONREADY,
79 playbackratechange: YTPlayerEvent.ONPLAYBACKRATECHANGE,
80 playbackqualitychange: YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
81 error: YTPlayerEvent.ONERROR,
82 apichange: YTPlayerEvent.ONAPICHANGE
83 };
84 // As every state event requires YTPlayerEvent.ONSTATECHANGE, they are added
85 // to CaptureEventToYouTubeEvent with the below loop
86 Object.keys(YTStateEvent).forEach(function (k) { return (CaptureEventToYouTubeEvent[YTStateEvent[k]] = YTPlayerEvent.ONSTATECHANGE); });
87 var YTState;
88 (function (YTState) {
89 YTState["UNSTARTED"] = "unstarted";
90 YTState["ENDED"] = "ended";
91 YTState["PLAYING"] = "play";
92 YTState["PAUSED"] = "pause";
93 YTState["BUFFERING"] = "buffering";
94 YTState["CUED"] = "cued";
95 })(YTState || (YTState = {}));
96 var YTError = {
97 2: 'INVALID_URL',
98 5: 'HTML5_ERROR',
99 100: 'VIDEO_NOT_FOUND',
100 101: 'MISSING_EMBED_PERMISSION',
101 150: 'MISSING_EMBED_PERMISSION'
102 };
103
104 var YTEvent;
105 (function (YTEvent) {
106 YTEvent["STATECHANGE"] = "statechange";
107 YTEvent["PLAYBACKQUALITYCHANGE"] = "playbackqualitychange";
108 YTEvent["ERROR"] = "error";
109 YTEvent["APICHANGE"] = "apichange";
110 YTEvent["PLAYBACKRATECHANGE"] = "playbackratechange";
111 YTEvent["READY"] = "ready";
112 })(YTEvent || (YTEvent = {}));
113 [
114 YTState.BUFFERING,
115 YTState.CUED,
116 YTState.ENDED,
117 YTState.PAUSED,
118 YTState.PLAYING,
119 YTState.UNSTARTED,
120 ];
121
122 var AllEvents = __spreadArray(__spreadArray(__spreadArray([], Object.keys(YTEvent).map(function (k) { return YTEvent[k]; })), Object.keys(SnowplowEvent).map(function (k) { return SnowplowEvent[k]; })), Object.keys(YTState).map(function (k) { return YTState[k]; }));
123 var DefaultEvents = [
124 YTState.PAUSED,
125 YTState.PLAYING,
126 YTState.ENDED,
127 SnowplowEvent.SEEK,
128 SnowplowEvent.VOLUMECHANGE,
129 YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
130 YTPlayerEvent.ONPLAYBACKRATECHANGE,
131 SnowplowEvent.PERCENTPROGRESS,
132 ];
133 var EventGroups = {
134 AllEvents: AllEvents,
135 DefaultEvents: DefaultEvents
136 };
137
138 function trackingOptionsParser(mediaId, conf) {
139 var defaults = {
140 mediaId: mediaId,
141 captureEvents: DefaultEvents,
142 youtubeEvents: [
143 YTPlayerEvent.ONSTATECHANGE,
144 YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
145 YTPlayerEvent.ONERROR,
146 YTPlayerEvent.ONPLAYBACKRATECHANGE,
147 ],
148 updateRate: 500,
149 progress: {
150 boundaries: [10, 25, 50, 75],
151 boundaryTimeoutIds: []
152 }
153 };
154 if (!conf)
155 return defaults;
156 if (conf.updateRate)
157 defaults.updateRate = conf.updateRate;
158 if (conf.captureEvents) {
159 var parsedEvents = [];
160 var _loop_1 = function (ev) {
161 // If an event is an EventGroup, get the events from that group
162 if (EventGroups.hasOwnProperty(ev)) {
163 parsedEvents = parsedEvents.concat(EventGroups[ev]);
164 }
165 else if (!Object.keys(AllEvents).filter(function (k) { return k === ev; })) {
166 console.warn("'" + ev + "' is not a valid event.");
167 }
168 else {
169 parsedEvents.push(ev);
170 }
171 };
172 for (var _i = 0, _a = conf.captureEvents; _i < _a.length; _i++) {
173 var ev = _a[_i];
174 _loop_1(ev);
175 }
176 conf.captureEvents = parsedEvents;
177 for (var _b = 0, _c = conf.captureEvents; _b < _c.length; _b++) {
178 var ev = _c[_b];
179 var youtubeEvent = CaptureEventToYouTubeEvent[ev];
180 if (CaptureEventToYouTubeEvent.hasOwnProperty(ev) && defaults.youtubeEvents.indexOf(youtubeEvent) === -1) {
181 defaults.youtubeEvents.push(youtubeEvent);
182 }
183 }
184 if (conf.captureEvents.indexOf(SnowplowEvent.PERCENTPROGRESS) !== -1) {
185 defaults.progress = {
186 boundaries: (conf === null || conf === void 0 ? void 0 : conf.boundaries) || defaults.progress.boundaries,
187 boundaryTimeoutIds: []
188 };
189 }
190 }
191 return __assign(__assign({}, defaults), conf);
192 }
193 // URLSearchParams is not supported in IE
194 // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
195 function addUrlParam(url, key, value) {
196 var urlParams = parseUrlParams(url);
197 urlParams[key] = value;
198 return url + "?" + urlParamsToString(urlParams);
199 }
200 function parseUrlParams(url) {
201 var params = {};
202 var urlParams = url.split('?')[1];
203 if (!urlParams)
204 return params;
205 urlParams.split('&').forEach(function (p) {
206 var param = p.split('=');
207 params[param[0]] = param[1];
208 });
209 return params;
210 }
211 function urlParamsToString(urlParams) {
212 // convert an object of url parameters to a string
213 var params = '';
214 Object.keys(urlParams).forEach(function (p) {
215 params += p + "=" + urlParams[p] + "&";
216 });
217 return params.slice(0, -1);
218 }
219
220 /*!
221 * Core functionality for Snowplow JavaScript trackers v3.2.3 (http://bit.ly/sp-js)
222 * Copyright 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
223 * Licensed under BSD-3-Clause
224 */
225
226 /*
227 * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
228 * All rights reserved.
229 *
230 * Redistribution and use in source and binary forms, with or without
231 * modification, are permitted provided that the following conditions are met:
232 *
233 * 1. Redistributions of source code must retain the above copyright notice, this
234 * list of conditions and the following disclaimer.
235 *
236 * 2. Redistributions in binary form must reproduce the above copyright notice,
237 * this list of conditions and the following disclaimer in the documentation
238 * and/or other materials provided with the distribution.
239 *
240 * 3. Neither the name of the copyright holder nor the names of its
241 * contributors may be used to endorse or promote products derived from
242 * this software without specific prior written permission.
243 *
244 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
245 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
246 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
247 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
248 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
249 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
250 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
251 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
252 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
253 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
254 */
255 function payloadBuilder() {
256 var dict = {}, allJson = [], jsonForProcessing = [];
257 var processor;
258 var add = function (key, value) {
259 if (value != null && value !== '') {
260 // null also checks undefined
261 dict[key] = value;
262 }
263 };
264 var addDict = function (dict) {
265 for (var key in dict) {
266 if (Object.prototype.hasOwnProperty.call(dict, key)) {
267 add(key, dict[key]);
268 }
269 }
270 };
271 var addJson = function (keyIfEncoded, keyIfNotEncoded, json) {
272 if (json && isNonEmptyJson(json)) {
273 var jsonWithKeys = { keyIfEncoded: keyIfEncoded, keyIfNotEncoded: keyIfNotEncoded, json: json };
274 jsonForProcessing.push(jsonWithKeys);
275 allJson.push(jsonWithKeys);
276 }
277 };
278 return {
279 add: add,
280 addDict: addDict,
281 addJson: addJson,
282 getPayload: function () { return dict; },
283 getJson: function () { return allJson; },
284 withJsonProcessor: function (jsonProcessor) {
285 processor = jsonProcessor;
286 },
287 build: function () {
288 processor === null || processor === void 0 ? void 0 : processor(this, jsonForProcessing);
289 return dict;
290 }
291 };
292 }
293 /**
294 * Is property a non-empty JSON?
295 * @param property - Checks if object is non-empty json
296 */
297 function isNonEmptyJson(property) {
298 if (!isJson(property)) {
299 return false;
300 }
301 for (var key in property) {
302 if (Object.prototype.hasOwnProperty.call(property, key)) {
303 return true;
304 }
305 }
306 return false;
307 }
308 /**
309 * Is property a JSON?
310 * @param property - Checks if object is json
311 */
312 function isJson(property) {
313 return (typeof property !== 'undefined' &&
314 property !== null &&
315 (property.constructor === {}.constructor || property.constructor === [].constructor));
316 }
317
318 /*
319 * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
320 * All rights reserved.
321 *
322 * Redistribution and use in source and binary forms, with or without
323 * modification, are permitted provided that the following conditions are met:
324 *
325 * 1. Redistributions of source code must retain the above copyright notice, this
326 * list of conditions and the following disclaimer.
327 *
328 * 2. Redistributions in binary form must reproduce the above copyright notice,
329 * this list of conditions and the following disclaimer in the documentation
330 * and/or other materials provided with the distribution.
331 *
332 * 3. Neither the name of the copyright holder nor the names of its
333 * contributors may be used to endorse or promote products derived from
334 * this software without specific prior written permission.
335 *
336 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
337 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
338 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
339 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
340 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
341 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
342 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
343 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
344 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
345 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
346 */
347 var label = 'Snowplow: ';
348 var LOG_LEVEL;
349 (function (LOG_LEVEL) {
350 LOG_LEVEL[LOG_LEVEL["none"] = 0] = "none";
351 LOG_LEVEL[LOG_LEVEL["error"] = 1] = "error";
352 LOG_LEVEL[LOG_LEVEL["warn"] = 2] = "warn";
353 LOG_LEVEL[LOG_LEVEL["debug"] = 3] = "debug";
354 LOG_LEVEL[LOG_LEVEL["info"] = 4] = "info";
355 })(LOG_LEVEL || (LOG_LEVEL = {}));
356 var LOG$1 = logger();
357 function logger(logLevel) {
358 if (logLevel === void 0) { logLevel = LOG_LEVEL.warn; }
359 function setLogLevel(level) {
360 if (LOG_LEVEL[level]) {
361 logLevel = level;
362 }
363 else {
364 logLevel = LOG_LEVEL.warn;
365 }
366 }
367 /**
368 * Log errors, with or without error object
369 */
370 function error(message, error) {
371 var extraParams = [];
372 for (var _i = 2; _i < arguments.length; _i++) {
373 extraParams[_i - 2] = arguments[_i];
374 }
375 if (logLevel >= LOG_LEVEL.error && typeof console !== 'undefined') {
376 var logMsg = label + message + '\n';
377 if (error) {
378 console.error.apply(console, __spreadArray([logMsg + '\n', error], extraParams));
379 }
380 else {
381 console.error.apply(console, __spreadArray([logMsg], extraParams));
382 }
383 }
384 }
385 /**
386 * Log warnings, with or without error object
387 */
388 function warn(message, error) {
389 var extraParams = [];
390 for (var _i = 2; _i < arguments.length; _i++) {
391 extraParams[_i - 2] = arguments[_i];
392 }
393 if (logLevel >= LOG_LEVEL.warn && typeof console !== 'undefined') {
394 var logMsg = label + message;
395 if (error) {
396 console.warn.apply(console, __spreadArray([logMsg + '\n', error], extraParams));
397 }
398 else {
399 console.warn.apply(console, __spreadArray([logMsg], extraParams));
400 }
401 }
402 }
403 /**
404 * Log debug messages
405 */
406 function debug(message) {
407 var extraParams = [];
408 for (var _i = 1; _i < arguments.length; _i++) {
409 extraParams[_i - 1] = arguments[_i];
410 }
411 if (logLevel >= LOG_LEVEL.debug && typeof console !== 'undefined') {
412 console.debug.apply(console, __spreadArray([label + message], extraParams));
413 }
414 }
415 /**
416 * Log info messages
417 */
418 function info(message) {
419 var extraParams = [];
420 for (var _i = 1; _i < arguments.length; _i++) {
421 extraParams[_i - 1] = arguments[_i];
422 }
423 if (logLevel >= LOG_LEVEL.info && typeof console !== 'undefined') {
424 console.info.apply(console, __spreadArray([label + message], extraParams));
425 }
426 }
427 return { setLogLevel: setLogLevel, warn: warn, error: error, debug: debug, info: info };
428 }
429 /**
430 * Build a self-describing event
431 * A custom event type, allowing for an event to be tracked using your own custom schema
432 * and a data object which conforms to the supplied schema
433 *
434 * @param event - Contains the properties and schema location for the event
435 * @returns PayloadBuilder to be sent to {@link @snowplow/tracker-core#TrackerCore.track}
436 */
437 function buildSelfDescribingEvent(event) {
438 var _a = event.event, schema = _a.schema, data = _a.data, pb = payloadBuilder();
439 var ueJson = {
440 schema: 'iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0',
441 data: { schema: schema, data: data }
442 };
443 pb.add('e', 'ue');
444 pb.addJson('ue_px', 'ue_pr', ueJson);
445 return pb;
446 }
447
448 /*!
449 * Core functionality for Snowplow Browser trackers v3.2.3 (http://bit.ly/sp-js)
450 * Copyright 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
451 * Licensed under BSD-3-Clause
452 */
453 /**
454 * Dispatch function to all specified trackers from the supplied collection
455 *
456 * @param trackers - An optional list of trackers to send the event to, or will send to all trackers
457 * @param trackerCollection - The collection which the trackers will be selected from
458 * @param fn - The function which will run against each tracker
459 */
460 function dispatchToTrackersInCollection(trackers, trackerCollection, fn) {
461 try {
462 getTrackersFromCollection(trackers !== null && trackers !== void 0 ? trackers : Object.keys(trackerCollection), trackerCollection).forEach(fn);
463 }
464 catch (ex) {
465 LOG$1.error('Function failed', ex);
466 }
467 }
468 function getTrackersFromCollection(trackerIds, trackerCollection) {
469 var trackers = [];
470 for (var _i = 0, trackerIds_1 = trackerIds; _i < trackerIds_1.length; _i++) {
471 var id = trackerIds_1[_i];
472 if (trackerCollection.hasOwnProperty(id)) {
473 trackers.push(trackerCollection[id]);
474 }
475 else {
476 LOG$1.warn(id + ' not configured');
477 }
478 }
479 return trackers;
480 }
481
482 function buildYouTubeEvent(player, eventName, conf, eventData) {
483 var data = { type: eventName };
484 if (conf.hasOwnProperty('label'))
485 data.label = conf.label;
486 var context = [
487 getYouTubeEntities(player, conf.urlParameters, eventData),
488 getMediaPlayerEntities(eventName, player, conf.urlParameters, eventData),
489 ];
490 return {
491 schema: 'iglu:com.snowplowanalytics.snowplow/media_player_event/jsonschema/1-0-0',
492 data: data,
493 context: context
494 };
495 }
496 function getYouTubeEntities(player, urlParameters, eventData) {
497 var spherical = player.getSphericalProperties();
498 var playerStates = {
499 buffering: false,
500 cued: false,
501 unstarted: false
502 };
503 var state = player.getPlayerState();
504 if (playerStates.hasOwnProperty(YTStateEvent[state])) {
505 playerStates[YTStateEvent[state]] = true;
506 }
507 var data = {
508 autoPlay: urlParameters.autoplay === '1',
509 avaliablePlaybackRates: player.getAvailablePlaybackRates(),
510 buffering: playerStates.buffering,
511 controls: urlParameters.controls !== '0',
512 cued: playerStates.cued,
513 loaded: parseInt(String(player.getVideoLoadedFraction() * 100)),
514 playbackQuality: player.getPlaybackQuality(),
515 playerId: player.getIframe().id,
516 unstarted: playerStates.unstarted,
517 url: player.getVideoUrl()
518 };
519 if (spherical)
520 data = __assign(__assign({}, data), spherical);
521 if (eventData === null || eventData === void 0 ? void 0 : eventData.error)
522 data.error = eventData.error;
523 var playlistIndex = player.getPlaylistIndex();
524 if (playlistIndex !== -1)
525 data.playlistIndex = playlistIndex;
526 var playlist = player.getPlaylist();
527 if (playlist) {
528 data.playlist = playlist.map(function (item) { return parseInt(item); });
529 }
530 var qualityLevels = player.getAvailableQualityLevels();
531 if (qualityLevels)
532 data.avaliableQualityLevels = qualityLevels;
533 return {
534 schema: 'iglu:com.youtube/youtube/jsonschema/1-0-0',
535 data: data
536 };
537 }
538 function getMediaPlayerEntities(e, player, urlParameters, eventData) {
539 var playerStates = {
540 ended: false,
541 paused: false
542 };
543 var state = player.getPlayerState();
544 if (playerStates.hasOwnProperty(YTStateEvent[state])) {
545 playerStates[YTStateEvent[state]] = true;
546 }
547 var data = {
548 currentTime: player.getCurrentTime(),
549 duration: player.getDuration(),
550 ended: playerStates.ended,
551 loop: urlParameters.loop === '1',
552 muted: player.isMuted(),
553 paused: playerStates.paused,
554 playbackRate: player.getPlaybackRate(),
555 volume: player.getVolume()
556 };
557 if (e === SnowplowEvent.PERCENTPROGRESS) {
558 data.percentProgress = eventData.percentThrough;
559 }
560 return {
561 schema: 'iglu:com.snowplowanalytics.snowplow/media_player/jsonschema/1-0-0',
562 data: data
563 };
564 }
565
566 var _trackers = {};
567 var trackedPlayers = {};
568 var trackingQueue = [];
569 var LOG;
570 function YouTubeTrackingPlugin() {
571 return {
572 activateBrowserPlugin: function (tracker) {
573 _trackers[tracker.id] = tracker;
574 },
575 logger: function (logger) {
576 LOG = logger;
577 }
578 };
579 }
580 function trackEvent(event, trackers) {
581 if (trackers === void 0) { trackers = Object.keys(_trackers); }
582 dispatchToTrackersInCollection(trackers, _trackers, function (t) {
583 t.core.track(buildSelfDescribingEvent({ event: event }), event.context, event.timestamp);
584 });
585 }
586 function enableYouTubeTracking(args) {
587 var conf = trackingOptionsParser(args.id, args.options);
588 var el = document.getElementById(args.id);
589 if (!el) {
590 LOG.error('Cannot find YouTube iframe');
591 return;
592 }
593 // The 'enablejsapi' parameter is required to be '1' for the API to be able to communicate with the player
594 if (el.src.indexOf('enablejsapi') === -1) {
595 el.src = addUrlParam(el.src, 'enablejsapi', '1');
596 }
597 conf.urlParameters = parseUrlParams(el.src);
598 // If the API is ready, we can immediately add the listeners
599 if (typeof YT !== 'undefined' && typeof YT.Player !== 'undefined') {
600 addListeners(conf);
601 }
602 else {
603 // If not, we put them into a queue that will have listeners added once the API is ready
604 // and start trying to load the iframe API
605 trackingQueue.push(conf);
606 handleYouTubeIframeAPI();
607 }
608 }
609 var iframeAPIRetryWait = 100;
610 function handleYouTubeIframeAPI() {
611 // First we check if the script tag exists in the DOM, and enable the API if not
612 var scriptTags = Array.prototype.slice.call(document.getElementsByTagName('script'));
613 if (!scriptTags.some(function (s) { return s.src === YouTubeIFrameAPIURL; })) {
614 // Load the Iframe API
615 // https://developers.google.com/youtube/iframe_api_reference
616 var tag = document.createElement('script');
617 tag.src = YouTubeIFrameAPIURL;
618 var firstScriptTag = document.getElementsByTagName('script')[0];
619 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
620 }
621 // Once the API is ready to use, 'YT.Player' will be defined
622 // 'YT.Player' is not available immediately after 'YT' is defined,
623 // so we need to wait until 'YT' is defined to then check 'YT.Player'
624 if (typeof YT === 'undefined' || typeof YT.Player === 'undefined') {
625 if (iframeAPIRetryWait <= 6400) {
626 setTimeout(handleYouTubeIframeAPI, iframeAPIRetryWait);
627 iframeAPIRetryWait *= 2;
628 }
629 else {
630 LOG.error('YouTube iframe API failed to load.');
631 }
632 }
633 else {
634 // Once the API is avaliable, listeners are attached to anything sitting in the queue
635 while (trackingQueue.length) {
636 addListeners(trackingQueue.pop());
637 }
638 }
639 }
640 function addListeners(conf) {
641 var _a;
642 var builtInEvents = (_a = {},
643 _a[YTPlayerEvent.ONREADY] = function () { return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.READY, conf); },
644 _a[YTPlayerEvent.ONSTATECHANGE] = function (e) {
645 if (conf.captureEvents.indexOf(YTStateEvent[e.data.toString()]) !== -1) {
646 youtubeEvent(trackedPlayers[conf.mediaId].player, YTStateEvent[e.data], conf);
647 }
648 },
649 _a[YTPlayerEvent.ONPLAYBACKQUALITYCHANGE] = function () {
650 return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.PLAYBACKQUALITYCHANGE, conf);
651 },
652 _a[YTPlayerEvent.ONAPICHANGE] = function () { return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.APICHANGE, conf); },
653 _a[YTPlayerEvent.ONERROR] = function (e) {
654 return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.ERROR, conf, { error: YTError[e.data] });
655 },
656 _a[YTPlayerEvent.ONPLAYBACKRATECHANGE] = function () {
657 return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.PLAYBACKRATECHANGE, conf);
658 },
659 _a);
660 var playerEvents = {};
661 conf.youtubeEvents.forEach(function (e) {
662 playerEvents[e] = builtInEvents[e];
663 });
664 trackedPlayers[conf.mediaId] = {
665 player: new YT.Player(conf.mediaId, { events: __assign({}, playerEvents) }),
666 conf: conf,
667 seekTracking: {
668 prevTime: 0,
669 enabled: false
670 },
671 volumeTracking: {
672 prevVolume: 0,
673 enabled: false
674 }
675 };
676 }
677 function youtubeEvent(player, eventName, conf, eventData) {
678 var playerInstance = trackedPlayers[conf.mediaId];
679 if (!playerInstance.seekTracking.enabled && conf.captureEvents.indexOf('seek') !== 1) {
680 enableSeekTracking(player, conf, eventData);
681 }
682 if (!playerInstance.volumeTracking.enabled && conf.captureEvents.indexOf('volume') !== 1) {
683 enableVolumeTracking(player, conf, eventData);
684 }
685 if (conf.hasOwnProperty('boundaries')) {
686 progressHandler(player, eventName, conf);
687 }
688 var event = buildYouTubeEvent(player, eventName, conf, eventData);
689 trackEvent(event);
690 }
691 // Progress Tracking
692 function progressHandler(player, eventName, conf) {
693 var timeoutIds = conf.progress.boundaryTimeoutIds;
694 if (eventName === YTState.PAUSED) {
695 timeoutIds.forEach(function (id) { return clearTimeout(id); });
696 timeoutIds.length = 0;
697 }
698 if (eventName === YTState.PLAYING) {
699 setPercentageBoundTimeouts(player, conf);
700 }
701 }
702 function setPercentageBoundTimeouts(player, conf) {
703 var _a;
704 var currentTime = player.getCurrentTime();
705 (_a = conf.progress) === null || _a === void 0 ? void 0 : _a.boundaries.forEach(function (p) {
706 var _a;
707 var percentTime = player.getDuration() * 1000 * (p / 100);
708 if (currentTime !== 0) {
709 percentTime -= currentTime * 1000;
710 }
711 if (p < percentTime) {
712 (_a = conf.progress) === null || _a === void 0 ? void 0 : _a.boundaryTimeoutIds.push(setTimeout(function () { return waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p); }, percentTime));
713 }
714 });
715 }
716 // The timeout in setPercentageBoundTimeouts fires ~100 - 300ms early
717 // waitAnyRemainingTimeAfterTimeout ensures the event is fired accurately
718 function waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p) {
719 if (player.getCurrentTime() * 1000 < percentTime) {
720 setTimeout(function () { return waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p); }, 10);
721 }
722 else {
723 youtubeEvent(player, SnowplowEvent.PERCENTPROGRESS, conf, {
724 percentThrough: p
725 });
726 }
727 }
728 // Seek Tracking
729 function enableSeekTracking(player, conf, eventData) {
730 trackedPlayers[conf.mediaId].seekTracking.enabled = true;
731 setInterval(function () { return seekEventTracker(player, conf, eventData); }, conf.updateRate);
732 }
733 function seekEventTracker(player, conf, eventData) {
734 var playerInstance = trackedPlayers[conf.mediaId];
735 var playerTime = player.getCurrentTime();
736 if (Math.abs(playerTime - (playerInstance.seekTracking.prevTime + 0.5)) > 1) {
737 youtubeEvent(player, SnowplowEvent.SEEK, conf, eventData);
738 }
739 playerInstance.seekTracking.prevTime = playerTime;
740 }
741 // Volume Tracking
742 function enableVolumeTracking(player, conf, eventData) {
743 trackedPlayers[conf.mediaId].volumeTracking.enabled = true;
744 trackedPlayers[conf.mediaId].volumeTracking.prevVolume = player.getVolume();
745 setInterval(function () { return volumeEventTracker(player, conf, eventData); }, conf.updateRate);
746 }
747 function volumeEventTracker(player, conf, eventData) {
748 var playerVolumeTracking = trackedPlayers[conf.mediaId].volumeTracking;
749 var playerVolume = player.getVolume();
750 if (playerVolume !== playerVolumeTracking.prevVolume) {
751 youtubeEvent(player, SnowplowEvent.VOLUMECHANGE, conf, eventData);
752 }
753 playerVolumeTracking.prevVolume = playerVolume;
754 }
755
756 exports.YouTubeTrackingPlugin = YouTubeTrackingPlugin;
757 exports.enableYouTubeTracking = enableYouTubeTracking;
758
759 Object.defineProperty(exports, '__esModule', { value: true });
760
761})));
762//# sourceMappingURL=index.umd.js.map