├── .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 | [](https://badge.fury.io/js/clappr-stats)
2 | [](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":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;QCVA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;;QAGA;QACA;;;;;;;;;;;;;;;;;AClFA;;;;;;;;;AASA;AACA,IAAIA,kBAAkB,qBAAtB;;AAEA;AACA,IAAIC,iBAAiB,2BAArB;;AAEA;AACA,IAAIC,WAAW,IAAI,CAAnB;;AAEA;AACA,IAAIC,UAAU,mBAAd;AAAA,IACIC,SAAS,4BADb;AAAA,IAEIC,YAAY,iBAFhB;;AAIA;AACA,IAAIC,eAAe,kDAAnB;AAAA,IACIC,gBAAgB,OADpB;AAAA,IAEIC,eAAe,KAFnB;AAAA,IAGIC,aAAa,kGAHjB;;AAKA;;;;AAIA,IAAIC,eAAe,qBAAnB;;AAEA;AACA,IAAIC,eAAe,UAAnB;;AAEA;AACA,IAAIC,eAAe,6BAAnB;;AAEA;AACA,IAAIC,aAAa,QAAOC,MAAP,yCAAOA,MAAP,MAAiB,QAAjB,IAA6BA,MAA7B,IAAuCA,OAAOC,MAAP,KAAkBA,MAAzD,IAAmED,MAApF;;AAEA;AACA,IAAIE,WAAW,QAAOC,IAAP,yCAAOA,IAAP,MAAe,QAAf,IAA2BA,IAA3B,IAAmCA,KAAKF,MAAL,KAAgBA,MAAnD,IAA6DE,IAA5E;;AAEA;AACA,IAAIC,OAAOL,cAAcG,QAAd,IAA0BG,SAAS,aAAT,GAArC;;AAEA;;;;;;;;AAQA,SAASC,QAAT,CAAkBC,MAAlB,EAA0BC,GAA1B,EAA+B;AAC7B,SAAOD,UAAU,IAAV,GAAiBE,SAAjB,GAA6BF,OAAOC,GAAP,CAApC;AACD;;AAED;;;;;;;AAOA,SAASE,YAAT,CAAsBC,KAAtB,EAA6B;AAC3B;AACA;AACA,MAAIC,SAAS,KAAb;AACA,MAAID,SAAS,IAAT,IAAiB,OAAOA,MAAME,QAAb,IAAyB,UAA9C,EAA0D;AACxD,QAAI;AACFD,eAAS,CAAC,EAAED,QAAQ,EAAV,CAAV;AACD,KAFD,CAEE,OAAOG,CAAP,EAAU,CAAE;AACf;AACD,SAAOF,MAAP;AACD;;AAED;AACA,IAAIG,aAAaC,MAAMC,SAAvB;AAAA,IACIC,YAAYb,SAASY,SADzB;AAAA,IAEIE,cAAclB,OAAOgB,SAFzB;;AAIA;AACA,IAAIG,aAAahB,KAAK,oBAAL,CAAjB;;AAEA;AACA,IAAIiB,aAAc,YAAW;AAC3B,MAAIC,MAAM,SAASC,IAAT,CAAcH,cAAcA,WAAWI,IAAzB,IAAiCJ,WAAWI,IAAX,CAAgBC,QAAjD,IAA6D,EAA3E,CAAV;AACA,SAAOH,MAAO,mBAAmBA,GAA1B,GAAiC,EAAxC;AACD,CAHiB,EAAlB;;AAKA;AACA,IAAII,eAAeR,UAAUL,QAA7B;;AAEA;AACA,IAAIc,iBAAiBR,YAAYQ,cAAjC;;AAEA;;;;;AAKA,IAAIC,iBAAiBT,YAAYN,QAAjC;;AAEA;AACA,IAAIgB,aAAaC,OAAO,MACtBJ,aAAaK,IAAb,CAAkBJ,cAAlB,EAAkCK,OAAlC,CAA0CpC,YAA1C,EAAwD,MAAxD,EACCoC,OADD,CACS,wDADT,EACmE,OADnE,CADsB,GAEwD,GAF/D,CAAjB;;AAKA;AACA,IAAIC,UAAS7B,KAAK6B,MAAlB;AAAA,IACIC,SAASnB,WAAWmB,MADxB;;AAGA;AACA,IAAIC,MAAMC,UAAUhC,IAAV,EAAgB,KAAhB,CAAV;AAAA,IACIiC,eAAeD,UAAUnC,MAAV,EAAkB,QAAlB,CADnB;;AAGA;AACA,IAAIqC,cAAcL,UAASA,QAAOhB,SAAhB,GAA4BR,SAA9C;AAAA,IACI8B,iBAAiBD,cAAcA,YAAYzB,QAA1B,GAAqCJ,SAD1D;;AAGA;;;;;;;AAOA,SAAS+B,IAAT,CAAcC,OAAd,EAAuB;AACrB,MAAIC,QAAQ,CAAC,CAAb;AAAA,MACIC,SAASF,UAAUA,QAAQE,MAAlB,GAA2B,CADxC;;AAGA,OAAKC,KAAL;AACA,SAAO,EAAEF,KAAF,GAAUC,MAAjB,EAAyB;AACvB,QAAIE,QAAQJ,QAAQC,KAAR,CAAZ;AACA,SAAKI,GAAL,CAASD,MAAM,CAAN,CAAT,EAAmBA,MAAM,CAAN,CAAnB;AACD;AACF;;AAED;;;;;;;AAOA,SAASE,SAAT,GAAqB;AACnB,OAAKC,QAAL,GAAgBX,eAAeA,aAAa,IAAb,CAAf,GAAoC,EAApD;AACD;;AAED;;;;;;;;;;AAUA,SAASY,UAAT,CAAoBzC,GAApB,EAAyB;AACvB,SAAO,KAAK0C,GAAL,CAAS1C,GAAT,KAAiB,OAAO,KAAKwC,QAAL,CAAcxC,GAAd,CAA/B;AACD;;AAED;;;;;;;;;AASA,SAAS2C,OAAT,CAAiB3C,GAAjB,EAAsB;AACpB,MAAI4C,OAAO,KAAKJ,QAAhB;AACA,MAAIX,YAAJ,EAAkB;AAChB,QAAIzB,SAASwC,KAAK5C,GAAL,CAAb;AACA,WAAOI,WAAWzB,cAAX,GAA4BsB,SAA5B,GAAwCG,MAA/C;AACD;AACD,SAAOe,eAAeI,IAAf,CAAoBqB,IAApB,EAA0B5C,GAA1B,IAAiC4C,KAAK5C,GAAL,CAAjC,GAA6CC,SAApD;AACD;;AAED;;;;;;;;;AASA,SAAS4C,OAAT,CAAiB7C,GAAjB,EAAsB;AACpB,MAAI4C,OAAO,KAAKJ,QAAhB;AACA,SAAOX,eAAee,KAAK5C,GAAL,MAAcC,SAA7B,GAAyCkB,eAAeI,IAAf,CAAoBqB,IAApB,EAA0B5C,GAA1B,CAAhD;AACD;;AAED;;;;;;;;;;AAUA,SAAS8C,OAAT,CAAiB9C,GAAjB,EAAsBG,KAAtB,EAA6B;AAC3B,MAAIyC,OAAO,KAAKJ,QAAhB;AACAI,OAAK5C,GAAL,IAAa6B,gBAAgB1B,UAAUF,SAA3B,GAAwCtB,cAAxC,GAAyDwB,KAArE;AACA,SAAO,IAAP;AACD;;AAED;AACA6B,KAAKvB,SAAL,CAAe2B,KAAf,GAAuBG,SAAvB;AACAP,KAAKvB,SAAL,CAAe,QAAf,IAA2BgC,UAA3B;AACAT,KAAKvB,SAAL,CAAesC,GAAf,GAAqBJ,OAArB;AACAX,KAAKvB,SAAL,CAAeiC,GAAf,GAAqBG,OAArB;AACAb,KAAKvB,SAAL,CAAe6B,GAAf,GAAqBQ,OAArB;;AAEA;;;;;;;AAOA,SAASE,SAAT,CAAmBf,OAAnB,EAA4B;AAC1B,MAAIC,QAAQ,CAAC,CAAb;AAAA,MACIC,SAASF,UAAUA,QAAQE,MAAlB,GAA2B,CADxC;;AAGA,OAAKC,KAAL;AACA,SAAO,EAAEF,KAAF,GAAUC,MAAjB,EAAyB;AACvB,QAAIE,QAAQJ,QAAQC,KAAR,CAAZ;AACA,SAAKI,GAAL,CAASD,MAAM,CAAN,CAAT,EAAmBA,MAAM,CAAN,CAAnB;AACD;AACF;;AAED;;;;;;;AAOA,SAASY,cAAT,GAA0B;AACxB,OAAKT,QAAL,GAAgB,EAAhB;AACD;;AAED;;;;;;;;;AASA,SAASU,eAAT,CAAyBlD,GAAzB,EAA8B;AAC5B,MAAI4C,OAAO,KAAKJ,QAAhB;AAAA,MACIN,QAAQiB,aAAaP,IAAb,EAAmB5C,GAAnB,CADZ;;AAGA,MAAIkC,QAAQ,CAAZ,EAAe;AACb,WAAO,KAAP;AACD;AACD,MAAIkB,YAAYR,KAAKT,MAAL,GAAc,CAA9B;AACA,MAAID,SAASkB,SAAb,EAAwB;AACtBR,SAAKS,GAAL;AACD,GAFD,MAEO;AACL3B,WAAOH,IAAP,CAAYqB,IAAZ,EAAkBV,KAAlB,EAAyB,CAAzB;AACD;AACD,SAAO,IAAP;AACD;;AAED;;;;;;;;;AASA,SAASoB,YAAT,CAAsBtD,GAAtB,EAA2B;AACzB,MAAI4C,OAAO,KAAKJ,QAAhB;AAAA,MACIN,QAAQiB,aAAaP,IAAb,EAAmB5C,GAAnB,CADZ;;AAGA,SAAOkC,QAAQ,CAAR,GAAYjC,SAAZ,GAAwB2C,KAAKV,KAAL,EAAY,CAAZ,CAA/B;AACD;;AAED;;;;;;;;;AASA,SAASqB,YAAT,CAAsBvD,GAAtB,EAA2B;AACzB,SAAOmD,aAAa,KAAKX,QAAlB,EAA4BxC,GAA5B,IAAmC,CAAC,CAA3C;AACD;;AAED;;;;;;;;;;AAUA,SAASwD,YAAT,CAAsBxD,GAAtB,EAA2BG,KAA3B,EAAkC;AAChC,MAAIyC,OAAO,KAAKJ,QAAhB;AAAA,MACIN,QAAQiB,aAAaP,IAAb,EAAmB5C,GAAnB,CADZ;;AAGA,MAAIkC,QAAQ,CAAZ,EAAe;AACbU,SAAKa,IAAL,CAAU,CAACzD,GAAD,EAAMG,KAAN,CAAV;AACD,GAFD,MAEO;AACLyC,SAAKV,KAAL,EAAY,CAAZ,IAAiB/B,KAAjB;AACD;AACD,SAAO,IAAP;AACD;;AAED;AACA6C,UAAUvC,SAAV,CAAoB2B,KAApB,GAA4Ba,cAA5B;AACAD,UAAUvC,SAAV,CAAoB,QAApB,IAAgCyC,eAAhC;AACAF,UAAUvC,SAAV,CAAoBsC,GAApB,GAA0BO,YAA1B;AACAN,UAAUvC,SAAV,CAAoBiC,GAApB,GAA0Ba,YAA1B;AACAP,UAAUvC,SAAV,CAAoB6B,GAApB,GAA0BkB,YAA1B;;AAEA;;;;;;;AAOA,SAASE,QAAT,CAAkBzB,OAAlB,EAA2B;AACzB,MAAIC,QAAQ,CAAC,CAAb;AAAA,MACIC,SAASF,UAAUA,QAAQE,MAAlB,GAA2B,CADxC;;AAGA,OAAKC,KAAL;AACA,SAAO,EAAEF,KAAF,GAAUC,MAAjB,EAAyB;AACvB,QAAIE,QAAQJ,QAAQC,KAAR,CAAZ;AACA,SAAKI,GAAL,CAASD,MAAM,CAAN,CAAT,EAAmBA,MAAM,CAAN,CAAnB;AACD;AACF;;AAED;;;;;;;AAOA,SAASsB,aAAT,GAAyB;AACvB,OAAKnB,QAAL,GAAgB;AACd,YAAQ,IAAIR,IAAJ,EADM;AAEd,WAAO,KAAKL,OAAOqB,SAAZ,GAFO;AAGd,cAAU,IAAIhB,IAAJ;AAHI,GAAhB;AAKD;;AAED;;;;;;;;;AASA,SAAS4B,cAAT,CAAwB5D,GAAxB,EAA6B;AAC3B,SAAO6D,WAAW,IAAX,EAAiB7D,GAAjB,EAAsB,QAAtB,EAAgCA,GAAhC,CAAP;AACD;;AAED;;;;;;;;;AASA,SAAS8D,WAAT,CAAqB9D,GAArB,EAA0B;AACxB,SAAO6D,WAAW,IAAX,EAAiB7D,GAAjB,EAAsB+C,GAAtB,CAA0B/C,GAA1B,CAAP;AACD;;AAED;;;;;;;;;AASA,SAAS+D,WAAT,CAAqB/D,GAArB,EAA0B;AACxB,SAAO6D,WAAW,IAAX,EAAiB7D,GAAjB,EAAsB0C,GAAtB,CAA0B1C,GAA1B,CAAP;AACD;;AAED;;;;;;;;;;AAUA,SAASgE,WAAT,CAAqBhE,GAArB,EAA0BG,KAA1B,EAAiC;AAC/B0D,aAAW,IAAX,EAAiB7D,GAAjB,EAAsBsC,GAAtB,CAA0BtC,GAA1B,EAA+BG,KAA/B;AACA,SAAO,IAAP;AACD;;AAED;AACAuD,SAASjD,SAAT,CAAmB2B,KAAnB,GAA2BuB,aAA3B;AACAD,SAASjD,SAAT,CAAmB,QAAnB,IAA+BmD,cAA/B;AACAF,SAASjD,SAAT,CAAmBsC,GAAnB,GAAyBe,WAAzB;AACAJ,SAASjD,SAAT,CAAmBiC,GAAnB,GAAyBqB,WAAzB;AACAL,SAASjD,SAAT,CAAmB6B,GAAnB,GAAyB0B,WAAzB;;AAEA;;;;;;;;AAQA,SAASb,YAAT,CAAsBc,KAAtB,EAA6BjE,GAA7B,EAAkC;AAChC,MAAImC,SAAS8B,MAAM9B,MAAnB;AACA,SAAOA,QAAP,EAAiB;AACf,QAAI+B,GAAGD,MAAM9B,MAAN,EAAc,CAAd,CAAH,EAAqBnC,GAArB,CAAJ,EAA+B;AAC7B,aAAOmC,MAAP;AACD;AACF;AACD,SAAO,CAAC,CAAR;AACD;;AAED;;;;;;;;AAQA,SAASgC,OAAT,CAAiBpE,MAAjB,EAAyBqE,IAAzB,EAA+B;AAC7BA,SAAOC,MAAMD,IAAN,EAAYrE,MAAZ,IAAsB,CAACqE,IAAD,CAAtB,GAA+BE,SAASF,IAAT,CAAtC;;AAEA,MAAIlC,QAAQ,CAAZ;AAAA,MACIC,SAASiC,KAAKjC,MADlB;;AAGA,SAAOpC,UAAU,IAAV,IAAkBmC,QAAQC,MAAjC,EAAyC;AACvCpC,aAASA,OAAOwE,MAAMH,KAAKlC,OAAL,CAAN,CAAP,CAAT;AACD;AACD,SAAQA,SAASA,SAASC,MAAnB,GAA6BpC,MAA7B,GAAsCE,SAA7C;AACD;;AAED;;;;;;;;AAQA,SAASuE,YAAT,CAAsBrE,KAAtB,EAA6B;AAC3B,MAAI,CAACsE,SAAStE,KAAT,CAAD,IAAoBuE,SAASvE,KAAT,CAAxB,EAAyC;AACvC,WAAO,KAAP;AACD;AACD,MAAIwE,UAAWC,WAAWzE,KAAX,KAAqBD,aAAaC,KAAb,CAAtB,GAA6CkB,UAA7C,GAA0D/B,YAAxE;AACA,SAAOqF,QAAQE,IAAR,CAAaC,SAAS3E,KAAT,CAAb,CAAP;AACD;;AAED;;;;;;;;AAQA,SAAS4E,YAAT,CAAsB5E,KAAtB,EAA6B;AAC3B;AACA,MAAI,OAAOA,KAAP,IAAgB,QAApB,EAA8B;AAC5B,WAAOA,KAAP;AACD;AACD,MAAI6E,SAAS7E,KAAT,CAAJ,EAAqB;AACnB,WAAO4B,iBAAiBA,eAAeR,IAAf,CAAoBpB,KAApB,CAAjB,GAA8C,EAArD;AACD;AACD,MAAIC,SAAUD,QAAQ,EAAtB;AACA,SAAQC,UAAU,GAAV,IAAkB,IAAID,KAAL,IAAe,CAACvB,QAAlC,GAA8C,IAA9C,GAAqDwB,MAA5D;AACD;;AAED;;;;;;;AAOA,SAASkE,QAAT,CAAkBnE,KAAlB,EAAyB;AACvB,SAAO8E,QAAQ9E,KAAR,IAAiBA,KAAjB,GAAyB+E,aAAa/E,KAAb,CAAhC;AACD;;AAED;;;;;;;;AAQA,SAAS0D,UAAT,CAAoBsB,GAApB,EAAyBnF,GAAzB,EAA8B;AAC5B,MAAI4C,OAAOuC,IAAI3C,QAAf;AACA,SAAO4C,UAAUpF,GAAV,IACH4C,KAAK,OAAO5C,GAAP,IAAc,QAAd,GAAyB,QAAzB,GAAoC,MAAzC,CADG,GAEH4C,KAAKuC,GAFT;AAGD;;AAED;;;;;;;;AAQA,SAASvD,SAAT,CAAmB7B,MAAnB,EAA2BC,GAA3B,EAAgC;AAC9B,MAAIG,QAAQL,SAASC,MAAT,EAAiBC,GAAjB,CAAZ;AACA,SAAOwE,aAAarE,KAAb,IAAsBA,KAAtB,GAA8BF,SAArC;AACD;;AAED;;;;;;;;AAQA,SAASoE,KAAT,CAAelE,KAAf,EAAsBJ,MAAtB,EAA8B;AAC5B,MAAIkF,QAAQ9E,KAAR,CAAJ,EAAoB;AAClB,WAAO,KAAP;AACD;AACD,MAAIkF,cAAclF,KAAd,yCAAcA,KAAd,CAAJ;AACA,MAAIkF,QAAQ,QAAR,IAAoBA,QAAQ,QAA5B,IAAwCA,QAAQ,SAAhD,IACAlF,SAAS,IADT,IACiB6E,SAAS7E,KAAT,CADrB,EACsC;AACpC,WAAO,IAAP;AACD;AACD,SAAOlB,cAAc4F,IAAd,CAAmB1E,KAAnB,KAA6B,CAACnB,aAAa6F,IAAb,CAAkB1E,KAAlB,CAA9B,IACJJ,UAAU,IAAV,IAAkBI,SAASV,OAAOM,MAAP,CAD9B;AAED;;AAED;;;;;;;AAOA,SAASqF,SAAT,CAAmBjF,KAAnB,EAA0B;AACxB,MAAIkF,cAAclF,KAAd,yCAAcA,KAAd,CAAJ;AACA,SAAQkF,QAAQ,QAAR,IAAoBA,QAAQ,QAA5B,IAAwCA,QAAQ,QAAhD,IAA4DA,QAAQ,SAArE,GACFlF,UAAU,WADR,GAEFA,UAAU,IAFf;AAGD;;AAED;;;;;;;AAOA,SAASuE,QAAT,CAAkBY,IAAlB,EAAwB;AACtB,SAAO,CAAC,CAACzE,UAAF,IAAiBA,cAAcyE,IAAtC;AACD;;AAED;;;;;;;AAOA,IAAIJ,eAAeK,QAAQ,UAASC,MAAT,EAAiB;AAC1CA,WAASnF,SAASmF,MAAT,CAAT;;AAEA,MAAIpF,SAAS,EAAb;AACA,MAAIlB,aAAa2F,IAAb,CAAkBW,MAAlB,CAAJ,EAA+B;AAC7BpF,WAAOqD,IAAP,CAAY,EAAZ;AACD;AACD+B,SAAOhE,OAAP,CAAerC,UAAf,EAA2B,UAASsG,KAAT,EAAgBC,MAAhB,EAAwBC,KAAxB,EAA+BH,MAA/B,EAAuC;AAChEpF,WAAOqD,IAAP,CAAYkC,QAAQH,OAAOhE,OAAP,CAAenC,YAAf,EAA6B,IAA7B,CAAR,GAA8CqG,UAAUD,KAApE;AACD,GAFD;AAGA,SAAOrF,MAAP;AACD,CAXkB,CAAnB;;AAaA;;;;;;;AAOA,SAASmE,KAAT,CAAepE,KAAf,EAAsB;AACpB,MAAI,OAAOA,KAAP,IAAgB,QAAhB,IAA4B6E,SAAS7E,KAAT,CAAhC,EAAiD;AAC/C,WAAOA,KAAP;AACD;AACD,MAAIC,SAAUD,QAAQ,EAAtB;AACA,SAAQC,UAAU,GAAV,IAAkB,IAAID,KAAL,IAAe,CAACvB,QAAlC,GAA8C,IAA9C,GAAqDwB,MAA5D;AACD;;AAED;;;;;;;AAOA,SAAS0E,QAAT,CAAkBQ,IAAlB,EAAwB;AACtB,MAAIA,QAAQ,IAAZ,EAAkB;AAChB,QAAI;AACF,aAAOpE,aAAaK,IAAb,CAAkB+D,IAAlB,CAAP;AACD,KAFD,CAEE,OAAOhF,CAAP,EAAU,CAAE;AACd,QAAI;AACF,aAAQgF,OAAO,EAAf;AACD,KAFD,CAEE,OAAOhF,CAAP,EAAU,CAAE;AACf;AACD,SAAO,EAAP;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAASiF,OAAT,CAAiBD,IAAjB,EAAuBM,QAAvB,EAAiC;AAC/B,MAAI,OAAON,IAAP,IAAe,UAAf,IAA8BM,YAAY,OAAOA,QAAP,IAAmB,UAAjE,EAA8E;AAC5E,UAAM,IAAIC,SAAJ,CAAcnH,eAAd,CAAN;AACD;AACD,MAAIoH,WAAW,SAAXA,QAAW,GAAW;AACxB,QAAIC,OAAOC,SAAX;AAAA,QACIhG,MAAM4F,WAAWA,SAASK,KAAT,CAAe,IAAf,EAAqBF,IAArB,CAAX,GAAwCA,KAAK,CAAL,CADlD;AAAA,QAEIG,QAAQJ,SAASI,KAFrB;;AAIA,QAAIA,MAAMxD,GAAN,CAAU1C,GAAV,CAAJ,EAAoB;AAClB,aAAOkG,MAAMnD,GAAN,CAAU/C,GAAV,CAAP;AACD;AACD,QAAII,SAASkF,KAAKW,KAAL,CAAW,IAAX,EAAiBF,IAAjB,CAAb;AACAD,aAASI,KAAT,GAAiBA,MAAM5D,GAAN,CAAUtC,GAAV,EAAeI,MAAf,CAAjB;AACA,WAAOA,MAAP;AACD,GAXD;AAYA0F,WAASI,KAAT,GAAiB,KAAKX,QAAQY,KAAR,IAAiBzC,QAAtB,GAAjB;AACA,SAAOoC,QAAP;AACD;;AAED;AACAP,QAAQY,KAAR,GAAgBzC,QAAhB;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAASQ,EAAT,CAAY/D,KAAZ,EAAmBiG,KAAnB,EAA0B;AACxB,SAAOjG,UAAUiG,KAAV,IAAoBjG,UAAUA,KAAV,IAAmBiG,UAAUA,KAAxD;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAInB,UAAUzE,MAAMyE,OAApB;;AAEA;;;;;;;;;;;;;;;;;AAiBA,SAASL,UAAT,CAAoBzE,KAApB,EAA2B;AACzB;AACA;AACA,MAAIkG,MAAM5B,SAAStE,KAAT,IAAkBiB,eAAeG,IAAf,CAAoBpB,KAApB,CAAlB,GAA+C,EAAzD;AACA,SAAOkG,OAAOxH,OAAP,IAAkBwH,OAAOvH,MAAhC;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS2F,QAAT,CAAkBtE,KAAlB,EAAyB;AACvB,MAAIkF,cAAclF,KAAd,yCAAcA,KAAd,CAAJ;AACA,SAAO,CAAC,CAACA,KAAF,KAAYkF,QAAQ,QAAR,IAAoBA,QAAQ,UAAxC,CAAP;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAASiB,YAAT,CAAsBnG,KAAtB,EAA6B;AAC3B,SAAO,CAAC,CAACA,KAAF,IAAW,QAAOA,KAAP,yCAAOA,KAAP,MAAgB,QAAlC;AACD;;AAED;;;;;;;;;;;;;;;;;AAiBA,SAAS6E,QAAT,CAAkB7E,KAAlB,EAAyB;AACvB,SAAO,QAAOA,KAAP,yCAAOA,KAAP,MAAgB,QAAhB,IACJmG,aAAanG,KAAb,KAAuBiB,eAAeG,IAAf,CAAoBpB,KAApB,KAA8BpB,SADxD;AAED;;AAED;;;;;;;;;;;;;;;;;;;;;AAqBA,SAASsB,QAAT,CAAkBF,KAAlB,EAAyB;AACvB,SAAOA,SAAS,IAAT,GAAgB,EAAhB,GAAqB4E,aAAa5E,KAAb,CAA5B;AACD;;AAED;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS4C,GAAT,CAAahD,MAAb,EAAqBqE,IAArB,EAA2BmC,YAA3B,EAAyC;AACvC,MAAInG,SAASL,UAAU,IAAV,GAAiBE,SAAjB,GAA6BkE,QAAQpE,MAAR,EAAgBqE,IAAhB,CAA1C;AACA,SAAOhE,WAAWH,SAAX,GAAuBsG,YAAvB,GAAsCnG,MAA7C;AACD;;AAEDoG,OAAOC,OAAP,GAAiB1D,GAAjB,C;;;;;;;;;;;;;;;;;ACl6BA,IAAI2D,CAAJ;;AAEA;AACAA,IAAK,YAAW;AACf,QAAO,IAAP;AACA,CAFG,EAAJ;;AAIA,IAAI;AACH;AACAA,KAAIA,KAAK,IAAI7G,QAAJ,CAAa,aAAb,GAAT;AACA,CAHD,CAGE,OAAOS,CAAP,EAAU;AACX;AACA,KAAI,QAAOqG,MAAP,yCAAOA,MAAP,OAAkB,QAAtB,EAAgCD,IAAIC,MAAJ;AAChC;;AAED;AACA;AACA;;AAEAH,OAAOC,OAAP,GAAiBC,CAAjB,C;;;;;;;;;;;;;;;;;;;;;;ACnBA;;AACA;;;;;;;;;;;;IAGqBE,W;;;;;2BAMZ;AACL,UAAMC,wBAAwBF,OAAOG,WAAP,IAAsB,OAAOH,OAAOG,WAAP,CAAmBC,GAA1B,KAAmC,UAAvF;AACA,aAAQF,qBAAD,GAAwBF,OAAOG,WAAP,CAAmBC,GAAnB,EAAxB,GAAiD,IAAIC,IAAJ,EAAxD;AACD;;;yBACIC,O,EAAS;AAAC,WAAKC,QAAL,CAAcC,QAAd,CAAuBF,OAAvB,KAAmC,CAAnC;AAAqC;;;qCACnCG,K,EAAM;AAAC,aAAO,gBAAcA,KAAd,MAA2BnH,SAAlC;AAA4C;;;2BAC7DmH,K,EAAO;AAAC,sBAAcA,KAAd,IAAyB,KAAKC,IAAL,EAAzB;AAAqC;;;0BAC9CD,K,EAAO;AAAC,WAAKF,QAAL,CAAcI,MAAd,CAAqBF,KAArB,KAA+B,KAAKC,IAAL,KAAc,gBAAcD,KAAd,CAA7C;AAAoE;;;mCACnEG,O,EAAS;AAACC,cAAQC,GAAR,CAAYF,OAAZ;AAAqB,K,CAAC;;;;wBAbpC;AAAE,aAAO,cAAP;AAAuB;;;wBACb;AAAE,aAAO,EAAEG,KAAK,OAAP,EAAP;AAAyB;;;wBAE9B;AAAC,aAAO,KAAKC,SAAL,CAAeC,QAAf,CAAwBC,IAA/B;AAAoC;;;wBACrC;AAAC,aAAO,KAAKF,SAAL,CAAeG,eAAf,EAAP;AAAwC;;;AAW7D,uBAAYH,SAAZ,EAAuB;AAAA;;AAAA,0HACfA,SADe;;AAErB,UAAKI,QAAL,GAAgB,sBAAIJ,SAAJ,EAAe,6BAAf,EAA8C,IAA9C,CAAhB;AACA,UAAKK,SAAL,GAAiB,sBAAIL,SAAJ,EAAe,8BAAf,EAA+C,MAAKM,cAApD,CAAjB;AACA,UAAKC,oBAAL,GAA4B,sBAAIP,SAAJ,EAAe,yCAAf,CAA5B;AACA,UAAKQ,uBAAL,GAA+B,sBAAIR,SAAJ,EAAe,4CAAf,CAA/B;AACA,UAAKS,sBAAL,GAA8B,sBAAIT,SAAJ,EAAe,2CAAf,EAA4D,EAA5D,CAA9B;AACA,UAAKU,eAAL,GAAuB,CAAvB;;AAEA,UAAKC,WAAL,GAAmB;AACjBC,aAAO,sBAAIZ,SAAJ,EAAe,kCAAf,EAAmD,EAAnD,CADU;AAEjBa,aAAO;AAFU,KAAnB;;AAKA,UAAKC,WAAL;AACA,UAAKC,EAAL,CAAQ9B,YAAY+B,YAApB,EAAkC,MAAKX,SAAvC;AAfqB;AAgBtB;;;;iCAEY;AAAA;;AACX,WAAKY,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOkB,iBAArC,EAAwD,KAAKC,SAA7D;AACA,WAAKF,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOoB,cAArC,EAAqD,KAAKC,aAA1D;AACA,WAAKJ,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOsB,eAArC,EAAsD,KAAKD,aAA3D;AACA,WAAKE,YAAL,CAAkB,KAAKvB,SAAL,CAAeC,QAAjC,EAA2C,aAAOuB,oBAAlD,EAAwE,KAAKC,WAA7E;AACA,WAAKF,YAAL,CAAkB,KAAKvB,SAAvB,EAAkC,aAAO0B,cAAzC,EAAyD,KAAKC,cAA9D;AACA,WAAKV,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAO0B,cAArC,EAAqD,KAAKE,MAA1D;AACA,WAAKX,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAO6B,eAArC,EAAsD,KAAKC,OAA3D;AACA,WAAKP,YAAL,CAAkB,KAAKvB,SAAvB,EAAkC,aAAO+B,yBAAzC,EAAoE,KAAKC,WAAzE;AACA,WAAKf,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOiC,cAArC,EAAqD,KAAKC,MAA1D;AACA,WAAKjB,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOmC,eAArC,EAAsD;AAAA,eAAM,OAAKC,IAAL,CAAU,OAAV,CAAN;AAAA,OAAtD;AACA,WAAKnB,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOqC,oBAArC,EAA2D;AAAA,eAAM,OAAKD,IAAL,CAAU,YAAV,CAAN;AAAA,OAA3D;AACA,WAAKnB,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAOsC,iCAArC,EAAwE,UAACC,QAAD,EAAc;AAACA,oBAAY,OAAKH,IAAL,CAAU,UAAV,CAAZ;AAAkC,OAAzH;AACA,WAAKnB,QAAL,CAAc,KAAKjB,SAAL,CAAeC,QAA7B,EAAuC,aAAOuC,iBAA9C,EAAiE,KAAKC,UAAtE;AACA,WAAKxB,QAAL,CAAc,KAAKjB,SAAL,CAAeC,QAA7B,EAAuC,aAAOyC,mBAA9C,EAAmE,KAAKC,YAAxE;AACD;;;8BAES;AACR,WAAKtB,aAAL;AACA;AACD;;;8BAESuB,U,EAAY;AACpB,UAAIC,UAAUC,SAAS,sBAAIF,UAAJ,EAAgB,SAAhB,EAA2B,CAA3B,CAAT,EAAwC,EAAxC,CAAd;AACA,UAAIxD,MAAM,KAAKM,IAAL,EAAV;;AAEA,UAAI,KAAKH,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxI,MAApC,GAA6C,CAAjD,EAAoD;AAClD,YAAIyI,aAAa,KAAK1D,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoC,KAAKzD,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxI,MAApC,GAA2C,CAA/E,CAAjB;AACAyI,mBAAWC,GAAX,GAAiB9D,GAAjB;AACA6D,mBAAWE,IAAX,GAAkB/D,MAAM6D,WAAWG,KAAnC;AACD;;AAED,WAAK7D,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoClH,IAApC,CAAyC,EAACsH,OAAO,KAAK1D,IAAL,EAAR,EAAqBmD,SAASA,OAA9B,EAAzC;;AAEA,WAAKT,IAAL,CAAU,aAAV;AACD;;;oCAEe;AACd,WAAKiB,YAAL;;AAEAC,oBAAc,KAAKC,WAAnB;AACA,WAAKzC,WAAL;;AAEA,WAAK0C,aAAL;AACA,WAAKC,UAAL;AACD;;;kCAEa;AACZ,WAAKF,WAAL,GAAmBG,YAAY,KAAKL,YAAL,CAAkBM,IAAlB,CAAuB,IAAvB,CAAZ,EAA0C,KAAKvD,QAA/C,CAAnB;AACA,WAAKwD,MAAL,CAAY,SAAZ;AACA,WAAKA,MAAL,CAAY,SAAZ;AACD;;;qCAEgB;AACf,WAAK3C,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAO6D,oBAArC,EAA2D,KAAKC,6BAAhE;;AAEA,WAAKF,MAAL,CAAY,OAAZ;AACA,WAAKG,KAAL,CAAW,SAAX;AACD;;;qCAEgB;AACf,WAAK9C,QAAL,CAAc,KAAKjB,SAAnB,EAA8B,aAAO6D,oBAArC,EAA2D,KAAKC,6BAAhE;AACA,WAAKC,KAAL,CAAW,OAAX;AACA,WAAKH,MAAL,CAAY,OAAZ;AACD;;;6BAEQ;AACP,WAAKxB,IAAL,CAAU,MAAV;AACD;;;8BAES;AACR,WAAK2B,KAAL,CAAW,OAAX;AACA,WAAKH,MAAL,CAAY,OAAZ;AACA,WAAKxB,IAAL,CAAU,OAAV;AACA,WAAKb,YAAL,CAAkB,KAAKvB,SAAvB,EAAkC,aAAO0B,cAAzC,EAAyD,KAAKsC,cAA9D;AACA,WAAKR,aAAL,CAAmB,KAAKxD,SAAxB,EAAmC,aAAO6D,oBAA1C,EAAgE,KAAKC,6BAArE;AACD;;;2BAEMnL,C,EAAG;AACR,WAAKyJ,IAAL,CAAU,MAAV;AACA,WAAK7C,QAAL,CAAcwD,KAAd,CAAoBkB,YAApB,CAAiCnI,IAAjC,CAAsC,CAACnD,IAAI,IAAL,EAAWA,IAAI,IAAf,CAAtC;AACD;;;iCAEYA,C,EAAG;AACd,UAAIuL,UAAUvL,EAAEuL,OAAF,GAAY,IAA1B;AAAA,UACEC,QAAQxL,EAAEwL,KAAF,GAAU,IADpB;AAAA,UAEEC,IAAI,KAAK7E,QAAL,CAAcwD,KAAd,CAAoBkB,YAApB,CAAiCzJ,MAFvC;;AAIA,WAAK+E,QAAL,CAAcwD,KAAd,CAAoBsB,QAApB,GAA+BF,KAA/B;AACA,WAAK5E,QAAL,CAAcwD,KAAd,CAAoBuB,WAApB,GAAkCJ,OAAlC;AACA,WAAK3E,QAAL,CAAcwD,KAAd,CAAoBwB,iBAApB,GAAyCL,UAAUC,KAAX,GAAoB,GAA5D;;AAEA,UAAIC,MAAM,CAAV,EAAa;AACX,aAAK7E,QAAL,CAAcwD,KAAd,CAAoBkB,YAApB,CAAiCnI,IAAjC,CAAsC,CAACoI,OAAD,EAAUA,OAAV,CAAtC;AACD,OAFD,MAEO;AACL,aAAK3E,QAAL,CAAcwD,KAAd,CAAoBkB,YAApB,CAAiCG,IAAE,CAAnC,EAAsC,CAAtC,IAA2CF,OAA3C;AACD;;AAED,UAAI,KAAK3E,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxI,MAApC,GAA6C,CAAjD,EAAoD;AAClD,YAAIgK,cAAc,KAAKjF,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoC,KAAKzD,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxI,MAApC,GAA2C,CAA/E,CAAlB;AACA,YAAI,CAACgK,YAAYtB,GAAjB,EAAsB;AACpBsB,sBAAYrB,IAAZ,GAAmB,KAAKzD,IAAL,KAAc8E,YAAYpB,KAA7C;AACD;AACF;;AAED,WAAKqB,aAAL;AACD;;;oDAE+B;AAC9B,UAAI,KAAKzE,SAAL,CAAeC,QAAf,CAAwByE,SAAxB,EAAJ,EAAyC;AACvC,aAAKX,KAAL,CAAW,OAAX;AACA,aAAKH,MAAL,CAAY,OAAZ;AACD;AACF;;;kCAEa;AACZ,WAAKxB,IAAL,CAAU,WAAV;AACA,WAAKwB,MAAL,CAAY,WAAZ;AACA,WAAKrC,YAAL,CAAkB,KAAKvB,SAAvB,EAAkC,aAAO2E,0BAAzC,EAAqE,KAAKC,YAA1E;AACD;;;mCAEc;AACb,WAAKb,KAAL,CAAW,WAAX;AACA,WAAKxC,YAAL,CAAkB,KAAKvB,SAAvB,EAAkC,aAAO+B,yBAAzC,EAAoE,KAAKC,WAAzE;AACD;;;+BAEU6C,Q,EAAU;AACnB,WAAKtF,QAAL,CAAcwD,KAAd,CAAoB+B,UAApB,GAAiCD,SAASX,OAAT,GAAmB,IAApD;AACD;;;kCAEa;AACZ,WAAK3E,QAAL,GAAgB;AACdC,kBAAU;AACRuF,gBAAM,CADE,EACCC,OAAO,CADR,EACWC,OAAO,CADlB,EACqBC,WAAW,CADhC,EACmCC,eAAe,CADlD,EACqDC,eAAe,CADpE;AAERC,eAAK,CAFG,EAEAC,aAAa,CAFb,EAEgBC,MAAM,CAFtB,EAEyBC,YAAY,CAFrC,EAEwCC,UAAU;AAFlD,SADI;AAKd9F,gBAAQ;AACN+F,mBAAS,CADH,EACM9E,OAAO,CADb,EACgBoE,OAAO,CADvB,EAC0BE,WAAW,CADrC,EACwCS,SAAS,CADjD,EACoDC,SAAS;AAD7D,SALM;AAQd7C,eAAO;AACL8C,wBAAc,EADT,EACaC,cAAc,EAD3B,EAC+B9C,iBAAiB,EADhD,EACoD+C,qBAAqB,CADzE;AAELC,2BAAiB,CAFZ,EAEelB,YAAY,CAF3B,EAE8Bb,cAAc,EAF5C,EAEgDM,mBAAmB,CAFnE;AAGL0B,+BAAqB,CAHhB,EAGmBC,WAAW,CAH9B,EAGiC7B,UAAU,CAH3C,EAG8CC,aAAa;AAH3D;AARO,OAAhB;AAcD;;;oCAEe;AACd,UAAI6B,oBAAoB,KAAK5G,QAAL,CAAcwD,KAAd,CAAoBwB,iBAA5C;AACA,UAAI6B,iBAAiB,KAAKzF,WAAL,CAAiBC,KAAtC;AACA,UAAIyF,WAAW,KAAK1F,WAAL,CAAiBE,KAAjB,CAAuByF,OAAvB,CAA+BH,iBAA/B,KAAqD,CAAC,CAArE;;AAEA,UAAIC,eAAeE,OAAf,CAAuBH,iBAAvB,KAA6C,CAAC,CAA9C,IAAmD,CAACE,QAAxD,EAAkE;AAChE,kBAAIE,IAAJ,CAAS,KAAKrG,IAAL,GAAY,qBAAZ,GAAoCiG,iBAA7C;AACA,aAAKxF,WAAL,CAAiBE,KAAjB,CAAuB/E,IAAvB,CAA4BqK,iBAA5B;AACA,aAAKK,OAAL,CAAavH,YAAYwH,gBAAzB,EAA2CN,iBAA3C;AACD;AACF;;;mCAEc;AACb,WAAKpC,KAAL,CAAW,SAAX;AACA,WAAKH,MAAL,CAAY,SAAZ;;AAEA,WAAKrE,QAAL,CAAcwD,KAAd,CAAoB8C,YAApB,GAAmC,KAAKa,aAAxC;AACA,WAAKnH,QAAL,CAAcwD,KAAd,CAAoB+C,YAApB,GAAmC,KAAKa,aAAxC;;AAEA,WAAKC,kBAAL;AACA,WAAKC,qBAAL;AACA,WAAKC,SAAL;AACA,WAAKC,eAAL;AACA,WAAKC,iBAAL;;AAEA,WAAKR,OAAL,CAAavH,YAAY+B,YAAzB,EAAuCiG,KAAKC,KAAL,CAAWD,KAAKE,SAAL,CAAe,KAAK5H,QAApB,CAAX,CAAvC;AACD;;;gCAEW;AACV;AACA;AACA,UAAM6H,WAAW;AACf,uBAAe,KAAKC,cADL;AAEf,eAAO,KAAKA,cAFG;AAGf,+BAAuB,KAAKA;AAHb,OAAjB;;AAMAD,eAAS,KAAKV,aAAd,KAAgCU,SAAS,KAAKV,aAAd,EAA6B9M,IAA7B,CAAkC,IAAlC,CAAhC;AACD;;;yCAEoB;AACnB,UAAI0N,YAAY,KAAK/H,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxF,GAApC,CAAwC,UAAC+J,CAAD;AAAA,eAAOA,EAAEpE,IAAT;AAAA,OAAxC,EAAuDqE,MAAvD,CAA8D,UAACC,CAAD,EAAGC,CAAH;AAAA,eAASD,IAAIC,CAAb;AAAA,OAA9D,EAA8E,CAA9E,CAAhB;AACA,WAAKnI,QAAL,CAAcwD,KAAd,CAAoBgD,mBAApB,GAA0C,KAAKxG,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxF,GAApC,CAAwC,UAAC+J,CAAD,EAAO;AACvF,eAAOA,EAAE1E,OAAF,GAAY0E,EAAEpE,IAArB;AACD,OAFyC,EAEvCqE,MAFuC,CAEhC,UAACC,CAAD,EAAGC,CAAH;AAAA,eAASD,IAAIC,CAAb;AAAA,OAFgC,EAEhB,CAFgB,IAEXJ,SAF/B;;AAIA,UAAI,KAAK/H,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoCxI,MAApC,GAA6C,CAAjD,EAAoD;AAClD,aAAK+E,QAAL,CAAcwD,KAAd,CAAoBiD,eAApB,GAAsC,KAAKzG,QAAL,CAAcwD,KAAd,CAAoBC,eAApB,CAAoC2E,KAApC,GAA4CC,IAA5C,CAAiD,UAACH,CAAD,EAAGC,CAAH;AAAA,iBAASD,EAAEtE,IAAF,GAASuE,EAAEvE,IAApB;AAAA,SAAjD,EAA2E,CAA3E,EAA8EN,OAApH;AACD;AACF;;;4CAEuB;AACtB,UAAI,KAAKtD,QAAL,CAAcwD,KAAd,CAAoBsB,QAApB,GAA+B,CAAnC,EAAsC;AACpC,aAAK9E,QAAL,CAAcwD,KAAd,CAAoBkD,mBAApB,GAA2C,KAAK1G,QAAL,CAAcI,MAAd,CAAqBuF,SAArB,GAAiC,KAAK3F,QAAL,CAAcwD,KAAd,CAAoBsB,QAAtD,GAAkE,GAA5G;AACD;AACF;;;qCAEgB;AACf,UAAMwD,WAAW,KAAK7H,SAAL,CAAeC,QAAf,CAAwB6H,EAAzC;AACA,UAAM3C,gBAAgB0C,SAASE,uBAAT,IAAoCF,SAASG,gBAA7C,IAAiE,CAAvF;AACA,UAAM5C,gBAAiByC,SAASI,uBAAT,IAAqCJ,SAASK,eAAT,GAA2BL,SAASG,gBAA1E,IAAgG,CAAtH;AACA,UAAMG,wBAAwBhD,iBAAiB,KAAKiD,uBAAL,IAAgC,CAAjD,CAA9B;;AAEA,WAAK7I,QAAL,CAAcC,QAAd,CAAuB2F,aAAvB,GAAuCA,aAAvC;AACA,WAAK5F,QAAL,CAAcC,QAAd,CAAuB4F,aAAvB,GAAuCA,aAAvC;AACA,WAAK7F,QAAL,CAAcC,QAAd,CAAuB6F,GAAvB,GAA6B8C,yBAAyB,KAAK/H,QAAL,GAAgB,IAAzC,CAA7B;;AAEA,WAAKgI,uBAAL,GAA+BjD,aAA/B;AACD;;AAED;;;;sCACkB;AAAA;;AAChB,UAAI,KAAK5E,oBAAT,EAA+B;AAC7B,YAAI8H,IAAE,EAAN;AAAA,YAAUC,IAAE,CAAZ;AAAA,YAAeC,GAAf;AACA,YAAIC,KAAK,SAALA,EAAK,GAAM;AACbH,YAAEvM,IAAF,CAAO,OAAK4D,IAAL,EAAP;AACA,cAAG2I,EAAE7N,MAAF,GAAW8N,CAAd,EACEG,OADF,KAEK;AACH,gBAAIC,MAAM,IAAIC,KAAJ,EAAV;AACAD,gBAAIE,MAAJ,GAAaJ,EAAb;AACAE,gBAAIG,GAAJ,GAAQ,OAAKtI,oBAAL,GAA4B,GAA5B,GAAkCuI,KAAKC,MAAL,EAAlC,GACN,GADM,GACA,OAAKrJ,IAAL,EADR;AAED;AACF,SAVD;AAWA,YAAI+I,OAAO,SAAPA,IAAO,GAAM;AACfF,gBAAIF,EAAE,CAAF,IAAKA,EAAE,CAAF,CAAT;AACA,iBAAK9I,QAAL,CAAcI,MAAd,CAAqBiG,OAArB,GAA+B2C,GAA/B;AACD,SAHD;AAIAC;AACD;AACF;;AAED;;;;wCACoB;AAAA;;AAClB,UAAI,KAAKhI,uBAAL,IAAiC,KAAKE,eAAL,GAAuB,KAAKD,sBAA5B,IAAsD,CAA3F,EAA+F;AAC7F,YAAIuI,IAAI,CAAR;;AAEA,YAAIR,KAAK,SAALA,EAAK,CAAC7P,CAAD,EAAO;AACd,cAAIqQ,IAAI,CAAR,EAAW;AACT,mBAAKxI,uBAAL,CAA6BwI,IAAE,CAA/B,EAAkC9F,GAAlC,GAAwC,OAAKxD,IAAL,EAAxC;AACAuJ,yBAAa,OAAKzI,uBAAL,CAA6BwI,IAAE,CAA/B,EAAkCvJ,KAA/C;AACD;AACD,cAAIuJ,KAAK,OAAKxI,uBAAL,CAA6BhG,MAAlC,IAA6CwO,IAAI,CAAJ,IAAS,OAAKxI,uBAAL,CAA6BwI,IAAE,CAA/B,EAAkCE,OAA5F,EACET,KAAK9P,CAAL,EADF,KAEK;AACH,gBAAIwQ,MAAM,IAAIC,cAAJ,EAAV;AACAD,gBAAIE,IAAJ,CAAS,KAAT,EAAgB,OAAK7I,uBAAL,CAA6BwI,CAA7B,EAAgCM,GAAhD,EAAqD,IAArD;AACAH,gBAAII,YAAJ,GAAmB,aAAnB;AACAJ,gBAAIP,MAAJ,GAAaO,IAAIK,OAAJ,GAAchB,EAA3B;AACA,mBAAKhI,uBAAL,CAA6BwI,CAA7B,EAAgC5F,KAAhC,GAAwC,OAAK1D,IAAL,EAAxC;AACA,mBAAKc,uBAAL,CAA6BwI,CAA7B,EAAgCvJ,KAAhC,GAAwCgK,WAAW,UAACC,CAAD,EAAO;AACxD,qBAAKlJ,uBAAL,CAA6BkJ,CAA7B,EAAgCR,OAAhC,GAA0C,IAA1C;AACAC,kBAAIQ,KAAJ;AACD,aAHuC,EAGrC,OAAKnJ,uBAAL,CAA6BwI,CAA7B,EAAgCY,OAHK,EAGIZ,CAHJ,CAAxC;AAIAG,gBAAIU,IAAJ;AACD;AACDb;AACD,SApBD;;AAsBA,YAAIP,OAAO,SAAPA,IAAO,CAAC9P,CAAD,EAAO;AAChB,cAAImR,YAAY,CAAC,OAAKtJ,uBAAL,CAA6BwI,IAAE,CAA/B,EAAkC9F,GAAlC,GAAwC,OAAK1C,uBAAL,CAA6BwI,IAAE,CAA/B,EAAkC5F,KAA3E,IAAoF,IAApG;AACA,cAAI2G,eAAgBpR,EAAEqR,MAAF,GAAW,CAAZ,GAAiBF,SAApC;AACA,iBAAKvK,QAAL,CAAcwD,KAAd,CAAoBmD,SAApB,GAAgC6D,YAAhC;AACA,iBAAKvJ,uBAAL,CAA6ByJ,OAA7B,CAAqC,UAAC1C,CAAD,EAAO;AAC1CA,cAAEnE,KAAF,GAAU,CAAV;AACAmE,cAAErE,GAAF,GAAQ,CAAR;AACAqE,cAAE2B,OAAF,GAAY,KAAZ;AACAD,yBAAa1B,EAAE9H,KAAf;AACD,WALD;AAMD,SAVD;;AAYA+I;AACD;AACD,WAAK9H,eAAL;AACD;;;;;;kBA5TkBzB,W;;;AA+TrBA,YAAY+B,YAAZ,GAA2B,qBAA3B;AACA/B,YAAYwH,gBAAZ,GAA+B,yBAA/B;;;;;;;;;;;;ACpUA,0D","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 |
--------------------------------------------------------------------------------