├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── .yarnclean ├── .yarnrc ├── LICENSE ├── README.md ├── dist ├── clappr-stats.js ├── clappr-stats.js.map └── clappr-stats.min.js ├── karma.conf.js ├── package.json ├── public └── index.html ├── src └── clappr-stats.js ├── test ├── clappr-stats.spec.js └── util.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'commonjs': true, 5 | 'es6': true, 6 | 'node': true, 7 | 'mocha': true, 8 | }, 9 | 'extends': 'eslint:recommended', 10 | 'parserOptions': { 11 | 'sourceType': 'module' 12 | }, 13 | 'rules': { 14 | 'indent': [ 15 | 'error', 16 | 2 17 | ], 18 | 'linebreak-style': [ 19 | 'error', 20 | 'unix' 21 | ], 22 | 'quotes': [ 23 | 'error', 24 | 'single' 25 | ], 26 | 'semi': [ 27 | 'error', 28 | 'never' 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .env 4 | coverage 5 | build/ 6 | docs/ 7 | src/base/jst.js 8 | *.cache 9 | aws.json 10 | npm-debug.log 11 | 12 | # bump 13 | *.bkp 14 | 15 | # Vim 16 | *~ 17 | *.swp 18 | *.swo 19 | 20 | # PhpStorm 21 | .idea 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: "10" 4 | cache: yarn 5 | dist: xenial 6 | 7 | services: 8 | - xvfb 9 | 10 | addons: 11 | chrome: "stable" 12 | firefox: "latest" 13 | 14 | # env: 15 | # - COVERALLS_SERVICE_NAME="travis-ci" COVERALLS_REPO_TOKEN="" 16 | 17 | notifications: 18 | email: 19 | - videos5@corp.globo.com 20 | slack: globo:F1iVlyrzR4iX8OS7h8fwMcqx 21 | 22 | before_script: "yarn lint" 23 | 24 | # after_script: "cat coverage/C*/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 25 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | 13 | # examples 14 | example 15 | examples 16 | 17 | # code coverage directories 18 | coverage 19 | .nyc_output 20 | 21 | # build scripts 22 | Makefile 23 | Gulpfile.js 24 | Gruntfile.js 25 | 26 | # configs 27 | appveyor.yml 28 | circle.yml 29 | codeship-services.yml 30 | codeship-steps.yml 31 | wercker.yml 32 | .tern-project 33 | .gitattributes 34 | .editorconfig 35 | .*ignore 36 | .flowconfig 37 | .documentup.json 38 | .yarn-metadata.json 39 | .travis.yml 40 | 41 | # misc 42 | *.md 43 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --install.ignore-engines true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Leandro Moreira 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of clappr-stats nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/clappr-stats.svg)](https://badge.fury.io/js/clappr-stats) 2 | [![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg) 3 | 4 | # Usage 5 | 6 | You can use it from JSDelivr `https://cdn.jsdelivr.net/npm/@clappr/stats-plugin@latest/dist/clappr-stats.min.js` or as a npm package. 7 | 8 | ```html 9 | 44 | ``` 45 | 46 | # Metrics 47 | 48 | ```javascript 49 | { 50 | counters: { 51 | play: 0, // number of plays 52 | pause: 0, // number of pauses 53 | error: 0, // number of errors 54 | buffering: 0, // number of bufferings 55 | decodedFrames: 0, // number of decoded frames (when available) 56 | droppedFrames: 0, // number of dropped frames (when available) 57 | fps: 0, // frames per second (when available) 58 | changeLevel: 0, // number of adaptative bitrate changes 59 | seek: 0, // number of seeks 60 | fullscreen: 0, // number of times that user went to fullscreen 61 | dvrUsage: 0 // number of time that user used dvr seek (at live stream) 62 | }, 63 | timers: { 64 | startup: 0, // time (ms) since user click/touch play (intent to play) to the play 65 | watch: 0, // time (ms) of watched content (does not include pause and buffering) 66 | pause: 0, // time (ms) of paused content 67 | buffering: 0, // time (ms) of buffering 68 | session: 0, // time (ms) of session (sum of watch+pause+buffering) 69 | latency: 0, // time (ms) of latency between user and the provided uri 70 | }, 71 | extra: { 72 | playbackName: '', // playback name (hls, html5_video, flashls) 73 | playbackType: '', // vod or live 74 | buffersize: 0, // buffersize in ms 75 | duration: 0, // duration time in ms 76 | currentTime: 0, // current time in ms 77 | bitratesHistory: [], // the bitrates changes history 78 | bitrateWeightedMean: 0, // bitrate weighted mean (bps) 79 | bitrateMostUsed: 0, // most used (based on time) bitrate (bps) 80 | watchHistory: [], // an array of an array of watched range time ex: [0, 2200] 81 | watchedPercentage: 0, // % of watched time 82 | bufferingPercentage: 0, // % of buffering time 83 | bandwidth: 0, // user bandwidth (bps) 84 | } 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /dist/clappr-stats.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("Clappr")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["Clappr"], factory); 6 | else if(typeof exports === 'object') 7 | exports["ClapprStats"] = factory(require("Clappr")); 8 | else 9 | root["ClapprStats"] = factory(root["Clappr"]); 10 | })(window, function(__WEBPACK_EXTERNAL_MODULE__clappr_core__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) { 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ } 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ i: moduleId, 25 | /******/ l: false, 26 | /******/ exports: {} 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.l = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // define getter function for harmony exports 47 | /******/ __webpack_require__.d = function(exports, name, getter) { 48 | /******/ if(!__webpack_require__.o(exports, name)) { 49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 50 | /******/ } 51 | /******/ }; 52 | /******/ 53 | /******/ // define __esModule on exports 54 | /******/ __webpack_require__.r = function(exports) { 55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 57 | /******/ } 58 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 59 | /******/ }; 60 | /******/ 61 | /******/ // create a fake namespace object 62 | /******/ // mode & 1: value is a module id, require it 63 | /******/ // mode & 2: merge all properties of value into the ns 64 | /******/ // mode & 4: return value when already ns object 65 | /******/ // mode & 8|1: behave like require 66 | /******/ __webpack_require__.t = function(value, mode) { 67 | /******/ if(mode & 1) value = __webpack_require__(value); 68 | /******/ if(mode & 8) return value; 69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 70 | /******/ var ns = Object.create(null); 71 | /******/ __webpack_require__.r(ns); 72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 74 | /******/ return ns; 75 | /******/ }; 76 | /******/ 77 | /******/ // getDefaultExport function for compatibility with non-harmony modules 78 | /******/ __webpack_require__.n = function(module) { 79 | /******/ var getter = module && module.__esModule ? 80 | /******/ function getDefault() { return module['default']; } : 81 | /******/ function getModuleExports() { return module; }; 82 | /******/ __webpack_require__.d(getter, 'a', getter); 83 | /******/ return getter; 84 | /******/ }; 85 | /******/ 86 | /******/ // Object.prototype.hasOwnProperty.call 87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 88 | /******/ 89 | /******/ // __webpack_public_path__ 90 | /******/ __webpack_require__.p = "latest/"; 91 | /******/ 92 | /******/ 93 | /******/ // Load entry module and return exports 94 | /******/ return __webpack_require__(__webpack_require__.s = "./src/clappr-stats.js"); 95 | /******/ }) 96 | /************************************************************************/ 97 | /******/ ({ 98 | 99 | /***/ "./node_modules/lodash.get/index.js": 100 | /*!******************************************!*\ 101 | !*** ./node_modules/lodash.get/index.js ***! 102 | \******************************************/ 103 | /*! no static exports found */ 104 | /***/ (function(module, exports, __webpack_require__) { 105 | 106 | "use strict"; 107 | /* WEBPACK VAR INJECTION */(function(global) { 108 | 109 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 110 | 111 | /** 112 | * lodash (Custom Build) 113 | * Build: `lodash modularize exports="npm" -o ./` 114 | * Copyright jQuery Foundation and other contributors 115 | * Released under MIT license 116 | * Based on Underscore.js 1.8.3 117 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 118 | */ 119 | 120 | /** Used as the `TypeError` message for "Functions" methods. */ 121 | var FUNC_ERROR_TEXT = 'Expected a function'; 122 | 123 | /** Used to stand-in for `undefined` hash values. */ 124 | var HASH_UNDEFINED = '__lodash_hash_undefined__'; 125 | 126 | /** Used as references for various `Number` constants. */ 127 | var INFINITY = 1 / 0; 128 | 129 | /** `Object#toString` result references. */ 130 | var funcTag = '[object Function]', 131 | genTag = '[object GeneratorFunction]', 132 | symbolTag = '[object Symbol]'; 133 | 134 | /** Used to match property names within property paths. */ 135 | var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, 136 | reIsPlainProp = /^\w*$/, 137 | reLeadingDot = /^\./, 138 | rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; 139 | 140 | /** 141 | * Used to match `RegExp` 142 | * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). 143 | */ 144 | var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; 145 | 146 | /** Used to match backslashes in property paths. */ 147 | var reEscapeChar = /\\(\\)?/g; 148 | 149 | /** Used to detect host constructors (Safari). */ 150 | var reIsHostCtor = /^\[object .+?Constructor\]$/; 151 | 152 | /** Detect free variable `global` from Node.js. */ 153 | var freeGlobal = (typeof global === 'undefined' ? 'undefined' : _typeof(global)) == 'object' && global && global.Object === Object && global; 154 | 155 | /** Detect free variable `self`. */ 156 | var freeSelf = (typeof self === 'undefined' ? 'undefined' : _typeof(self)) == 'object' && self && self.Object === Object && self; 157 | 158 | /** Used as a reference to the global object. */ 159 | var root = freeGlobal || freeSelf || Function('return this')(); 160 | 161 | /** 162 | * Gets the value at `key` of `object`. 163 | * 164 | * @private 165 | * @param {Object} [object] The object to query. 166 | * @param {string} key The key of the property to get. 167 | * @returns {*} Returns the property value. 168 | */ 169 | function getValue(object, key) { 170 | return object == null ? undefined : object[key]; 171 | } 172 | 173 | /** 174 | * Checks if `value` is a host object in IE < 9. 175 | * 176 | * @private 177 | * @param {*} value The value to check. 178 | * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 179 | */ 180 | function isHostObject(value) { 181 | // Many host objects are `Object` objects that can coerce to strings 182 | // despite having improperly defined `toString` methods. 183 | var result = false; 184 | if (value != null && typeof value.toString != 'function') { 185 | try { 186 | result = !!(value + ''); 187 | } catch (e) {} 188 | } 189 | return result; 190 | } 191 | 192 | /** Used for built-in method references. */ 193 | var arrayProto = Array.prototype, 194 | funcProto = Function.prototype, 195 | objectProto = Object.prototype; 196 | 197 | /** Used to detect overreaching core-js shims. */ 198 | var coreJsData = root['__core-js_shared__']; 199 | 200 | /** Used to detect methods masquerading as native. */ 201 | var maskSrcKey = function () { 202 | var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); 203 | return uid ? 'Symbol(src)_1.' + uid : ''; 204 | }(); 205 | 206 | /** Used to resolve the decompiled source of functions. */ 207 | var funcToString = funcProto.toString; 208 | 209 | /** Used to check objects for own properties. */ 210 | var hasOwnProperty = objectProto.hasOwnProperty; 211 | 212 | /** 213 | * Used to resolve the 214 | * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 215 | * of values. 216 | */ 217 | var objectToString = objectProto.toString; 218 | 219 | /** Used to detect if a method is native. */ 220 | var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'); 221 | 222 | /** Built-in value references. */ 223 | var _Symbol = root.Symbol, 224 | splice = arrayProto.splice; 225 | 226 | /* Built-in method references that are verified to be native. */ 227 | var Map = getNative(root, 'Map'), 228 | nativeCreate = getNative(Object, 'create'); 229 | 230 | /** Used to convert symbols to primitives and strings. */ 231 | var symbolProto = _Symbol ? _Symbol.prototype : undefined, 232 | symbolToString = symbolProto ? symbolProto.toString : undefined; 233 | 234 | /** 235 | * Creates a hash object. 236 | * 237 | * @private 238 | * @constructor 239 | * @param {Array} [entries] The key-value pairs to cache. 240 | */ 241 | function Hash(entries) { 242 | var index = -1, 243 | length = entries ? entries.length : 0; 244 | 245 | this.clear(); 246 | while (++index < length) { 247 | var entry = entries[index]; 248 | this.set(entry[0], entry[1]); 249 | } 250 | } 251 | 252 | /** 253 | * Removes all key-value entries from the hash. 254 | * 255 | * @private 256 | * @name clear 257 | * @memberOf Hash 258 | */ 259 | function hashClear() { 260 | this.__data__ = nativeCreate ? nativeCreate(null) : {}; 261 | } 262 | 263 | /** 264 | * Removes `key` and its value from the hash. 265 | * 266 | * @private 267 | * @name delete 268 | * @memberOf Hash 269 | * @param {Object} hash The hash to modify. 270 | * @param {string} key The key of the value to remove. 271 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 272 | */ 273 | function hashDelete(key) { 274 | return this.has(key) && delete this.__data__[key]; 275 | } 276 | 277 | /** 278 | * Gets the hash value for `key`. 279 | * 280 | * @private 281 | * @name get 282 | * @memberOf Hash 283 | * @param {string} key The key of the value to get. 284 | * @returns {*} Returns the entry value. 285 | */ 286 | function hashGet(key) { 287 | var data = this.__data__; 288 | if (nativeCreate) { 289 | var result = data[key]; 290 | return result === HASH_UNDEFINED ? undefined : result; 291 | } 292 | return hasOwnProperty.call(data, key) ? data[key] : undefined; 293 | } 294 | 295 | /** 296 | * Checks if a hash value for `key` exists. 297 | * 298 | * @private 299 | * @name has 300 | * @memberOf Hash 301 | * @param {string} key The key of the entry to check. 302 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 303 | */ 304 | function hashHas(key) { 305 | var data = this.__data__; 306 | return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); 307 | } 308 | 309 | /** 310 | * Sets the hash `key` to `value`. 311 | * 312 | * @private 313 | * @name set 314 | * @memberOf Hash 315 | * @param {string} key The key of the value to set. 316 | * @param {*} value The value to set. 317 | * @returns {Object} Returns the hash instance. 318 | */ 319 | function hashSet(key, value) { 320 | var data = this.__data__; 321 | data[key] = nativeCreate && value === undefined ? HASH_UNDEFINED : value; 322 | return this; 323 | } 324 | 325 | // Add methods to `Hash`. 326 | Hash.prototype.clear = hashClear; 327 | Hash.prototype['delete'] = hashDelete; 328 | Hash.prototype.get = hashGet; 329 | Hash.prototype.has = hashHas; 330 | Hash.prototype.set = hashSet; 331 | 332 | /** 333 | * Creates an list cache object. 334 | * 335 | * @private 336 | * @constructor 337 | * @param {Array} [entries] The key-value pairs to cache. 338 | */ 339 | function ListCache(entries) { 340 | var index = -1, 341 | length = entries ? entries.length : 0; 342 | 343 | this.clear(); 344 | while (++index < length) { 345 | var entry = entries[index]; 346 | this.set(entry[0], entry[1]); 347 | } 348 | } 349 | 350 | /** 351 | * Removes all key-value entries from the list cache. 352 | * 353 | * @private 354 | * @name clear 355 | * @memberOf ListCache 356 | */ 357 | function listCacheClear() { 358 | this.__data__ = []; 359 | } 360 | 361 | /** 362 | * Removes `key` and its value from the list cache. 363 | * 364 | * @private 365 | * @name delete 366 | * @memberOf ListCache 367 | * @param {string} key The key of the value to remove. 368 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 369 | */ 370 | function listCacheDelete(key) { 371 | var data = this.__data__, 372 | index = assocIndexOf(data, key); 373 | 374 | if (index < 0) { 375 | return false; 376 | } 377 | var lastIndex = data.length - 1; 378 | if (index == lastIndex) { 379 | data.pop(); 380 | } else { 381 | splice.call(data, index, 1); 382 | } 383 | return true; 384 | } 385 | 386 | /** 387 | * Gets the list cache value for `key`. 388 | * 389 | * @private 390 | * @name get 391 | * @memberOf ListCache 392 | * @param {string} key The key of the value to get. 393 | * @returns {*} Returns the entry value. 394 | */ 395 | function listCacheGet(key) { 396 | var data = this.__data__, 397 | index = assocIndexOf(data, key); 398 | 399 | return index < 0 ? undefined : data[index][1]; 400 | } 401 | 402 | /** 403 | * Checks if a list cache value for `key` exists. 404 | * 405 | * @private 406 | * @name has 407 | * @memberOf ListCache 408 | * @param {string} key The key of the entry to check. 409 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 410 | */ 411 | function listCacheHas(key) { 412 | return assocIndexOf(this.__data__, key) > -1; 413 | } 414 | 415 | /** 416 | * Sets the list cache `key` to `value`. 417 | * 418 | * @private 419 | * @name set 420 | * @memberOf ListCache 421 | * @param {string} key The key of the value to set. 422 | * @param {*} value The value to set. 423 | * @returns {Object} Returns the list cache instance. 424 | */ 425 | function listCacheSet(key, value) { 426 | var data = this.__data__, 427 | index = assocIndexOf(data, key); 428 | 429 | if (index < 0) { 430 | data.push([key, value]); 431 | } else { 432 | data[index][1] = value; 433 | } 434 | return this; 435 | } 436 | 437 | // Add methods to `ListCache`. 438 | ListCache.prototype.clear = listCacheClear; 439 | ListCache.prototype['delete'] = listCacheDelete; 440 | ListCache.prototype.get = listCacheGet; 441 | ListCache.prototype.has = listCacheHas; 442 | ListCache.prototype.set = listCacheSet; 443 | 444 | /** 445 | * Creates a map cache object to store key-value pairs. 446 | * 447 | * @private 448 | * @constructor 449 | * @param {Array} [entries] The key-value pairs to cache. 450 | */ 451 | function MapCache(entries) { 452 | var index = -1, 453 | length = entries ? entries.length : 0; 454 | 455 | this.clear(); 456 | while (++index < length) { 457 | var entry = entries[index]; 458 | this.set(entry[0], entry[1]); 459 | } 460 | } 461 | 462 | /** 463 | * Removes all key-value entries from the map. 464 | * 465 | * @private 466 | * @name clear 467 | * @memberOf MapCache 468 | */ 469 | function mapCacheClear() { 470 | this.__data__ = { 471 | 'hash': new Hash(), 472 | 'map': new (Map || ListCache)(), 473 | 'string': new Hash() 474 | }; 475 | } 476 | 477 | /** 478 | * Removes `key` and its value from the map. 479 | * 480 | * @private 481 | * @name delete 482 | * @memberOf MapCache 483 | * @param {string} key The key of the value to remove. 484 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 485 | */ 486 | function mapCacheDelete(key) { 487 | return getMapData(this, key)['delete'](key); 488 | } 489 | 490 | /** 491 | * Gets the map value for `key`. 492 | * 493 | * @private 494 | * @name get 495 | * @memberOf MapCache 496 | * @param {string} key The key of the value to get. 497 | * @returns {*} Returns the entry value. 498 | */ 499 | function mapCacheGet(key) { 500 | return getMapData(this, key).get(key); 501 | } 502 | 503 | /** 504 | * Checks if a map value for `key` exists. 505 | * 506 | * @private 507 | * @name has 508 | * @memberOf MapCache 509 | * @param {string} key The key of the entry to check. 510 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 511 | */ 512 | function mapCacheHas(key) { 513 | return getMapData(this, key).has(key); 514 | } 515 | 516 | /** 517 | * Sets the map `key` to `value`. 518 | * 519 | * @private 520 | * @name set 521 | * @memberOf MapCache 522 | * @param {string} key The key of the value to set. 523 | * @param {*} value The value to set. 524 | * @returns {Object} Returns the map cache instance. 525 | */ 526 | function mapCacheSet(key, value) { 527 | getMapData(this, key).set(key, value); 528 | return this; 529 | } 530 | 531 | // Add methods to `MapCache`. 532 | MapCache.prototype.clear = mapCacheClear; 533 | MapCache.prototype['delete'] = mapCacheDelete; 534 | MapCache.prototype.get = mapCacheGet; 535 | MapCache.prototype.has = mapCacheHas; 536 | MapCache.prototype.set = mapCacheSet; 537 | 538 | /** 539 | * Gets the index at which the `key` is found in `array` of key-value pairs. 540 | * 541 | * @private 542 | * @param {Array} array The array to inspect. 543 | * @param {*} key The key to search for. 544 | * @returns {number} Returns the index of the matched value, else `-1`. 545 | */ 546 | function assocIndexOf(array, key) { 547 | var length = array.length; 548 | while (length--) { 549 | if (eq(array[length][0], key)) { 550 | return length; 551 | } 552 | } 553 | return -1; 554 | } 555 | 556 | /** 557 | * The base implementation of `_.get` without support for default values. 558 | * 559 | * @private 560 | * @param {Object} object The object to query. 561 | * @param {Array|string} path The path of the property to get. 562 | * @returns {*} Returns the resolved value. 563 | */ 564 | function baseGet(object, path) { 565 | path = isKey(path, object) ? [path] : castPath(path); 566 | 567 | var index = 0, 568 | length = path.length; 569 | 570 | while (object != null && index < length) { 571 | object = object[toKey(path[index++])]; 572 | } 573 | return index && index == length ? object : undefined; 574 | } 575 | 576 | /** 577 | * The base implementation of `_.isNative` without bad shim checks. 578 | * 579 | * @private 580 | * @param {*} value The value to check. 581 | * @returns {boolean} Returns `true` if `value` is a native function, 582 | * else `false`. 583 | */ 584 | function baseIsNative(value) { 585 | if (!isObject(value) || isMasked(value)) { 586 | return false; 587 | } 588 | var pattern = isFunction(value) || isHostObject(value) ? reIsNative : reIsHostCtor; 589 | return pattern.test(toSource(value)); 590 | } 591 | 592 | /** 593 | * The base implementation of `_.toString` which doesn't convert nullish 594 | * values to empty strings. 595 | * 596 | * @private 597 | * @param {*} value The value to process. 598 | * @returns {string} Returns the string. 599 | */ 600 | function baseToString(value) { 601 | // Exit early for strings to avoid a performance hit in some environments. 602 | if (typeof value == 'string') { 603 | return value; 604 | } 605 | if (isSymbol(value)) { 606 | return symbolToString ? symbolToString.call(value) : ''; 607 | } 608 | var result = value + ''; 609 | return result == '0' && 1 / value == -INFINITY ? '-0' : result; 610 | } 611 | 612 | /** 613 | * Casts `value` to a path array if it's not one. 614 | * 615 | * @private 616 | * @param {*} value The value to inspect. 617 | * @returns {Array} Returns the cast property path array. 618 | */ 619 | function castPath(value) { 620 | return isArray(value) ? value : stringToPath(value); 621 | } 622 | 623 | /** 624 | * Gets the data for `map`. 625 | * 626 | * @private 627 | * @param {Object} map The map to query. 628 | * @param {string} key The reference key. 629 | * @returns {*} Returns the map data. 630 | */ 631 | function getMapData(map, key) { 632 | var data = map.__data__; 633 | return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map; 634 | } 635 | 636 | /** 637 | * Gets the native function at `key` of `object`. 638 | * 639 | * @private 640 | * @param {Object} object The object to query. 641 | * @param {string} key The key of the method to get. 642 | * @returns {*} Returns the function if it's native, else `undefined`. 643 | */ 644 | function getNative(object, key) { 645 | var value = getValue(object, key); 646 | return baseIsNative(value) ? value : undefined; 647 | } 648 | 649 | /** 650 | * Checks if `value` is a property name and not a property path. 651 | * 652 | * @private 653 | * @param {*} value The value to check. 654 | * @param {Object} [object] The object to query keys on. 655 | * @returns {boolean} Returns `true` if `value` is a property name, else `false`. 656 | */ 657 | function isKey(value, object) { 658 | if (isArray(value)) { 659 | return false; 660 | } 661 | var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); 662 | if (type == 'number' || type == 'symbol' || type == 'boolean' || value == null || isSymbol(value)) { 663 | return true; 664 | } 665 | return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || object != null && value in Object(object); 666 | } 667 | 668 | /** 669 | * Checks if `value` is suitable for use as unique object key. 670 | * 671 | * @private 672 | * @param {*} value The value to check. 673 | * @returns {boolean} Returns `true` if `value` is suitable, else `false`. 674 | */ 675 | function isKeyable(value) { 676 | var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); 677 | return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null; 678 | } 679 | 680 | /** 681 | * Checks if `func` has its source masked. 682 | * 683 | * @private 684 | * @param {Function} func The function to check. 685 | * @returns {boolean} Returns `true` if `func` is masked, else `false`. 686 | */ 687 | function isMasked(func) { 688 | return !!maskSrcKey && maskSrcKey in func; 689 | } 690 | 691 | /** 692 | * Converts `string` to a property path array. 693 | * 694 | * @private 695 | * @param {string} string The string to convert. 696 | * @returns {Array} Returns the property path array. 697 | */ 698 | var stringToPath = memoize(function (string) { 699 | string = toString(string); 700 | 701 | var result = []; 702 | if (reLeadingDot.test(string)) { 703 | result.push(''); 704 | } 705 | string.replace(rePropName, function (match, number, quote, string) { 706 | result.push(quote ? string.replace(reEscapeChar, '$1') : number || match); 707 | }); 708 | return result; 709 | }); 710 | 711 | /** 712 | * Converts `value` to a string key if it's not a string or symbol. 713 | * 714 | * @private 715 | * @param {*} value The value to inspect. 716 | * @returns {string|symbol} Returns the key. 717 | */ 718 | function toKey(value) { 719 | if (typeof value == 'string' || isSymbol(value)) { 720 | return value; 721 | } 722 | var result = value + ''; 723 | return result == '0' && 1 / value == -INFINITY ? '-0' : result; 724 | } 725 | 726 | /** 727 | * Converts `func` to its source code. 728 | * 729 | * @private 730 | * @param {Function} func The function to process. 731 | * @returns {string} Returns the source code. 732 | */ 733 | function toSource(func) { 734 | if (func != null) { 735 | try { 736 | return funcToString.call(func); 737 | } catch (e) {} 738 | try { 739 | return func + ''; 740 | } catch (e) {} 741 | } 742 | return ''; 743 | } 744 | 745 | /** 746 | * Creates a function that memoizes the result of `func`. If `resolver` is 747 | * provided, it determines the cache key for storing the result based on the 748 | * arguments provided to the memoized function. By default, the first argument 749 | * provided to the memoized function is used as the map cache key. The `func` 750 | * is invoked with the `this` binding of the memoized function. 751 | * 752 | * **Note:** The cache is exposed as the `cache` property on the memoized 753 | * function. Its creation may be customized by replacing the `_.memoize.Cache` 754 | * constructor with one whose instances implement the 755 | * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) 756 | * method interface of `delete`, `get`, `has`, and `set`. 757 | * 758 | * @static 759 | * @memberOf _ 760 | * @since 0.1.0 761 | * @category Function 762 | * @param {Function} func The function to have its output memoized. 763 | * @param {Function} [resolver] The function to resolve the cache key. 764 | * @returns {Function} Returns the new memoized function. 765 | * @example 766 | * 767 | * var object = { 'a': 1, 'b': 2 }; 768 | * var other = { 'c': 3, 'd': 4 }; 769 | * 770 | * var values = _.memoize(_.values); 771 | * values(object); 772 | * // => [1, 2] 773 | * 774 | * values(other); 775 | * // => [3, 4] 776 | * 777 | * object.a = 2; 778 | * values(object); 779 | * // => [1, 2] 780 | * 781 | * // Modify the result cache. 782 | * values.cache.set(object, ['a', 'b']); 783 | * values(object); 784 | * // => ['a', 'b'] 785 | * 786 | * // Replace `_.memoize.Cache`. 787 | * _.memoize.Cache = WeakMap; 788 | */ 789 | function memoize(func, resolver) { 790 | if (typeof func != 'function' || resolver && typeof resolver != 'function') { 791 | throw new TypeError(FUNC_ERROR_TEXT); 792 | } 793 | var memoized = function memoized() { 794 | var args = arguments, 795 | key = resolver ? resolver.apply(this, args) : args[0], 796 | cache = memoized.cache; 797 | 798 | if (cache.has(key)) { 799 | return cache.get(key); 800 | } 801 | var result = func.apply(this, args); 802 | memoized.cache = cache.set(key, result); 803 | return result; 804 | }; 805 | memoized.cache = new (memoize.Cache || MapCache)(); 806 | return memoized; 807 | } 808 | 809 | // Assign cache to `_.memoize`. 810 | memoize.Cache = MapCache; 811 | 812 | /** 813 | * Performs a 814 | * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 815 | * comparison between two values to determine if they are equivalent. 816 | * 817 | * @static 818 | * @memberOf _ 819 | * @since 4.0.0 820 | * @category Lang 821 | * @param {*} value The value to compare. 822 | * @param {*} other The other value to compare. 823 | * @returns {boolean} Returns `true` if the values are equivalent, else `false`. 824 | * @example 825 | * 826 | * var object = { 'a': 1 }; 827 | * var other = { 'a': 1 }; 828 | * 829 | * _.eq(object, object); 830 | * // => true 831 | * 832 | * _.eq(object, other); 833 | * // => false 834 | * 835 | * _.eq('a', 'a'); 836 | * // => true 837 | * 838 | * _.eq('a', Object('a')); 839 | * // => false 840 | * 841 | * _.eq(NaN, NaN); 842 | * // => true 843 | */ 844 | function eq(value, other) { 845 | return value === other || value !== value && other !== other; 846 | } 847 | 848 | /** 849 | * Checks if `value` is classified as an `Array` object. 850 | * 851 | * @static 852 | * @memberOf _ 853 | * @since 0.1.0 854 | * @category Lang 855 | * @param {*} value The value to check. 856 | * @returns {boolean} Returns `true` if `value` is an array, else `false`. 857 | * @example 858 | * 859 | * _.isArray([1, 2, 3]); 860 | * // => true 861 | * 862 | * _.isArray(document.body.children); 863 | * // => false 864 | * 865 | * _.isArray('abc'); 866 | * // => false 867 | * 868 | * _.isArray(_.noop); 869 | * // => false 870 | */ 871 | var isArray = Array.isArray; 872 | 873 | /** 874 | * Checks if `value` is classified as a `Function` object. 875 | * 876 | * @static 877 | * @memberOf _ 878 | * @since 0.1.0 879 | * @category Lang 880 | * @param {*} value The value to check. 881 | * @returns {boolean} Returns `true` if `value` is a function, else `false`. 882 | * @example 883 | * 884 | * _.isFunction(_); 885 | * // => true 886 | * 887 | * _.isFunction(/abc/); 888 | * // => false 889 | */ 890 | function isFunction(value) { 891 | // The use of `Object#toString` avoids issues with the `typeof` operator 892 | // in Safari 8-9 which returns 'object' for typed array and other constructors. 893 | var tag = isObject(value) ? objectToString.call(value) : ''; 894 | return tag == funcTag || tag == genTag; 895 | } 896 | 897 | /** 898 | * Checks if `value` is the 899 | * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 900 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 901 | * 902 | * @static 903 | * @memberOf _ 904 | * @since 0.1.0 905 | * @category Lang 906 | * @param {*} value The value to check. 907 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 908 | * @example 909 | * 910 | * _.isObject({}); 911 | * // => true 912 | * 913 | * _.isObject([1, 2, 3]); 914 | * // => true 915 | * 916 | * _.isObject(_.noop); 917 | * // => true 918 | * 919 | * _.isObject(null); 920 | * // => false 921 | */ 922 | function isObject(value) { 923 | var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); 924 | return !!value && (type == 'object' || type == 'function'); 925 | } 926 | 927 | /** 928 | * Checks if `value` is object-like. A value is object-like if it's not `null` 929 | * and has a `typeof` result of "object". 930 | * 931 | * @static 932 | * @memberOf _ 933 | * @since 4.0.0 934 | * @category Lang 935 | * @param {*} value The value to check. 936 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 937 | * @example 938 | * 939 | * _.isObjectLike({}); 940 | * // => true 941 | * 942 | * _.isObjectLike([1, 2, 3]); 943 | * // => true 944 | * 945 | * _.isObjectLike(_.noop); 946 | * // => false 947 | * 948 | * _.isObjectLike(null); 949 | * // => false 950 | */ 951 | function isObjectLike(value) { 952 | return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object'; 953 | } 954 | 955 | /** 956 | * Checks if `value` is classified as a `Symbol` primitive or object. 957 | * 958 | * @static 959 | * @memberOf _ 960 | * @since 4.0.0 961 | * @category Lang 962 | * @param {*} value The value to check. 963 | * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. 964 | * @example 965 | * 966 | * _.isSymbol(Symbol.iterator); 967 | * // => true 968 | * 969 | * _.isSymbol('abc'); 970 | * // => false 971 | */ 972 | function isSymbol(value) { 973 | return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'symbol' || isObjectLike(value) && objectToString.call(value) == symbolTag; 974 | } 975 | 976 | /** 977 | * Converts `value` to a string. An empty string is returned for `null` 978 | * and `undefined` values. The sign of `-0` is preserved. 979 | * 980 | * @static 981 | * @memberOf _ 982 | * @since 4.0.0 983 | * @category Lang 984 | * @param {*} value The value to process. 985 | * @returns {string} Returns the string. 986 | * @example 987 | * 988 | * _.toString(null); 989 | * // => '' 990 | * 991 | * _.toString(-0); 992 | * // => '-0' 993 | * 994 | * _.toString([1, 2, 3]); 995 | * // => '1,2,3' 996 | */ 997 | function toString(value) { 998 | return value == null ? '' : baseToString(value); 999 | } 1000 | 1001 | /** 1002 | * Gets the value at `path` of `object`. If the resolved value is 1003 | * `undefined`, the `defaultValue` is returned in its place. 1004 | * 1005 | * @static 1006 | * @memberOf _ 1007 | * @since 3.7.0 1008 | * @category Object 1009 | * @param {Object} object The object to query. 1010 | * @param {Array|string} path The path of the property to get. 1011 | * @param {*} [defaultValue] The value returned for `undefined` resolved values. 1012 | * @returns {*} Returns the resolved value. 1013 | * @example 1014 | * 1015 | * var object = { 'a': [{ 'b': { 'c': 3 } }] }; 1016 | * 1017 | * _.get(object, 'a[0].b.c'); 1018 | * // => 3 1019 | * 1020 | * _.get(object, ['a', '0', 'b', 'c']); 1021 | * // => 3 1022 | * 1023 | * _.get(object, 'a.b.c', 'default'); 1024 | * // => 'default' 1025 | */ 1026 | function get(object, path, defaultValue) { 1027 | var result = object == null ? undefined : baseGet(object, path); 1028 | return result === undefined ? defaultValue : result; 1029 | } 1030 | 1031 | module.exports = get; 1032 | /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js"))) 1033 | 1034 | /***/ }), 1035 | 1036 | /***/ "./node_modules/webpack/buildin/global.js": 1037 | /*!***********************************!*\ 1038 | !*** (webpack)/buildin/global.js ***! 1039 | \***********************************/ 1040 | /*! no static exports found */ 1041 | /***/ (function(module, exports, __webpack_require__) { 1042 | 1043 | "use strict"; 1044 | 1045 | 1046 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 1047 | 1048 | var g; 1049 | 1050 | // This works in non-strict mode 1051 | g = function () { 1052 | return this; 1053 | }(); 1054 | 1055 | try { 1056 | // This works if eval is allowed (see CSP) 1057 | g = g || new Function("return this")(); 1058 | } catch (e) { 1059 | // This works if the window reference is available 1060 | if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object") g = window; 1061 | } 1062 | 1063 | // g can still be undefined, but nothing to do about it... 1064 | // We return undefined, instead of nothing here, so it's 1065 | // easier to handle this case. if(!global) { ...} 1066 | 1067 | module.exports = g; 1068 | 1069 | /***/ }), 1070 | 1071 | /***/ "./src/clappr-stats.js": 1072 | /*!*****************************!*\ 1073 | !*** ./src/clappr-stats.js ***! 1074 | \*****************************/ 1075 | /*! no static exports found */ 1076 | /***/ (function(module, exports, __webpack_require__) { 1077 | 1078 | "use strict"; 1079 | 1080 | 1081 | Object.defineProperty(exports, "__esModule", { 1082 | value: true 1083 | }); 1084 | 1085 | var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; 1086 | 1087 | 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; }; }(); 1088 | 1089 | var _core = __webpack_require__(/*! @clappr/core */ "@clappr/core"); 1090 | 1091 | var _lodash = __webpack_require__(/*! lodash.get */ "./node_modules/lodash.get/index.js"); 1092 | 1093 | var _lodash2 = _interopRequireDefault(_lodash); 1094 | 1095 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1096 | 1097 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1098 | 1099 | 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; } 1100 | 1101 | 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; } 1102 | 1103 | var ClapprStats = function (_ContainerPlugin) { 1104 | _inherits(ClapprStats, _ContainerPlugin); 1105 | 1106 | _createClass(ClapprStats, [{ 1107 | key: '_now', 1108 | value: function _now() { 1109 | var hasPerformanceSupport = window.performance && typeof window.performance.now === 'function'; 1110 | return hasPerformanceSupport ? window.performance.now() : new Date(); 1111 | } 1112 | }, { 1113 | key: '_inc', 1114 | value: function _inc(counter) { 1115 | this._metrics.counters[counter] += 1; 1116 | } 1117 | }, { 1118 | key: '_timerHasStarted', 1119 | value: function _timerHasStarted(timer) { 1120 | return this['_start' + timer] !== undefined; 1121 | } 1122 | }, { 1123 | key: '_start', 1124 | value: function _start(timer) { 1125 | this['_start' + timer] = this._now(); 1126 | } 1127 | }, { 1128 | key: '_stop', 1129 | value: function _stop(timer) { 1130 | this._metrics.timers[timer] += this._now() - this['_start' + timer]; 1131 | } 1132 | }, { 1133 | key: '_defaultReport', 1134 | value: function _defaultReport(metrics) { 1135 | console.log(metrics); 1136 | } //eslint-disable-line no-console 1137 | 1138 | }, { 1139 | key: 'name', 1140 | get: function get() { 1141 | return 'clappr_stats'; 1142 | } 1143 | }, { 1144 | key: 'supportedVersion', 1145 | get: function get() { 1146 | return { min: '0.4.2' }; 1147 | } 1148 | }, { 1149 | key: '_playbackName', 1150 | get: function get() { 1151 | return this.container.playback.name; 1152 | } 1153 | }, { 1154 | key: '_playbackType', 1155 | get: function get() { 1156 | return this.container.getPlaybackType(); 1157 | } 1158 | }]); 1159 | 1160 | function ClapprStats(container) { 1161 | _classCallCheck(this, ClapprStats); 1162 | 1163 | var _this = _possibleConstructorReturn(this, (ClapprStats.__proto__ || Object.getPrototypeOf(ClapprStats)).call(this, container)); 1164 | 1165 | _this._runEach = (0, _lodash2.default)(container, 'options.clapprStats.runEach', 5000); 1166 | _this._onReport = (0, _lodash2.default)(container, 'options.clapprStats.onReport', _this._defaultReport); 1167 | _this._uriToMeasureLatency = (0, _lodash2.default)(container, 'options.clapprStats.uriToMeasureLatency'); 1168 | _this._urisToMeasureBandwidth = (0, _lodash2.default)(container, 'options.clapprStats.urisToMeasureBandwidth'); 1169 | _this._runBandwidthTestEvery = (0, _lodash2.default)(container, 'options.clapprStats.runBandwidthTestEvery', 10); 1170 | _this._bwMeasureCount = 0; 1171 | 1172 | _this._completion = { 1173 | watch: (0, _lodash2.default)(container, 'options.clapprStats.onCompletion', []), 1174 | calls: [] 1175 | }; 1176 | 1177 | _this._newMetrics(); 1178 | _this.on(ClapprStats.REPORT_EVENT, _this._onReport); 1179 | return _this; 1180 | } 1181 | 1182 | _createClass(ClapprStats, [{ 1183 | key: 'bindEvents', 1184 | value: function bindEvents() { 1185 | var _this2 = this; 1186 | 1187 | this.listenTo(this.container, _core.Events.CONTAINER_BITRATE, this.onBitrate); 1188 | this.listenTo(this.container, _core.Events.CONTAINER_STOP, this.stopReporting); 1189 | this.listenTo(this.container, _core.Events.CONTAINER_ENDED, this.stopReporting); 1190 | this.listenToOnce(this.container.playback, _core.Events.PLAYBACK_PLAY_INTENT, this.startTimers); 1191 | this.listenToOnce(this.container, _core.Events.CONTAINER_PLAY, this.onFirstPlaying); 1192 | this.listenTo(this.container, _core.Events.CONTAINER_PLAY, this.onPlay); 1193 | this.listenTo(this.container, _core.Events.CONTAINER_PAUSE, this.onPause); 1194 | this.listenToOnce(this.container, _core.Events.CONTAINER_STATE_BUFFERING, this.onBuffering); 1195 | this.listenTo(this.container, _core.Events.CONTAINER_SEEK, this.onSeek); 1196 | this.listenTo(this.container, _core.Events.CONTAINER_ERROR, function () { 1197 | return _this2._inc('error'); 1198 | }); 1199 | this.listenTo(this.container, _core.Events.CONTAINER_FULLSCREEN, function () { 1200 | return _this2._inc('fullscreen'); 1201 | }); 1202 | this.listenTo(this.container, _core.Events.CONTAINER_PLAYBACKDVRSTATECHANGED, function (dvrInUse) { 1203 | dvrInUse && _this2._inc('dvrUsage'); 1204 | }); 1205 | this.listenTo(this.container.playback, _core.Events.PLAYBACK_PROGRESS, this.onProgress); 1206 | this.listenTo(this.container.playback, _core.Events.PLAYBACK_TIMEUPDATE, this.onTimeUpdate); 1207 | } 1208 | }, { 1209 | key: 'destroy', 1210 | value: function destroy() { 1211 | this.stopReporting(); 1212 | _get(ClapprStats.prototype.__proto__ || Object.getPrototypeOf(ClapprStats.prototype), 'destroy', this).call(this); 1213 | } 1214 | }, { 1215 | key: 'onBitrate', 1216 | value: function onBitrate(newBitrate) { 1217 | var bitrate = parseInt((0, _lodash2.default)(newBitrate, 'bitrate', 0), 10); 1218 | var now = this._now(); 1219 | 1220 | if (this._metrics.extra.bitratesHistory.length > 0) { 1221 | var beforeLast = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length - 1]; 1222 | beforeLast.end = now; 1223 | beforeLast.time = now - beforeLast.start; 1224 | } 1225 | 1226 | this._metrics.extra.bitratesHistory.push({ start: this._now(), bitrate: bitrate }); 1227 | 1228 | this._inc('changeLevel'); 1229 | } 1230 | }, { 1231 | key: 'stopReporting', 1232 | value: function stopReporting() { 1233 | this._buildReport(); 1234 | 1235 | clearInterval(this._intervalId); 1236 | this._newMetrics(); 1237 | 1238 | this.stopListening(); 1239 | this.bindEvents(); 1240 | } 1241 | }, { 1242 | key: 'startTimers', 1243 | value: function startTimers() { 1244 | this._intervalId = setInterval(this._buildReport.bind(this), this._runEach); 1245 | this._start('session'); 1246 | this._start('startup'); 1247 | } 1248 | }, { 1249 | key: 'onFirstPlaying', 1250 | value: function onFirstPlaying() { 1251 | this.listenTo(this.container, _core.Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying); 1252 | 1253 | this._start('watch'); 1254 | this._stop('startup'); 1255 | } 1256 | }, { 1257 | key: 'playAfterPause', 1258 | value: function playAfterPause() { 1259 | this.listenTo(this.container, _core.Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying); 1260 | this._stop('pause'); 1261 | this._start('watch'); 1262 | } 1263 | }, { 1264 | key: 'onPlay', 1265 | value: function onPlay() { 1266 | this._inc('play'); 1267 | } 1268 | }, { 1269 | key: 'onPause', 1270 | value: function onPause() { 1271 | this._stop('watch'); 1272 | this._start('pause'); 1273 | this._inc('pause'); 1274 | this.listenToOnce(this.container, _core.Events.CONTAINER_PLAY, this.playAfterPause); 1275 | this.stopListening(this.container, _core.Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying); 1276 | } 1277 | }, { 1278 | key: 'onSeek', 1279 | value: function onSeek(e) { 1280 | this._inc('seek'); 1281 | this._metrics.extra.watchHistory.push([e * 1000, e * 1000]); 1282 | } 1283 | }, { 1284 | key: 'onTimeUpdate', 1285 | value: function onTimeUpdate(e) { 1286 | var current = e.current * 1000, 1287 | total = e.total * 1000, 1288 | l = this._metrics.extra.watchHistory.length; 1289 | 1290 | this._metrics.extra.duration = total; 1291 | this._metrics.extra.currentTime = current; 1292 | this._metrics.extra.watchedPercentage = current / total * 100; 1293 | 1294 | if (l === 0) { 1295 | this._metrics.extra.watchHistory.push([current, current]); 1296 | } else { 1297 | this._metrics.extra.watchHistory[l - 1][1] = current; 1298 | } 1299 | 1300 | if (this._metrics.extra.bitratesHistory.length > 0) { 1301 | var lastBitrate = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length - 1]; 1302 | if (!lastBitrate.end) { 1303 | lastBitrate.time = this._now() - lastBitrate.start; 1304 | } 1305 | } 1306 | 1307 | this._onCompletion(); 1308 | } 1309 | }, { 1310 | key: 'onContainerUpdateWhilePlaying', 1311 | value: function onContainerUpdateWhilePlaying() { 1312 | if (this.container.playback.isPlaying()) { 1313 | this._stop('watch'); 1314 | this._start('watch'); 1315 | } 1316 | } 1317 | }, { 1318 | key: 'onBuffering', 1319 | value: function onBuffering() { 1320 | this._inc('buffering'); 1321 | this._start('buffering'); 1322 | this.listenToOnce(this.container, _core.Events.CONTAINER_STATE_BUFFERFULL, this.onBufferfull); 1323 | } 1324 | }, { 1325 | key: 'onBufferfull', 1326 | value: function onBufferfull() { 1327 | this._stop('buffering'); 1328 | this.listenToOnce(this.container, _core.Events.CONTAINER_STATE_BUFFERING, this.onBuffering); 1329 | } 1330 | }, { 1331 | key: 'onProgress', 1332 | value: function onProgress(progress) { 1333 | this._metrics.extra.buffersize = progress.current * 1000; 1334 | } 1335 | }, { 1336 | key: '_newMetrics', 1337 | value: function _newMetrics() { 1338 | this._metrics = { 1339 | counters: { 1340 | play: 0, pause: 0, error: 0, buffering: 0, decodedFrames: 0, droppedFrames: 0, 1341 | fps: 0, changeLevel: 0, seek: 0, fullscreen: 0, dvrUsage: 0 1342 | }, 1343 | timers: { 1344 | startup: 0, watch: 0, pause: 0, buffering: 0, session: 0, latency: 0 1345 | }, 1346 | extra: { 1347 | playbackName: '', playbackType: '', bitratesHistory: [], bitrateWeightedMean: 0, 1348 | bitrateMostUsed: 0, buffersize: 0, watchHistory: [], watchedPercentage: 0, 1349 | bufferingPercentage: 0, bandwidth: 0, duration: 0, currentTime: 0 1350 | } 1351 | }; 1352 | } 1353 | }, { 1354 | key: '_onCompletion', 1355 | value: function _onCompletion() { 1356 | var currentPercentage = this._metrics.extra.watchedPercentage; 1357 | var allPercentages = this._completion.watch; 1358 | var isCalled = this._completion.calls.indexOf(currentPercentage) != -1; 1359 | 1360 | if (allPercentages.indexOf(currentPercentage) != -1 && !isCalled) { 1361 | _core.Log.info(this.name + ' PERCENTAGE_EVENT: ' + currentPercentage); 1362 | this._completion.calls.push(currentPercentage); 1363 | this.trigger(ClapprStats.PERCENTAGE_EVENT, currentPercentage); 1364 | } 1365 | } 1366 | }, { 1367 | key: '_buildReport', 1368 | value: function _buildReport() { 1369 | this._stop('session'); 1370 | this._start('session'); 1371 | 1372 | this._metrics.extra.playbackName = this._playbackName; 1373 | this._metrics.extra.playbackType = this._playbackType; 1374 | 1375 | this._calculateBitrates(); 1376 | this._calculatePercentages(); 1377 | this._fetchFPS(); 1378 | this._measureLatency(); 1379 | this._measureBandwidth(); 1380 | 1381 | this.trigger(ClapprStats.REPORT_EVENT, JSON.parse(JSON.stringify(this._metrics))); 1382 | } 1383 | }, { 1384 | key: '_fetchFPS', 1385 | value: function _fetchFPS() { 1386 | // flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds) 1387 | // hls ??? (use the same?) 1388 | var fetchFPS = { 1389 | 'html5_video': this._html5FetchFPS, 1390 | 'hls': this._html5FetchFPS, 1391 | 'dash_shaka_playback': this._html5FetchFPS 1392 | }; 1393 | 1394 | fetchFPS[this._playbackName] && fetchFPS[this._playbackName].call(this); 1395 | } 1396 | }, { 1397 | key: '_calculateBitrates', 1398 | value: function _calculateBitrates() { 1399 | var totalTime = this._metrics.extra.bitratesHistory.map(function (x) { 1400 | return x.time; 1401 | }).reduce(function (a, b) { 1402 | return a + b; 1403 | }, 0); 1404 | this._metrics.extra.bitrateWeightedMean = this._metrics.extra.bitratesHistory.map(function (x) { 1405 | return x.bitrate * x.time; 1406 | }).reduce(function (a, b) { 1407 | return a + b; 1408 | }, 0) / totalTime; 1409 | 1410 | if (this._metrics.extra.bitratesHistory.length > 0) { 1411 | this._metrics.extra.bitrateMostUsed = this._metrics.extra.bitratesHistory.slice().sort(function (a, b) { 1412 | return a.time < b.time; 1413 | })[0].bitrate; 1414 | } 1415 | } 1416 | }, { 1417 | key: '_calculatePercentages', 1418 | value: function _calculatePercentages() { 1419 | if (this._metrics.extra.duration > 0) { 1420 | this._metrics.extra.bufferingPercentage = this._metrics.timers.buffering / this._metrics.extra.duration * 100; 1421 | } 1422 | } 1423 | }, { 1424 | key: '_html5FetchFPS', 1425 | value: function _html5FetchFPS() { 1426 | var videoTag = this.container.playback.el; 1427 | var decodedFrames = videoTag.webkitDecodedFrameCount || videoTag.mozDecodedFrames || 0; 1428 | var droppedFrames = videoTag.webkitDroppedFrameCount || videoTag.mozParsedFrames - videoTag.mozDecodedFrames || 0; 1429 | var decodedFramesLastTime = decodedFrames - (this._lastDecodedFramesCount || 0); 1430 | 1431 | this._metrics.counters.decodedFrames = decodedFrames; 1432 | this._metrics.counters.droppedFrames = droppedFrames; 1433 | this._metrics.counters.fps = decodedFramesLastTime / (this._runEach / 1000); 1434 | 1435 | this._lastDecodedFramesCount = decodedFrames; 1436 | } 1437 | 1438 | // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/ 1439 | 1440 | }, { 1441 | key: '_measureLatency', 1442 | value: function _measureLatency() { 1443 | var _this3 = this; 1444 | 1445 | if (this._uriToMeasureLatency) { 1446 | var t = [], 1447 | n = 2, 1448 | rtt; 1449 | var ld = function ld() { 1450 | t.push(_this3._now()); 1451 | if (t.length > n) done();else { 1452 | var img = new Image(); 1453 | img.onload = ld; 1454 | img.src = _this3._uriToMeasureLatency + '?' + Math.random() + '=' + _this3._now(); 1455 | } 1456 | }; 1457 | var done = function done() { 1458 | rtt = t[2] - t[1]; 1459 | _this3._metrics.timers.latency = rtt; 1460 | }; 1461 | ld(); 1462 | } 1463 | } 1464 | 1465 | // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/ 1466 | 1467 | }, { 1468 | key: '_measureBandwidth', 1469 | value: function _measureBandwidth() { 1470 | var _this4 = this; 1471 | 1472 | if (this._urisToMeasureBandwidth && this._bwMeasureCount % this._runBandwidthTestEvery == 0) { 1473 | var i = 0; 1474 | 1475 | var ld = function ld(e) { 1476 | if (i > 0) { 1477 | _this4._urisToMeasureBandwidth[i - 1].end = _this4._now(); 1478 | clearTimeout(_this4._urisToMeasureBandwidth[i - 1].timer); 1479 | } 1480 | if (i >= _this4._urisToMeasureBandwidth.length || i > 0 && _this4._urisToMeasureBandwidth[i - 1].expired) done(e);else { 1481 | var xhr = new XMLHttpRequest(); 1482 | xhr.open('GET', _this4._urisToMeasureBandwidth[i].url, true); 1483 | xhr.responseType = 'arraybuffer'; 1484 | xhr.onload = xhr.onabort = ld; 1485 | _this4._urisToMeasureBandwidth[i].start = _this4._now(); 1486 | _this4._urisToMeasureBandwidth[i].timer = setTimeout(function (j) { 1487 | _this4._urisToMeasureBandwidth[j].expired = true; 1488 | xhr.abort(); 1489 | }, _this4._urisToMeasureBandwidth[i].timeout, i); 1490 | xhr.send(); 1491 | } 1492 | i++; 1493 | }; 1494 | 1495 | var done = function done(e) { 1496 | var timeSpent = (_this4._urisToMeasureBandwidth[i - 1].end - _this4._urisToMeasureBandwidth[i - 1].start) / 1000; 1497 | var bandwidthBps = e.loaded * 8 / timeSpent; 1498 | _this4._metrics.extra.bandwidth = bandwidthBps; 1499 | _this4._urisToMeasureBandwidth.forEach(function (x) { 1500 | x.start = 0; 1501 | x.end = 0; 1502 | x.expired = false; 1503 | clearTimeout(x.timer); 1504 | }); 1505 | }; 1506 | 1507 | ld(); 1508 | } 1509 | this._bwMeasureCount++; 1510 | } 1511 | }]); 1512 | 1513 | return ClapprStats; 1514 | }(_core.ContainerPlugin); 1515 | 1516 | exports.default = ClapprStats; 1517 | 1518 | 1519 | ClapprStats.REPORT_EVENT = 'clappr:stats:report'; 1520 | ClapprStats.PERCENTAGE_EVENT = 'clappr:stats:percentage'; 1521 | module.exports = exports['default']; 1522 | 1523 | /***/ }), 1524 | 1525 | /***/ "@clappr/core": 1526 | /*!*************************!*\ 1527 | !*** external "Clappr" ***! 1528 | \*************************/ 1529 | /*! no static exports found */ 1530 | /***/ (function(module, exports) { 1531 | 1532 | module.exports = __WEBPACK_EXTERNAL_MODULE__clappr_core__; 1533 | 1534 | /***/ }) 1535 | 1536 | /******/ }); 1537 | }); 1538 | //# sourceMappingURL=clappr-stats.js.map -------------------------------------------------------------------------------- /dist/clappr-stats.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://ClapprStats/webpack/universalModuleDefinition","webpack://ClapprStats/webpack/bootstrap","webpack://ClapprStats/./node_modules/lodash.get/index.js","webpack://ClapprStats/(webpack)/buildin/global.js","webpack://ClapprStats/./src/clappr-stats.js","webpack://ClapprStats/external \"Clappr\""],"names":["FUNC_ERROR_TEXT","HASH_UNDEFINED","INFINITY","funcTag","genTag","symbolTag","reIsDeepProp","reIsPlainProp","reLeadingDot","rePropName","reRegExpChar","reEscapeChar","reIsHostCtor","freeGlobal","global","Object","freeSelf","self","root","Function","getValue","object","key","undefined","isHostObject","value","result","toString","e","arrayProto","Array","prototype","funcProto","objectProto","coreJsData","maskSrcKey","uid","exec","keys","IE_PROTO","funcToString","hasOwnProperty","objectToString","reIsNative","RegExp","call","replace","Symbol","splice","Map","getNative","nativeCreate","symbolProto","symbolToString","Hash","entries","index","length","clear","entry","set","hashClear","__data__","hashDelete","has","hashGet","data","hashHas","hashSet","get","ListCache","listCacheClear","listCacheDelete","assocIndexOf","lastIndex","pop","listCacheGet","listCacheHas","listCacheSet","push","MapCache","mapCacheClear","mapCacheDelete","getMapData","mapCacheGet","mapCacheHas","mapCacheSet","array","eq","baseGet","path","isKey","castPath","toKey","baseIsNative","isObject","isMasked","pattern","isFunction","test","toSource","baseToString","isSymbol","isArray","stringToPath","map","isKeyable","type","func","memoize","string","match","number","quote","resolver","TypeError","memoized","args","arguments","apply","cache","Cache","other","tag","isObjectLike","defaultValue","module","exports","g","window","ClapprStats","hasPerformanceSupport","performance","now","Date","counter","_metrics","counters","timer","_now","timers","metrics","console","log","min","container","playback","name","getPlaybackType","_runEach","_onReport","_defaultReport","_uriToMeasureLatency","_urisToMeasureBandwidth","_runBandwidthTestEvery","_bwMeasureCount","_completion","watch","calls","_newMetrics","on","REPORT_EVENT","listenTo","CONTAINER_BITRATE","onBitrate","CONTAINER_STOP","stopReporting","CONTAINER_ENDED","listenToOnce","PLAYBACK_PLAY_INTENT","startTimers","CONTAINER_PLAY","onFirstPlaying","onPlay","CONTAINER_PAUSE","onPause","CONTAINER_STATE_BUFFERING","onBuffering","CONTAINER_SEEK","onSeek","CONTAINER_ERROR","_inc","CONTAINER_FULLSCREEN","CONTAINER_PLAYBACKDVRSTATECHANGED","dvrInUse","PLAYBACK_PROGRESS","onProgress","PLAYBACK_TIMEUPDATE","onTimeUpdate","newBitrate","bitrate","parseInt","extra","bitratesHistory","beforeLast","end","time","start","_buildReport","clearInterval","_intervalId","stopListening","bindEvents","setInterval","bind","_start","CONTAINER_TIMEUPDATE","onContainerUpdateWhilePlaying","_stop","playAfterPause","watchHistory","current","total","l","duration","currentTime","watchedPercentage","lastBitrate","_onCompletion","isPlaying","CONTAINER_STATE_BUFFERFULL","onBufferfull","progress","buffersize","play","pause","error","buffering","decodedFrames","droppedFrames","fps","changeLevel","seek","fullscreen","dvrUsage","startup","session","latency","playbackName","playbackType","bitrateWeightedMean","bitrateMostUsed","bufferingPercentage","bandwidth","currentPercentage","allPercentages","isCalled","indexOf","info","trigger","PERCENTAGE_EVENT","_playbackName","_playbackType","_calculateBitrates","_calculatePercentages","_fetchFPS","_measureLatency","_measureBandwidth","JSON","parse","stringify","fetchFPS","_html5FetchFPS","totalTime","x","reduce","a","b","slice","sort","videoTag","el","webkitDecodedFrameCount","mozDecodedFrames","webkitDroppedFrameCount","mozParsedFrames","decodedFramesLastTime","_lastDecodedFramesCount","t","n","rtt","ld","done","img","Image","onload","src","Math","random","i","clearTimeout","expired","xhr","XMLHttpRequest","open","url","responseType","onabort","setTimeout","j","abort","timeout","send","timeSpent","bandwidthBps","loaded","forEach"],"mappings":"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","file":"clappr-stats.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"Clappr\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"Clappr\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClapprStats\"] = factory(require(\"Clappr\"));\n\telse\n\t\troot[\"ClapprStats\"] = factory(root[\"Clappr\"]);\n})(window, function(__WEBPACK_EXTERNAL_MODULE__clappr_core__) {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"latest/\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./src/clappr-stats.js\");\n","/**\n * lodash (Custom Build) \n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright jQuery Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n\n/** Used as the `TypeError` message for \"Functions\" methods. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/** Used to stand-in for `undefined` hash values. */\nvar HASH_UNDEFINED = '__lodash_hash_undefined__';\n\n/** Used as references for various `Number` constants. */\nvar INFINITY = 1 / 0;\n\n/** `Object#toString` result references. */\nvar funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n symbolTag = '[object Symbol]';\n\n/** Used to match property names within property paths. */\nvar reIsDeepProp = /\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,\n reIsPlainProp = /^\\w*$/,\n reLeadingDot = /^\\./,\n rePropName = /[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g;\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to match backslashes in property paths. */\nvar reEscapeChar = /\\\\(\\\\)?/g;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\n/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\n/**\n * Checks if `value` is a host object in IE < 9.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a host object, else `false`.\n */\nfunction isHostObject(value) {\n // Many host objects are `Object` objects that can coerce to strings\n // despite having improperly defined `toString` methods.\n var result = false;\n if (value != null && typeof value.toString != 'function') {\n try {\n result = !!(value + '');\n } catch (e) {}\n }\n return result;\n}\n\n/** Used for built-in method references. */\nvar arrayProto = Array.prototype,\n funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objectToString = objectProto.toString;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/** Built-in value references. */\nvar Symbol = root.Symbol,\n splice = arrayProto.splice;\n\n/* Built-in method references that are verified to be native. */\nvar Map = getNative(root, 'Map'),\n nativeCreate = getNative(Object, 'create');\n\n/** Used to convert symbols to primitives and strings. */\nvar symbolProto = Symbol ? Symbol.prototype : undefined,\n symbolToString = symbolProto ? symbolProto.toString : undefined;\n\n/**\n * Creates a hash object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Hash(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the hash.\n *\n * @private\n * @name clear\n * @memberOf Hash\n */\nfunction hashClear() {\n this.__data__ = nativeCreate ? nativeCreate(null) : {};\n}\n\n/**\n * Removes `key` and its value from the hash.\n *\n * @private\n * @name delete\n * @memberOf Hash\n * @param {Object} hash The hash to modify.\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction hashDelete(key) {\n return this.has(key) && delete this.__data__[key];\n}\n\n/**\n * Gets the hash value for `key`.\n *\n * @private\n * @name get\n * @memberOf Hash\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction hashGet(key) {\n var data = this.__data__;\n if (nativeCreate) {\n var result = data[key];\n return result === HASH_UNDEFINED ? undefined : result;\n }\n return hasOwnProperty.call(data, key) ? data[key] : undefined;\n}\n\n/**\n * Checks if a hash value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Hash\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction hashHas(key) {\n var data = this.__data__;\n return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);\n}\n\n/**\n * Sets the hash `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Hash\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the hash instance.\n */\nfunction hashSet(key, value) {\n var data = this.__data__;\n data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;\n return this;\n}\n\n// Add methods to `Hash`.\nHash.prototype.clear = hashClear;\nHash.prototype['delete'] = hashDelete;\nHash.prototype.get = hashGet;\nHash.prototype.has = hashHas;\nHash.prototype.set = hashSet;\n\n/**\n * Creates an list cache object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction ListCache(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the list cache.\n *\n * @private\n * @name clear\n * @memberOf ListCache\n */\nfunction listCacheClear() {\n this.__data__ = [];\n}\n\n/**\n * Removes `key` and its value from the list cache.\n *\n * @private\n * @name delete\n * @memberOf ListCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction listCacheDelete(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n return false;\n }\n var lastIndex = data.length - 1;\n if (index == lastIndex) {\n data.pop();\n } else {\n splice.call(data, index, 1);\n }\n return true;\n}\n\n/**\n * Gets the list cache value for `key`.\n *\n * @private\n * @name get\n * @memberOf ListCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction listCacheGet(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n return index < 0 ? undefined : data[index][1];\n}\n\n/**\n * Checks if a list cache value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf ListCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction listCacheHas(key) {\n return assocIndexOf(this.__data__, key) > -1;\n}\n\n/**\n * Sets the list cache `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf ListCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the list cache instance.\n */\nfunction listCacheSet(key, value) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n data.push([key, value]);\n } else {\n data[index][1] = value;\n }\n return this;\n}\n\n// Add methods to `ListCache`.\nListCache.prototype.clear = listCacheClear;\nListCache.prototype['delete'] = listCacheDelete;\nListCache.prototype.get = listCacheGet;\nListCache.prototype.has = listCacheHas;\nListCache.prototype.set = listCacheSet;\n\n/**\n * Creates a map cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction MapCache(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the map.\n *\n * @private\n * @name clear\n * @memberOf MapCache\n */\nfunction mapCacheClear() {\n this.__data__ = {\n 'hash': new Hash,\n 'map': new (Map || ListCache),\n 'string': new Hash\n };\n}\n\n/**\n * Removes `key` and its value from the map.\n *\n * @private\n * @name delete\n * @memberOf MapCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction mapCacheDelete(key) {\n return getMapData(this, key)['delete'](key);\n}\n\n/**\n * Gets the map value for `key`.\n *\n * @private\n * @name get\n * @memberOf MapCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction mapCacheGet(key) {\n return getMapData(this, key).get(key);\n}\n\n/**\n * Checks if a map value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf MapCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction mapCacheHas(key) {\n return getMapData(this, key).has(key);\n}\n\n/**\n * Sets the map `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf MapCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the map cache instance.\n */\nfunction mapCacheSet(key, value) {\n getMapData(this, key).set(key, value);\n return this;\n}\n\n// Add methods to `MapCache`.\nMapCache.prototype.clear = mapCacheClear;\nMapCache.prototype['delete'] = mapCacheDelete;\nMapCache.prototype.get = mapCacheGet;\nMapCache.prototype.has = mapCacheHas;\nMapCache.prototype.set = mapCacheSet;\n\n/**\n * Gets the index at which the `key` is found in `array` of key-value pairs.\n *\n * @private\n * @param {Array} array The array to inspect.\n * @param {*} key The key to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction assocIndexOf(array, key) {\n var length = array.length;\n while (length--) {\n if (eq(array[length][0], key)) {\n return length;\n }\n }\n return -1;\n}\n\n/**\n * The base implementation of `_.get` without support for default values.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Array|string} path The path of the property to get.\n * @returns {*} Returns the resolved value.\n */\nfunction baseGet(object, path) {\n path = isKey(path, object) ? [path] : castPath(path);\n\n var index = 0,\n length = path.length;\n\n while (object != null && index < length) {\n object = object[toKey(path[index++])];\n }\n return (index && index == length) ? object : undefined;\n}\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\n/**\n * The base implementation of `_.toString` which doesn't convert nullish\n * values to empty strings.\n *\n * @private\n * @param {*} value The value to process.\n * @returns {string} Returns the string.\n */\nfunction baseToString(value) {\n // Exit early for strings to avoid a performance hit in some environments.\n if (typeof value == 'string') {\n return value;\n }\n if (isSymbol(value)) {\n return symbolToString ? symbolToString.call(value) : '';\n }\n var result = (value + '');\n return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;\n}\n\n/**\n * Casts `value` to a path array if it's not one.\n *\n * @private\n * @param {*} value The value to inspect.\n * @returns {Array} Returns the cast property path array.\n */\nfunction castPath(value) {\n return isArray(value) ? value : stringToPath(value);\n}\n\n/**\n * Gets the data for `map`.\n *\n * @private\n * @param {Object} map The map to query.\n * @param {string} key The reference key.\n * @returns {*} Returns the map data.\n */\nfunction getMapData(map, key) {\n var data = map.__data__;\n return isKeyable(key)\n ? data[typeof key == 'string' ? 'string' : 'hash']\n : data.map;\n}\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\n/**\n * Checks if `value` is a property name and not a property path.\n *\n * @private\n * @param {*} value The value to check.\n * @param {Object} [object] The object to query keys on.\n * @returns {boolean} Returns `true` if `value` is a property name, else `false`.\n */\nfunction isKey(value, object) {\n if (isArray(value)) {\n return false;\n }\n var type = typeof value;\n if (type == 'number' || type == 'symbol' || type == 'boolean' ||\n value == null || isSymbol(value)) {\n return true;\n }\n return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||\n (object != null && value in Object(object));\n}\n\n/**\n * Checks if `value` is suitable for use as unique object key.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is suitable, else `false`.\n */\nfunction isKeyable(value) {\n var type = typeof value;\n return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')\n ? (value !== '__proto__')\n : (value === null);\n}\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\n/**\n * Converts `string` to a property path array.\n *\n * @private\n * @param {string} string The string to convert.\n * @returns {Array} Returns the property path array.\n */\nvar stringToPath = memoize(function(string) {\n string = toString(string);\n\n var result = [];\n if (reLeadingDot.test(string)) {\n result.push('');\n }\n string.replace(rePropName, function(match, number, quote, string) {\n result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));\n });\n return result;\n});\n\n/**\n * Converts `value` to a string key if it's not a string or symbol.\n *\n * @private\n * @param {*} value The value to inspect.\n * @returns {string|symbol} Returns the key.\n */\nfunction toKey(value) {\n if (typeof value == 'string' || isSymbol(value)) {\n return value;\n }\n var result = (value + '');\n return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;\n}\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to process.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\n/**\n * Creates a function that memoizes the result of `func`. If `resolver` is\n * provided, it determines the cache key for storing the result based on the\n * arguments provided to the memoized function. By default, the first argument\n * provided to the memoized function is used as the map cache key. The `func`\n * is invoked with the `this` binding of the memoized function.\n *\n * **Note:** The cache is exposed as the `cache` property on the memoized\n * function. Its creation may be customized by replacing the `_.memoize.Cache`\n * constructor with one whose instances implement the\n * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)\n * method interface of `delete`, `get`, `has`, and `set`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Function\n * @param {Function} func The function to have its output memoized.\n * @param {Function} [resolver] The function to resolve the cache key.\n * @returns {Function} Returns the new memoized function.\n * @example\n *\n * var object = { 'a': 1, 'b': 2 };\n * var other = { 'c': 3, 'd': 4 };\n *\n * var values = _.memoize(_.values);\n * values(object);\n * // => [1, 2]\n *\n * values(other);\n * // => [3, 4]\n *\n * object.a = 2;\n * values(object);\n * // => [1, 2]\n *\n * // Modify the result cache.\n * values.cache.set(object, ['a', 'b']);\n * values(object);\n * // => ['a', 'b']\n *\n * // Replace `_.memoize.Cache`.\n * _.memoize.Cache = WeakMap;\n */\nfunction memoize(func, resolver) {\n if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {\n throw new TypeError(FUNC_ERROR_TEXT);\n }\n var memoized = function() {\n var args = arguments,\n key = resolver ? resolver.apply(this, args) : args[0],\n cache = memoized.cache;\n\n if (cache.has(key)) {\n return cache.get(key);\n }\n var result = func.apply(this, args);\n memoized.cache = cache.set(key, result);\n return result;\n };\n memoized.cache = new (memoize.Cache || MapCache);\n return memoized;\n}\n\n// Assign cache to `_.memoize`.\nmemoize.Cache = MapCache;\n\n/**\n * Performs a\n * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\nfunction eq(value, other) {\n return value === other || (value !== value && other !== other);\n}\n\n/**\n * Checks if `value` is classified as an `Array` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\n * @example\n *\n * _.isArray([1, 2, 3]);\n * // => true\n *\n * _.isArray(document.body.children);\n * // => false\n *\n * _.isArray('abc');\n * // => false\n *\n * _.isArray(_.noop);\n * // => false\n */\nvar isArray = Array.isArray;\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 8-9 which returns 'object' for typed array and other constructors.\n var tag = isObject(value) ? objectToString.call(value) : '';\n return tag == funcTag || tag == genTag;\n}\n\n/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return !!value && (type == 'object' || type == 'function');\n}\n\n/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return !!value && typeof value == 'object';\n}\n\n/**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\nfunction isSymbol(value) {\n return typeof value == 'symbol' ||\n (isObjectLike(value) && objectToString.call(value) == symbolTag);\n}\n\n/**\n * Converts `value` to a string. An empty string is returned for `null`\n * and `undefined` values. The sign of `-0` is preserved.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to process.\n * @returns {string} Returns the string.\n * @example\n *\n * _.toString(null);\n * // => ''\n *\n * _.toString(-0);\n * // => '-0'\n *\n * _.toString([1, 2, 3]);\n * // => '1,2,3'\n */\nfunction toString(value) {\n return value == null ? '' : baseToString(value);\n}\n\n/**\n * Gets the value at `path` of `object`. If the resolved value is\n * `undefined`, the `defaultValue` is returned in its place.\n *\n * @static\n * @memberOf _\n * @since 3.7.0\n * @category Object\n * @param {Object} object The object to query.\n * @param {Array|string} path The path of the property to get.\n * @param {*} [defaultValue] The value returned for `undefined` resolved values.\n * @returns {*} Returns the resolved value.\n * @example\n *\n * var object = { 'a': [{ 'b': { 'c': 3 } }] };\n *\n * _.get(object, 'a[0].b.c');\n * // => 3\n *\n * _.get(object, ['a', '0', 'b', 'c']);\n * // => 3\n *\n * _.get(object, 'a.b.c', 'default');\n * // => 'default'\n */\nfunction get(object, path, defaultValue) {\n var result = object == null ? undefined : baseGet(object, path);\n return result === undefined ? defaultValue : result;\n}\n\nmodule.exports = get;\n","var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn this;\n})();\n\ntry {\n\t// This works if eval is allowed (see CSP)\n\tg = g || new Function(\"return this\")();\n} catch (e) {\n\t// This works if the window reference is available\n\tif (typeof window === \"object\") g = window;\n}\n\n// g can still be undefined, but nothing to do about it...\n// We return undefined, instead of nothing here, so it's\n// easier to handle this case. if(!global) { ...}\n\nmodule.exports = g;\n","import {ContainerPlugin, Events, Log} from '@clappr/core'\nimport get from 'lodash.get'\n\n\nexport default class ClapprStats extends ContainerPlugin {\n get name() { return 'clappr_stats' }\n get supportedVersion() { return { min: '0.4.2' } }\n\n get _playbackName() {return this.container.playback.name}\n get _playbackType() {return this.container.getPlaybackType()}\n _now() {\n const hasPerformanceSupport = window.performance && typeof(window.performance.now) === 'function'\n return (hasPerformanceSupport)?window.performance.now():new Date()\n }\n _inc(counter) {this._metrics.counters[counter] += 1}\n _timerHasStarted(timer){return this[`_start${timer}`] !== undefined}\n _start(timer) {this[`_start${timer}`] = this._now()}\n _stop(timer) {this._metrics.timers[timer] += this._now() - this[`_start${timer}`]}\n _defaultReport(metrics) {console.log(metrics)} //eslint-disable-line no-console\n\n constructor(container) {\n super(container)\n this._runEach = get(container, 'options.clapprStats.runEach', 5000)\n this._onReport = get(container, 'options.clapprStats.onReport', this._defaultReport)\n this._uriToMeasureLatency = get(container, 'options.clapprStats.uriToMeasureLatency')\n this._urisToMeasureBandwidth = get(container, 'options.clapprStats.urisToMeasureBandwidth')\n this._runBandwidthTestEvery = get(container, 'options.clapprStats.runBandwidthTestEvery', 10)\n this._bwMeasureCount = 0\n\n this._completion = {\n watch: get(container, 'options.clapprStats.onCompletion', []),\n calls: []\n }\n\n this._newMetrics()\n this.on(ClapprStats.REPORT_EVENT, this._onReport)\n }\n\n bindEvents() {\n this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onBitrate)\n this.listenTo(this.container, Events.CONTAINER_STOP, this.stopReporting)\n this.listenTo(this.container, Events.CONTAINER_ENDED, this.stopReporting)\n this.listenToOnce(this.container.playback, Events.PLAYBACK_PLAY_INTENT, this.startTimers)\n this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.onFirstPlaying)\n this.listenTo(this.container, Events.CONTAINER_PLAY, this.onPlay)\n this.listenTo(this.container, Events.CONTAINER_PAUSE, this.onPause)\n this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering)\n this.listenTo(this.container, Events.CONTAINER_SEEK, this.onSeek)\n this.listenTo(this.container, Events.CONTAINER_ERROR, () => this._inc('error'))\n this.listenTo(this.container, Events.CONTAINER_FULLSCREEN, () => this._inc('fullscreen'))\n this.listenTo(this.container, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, (dvrInUse) => {dvrInUse && this._inc('dvrUsage')})\n this.listenTo(this.container.playback, Events.PLAYBACK_PROGRESS, this.onProgress)\n this.listenTo(this.container.playback, Events.PLAYBACK_TIMEUPDATE, this.onTimeUpdate)\n }\n\n destroy() {\n this.stopReporting()\n super.destroy()\n }\n\n onBitrate(newBitrate) {\n var bitrate = parseInt(get(newBitrate, 'bitrate', 0), 10)\n var now = this._now()\n\n if (this._metrics.extra.bitratesHistory.length > 0) {\n var beforeLast = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length-1]\n beforeLast.end = now\n beforeLast.time = now - beforeLast.start\n }\n\n this._metrics.extra.bitratesHistory.push({start: this._now(), bitrate: bitrate})\n\n this._inc('changeLevel')\n }\n\n stopReporting() {\n this._buildReport()\n\n clearInterval(this._intervalId)\n this._newMetrics()\n\n this.stopListening()\n this.bindEvents()\n }\n\n startTimers() {\n this._intervalId = setInterval(this._buildReport.bind(this), this._runEach)\n this._start('session')\n this._start('startup')\n }\n\n onFirstPlaying() {\n this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying)\n\n this._start('watch')\n this._stop('startup')\n }\n\n playAfterPause() {\n this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying)\n this._stop('pause')\n this._start('watch')\n }\n\n onPlay() {\n this._inc('play')\n }\n\n onPause() {\n this._stop('watch')\n this._start('pause')\n this._inc('pause')\n this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.playAfterPause)\n this.stopListening(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying)\n }\n\n onSeek(e) {\n this._inc('seek')\n this._metrics.extra.watchHistory.push([e * 1000, e * 1000])\n }\n\n onTimeUpdate(e) {\n var current = e.current * 1000,\n total = e.total * 1000,\n l = this._metrics.extra.watchHistory.length\n\n this._metrics.extra.duration = total\n this._metrics.extra.currentTime = current\n this._metrics.extra.watchedPercentage = (current / total) * 100\n\n if (l === 0) {\n this._metrics.extra.watchHistory.push([current, current])\n } else {\n this._metrics.extra.watchHistory[l-1][1] = current\n }\n\n if (this._metrics.extra.bitratesHistory.length > 0) {\n var lastBitrate = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length-1]\n if (!lastBitrate.end) {\n lastBitrate.time = this._now() - lastBitrate.start\n }\n }\n\n this._onCompletion()\n }\n\n onContainerUpdateWhilePlaying() {\n if (this.container.playback.isPlaying()) {\n this._stop('watch')\n this._start('watch')\n }\n }\n\n onBuffering() {\n this._inc('buffering')\n this._start('buffering')\n this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferfull)\n }\n\n onBufferfull() {\n this._stop('buffering')\n this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering)\n }\n\n onProgress(progress) {\n this._metrics.extra.buffersize = progress.current * 1000\n }\n\n _newMetrics() {\n this._metrics = {\n counters: {\n play: 0, pause: 0, error: 0, buffering: 0, decodedFrames: 0, droppedFrames: 0,\n fps: 0, changeLevel: 0, seek: 0, fullscreen: 0, dvrUsage: 0\n },\n timers: {\n startup: 0, watch: 0, pause: 0, buffering: 0, session: 0, latency: 0\n },\n extra: {\n playbackName: '', playbackType: '', bitratesHistory: [], bitrateWeightedMean: 0,\n bitrateMostUsed: 0, buffersize: 0, watchHistory: [], watchedPercentage: 0,\n bufferingPercentage: 0, bandwidth: 0, duration: 0, currentTime: 0\n }\n }\n }\n\n _onCompletion() {\n let currentPercentage = this._metrics.extra.watchedPercentage\n let allPercentages = this._completion.watch\n let isCalled = this._completion.calls.indexOf(currentPercentage) != -1\n\n if (allPercentages.indexOf(currentPercentage) != -1 && !isCalled) {\n Log.info(this.name + ' PERCENTAGE_EVENT: ' + currentPercentage)\n this._completion.calls.push(currentPercentage)\n this.trigger(ClapprStats.PERCENTAGE_EVENT, currentPercentage)\n }\n }\n\n _buildReport() {\n this._stop('session')\n this._start('session')\n\n this._metrics.extra.playbackName = this._playbackName\n this._metrics.extra.playbackType = this._playbackType\n\n this._calculateBitrates()\n this._calculatePercentages()\n this._fetchFPS()\n this._measureLatency()\n this._measureBandwidth()\n\n this.trigger(ClapprStats.REPORT_EVENT, JSON.parse(JSON.stringify(this._metrics)))\n }\n\n _fetchFPS() {\n // flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds)\n // hls ??? (use the same?)\n const fetchFPS = {\n 'html5_video': this._html5FetchFPS,\n 'hls': this._html5FetchFPS,\n 'dash_shaka_playback': this._html5FetchFPS\n }\n\n fetchFPS[this._playbackName] && fetchFPS[this._playbackName].call(this)\n }\n\n _calculateBitrates() {\n var totalTime = this._metrics.extra.bitratesHistory.map((x) => x.time).reduce((a,b) => a + b, 0)\n this._metrics.extra.bitrateWeightedMean = this._metrics.extra.bitratesHistory.map((x) => {\n return x.bitrate * x.time\n }).reduce((a,b) => a + b, 0) / totalTime\n\n if (this._metrics.extra.bitratesHistory.length > 0) {\n this._metrics.extra.bitrateMostUsed = this._metrics.extra.bitratesHistory.slice().sort((a,b) => a.time < b.time)[0].bitrate\n }\n }\n\n _calculatePercentages() {\n if (this._metrics.extra.duration > 0) {\n this._metrics.extra.bufferingPercentage = (this._metrics.timers.buffering / this._metrics.extra.duration) * 100\n }\n }\n\n _html5FetchFPS() {\n const videoTag = this.container.playback.el\n const decodedFrames = videoTag.webkitDecodedFrameCount || videoTag.mozDecodedFrames || 0\n const droppedFrames = (videoTag.webkitDroppedFrameCount || (videoTag.mozParsedFrames - videoTag.mozDecodedFrames)) || 0\n const decodedFramesLastTime = decodedFrames - (this._lastDecodedFramesCount || 0)\n\n this._metrics.counters.decodedFrames = decodedFrames\n this._metrics.counters.droppedFrames = droppedFrames\n this._metrics.counters.fps = decodedFramesLastTime / (this._runEach / 1000)\n\n this._lastDecodedFramesCount = decodedFrames\n }\n\n // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/\n _measureLatency() {\n if (this._uriToMeasureLatency) {\n var t=[], n=2, rtt\n var ld = () => {\n t.push(this._now())\n if(t.length > n)\n done()\n else {\n var img = new Image\n img.onload = ld\n img.src=this._uriToMeasureLatency + '?' + Math.random()\n + '=' + this._now()\n }\n }\n var done = () => {\n rtt=t[2]-t[1]\n this._metrics.timers.latency = rtt\n }\n ld()\n }\n }\n\n // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/\n _measureBandwidth() {\n if (this._urisToMeasureBandwidth && (this._bwMeasureCount % this._runBandwidthTestEvery == 0)) {\n var i = 0\n\n var ld = (e) => {\n if (i > 0) {\n this._urisToMeasureBandwidth[i-1].end = this._now()\n clearTimeout(this._urisToMeasureBandwidth[i-1].timer)\n }\n if (i >= this._urisToMeasureBandwidth.length || (i > 0 && this._urisToMeasureBandwidth[i-1].expired))\n done(e)\n else {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', this._urisToMeasureBandwidth[i].url, true)\n xhr.responseType = 'arraybuffer'\n xhr.onload = xhr.onabort = ld\n this._urisToMeasureBandwidth[i].start = this._now()\n this._urisToMeasureBandwidth[i].timer = setTimeout((j) => {\n this._urisToMeasureBandwidth[j].expired = true\n xhr.abort()\n }, this._urisToMeasureBandwidth[i].timeout, i)\n xhr.send()\n }\n i++\n }\n\n var done = (e) => {\n var timeSpent = (this._urisToMeasureBandwidth[i-1].end - this._urisToMeasureBandwidth[i-1].start) / 1000\n var bandwidthBps = (e.loaded * 8) / timeSpent\n this._metrics.extra.bandwidth = bandwidthBps\n this._urisToMeasureBandwidth.forEach((x) => {\n x.start = 0\n x.end = 0\n x.expired = false\n clearTimeout(x.timer)\n })\n }\n\n ld()\n }\n this._bwMeasureCount++\n }\n}\n\nClapprStats.REPORT_EVENT = 'clappr:stats:report'\nClapprStats.PERCENTAGE_EVENT = 'clappr:stats:percentage'\n","module.exports = __WEBPACK_EXTERNAL_MODULE__clappr_core__;"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/clappr-stats.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("Clappr")):"function"==typeof define&&define.amd?define(["Clappr"],e):"object"==typeof exports?exports.ClapprStats=e(require("Clappr")):t.ClapprStats=e(t.Clappr)}(window,function(n){return o={},i.m=r=[function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(t,e,n){return e&&i(t.prototype,e),n&&i(t,n),t};function i(t,e){for(var n=0;n=r._urisToMeasureBandwidth.length||0 2 | 3 | 4 | 5 | clappr 6 | 7 | 8 |
9 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/clappr-stats.js: -------------------------------------------------------------------------------- 1 | import { ContainerPlugin, Events, Log, version } from '@clappr/core' 2 | import get from 'lodash.get' 3 | 4 | 5 | export default class ClapprStats extends ContainerPlugin { 6 | get name() { return 'clappr_stats' } 7 | get supportedVersion() { return { min: version } } 8 | 9 | get _playbackName() {return this.container.playback.name} 10 | get _playbackType() {return this.container.getPlaybackType()} 11 | _now() { 12 | const hasPerformanceSupport = window.performance && typeof(window.performance.now) === 'function' 13 | return (hasPerformanceSupport)?window.performance.now():new Date() 14 | } 15 | _inc(counter) {this._metrics.counters[counter] += 1} 16 | _timerHasStarted(timer){return this[`_start${timer}`] !== undefined} 17 | _start(timer) {this[`_start${timer}`] = this._now()} 18 | _stop(timer) {this._metrics.timers[timer] += this._now() - this[`_start${timer}`]} 19 | _defaultReport(metrics) {console.log(metrics)} //eslint-disable-line no-console 20 | 21 | constructor(container) { 22 | super(container) 23 | this._runEach = get(container, 'options.clapprStats.runEach', 5000) 24 | this._onReport = get(container, 'options.clapprStats.onReport', this._defaultReport) 25 | this._uriToMeasureLatency = get(container, 'options.clapprStats.uriToMeasureLatency') 26 | this._urisToMeasureBandwidth = get(container, 'options.clapprStats.urisToMeasureBandwidth') 27 | this._runBandwidthTestEvery = get(container, 'options.clapprStats.runBandwidthTestEvery', 10) 28 | this._bwMeasureCount = 0 29 | 30 | this._completion = { 31 | watch: get(container, 'options.clapprStats.onCompletion', []), 32 | calls: [] 33 | } 34 | 35 | this._newMetrics() 36 | this.on(ClapprStats.REPORT_EVENT, this._onReport) 37 | } 38 | 39 | bindEvents() { 40 | this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onBitrate) 41 | this.listenTo(this.container, Events.CONTAINER_STOP, this.stopReporting) 42 | this.listenTo(this.container, Events.CONTAINER_ENDED, this.stopReporting) 43 | this.listenToOnce(this.container.playback, Events.PLAYBACK_PLAY_INTENT, this.startTimers) 44 | this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.onFirstPlaying) 45 | this.listenTo(this.container, Events.CONTAINER_PLAY, this.onPlay) 46 | this.listenTo(this.container, Events.CONTAINER_PAUSE, this.onPause) 47 | this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering) 48 | this.listenTo(this.container, Events.CONTAINER_SEEK, this.onSeek) 49 | this.listenTo(this.container, Events.CONTAINER_ERROR, () => this._inc('error')) 50 | this.listenTo(this.container, Events.CONTAINER_FULLSCREEN, () => this._inc('fullscreen')) 51 | this.listenTo(this.container, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, (dvrInUse) => {dvrInUse && this._inc('dvrUsage')}) 52 | this.listenTo(this.container.playback, Events.PLAYBACK_PROGRESS, this.onProgress) 53 | this.listenTo(this.container.playback, Events.PLAYBACK_TIMEUPDATE, this.onTimeUpdate) 54 | } 55 | 56 | destroy() { 57 | this.stopReporting() 58 | super.destroy() 59 | } 60 | 61 | onBitrate(newBitrate) { 62 | var bitrate = parseInt(get(newBitrate, 'bitrate', 0), 10) 63 | var now = this._now() 64 | 65 | if (this._metrics.extra.bitratesHistory.length > 0) { 66 | var beforeLast = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length-1] 67 | beforeLast.end = now 68 | beforeLast.time = now - beforeLast.start 69 | } 70 | 71 | this._metrics.extra.bitratesHistory.push({start: this._now(), bitrate: bitrate}) 72 | 73 | this._inc('changeLevel') 74 | } 75 | 76 | stopReporting() { 77 | this._buildReport() 78 | 79 | clearInterval(this._intervalId) 80 | this._newMetrics() 81 | 82 | this.stopListening() 83 | this.bindEvents() 84 | } 85 | 86 | startTimers() { 87 | this._intervalId = setInterval(this._buildReport.bind(this), this._runEach) 88 | this._start('session') 89 | this._start('startup') 90 | } 91 | 92 | onFirstPlaying() { 93 | this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying) 94 | 95 | this._start('watch') 96 | this._stop('startup') 97 | } 98 | 99 | playAfterPause() { 100 | this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying) 101 | this._stop('pause') 102 | this._start('watch') 103 | } 104 | 105 | onPlay() { 106 | this._inc('play') 107 | } 108 | 109 | onPause() { 110 | this._stop('watch') 111 | this._start('pause') 112 | this._inc('pause') 113 | this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.playAfterPause) 114 | this.stopListening(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying) 115 | } 116 | 117 | onSeek(e) { 118 | this._inc('seek') 119 | this._metrics.extra.watchHistory.push([e * 1000, e * 1000]) 120 | } 121 | 122 | onTimeUpdate(e) { 123 | var current = e.current * 1000, 124 | total = e.total * 1000, 125 | l = this._metrics.extra.watchHistory.length 126 | 127 | this._metrics.extra.duration = total 128 | this._metrics.extra.currentTime = current 129 | this._metrics.extra.watchedPercentage = (current / total) * 100 130 | 131 | if (l === 0) { 132 | this._metrics.extra.watchHistory.push([current, current]) 133 | } else { 134 | this._metrics.extra.watchHistory[l-1][1] = current 135 | } 136 | 137 | if (this._metrics.extra.bitratesHistory.length > 0) { 138 | var lastBitrate = this._metrics.extra.bitratesHistory[this._metrics.extra.bitratesHistory.length-1] 139 | if (!lastBitrate.end) { 140 | lastBitrate.time = this._now() - lastBitrate.start 141 | } 142 | } 143 | 144 | this._onCompletion() 145 | } 146 | 147 | onContainerUpdateWhilePlaying() { 148 | if (this.container.playback.isPlaying()) { 149 | this._stop('watch') 150 | this._start('watch') 151 | } 152 | } 153 | 154 | onBuffering() { 155 | this._inc('buffering') 156 | this._start('buffering') 157 | this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferfull) 158 | } 159 | 160 | onBufferfull() { 161 | this._stop('buffering') 162 | this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering) 163 | } 164 | 165 | onProgress(progress) { 166 | this._metrics.extra.buffersize = progress.current * 1000 167 | } 168 | 169 | _newMetrics() { 170 | this._metrics = { 171 | counters: { 172 | play: 0, pause: 0, error: 0, buffering: 0, decodedFrames: 0, droppedFrames: 0, 173 | fps: 0, changeLevel: 0, seek: 0, fullscreen: 0, dvrUsage: 0 174 | }, 175 | timers: { 176 | startup: 0, watch: 0, pause: 0, buffering: 0, session: 0, latency: 0 177 | }, 178 | extra: { 179 | playbackName: '', playbackType: '', bitratesHistory: [], bitrateWeightedMean: 0, 180 | bitrateMostUsed: 0, buffersize: 0, watchHistory: [], watchedPercentage: 0, 181 | bufferingPercentage: 0, bandwidth: 0, duration: 0, currentTime: 0 182 | } 183 | } 184 | } 185 | 186 | _onCompletion() { 187 | let currentPercentage = this._metrics.extra.watchedPercentage 188 | let allPercentages = this._completion.watch 189 | let isCalled = this._completion.calls.indexOf(currentPercentage) != -1 190 | 191 | if (allPercentages.indexOf(currentPercentage) != -1 && !isCalled) { 192 | Log.info(this.name + ' PERCENTAGE_EVENT: ' + currentPercentage) 193 | this._completion.calls.push(currentPercentage) 194 | this.trigger(ClapprStats.PERCENTAGE_EVENT, currentPercentage) 195 | } 196 | } 197 | 198 | _buildReport() { 199 | this._stop('session') 200 | this._start('session') 201 | 202 | this._metrics.extra.playbackName = this._playbackName 203 | this._metrics.extra.playbackType = this._playbackType 204 | 205 | this._calculateBitrates() 206 | this._calculatePercentages() 207 | this._fetchFPS() 208 | this._measureLatency() 209 | this._measureBandwidth() 210 | 211 | this.trigger(ClapprStats.REPORT_EVENT, JSON.parse(JSON.stringify(this._metrics))) 212 | } 213 | 214 | _fetchFPS() { 215 | // flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds) 216 | // hls ??? (use the same?) 217 | const fetchFPS = { 218 | 'html5_video': this._html5FetchFPS, 219 | 'hls': this._html5FetchFPS, 220 | 'dash_shaka_playback': this._html5FetchFPS 221 | } 222 | 223 | fetchFPS[this._playbackName] && fetchFPS[this._playbackName].call(this) 224 | } 225 | 226 | _calculateBitrates() { 227 | var totalTime = this._metrics.extra.bitratesHistory.map((x) => x.time).reduce((a,b) => a + b, 0) 228 | this._metrics.extra.bitrateWeightedMean = this._metrics.extra.bitratesHistory.map((x) => { 229 | return x.bitrate * x.time 230 | }).reduce((a,b) => a + b, 0) / totalTime 231 | 232 | if (this._metrics.extra.bitratesHistory.length > 0) { 233 | this._metrics.extra.bitrateMostUsed = this._metrics.extra.bitratesHistory.slice().sort((a,b) => a.time < b.time)[0].bitrate 234 | } 235 | } 236 | 237 | _calculatePercentages() { 238 | if (this._metrics.extra.duration > 0) { 239 | this._metrics.extra.bufferingPercentage = (this._metrics.timers.buffering / this._metrics.extra.duration) * 100 240 | } 241 | } 242 | 243 | _html5FetchFPS() { 244 | const videoTag = this.container.playback.el 245 | const decodedFrames = videoTag.webkitDecodedFrameCount || videoTag.mozDecodedFrames || 0 246 | const droppedFrames = (videoTag.webkitDroppedFrameCount || (videoTag.mozParsedFrames - videoTag.mozDecodedFrames)) || 0 247 | const decodedFramesLastTime = decodedFrames - (this._lastDecodedFramesCount || 0) 248 | 249 | this._metrics.counters.decodedFrames = decodedFrames 250 | this._metrics.counters.droppedFrames = droppedFrames 251 | this._metrics.counters.fps = decodedFramesLastTime / (this._runEach / 1000) 252 | 253 | this._lastDecodedFramesCount = decodedFrames 254 | } 255 | 256 | // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/ 257 | _measureLatency() { 258 | if (this._uriToMeasureLatency) { 259 | var t=[], n=2, rtt 260 | var ld = () => { 261 | t.push(this._now()) 262 | if(t.length > n) 263 | done() 264 | else { 265 | var img = new Image 266 | img.onload = ld 267 | img.src=this._uriToMeasureLatency + '?' + Math.random() 268 | + '=' + this._now() 269 | } 270 | } 271 | var done = () => { 272 | rtt=t[2]-t[1] 273 | this._metrics.timers.latency = rtt 274 | } 275 | ld() 276 | } 277 | } 278 | 279 | // originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/ 280 | _measureBandwidth() { 281 | if (this._urisToMeasureBandwidth && (this._bwMeasureCount % this._runBandwidthTestEvery == 0)) { 282 | var i = 0 283 | 284 | var ld = (e) => { 285 | if (i > 0) { 286 | this._urisToMeasureBandwidth[i-1].end = this._now() 287 | clearTimeout(this._urisToMeasureBandwidth[i-1].timer) 288 | } 289 | if (i >= this._urisToMeasureBandwidth.length || (i > 0 && this._urisToMeasureBandwidth[i-1].expired)) 290 | done(e) 291 | else { 292 | var xhr = new XMLHttpRequest() 293 | xhr.open('GET', this._urisToMeasureBandwidth[i].url, true) 294 | xhr.responseType = 'arraybuffer' 295 | xhr.onload = xhr.onabort = ld 296 | this._urisToMeasureBandwidth[i].start = this._now() 297 | this._urisToMeasureBandwidth[i].timer = setTimeout((j) => { 298 | this._urisToMeasureBandwidth[j].expired = true 299 | xhr.abort() 300 | }, this._urisToMeasureBandwidth[i].timeout, i) 301 | xhr.send() 302 | } 303 | i++ 304 | } 305 | 306 | var done = (e) => { 307 | var timeSpent = (this._urisToMeasureBandwidth[i-1].end - this._urisToMeasureBandwidth[i-1].start) / 1000 308 | var bandwidthBps = (e.loaded * 8) / timeSpent 309 | this._metrics.extra.bandwidth = bandwidthBps 310 | this._urisToMeasureBandwidth.forEach((x) => { 311 | x.start = 0 312 | x.end = 0 313 | x.expired = false 314 | clearTimeout(x.timer) 315 | }) 316 | } 317 | 318 | ld() 319 | } 320 | this._bwMeasureCount++ 321 | } 322 | } 323 | 324 | ClapprStats.REPORT_EVENT = 'clappr:stats:report' 325 | ClapprStats.PERCENTAGE_EVENT = 'clappr:stats:percentage' 326 | -------------------------------------------------------------------------------- /test/clappr-stats.spec.js: -------------------------------------------------------------------------------- 1 | import { expect, assert } from 'chai' 2 | 3 | import ClapprStats from '../src/clappr-stats' 4 | import { PlayerSimulator } from './util' 5 | 6 | import sinon from 'sinon' 7 | 8 | const randomNumber = (max=20, min=5) => { 9 | let number = Math.random() * (max - min) + min 10 | return Math.trunc(number) 11 | } 12 | 13 | describe('Clappr Stats', function() { 14 | 15 | before(function() { 16 | this.timeInterval = 100 17 | this.clock = sinon.useFakeTimers(Date.now()) 18 | }) 19 | 20 | after(function() { 21 | this.clock.restore() 22 | }) 23 | 24 | beforeEach(function() { 25 | this.callback = sinon.spy() 26 | this.callbackOptions = sinon.spy() 27 | this.options = { 28 | src: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4', 29 | clapprStats: { 30 | runEach: this.timeInterval, 31 | onCompletion: [10, 25, 50, 100], 32 | onReport: this.callbackOptions 33 | } 34 | } 35 | 36 | this.simulator = new PlayerSimulator(this.options, ClapprStats) 37 | this.plugin = this.simulator.plugin 38 | }) 39 | 40 | it('call callbackOption when REPORT_EVENT is fired', function() { 41 | this.simulator.play() 42 | this.clock.tick(this.timeInterval) 43 | 44 | assert.isOk(this.callbackOptions.called) 45 | }) 46 | 47 | it('call REPORT_EVENT every time interval', function() { 48 | this.plugin.on(ClapprStats.REPORT_EVENT, this.callback) 49 | let attempts = randomNumber() 50 | 51 | this.simulator.play() 52 | this.clock.tick(this.timeInterval) 53 | 54 | assert.isOk(this.callback.called) 55 | for(let i = 0; i < attempts; i++) { 56 | this.clock.tick(this.timeInterval) 57 | let metrics = this.callback.getCall(i).args[0] 58 | 59 | assert.isObject(metrics.counters) 60 | assert.isObject(metrics.extra) 61 | assert.isObject(metrics.timers) 62 | } 63 | }) 64 | 65 | it('call PERCENTAGE_EVENT when PLAYBACK_TIMEUPDATE event is fired', function() { 66 | this.plugin.on(ClapprStats.PERCENTAGE_EVENT, this.callback) 67 | 68 | this.simulator.play(10) 69 | 70 | let percentage = this.callback.getCall(0).args[0] 71 | 72 | expect(percentage).to.be.equal(25) 73 | }) 74 | 75 | it('call PERCENTAGE_EVENT if video start in middle time and make seek for past', function() { 76 | this.plugin.on(ClapprStats.PERCENTAGE_EVENT, this.callback) 77 | 78 | this.simulator.play(10) 79 | assert.isOk(this.callback.calledOnce) 80 | 81 | this.simulator.play(4) 82 | assert.isOk(this.callback.calledTwice) 83 | }) 84 | 85 | it('call PERCENTAGE_EVENT once with the same state', function() { 86 | this.plugin.on(ClapprStats.PERCENTAGE_EVENT, this.callback) 87 | 88 | this.simulator.play(4) 89 | assert.isOk(this.callback.calledOnce) 90 | 91 | this.simulator.play(4) 92 | assert.isOk(this.callback.calledOnce) 93 | }) 94 | 95 | it('should update counters', function() { 96 | this.plugin.on(ClapprStats.REPORT_EVENT, this.callback) 97 | 98 | this.simulator.play() 99 | this.simulator.enableFullscreen() 100 | this.simulator.pause() 101 | this.simulator.simulateError() 102 | this.simulator.seek(15) 103 | this.clock.tick(this.timeInterval) 104 | 105 | let metrics = this.callback.getCall(0).args[0] 106 | 107 | expect(metrics.counters.play).to.be.equal(1) 108 | expect(metrics.counters.buffering).to.be.equal(1) 109 | expect(metrics.counters.changeLevel).to.be.equal(1) 110 | expect(metrics.counters.pause).to.be.equal(1) 111 | expect(metrics.counters.error).to.be.equal(1) 112 | expect(metrics.counters.seek).to.be.equal(1) 113 | expect(metrics.counters.dvrUsage).to.be.equal(1) 114 | expect(metrics.counters.fullscreen).to.be.equal(1) 115 | }) 116 | 117 | it('should update timer', function() { 118 | this.plugin.on(ClapprStats.REPORT_EVENT, this.callback) 119 | 120 | this.simulator.play() 121 | this.clock.tick(this.timeInterval) 122 | 123 | let metrics = this.callback.getCall(0).args[0] 124 | 125 | expect(metrics.timers.startup).to.be.an('number') 126 | expect(metrics.timers.watch).to.be.an('number') 127 | expect(metrics.timers.session).to.be.an('number') 128 | }) 129 | }) 130 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | import { HTML5Video, Container, Events } from '@clappr/core' 2 | 3 | 4 | class PlayerSimulator { 5 | 6 | constructor(options, plugin) { 7 | this.playback = new HTML5Video(options) 8 | options.playback = this.playback 9 | this.container = new Container(options) 10 | this.plugin = new plugin(this.container) 11 | this.container.addPlugin(this.plugin) 12 | } 13 | 14 | play(time=2, total=40) { 15 | this.container.play() 16 | this.container.onBuffering() 17 | this.container.onProgress({current: 50}) 18 | this.container.playing() 19 | this.container.updateBitrate(480) 20 | this.playback.trigger(Events.PLAYBACK_TIMEUPDATE, {current: time, total: total}) 21 | } 22 | 23 | pause() { 24 | this.container.pause() 25 | this.container.paused() 26 | } 27 | 28 | stop() { 29 | this.container.stop() 30 | this.container.stopped() 31 | } 32 | 33 | seek(time) { 34 | this.container.seek(time) 35 | this.container.playbackDvrStateChanged({}) 36 | } 37 | 38 | simulateError() { 39 | this.container.error({}) 40 | } 41 | 42 | enableFullscreen() { 43 | this.container.fullscreen() 44 | } 45 | } 46 | 47 | export { 48 | PlayerSimulator 49 | } 50 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const DirectoryNamedWebpackPlugin = require('directory-named-webpack-plugin') 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 5 | 6 | const minimize = !!process.env.MINIMIZE 7 | const packageName = 'clappr-stats' 8 | 9 | let configurations = [] 10 | 11 | const webpackConfig = (config) => { 12 | return { 13 | mode: config.mode || 'none', 14 | devtool: config.devtool || 'source-maps', 15 | entry: path.resolve(__dirname, 'src/clappr-stats.js'), 16 | externals: { 17 | '@clappr/core': 'Clappr' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | loader: 'babel-loader' 24 | } 25 | ], 26 | }, 27 | resolve: { 28 | plugins: [ 29 | new DirectoryNamedWebpackPlugin(true), 30 | ], 31 | extensions: ['.js'] 32 | }, 33 | plugins: [ 34 | ...(config.plugins || []) 35 | ], 36 | optimization: config.optimization, 37 | output: { 38 | path: path.resolve(__dirname, 'dist'), 39 | publicPath: 'latest/', 40 | filename: config.filename, 41 | library: 'ClapprStats', 42 | libraryTarget: 'umd', 43 | }, 44 | devServer: { 45 | contentBase: 'public/', 46 | host: '0.0.0.0', 47 | disableHostCheck: true, 48 | hot: true 49 | } 50 | } 51 | } 52 | 53 | configurations.push(webpackConfig({ 54 | filename: `${packageName}.js`, 55 | mode: 'development' 56 | })) 57 | 58 | if (minimize) { 59 | console.log('NOTE: Enabled minifying bundle (uglify)') 60 | const loaderOptions = new webpack.LoaderOptionsPlugin({ minimize, debug: !minimize }) 61 | 62 | const uglify = new UglifyJsPlugin({ 63 | uglifyOptions: { 64 | warnings: false, 65 | compress: {}, 66 | mangle: true, 67 | sourceMap: true, 68 | comments: false, 69 | output: { comments: false } 70 | }, 71 | }) 72 | 73 | configurations.push(webpackConfig({ 74 | filename: `${packageName}.min.js`, 75 | plugins: [ 76 | loaderOptions, 77 | ], 78 | optimization: { 79 | minimizer: [ 80 | uglify, 81 | ], 82 | }, 83 | mode: 'production' 84 | })) 85 | } 86 | 87 | module.exports = configurations 88 | --------------------------------------------------------------------------------