jellyfin-web/dashboard-ui/bower_components/hls.js/dist/hls.js.map

79 lines
409 KiB
Plaintext
Raw Normal View History

2015-12-15 22:30:14 -07:00
{
"version": 3,
"sources": [
"node_modules/browserify/node_modules/browser-pack/_prelude.js",
"node_modules/browserify/node_modules/events/events.js",
"node_modules/webworkify/index.js",
"src/controller/abr-controller.js",
"src/controller/level-controller.js",
"src/controller/mse-media-controller.js",
2016-02-01 10:02:17 -07:00
"src/controller/timeline-controller.js",
2015-12-15 22:30:14 -07:00
"src/crypt/aes.js",
"src/crypt/aes128-decrypter.js",
"src/crypt/decrypter.js",
"src/demux/aacdemuxer.js",
2016-01-13 13:58:12 -07:00
"src/demux/adts.js",
2015-12-15 22:30:14 -07:00
"src/demux/demuxer-inline.js",
"src/demux/demuxer-worker.js",
"src/demux/demuxer.js",
"src/demux/exp-golomb.js",
"src/demux/id3.js",
"src/demux/tsdemuxer.js",
"src/errors.js",
2016-01-18 12:07:26 -07:00
"src/event-handler.js",
2015-12-15 22:30:14 -07:00
"src/events.js",
"src/helper/level-helper.js",
"src/hls.js",
"src/loader/fragment-loader.js",
"src/loader/key-loader.js",
"src/loader/playlist-loader.js",
"src/remux/mp4-generator.js",
"src/remux/mp4-remuxer.js",
2016-01-13 13:58:12 -07:00
"src/utils/attr-list.js",
2015-12-15 22:30:14 -07:00
"src/utils/binary-search.js",
2016-02-01 10:02:17 -07:00
"src/utils/cea-708-interpreter.js",
2015-12-15 22:30:14 -07:00
"src/utils/logger.js",
"src/utils/url.js",
"src/utils/xhr-loader.js"
],
"names": [],
2016-02-01 10:02:17 -07:00
"mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC7SA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;sBC7DkB,WAAW;;;;4BACJ,kBAAkB;;;;IAErC,aAAa;YAAb,aAAa;;AAEN,WAFP,aAAa,CAEL,GAAG,EAAE;0BAFb,aAAa;;AAGf,+BAHE,aAAa,6CAGT,GAAG,EAAE,oBAAM,kBAAkB,EAAE;AACrC,QAAI,CAAC,cAAc,GAAG,CAAC,CAAC;AACxB,QAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC;AAC5B,QAAI,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;GAC1B;;eAPG,aAAa;;WASV,mBAAG;AACR,gCAAa,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAC3C;;;WAEiB,4BAAC,IAAI,EAAE;AACvB,UAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;AACvB,UAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE;AAC/B,YAAI,CAAC,iBAAiB,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAA,GAAI,IAAI,CAAC;AACrE,YAAI,CAAC,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;AACtC,YAAI,CAAC,MAAM,GAAG,AAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAI,IAAI,CAAC,iBAAiB,CAAC;;OAE3D;KACF;;;;;SAGmB,eAAG;AACrB,aAAO,IAAI,CAAC,iBAAiB,CAAC;KAC/B;;;SAGmB,aAAC,QAAQ,EAAE;AAC7B,UAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC;KACnC;;;SAEgB,eAAG;AAClB,UAAI,MAAM,GAAG,IAAI,CAAC,MAAM;UAAE,GAAG,GAAG,IAAI,CAAC,GAAG;UAAC,UAAU;UAAE,CAAC;UAAE,YAAY,CAAC;AACrE,UAAI,IAAI,CAAC,iBAAiB,KAAK,CAAC,CAAC,EAAE;AACjC,oBAAY,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;OACtC,MAAM;AACL,oBAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC;OACvC;;AAED,UAAI,IAAI,CAAC,cAAc,KAAK,CAAC,CAAC,EAAE;AAC9B,YAAI,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,EAAC,YAAY,CAAC,CAAC;AAC3D,YAAI,SAAS,KAAK,IAAI,CAAC,cAAc,EAAE;AACrC,cAAI,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;SAC1B,MAAM;AACL,iBAAO,SAAS,CAAC;SAClB;OACF;;;;;AAKD,WAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,YAAY,EAAE,CAAC,EAAE,EAAE;;;;AAIlC,YAAI,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE;AAC5B,oBAAU,GAAG,GAAG,GAAG,MAAM,CAAC;SAC3B,MAAM;AACL,oBAAU,GAAG,GAAG,GAAG,MAAM,CAAC;SAC3B;AACD,YAAI,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE;AACtC,iBAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SAC3B;OACF;AACD,aAAO,CAAC,GAAG,CAAC,CAAC;KACd;SAEgB,aAAC,SAAS,EAAE;AAC3B,UAAI,CAAC,cAAc,GAAG,SAAS,CAAC;KACjC;;;SAvEG,aAAa;;;qBA0EJ,aAAa;;;;;;;;;;;;;;;;;;;;;;;;sBC7EV,WAAW;;;;4BACJ,kBAAkB;;;;2BACtB,iBAAiB;;sBACC,WAAW;;IAE5C,eAAe;YAAf,eAAe;;AAER,WAFP,eAAe,CAEP,GAAG,EAAE;0BAFb,eAAe;;AAGjB,+BAHE,eAAe,6CAGX,GAAG,EACP,oBAAM,eAAe,EACrB,oBAAM,YAAY,EAClB,oBAAM,KAAK,EAAE;AACf,QAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,QAAI,CAAC,YAAY,GAAG,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC;GACjD;;eATG,eAAe;;WAWZ,mBAAG;AA
2015-12-15 22:30:14 -07:00
"file": "generated.js",
"sourceRoot": "",
"sourcesContent": [
"(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})",
"// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nfunction EventEmitter() {\n this._events = this._events || {};\n this._maxListeners = this._maxListeners || undefined;\n}\nmodule.exports = EventEmitter;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nEventEmitter.defaultMaxListeners = 10;\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function(n) {\n if (!isNumber(n) || n < 0 || isNaN(n))\n throw TypeError('n must be a positive number');\n this._maxListeners = n;\n return this;\n};\n\nEventEmitter.prototype.emit = function(type) {\n var er, handler, len, args, i, listeners;\n\n if (!this._events)\n this._events = {};\n\n // If there is no 'error' event listener then throw.\n if (type === 'error') {\n if (!this._events.error ||\n (isObject(this._events.error) && !this._events.error.length)) {\n er = arguments[1];\n if (er instanceof Error) {\n throw er; // Unhandled 'error' event\n }\n throw TypeError('Uncaught, unspecified \"error\" event.');\n }\n }\n\n handler = this._events[type];\n\n if (isUndefined(handler))\n return false;\n\n if (isFunction(handler)) {\n switch (arguments.length) {\n // fast cases\n case 1:\n handler.call(this);\n break;\n case 2:\n handler.call(this, arguments[1]);\n break;\n case 3:\n handler.call(this, arguments[1], arguments[2]);\n break;\n // slower\n default:\n len = arguments.length;\n args = new Array(len - 1);\n for (i = 1; i < len; i++)\n args[i - 1] = arguments[i];\n handler.apply(this, args);\n }\n } else if (isObject(handler)) {\n len = arguments.length;\n args = new Array(len - 1);\n for (i = 1; i < len; i++)\n args[i - 1] = arguments[i];\n\n listeners = handler.slice();\n len = listeners.length;\n for (i = 0; i < len; i++)\n listeners[i].apply(this, args);\n }\n\n return true;\n};\n\nEventEmitter.prototype.addListener = function(type, listener) {\n var m;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events)\n this._events = {};\n\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (this._events.newListener)\n this.emit('newListener', type,\n isFunction(listener.listener) ?\n listener.listener : listener);\n\n if (!this._events[type])\n // Optimize the case of one listener. Don't need the extra
2016-02-01 10:02:17 -07:00
"var bundleFn = arguments[3];\nvar sources = arguments[4];\nvar cache = arguments[5];\n\nvar stringify = JSON.stringify;\n\nmodule.exports = function (fn) {\n var keys = [];\n var wkey;\n var cacheKeys = Object.keys(cache);\n\n for (var i = 0, l = cacheKeys.length; i < l; i++) {\n var key = cacheKeys[i];\n var exp = cache[key].exports;\n // Using babel as a transpiler to use esmodule, the export will always\n // be an object with the default export as a property of it. To ensure\n // the existing api and babel esmodule exports are both supported we\n // check for both\n if (exp === fn || exp.default === fn) {\n wkey = key;\n break;\n }\n }\n\n if (!wkey) {\n wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);\n var wcache = {};\n for (var i = 0, l = cacheKeys.length; i < l; i++) {\n var key = cacheKeys[i];\n wcache[key] = key;\n }\n sources[wkey] = [\n Function(['require','module','exports'], '(' + fn + ')(self)'),\n wcache\n ];\n }\n var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);\n\n var scache = {}; scache[wkey] = wkey;\n sources[skey] = [\n Function(['require'], (\n // try to call default if defined to also support babel esmodule\n // exports\n 'var f = require(' + stringify(wkey) + ');' +\n '(f.default ? f.default : f)(self);'\n )),\n scache\n ];\n\n var src = '(' + bundleFn + ')({'\n + Object.keys(sources).map(function (key) {\n return stringify(key) + ':['\n + sources[key][0]\n + ',' + stringify(sources[key][1]) + ']'\n ;\n }).join(',')\n + '},{},[' + stringify(skey) + '])'\n ;\n\n var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;\n\n return new Worker(URL.createObjectURL(\n new Blob([src], { type: 'text/javascript' })\n ));\n};\n",
2016-01-18 12:07:26 -07:00
"/*\n * simple ABR Controller\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\n\nclass AbrController extends EventHandler {\n\n constructor(hls) {\n super(hls, Event.FRAG_LOAD_PROGRESS);\n this.lastfetchlevel = 0;\n this._autoLevelCapping = -1;\n this._nextAutoLevel = -1;\n }\n\n destroy() {\n EventHandler.prototype.destroy.call(this);\n }\n\n onFragLoadProgress(data) {\n var stats = data.stats;\n if (stats.aborted === undefined) {\n this.lastfetchduration = (performance.now() - stats.trequest) / 1000;\n this.lastfetchlevel = data.frag.level;\n this.lastbw = (stats.loaded * 8) / this.lastfetchduration;\n //console.log(`fetchDuration:${this.lastfetchduration},bw:${(this.lastbw/1000).toFixed(0)}/${stats.aborted}`);\n }\n }\n\n /** Return the capping/max level value that could be used by automatic level selection algorithm **/\n get autoLevelCapping() {\n return this._autoLevelCapping;\n }\n\n /** set the capping/max level value that could be used by automatic level selection algorithm **/\n set autoLevelCapping(newLevel) {\n this._autoLevelCapping = newLevel;\n }\n\n get nextAutoLevel() {\n var lastbw = this.lastbw, hls = this.hls,adjustedbw, i, maxAutoLevel;\n if (this._autoLevelCapping === -1) {\n maxAutoLevel = hls.levels.length - 1;\n } else {\n maxAutoLevel = this._autoLevelCapping;\n }\n\n if (this._nextAutoLevel !== -1) {\n var nextLevel = Math.min(this._nextAutoLevel,maxAutoLevel);\n if (nextLevel === this.lastfetchlevel) {\n this._nextAutoLevel = -1;\n } else {\n return nextLevel;\n }\n }\n\n // follow algorithm captured from stagefright :\n // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp\n // Pick the highest bandwidth stream below or equal to estimated bandwidth.\n for (i = 0; i <= maxAutoLevel; i++) {\n // consider only 80% of the available bandwidth, but if we are switching up,\n // be even more conservative (70%) to avoid overestimating and immediately\n // switching back.\n if (i <= this.lastfetchlevel) {\n adjustedbw = 0.8 * lastbw;\n } else {\n adjustedbw = 0.7 * lastbw;\n }\n if (adjustedbw < hls.levels[i].bitrate) {\n return Math.max(0, i - 1);\n }\n }\n return i - 1;\n }\n\n set nextAutoLevel(nextLevel) {\n this._nextAutoLevel = nextLevel;\n }\n}\n\nexport default AbrController;\n\n",
2016-02-01 10:02:17 -07:00
"/*\n * Level Controller\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport {logger} from '../utils/logger';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\nclass LevelController extends EventHandler {\n\n constructor(hls) {\n super(hls,\n Event.MANIFEST_LOADED,\n Event.LEVEL_LOADED,\n Event.ERROR);\n this.ontick = this.tick.bind(this);\n this._manualLevel = this._autoLevelCapping = -1;\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer);\n }\n this._manualLevel = -1;\n }\n\n onManifestLoaded(data) {\n var levels0 = [], levels = [], bitrateStart, i, bitrateSet = {}, videoCodecFound = false, audioCodecFound = false, hls = this.hls;\n\n // regroup redundant level together\n data.levels.forEach(level => {\n if(level.videoCodec) {\n videoCodecFound = true;\n }\n if(level.audioCodec) {\n audioCodecFound = true;\n }\n var redundantLevelId = bitrateSet[level.bitrate];\n if (redundantLevelId === undefined) {\n bitrateSet[level.bitrate] = levels0.length;\n level.url = [level.url];\n level.urlId = 0;\n levels0.push(level);\n } else {\n levels0[redundantLevelId].url.push(level.url);\n }\n });\n\n // remove audio-only level if we also have levels with audio+video codecs signalled\n if(videoCodecFound && audioCodecFound) {\n levels0.forEach(level => {\n if(level.videoCodec) {\n levels.push(level);\n }\n });\n } else {\n levels = levels0;\n }\n\n // only keep level with supported audio/video codecs\n levels = levels.filter(function(level) {\n var checkSupportedAudio = function(codec) { return MediaSource.isTypeSupported(`audio/mp4;codecs=${codec}`);};\n var checkSupportedVideo = function(codec) { return MediaSource.isTypeSupported(`video/mp4;codecs=${codec}`);};\n var audioCodec = level.audioCodec, videoCodec = level.videoCodec;\n\n return (!audioCodec || checkSupportedAudio(audioCodec)) &&\n (!videoCodec || checkSupportedVideo(videoCodec));\n });\n\n if(levels.length) {\n // start bitrate is the first bitrate of the manifest\n bitrateStart = levels[0].bitrate;\n // sort level on bitrate\n levels.sort(function (a, b) {\n return a.bitrate - b.bitrate;\n });\n this._levels = levels;\n // find index of first level in sorted levels\n for (i = 0; i < levels.length; i++) {\n if (levels[i].bitrate === bitrateStart) {\n this._firstLevel = i;\n logger.log(`manifest loaded,${levels.length} level(s) found, first bitrate:${bitrateStart}`);\n break;\n }\n }\n hls.trigger(Event.MANIFEST_PARSED, {levels: this._levels, firstLevel: this._firstLevel, stats: data.stats});\n } else {\n hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.MANIFEST_PARSING_ERROR, fatal: true, url: hls.url, reason: 'no compatible level found in manifest'});\n }\n return;\n }\n\n get levels() {\n return this._levels;\n }\n\n get level() {\n return this._level;\n }\n\n set level(newLevel) {\n if (this._level !== newLevel || this._levels[newLevel].details === undefined) {\n this.setLevelInternal(newLevel);\n }\n }\n\n setLevelInternal(newLevel) {\n // check if level idx is valid\n if (newLevel >= 0 && newLevel < this._levels.length) {\n // stopping live reloading timer if any\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this._level = newLevel;\n logger.log(`switching to level ${newLevel}`);\n this.hls.trigger(Event.LEVEL_SWITCH, {level: newLevel});\n var level = this._levels[newLevel];\n // check if we need to load playlist for this level\n if (level.details === undefined || level.details.live === true) {\n // level not retrieved yet, or live playlist we need to (re)load it\n logger.log(`(re)loading playlist
"/*\n * MSE Media Controller\n*/\n\nimport Demuxer from '../demux/demuxer';\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport {logger} from '../utils/logger';\nimport BinarySearch from '../utils/binary-search';\nimport LevelHelper from '../helper/level-helper';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\nconst State = {\n ERROR : -2,\n STARTING : -1,\n IDLE : 0,\n KEY_LOADING : 1,\n FRAG_LOADING : 2,\n FRAG_LOADING_WAITING_RETRY : 3,\n WAITING_LEVEL : 4,\n PARSING : 5,\n PARSED : 6,\n APPENDING : 7,\n BUFFER_FLUSHING : 8,\n ENDED : 9\n};\n\nclass MSEMediaController extends EventHandler {\n\n constructor(hls) {\n super(hls, Event.MEDIA_ATTACHING,\n Event.MEDIA_DETACHING,\n Event.MANIFEST_PARSED,\n Event.LEVEL_LOADED,\n Event.KEY_LOADED,\n Event.FRAG_LOADED,\n Event.FRAG_PARSING_INIT_SEGMENT,\n Event.FRAG_PARSING_DATA,\n Event.FRAG_PARSED,\n Event.ERROR);\n this.config = hls.config;\n this.audioCodecSwap = false;\n this.ticks = 0;\n // Source Buffer listeners\n this.onsbue = this.onSBUpdateEnd.bind(this);\n this.onsbe = this.onSBUpdateError.bind(this);\n this.ontick = this.tick.bind(this);\n }\n\n destroy() {\n this.stop();\n EventHandler.prototype.destroy.call(this);\n this.state = State.IDLE;\n }\n\n startLoad() {\n if (this.levels && this.media) {\n this.startInternal();\n if (this.lastCurrentTime) {\n logger.log(`seeking @ ${this.lastCurrentTime}`);\n if (!this.lastPaused) {\n logger.log('resuming video');\n this.media.play();\n }\n this.state = State.IDLE;\n } else {\n this.lastCurrentTime = 0;\n this.state = State.STARTING;\n }\n this.nextLoadPosition = this.startPosition = this.lastCurrentTime;\n this.tick();\n } else {\n logger.warn('cannot start loading as either manifest not parsed or video not attached');\n }\n }\n\n startInternal() {\n var hls = this.hls;\n this.stop();\n this.demuxer = new Demuxer(hls);\n this.timer = setInterval(this.ontick, 100);\n this.level = -1;\n this.fragLoadError = 0;\n }\n\n stop() {\n this.mp4segments = [];\n this.flushRange = [];\n this.bufferRange = [];\n this.stalled = false;\n var frag = this.fragCurrent;\n if (frag) {\n if (frag.loader) {\n frag.loader.abort();\n }\n this.fragCurrent = null;\n }\n this.fragPrevious = null;\n if (this.sourceBuffer) {\n for(var type in this.sourceBuffer) {\n var sb = this.sourceBuffer[type];\n try {\n this.mediaSource.removeSourceBuffer(sb);\n sb.removeEventListener('updateend', this.onsbue);\n sb.removeEventListener('error', this.onsbe);\n } catch(err) {\n }\n }\n this.sourceBuffer = null;\n }\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n if (this.demuxer) {\n this.demuxer.destroy();\n this.demuxer = null;\n }\n }\n\n tick() {\n this.ticks++;\n if (this.ticks === 1) {\n this.doTick();\n if (this.ticks > 1) {\n setTimeout(this.tick, 1);\n }\n this.ticks = 0;\n }\n }\n\n doTick() {\n var pos, level, levelDetails, hls = this.hls;\n switch(this.state) {\n case State.ERROR:\n //don't do anything in error state to avoid breaking further ...\n break;\n case State.STARTING:\n // determine load level\n this.startLevel = hls.startLevel;\n if (this.startLevel === -1) {\n // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level\n this.startLevel = 0;\n this.fragBitrateTest = true;\n }\n // set new level to playlist loader : this will trigger start level load\n this.level = hls.nextLoadLevel = this.startLevel;\n this.state = State.WAITING_LEVEL;\n this.loadedmetadata = false;\n break;\n case State.IDLE:\n
"/*\n * Timeline Controller\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport CEA708Interpreter from '../utils/cea-708-interpreter';\n\nclass TimelineController extends EventHandler {\n\n constructor(hls) {\n super(hls, Event.MEDIA_ATTACHING,\n Event.MEDIA_DETACHING,\n Event.FRAG_PARSING_USERDATA,\n Event.MANIFEST_LOADING,\n Event.FRAG_LOADED);\n\n this.hls = hls;\n this.config = hls.config;\n\n if (this.config.enableCEA708Captions)\n {\n this.cea708Interpreter = new CEA708Interpreter();\n }\n }\n\n destroy() {\n EventHandler.prototype.destroy.call(this);\n }\n\n onMediaAttaching(data) {\n var media = this.media = data.media;\n this.cea708Interpreter.attach(media);\n }\n\n onMediaDetaching() {\n this.cea708Interpreter.detach();\n }\n\n onManifestLoading()\n {\n this.lastPts = Number.POSITIVE_INFINITY;\n }\n\n onFragLoaded(data)\n {\n var pts = data.frag.start; //Number.POSITIVE_INFINITY;\n\n // if this is a frag for a previously loaded timerange, remove all captions\n // TODO: consider just removing captions for the timerange\n if (pts <= this.lastPts)\n {\n this.cea708Interpreter.clear();\n }\n\n this.lastPts = pts;\n }\n\n onFragParsingUserdata(data) {\n // push all of the CEA-708 messages into the interpreter\n // immediately. It will create the proper timestamps based on our PTS value\n for (var i=0; i<data.samples.length; i++)\n {\n this.cea708Interpreter.push(data.samples[i].pts, data.samples[i].bytes);\n }\n }\n}\n\nexport default TimelineController;\n",
2015-12-15 22:30:14 -07:00
"/*\n *\n * This file contains an adaptation of the AES decryption algorithm\n * from the Standford Javascript Cryptography Library. That work is\n * covered by the following copyright and permissions notice:\n *\n * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * 1. Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following\n * disclaimer in the documentation and/or other materials provided\n * with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\n * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN\n * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * The views and conclusions contained in the software and documentation\n * are those of the authors and should not be interpreted as representing\n * official policies, either expressed or implied, of the authors.\n */\nclass AES {\n\n /**\n * Schedule out an AES key for both encryption and decryption. This\n * is a low-level class. Use a cipher mode to do bulk encryption.\n *\n * @constructor\n * @param key {Array} The key as an array of 4, 6 or 8 words.\n */\n constructor(key) {\n /**\n * The expanded S-box and inverse S-box tables. These will be computed\n * on the client so that we don't have to send them down the wire.\n *\n * There are two tables, _tables[0] is for encryption and\n * _tables[1] is for decryption.\n *\n * The first 4 sub-tables are the expanded S-box with MixColumns. The\n * last (_tables[01][4]) is the S-box itself.\n *\n * @private\n */\n this._tables = [[[],[],[],[],[]],[[],[],[],[],[]]];\n\n this._precompute();\n\n var i, j, tmp,\n encKey, decKey,\n sbox = this._tables[0][4], decTable = this._tables[1],\n keyLen = key.length, rcon = 1;\n\n if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {\n throw new Error('Invalid aes key size=' + keyLen);\n }\n\n encKey = key.slice(0);\n decKey = [];\n this._key = [encKey, decKey];\n\n // schedule encryption keys\n for (i = keyLen; i < 4 * keyLen + 28; i++) {\n tmp = encKey[i-1];\n\n // apply sbox\n if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {\n tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];\n\n // shift rows and add rcon\n if (i%keyLen === 0) {\n tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;\n rcon = rcon<<1 ^ (rcon>>7)*283;\n }\n }\n\n encKey[i] = encKey[i-keyLen] ^ tmp;\n }\n\n // schedule decryption keys\n for (j = 0; i; j++, i--) {\n tmp = encKey[j&3 ? i : i - 4];\n if (i<=4 || j<4) {\n decKey[j] = tmp;\n } else {\n decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^\n decTable[1][sbox[tmp>>16 & 255]] ^\n decTable[2][sbox[tmp>>8 & 255]] ^\n decTable[3][sbox[tmp & 255]];\n }\n }\n }\n\n /**\n * Expand the S-box tables.\n *\n * @private\n */\n _precompute() {\n var encTable = this._tables[0], decTable = this._tables[1],\n sbox = encTable[4], sboxInv = d
2016-01-13 13:58:12 -07:00
"/*\n *\n * This file contains an adaptation of the AES decryption algorithm\n * from the Standford Javascript Cryptography Library. That work is\n * covered by the following copyright and permissions notice:\n *\n * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n * 1. Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following\n * disclaimer in the documentation and/or other materials provided\n * with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\n * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\n * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN\n * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * The views and conclusions contained in the software and documentation\n * are those of the authors and should not be interpreted as representing\n * official policies, either expressed or implied, of the authors.\n */\n\nimport AES from './aes';\n\nclass AES128Decrypter {\n\n constructor(key, initVector) {\n this.key = key;\n this.iv = initVector;\n }\n\n /**\n * Convert network-order (big-endian) bytes into their little-endian\n * representation.\n */\n ntoh(word) {\n return (word << 24) |\n ((word & 0xff00) << 8) |\n ((word & 0xff0000) >> 8) |\n (word >>> 24);\n }\n\n\n /**\n * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.\n * @param encrypted {Uint8Array} the encrypted bytes\n * @param key {Uint32Array} the bytes of the decryption key\n * @param initVector {Uint32Array} the initialization vector (IV) to\n * use for the first round of CBC.\n * @return {Uint8Array} the decrypted bytes\n *\n * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard\n * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29\n * @see https://tools.ietf.org/html/rfc2315\n */\n doDecrypt(encrypted, key, initVector) {\n var\n // word-level access to the encrypted bytes\n encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2),\n\n decipher = new AES(Array.prototype.slice.call(key)),\n\n // byte and word-level access for the decrypted output\n decrypted = new Uint8Array(encrypted.byteLength),\n decrypted32 = new Int32Array(decrypted.buffer),\n\n // temporary variables for working with the IV, encrypted, and\n // decrypted data\n init0, init1, init2, init3,\n encrypted0, encrypted1, encrypted2, encrypted3,\n\n // iteration variable\n wordIx;\n\n // pull out the words of the IV to ensure we don't modify the\n // passed-in reference and easier access\n init0 = ~~initVector[0];\n init1 = ~~initVector[1];\n init2 = ~~initVector[2];\n init3 = ~~initVector[3];\n\n // decrypt four word sequences, applying cipher-block chaining (CBC)\n // to each decrypted block\n for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {\n // convert big-endian (network order) words into little-endian\n // (javascript order)\n encrypted0 = ~~this.ntoh(encrypted32[wordIx]);\n encrypted1 = ~~this.ntoh(encrypted32[wordIx + 1]);\n
2015-12-15 22:30:14 -07:00
"/*\n * AES128 decryption.\n */\n\nimport AES128Decrypter from './aes128-decrypter';\nimport {ErrorTypes, ErrorDetails} from '../errors';\nimport {logger} from '../utils/logger';\n\nclass Decrypter {\n\n constructor(hls) {\n this.hls = hls;\n try {\n const browserCrypto = window ? window.crypto : crypto;\n this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;\n this.disableWebCrypto = !this.subtle;\n } catch (e) {\n this.disableWebCrypto = true;\n }\n }\n\n destroy() {\n }\n\n decrypt(data, key, iv, callback) {\n if (this.disableWebCrypto && this.hls.config.enableSoftwareAES) {\n this.decryptBySoftware(data, key, iv, callback);\n } else {\n this.decryptByWebCrypto(data, key, iv, callback);\n }\n }\n\n decryptByWebCrypto(data, key, iv, callback) {\n logger.log('decrypting by WebCrypto API');\n\n this.subtle.importKey('raw', key, { name : 'AES-CBC', length : 128 }, false, ['decrypt']).\n then((importedKey) => {\n this.subtle.decrypt({ name : 'AES-CBC', iv : iv.buffer }, importedKey, data).\n then(callback).\n catch ((err) => {\n this.onWebCryptoError(err, data, key, iv, callback);\n });\n }).\n catch ((err) => {\n this.onWebCryptoError(err, data, key, iv, callback);\n });\n }\n\n decryptBySoftware(data, key8, iv8, callback) {\n logger.log('decrypting by JavaScript Implementation');\n\n var view = new DataView(key8.buffer);\n var key = new Uint32Array([\n view.getUint32(0),\n view.getUint32(4),\n view.getUint32(8),\n view.getUint32(12)\n ]);\n\n view = new DataView(iv8.buffer);\n var iv = new Uint32Array([\n view.getUint32(0),\n view.getUint32(4),\n view.getUint32(8),\n view.getUint32(12)\n ]);\n\n var decrypter = new AES128Decrypter(key, iv);\n callback(decrypter.decrypt(data).buffer);\n }\n\n onWebCryptoError(err, data, key, iv, callback) {\n if (this.hls.config.enableSoftwareAES) {\n logger.log('disabling to use WebCrypto API');\n this.disableWebCrypto = true;\n this.decryptBySoftware(data, key, iv, callback);\n }\n else {\n logger.error(`decrypting error : ${err.message}`);\n this.hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details : ErrorDetails.FRAG_DECRYPT_ERROR, fatal : true, reason : err.message});\n }\n }\n\n}\n\nexport default Decrypter;\n",
2016-01-13 13:58:12 -07:00
"/**\n * AAC demuxer\n */\nimport ADTS from './adts';\nimport {logger} from '../utils/logger';\nimport ID3 from '../demux/id3';\n\n class AACDemuxer {\n\n constructor(observer,remuxerClass) {\n this.observer = observer;\n this.remuxerClass = remuxerClass;\n this.remuxer = new this.remuxerClass(observer);\n this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};\n }\n\n static probe(data) {\n // check if data contains ID3 timestamp and ADTS sync worc\n var id3 = new ID3(data), adtsStartOffset,len;\n if(id3.hasTimeStamp) {\n // look for ADTS header (0xFFFx)\n for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) {\n if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) {\n //logger.log('ADTS sync word found !');\n return true;\n }\n }\n }\n return false;\n }\n\n\n // feed incoming data to the front of the parsing pipeline\n push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {\n var track = this._aacTrack,\n id3 = new ID3(data),\n pts = 90*id3.timeStamp,\n config, adtsFrameSize, adtsStartOffset, adtsHeaderLen, stamp, nbSamples, len, aacSample;\n // look for ADTS header (0xFFFx)\n for (adtsStartOffset = id3.length, len = data.length; adtsStartOffset < len - 1; adtsStartOffset++) {\n if ((data[adtsStartOffset] === 0xff) && (data[adtsStartOffset+1] & 0xf0) === 0xf0) {\n break;\n }\n }\n\n if (!track.audiosamplerate) {\n config = ADTS.getAudioConfig(this.observer,data, adtsStartOffset, audioCodec);\n track.config = config.config;\n track.audiosamplerate = config.samplerate;\n track.channelCount = config.channelCount;\n track.codec = config.codec;\n track.timescale = this.remuxer.timescale;\n track.duration = this.remuxer.timescale * duration;\n logger.log(`parsed codec:${track.codec},rate:${config.samplerate},nb channel:${config.channelCount}`);\n }\n nbSamples = 0;\n while ((adtsStartOffset + 5) < len) {\n // retrieve frame size\n adtsFrameSize = ((data[adtsStartOffset + 3] & 0x03) << 11);\n // byte 4\n adtsFrameSize |= (data[adtsStartOffset + 4] << 3);\n // byte 5\n adtsFrameSize |= ((data[adtsStartOffset + 5] & 0xE0) >>> 5);\n adtsHeaderLen = (!!(data[adtsStartOffset + 1] & 0x01) ? 7 : 9);\n adtsFrameSize -= adtsHeaderLen;\n stamp = Math.round(pts + nbSamples * 1024 * 90000 / track.audiosamplerate);\n //stamp = pes.pts;\n //console.log('AAC frame, offset/length/pts:' + (adtsStartOffset+7) + '/' + adtsFrameSize + '/' + stamp.toFixed(0));\n if ((adtsFrameSize > 0) && ((adtsStartOffset + adtsHeaderLen + adtsFrameSize) <= len)) {\n aacSample = {unit: data.subarray(adtsStartOffset + adtsHeaderLen, adtsStartOffset + adtsHeaderLen + adtsFrameSize), pts: stamp, dts: stamp};\n track.samples.push(aacSample);\n track.len += adtsFrameSize;\n adtsStartOffset += adtsFrameSize + adtsHeaderLen;\n nbSamples++;\n // look for ADTS header (0xFFFx)\n for ( ; adtsStartOffset < (len - 1); adtsStartOffset++) {\n if ((data[adtsStartOffset] === 0xff) && ((data[adtsStartOffset + 1] & 0xf0) === 0xf0)) {\n break;\n }\n }\n } else {\n break;\n }\n }\n this.remuxer.remux(this._aacTrack,{samples : []}, {samples : [ { pts: pts, dts : pts, unit : id3.payload} ]}, timeOffset);\n }\n\n destroy() {\n }\n\n}\n\nexport default AACDemuxer;\n",
"/**\n * ADTS parser helper\n */\nimport {logger} from '../utils/logger';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\n class ADTS {\n\n static getAudioConfig(observer, data, offset, audioCodec) {\n var adtsObjectType, // :int\n adtsSampleingIndex, // :int\n adtsExtensionSampleingIndex, // :int\n adtsChanelConfig, // :int\n config,\n userAgent = navigator.userAgent.toLowerCase(),\n adtsSampleingRates = [\n 96000, 88200,\n 64000, 48000,\n 44100, 32000,\n 24000, 22050,\n 16000, 12000,\n 11025, 8000,\n 7350];\n // byte 2\n adtsObjectType = ((data[offset + 2] & 0xC0) >>> 6) + 1;\n adtsSampleingIndex = ((data[offset + 2] & 0x3C) >>> 2);\n if(adtsSampleingIndex > adtsSampleingRates.length-1) {\n observer.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: `invalid ADTS sampling index:${adtsSampleingIndex}`});\n return;\n }\n adtsChanelConfig = ((data[offset + 2] & 0x01) << 2);\n // byte 3\n adtsChanelConfig |= ((data[offset + 3] & 0xC0) >>> 6);\n logger.log(`manifest codec:${audioCodec},ADTS data:type:${adtsObjectType},sampleingIndex:${adtsSampleingIndex}[${adtsSampleingRates[adtsSampleingIndex]}Hz],channelConfig:${adtsChanelConfig}`);\n // firefox: freq less than 24kHz = AAC SBR (HE-AAC)\n if (userAgent.indexOf('firefox') !== -1) {\n if (adtsSampleingIndex >= 6) {\n adtsObjectType = 5;\n config = new Array(4);\n // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies\n // there is a factor 2 between frame sample rate and output sample rate\n // multiply frequency by 2 (see table below, equivalent to substract 3)\n adtsExtensionSampleingIndex = adtsSampleingIndex - 3;\n } else {\n adtsObjectType = 2;\n config = new Array(2);\n adtsExtensionSampleingIndex = adtsSampleingIndex;\n }\n // Android : always use AAC\n } else if (userAgent.indexOf('android') !== -1) {\n adtsObjectType = 2;\n config = new Array(2);\n adtsExtensionSampleingIndex = adtsSampleingIndex;\n } else {\n /* for other browsers (chrome ...)\n always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)\n */\n adtsObjectType = 5;\n config = new Array(4);\n // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)\n if ((audioCodec && ((audioCodec.indexOf('mp4a.40.29') !== -1) ||\n (audioCodec.indexOf('mp4a.40.5') !== -1))) ||\n (!audioCodec && adtsSampleingIndex >= 6)) {\n // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies\n // there is a factor 2 between frame sample rate and output sample rate\n // multiply frequency by 2 (see table below, equivalent to substract 3)\n adtsExtensionSampleingIndex = adtsSampleingIndex - 3;\n } else {\n // if (manifest codec is AAC) AND (frequency less than 24kHz OR nb channel is 1) OR (manifest codec not specified and mono audio)\n // Chrome fails to play back with AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.\n if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSampleingIndex >= 6 || adtsChanelConfig === 1) ||\n (!audioCodec && adtsChanelConfig === 1)) {\n adtsObjectType = 2;\n config = new Array(2);\n }\n adtsExtensionSampleingIndex = adtsSampleingIndex;\n }\n }\n /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config\n ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()\n Audio Profile / Audio Object Type\n 0: Null\n 1: AAC Main\n 2: AAC LC (Low Complexity)\n 3: AAC SSR (S
2015-12-15 22:30:14 -07:00
"/* inline demuxer.\n * probe fragments and instantiate appropriate demuxer depending on content type (TSDemuxer, AACDemuxer, ...)\n */\n\nimport Event from '../events';\nimport {ErrorTypes, ErrorDetails} from '../errors';\nimport AACDemuxer from '../demux/aacdemuxer';\nimport TSDemuxer from '../demux/tsdemuxer';\n\nclass DemuxerInline {\n\n constructor(hls,remuxer) {\n this.hls = hls;\n this.remuxer = remuxer;\n }\n\n destroy() {\n var demuxer = this.demuxer;\n if (demuxer) {\n demuxer.destroy();\n }\n }\n\n push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {\n var demuxer = this.demuxer;\n if (!demuxer) {\n // probe for content type\n if (TSDemuxer.probe(data)) {\n demuxer = this.demuxer = new TSDemuxer(this.hls,this.remuxer);\n } else if(AACDemuxer.probe(data)) {\n demuxer = this.demuxer = new AACDemuxer(this.hls,this.remuxer);\n } else {\n this.hls.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, reason: 'no demux matching with content found'});\n return;\n }\n }\n demuxer.push(data,audioCodec,videoCodec,timeOffset,cc,level,sn,duration);\n }\n}\n\nexport default DemuxerInline;\n",
2016-02-01 10:02:17 -07:00
"/* demuxer web worker.\n * - listen to worker message, and trigger DemuxerInline upon reception of Fragments.\n * - provides MP4 Boxes back to main thread using [transferable objects](https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast) in order to minimize message passing overhead.\n */\n\n import DemuxerInline from '../demux/demuxer-inline';\n import Event from '../events';\n import EventEmitter from 'events';\n import MP4Remuxer from '../remux/mp4-remuxer';\n\nvar DemuxerWorker = function (self) {\n // observer setup\n var observer = new EventEmitter();\n observer.trigger = function trigger (event, ...data) {\n observer.emit(event, event, ...data);\n };\n\n observer.off = function off (event, ...data) {\n observer.removeListener(event, ...data);\n };\n self.addEventListener('message', function (ev) {\n //console.log('demuxer cmd:' + ev.data.cmd);\n switch (ev.data.cmd) {\n case 'init':\n self.demuxer = new DemuxerInline(observer,MP4Remuxer);\n break;\n case 'demux':\n var data = ev.data;\n self.demuxer.push(new Uint8Array(data.data), data.audioCodec, data.videoCodec, data.timeOffset, data.cc, data.level, data.sn, data.duration);\n break;\n default:\n break;\n }\n });\n\n // listen to events triggered by TS Demuxer\n observer.on(Event.FRAG_PARSING_INIT_SEGMENT, function(ev, data) {\n var objData = {event: ev};\n var objTransferable = [];\n if (data.audioCodec) {\n objData.audioCodec = data.audioCodec;\n objData.audioMoov = data.audioMoov.buffer;\n objData.audioChannelCount = data.audioChannelCount;\n objTransferable.push(objData.audioMoov);\n }\n if (data.videoCodec) {\n objData.videoCodec = data.videoCodec;\n objData.videoMoov = data.videoMoov.buffer;\n objData.videoWidth = data.videoWidth;\n objData.videoHeight = data.videoHeight;\n objTransferable.push(objData.videoMoov);\n }\n // pass moov as transferable object (no copy)\n self.postMessage(objData,objTransferable);\n });\n\n observer.on(Event.FRAG_PARSING_DATA, function(ev, data) {\n var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, moof: data.moof.buffer, mdat: data.mdat.buffer, nb: data.nb};\n // pass moof/mdat data as transferable object (no copy)\n self.postMessage(objData, [objData.moof, objData.mdat]);\n });\n\n observer.on(Event.FRAG_PARSED, function(event) {\n self.postMessage({event: event});\n });\n\n observer.on(Event.ERROR, function(event, data) {\n self.postMessage({event: event, data: data});\n });\n\n observer.on(Event.FRAG_PARSING_METADATA, function(event, data) {\n var objData = {event: event, samples: data.samples};\n self.postMessage(objData);\n });\n\n observer.on(Event.FRAG_PARSING_USERDATA, function(event, data) {\n var objData = {event: event, samples: data.samples};\n self.postMessage(objData);\n });\n\n};\n\nexport default DemuxerWorker;\n\n",
"import Event from '../events';\nimport DemuxerInline from '../demux/demuxer-inline';\nimport DemuxerWorker from '../demux/demuxer-worker';\nimport {logger} from '../utils/logger';\nimport MP4Remuxer from '../remux/mp4-remuxer';\nimport Decrypter from '../crypt/decrypter';\n\nclass Demuxer {\n\n constructor(hls) {\n this.hls = hls;\n if (hls.config.enableWorker && (typeof(Worker) !== 'undefined')) {\n logger.log('demuxing in webworker');\n try {\n var work = require('webworkify');\n this.w = work(DemuxerWorker);\n this.onwmsg = this.onWorkerMessage.bind(this);\n this.w.addEventListener('message', this.onwmsg);\n this.w.postMessage({cmd: 'init'});\n } catch(err) {\n logger.error('error while initializing DemuxerWorker, fallback on DemuxerInline');\n this.demuxer = new DemuxerInline(hls,MP4Remuxer);\n }\n } else {\n this.demuxer = new DemuxerInline(hls,MP4Remuxer);\n }\n this.demuxInitialized = true;\n }\n\n destroy() {\n if (this.w) {\n this.w.removeEventListener('message', this.onwmsg);\n this.w.terminate();\n this.w = null;\n } else {\n this.demuxer.destroy();\n this.demuxer = null;\n }\n if (this.decrypter) {\n this.decrypter.destroy();\n this.decrypter = null;\n }\n }\n\n pushDecrypted(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {\n if (this.w) {\n // post fragment payload as transferable objects (no copy)\n this.w.postMessage({cmd: 'demux', data: data, audioCodec: audioCodec, videoCodec: videoCodec, timeOffset: timeOffset, cc: cc, level: level, sn : sn, duration: duration}, [data]);\n } else {\n this.demuxer.push(new Uint8Array(data), audioCodec, videoCodec, timeOffset, cc, level, sn, duration);\n }\n }\n\n push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration, decryptdata) {\n if ((data.byteLength > 0) && (decryptdata != null) && (decryptdata.key != null) && (decryptdata.method === 'AES-128')) {\n if (this.decrypter == null) {\n this.decrypter = new Decrypter(this.hls);\n }\n \n var localthis = this;\n this.decrypter.decrypt(data, decryptdata.key, decryptdata.iv, function(decryptedData){\n localthis.pushDecrypted(decryptedData, audioCodec, videoCodec, timeOffset, cc, level, sn, duration);\n });\n } else {\n this.pushDecrypted(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration);\n }\n }\n\n onWorkerMessage(ev) {\n //console.log('onWorkerMessage:' + ev.data.event);\n switch(ev.data.event) {\n case Event.FRAG_PARSING_INIT_SEGMENT:\n var obj = {};\n if (ev.data.audioMoov) {\n obj.audioMoov = new Uint8Array(ev.data.audioMoov);\n obj.audioCodec = ev.data.audioCodec;\n obj.audioChannelCount = ev.data.audioChannelCount;\n }\n if (ev.data.videoMoov) {\n obj.videoMoov = new Uint8Array(ev.data.videoMoov);\n obj.videoCodec = ev.data.videoCodec;\n obj.videoWidth = ev.data.videoWidth;\n obj.videoHeight = ev.data.videoHeight;\n }\n this.hls.trigger(Event.FRAG_PARSING_INIT_SEGMENT, obj);\n break;\n case Event.FRAG_PARSING_DATA:\n this.hls.trigger(Event.FRAG_PARSING_DATA,{\n moof: new Uint8Array(ev.data.moof),\n mdat: new Uint8Array(ev.data.mdat),\n startPTS: ev.data.startPTS,\n endPTS: ev.data.endPTS,\n startDTS: ev.data.startDTS,\n endDTS: ev.data.endDTS,\n type: ev.data.type,\n nb: ev.data.nb\n });\n break;\n case Event.FRAG_PARSING_METADATA:\n this.hls.trigger(Event.FRAG_PARSING_METADATA, {\n samples: ev.data.samples\n });\n break;\n case Event.FRAG_PARSING_USERDATA:\n this.hls.trigger(Event.FRAG_PARSING_USERDATA, {\n samples: ev.data.samples\n });\n break;\n default:\n this.hls.trigger(ev.data.event, ev.data.data
"/**\n * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.\n*/\n\nimport {logger} from '../utils/logger';\n\nclass ExpGolomb {\n\n constructor(data) {\n this.data = data;\n // the number of bytes left to examine in this.data\n this.bytesAvailable = this.data.byteLength;\n // the current word being examined\n this.word = 0; // :uint\n // the number of bits left to examine in the current word\n this.bitsAvailable = 0; // :uint\n }\n\n // ():void\n loadWord() {\n var\n position = this.data.byteLength - this.bytesAvailable,\n workingBytes = new Uint8Array(4),\n availableBytes = Math.min(4, this.bytesAvailable);\n if (availableBytes === 0) {\n throw new Error('no bytes available');\n }\n workingBytes.set(this.data.subarray(position, position + availableBytes));\n this.word = new DataView(workingBytes.buffer).getUint32(0);\n // track the amount of this.data that has been processed\n this.bitsAvailable = availableBytes * 8;\n this.bytesAvailable -= availableBytes;\n }\n\n // (count:int):void\n skipBits(count) {\n var skipBytes; // :int\n if (this.bitsAvailable > count) {\n this.word <<= count;\n this.bitsAvailable -= count;\n } else {\n count -= this.bitsAvailable;\n skipBytes = count >> 3;\n count -= (skipBytes >> 3);\n this.bytesAvailable -= skipBytes;\n this.loadWord();\n this.word <<= count;\n this.bitsAvailable -= count;\n }\n }\n\n // (size:int):uint\n readBits(size) {\n var\n bits = Math.min(this.bitsAvailable, size), // :uint\n valu = this.word >>> (32 - bits); // :uint\n if (size > 32) {\n logger.error('Cannot read more than 32 bits at a time');\n }\n this.bitsAvailable -= bits;\n if (this.bitsAvailable > 0) {\n this.word <<= bits;\n } else if (this.bytesAvailable > 0) {\n this.loadWord();\n }\n bits = size - bits;\n if (bits > 0) {\n return valu << bits | this.readBits(bits);\n } else {\n return valu;\n }\n }\n\n // ():uint\n skipLZ() {\n var leadingZeroCount; // :uint\n for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {\n if (0 !== (this.word & (0x80000000 >>> leadingZeroCount))) {\n // the first bit of working word is 1\n this.word <<= leadingZeroCount;\n this.bitsAvailable -= leadingZeroCount;\n return leadingZeroCount;\n }\n }\n // we exhausted word and still have not found a 1\n this.loadWord();\n return leadingZeroCount + this.skipLZ();\n }\n\n // ():void\n skipUEG() {\n this.skipBits(1 + this.skipLZ());\n }\n\n // ():void\n skipEG() {\n this.skipBits(1 + this.skipLZ());\n }\n\n // ():uint\n readUEG() {\n var clz = this.skipLZ(); // :uint\n return this.readBits(clz + 1) - 1;\n }\n\n // ():int\n readEG() {\n var valu = this.readUEG(); // :int\n if (0x01 & valu) {\n // the number is odd if the low order bit is set\n return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2\n } else {\n return -1 * (valu >>> 1); // divide by two then make it negative\n }\n }\n\n // Some convenience functions\n // :Boolean\n readBoolean() {\n return 1 === this.readBits(1);\n }\n\n // ():int\n readUByte() {\n return this.readBits(8);\n }\n\n // ():int\n readUShort() {\n return this.readBits(16);\n }\n // ():int\n readUInt() {\n return this.readBits(32);\n }\n\n /**\n * Advance the ExpGolomb decoder past a scaling list. The scaling\n * list is optionally transmitted as part of a sequence parameter\n * set and is not relevant to transmuxing.\n * @param count {number} the number of entries in this scaling list\n * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1\n */\n skipScalingList(count) {\n var\n lastScale = 8,\n nextScale = 8,\n j,\n deltaScale;\n for (j = 0; j < count; j++) {\n if (nextScale !== 0) {\n deltaScale = this.readEG();\n nextScale =
2015-12-23 10:46:01 -07:00
"/**\n * ID3 parser\n */\nimport {logger} from '../utils/logger';\n//import Hex from '../utils/hex';\n\n class ID3 {\n\n constructor(data) {\n this._hasTimeStamp = false;\n var offset = 0, byte1,byte2,byte3,byte4,tagSize,endPos,header,len;\n do {\n header = this.readUTF(data,offset,3);\n offset+=3;\n // first check for ID3 header\n if (header === 'ID3') {\n // skip 24 bits\n offset += 3;\n // retrieve tag(s) length\n byte1 = data[offset++] & 0x7f;\n byte2 = data[offset++] & 0x7f;\n byte3 = data[offset++] & 0x7f;\n byte4 = data[offset++] & 0x7f;\n tagSize = (byte1 << 21) + (byte2 << 14) + (byte3 << 7) + byte4;\n endPos = offset + tagSize;\n //logger.log(`ID3 tag found, size/end: ${tagSize}/${endPos}`);\n\n // read ID3 tags\n this._parseID3Frames(data, offset,endPos);\n offset = endPos;\n } else if (header === '3DI') {\n // http://id3.org/id3v2.4.0-structure chapter 3.4. ID3v2 footer\n offset += 7;\n logger.log(`3DI footer found, end: ${offset}`);\n } else {\n offset -= 3;\n len = offset;\n if (len) {\n //logger.log(`ID3 len: ${len}`);\n if (!this.hasTimeStamp) {\n logger.warn('ID3 tag found, but no timestamp');\n }\n this._length = len;\n this._payload = data.subarray(0,len);\n }\n return;\n }\n } while (true);\n }\n\n readUTF(data,start,len) {\n\n var result = '',offset = start, end = start + len;\n do {\n result += String.fromCharCode(data[offset++]);\n } while(offset < end);\n return result;\n }\n\n _parseID3Frames(data,offset,endPos) {\n var tagId,tagLen,tagStart,tagFlags,timestamp;\n while(offset + 8 <= endPos) {\n tagId = this.readUTF(data,offset,4);\n offset +=4;\n\n tagLen = data[offset++] << 24 +\n data[offset++] << 16 +\n data[offset++] << 8 +\n data[offset++];\n\n tagFlags = data[offset++] << 8 +\n data[offset++];\n\n tagStart = offset;\n //logger.log(\"ID3 tag id:\" + tagId);\n switch(tagId) {\n case 'PRIV':\n //logger.log('parse frame:' + Hex.hexDump(data.subarray(offset,endPos)));\n // owner should be \"com.apple.streaming.transportStreamTimestamp\"\n if (this.readUTF(data,offset,44) === 'com.apple.streaming.transportStreamTimestamp') {\n offset+=44;\n // smelling even better ! we found the right descriptor\n // skip null character (string end) + 3 first bytes\n offset+= 4;\n\n // timestamp is 33 bit expressed as a big-endian eight-octet number, with the upper 31 bits set to zero.\n var pts33Bit = data[offset++] & 0x1;\n this._hasTimeStamp = true;\n\n timestamp = ((data[offset++] << 23) +\n (data[offset++] << 15) +\n (data[offset++] << 7) +\n data[offset++]) /45;\n\n if (pts33Bit) {\n timestamp += 47721858.84; // 2^32 / 90\n }\n timestamp = Math.round(timestamp);\n logger.trace(`ID3 timestamp found: ${timestamp}`);\n this._timeStamp = timestamp;\n }\n break;\n default:\n break;\n }\n }\n }\n\n get hasTimeStamp() {\n return this._hasTimeStamp;\n }\n\n get timeStamp() {\n return this._timeStamp;\n }\n\n get length() {\n return this._length;\n }\n\n get payload() {\n return this._payload;\n }\n\n}\n\nexport default ID3;\n\n",
2016-02-01 10:02:17 -07:00
"/**\n * highly optimized TS demuxer:\n * parse PAT, PMT\n * extract PES packet from audio and video PIDs\n * extract AVC/H264 NAL units and AAC/ADTS samples from PES packet\n * trigger the remuxer upon parsing completion\n * it also tries to workaround as best as it can audio codec switch (HE-AAC to AAC and vice versa), without having to restart the MediaSource.\n * it also controls the remuxing process :\n * upon discontinuity or level switch detection, it will also notifies the remuxer so that it can reset its state.\n*/\n\n import ADTS from './adts';\n import Event from '../events';\n import ExpGolomb from './exp-golomb';\n// import Hex from '../utils/hex';\n import {logger} from '../utils/logger';\n import {ErrorTypes, ErrorDetails} from '../errors';\n\n class TSDemuxer {\n\n constructor(observer,remuxerClass) {\n this.observer = observer;\n this.remuxerClass = remuxerClass;\n this.lastCC = 0;\n this.remuxer = new this.remuxerClass(observer);\n this._userData = [];\n }\n\n static probe(data) {\n // a TS fragment should contain at least 3 TS packets, a PAT, a PMT, and one PID, each starting with 0x47\n if (data.length >= 3*188 && data[0] === 0x47 && data[188] === 0x47 && data[2*188] === 0x47) {\n return true;\n } else {\n return false;\n }\n }\n\n switchLevel() {\n this.pmtParsed = false;\n this._pmtId = -1;\n this.lastAacPTS = null;\n this.aacOverFlow = null;\n this._avcTrack = {type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0};\n this._aacTrack = {type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};\n this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0};\n this._txtTrack = {type: 'text', id: -1, sequenceNumber: 0, samples: [], len: 0};\n this.remuxer.switchLevel();\n }\n\n insertDiscontinuity() {\n this.switchLevel();\n this.remuxer.insertDiscontinuity();\n }\n\n // feed incoming data to the front of the parsing pipeline\n push(data, audioCodec, videoCodec, timeOffset, cc, level, sn, duration) {\n var avcData, aacData, id3Data,\n start, len = data.length, stt, pid, atf, offset;\n this.audioCodec = audioCodec;\n this.videoCodec = videoCodec;\n this.timeOffset = timeOffset;\n this._duration = duration;\n this.contiguous = false;\n if (cc !== this.lastCC) {\n logger.log('discontinuity detected');\n this.insertDiscontinuity();\n this.lastCC = cc;\n } else if (level !== this.lastLevel) {\n logger.log('level switch detected');\n this.switchLevel();\n this.lastLevel = level;\n } else if (sn === (this.lastSN+1)) {\n this.contiguous = true;\n }\n this.lastSN = sn;\n\n if(!this.contiguous) {\n // flush any partial content\n this.aacOverFlow = null;\n }\n\n var pmtParsed = this.pmtParsed,\n avcId = this._avcTrack.id,\n aacId = this._aacTrack.id,\n id3Id = this._id3Track.id;\n\n // don't parse last TS packet if incomplete\n len -= len % 188;\n // loop through TS packets\n for (start = 0; start < len; start += 188) {\n if (data[start] === 0x47) {\n stt = !!(data[start + 1] & 0x40);\n // pid is a 13-bit field starting at the last bit of TS[1]\n pid = ((data[start + 1] & 0x1f) << 8) + data[start + 2];\n atf = (data[start + 3] & 0x30) >> 4;\n // if an adaption field is present, its length is specified by the fifth byte of the TS packet header.\n if (atf > 1) {\n offset = start + 5 + data[start + 4];\n // continue if there is only adaptation field\n if (offset === (start + 188)) {\n continue;\n }\n } else {\n offset = start + 4;\n }\n if (pmtParsed) {\n if (pid === avcId) {\n if (stt) {\n if (avcData) {\n this._parseAVCPES(this._parsePES(avcData));\n }\n avcData = {data: [], size: 0};\n }\n if (avcData) {\n avcData.data.push(dat
2016-01-25 13:28:29 -07:00
"export const ErrorTypes = {\n // Identifier for a network error (loading error / timeout ...)\n NETWORK_ERROR: 'hlsNetworkError',\n // Identifier for a media Error (video/parsing/mediasource error)\n MEDIA_ERROR: 'hlsMediaError',\n // Identifier for all other errors\n OTHER_ERROR: 'hlsOtherError'\n};\n\nexport const ErrorDetails = {\n // Identifier for a manifest load error - data: { url : faulty URL, response : XHR response}\n MANIFEST_LOAD_ERROR: 'manifestLoadError',\n // Identifier for a manifest load timeout - data: { url : faulty URL, response : XHR response}\n MANIFEST_LOAD_TIMEOUT: 'manifestLoadTimeOut',\n // Identifier for a manifest parsing error - data: { url : faulty URL, reason : error reason}\n MANIFEST_PARSING_ERROR: 'manifestParsingError',\n // Identifier for playlist load error - data: { url : faulty URL, response : XHR response}\n LEVEL_LOAD_ERROR: 'levelLoadError',\n // Identifier for playlist load timeout - data: { url : faulty URL, response : XHR response}\n LEVEL_LOAD_TIMEOUT: 'levelLoadTimeOut',\n // Identifier for a level switch error - data: { level : faulty level Id, event : error description}\n LEVEL_SWITCH_ERROR: 'levelSwitchError',\n // Identifier for fragment load error - data: { frag : fragment object, response : XHR response}\n FRAG_LOAD_ERROR: 'fragLoadError',\n // Identifier for fragment loop loading error - data: { frag : fragment object}\n FRAG_LOOP_LOADING_ERROR: 'fragLoopLoadingError',\n // Identifier for fragment load timeout error - data: { frag : fragment object}\n FRAG_LOAD_TIMEOUT: 'fragLoadTimeOut',\n // Identifier for a fragment decryption error event - data: parsing error description\n FRAG_DECRYPT_ERROR: 'fragDecryptError',\n // Identifier for a fragment parsing error event - data: parsing error description\n FRAG_PARSING_ERROR: 'fragParsingError',\n // Identifier for decrypt key load error - data: { frag : fragment object, response : XHR response}\n KEY_LOAD_ERROR: 'keyLoadError',\n // Identifier for decrypt key load timeout error - data: { frag : fragment object}\n KEY_LOAD_TIMEOUT: 'keyLoadTimeOut',\n // Identifier for a buffer append error - data: append error description\n BUFFER_APPEND_ERROR: 'bufferAppendError',\n // Identifier for a buffer appending error event - data: appending error description\n BUFFER_APPENDING_ERROR: 'bufferAppendingError',\n // Identifier for a buffer stalled error event\n BUFFER_STALLED_ERROR: 'bufferStalledError'\n};\n",
2016-01-18 12:07:26 -07:00
"/*\n*\n* All objects in the event handling chain should inherit from this class\n*\n*/\n\n//import {logger} from './utils/logger';\n\nclass EventHandler {\n\n constructor(hls, ...events) {\n this.hls = hls;\n this.onEvent = this.onEvent.bind(this);\n this.handledEvents = events;\n this.useGenericHandler = true;\n\n this.registerListeners();\n }\n\n destroy() {\n this.unregisterListeners();\n }\n\n isEventHandler() {\n return typeof this.handledEvents === 'object' && this.handledEvents.length && typeof this.onEvent === 'function';\n }\n\n registerListeners() {\n if (this.isEventHandler()) {\n this.handledEvents.forEach(function(event) {\n if (event === 'hlsEventGeneric') {\n throw new Error('Forbidden event name: ' + event);\n }\n this.hls.on(event, this.onEvent);\n }.bind(this));\n }\n }\n\n unregisterListeners() {\n if (this.isEventHandler()) {\n this.handledEvents.forEach(function(event) {\n this.hls.off(event, this.onEvent);\n }.bind(this));\n }\n }\n\n /*\n * arguments: event (string), data (any)\n */\n onEvent(event, data) {\n this.onEventGeneric(event, data);\n }\n\n onEventGeneric(event, data) {\n var eventToFunction = function(event, data) {\n var funcName = 'on' + event.replace('hls', '');\n if (typeof this[funcName] !== 'function') {\n throw new Error(`Event ${event} has no generic handler in this ${this.constructor.name} class (tried ${funcName})`);\n }\n return this[funcName].bind(this, data);\n };\n eventToFunction.call(this, event, data).call();\n }\n}\n\nexport default EventHandler;",
2016-02-01 10:02:17 -07:00
"module.exports = {\n // fired before MediaSource is attaching to media element - data: { media }\n MEDIA_ATTACHING: 'hlsMediaAttaching',\n // fired when MediaSource has been succesfully attached to media element - data: { }\n MEDIA_ATTACHED: 'hlsMediaAttached',\n // fired before detaching MediaSource from media element - data: { }\n MEDIA_DETACHING: 'hlsMediaDetaching',\n // fired when MediaSource has been detached from media element - data: { }\n MEDIA_DETACHED: 'hlsMediaDetached',\n // fired to signal that a manifest loading starts - data: { url : manifestURL}\n MANIFEST_LOADING: 'hlsManifestLoading',\n // fired after manifest has been loaded - data: { levels : [available quality levels] , url : manifestURL, stats : { trequest, tfirst, tload, mtime}}\n MANIFEST_LOADED: 'hlsManifestLoaded',\n // fired after manifest has been parsed - data: { levels : [available quality levels] , firstLevel : index of first quality level appearing in Manifest}\n MANIFEST_PARSED: 'hlsManifestParsed',\n // fired when a level playlist loading starts - data: { url : level URL level : id of level being loaded}\n LEVEL_LOADING: 'hlsLevelLoading',\n // fired when a level playlist loading finishes - data: { details : levelDetails object, level : id of loaded level, stats : { trequest, tfirst, tload, mtime} }\n LEVEL_LOADED: 'hlsLevelLoaded',\n // fired when a level's details have been updated based on previous details, after it has been loaded. - data: { details : levelDetails object, level : id of updated level }\n LEVEL_UPDATED: 'hlsLevelUpdated',\n // fired when a level's PTS information has been updated after parsing a fragment - data: { details : levelDetails object, level : id of updated level, drift: PTS drift observed when parsing last fragment }\n LEVEL_PTS_UPDATED: 'hlsLevelPtsUpdated',\n // fired when a level switch is requested - data: { level : id of new level }\n LEVEL_SWITCH: 'hlsLevelSwitch',\n // fired when a fragment loading starts - data: { frag : fragment object}\n FRAG_LOADING: 'hlsFragLoading',\n // fired when a fragment loading is progressing - data: { frag : fragment object, { trequest, tfirst, loaded}}\n FRAG_LOAD_PROGRESS: 'hlsFragLoadProgress',\n // Identifier for fragment load aborting for emergency switch down - data: {frag : fragment object}\n FRAG_LOAD_EMERGENCY_ABORTED: 'hlsFragLoadEmergencyAborted',\n // fired when a fragment loading is completed - data: { frag : fragment object, payload : fragment payload, stats : { trequest, tfirst, tload, length}}\n FRAG_LOADED: 'hlsFragLoaded',\n // fired when Init Segment has been extracted from fragment - data: { moov : moov MP4 box, codecs : codecs found while parsing fragment}\n FRAG_PARSING_INIT_SEGMENT: 'hlsFragParsingInitSegment',\n // fired when parsing sei text is completed - data: { samples : [ sei samples pes ] }\n FRAG_PARSING_USERDATA: 'hlsFragParsingUserdata',\n // fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] }\n FRAG_PARSING_METADATA: 'hlsFragParsingMetadata',\n // fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box}\n FRAG_PARSING_DATA: 'hlsFragParsingData',\n // fired when fragment parsing is completed - data: undefined\n FRAG_PARSED: 'hlsFragParsed',\n // fired when fragment remuxed MP4 boxes have all been appended into SourceBuffer - data: { frag : fragment object, stats : { trequest, tfirst, tload, tparsed, tbuffered, length} }\n FRAG_BUFFERED: 'hlsFragBuffered',\n // fired when fragment matching with current media position is changing - data : { frag : fragment object }\n FRAG_CHANGED: 'hlsFragChanged',\n // Identifier for a FPS drop event - data: {curentDropped, currentDecoded, totalDroppedFrames}\n FPS_DROP: 'hlsFpsDrop',\n // Identifier for an error event - data: { type : error type, details : error details, fatal : if true, hls.js cannot/will not try to recover, if false, hls.js will try to recover,other error specific data}\n ERROR: 'hlsError',\n // fired when hls.js instance starts destroying. Different fro
2016-01-13 13:58:12 -07:00
"/**\n * Level Helper class, providing methods dealing with playlist sliding and drift\n*/\n\nimport {logger} from '../utils/logger';\n\nclass LevelHelper {\n\n static mergeDetails(oldDetails,newDetails) {\n var start = Math.max(oldDetails.startSN,newDetails.startSN)-newDetails.startSN,\n end = Math.min(oldDetails.endSN,newDetails.endSN)-newDetails.startSN,\n delta = newDetails.startSN - oldDetails.startSN,\n oldfragments = oldDetails.fragments,\n newfragments = newDetails.fragments,\n ccOffset =0,\n PTSFrag;\n\n // check if old/new playlists have fragments in common\n if ( end < start) {\n newDetails.PTSKnown = false;\n return;\n }\n // loop through overlapping SN and update startPTS , cc, and duration if any found\n for(var i = start ; i <= end ; i++) {\n var oldFrag = oldfragments[delta+i],\n newFrag = newfragments[i];\n ccOffset = oldFrag.cc - newFrag.cc;\n if (!isNaN(oldFrag.startPTS)) {\n newFrag.start = newFrag.startPTS = oldFrag.startPTS;\n newFrag.endPTS = oldFrag.endPTS;\n newFrag.duration = oldFrag.duration;\n PTSFrag = newFrag;\n }\n }\n\n if(ccOffset) {\n logger.log(`discontinuity sliding from playlist, take drift into account`);\n for(i = 0 ; i < newfragments.length ; i++) {\n newfragments[i].cc += ccOffset;\n }\n }\n\n // if at least one fragment contains PTS info, recompute PTS information for all fragments\n if(PTSFrag) {\n LevelHelper.updateFragPTS(newDetails,PTSFrag.sn,PTSFrag.startPTS,PTSFrag.endPTS);\n } else {\n // adjust start by sliding offset\n var sliding = oldfragments[delta].start;\n for(i = 0 ; i < newfragments.length ; i++) {\n newfragments[i].start += sliding;\n }\n }\n // if we are here, it means we have fragments overlapping between\n // old and new level. reliable PTS info is thus relying on old level\n newDetails.PTSKnown = oldDetails.PTSKnown;\n return;\n }\n\n static updateFragPTS(details,sn,startPTS,endPTS) {\n var fragIdx, fragments, frag, i;\n // exit if sn out of range\n if (sn < details.startSN || sn > details.endSN) {\n return 0;\n }\n fragIdx = sn - details.startSN;\n fragments = details.fragments;\n frag = fragments[fragIdx];\n if(!isNaN(frag.startPTS)) {\n startPTS = Math.min(startPTS,frag.startPTS);\n endPTS = Math.max(endPTS, frag.endPTS);\n }\n\n var drift = startPTS - frag.start;\n\n frag.start = frag.startPTS = startPTS;\n frag.endPTS = endPTS;\n frag.duration = endPTS - startPTS;\n // adjust fragment PTS/duration from seqnum-1 to frag 0\n for(i = fragIdx ; i > 0 ; i--) {\n LevelHelper.updatePTS(fragments,i,i-1);\n }\n\n // adjust fragment PTS/duration from seqnum to last frag\n for(i = fragIdx ; i < fragments.length - 1 ; i++) {\n LevelHelper.updatePTS(fragments,i,i+1);\n }\n details.PTSKnown = true;\n //logger.log(` frag start/end:${startPTS.toFixed(3)}/${endPTS.toFixed(3)}`);\n\n return drift;\n }\n\n static updatePTS(fragments,fromIdx, toIdx) {\n var fragFrom = fragments[fromIdx],fragTo = fragments[toIdx], fragToPTS = fragTo.startPTS;\n // if we know startPTS[toIdx]\n if(!isNaN(fragToPTS)) {\n // update fragment duration.\n // it helps to fix drifts between playlist reported duration and fragment real duration\n if (toIdx > fromIdx) {\n fragFrom.duration = fragToPTS-fragFrom.start;\n if(fragFrom.duration < 0) {\n logger.error(`negative duration computed for frag ${fragFrom.sn},level ${fragFrom.level}, there should be some duration drift between playlist and fragment!`);\n }\n } else {\n fragTo.duration = fragFrom.start - fragToPTS;\n if(fragTo.duration < 0) {\n logger.error(`negative duration computed for frag ${fragTo.sn},level ${fragTo.level}, there should be some duration drift between playlist and fragment!`);\n }\n }\n
2016-02-01 10:02:17 -07:00
"/**\n * HLS interface\n */\n'use strict';\n\nimport Event from './events';\nimport {ErrorTypes, ErrorDetails} from './errors';\nimport PlaylistLoader from './loader/playlist-loader';\nimport FragmentLoader from './loader/fragment-loader';\nimport AbrController from './controller/abr-controller';\nimport MSEMediaController from './controller/mse-media-controller';\nimport LevelController from './controller/level-controller';\nimport TimelineController from './controller/timeline-controller';\n//import FPSController from './controller/fps-controller';\nimport {logger, enableLogs} from './utils/logger';\nimport XhrLoader from './utils/xhr-loader';\nimport EventEmitter from 'events';\nimport KeyLoader from './loader/key-loader';\n\nclass Hls {\n\n static isSupported() {\n return (window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"'));\n }\n\n static get Events() {\n return Event;\n }\n\n static get ErrorTypes() {\n return ErrorTypes;\n }\n\n static get ErrorDetails() {\n return ErrorDetails;\n }\n\n static get DefaultConfig() {\n if(!Hls.defaultConfig) {\n Hls.defaultConfig = {\n autoStartLoad: true,\n debug: false,\n maxBufferLength: 30,\n maxBufferSize: 60 * 1000 * 1000,\n maxBufferHole: 0.3,\n maxSeekHole: 2,\n liveSyncDurationCount:3,\n liveMaxLatencyDurationCount: Infinity,\n maxMaxBufferLength: 600,\n enableWorker: true,\n enableSoftwareAES: true,\n manifestLoadingTimeOut: 10000,\n manifestLoadingMaxRetry: 1,\n manifestLoadingRetryDelay: 1000,\n levelLoadingTimeOut: 10000,\n levelLoadingMaxRetry: 4,\n levelLoadingRetryDelay: 1000,\n fragLoadingTimeOut: 20000,\n fragLoadingMaxRetry: 6,\n fragLoadingRetryDelay: 1000,\n fragLoadingLoopThreshold: 3,\n // fpsDroppedMonitoringPeriod: 5000,\n // fpsDroppedMonitoringThreshold: 0.2,\n appendErrorMaxRetry: 3,\n loader: XhrLoader,\n fLoader: undefined,\n pLoader: undefined,\n abrController : AbrController,\n mediaController: MSEMediaController,\n timelineController: TimelineController,\n enableCEA708Captions: true\n };\n }\n return Hls.defaultConfig;\n }\n\n static set DefaultConfig(defaultConfig) {\n Hls.defaultConfig = defaultConfig;\n }\n\n constructor(config = {}) {\n var defaultConfig = Hls.DefaultConfig;\n for (var prop in defaultConfig) {\n if (prop in config) { continue; }\n config[prop] = defaultConfig[prop];\n }\n\n if (config.liveMaxLatencyDurationCount !== undefined && config.liveMaxLatencyDurationCount <= config.liveSyncDurationCount) {\n throw new Error('Illegal hls.js config: \"liveMaxLatencyDurationCount\" must be gt \"liveSyncDurationCount\"');\n }\n\n enableLogs(config.debug);\n this.config = config;\n // observer setup\n var observer = this.observer = new EventEmitter();\n observer.trigger = function trigger (event, ...data) {\n observer.emit(event, event, ...data);\n };\n\n observer.off = function off (event, ...data) {\n observer.removeListener(event, ...data);\n };\n this.on = observer.on.bind(observer);\n this.off = observer.off.bind(observer);\n this.trigger = observer.trigger.bind(observer);\n this.playlistLoader = new PlaylistLoader(this);\n this.fragmentLoader = new FragmentLoader(this);\n this.levelController = new LevelController(this);\n this.abrController = new config.abrController(this);\n this.mediaController = new config.mediaController(this);\n this.timelineController = new config.timelineController(this);\n this.keyLoader = new KeyLoader(this);\n //this.fpsController = new FPSController(this);\n }\n\n destroy() {\n logger.log('destroy');\n this.trigger(Event.DESTROYING);\n this.detachMedia();\n this.playlistLoader.destroy();\n this.fragmentLoader.
2016-01-18 12:07:26 -07:00
"/*\n * Fragment Loader\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\nclass FragmentLoader extends EventHandler {\n\n constructor(hls) {\n super(hls, Event.FRAG_LOADING);\n }\n\n destroy() {\n if (this.loader) {\n this.loader.destroy();\n this.loader = null;\n }\n EventHandler.prototype.destroy.call(this);\n }\n\n onFragLoading(data) {\n var frag = data.frag;\n this.frag = frag;\n this.frag.loaded = 0;\n var config = this.hls.config;\n frag.loader = this.loader = typeof(config.fLoader) !== 'undefined' ? new config.fLoader(config) : new config.loader(config);\n this.loader.load(frag.url, 'arraybuffer', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), config.fragLoadingTimeOut, 1, 0, this.loadprogress.bind(this), frag);\n }\n\n loadsuccess(event, stats) {\n var payload = event.currentTarget.response;\n stats.length = payload.byteLength;\n // detach fragment loader on load success\n this.frag.loader = undefined;\n this.hls.trigger(Event.FRAG_LOADED, {payload: payload, frag: this.frag, stats: stats});\n }\n\n loaderror(event) {\n this.loader.abort();\n this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag: this.frag, response: event});\n }\n\n loadtimeout() {\n this.loader.abort();\n this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag: this.frag});\n }\n\n loadprogress(event, stats) {\n this.frag.loaded = stats.loaded;\n this.hls.trigger(Event.FRAG_LOAD_PROGRESS, {frag: this.frag, stats: stats});\n }\n}\n\nexport default FragmentLoader;\n",
"/*\n * Decrypt key Loader\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\nclass KeyLoader extends EventHandler {\n\n constructor(hls) {\n super(hls, Event.KEY_LOADING);\n this.decryptkey = null;\n this.decrypturl = null;\n }\n\n destroy() {\n if (this.loader) {\n this.loader.destroy();\n this.loader = null;\n }\n EventHandler.prototype.destroy.call(this);\n }\n\n onKeyLoading(data) {\n var frag = this.frag = data.frag,\n decryptdata = frag.decryptdata,\n uri = decryptdata.uri;\n // if uri is different from previous one or if decrypt key not retrieved yet\n if (uri !== this.decrypturl || this.decryptkey === null) {\n var config = this.hls.config;\n frag.loader = this.loader = new config.loader(config);\n this.decrypturl = uri;\n this.decryptkey = null;\n frag.loader.load(uri, 'arraybuffer', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), config.fragLoadingTimeOut, config.fragLoadingMaxRetry, config.fragLoadingRetryDelay, this.loadprogress.bind(this), frag);\n } else if (this.decryptkey) {\n // we already loaded this key, return it\n decryptdata.key = this.decryptkey;\n this.hls.trigger(Event.KEY_LOADED, {frag: frag});\n }\n }\n\n loadsuccess(event) {\n var frag = this.frag;\n this.decryptkey = frag.decryptdata.key = new Uint8Array(event.currentTarget.response);\n // detach fragment loader on load success\n frag.loader = undefined;\n this.hls.trigger(Event.KEY_LOADED, {frag: frag});\n }\n\n loaderror(event) {\n this.loader.abort();\n this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.KEY_LOAD_ERROR, fatal: false, frag: this.frag, response: event});\n }\n\n loadtimeout() {\n this.loader.abort();\n this.hls.trigger(Event.ERROR, {type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.KEY_LOAD_TIMEOUT, fatal: false, frag: this.frag});\n }\n\n loadprogress() {\n\n }\n}\n\nexport default KeyLoader;\n",
"/**\n * Playlist Loader\n*/\n\nimport Event from '../events';\nimport EventHandler from '../event-handler';\nimport {ErrorTypes, ErrorDetails} from '../errors';\nimport URLHelper from '../utils/url';\nimport AttrList from '../utils/attr-list';\n//import {logger} from '../utils/logger';\n\nclass PlaylistLoader extends EventHandler {\n\n constructor(hls) {\n super(hls,\n Event.MANIFEST_LOADING,\n Event.LEVEL_LOADING);\n }\n\n destroy() {\n if (this.loader) {\n this.loader.destroy();\n this.loader = null;\n }\n this.url = this.id = null;\n EventHandler.prototype.destroy.call(this);\n }\n\n onManifestLoading(data) {\n this.load(data.url, null);\n }\n\n onLevelLoading(data) {\n this.load(data.url, data.level, data.id);\n }\n\n load(url, id1, id2) {\n var config = this.hls.config,\n retry,\n timeout,\n retryDelay;\n this.url = url;\n this.id = id1;\n this.id2 = id2;\n if(this.id === undefined) {\n retry = config.manifestLoadingMaxRetry;\n timeout = config.manifestLoadingTimeOut;\n retryDelay = config.manifestLoadingRetryDelay;\n } else {\n retry = config.levelLoadingMaxRetry;\n timeout = config.levelLoadingTimeOut;\n retryDelay = config.levelLoadingRetryDelay;\n }\n this.loader = typeof(config.pLoader) !== 'undefined' ? new config.pLoader(config) : new config.loader(config);\n this.loader.load(url, '', this.loadsuccess.bind(this), this.loaderror.bind(this), this.loadtimeout.bind(this), timeout, retry, retryDelay);\n }\n\n resolve(url, baseUrl) {\n return URLHelper.buildAbsoluteURL(baseUrl, url);\n }\n\n parseMasterPlaylist(string, baseurl) {\n let levels = [], result;\n\n // https://regex101.com is your friend\n const re = /#EXT-X-STREAM-INF:([^\\n\\r]*)[\\r\\n]+([^\\r\\n]+)/g;\n while ((result = re.exec(string)) != null){\n const level = {};\n\n var attrs = level.attrs = new AttrList(result[1]);\n level.url = this.resolve(result[2], baseurl);\n\n var resolution = attrs.decimalResolution('RESOLUTION');\n if(resolution) {\n level.width = resolution.width;\n level.height = resolution.height;\n }\n level.bitrate = attrs.decimalInteger('BANDWIDTH');\n level.name = attrs.NAME;\n\n var codecs = attrs.CODECS;\n if(codecs) {\n codecs = codecs.split(',');\n for (let i = 0; i < codecs.length; i++) {\n const codec = codecs[i];\n if (codec.indexOf('avc1') !== -1) {\n level.videoCodec = this.avc1toavcoti(codec);\n } else {\n level.audioCodec = codec;\n }\n }\n }\n\n levels.push(level);\n }\n return levels;\n }\n\n avc1toavcoti(codec) {\n var result, avcdata = codec.split('.');\n if (avcdata.length > 2) {\n result = avcdata.shift() + '.';\n result += parseInt(avcdata.shift()).toString(16);\n result += ('000' + parseInt(avcdata.shift()).toString(16)).substr(-4);\n } else {\n result = codec;\n }\n return result;\n }\n\n cloneObj(obj) {\n return JSON.parse(JSON.stringify(obj));\n }\n\n parseLevelPlaylist(string, baseurl, id) {\n var currentSN = 0,\n totalduration = 0,\n level = {url: baseurl, fragments: [], live: true, startSN: 0},\n levelkey = {method : null, key : null, iv : null, uri : null},\n cc = 0,\n programDateTime = null,\n frag = null,\n result,\n regexp,\n byteRangeEndOffset,\n byteRangeStartOffset;\n\n regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\\d+))|(?:#EXT-X-(TARGETDURATION):(\\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT(INF):([\\d\\.]+)[^\\r\\n]*([\\r\\n]+[^#|\\r\\n]+)?)|(?:#EXT-X-(BYTERANGE):([\\d]+[@[\\d]*)]*[\\r\\n]+([^#|\\r\\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(PROGRAM-DATE-TIME):(.*))/g;\n while ((result = regexp.exec(string)) !== null) {\n result.shift();\n result = result.filter(function(n) { return (n !== undefined); });\n switch (result[0]) {\n case 'MEDIA
2016-02-01 10:02:17 -07:00
"/**\n * Generate MP4 Box\n*/\n\n//import Hex from '../utils/hex';\nclass MP4 {\n static init() {\n MP4.types = {\n avc1: [], // codingname\n avcC: [],\n btrt: [],\n dinf: [],\n dref: [],\n esds: [],\n ftyp: [],\n hdlr: [],\n mdat: [],\n mdhd: [],\n mdia: [],\n mfhd: [],\n minf: [],\n moof: [],\n moov: [],\n mp4a: [],\n mvex: [],\n mvhd: [],\n sdtp: [],\n stbl: [],\n stco: [],\n stsc: [],\n stsd: [],\n stsz: [],\n stts: [],\n tfdt: [],\n tfhd: [],\n traf: [],\n trak: [],\n trun: [],\n trex: [],\n tkhd: [],\n vmhd: [],\n smhd: []\n };\n\n var i;\n for (i in MP4.types) {\n if (MP4.types.hasOwnProperty(i)) {\n MP4.types[i] = [\n i.charCodeAt(0),\n i.charCodeAt(1),\n i.charCodeAt(2),\n i.charCodeAt(3)\n ];\n }\n }\n\n var videoHdlr = new Uint8Array([\n 0x00, // version 0\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x00, // pre_defined\n 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x56, 0x69, 0x64, 0x65,\n 0x6f, 0x48, 0x61, 0x6e,\n 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'\n ]);\n\n var audioHdlr = new Uint8Array([\n 0x00, // version 0\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x00, // pre_defined\n 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x00, 0x00, 0x00, 0x00, // reserved\n 0x53, 0x6f, 0x75, 0x6e,\n 0x64, 0x48, 0x61, 0x6e,\n 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'\n ]);\n\n MP4.HDLR_TYPES = {\n 'video': videoHdlr,\n 'audio': audioHdlr\n };\n\n var dref = new Uint8Array([\n 0x00, // version 0\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x01, // entry_count\n 0x00, 0x00, 0x00, 0x0c, // entry_size\n 0x75, 0x72, 0x6c, 0x20, // 'url' type\n 0x00, // version 0\n 0x00, 0x00, 0x01 // entry_flags\n ]);\n\n var stco = new Uint8Array([\n 0x00, // version\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x00 // entry_count\n ]);\n\n MP4.STTS = MP4.STSC = MP4.STCO = stco;\n\n MP4.STSZ = new Uint8Array([\n 0x00, // version\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x00, // sample_size\n 0x00, 0x00, 0x00, 0x00, // sample_count\n ]);\n MP4.VMHD = new Uint8Array([\n 0x00, // version\n 0x00, 0x00, 0x01, // flags\n 0x00, 0x00, // graphicsmode\n 0x00, 0x00,\n 0x00, 0x00,\n 0x00, 0x00 // opcolor\n ]);\n MP4.SMHD = new Uint8Array([\n 0x00, // version\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, // balance\n 0x00, 0x00 // reserved\n ]);\n\n MP4.STSD = new Uint8Array([\n 0x00, // version 0\n 0x00, 0x00, 0x00, // flags\n 0x00, 0x00, 0x00, 0x01]);// entry_count\n\n var majorBrand = new Uint8Array([105,115,111,109]); // isom\n var avc1Brand = new Uint8Array([97,118,99,49]); // avc1\n var minorVersion = new Uint8Array([0, 0, 0, 1]);\n\n MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);\n MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));\n }\n\n static box(type) {\n var\n payload = Array.prototype.slice.call(arguments, 1),\n size = 8,\n i = payload.length,\n len = i,\n result;\n // calculate the total size we need to allocate\n while (i--) {\n size += payload[i].byteLength;\n }\n result = new Uint8Array(size);\n result[0] = (size >> 24) & 0xff;\n result[1] = (size >> 16) & 0xff;\n result[2] = (size >> 8) & 0xff;\n result[3] = size & 0xff;\n result.set(type, 4);\n // copy the payload into the result\n for (i = 0, size = 8; i < len; i++) {\n
"/**\n * fMP4 remuxer\n*/\n\n\nimport Event from '../events';\nimport {logger} from '../utils/logger';\nimport MP4 from '../remux/mp4-generator';\nimport {ErrorTypes, ErrorDetails} from '../errors';\n\nclass MP4Remuxer {\n constructor(observer) {\n this.observer = observer;\n this.ISGenerated = false;\n this.PES2MP4SCALEFACTOR = 4;\n this.PES_TIMESCALE = 90000;\n this.MP4_TIMESCALE = this.PES_TIMESCALE / this.PES2MP4SCALEFACTOR;\n }\n\n get timescale() {\n return this.MP4_TIMESCALE;\n }\n\n destroy() {\n }\n\n insertDiscontinuity() {\n this._initPTS = this._initDTS = this.nextAacPts = this.nextAvcDts = undefined;\n }\n\n switchLevel() {\n this.ISGenerated = false;\n }\n\n remux(audioTrack,videoTrack,id3Track,textTrack,timeOffset, contiguous) {\n // generate Init Segment if needed\n if (!this.ISGenerated) {\n this.generateIS(audioTrack,videoTrack,timeOffset);\n }\n //logger.log('nb AVC samples:' + videoTrack.samples.length);\n if (videoTrack.samples.length) {\n this.remuxVideo(videoTrack,timeOffset,contiguous);\n }\n //logger.log('nb AAC samples:' + audioTrack.samples.length);\n if (audioTrack.samples.length) {\n this.remuxAudio(audioTrack,timeOffset,contiguous);\n }\n //logger.log('nb ID3 samples:' + audioTrack.samples.length);\n if (id3Track.samples.length) {\n this.remuxID3(id3Track,timeOffset);\n }\n //logger.log('nb ID3 samples:' + audioTrack.samples.length);\n if (textTrack.samples.length) {\n this.remuxText(textTrack,timeOffset);\n }\n //notify end of parsing\n this.observer.trigger(Event.FRAG_PARSED);\n }\n\n generateIS(audioTrack,videoTrack,timeOffset) {\n var observer = this.observer,\n audioSamples = audioTrack.samples,\n videoSamples = videoTrack.samples,\n nbAudio = audioSamples.length,\n nbVideo = videoSamples.length,\n pesTimeScale = this.PES_TIMESCALE;\n\n if(nbAudio === 0 && nbVideo === 0) {\n observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, reason: 'no audio/video samples found'});\n } else if (nbVideo === 0) {\n //audio only\n if (audioTrack.config) {\n observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {\n audioMoov: MP4.initSegment([audioTrack]),\n audioCodec : audioTrack.codec,\n audioChannelCount : audioTrack.channelCount\n });\n this.ISGenerated = true;\n }\n if (this._initPTS === undefined) {\n // remember first PTS of this demuxing context\n this._initPTS = audioSamples[0].pts - pesTimeScale * timeOffset;\n this._initDTS = audioSamples[0].dts - pesTimeScale * timeOffset;\n }\n } else\n if (nbAudio === 0) {\n //video only\n if (videoTrack.sps && videoTrack.pps) {\n observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {\n videoMoov: MP4.initSegment([videoTrack]),\n videoCodec: videoTrack.codec,\n videoWidth: videoTrack.width,\n videoHeight: videoTrack.height\n });\n this.ISGenerated = true;\n if (this._initPTS === undefined) {\n // remember first PTS of this demuxing context\n this._initPTS = videoSamples[0].pts - pesTimeScale * timeOffset;\n this._initDTS = videoSamples[0].dts - pesTimeScale * timeOffset;\n }\n }\n } else {\n //audio and video\n if (audioTrack.config && videoTrack.sps && videoTrack.pps) {\n observer.trigger(Event.FRAG_PARSING_INIT_SEGMENT, {\n audioMoov: MP4.initSegment([audioTrack]),\n audioCodec: audioTrack.codec,\n audioChannelCount: audioTrack.channelCount,\n videoMoov: MP4.initSegment([videoTrack]),\n videoCodec: videoTrack.codec,\n videoWidth: videoTrack.width,\n videoHeight: videoTrack.height\n });\n this.ISGenerated = true;\n if (this._initPTS === undefined) {\n // remember first PTS of this demuxing context\n th
2016-01-25 13:28:29 -07:00
"\n// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js\nclass AttrList {\n\n constructor(attrs) {\n if (typeof attrs === 'string') {\n attrs = AttrList.parseAttrList(attrs);\n }\n for(var attr in attrs){\n if(attrs.hasOwnProperty(attr)) {\n this[attr] = attrs[attr];\n }\n }\n }\n\n decimalInteger(attrName) {\n const intValue = parseInt(this[attrName], 10);\n if (intValue > Number.MAX_SAFE_INTEGER) {\n return Infinity;\n }\n return intValue;\n }\n\n hexadecimalInteger(attrName) {\n if(this[attrName]) {\n let stringValue = (this[attrName] || '0x').slice(2);\n stringValue = ((stringValue.length & 1) ? '0' : '') + stringValue;\n\n const value = new Uint8Array(stringValue.length / 2);\n for (let i = 0; i < stringValue.length / 2; i++) {\n value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);\n }\n return value;\n } else {\n return null;\n }\n }\n\n hexadecimalIntegerAsNumber(attrName) {\n const intValue = parseInt(this[attrName], 16);\n if (intValue > Number.MAX_SAFE_INTEGER) {\n return Infinity;\n }\n return intValue;\n }\n\n decimalFloatingPoint(attrName) {\n return parseFloat(this[attrName]);\n }\n\n enumeratedString(attrName) {\n return this[attrName];\n }\n\n decimalResolution(attrName) {\n const res = /^(\\d+)x(\\d+)$/.exec(this[attrName]);\n if (res === null) {\n return undefined;\n }\n return {\n width: parseInt(res[1], 10),\n height: parseInt(res[2], 10)\n };\n }\n\n static parseAttrList(input) {\n const re = /\\s*(.+?)\\s*=((?:\\\".*?\\\")|.*?)(?:,|$)/g;\n var match, attrs = {};\n while ((match = re.exec(input)) !== null) {\n var value = match[2], quote = '\"';\n\n if (value.indexOf(quote) === 0 &&\n value.lastIndexOf(quote) === (value.length-1)) {\n value = value.slice(1, -1);\n }\n attrs[match[1]] = value;\n }\n return attrs;\n }\n\n}\n\nexport default AttrList;\n",
2015-12-15 22:30:14 -07:00
"var BinarySearch = {\n /**\n * Searches for an item in an array which matches a certain condition.\n * This requires the condition to only match one item in the array,\n * and for the array to be ordered.\n *\n * @param {Array} list The array to search.\n * @param {Function} comparisonFunction\n * Called and provided a candidate item as the first argument.\n * Should return:\n * > -1 if the item should be located at a lower index than the provided item.\n * > 1 if the item should be located at a higher index than the provided item.\n * > 0 if the item is the item you're looking for.\n *\n * @return {*} The object if it is found or null otherwise.\n */\n search: function(list, comparisonFunction) {\n var minIndex = 0;\n var maxIndex = list.length - 1;\n var currentIndex = null;\n var currentElement = null;\n \n while (minIndex <= maxIndex) {\n currentIndex = (minIndex + maxIndex) / 2 | 0;\n currentElement = list[currentIndex];\n \n var comparisonResult = comparisonFunction(currentElement);\n if (comparisonResult > 0) {\n minIndex = currentIndex + 1;\n }\n else if (comparisonResult < 0) {\n maxIndex = currentIndex - 1;\n }\n else {\n return currentElement;\n }\n }\n \n return null;\n }\n};\n\nmodule.exports = BinarySearch;\n",
2016-02-01 10:02:17 -07:00
"/*\n * CEA-708 interpreter\n*/\n\nclass CEA708Interpreter {\n\n constructor() {\n }\n\n attach(media) {\n this.media = media;\n this.display = [];\n this.memory = [];\n }\n\n detach()\n {\n this.clear();\n }\n\n destroy() {\n }\n\n _createCue()\n {\n var VTTCue = window.VTTCue || window.TextTrackCue;\n\n this.cue = new VTTCue(-1, -1, '');\n this.cue.text = '';\n this.cue.pauseOnExit = false;\n\n // make sure it doesn't show up before it's ready\n this.startTime = Number.MAX_VALUE;\n\n // show it 'forever' once we do show it\n // (we'll set the end time once we know it later)\n this.cue.endTime = Number.MAX_VALUE;\n\n this.memory.push(this.cue);\n }\n\n clear()\n {\n if (this._textTrack && this._textTrack.cues)\n {\n while (this._textTrack.cues.length > 0)\n {\n this._textTrack.removeCue(this._textTrack.cues[0]);\n }\n }\n }\n\n push(timestamp, bytes)\n {\n if (!this.cue)\n {\n this._createCue();\n }\n\n var count = bytes[0] & 31;\n var position = 2;\n var byte, ccbyte1, ccbyte2, ccValid, ccType;\n\n for (var j=0; j<count; j++)\n {\n byte = bytes[position++];\n ccbyte1 = 0x7F & bytes[position++];\n ccbyte2 = 0x7F & bytes[position++];\n ccValid = ((4 & byte) === 0 ? false : true);\n ccType = (3 & byte);\n\n if (ccbyte1 === 0 && ccbyte2 === 0)\n {\n continue;\n }\n\n if (ccValid)\n {\n if (ccType === 0) // || ccType === 1\n {\n // Standard Characters\n if (0x20 & ccbyte1 || 0x40 & ccbyte1)\n {\n this.cue.text += this._fromCharCode(ccbyte1) + this._fromCharCode(ccbyte2);\n }\n // Special Characters\n else if ((ccbyte1 === 0x11 || ccbyte1 === 0x19) && ccbyte2 >= 0x30 && ccbyte2 <= 0x3F)\n {\n // extended chars, e.g. musical note, accents\n switch (ccbyte2)\n {\n case 48:\n this.cue.text += '®';\n break;\n case 49:\n this.cue.text += '°';\n break;\n case 50:\n this.cue.text += '½';\n break;\n case 51:\n this.cue.text += '¿';\n break;\n case 52:\n this.cue.text += '™';\n break;\n case 53:\n this.cue.text += '¢';\n break;\n case 54:\n this.cue.text += '';\n break;\n case 55:\n this.cue.text += '£';\n break;\n case 56:\n this.cue.text += '♪';\n break;\n case 57:\n this.cue.text += ' ';\n break;\n case 58:\n this.cue.text += 'è';\n break;\n case 59:\n this.cue.text += 'â';\n break;\n case 60:\n this.cue.text += 'ê';\n break;\n case 61:\n this.cue.text += 'î';\n break;\n case 62:\n this.cue.text += 'ô';\n break;\n case 63:\n this.cue.text += 'û';\n break;\n }\n }\n if ((ccbyte1 === 0x11 || ccbyte1 === 0x19) && ccbyte2 >= 0x20 && ccbyte2 <= 0x2F)\n {\n // Mid-row codes: color/underline\n switch (ccbyte2)\n {\n case 0x20:\n // White\n break;\n case 0x21:\n // White Underline\n break;\n case 0x22:\n // Green\n break;\n case 0x23:\n // Green Underline\n break;\n case 0x24:\n // Blue\n break;\n case 0x25:\n // Blue Underline\n
2015-12-15 22:30:14 -07:00
"'use strict';\n\nfunction noop() {}\n\nconst fakeLogger = {\n trace: noop,\n debug: noop,\n log: noop,\n warn: noop,\n info: noop,\n error: noop\n};\n\nlet exportedLogger = fakeLogger;\n\n//let lastCallTime;\n// function formatMsgWithTimeInfo(type, msg) {\n// const now = Date.now();\n// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';\n// lastCallTime = now;\n// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';\n// return msg;\n// }\n\nfunction formatMsg(type, msg) {\n msg = '[' + type + '] > ' + msg;\n return msg;\n}\n\nfunction consolePrintFn(type) {\n const func = window.console[type];\n if (func) {\n return function(...args) {\n if(args[0]) {\n args[0] = formatMsg(type, args[0]);\n }\n func.apply(window.console, args);\n };\n }\n return noop;\n}\n\nfunction exportLoggerFunctions(debugConfig, ...functions) {\n functions.forEach(function(type) {\n exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);\n });\n}\n\nexport var enableLogs = function(debugConfig) {\n if (debugConfig === true || typeof debugConfig === 'object') {\n exportLoggerFunctions(debugConfig,\n // Remove out from list here to hard-disable a log-level\n //'trace',\n 'debug',\n 'log',\n 'info',\n 'warn',\n 'error'\n );\n // Some browsers don't allow to use bind on console object anyway\n // fallback to default if needed\n try {\n exportedLogger.log();\n } catch (e) {\n exportedLogger = fakeLogger;\n }\n }\n else {\n exportedLogger = fakeLogger;\n }\n};\n\nexport var logger = exportedLogger;\n",
2016-02-01 10:02:17 -07:00
"var URLHelper = {\n\n // build an absolute URL from a relative one using the provided baseURL\n // if relativeURL is an absolute URL it will be returned as is.\n buildAbsoluteURL: function(baseURL, relativeURL) {\n // remove any remaining space and CRLF\n relativeURL = relativeURL.trim();\n if (/^[a-z]+:/i.test(relativeURL)) {\n // complete url, not relative\n return relativeURL;\n }\n\n var relativeURLQuery = null;\n var relativeURLHash = null;\n\n var relativeURLHashSplit = /^([^#]*)(.*)$/.exec(relativeURL);\n if (relativeURLHashSplit) {\n relativeURLHash = relativeURLHashSplit[2];\n relativeURL = relativeURLHashSplit[1];\n }\n var relativeURLQuerySplit = /^([^\\?]*)(.*)$/.exec(relativeURL);\n if (relativeURLQuerySplit) {\n relativeURLQuery = relativeURLQuerySplit[2];\n relativeURL = relativeURLQuerySplit[1];\n }\n\n var baseURLHashSplit = /^([^#]*)(.*)$/.exec(baseURL);\n if (baseURLHashSplit) {\n baseURL = baseURLHashSplit[1];\n }\n var baseURLQuerySplit = /^([^\\?]*)(.*)$/.exec(baseURL);\n if (baseURLQuerySplit) {\n baseURL = baseURLQuerySplit[1];\n }\n\n var baseURLDomainSplit = /^((([a-z]+):)?\\/\\/[a-z0-9\\.-]+(:[0-9]+)?\\/)(.*)$/i.exec(baseURL);\n var baseURLProtocol = baseURLDomainSplit[3];\n var baseURLDomain = baseURLDomainSplit[1];\n var baseURLPath = baseURLDomainSplit[5];\n\n var builtURL = null;\n if (/^\\/\\//.test(relativeURL)) {\n builtURL = baseURLProtocol+'://'+URLHelper.buildAbsolutePath('', relativeURL.substring(2));\n }\n else if (/^\\//.test(relativeURL)) {\n builtURL = baseURLDomain+URLHelper.buildAbsolutePath('', relativeURL.substring(1));\n }\n else {\n builtURL = URLHelper.buildAbsolutePath(baseURLDomain+baseURLPath, relativeURL);\n }\n\n // put the query and hash parts back\n if (relativeURLQuery) {\n builtURL += relativeURLQuery;\n }\n if (relativeURLHash) {\n builtURL += relativeURLHash;\n }\n return builtURL;\n },\n\n // build an absolute path using the provided basePath\n // adapted from https://developer.mozilla.org/en-US/docs/Web/API/document/cookie#Using_relative_URLs_in_the_path_parameter\n // this does not handle the case where relativePath is \"/\" or \"//\". These cases should be handled outside this.\n buildAbsolutePath: function(basePath, relativePath) {\n var sRelPath = relativePath;\n var nUpLn, sDir = '', sPath = basePath.replace(/[^\\/]*$/, sRelPath.replace(/(\\/|^)(?:\\.?\\/+)+/g, '$1'));\n for (var nEnd, nStart = 0; nEnd = sPath.indexOf('/../', nStart), nEnd > -1; nStart = nEnd + nUpLn) {\n nUpLn = /^\\/(?:\\.\\.\\/)*/.exec(sPath.slice(nEnd))[0].length;\n sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp('(?:\\\\\\/+[^\\\\\\/]*){0,' + ((nUpLn - 1) / 3) + '}$'), '/');\n }\n return sDir + sPath.substr(nStart);\n }\n};\n\nmodule.exports = URLHelper;\n",
2016-01-25 13:28:29 -07:00
"/**\n * XHR based logger\n*/\n\nimport {logger} from '../utils/logger';\n\nclass XhrLoader {\n\n constructor(config) {\n if (config && config.xhrSetup) {\n this.xhrSetup = config.xhrSetup;\n }\n }\n\n destroy() {\n this.abort();\n this.loader = null;\n }\n\n abort() {\n var loader = this.loader,\n timeoutHandle = this.timeoutHandle;\n if (loader && loader.readyState !== 4) {\n this.stats.aborted = true;\n loader.abort();\n }\n if (timeoutHandle) {\n window.clearTimeout(timeoutHandle);\n }\n }\n\n load(url, responseType, onSuccess, onError, onTimeout, timeout, maxRetry, retryDelay, onProgress = null, frag = null) {\n this.url = url;\n if (frag && !isNaN(frag.byteRangeStartOffset) && !isNaN(frag.byteRangeEndOffset)) {\n this.byteRange = frag.byteRangeStartOffset + '-' + (frag.byteRangeEndOffset-1);\n }\n this.responseType = responseType;\n this.onSuccess = onSuccess;\n this.onProgress = onProgress;\n this.onTimeout = onTimeout;\n this.onError = onError;\n this.stats = {trequest: performance.now(), retry: 0};\n this.timeout = timeout;\n this.maxRetry = maxRetry;\n this.retryDelay = retryDelay;\n this.timeoutHandle = window.setTimeout(this.loadtimeout.bind(this), timeout);\n this.loadInternal();\n }\n\n loadInternal() {\n var xhr;\n \n if (typeof XDomainRequest !== 'undefined') {\n xhr = this.loader = new XDomainRequest();\n } else {\n xhr = this.loader = new XMLHttpRequest();\n }\n \n xhr.onloadend = this.loadend.bind(this);\n xhr.onprogress = this.loadprogress.bind(this);\n\n xhr.open('GET', this.url, true);\n if (this.byteRange) {\n xhr.setRequestHeader('Range', 'bytes=' + this.byteRange);\n }\n xhr.responseType = this.responseType;\n this.stats.tfirst = null;\n this.stats.loaded = 0;\n if (this.xhrSetup) {\n this.xhrSetup(xhr, this.url);\n }\n xhr.send();\n }\n\n loadend(event) {\n var xhr = event.currentTarget,\n status = xhr.status,\n stats = this.stats;\n // don't proceed if xhr has been aborted\n if (!stats.aborted) {\n // http status between 200 to 299 are all successful\n if (status >= 200 && status < 300) {\n window.clearTimeout(this.timeoutHandle);\n stats.tload = performance.now();\n this.onSuccess(event, stats);\n } else {\n // error ...\n if (stats.retry < this.maxRetry) {\n logger.warn(`${status} while loading ${this.url}, retrying in ${this.retryDelay}...`);\n this.destroy();\n window.setTimeout(this.loadInternal.bind(this), this.retryDelay);\n // exponential backoff\n this.retryDelay = Math.min(2 * this.retryDelay, 64000);\n stats.retry++;\n } else {\n window.clearTimeout(this.timeoutHandle);\n logger.error(`${status} while loading ${this.url}` );\n this.onError(event);\n }\n }\n }\n }\n\n loadtimeout(event) {\n logger.warn(`timeout while loading ${this.url}` );\n this.onTimeout(event, this.stats);\n }\n\n loadprogress(event) {\n var stats = this.stats;\n if (stats.tfirst === null) {\n stats.tfirst = performance.now();\n }\n stats.loaded = event.loaded;\n if (this.onProgress) {\n this.onProgress(event, stats);\n }\n }\n}\n\nexport default XhrLoader;\n"
2015-12-15 22:30:14 -07:00
]
}