@segment/analytics.js-video-plugins
Advanced tools
Comparing version
@@ -1,711 +0,1 @@ | ||
(function webpackUniversalModuleDefinition(root, factory) { | ||
if(typeof exports === 'object' && typeof module === 'object') | ||
module.exports = factory(); | ||
else if(typeof define === 'function' && define.amd) | ||
define([], factory); | ||
else if(typeof exports === 'object') | ||
exports["analyticsVideoPlugins"] = factory(); | ||
else | ||
root["analyticsVideoPlugins"] = factory(); | ||
})(window, function() { | ||
return /******/ (function(modules) { // webpackBootstrap | ||
/******/ // The module cache | ||
/******/ var installedModules = {}; | ||
/******/ | ||
/******/ // The require function | ||
/******/ function __webpack_require__(moduleId) { | ||
/******/ | ||
/******/ // Check if module is in cache | ||
/******/ if(installedModules[moduleId]) { | ||
/******/ return installedModules[moduleId].exports; | ||
/******/ } | ||
/******/ // Create a new module (and put it into the cache) | ||
/******/ var module = installedModules[moduleId] = { | ||
/******/ i: moduleId, | ||
/******/ l: false, | ||
/******/ exports: {} | ||
/******/ }; | ||
/******/ | ||
/******/ // Execute the module function | ||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | ||
/******/ | ||
/******/ // Flag the module as loaded | ||
/******/ module.l = true; | ||
/******/ | ||
/******/ // Return the exports of the module | ||
/******/ return module.exports; | ||
/******/ } | ||
/******/ | ||
/******/ | ||
/******/ // expose the modules object (__webpack_modules__) | ||
/******/ __webpack_require__.m = modules; | ||
/******/ | ||
/******/ // expose the module cache | ||
/******/ __webpack_require__.c = installedModules; | ||
/******/ | ||
/******/ // define getter function for harmony exports | ||
/******/ __webpack_require__.d = function(exports, name, getter) { | ||
/******/ if(!__webpack_require__.o(exports, name)) { | ||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); | ||
/******/ } | ||
/******/ }; | ||
/******/ | ||
/******/ // define __esModule on exports | ||
/******/ __webpack_require__.r = function(exports) { | ||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { | ||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
/******/ } | ||
/******/ Object.defineProperty(exports, '__esModule', { value: true }); | ||
/******/ }; | ||
/******/ | ||
/******/ // create a fake namespace object | ||
/******/ // mode & 1: value is a module id, require it | ||
/******/ // mode & 2: merge all properties of value into the ns | ||
/******/ // mode & 4: return value when already ns object | ||
/******/ // mode & 8|1: behave like require | ||
/******/ __webpack_require__.t = function(value, mode) { | ||
/******/ if(mode & 1) value = __webpack_require__(value); | ||
/******/ if(mode & 8) return value; | ||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; | ||
/******/ var ns = Object.create(null); | ||
/******/ __webpack_require__.r(ns); | ||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); | ||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); | ||
/******/ return ns; | ||
/******/ }; | ||
/******/ | ||
/******/ // getDefaultExport function for compatibility with non-harmony modules | ||
/******/ __webpack_require__.n = function(module) { | ||
/******/ var getter = module && module.__esModule ? | ||
/******/ function getDefault() { return module['default']; } : | ||
/******/ function getModuleExports() { return module; }; | ||
/******/ __webpack_require__.d(getter, 'a', getter); | ||
/******/ return getter; | ||
/******/ }; | ||
/******/ | ||
/******/ // Object.prototype.hasOwnProperty.call | ||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; | ||
/******/ | ||
/******/ // __webpack_public_path__ | ||
/******/ __webpack_require__.p = ""; | ||
/******/ | ||
/******/ | ||
/******/ // Load entry module and return exports | ||
/******/ return __webpack_require__(__webpack_require__.s = 2); | ||
/******/ }) | ||
/************************************************************************/ | ||
/******/ ([ | ||
/* 0 */ | ||
/***/ (function(module, __webpack_exports__, __webpack_require__) { | ||
"use strict"; | ||
__webpack_require__.r(__webpack_exports__); | ||
var index = typeof fetch=='function' ? fetch.bind() : function(url, options) { | ||
options = options || {}; | ||
return new Promise( function (resolve, reject) { | ||
var request = new XMLHttpRequest(); | ||
request.open(options.method || 'get', url, true); | ||
for (var i in options.headers) { | ||
request.setRequestHeader(i, options.headers[i]); | ||
} | ||
request.withCredentials = options.credentials=='include'; | ||
request.onload = function () { | ||
resolve(response()); | ||
}; | ||
request.onerror = reject; | ||
request.send(options.body); | ||
function response() { | ||
var keys = [], | ||
all = [], | ||
headers = {}, | ||
header; | ||
request.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, function (m, key, value) { | ||
keys.push(key = key.toLowerCase()); | ||
all.push([key, value]); | ||
header = headers[key]; | ||
headers[key] = header ? (header + "," + value) : value; | ||
}); | ||
return { | ||
ok: (request.status/100|0) == 2, // 200-299 | ||
status: request.status, | ||
statusText: request.statusText, | ||
url: request.responseURL, | ||
clone: response, | ||
text: function () { return Promise.resolve(request.responseText); }, | ||
json: function () { return Promise.resolve(request.responseText).then(JSON.parse); }, | ||
blob: function () { return Promise.resolve(new Blob([request.response])); }, | ||
headers: { | ||
keys: function () { return keys; }, | ||
entries: function () { return all; }, | ||
get: function (n) { return headers[n.toLowerCase()]; }, | ||
has: function (n) { return n.toLowerCase() in headers; } | ||
} | ||
}; | ||
} | ||
}); | ||
}; | ||
/* harmony default export */ __webpack_exports__["default"] = (index); | ||
//# sourceMappingURL=unfetch.es.js.map | ||
/***/ }), | ||
/* 1 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var VideoPlugin = function () { | ||
function VideoPlugin(name, version) { | ||
_classCallCheck(this, VideoPlugin); | ||
this.pluginName = name; | ||
} | ||
_createClass(VideoPlugin, [{ | ||
key: "track", | ||
value: function track(event, properties) { | ||
window.analytics.track(event, properties, { | ||
integration: { | ||
name: this.pluginName | ||
} | ||
}); | ||
} | ||
}]); | ||
return VideoPlugin; | ||
}(); | ||
exports.default = VideoPlugin; | ||
/***/ }), | ||
/* 2 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.YouTubeAnalytics = exports.VimeoAnalytics = undefined; | ||
var _vimeo = __webpack_require__(3); | ||
var _vimeo2 = _interopRequireDefault(_vimeo); | ||
var _youtube = __webpack_require__(4); | ||
var _youtube2 = _interopRequireDefault(_youtube); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
exports.VimeoAnalytics = _vimeo2.default; | ||
exports.YouTubeAnalytics = _youtube2.default; | ||
/***/ }), | ||
/* 3 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _unfetch = __webpack_require__(0); | ||
var _unfetch2 = _interopRequireDefault(_unfetch); | ||
var _plugin = __webpack_require__(1); | ||
var _plugin2 = _interopRequireDefault(_plugin); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
var VimeoAnalytics = function (_Plugin) { | ||
_inherits(VimeoAnalytics, _Plugin); | ||
function VimeoAnalytics(player, authToken) { | ||
_classCallCheck(this, VimeoAnalytics); | ||
var _this = _possibleConstructorReturn(this, (VimeoAnalytics.__proto__ || Object.getPrototypeOf(VimeoAnalytics)).call(this, "VimeoAnalytics")); | ||
_this.authToken = authToken; | ||
_this.player = player; | ||
_this.metadata = { | ||
content: {}, | ||
playback: { videoPlayer: "Vimeo" } | ||
}; | ||
_this.mostRecentHeartbeat = 0; | ||
_this.isPaused = false; | ||
return _this; | ||
} | ||
_createClass(VimeoAnalytics, [{ | ||
key: "initialize", | ||
value: function initialize() { | ||
var _this2 = this; | ||
var events = { | ||
loaded: this.retrieveMetadata, | ||
play: this.trackPlay, | ||
pause: this.trackPause, | ||
ended: this.trackEnded, | ||
timeupdate: this.trackHeartbeat | ||
}; | ||
for (var event in events) { | ||
this.registerHandler(event, events[event]); | ||
} | ||
this.player.getVideoId().then(function (id) { | ||
_this2.retrieveMetadata({ id: id }); | ||
}).catch(console.error); | ||
} | ||
// This is a helper function used to facilitate easier testing. | ||
}, { | ||
key: "registerHandler", | ||
value: function registerHandler(event, handler) { | ||
var _this3 = this; | ||
this.player.on(event, function (data) { | ||
_this3.updateMetadata(data); | ||
handler.call(_this3, data); | ||
}); | ||
} | ||
}, { | ||
key: "trackPlay", | ||
value: function trackPlay() { | ||
if (this.isPaused) { | ||
this.track("Video Playback Resumed", this.metadata.playback); | ||
this.isPaused = false; | ||
} else { | ||
this.track("Video Playback Started", this.metadata.playback); | ||
this.track("Video Content Started", this.metadata.content); | ||
} | ||
} | ||
}, { | ||
key: "trackEnded", | ||
value: function trackEnded() { | ||
this.track("Video Playback Completed", this.metadata.playback); | ||
this.track("Video Content Completed", this.metadata.content); | ||
} | ||
// Vimeo provides time updates every 250ms while Segment documents sending heartbeats every 10s. | ||
// Therefore, we need to do some math to ensure we are not sending 4 heartbeat events when we reach | ||
// 10 second intervals. | ||
}, { | ||
key: "trackHeartbeat", | ||
value: function trackHeartbeat() { | ||
var mostRecentHeartbeat = this.mostRecentHeartbeat; | ||
var currentPosition = this.metadata.playback.position; | ||
if (currentPosition !== mostRecentHeartbeat && currentPosition - mostRecentHeartbeat >= 10) { | ||
this.track("Video Content Playing", this.metadata.content); | ||
this.mostRecentHeartbeat = Math.floor(currentPosition); | ||
} | ||
} | ||
}, { | ||
key: "trackPause", | ||
value: function trackPause() { | ||
this.isPaused = true; | ||
this.track("Video Playback Paused", this.metadata.playback); | ||
} | ||
// retrieve static video metadata from vimeo API | ||
}, { | ||
key: "retrieveMetadata", | ||
value: function retrieveMetadata(data) { | ||
var _this4 = this; | ||
return new Promise(function (resolve, reject) { | ||
var videoId = data.id; | ||
(0, _unfetch2.default)("https://api.vimeo.com/videos/" + videoId, { | ||
headers: { | ||
Authorization: "Bearer " + _this4.authToken | ||
} | ||
}).then(function (res) { | ||
if (res.ok) { | ||
return res.json(); | ||
} | ||
return reject(res); | ||
}).then(function (json) { | ||
_this4.metadata.content.title = json.name; | ||
_this4.metadata.content.description = json.description; | ||
_this4.metadata.content.publisher = json.user.name; | ||
_this4.metadata.playback.position = 0; | ||
_this4.metadata.playback.totalLength = json.duration; | ||
}).catch(function (err) { | ||
console.error("Request to Vimeo API Failed with: ", err); | ||
return reject(err); | ||
}); | ||
}); | ||
} | ||
// update dynamic video information | ||
}, { | ||
key: "updateMetadata", | ||
value: function updateMetadata(data) { | ||
var _this5 = this; | ||
return new Promise(function (resolve, reject) { | ||
_this5.player.getVolume().then(function (volume) { | ||
if (volume) _this5.metadata.playback.sound = volume * 100; | ||
_this5.metadata.playback.position = data.seconds; | ||
resolve(); | ||
}).catch(reject); | ||
}); | ||
} | ||
}]); | ||
return VimeoAnalytics; | ||
}(_plugin2.default); | ||
exports.default = VimeoAnalytics; | ||
/***/ }), | ||
/* 4 */ | ||
/***/ (function(module, exports, __webpack_require__) { | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _unfetch = __webpack_require__(0); | ||
var _unfetch2 = _interopRequireDefault(_unfetch); | ||
var _plugin = __webpack_require__(1); | ||
var _plugin2 = _interopRequireDefault(_plugin); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
var SEEK_THRESHOLD = 2000; | ||
var YouTubeAnalytics = function (_VideoPlugin) { | ||
_inherits(YouTubeAnalytics, _VideoPlugin); | ||
/** | ||
* Creates a YouTube video plugin to track events directly from the player. | ||
* | ||
* @param {YT.Player} player YouTube IFrame Player (docs: https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player). | ||
* @param {String} apiKey Youtube Data API key (docs: https://developers.google.com/youtube/registering_an_application). | ||
*/ | ||
function YouTubeAnalytics(player, apiKey) { | ||
_classCallCheck(this, YouTubeAnalytics); | ||
var _this = _possibleConstructorReturn(this, (YouTubeAnalytics.__proto__ || Object.getPrototypeOf(YouTubeAnalytics)).call(this, "YoutubeAnalytics")); | ||
_this.player = player; | ||
_this.apiKey = apiKey; | ||
_this.playerLoaded = false; | ||
_this.playbackStarted = false; | ||
_this.contentStarted = false; | ||
_this.isPaused = false; | ||
_this.isBuffering = false; | ||
_this.isSeeking = false; | ||
_this.lastRecordedTime = { | ||
// updated every event | ||
timeReported: Date.now(), | ||
timeElapsed: 0.0 | ||
}; | ||
_this.metadata = [{ playback: { video_player: "youtube" }, content: {} }]; | ||
_this.playlistIndex = 0; | ||
return _this; | ||
} | ||
_createClass(YouTubeAnalytics, [{ | ||
key: "initialize", | ||
value: function initialize() { | ||
// Youtube API requires listeners to exist as top-level props on window object | ||
window.segmentYoutubeOnStateChange = this.onPlayerStateChange.bind(this); | ||
window.segmentYoutubeOnReady = this.onPlayerReady.bind(this); | ||
this.player.addEventListener("onReady", "segmentYoutubeOnReady"); | ||
this.player.addEventListener("onStateChange", "segmentYoutubeOnStateChange"); | ||
} | ||
}, { | ||
key: "onPlayerReady", | ||
value: function onPlayerReady(event) { | ||
// this fires when the player html element loads | ||
this.retrieveMetadata(); | ||
} | ||
// yt reports events via state changes in the player rather than explicitly EVENTS like 'play', 'pause', etc | ||
}, { | ||
key: "onPlayerStateChange", | ||
value: function onPlayerStateChange(event) { | ||
var currentTime = this.player.getCurrentTime(); | ||
if (this.metadata[this.playlistIndex]) { | ||
this.metadata[this.playlistIndex].playback.position = this.metadata[this.playlistIndex].content.position = currentTime; | ||
this.metadata[this.playlistIndex].playback.quality = this.player.getPlaybackQuality(); | ||
this.metadata[this.playlistIndex].playback.sound = this.player.isMuted() ? 0 : this.player.getVolume(); | ||
} | ||
switch (event.data) { | ||
case -1: | ||
// this fires when a video or playlist has loaded | ||
if (this.playerLoaded) break; | ||
this.retrieveMetadata(); | ||
this.playerLoaded = true; | ||
break; | ||
case YT.PlayerState.BUFFERING: | ||
this.handleBuffer(); | ||
break; | ||
case YT.PlayerState.PLAYING: | ||
this.handlePlay(); | ||
break; | ||
case YT.PlayerState.PAUSED: | ||
this.handlePause(); | ||
break; | ||
case YT.PlayerState.ENDED: | ||
this.handleEnd(); | ||
break; | ||
} | ||
this.lastRecordedTime = { | ||
timeReported: Date.now(), | ||
timeElapsed: this.player.getCurrentTime() * 1000.0 | ||
}; | ||
} | ||
/** | ||
* Retrieves the video metadata from Youtube Data API using user's API Key | ||
* docs: https://developers.google.com/youtube/v3/docs/videos/list | ||
* | ||
* @returns {Promise} A promise that resolves in a metadata object from the video | ||
* contained in the player. | ||
*/ | ||
}, { | ||
key: "retrieveMetadata", | ||
value: function retrieveMetadata() { | ||
var _this2 = this; | ||
return new Promise(function (resolve, reject) { | ||
var videoData = _this2.player.getVideoData(); | ||
var playlist = _this2.player.getPlaylist() || [videoData.video_id]; | ||
var videoIds = playlist.join(); | ||
(0, _unfetch2.default)("https://www.googleapis.com/youtube/v3/videos?id=" + videoIds + "&part=snippet,contentDetails&key=" + _this2.apiKey).then(function (res) { | ||
if (!res.ok) { | ||
var err = new Error("Segment request to Youtube API failed (likely due to a bad API Key. Events will still be sent but will not contain video metadata)"); | ||
err.response = res; | ||
throw err; | ||
} | ||
return res.json(); | ||
}).then(function (json) { | ||
_this2.metadata = []; | ||
var total_length = 0; | ||
for (var i = 0; i < playlist.length; i++) { | ||
var videoInfo = json.items[i]; | ||
_this2.metadata.push({ | ||
content: { | ||
title: videoInfo.snippet.title, | ||
description: videoInfo.snippet.description, | ||
keywords: videoInfo.snippet.tags, | ||
channel: videoInfo.snippet.channelTitle, | ||
airdate: videoInfo.snippet.publishedAt | ||
} | ||
}); | ||
total_length += YTDurationToSeconds(videoInfo.contentDetails.duration); | ||
} | ||
for (var i = 0; i < playlist.length; i++) { | ||
_this2.metadata[i].playback = { | ||
total_length: total_length, | ||
video_player: "youtube" | ||
}; | ||
} | ||
resolve(); | ||
}).catch(function (err) { | ||
_this2.metadata = playlist.map(function (item) { | ||
return { playback: { video_player: "youtube" }, content: {} }; | ||
}); | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
}, { | ||
key: "handleBuffer", | ||
value: function handleBuffer() { | ||
var seekDetected = this.determineSeek(); | ||
if (!this.playbackStarted) { | ||
this.playbackStarted = true; | ||
this.track("Video Playback Started", this.metadata[this.playlistIndex].playback); | ||
} | ||
// user used keyboard to seek or seeked while video was paused | ||
if (seekDetected && !this.isSeeking) { | ||
this.isSeeking = true; | ||
this.track("Video Playback Seek Started", this.metadata[this.playlistIndex].playback); | ||
} | ||
// state changing to BUFFERING denotes seeking has completed | ||
if (this.isSeeking) { | ||
this.track("Video Playback Seek Completed", this.metadata[this.playlistIndex].playback); | ||
this.isSeeking = false; | ||
} | ||
// user clicked next video button (not possible in single video playback) | ||
var playlist = this.player.getPlaylist(); | ||
if (playlist && this.player.getCurrentTime() === 0 && this.player.getPlaylistIndex() !== this.playlistIndex) { | ||
this.contentStarted = false; | ||
if (this.playlistIndex === playlist.length - 1 && this.player.getPlaylistIndex() === 0) { | ||
// user skipped to end of last video in playlist | ||
this.track("Video Playback Completed", this.metadata[this.player.getPlaylistIndex()].playback); | ||
this.track("Video Playback Started", this.metadata[this.player.getPlaylistIndex()].playback); // playlist automatically starts from beginning | ||
} | ||
} | ||
this.track("Video Playback Buffer Started", this.metadata[this.playlistIndex].playback); | ||
this.isBuffering = true; | ||
} | ||
}, { | ||
key: "handlePlay", | ||
value: function handlePlay() { | ||
if (!this.contentStarted) { | ||
this.playlistIndex = this.player.getPlaylistIndex(); // will return -1 if the player has a singular video instead of a playlist | ||
if (this.playlistIndex === -1) this.playlistIndex = 0; | ||
this.track("Video Content Started", this.metadata[this.playlistIndex].content); | ||
this.contentStarted = true; | ||
} | ||
if (this.isBuffering) { | ||
this.track("Video Playback Buffer Completed", this.metadata[this.playlistIndex].playback); | ||
this.isBuffering = false; | ||
} | ||
if (this.isPaused) { | ||
this.track("Video Playback Resumed", this.metadata[this.playlistIndex].playback); | ||
this.isPaused = false; | ||
} | ||
} | ||
}, { | ||
key: "handlePause", | ||
value: function handlePause() { | ||
var seekDetected = this.determineSeek(); | ||
// user seeked while video was paused, it buffered, and then finished buffering | ||
if (this.isBuffering) { | ||
this.track("Video Playback Buffer Completed", this.metadata[this.playlistIndex].playback); | ||
this.isBuffering = false; | ||
} | ||
if (!this.isPaused) { | ||
// user seeked while video was playing | ||
if (seekDetected) { | ||
this.track("Video Playback Seek Started", this.metadata[this.playlistIndex].playback); | ||
this.isSeeking = true; | ||
} | ||
// user clicked pause while video was playing | ||
else { | ||
this.track("Video Playback Paused", this.metadata[this.playlistIndex].playback); | ||
this.isPaused = true; | ||
} | ||
} | ||
} | ||
}, { | ||
key: "handleEnd", | ||
value: function handleEnd() { | ||
this.track("Video Content Completed", this.metadata[this.playlistIndex].content); | ||
this.contentStarted = false; | ||
var playlistIndex = this.player.getPlaylistIndex(); | ||
var playlist = this.player.getPlaylist(); | ||
if (playlist && playlistIndex === playlist.length - 1 || playlistIndex === -1) { | ||
this.track("Video Playback Completed", this.metadata[this.playlistIndex].playback); | ||
this.playbackStarted = false; | ||
} | ||
} | ||
// yt doesn't natively track seeking so we have to manually calculate whether a seek has occurred based on expected vs actual event timestamps | ||
}, { | ||
key: "determineSeek", | ||
value: function determineSeek() { | ||
var expectedTimeElapsed = this.isPaused || this.isBuffering ? 0 : Date.now() - this.lastRecordedTime.timeReported; | ||
var actualTimeElapsed = this.player.getCurrentTime() * 1000.0 - this.lastRecordedTime.timeElapsed; | ||
return Math.abs(expectedTimeElapsed - actualTimeElapsed) > SEEK_THRESHOLD; // if the diff btwn the 2 is > the threshold we can reasonably assume a seek has occurred | ||
} | ||
}]); | ||
return YouTubeAnalytics; | ||
}(_plugin2.default); | ||
// convert from ISO 8601 to seconds | ||
exports.default = YouTubeAnalytics; | ||
function YTDurationToSeconds(duration) { | ||
var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/); | ||
match = match.slice(1).map(function (x) { | ||
if (x != null) { | ||
return x.replace(/\D/, ""); | ||
} | ||
}); | ||
var hours = parseInt(match[0]) || 0; | ||
var minutes = parseInt(match[1]) || 0; | ||
var seconds = parseInt(match[2]) || 0; | ||
return hours * 3600 + minutes * 60 + seconds; | ||
} | ||
/***/ }) | ||
/******/ ]); | ||
}); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.analyticsVideoPlugins=t():e.analyticsVideoPlugins=t()}(window,(function(){return function(e){var t={};function a(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,a),i.l=!0,i.exports}return a.m=e,a.c=t,a.d=function(e,t,n){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)a.d(n,i,function(t){return e[t]}.bind(null,i));return n},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=2)}([function(e,t,a){"use strict";a.r(t);var n="function"==typeof fetch?fetch.bind():function(e,t){return t=t||{},new Promise((function(a,n){var i=new XMLHttpRequest;for(var r in i.open(t.method||"get",e,!0),t.headers)i.setRequestHeader(r,t.headers[r]);function o(){var e,t=[],a=[],n={};return i.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,(function(i,r,o){t.push(r=r.toLowerCase()),a.push([r,o]),e=n[r],n[r]=e?e+","+o:o})),{ok:2==(i.status/100|0),status:i.status,statusText:i.statusText,url:i.responseURL,clone:o,text:function(){return Promise.resolve(i.responseText)},json:function(){return Promise.resolve(i.responseText).then(JSON.parse)},blob:function(){return Promise.resolve(new Blob([i.response]))},headers:{keys:function(){return t},entries:function(){return a},get:function(e){return n[e.toLowerCase()]},has:function(e){return e.toLowerCase()in n}}}}i.withCredentials="include"==t.credentials,i.onload=function(){a(o())},i.onerror=n,i.send(t.body)}))};t.default=n},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,a,n){return a&&e(t.prototype,a),n&&e(t,n),t}}();var i=function(){function e(t,a){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.pluginName=t}return n(e,[{key:"track",value:function(e,t){window.analytics.track(e,t,{integration:{name:this.pluginName}})}}]),e}();t.default=i},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.YouTubeAnalytics=t.VimeoAnalytics=void 0;var n=r(a(3)),i=r(a(4));function r(e){return e&&e.__esModule?e:{default:e}}t.VimeoAnalytics=n.default,t.YouTubeAnalytics=i.default},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,a,n){return a&&e(t.prototype,a),n&&e(t,n),t}}(),i=r(a(0));function r(e){return e&&e.__esModule?e:{default:e}}var o=function(e){function t(e,a){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t);var n=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"VimeoAnalytics"));return n.authToken=a,n.player=e,n.metadata={content:{},playback:{videoPlayer:"Vimeo"}},n.mostRecentHeartbeat=0,n.isPaused=!1,n}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),n(t,[{key:"initialize",value:function(){var e=this,t={loaded:this.retrieveMetadata,play:this.trackPlay,pause:this.trackPause,ended:this.trackEnded,timeupdate:this.trackHeartbeat};for(var a in t)this.registerHandler(a,t[a]);this.player.getVideoId().then((function(t){e.retrieveMetadata({id:t})})).catch(console.error)}},{key:"registerHandler",value:function(e,t){var a=this;this.player.on(e,(function(e){a.updateMetadata(e),t.call(a,e)}))}},{key:"trackPlay",value:function(){this.isPaused?(this.track("Video Playback Resumed",this.metadata.playback),this.isPaused=!1):(this.track("Video Playback Started",this.metadata.playback),this.track("Video Content Started",this.metadata.content))}},{key:"trackEnded",value:function(){this.track("Video Playback Completed",this.metadata.playback),this.track("Video Content Completed",this.metadata.content)}},{key:"trackHeartbeat",value:function(){var e=this.mostRecentHeartbeat,t=this.metadata.playback.position;t!==e&&t-e>=10&&(this.track("Video Content Playing",this.metadata.content),this.mostRecentHeartbeat=Math.floor(t))}},{key:"trackPause",value:function(){this.isPaused=!0,this.track("Video Playback Paused",this.metadata.playback)}},{key:"retrieveMetadata",value:function(e){var t=this;return new Promise((function(a,n){var r=e.id;(0,i.default)("https://api.vimeo.com/videos/"+r,{headers:{Authorization:"Bearer "+t.authToken}}).then((function(e){return e.ok?e.json():n(e)})).then((function(e){t.metadata.content.title=e.name,t.metadata.content.description=e.description,t.metadata.content.publisher=e.user.name,t.metadata.playback.position=0,t.metadata.playback.totalLength=e.duration})).catch((function(e){return console.error("Request to Vimeo API Failed with: ",e),n(e)}))}))}},{key:"updateMetadata",value:function(e){var t=this;return new Promise((function(a,n){t.player.getVolume().then((function(n){n&&(t.metadata.playback.sound=100*n),t.metadata.playback.position=e.seconds,a()})).catch(n)}))}}]),t}(r(a(1)).default);t.default=o},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,a,n){return a&&e(t.prototype,a),n&&e(t,n),t}}(),i=o(a(0)),r=o(a(1));function o(e){return e&&e.__esModule?e:{default:e}}var s=function(e){function t(e,a){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t);var n=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,"YoutubeAnalytics"));return n.player=e,n.apiKey=a,n.playerLoaded=!1,n.playbackStarted=!1,n.contentStarted=!1,n.isPaused=!1,n.isBuffering=!1,n.isSeeking=!1,n.lastRecordedTime={timeReported:Date.now(),timeElapsed:0},n.metadata=[{playback:{video_player:"youtube"},content:{}}],n.playlistIndex=0,n}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),n(t,[{key:"initialize",value:function(){window.segmentYoutubeOnStateChange=this.onPlayerStateChange.bind(this),window.segmentYoutubeOnReady=this.onPlayerReady.bind(this),this.player.addEventListener("onReady","segmentYoutubeOnReady"),this.player.addEventListener("onStateChange","segmentYoutubeOnStateChange")}},{key:"onPlayerReady",value:function(e){this.retrieveMetadata()}},{key:"onPlayerStateChange",value:function(e){var t=this.player.getCurrentTime();switch(this.metadata[this.playlistIndex]&&(this.metadata[this.playlistIndex].playback.position=this.metadata[this.playlistIndex].content.position=t,this.metadata[this.playlistIndex].playback.quality=this.player.getPlaybackQuality(),this.metadata[this.playlistIndex].playback.sound=this.player.isMuted()?0:this.player.getVolume()),e.data){case-1:if(this.playerLoaded)break;this.retrieveMetadata(),this.playerLoaded=!0;break;case YT.PlayerState.BUFFERING:this.handleBuffer();break;case YT.PlayerState.PLAYING:this.handlePlay();break;case YT.PlayerState.PAUSED:this.handlePause();break;case YT.PlayerState.ENDED:this.handleEnd()}this.lastRecordedTime={timeReported:Date.now(),timeElapsed:1e3*this.player.getCurrentTime()}}},{key:"retrieveMetadata",value:function(){var e=this;return new Promise((function(t,a){var n=e.player.getVideoData(),r=e.player.getPlaylist()||[n.video_id],o=r.join();(0,i.default)("https://www.googleapis.com/youtube/v3/videos?id="+o+"&part=snippet,contentDetails&key="+e.apiKey).then((function(e){if(!e.ok){var t=new Error("Segment request to Youtube API failed (likely due to a bad API Key. Events will still be sent but will not contain video metadata)");throw t.response=e,t}return e.json()})).then((function(a){e.metadata=[];for(var n=0,i=0;i<r.length;i++){var o=a.items[i];e.metadata.push({content:{title:o.snippet.title,description:o.snippet.description,keywords:o.snippet.tags,channel:o.snippet.channelTitle,airdate:o.snippet.publishedAt}}),n+=l(o.contentDetails.duration)}for(i=0;i<r.length;i++)e.metadata[i].playback={total_length:n,video_player:"youtube"};t()})).catch((function(t){e.metadata=r.map((function(e){return{playback:{video_player:"youtube"},content:{}}})),a(t)}))}))}},{key:"handleBuffer",value:function(){var e=this.determineSeek();this.playbackStarted||(this.playbackStarted=!0,this.track("Video Playback Started",this.metadata[this.playlistIndex].playback)),e&&!this.isSeeking&&(this.isSeeking=!0,this.track("Video Playback Seek Started",this.metadata[this.playlistIndex].playback)),this.isSeeking&&(this.track("Video Playback Seek Completed",this.metadata[this.playlistIndex].playback),this.isSeeking=!1);var t=this.player.getPlaylist();t&&0===this.player.getCurrentTime()&&this.player.getPlaylistIndex()!==this.playlistIndex&&(this.contentStarted=!1,this.playlistIndex===t.length-1&&0===this.player.getPlaylistIndex()&&(this.track("Video Playback Completed",this.metadata[this.player.getPlaylistIndex()].playback),this.track("Video Playback Started",this.metadata[this.player.getPlaylistIndex()].playback))),this.track("Video Playback Buffer Started",this.metadata[this.playlistIndex].playback),this.isBuffering=!0}},{key:"handlePlay",value:function(){this.contentStarted||(this.playlistIndex=this.player.getPlaylistIndex(),-1===this.playlistIndex&&(this.playlistIndex=0),this.track("Video Content Started",this.metadata[this.playlistIndex].content),this.contentStarted=!0),this.isBuffering&&(this.track("Video Playback Buffer Completed",this.metadata[this.playlistIndex].playback),this.isBuffering=!1),this.isPaused&&(this.track("Video Playback Resumed",this.metadata[this.playlistIndex].playback),this.isPaused=!1)}},{key:"handlePause",value:function(){var e=this.determineSeek();this.isBuffering&&(this.track("Video Playback Buffer Completed",this.metadata[this.playlistIndex].playback),this.isBuffering=!1),this.isPaused||(e?(this.track("Video Playback Seek Started",this.metadata[this.playlistIndex].playback),this.isSeeking=!0):(this.track("Video Playback Paused",this.metadata[this.playlistIndex].playback),this.isPaused=!0))}},{key:"handleEnd",value:function(){this.track("Video Content Completed",this.metadata[this.playlistIndex].content),this.contentStarted=!1;var e=this.player.getPlaylistIndex(),t=this.player.getPlaylist();(t&&e===t.length-1||-1===e)&&(this.track("Video Playback Completed",this.metadata[this.playlistIndex].playback),this.playbackStarted=!1)}},{key:"determineSeek",value:function(){var e=this.isPaused||this.isBuffering?0:Date.now()-this.lastRecordedTime.timeReported,t=1e3*this.player.getCurrentTime()-this.lastRecordedTime.timeElapsed;return Math.abs(e-t)>2e3}}]),t}(r.default);function l(e){var t=e.match(/PT(\d+H)?(\d+M)?(\d+S)?/);return t=t.slice(1).map((function(e){if(null!=e)return e.replace(/\D/,"")})),3600*(parseInt(t[0])||0)+60*(parseInt(t[1])||0)+(parseInt(t[2])||0)}t.default=s}])})); |
{ | ||
"name": "@segment/analytics.js-video-plugins", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "scripts": { |
@@ -11,3 +11,3 @@ module.exports = { | ||
optimization: { | ||
minimize: false | ||
minimize: true | ||
}, | ||
@@ -14,0 +14,0 @@ module: { |
85848
-13.84%1298
-31.32%