1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import { __spreadArray, __assign } from 'tslib';
|
8 | import { dispatchToTrackersInCollection } from '@snowplow/browser-tracker-core';
|
9 | import { buildSelfDescribingEvent } from '@snowplow/tracker-core';
|
10 |
|
11 | var SnowplowEvent;
|
12 | (function (SnowplowEvent) {
|
13 | SnowplowEvent["PERCENTPROGRESS"] = "percentprogress";
|
14 | SnowplowEvent["SEEK"] = "seek";
|
15 | SnowplowEvent["VOLUMECHANGE"] = "volumechange";
|
16 | })(SnowplowEvent || (SnowplowEvent = {}));
|
17 |
|
18 | var YouTubeIFrameAPIURL = 'https://www.youtube.com/iframe_api';
|
19 |
|
20 |
|
21 |
|
22 | var YTPlayerEvent;
|
23 | (function (YTPlayerEvent) {
|
24 | YTPlayerEvent["ONSTATECHANGE"] = "onStateChange";
|
25 | YTPlayerEvent["ONPLAYBACKQUALITYCHANGE"] = "onPlaybackQualityChange";
|
26 | YTPlayerEvent["ONERROR"] = "onError";
|
27 | YTPlayerEvent["ONAPICHANGE"] = "onApiChange";
|
28 | YTPlayerEvent["ONPLAYBACKRATECHANGE"] = "onPlaybackRateChange";
|
29 | YTPlayerEvent["ONREADY"] = "onReady";
|
30 | })(YTPlayerEvent || (YTPlayerEvent = {}));
|
31 | var YTStateEvent = {
|
32 | '-1': 'unstarted',
|
33 | '0': 'ended',
|
34 | '1': 'play',
|
35 | '2': 'pause',
|
36 | '3': 'buffering',
|
37 | '5': 'cued'
|
38 | };
|
39 | var CaptureEventToYouTubeEvent = {
|
40 | ready: YTPlayerEvent.ONREADY,
|
41 | playbackratechange: YTPlayerEvent.ONPLAYBACKRATECHANGE,
|
42 | playbackqualitychange: YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
|
43 | error: YTPlayerEvent.ONERROR,
|
44 | apichange: YTPlayerEvent.ONAPICHANGE
|
45 | };
|
46 |
|
47 |
|
48 | Object.keys(YTStateEvent).forEach(function (k) { return (CaptureEventToYouTubeEvent[YTStateEvent[k]] = YTPlayerEvent.ONSTATECHANGE); });
|
49 | var YTState;
|
50 | (function (YTState) {
|
51 | YTState["UNSTARTED"] = "unstarted";
|
52 | YTState["ENDED"] = "ended";
|
53 | YTState["PLAYING"] = "play";
|
54 | YTState["PAUSED"] = "pause";
|
55 | YTState["BUFFERING"] = "buffering";
|
56 | YTState["CUED"] = "cued";
|
57 | })(YTState || (YTState = {}));
|
58 | var YTError = {
|
59 | 2: 'INVALID_URL',
|
60 | 5: 'HTML5_ERROR',
|
61 | 100: 'VIDEO_NOT_FOUND',
|
62 | 101: 'MISSING_EMBED_PERMISSION',
|
63 | 150: 'MISSING_EMBED_PERMISSION'
|
64 | };
|
65 |
|
66 | var YTEvent;
|
67 | (function (YTEvent) {
|
68 | YTEvent["STATECHANGE"] = "statechange";
|
69 | YTEvent["PLAYBACKQUALITYCHANGE"] = "playbackqualitychange";
|
70 | YTEvent["ERROR"] = "error";
|
71 | YTEvent["APICHANGE"] = "apichange";
|
72 | YTEvent["PLAYBACKRATECHANGE"] = "playbackratechange";
|
73 | YTEvent["READY"] = "ready";
|
74 | })(YTEvent || (YTEvent = {}));
|
75 | [
|
76 | YTState.BUFFERING,
|
77 | YTState.CUED,
|
78 | YTState.ENDED,
|
79 | YTState.PAUSED,
|
80 | YTState.PLAYING,
|
81 | YTState.UNSTARTED,
|
82 | ];
|
83 |
|
84 | 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]; }));
|
85 | var DefaultEvents = [
|
86 | YTState.PAUSED,
|
87 | YTState.PLAYING,
|
88 | YTState.ENDED,
|
89 | SnowplowEvent.SEEK,
|
90 | SnowplowEvent.VOLUMECHANGE,
|
91 | YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
|
92 | YTPlayerEvent.ONPLAYBACKRATECHANGE,
|
93 | SnowplowEvent.PERCENTPROGRESS,
|
94 | ];
|
95 | var EventGroups = {
|
96 | AllEvents: AllEvents,
|
97 | DefaultEvents: DefaultEvents
|
98 | };
|
99 |
|
100 | function trackingOptionsParser(mediaId, conf) {
|
101 | var defaults = {
|
102 | mediaId: mediaId,
|
103 | captureEvents: DefaultEvents,
|
104 | youtubeEvents: [
|
105 | YTPlayerEvent.ONSTATECHANGE,
|
106 | YTPlayerEvent.ONPLAYBACKQUALITYCHANGE,
|
107 | YTPlayerEvent.ONERROR,
|
108 | YTPlayerEvent.ONPLAYBACKRATECHANGE,
|
109 | ],
|
110 | updateRate: 500,
|
111 | progress: {
|
112 | boundaries: [10, 25, 50, 75],
|
113 | boundaryTimeoutIds: []
|
114 | }
|
115 | };
|
116 | if (!conf)
|
117 | return defaults;
|
118 | if (conf.updateRate)
|
119 | defaults.updateRate = conf.updateRate;
|
120 | if (conf.captureEvents) {
|
121 | var parsedEvents = [];
|
122 | var _loop_1 = function (ev) {
|
123 |
|
124 | if (EventGroups.hasOwnProperty(ev)) {
|
125 | parsedEvents = parsedEvents.concat(EventGroups[ev]);
|
126 | }
|
127 | else if (!Object.keys(AllEvents).filter(function (k) { return k === ev; })) {
|
128 | console.warn("'" + ev + "' is not a valid event.");
|
129 | }
|
130 | else {
|
131 | parsedEvents.push(ev);
|
132 | }
|
133 | };
|
134 | for (var _i = 0, _a = conf.captureEvents; _i < _a.length; _i++) {
|
135 | var ev = _a[_i];
|
136 | _loop_1(ev);
|
137 | }
|
138 | conf.captureEvents = parsedEvents;
|
139 | for (var _b = 0, _c = conf.captureEvents; _b < _c.length; _b++) {
|
140 | var ev = _c[_b];
|
141 | var youtubeEvent = CaptureEventToYouTubeEvent[ev];
|
142 | if (CaptureEventToYouTubeEvent.hasOwnProperty(ev) && defaults.youtubeEvents.indexOf(youtubeEvent) === -1) {
|
143 | defaults.youtubeEvents.push(youtubeEvent);
|
144 | }
|
145 | }
|
146 | if (conf.captureEvents.indexOf(SnowplowEvent.PERCENTPROGRESS) !== -1) {
|
147 | defaults.progress = {
|
148 | boundaries: (conf === null || conf === void 0 ? void 0 : conf.boundaries) || defaults.progress.boundaries,
|
149 | boundaryTimeoutIds: []
|
150 | };
|
151 | }
|
152 | }
|
153 | return __assign(__assign({}, defaults), conf);
|
154 | }
|
155 |
|
156 |
|
157 | function addUrlParam(url, key, value) {
|
158 | var urlParams = parseUrlParams(url);
|
159 | urlParams[key] = value;
|
160 | return url + "?" + urlParamsToString(urlParams);
|
161 | }
|
162 | function parseUrlParams(url) {
|
163 | var params = {};
|
164 | var urlParams = url.split('?')[1];
|
165 | if (!urlParams)
|
166 | return params;
|
167 | urlParams.split('&').forEach(function (p) {
|
168 | var param = p.split('=');
|
169 | params[param[0]] = param[1];
|
170 | });
|
171 | return params;
|
172 | }
|
173 | function urlParamsToString(urlParams) {
|
174 |
|
175 | var params = '';
|
176 | Object.keys(urlParams).forEach(function (p) {
|
177 | params += p + "=" + urlParams[p] + "&";
|
178 | });
|
179 | return params.slice(0, -1);
|
180 | }
|
181 |
|
182 | function buildYouTubeEvent(player, eventName, conf, eventData) {
|
183 | var data = { type: eventName };
|
184 | if (conf.hasOwnProperty('label'))
|
185 | data.label = conf.label;
|
186 | var context = [
|
187 | getYouTubeEntities(player, conf.urlParameters, eventData),
|
188 | getMediaPlayerEntities(eventName, player, conf.urlParameters, eventData),
|
189 | ];
|
190 | return {
|
191 | schema: 'iglu:com.snowplowanalytics.snowplow/media_player_event/jsonschema/1-0-0',
|
192 | data: data,
|
193 | context: context
|
194 | };
|
195 | }
|
196 | function getYouTubeEntities(player, urlParameters, eventData) {
|
197 | var spherical = player.getSphericalProperties();
|
198 | var playerStates = {
|
199 | buffering: false,
|
200 | cued: false,
|
201 | unstarted: false
|
202 | };
|
203 | var state = player.getPlayerState();
|
204 | if (playerStates.hasOwnProperty(YTStateEvent[state])) {
|
205 | playerStates[YTStateEvent[state]] = true;
|
206 | }
|
207 | var data = {
|
208 | autoPlay: urlParameters.autoplay === '1',
|
209 | avaliablePlaybackRates: player.getAvailablePlaybackRates(),
|
210 | buffering: playerStates.buffering,
|
211 | controls: urlParameters.controls !== '0',
|
212 | cued: playerStates.cued,
|
213 | loaded: parseInt(String(player.getVideoLoadedFraction() * 100)),
|
214 | playbackQuality: player.getPlaybackQuality(),
|
215 | playerId: player.getIframe().id,
|
216 | unstarted: playerStates.unstarted,
|
217 | url: player.getVideoUrl()
|
218 | };
|
219 | if (spherical)
|
220 | data = __assign(__assign({}, data), spherical);
|
221 | if (eventData === null || eventData === void 0 ? void 0 : eventData.error)
|
222 | data.error = eventData.error;
|
223 | var playlistIndex = player.getPlaylistIndex();
|
224 | if (playlistIndex !== -1)
|
225 | data.playlistIndex = playlistIndex;
|
226 | var playlist = player.getPlaylist();
|
227 | if (playlist) {
|
228 | data.playlist = playlist.map(function (item) { return parseInt(item); });
|
229 | }
|
230 | var qualityLevels = player.getAvailableQualityLevels();
|
231 | if (qualityLevels)
|
232 | data.avaliableQualityLevels = qualityLevels;
|
233 | return {
|
234 | schema: 'iglu:com.youtube/youtube/jsonschema/1-0-0',
|
235 | data: data
|
236 | };
|
237 | }
|
238 | function getMediaPlayerEntities(e, player, urlParameters, eventData) {
|
239 | var playerStates = {
|
240 | ended: false,
|
241 | paused: false
|
242 | };
|
243 | var state = player.getPlayerState();
|
244 | if (playerStates.hasOwnProperty(YTStateEvent[state])) {
|
245 | playerStates[YTStateEvent[state]] = true;
|
246 | }
|
247 | var data = {
|
248 | currentTime: player.getCurrentTime(),
|
249 | duration: player.getDuration(),
|
250 | ended: playerStates.ended,
|
251 | loop: urlParameters.loop === '1',
|
252 | muted: player.isMuted(),
|
253 | paused: playerStates.paused,
|
254 | playbackRate: player.getPlaybackRate(),
|
255 | volume: player.getVolume()
|
256 | };
|
257 | if (e === SnowplowEvent.PERCENTPROGRESS) {
|
258 | data.percentProgress = eventData.percentThrough;
|
259 | }
|
260 | return {
|
261 | schema: 'iglu:com.snowplowanalytics.snowplow/media_player/jsonschema/1-0-0',
|
262 | data: data
|
263 | };
|
264 | }
|
265 |
|
266 | var _trackers = {};
|
267 | var trackedPlayers = {};
|
268 | var trackingQueue = [];
|
269 | var LOG;
|
270 | function YouTubeTrackingPlugin() {
|
271 | return {
|
272 | activateBrowserPlugin: function (tracker) {
|
273 | _trackers[tracker.id] = tracker;
|
274 | },
|
275 | logger: function (logger) {
|
276 | LOG = logger;
|
277 | }
|
278 | };
|
279 | }
|
280 | function trackEvent(event, trackers) {
|
281 | if (trackers === void 0) { trackers = Object.keys(_trackers); }
|
282 | dispatchToTrackersInCollection(trackers, _trackers, function (t) {
|
283 | t.core.track(buildSelfDescribingEvent({ event: event }), event.context, event.timestamp);
|
284 | });
|
285 | }
|
286 | function enableYouTubeTracking(args) {
|
287 | var conf = trackingOptionsParser(args.id, args.options);
|
288 | var el = document.getElementById(args.id);
|
289 | if (!el) {
|
290 | LOG.error('Cannot find YouTube iframe');
|
291 | return;
|
292 | }
|
293 |
|
294 | if (el.src.indexOf('enablejsapi') === -1) {
|
295 | el.src = addUrlParam(el.src, 'enablejsapi', '1');
|
296 | }
|
297 | conf.urlParameters = parseUrlParams(el.src);
|
298 |
|
299 | if (typeof YT !== 'undefined' && typeof YT.Player !== 'undefined') {
|
300 | addListeners(conf);
|
301 | }
|
302 | else {
|
303 |
|
304 |
|
305 | trackingQueue.push(conf);
|
306 | handleYouTubeIframeAPI();
|
307 | }
|
308 | }
|
309 | var iframeAPIRetryWait = 100;
|
310 | function handleYouTubeIframeAPI() {
|
311 |
|
312 | var scriptTags = Array.prototype.slice.call(document.getElementsByTagName('script'));
|
313 | if (!scriptTags.some(function (s) { return s.src === YouTubeIFrameAPIURL; })) {
|
314 |
|
315 |
|
316 | var tag = document.createElement('script');
|
317 | tag.src = YouTubeIFrameAPIURL;
|
318 | var firstScriptTag = document.getElementsByTagName('script')[0];
|
319 | firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
320 | }
|
321 |
|
322 |
|
323 |
|
324 | if (typeof YT === 'undefined' || typeof YT.Player === 'undefined') {
|
325 | if (iframeAPIRetryWait <= 6400) {
|
326 | setTimeout(handleYouTubeIframeAPI, iframeAPIRetryWait);
|
327 | iframeAPIRetryWait *= 2;
|
328 | }
|
329 | else {
|
330 | LOG.error('YouTube iframe API failed to load.');
|
331 | }
|
332 | }
|
333 | else {
|
334 |
|
335 | while (trackingQueue.length) {
|
336 | addListeners(trackingQueue.pop());
|
337 | }
|
338 | }
|
339 | }
|
340 | function addListeners(conf) {
|
341 | var _a;
|
342 | var builtInEvents = (_a = {},
|
343 | _a[YTPlayerEvent.ONREADY] = function () { return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.READY, conf); },
|
344 | _a[YTPlayerEvent.ONSTATECHANGE] = function (e) {
|
345 | if (conf.captureEvents.indexOf(YTStateEvent[e.data.toString()]) !== -1) {
|
346 | youtubeEvent(trackedPlayers[conf.mediaId].player, YTStateEvent[e.data], conf);
|
347 | }
|
348 | },
|
349 | _a[YTPlayerEvent.ONPLAYBACKQUALITYCHANGE] = function () {
|
350 | return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.PLAYBACKQUALITYCHANGE, conf);
|
351 | },
|
352 | _a[YTPlayerEvent.ONAPICHANGE] = function () { return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.APICHANGE, conf); },
|
353 | _a[YTPlayerEvent.ONERROR] = function (e) {
|
354 | return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.ERROR, conf, { error: YTError[e.data] });
|
355 | },
|
356 | _a[YTPlayerEvent.ONPLAYBACKRATECHANGE] = function () {
|
357 | return youtubeEvent(trackedPlayers[conf.mediaId].player, YTEvent.PLAYBACKRATECHANGE, conf);
|
358 | },
|
359 | _a);
|
360 | var playerEvents = {};
|
361 | conf.youtubeEvents.forEach(function (e) {
|
362 | playerEvents[e] = builtInEvents[e];
|
363 | });
|
364 | trackedPlayers[conf.mediaId] = {
|
365 | player: new YT.Player(conf.mediaId, { events: __assign({}, playerEvents) }),
|
366 | conf: conf,
|
367 | seekTracking: {
|
368 | prevTime: 0,
|
369 | enabled: false
|
370 | },
|
371 | volumeTracking: {
|
372 | prevVolume: 0,
|
373 | enabled: false
|
374 | }
|
375 | };
|
376 | }
|
377 | function youtubeEvent(player, eventName, conf, eventData) {
|
378 | var playerInstance = trackedPlayers[conf.mediaId];
|
379 | if (!playerInstance.seekTracking.enabled && conf.captureEvents.indexOf('seek') !== 1) {
|
380 | enableSeekTracking(player, conf, eventData);
|
381 | }
|
382 | if (!playerInstance.volumeTracking.enabled && conf.captureEvents.indexOf('volume') !== 1) {
|
383 | enableVolumeTracking(player, conf, eventData);
|
384 | }
|
385 | if (conf.hasOwnProperty('boundaries')) {
|
386 | progressHandler(player, eventName, conf);
|
387 | }
|
388 | var event = buildYouTubeEvent(player, eventName, conf, eventData);
|
389 | trackEvent(event);
|
390 | }
|
391 |
|
392 | function progressHandler(player, eventName, conf) {
|
393 | var timeoutIds = conf.progress.boundaryTimeoutIds;
|
394 | if (eventName === YTState.PAUSED) {
|
395 | timeoutIds.forEach(function (id) { return clearTimeout(id); });
|
396 | timeoutIds.length = 0;
|
397 | }
|
398 | if (eventName === YTState.PLAYING) {
|
399 | setPercentageBoundTimeouts(player, conf);
|
400 | }
|
401 | }
|
402 | function setPercentageBoundTimeouts(player, conf) {
|
403 | var _a;
|
404 | var currentTime = player.getCurrentTime();
|
405 | (_a = conf.progress) === null || _a === void 0 ? void 0 : _a.boundaries.forEach(function (p) {
|
406 | var _a;
|
407 | var percentTime = player.getDuration() * 1000 * (p / 100);
|
408 | if (currentTime !== 0) {
|
409 | percentTime -= currentTime * 1000;
|
410 | }
|
411 | if (p < percentTime) {
|
412 | (_a = conf.progress) === null || _a === void 0 ? void 0 : _a.boundaryTimeoutIds.push(setTimeout(function () { return waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p); }, percentTime));
|
413 | }
|
414 | });
|
415 | }
|
416 |
|
417 |
|
418 | function waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p) {
|
419 | if (player.getCurrentTime() * 1000 < percentTime) {
|
420 | setTimeout(function () { return waitAnyRemainingTimeAfterTimeout(player, conf, percentTime, p); }, 10);
|
421 | }
|
422 | else {
|
423 | youtubeEvent(player, SnowplowEvent.PERCENTPROGRESS, conf, {
|
424 | percentThrough: p
|
425 | });
|
426 | }
|
427 | }
|
428 |
|
429 | function enableSeekTracking(player, conf, eventData) {
|
430 | trackedPlayers[conf.mediaId].seekTracking.enabled = true;
|
431 | setInterval(function () { return seekEventTracker(player, conf, eventData); }, conf.updateRate);
|
432 | }
|
433 | function seekEventTracker(player, conf, eventData) {
|
434 | var playerInstance = trackedPlayers[conf.mediaId];
|
435 | var playerTime = player.getCurrentTime();
|
436 | if (Math.abs(playerTime - (playerInstance.seekTracking.prevTime + 0.5)) > 1) {
|
437 | youtubeEvent(player, SnowplowEvent.SEEK, conf, eventData);
|
438 | }
|
439 | playerInstance.seekTracking.prevTime = playerTime;
|
440 | }
|
441 |
|
442 | function enableVolumeTracking(player, conf, eventData) {
|
443 | trackedPlayers[conf.mediaId].volumeTracking.enabled = true;
|
444 | trackedPlayers[conf.mediaId].volumeTracking.prevVolume = player.getVolume();
|
445 | setInterval(function () { return volumeEventTracker(player, conf, eventData); }, conf.updateRate);
|
446 | }
|
447 | function volumeEventTracker(player, conf, eventData) {
|
448 | var playerVolumeTracking = trackedPlayers[conf.mediaId].volumeTracking;
|
449 | var playerVolume = player.getVolume();
|
450 | if (playerVolume !== playerVolumeTracking.prevVolume) {
|
451 | youtubeEvent(player, SnowplowEvent.VOLUMECHANGE, conf, eventData);
|
452 | }
|
453 | playerVolumeTracking.prevVolume = playerVolume;
|
454 | }
|
455 |
|
456 | export { YouTubeTrackingPlugin, enableYouTubeTracking };
|
457 |
|