!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.DiadaoWpSdk=e():t.DiadaoWpSdk=e()}(self,(()=>(()=>{var __webpack_modules__=[(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   getPrivate: ()=> ( getPrivate),\n   setPrivate: ()=> ( setPrivate)\n });\nconst privateProps= new WeakMap();\nfunction getPrivate(instance){\n  return privateProps.get(instance) ?? setPrivate(instance, {});\n}\nfunction setPrivate(instance, props){\n  let saved=privateProps.get(instance);\n  if(!saved) privateProps.set(instance, saved={});\n  return Object.assign(saved, props);\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/utils.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   VideoRenditionList: ()=> ( VideoRenditionList),\n   addRendition: ()=> ( addRendition),\n   removeRendition: ()=> ( removeRendition),\n   selectedChanged: ()=> ( selectedChanged)\n });\n var _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 5);\n var _utils_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 0);\n\n\nfunction addRendition(track, rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media.videoRenditions;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track=track;\n  const renditionSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet;\n  renditionSet.add(rendition);\n  const index=renditionSet.size - 1;\n  if(!(index in VideoRenditionList.prototype)){\n    Object.defineProperty(VideoRenditionList.prototype, index, {\n      get(){\n        return getCurrentRenditions(this)[index];\n      }\n    });\n  }\n  queueMicrotask(()=> {\n    if(!track.selected) return;\n    renditionList.dispatchEvent(new _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__.RenditionEvent("addrendition", { rendition }));\n  });\n}\nfunction removeRendition(rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media.videoRenditions;\n  const track=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n  const renditionSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet;\n  renditionSet.delete(rendition);\n  queueMicrotask(()=> {\n    const track2=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n    if(!track2.selected) return;\n    renditionList.dispatchEvent(new _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__.RenditionEvent("removerendition", { rendition }));\n  });\n}\nfunction selectedChanged(rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media.videoRenditions;\n  if(!renditionList||(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested) return;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested=true;\n  queueMicrotask(()=> {\n    delete (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested;\n    const track=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n    if(!track.selected) return;\n    renditionList.dispatchEvent(new Event("change"));\n  });\n}\nfunction getCurrentRenditions(renditionList){\n  const media=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).media;\n  return [...media.videoTracks].filter((track)=> track.selected).flatMap((track)=> [...(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet]);\n}\nclass VideoRenditionList extends EventTarget {\n  #addRenditionCallback;\n  #removeRenditionCallback;\n  #changeCallback;\n  [Symbol.iterator](){\n    return getCurrentRenditions(this).values();\n  }\n  get length(){\n    return getCurrentRenditions(this).length;\n  }\n  getRenditionById(id){\n    return getCurrentRenditions(this).find((rendition)=> `${rendition.id}`===`${id}`) ?? null;\n  }\n  get selectedIndex(){\n    return getCurrentRenditions(this).findIndex((rendition)=> rendition.selected);\n  }\n  set selectedIndex(index){\n    for (const [i, rendition] of getCurrentRenditions(this).entries()){\n      rendition.selected=i===index;\n    }\n  }\n  get onaddrendition(){\n    return this.#addRenditionCallback;\n  }\n  set onaddrendition(callback){\n    if(this.#addRenditionCallback){\n      this.removeEventListener("addrendition", this.#addRenditionCallback);\n      this.#addRenditionCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#addRenditionCallback=callback;\n      this.addEventListener("addrendition", callback);\n    }\n  }\n  get onremoverendition(){\n    return this.#removeRenditionCallback;\n  }\n  set onremoverendition(callback){\n    if(this.#removeRenditionCallback){\n      this.removeEventListener("removerendition", this.#removeRenditionCallback);\n      this.#removeRenditionCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#removeRenditionCallback=callback;\n      this.addEventListener("removerendition", callback);\n    }\n  }\n  get onchange(){\n    return this.#changeCallback;\n  }\n  set onchange(callback){\n    if(this.#changeCallback){\n      this.removeEventListener("change", this.#changeCallback);\n      this.#changeCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#changeCallback=callback;\n      this.addEventListener("change", callback);\n    }\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/video-rendition-list.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AudioRenditionList: ()=> ( AudioRenditionList),\n   addRendition: ()=> ( addRendition),\n   removeRendition: ()=> ( removeRendition),\n   selectedChanged: ()=> ( selectedChanged)\n });\n var _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 5);\n var _utils_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 0);\n\n\nfunction addRendition(track, rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media.audioRenditions;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track=track;\n  const renditionSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet;\n  renditionSet.add(rendition);\n  const index=renditionSet.size - 1;\n  if(!(index in AudioRenditionList.prototype)){\n    Object.defineProperty(AudioRenditionList.prototype, index, {\n      get(){\n        return getCurrentRenditions(this)[index];\n      }\n    });\n  }\n  queueMicrotask(()=> {\n    if(!track.enabled) return;\n    renditionList.dispatchEvent(new _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__.RenditionEvent("addrendition", { rendition }));\n  });\n}\nfunction removeRendition(rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media.audioRenditions;\n  const track=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n  const renditionSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet;\n  renditionSet.delete(rendition);\n  queueMicrotask(()=> {\n    const track2=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n    if(!track2.enabled) return;\n    renditionList.dispatchEvent(new _rendition_event_js__WEBPACK_IMPORTED_MODULE_0__.RenditionEvent("removerendition", { rendition }));\n  });\n}\nfunction selectedChanged(rendition){\n  const renditionList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).media.audioRenditions;\n  if(!renditionList||(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested) return;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested=true;\n  queueMicrotask(()=> {\n    delete (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).changeRequested;\n    const track=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(rendition).track;\n    if(!track.enabled) return;\n    renditionList.dispatchEvent(new Event("change"));\n  });\n}\nfunction getCurrentRenditions(renditionList){\n  const media=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(renditionList).media;\n  return [...media.audioTracks].filter((track)=> track.enabled).flatMap((track)=> [...(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet]);\n}\nclass AudioRenditionList extends EventTarget {\n  #addRenditionCallback;\n  #removeRenditionCallback;\n  #changeCallback;\n  [Symbol.iterator](){\n    return getCurrentRenditions(this).values();\n  }\n  get length(){\n    return getCurrentRenditions(this).length;\n  }\n  getRenditionById(id){\n    return getCurrentRenditions(this).find((rendition)=> `${rendition.id}`===`${id}`) ?? null;\n  }\n  get selectedIndex(){\n    return getCurrentRenditions(this).findIndex((rendition)=> rendition.selected);\n  }\n  set selectedIndex(index){\n    for (const [i, rendition] of getCurrentRenditions(this).entries()){\n      rendition.selected=i===index;\n    }\n  }\n  get onaddrendition(){\n    return this.#addRenditionCallback;\n  }\n  set onaddrendition(callback){\n    if(this.#addRenditionCallback){\n      this.removeEventListener("addrendition", this.#addRenditionCallback);\n      this.#addRenditionCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#addRenditionCallback=callback;\n      this.addEventListener("addrendition", callback);\n    }\n  }\n  get onremoverendition(){\n    return this.#removeRenditionCallback;\n  }\n  set onremoverendition(callback){\n    if(this.#removeRenditionCallback){\n      this.removeEventListener("removerendition", this.#removeRenditionCallback);\n      this.#removeRenditionCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#removeRenditionCallback=callback;\n      this.addEventListener("removerendition", callback);\n    }\n  }\n  get onchange(){\n    return this.#changeCallback;\n  }\n  set onchange(callback){\n    if(this.#changeCallback){\n      this.removeEventListener("change", this.#changeCallback);\n      this.#changeCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#changeCallback=callback;\n      this.addEventListener("change", callback);\n    }\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/audio-rendition-list.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   VideoTrackList: ()=> ( VideoTrackList),\n   addVideoTrack: ()=> ( addVideoTrack),\n   removeVideoTrack: ()=> ( removeVideoTrack),\n   selectedChanged: ()=> ( selectedChanged)\n });\n var _track_event_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 4);\n var _utils_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 0);\n\n\nfunction addVideoTrack(media, track){\n  const trackList=media.videoTracks;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media=media;\n  if(!(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet){\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet= new Set();\n  }\n  const trackSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).trackSet;\n  trackSet.add(track);\n  const index=trackSet.size - 1;\n  if(!(index in VideoTrackList.prototype)){\n    Object.defineProperty(VideoTrackList.prototype, index, {\n      get(){\n        return [...(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet][index];\n      }\n    });\n  }\n  queueMicrotask(()=> {\n    trackList.dispatchEvent(new _track_event_js__WEBPACK_IMPORTED_MODULE_0__.TrackEvent("addtrack", { track }));\n  });\n}\nfunction removeVideoTrack(track){\n  const trackList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media?.videoTracks;\n  if(!trackList) return;\n  const trackSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).trackSet;\n  trackSet.delete(track);\n  queueMicrotask(()=> {\n    trackList.dispatchEvent(new _track_event_js__WEBPACK_IMPORTED_MODULE_0__.TrackEvent("removetrack", { track }));\n  });\n}\nfunction selectedChanged(selected){\n  const trackList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(selected).media.videoTracks ?? [];\n  let hasUnselected=false;\n  for (const track of trackList){\n    if(track===selected) continue;\n    track.selected=false;\n    hasUnselected=true;\n  }\n  if(hasUnselected){\n    if((0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested) return;\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested=true;\n    queueMicrotask(()=> {\n      delete (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested;\n      trackList.dispatchEvent(new Event("change"));\n    });\n  }\n}\nclass VideoTrackList extends EventTarget {\n  #addTrackCallback;\n  #removeTrackCallback;\n  #changeCallback;\n  constructor(){\n    super();\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet= new Set();\n  }\n  get #tracks(){\n    return (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet;\n  }\n  [Symbol.iterator](){\n    return this.#tracks.values();\n  }\n  get length(){\n    return this.#tracks.size;\n  }\n  getTrackById(id){\n    return [...this.#tracks].find((track)=> track.id===id) ?? null;\n  }\n  get selectedIndex(){\n    return [...this.#tracks].findIndex((track)=> track.selected);\n  }\n  get onaddtrack(){\n    return this.#addTrackCallback;\n  }\n  set onaddtrack(callback){\n    if(this.#addTrackCallback){\n      this.removeEventListener("addtrack", this.#addTrackCallback);\n      this.#addTrackCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#addTrackCallback=callback;\n      this.addEventListener("addtrack", callback);\n    }\n  }\n  get onremovetrack(){\n    return this.#removeTrackCallback;\n  }\n  set onremovetrack(callback){\n    if(this.#removeTrackCallback){\n      this.removeEventListener("removetrack", this.#removeTrackCallback);\n      this.#removeTrackCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#removeTrackCallback=callback;\n      this.addEventListener("removetrack", callback);\n    }\n  }\n  get onchange(){\n    return this.#changeCallback;\n  }\n  set onchange(callback){\n    if(this.#changeCallback){\n      this.removeEventListener("change", this.#changeCallback);\n      this.#changeCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#changeCallback=callback;\n      this.addEventListener("change", callback);\n    }\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/video-track-list.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   TrackEvent: ()=> ( TrackEvent)\n });\nclass TrackEvent extends Event {\n  track;\n  constructor(type, init){\n    super(type);\n    this.track=init.track;\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/track-event.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   RenditionEvent: ()=> ( RenditionEvent)\n });\nclass RenditionEvent extends Event {\n  rendition;\n  constructor(type, init){\n    super(type);\n    this.rendition=init.rendition;\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/rendition-event.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AudioTrackList: ()=> ( AudioTrackList),\n   addAudioTrack: ()=> ( addAudioTrack),\n   enabledChanged: ()=> ( enabledChanged),\n   removeAudioTrack: ()=> ( removeAudioTrack)\n });\n var _track_event_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 4);\n var _utils_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 0);\n\n\nfunction addAudioTrack(media, track){\n  const trackList=media.audioTracks;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media=media;\n  if(!(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet){\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).renditionSet= new Set();\n  }\n  const trackSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).trackSet;\n  trackSet.add(track);\n  const index=trackSet.size - 1;\n  if(!(index in AudioTrackList.prototype)){\n    Object.defineProperty(AudioTrackList.prototype, index, {\n      get(){\n        return [...(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet][index];\n      }\n    });\n  }\n  queueMicrotask(()=> {\n    trackList.dispatchEvent(new _track_event_js__WEBPACK_IMPORTED_MODULE_0__.TrackEvent("addtrack", { track }));\n  });\n}\nfunction removeAudioTrack(track){\n  const trackList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media?.audioTracks;\n  if(!trackList) return;\n  const trackSet=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).trackSet;\n  trackSet.delete(track);\n  queueMicrotask(()=> {\n    trackList.dispatchEvent(new _track_event_js__WEBPACK_IMPORTED_MODULE_0__.TrackEvent("removetrack", { track }));\n  });\n}\nfunction enabledChanged(track){\n  const trackList=(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(track).media.audioTracks;\n  if(!trackList||(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested) return;\n  (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested=true;\n  queueMicrotask(()=> {\n    delete (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(trackList).changeRequested;\n    trackList.dispatchEvent(new Event("change"));\n  });\n}\nclass AudioTrackList extends EventTarget {\n  #addTrackCallback;\n  #removeTrackCallback;\n  #changeCallback;\n  constructor(){\n    super();\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet= new Set();\n  }\n  get #tracks(){\n    return (0,_utils_js__WEBPACK_IMPORTED_MODULE_1__.getPrivate)(this).trackSet;\n  }\n  [Symbol.iterator](){\n    return this.#tracks.values();\n  }\n  get length(){\n    return this.#tracks.size;\n  }\n  getTrackById(id){\n    return [...this.#tracks].find((track)=> track.id===id) ?? null;\n  }\n  get onaddtrack(){\n    return this.#addTrackCallback;\n  }\n  set onaddtrack(callback){\n    if(this.#addTrackCallback){\n      this.removeEventListener("addtrack", this.#addTrackCallback);\n      this.#addTrackCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#addTrackCallback=callback;\n      this.addEventListener("addtrack", callback);\n    }\n  }\n  get onremovetrack(){\n    return this.#removeTrackCallback;\n  }\n  set onremovetrack(callback){\n    if(this.#removeTrackCallback){\n      this.removeEventListener("removetrack", this.#removeTrackCallback);\n      this.#removeTrackCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#removeTrackCallback=callback;\n      this.addEventListener("removetrack", callback);\n    }\n  }\n  get onchange(){\n    return this.#changeCallback;\n  }\n  set onchange(callback){\n    if(this.#changeCallback){\n      this.removeEventListener("change", this.#changeCallback);\n      this.#changeCallback=void 0;\n    }\n    if(typeof callback=="function"){\n      this.#changeCallback=callback;\n      this.addEventListener("change", callback);\n    }\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/audio-track-list.js?')},function(module,exports){eval('var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;\n(function(global, factory){\n\n\t"use strict";\n\n\tif(true&&typeof module.exports==="object"){\n\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery=require("jquery")(window);\n\t\t// See ticket trac-14549 for more info.\n\t\tmodule.exports=global.document ?\n\t\t\tfactory(global, true) :\n\t\t\tfunction(w){\n\t\t\t\tif(!w.document){\n\t\t\t\t\tthrow new Error("jQuery requires a window with a document");\n\t\t\t\t}\n\t\t\t\treturn factory(w);\n\t\t\t};\n\t}else{\n\t\tfactory(global);\n\t}\n\n// Pass this if window is not defined yet\n})(typeof window!=="undefined" ? window:this, function(window, noGlobal){\n\n// Edge <=12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1\n// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode\n// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common\n// enough that all such attempts are guarded in a try block.\n"use strict";\n\nvar arr=[];\n\nvar getProto=Object.getPrototypeOf;\n\nvar slice=arr.slice;\n\nvar flat=arr.flat ? function(array){\n\treturn arr.flat.call(array);\n}:function(array){\n\treturn arr.concat.apply([], array);\n};\n\n\nvar push=arr.push;\n\nvar indexOf=arr.indexOf;\n\nvar class2type={};\n\nvar toString=class2type.toString;\n\nvar hasOwn=class2type.hasOwnProperty;\n\nvar fnToString=hasOwn.toString;\n\nvar ObjectFunctionString=fnToString.call(Object);\n\nvar support={};\n\nvar isFunction=function isFunction(obj){\n\n\t\t// Support: Chrome <=57, Firefox <=52\n\t\t// In some browsers, typeof returns "function" for HTML <object> elements\n\t\t// (i.e., `typeof document.createElement("object")==="function"`).\n\t\t// We don\'t want to classify *any* DOM node as a function.\n\t\t// Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5\n\t\t// Plus for old WebKit, typeof returns "function" for HTML collections\n\t\t// (e.g., `typeof document.getElementsByTagName("div")==="function"`). (gh-4756)\n\t\treturn typeof obj==="function"&&typeof obj.nodeType!=="number"&&\n\t\t\ttypeof obj.item!=="function";\n\t};\n\n\nvar isWindow=function isWindow(obj){\n\t\treturn obj!=null&&obj===obj.window;\n\t};\n\n\nvar document=window.document;\n\n\n\n\tvar preservedScriptAttributes={\n\t\ttype: true,\n\t\tsrc: true,\n\t\tnonce: true,\n\t\tnoModule: true\n\t};\n\n\tfunction DOMEval(code, node, doc){\n\t\tdoc=doc||document;\n\n\t\tvar i, val,\n\t\t\tscript=doc.createElement("script");\n\n\t\tscript.text=code;\n\t\tif(node){\n\t\t\tfor(i in preservedScriptAttributes){\n\n\t\t\t\t// Support: Firefox 64+, Edge 18+\n\t\t\t\t// Some browsers don\'t support the "nonce" property on scripts.\n\t\t\t\t// On the other hand, just using `getAttribute` is not enough as\n\t\t\t\t// the `nonce` attribute is reset to an empty string whenever it\n\t\t\t\t// becomes browsing-context connected.\n\t\t\t\t// See https://github.com/whatwg/html/issues/2369\n\t\t\t\t// See https://html.spec.whatwg.org/#nonce-attributes\n\t\t\t\t// The `node.getAttribute` check was added for the sake of\n\t\t\t\t// `jQuery.globalEval` so that it can fake a nonce-containing node\n\t\t\t\t// via an object.\n\t\t\t\tval=node[ i ]||node.getAttribute&&node.getAttribute(i);\n\t\t\t\tif(val){\n\t\t\t\t\tscript.setAttribute(i, val);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdoc.head.appendChild(script).parentNode.removeChild(script);\n\t}\n\n\nfunction toType(obj){\n\tif(obj==null){\n\t\treturn obj + "";\n\t}\n\n\t// Support: Android <=2.3 only (functionish RegExp)\n\treturn typeof obj==="object"||typeof obj==="function" ?\n\t\tclass2type[ toString.call(obj) ]||"object" :\n\t\ttypeof obj;\n}\n\n// Defining this global in .eslintrc.json would create a danger of using the global\n// unguarded in another place, it seems safer to define global only for this module\n\n\n\nvar version="3.7.1",\n\n\trhtmlSuffix=/HTML$/i,\n\n\t// Define a local copy of jQuery\n\tjQuery=function(selector, context){\n\n\t\t// The jQuery object is actually just the init constructor \'enhanced\'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init(selector, context);\n\t};\n\njQuery.fn=jQuery.prototype={\n\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function(){\n\t\treturn slice.call(this);\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function(num){\n\n\t\t// Return all the elements in a clean array\n\t\tif(num==null){\n\t\t\treturn slice.call(this);\n\t\t}\n\n\t\t// Return just the one element from the set\n\t\treturn num < 0 ? this[ num + this.length ]:this[ num ];\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function(elems){\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret=jQuery.merge(this.constructor(), elems);\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject=this;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\teach: function(callback){\n\t\treturn jQuery.each(this, callback);\n\t},\n\n\tmap: function(callback){\n\t\treturn this.pushStack(jQuery.map(this, function(elem, i){\n\t\t\treturn callback.call(elem, i, elem);\n\t\t}) );\n\t},\n\n\tslice: function(){\n\t\treturn this.pushStack(slice.apply(this, arguments) );\n\t},\n\n\tfirst: function(){\n\t\treturn this.eq(0);\n\t},\n\n\tlast: function(){\n\t\treturn this.eq(-1);\n\t},\n\n\teven: function(){\n\t\treturn this.pushStack(jQuery.grep(this, function(_elem, i){\n\t\t\treturn(i + 1) % 2;\n\t\t}) );\n\t},\n\n\todd: function(){\n\t\treturn this.pushStack(jQuery.grep(this, function(_elem, i){\n\t\t\treturn i % 2;\n\t\t}) );\n\t},\n\n\teq: function(i){\n\t\tvar len=this.length,\n\t\t\tj=+i +(i < 0 ? len:0);\n\t\treturn this.pushStack(j >=0&&j < len ? [ this[ j ] ]:[]);\n\t},\n\n\tend: function(){\n\t\treturn this.prevObject||this.constructor();\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array\'s method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend=jQuery.fn.extend=function(){\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget=arguments[ 0 ]||{},\n\t\ti=1,\n\t\tlength=arguments.length,\n\t\tdeep=false;\n\n\t// Handle a deep copy situation\n\tif(typeof target==="boolean"){\n\t\tdeep=target;\n\n\t\t// Skip the boolean and the target\n\t\ttarget=arguments[ i ]||{};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif(typeof target!=="object"&&!isFunction(target) ){\n\t\ttarget={};\n\t}\n\n\t// Extend jQuery itself if only one argument is passed\n\tif(i===length){\n\t\ttarget=this;\n\t\ti--;\n\t}\n\n\tfor(; i < length; i++){\n\n\t\t// Only deal with non-null/undefined values\n\t\tif(( options=arguments[ i ])!=null){\n\n\t\t\t// Extend the base object\n\t\t\tfor(name in options){\n\t\t\t\tcopy=options[ name ];\n\n\t\t\t\t// Prevent Object.prototype pollution\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif(name==="__proto__"||target===copy){\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we\'re merging plain objects or arrays\n\t\t\t\tif(deep&&copy&&(jQuery.isPlainObject(copy)||\n\t\t\t\t\t(copyIsArray=Array.isArray(copy) )) ){\n\t\t\t\t\tsrc=target[ name ];\n\n\t\t\t\t\t// Ensure proper type for the source value\n\t\t\t\t\tif(copyIsArray&&!Array.isArray(src) ){\n\t\t\t\t\t\tclone=[];\n\t\t\t\t\t}else if(!copyIsArray&&!jQuery.isPlainObject(src) ){\n\t\t\t\t\t\tclone={};\n\t\t\t\t\t}else{\n\t\t\t\t\t\tclone=src;\n\t\t\t\t\t}\n\t\t\t\t\tcopyIsArray=false;\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ]=jQuery.extend(deep, clone, copy);\n\n\t\t\t\t// Don\'t bring in undefined values\n\t\t\t\t}else if(copy!==undefined){\n\t\t\t\t\ttarget[ name ]=copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\n\t// Unique for each copy of jQuery on the page\n\texpando: "jQuery" +(version + Math.random()).replace(/\\D/g, ""),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function(msg){\n\t\tthrow new Error(msg);\n\t},\n\n\tnoop: function(){},\n\n\tisPlainObject: function(obj){\n\t\tvar proto, Ctor;\n\n\t\t// Detect obvious negatives\n\t\t// Use toString instead of jQuery.type to catch host objects\n\t\tif(!obj||toString.call(obj)!=="[object Object]"){\n\t\t\treturn false;\n\t\t}\n\n\t\tproto=getProto(obj);\n\n\t\t// Objects with no prototype (e.g., `Object.create(null)`) are plain\n\t\tif(!proto){\n\t\t\treturn true;\n\t\t}\n\n\t\t// Objects with prototype are plain iff they were constructed by a global Object function\n\t\tCtor=hasOwn.call(proto, "constructor")&&proto.constructor;\n\t\treturn typeof Ctor==="function"&&fnToString.call(Ctor)===ObjectFunctionString;\n\t},\n\n\tisEmptyObject: function(obj){\n\t\tvar name;\n\n\t\tfor(name in obj){\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\t// Evaluates a script in a provided context; falls back to the global one\n\t// if not specified.\n\tglobalEval: function(code, options, doc){\n\t\tDOMEval(code, { nonce: options&&options.nonce }, doc);\n\t},\n\n\teach: function(obj, callback){\n\t\tvar length, i=0;\n\n\t\tif(isArrayLike(obj) ){\n\t\t\tlength=obj.length;\n\t\t\tfor(; i < length; i++){\n\t\t\t\tif(callback.call(obj[ i ], i, obj[ i ])===false){\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}else{\n\t\t\tfor(i in obj){\n\t\t\t\tif(callback.call(obj[ i ], i, obj[ i ])===false){\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\n\t// Retrieve the text value of an array of DOM nodes\n\ttext: function(elem){\n\t\tvar node,\n\t\t\tret="",\n\t\t\ti=0,\n\t\t\tnodeType=elem.nodeType;\n\n\t\tif(!nodeType){\n\n\t\t\t// If no nodeType, this is expected to be an array\n\t\t\twhile(( node=elem[ i++ ]) ){\n\n\t\t\t\t// Do not traverse comment nodes\n\t\t\t\tret +=jQuery.text(node);\n\t\t\t}\n\t\t}\n\t\tif(nodeType===1||nodeType===11){\n\t\t\treturn elem.textContent;\n\t\t}\n\t\tif(nodeType===9){\n\t\t\treturn elem.documentElement.textContent;\n\t\t}\n\t\tif(nodeType===3||nodeType===4){\n\t\t\treturn elem.nodeValue;\n\t\t}\n\n\t\t// Do not include comment or processing instruction nodes\n\n\t\treturn ret;\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function(arr, results){\n\t\tvar ret=results||[];\n\n\t\tif(arr!=null){\n\t\t\tif(isArrayLike(Object(arr) )){\n\t\t\t\tjQuery.merge(ret,\n\t\t\t\t\ttypeof arr==="string" ?\n\t\t\t\t\t\t[ arr ]:arr\n\t\t\t\t);\n\t\t\t}else{\n\t\t\t\tpush.call(ret, arr);\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function(elem, arr, i){\n\t\treturn arr==null ? -1:indexOf.call(arr, elem, i);\n\t},\n\n\tisXMLDoc: function(elem){\n\t\tvar namespace=elem&&elem.namespaceURI,\n\t\t\tdocElem=elem&&(elem.ownerDocument||elem).documentElement;\n\n\t\t// Assume HTML when documentElement doesn\'t yet exist, such as inside\n\t\t// document fragments.\n\t\treturn !rhtmlSuffix.test(namespace||docElem&&docElem.nodeName||"HTML");\n\t},\n\n\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t// push.apply(_, arraylike) throws on ancient WebKit\n\tmerge: function(first, second){\n\t\tvar len=+second.length,\n\t\t\tj=0,\n\t\t\ti=first.length;\n\n\t\tfor(; j < len; j++){\n\t\t\tfirst[ i++ ]=second[ j ];\n\t\t}\n\n\t\tfirst.length=i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function(elems, callback, invert){\n\t\tvar callbackInverse,\n\t\t\tmatches=[],\n\t\t\ti=0,\n\t\t\tlength=elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor(; i < length; i++){\n\t\t\tcallbackInverse = !callback(elems[ i ], i);\n\t\t\tif(callbackInverse!==callbackExpect){\n\t\t\t\tmatches.push(elems[ i ]);\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function(elems, callback, arg){\n\t\tvar length, value,\n\t\t\ti=0,\n\t\t\tret=[];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif(isArrayLike(elems) ){\n\t\t\tlength=elems.length;\n\t\t\tfor(; i < length; i++){\n\t\t\t\tvalue=callback(elems[ i ], i, arg);\n\n\t\t\t\tif(value!=null){\n\t\t\t\t\tret.push(value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t}else{\n\t\t\tfor(i in elems){\n\t\t\t\tvalue=callback(elems[ i ], i, arg);\n\n\t\t\t\tif(value!=null){\n\t\t\t\t\tret.push(value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn flat(ret);\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n});\n\nif(typeof Symbol==="function"){\n\tjQuery.fn[ Symbol.iterator ]=arr[ Symbol.iterator ];\n}\n\n// Populate the class2type map\njQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),\n\tfunction(_i, name){\n\t\tclass2type[ "[object " + name + "]" ]=name.toLowerCase();\n\t});\n\nfunction isArrayLike(obj){\n\n\t// Support: real iOS 8.2 only (not reproducible in simulator)\n\t// `in` check used to prevent JIT error (gh-2145)\n\t// hasOwn isn\'t used here due to false negatives\n\t// regarding Nodelist length in IE\n\tvar length = !!obj&&"length" in obj&&obj.length,\n\t\ttype=toType(obj);\n\n\tif(isFunction(obj)||isWindow(obj) ){\n\t\treturn false;\n\t}\n\n\treturn type==="array"||length===0||\n\t\ttypeof length==="number"&&length > 0&&(length - 1) in obj;\n}\n\n\nfunction nodeName(elem, name){\n\n\treturn elem.nodeName&&elem.nodeName.toLowerCase()===name.toLowerCase();\n\n}\nvar pop=arr.pop;\n\n\nvar sort=arr.sort;\n\n\nvar splice=arr.splice;\n\n\nvar whitespace="[\\\\x20\\\\t\\\\r\\\\n\\\\f]";\n\n\nvar rtrimCSS=new RegExp(\n\t"^" + whitespace + "+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)" + whitespace + "+$",\n\t"g"\n);\n\n\n\n\n// Note: an element does not contain itself\njQuery.contains=function(a, b){\n\tvar bup=b&&b.parentNode;\n\n\treturn a===bup||!!(bup&&bup.nodeType===1&&(\n\n\t\t// Support: IE 9 - 11+\n\t\t// IE doesn\'t have `contains` on SVG.\n\t\ta.contains ?\n\t\t\ta.contains(bup) :\n\t\t\ta.compareDocumentPosition&&a.compareDocumentPosition(bup) & 16\n\t));\n};\n\n\n\n\n// CSS string/identifier serialization\n// https://drafts.csswg.org/cssom/#common-serializing-idioms\nvar rcssescape=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\x80-\\uFFFF\\w-]/g;\n\nfunction fcssescape(ch, asCodePoint){\n\tif(asCodePoint){\n\n\t\t// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER\n\t\tif(ch==="\\0"){\n\t\t\treturn "\\uFFFD";\n\t\t}\n\n\t\t// Control characters and (dependent upon position) numbers get escaped as code points\n\t\treturn ch.slice(0, -1) + "\\\\" + ch.charCodeAt(ch.length - 1).toString(16) + " ";\n\t}\n\n\t// Other potentially-special ASCII characters get backslash-escaped\n\treturn "\\\\" + ch;\n}\n\njQuery.escapeSelector=function(sel){\n\treturn(sel + "").replace(rcssescape, fcssescape);\n};\n\n\n\n\nvar preferredDoc=document,\n\tpushNative=push;\n\n(function(){\n\nvar i,\n\tExpr,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\tpush=pushNative,\n\n\t// Local document vars\n\tdocument,\n\tdocumentElement,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\tmatches,\n\n\t// Instance-specific data\n\texpando=jQuery.expando,\n\tdirruns=0,\n\tdone=0,\n\tclassCache=createCache(),\n\ttokenCache=createCache(),\n\tcompilerCache=createCache(),\n\tnonnativeSelectorCache=createCache(),\n\tsortOrder=function(a, b){\n\t\tif(a===b){\n\t\t\thasDuplicate=true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\tbooleans="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +\n\t\t"loop|multiple|open|readonly|required|scoped",\n\n\t// Regular expressions\n\n\t// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram\n\tidentifier="(?:\\\\\\\\[\\\\da-fA-F]{1,6}" + whitespace +\n\t\t"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+",\n\n\t// Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes="\\\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +\n\n\t\t// Operator (capture 2)\n\t\t"*([*^$|!~]?=)" + whitespace +\n\n\t\t// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"\n\t\t"*(?:\'((?:\\\\\\\\.|[^\\\\\\\\\'])*)\'|\\"((?:\\\\\\\\.|[^\\\\\\\\\\"])*)\\"|(" + identifier + "))|)" +\n\t\twhitespace + "*\\\\]",\n\n\tpseudos=":(" + identifier + ")(?:\\\\((" +\n\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t"(\'((?:\\\\\\\\.|[^\\\\\\\\\'])*)\'|\\"((?:\\\\\\\\.|[^\\\\\\\\\\"])*)\\")|" +\n\n\t\t// 2. simple (capture 6)\n\t\t"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|" + attributes + ")*)|" +\n\n\t\t// 3. anything else (capture 2)\n\t\t".*" +\n\t\t")\\\\)|)",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace=new RegExp(whitespace + "+", "g"),\n\n\trcomma=new RegExp("^" + whitespace + "*," + whitespace + "*"),\n\trleadingCombinator=new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" +\n\t\twhitespace + "*"),\n\trdescend=new RegExp(whitespace + "|>"),\n\n\trpseudo=new RegExp(pseudos),\n\tridentifier=new RegExp("^" + identifier + "$"),\n\n\tmatchExpr={\n\t\tID: new RegExp("^#(" + identifier + ")"),\n\t\tCLASS: new RegExp("^\\\\.(" + identifier + ")"),\n\t\tTAG: new RegExp("^(" + identifier + "|[*])"),\n\t\tATTR: new RegExp("^" + attributes),\n\t\tPSEUDO: new RegExp("^" + pseudos),\n\t\tCHILD: new RegExp(\n\t\t\t"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(" +\n\t\t\t\twhitespace + "*(even|odd|(([+-]|)(\\\\d*)n|)" + whitespace + "*(?:([+-]|)" +\n\t\t\t\twhitespace + "*(\\\\d+)|))" + whitespace + "*\\\\)|)", "i"),\n\t\tbool: new RegExp("^(?:" + booleans + ")$", "i"),\n\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\tneedsContext: new RegExp("^" + whitespace +\n\t\t\t"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(" + whitespace +\n\t\t\t"*((?:-\\\\d)?\\\\d*)" + whitespace + "*\\\\)|)(?=[^-]|$)", "i")\n\t},\n\n\trinputs=/^(?:input|select|textarea|button)$/i,\n\trheader=/^h\\d$/i,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling=/[+~]/,\n\n\t// CSS escapes\n\t// https://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape=new RegExp("\\\\\\\\[\\\\da-fA-F]{1,6}" + whitespace +\n\t\t"?|\\\\\\\\([^\\\\r\\\\n\\\\f])", "g"),\n\tfunescape=function(escape, nonHex){\n\t\tvar high="0x" + escape.slice(1) - 0x10000;\n\n\t\tif(nonHex){\n\n\t\t\t// Strip the backslash prefix from a non-hex escape sequence\n\t\t\treturn nonHex;\n\t\t}\n\n\t\t// Replace a hexadecimal escape sequence with the encoded Unicode code point\n\t\t// Support: IE <=11+\n\t\t// For values outside the Basic Multilingual Plane (BMP), manually construct a\n\t\t// surrogate pair\n\t\treturn high < 0 ?\n\t\t\tString.fromCharCode(high + 0x10000) :\n\t\t\tString.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);\n\t},\n\n\t// Used for iframes; see `setDocument`.\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// Removing the function wrapper causes a "Permission Denied"\n\t// error in IE/Edge.\n\tunloadHandler=function(){\n\t\tsetDocument();\n\t},\n\n\tinDisabledFieldset=addCombinator(\n\t\tfunction(elem){\n\t\t\treturn elem.disabled===true&&nodeName(elem, "fieldset");\n\t\t},\n\t\t{ dir: "parentNode", next: "legend" }\n\t);\n\n// Support: IE <=9 only\n// Accessing document.activeElement can throw unexpectedly\n// https://bugs.jquery.com/ticket/13393\nfunction safeActiveElement(){\n\ttry {\n\t\treturn document.activeElement;\n\t} catch(err){ }\n}\n\n// Optimize for push.apply(_, NodeList)\ntry {\n\tpush.apply(\n\t\t(arr=slice.call(preferredDoc.childNodes) ),\n\t\tpreferredDoc.childNodes\n\t);\n\n\t// Support: Android <=4.0\n\t// Detect silently failing push.apply\n\t// eslint-disable-next-line no-unused-expressions\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch(e){\n\tpush={\n\t\tapply: function(target, els){\n\t\t\tpushNative.apply(target, slice.call(els) );\n\t\t},\n\t\tcall: function(target){\n\t\t\tpushNative.apply(target, slice.call(arguments, 1) );\n\t\t}\n\t};\n}\n\nfunction find(selector, context, results, seed){\n\tvar m, i, elem, nid, match, groups, newSelector,\n\t\tnewContext=context&&context.ownerDocument,\n\n\t\t// nodeType defaults to 9, since context defaults to document\n\t\tnodeType=context ? context.nodeType:9;\n\n\tresults=results||[];\n\n\t// Return early from calls with invalid selector or context\n\tif(typeof selector!=="string"||!selector||\n\t\tnodeType!==1&&nodeType!==9&&nodeType!==11){\n\n\t\treturn results;\n\t}\n\n\t// Try to shortcut find operations (as opposed to filters) in HTML documents\n\tif(!seed){\n\t\tsetDocument(context);\n\t\tcontext=context||document;\n\n\t\tif(documentIsHTML){\n\n\t\t\t// If the selector is sufficiently simple, try using a "get*By*" DOM method\n\t\t\t// (excepting DocumentFragment context, where the methods don\'t exist)\n\t\t\tif(nodeType!==11&&(match=rquickExpr.exec(selector) )){\n\n\t\t\t\t// ID selector\n\t\t\t\tif(( m=match[ 1 ]) ){\n\n\t\t\t\t\t// Document context\n\t\t\t\t\tif(nodeType===9){\n\t\t\t\t\t\tif(( elem=context.getElementById(m) )){\n\n\t\t\t\t\t\t\t// Support: IE 9 only\n\t\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\t\tif(elem.id===m){\n\t\t\t\t\t\t\t\tpush.call(results, elem);\n\t\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// Element context\n\t\t\t\t\t}else{\n\n\t\t\t\t\t\t// Support: IE 9 only\n\t\t\t\t\t\t// getElementById can match elements by name instead of ID\n\t\t\t\t\t\tif(newContext&&(elem=newContext.getElementById(m) )&&\n\t\t\t\t\t\t\tfind.contains(context, elem)&&\n\t\t\t\t\t\t\telem.id===m){\n\n\t\t\t\t\t\t\tpush.call(results, elem);\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t// Type selector\n\t\t\t\t}else if(match[ 2 ]){\n\t\t\t\t\tpush.apply(results, context.getElementsByTagName(selector) );\n\t\t\t\t\treturn results;\n\n\t\t\t\t// Class selector\n\t\t\t\t}else if(( m=match[ 3 ])&&context.getElementsByClassName){\n\t\t\t\t\tpush.apply(results, context.getElementsByClassName(m) );\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Take advantage of querySelectorAll\n\t\t\tif(!nonnativeSelectorCache[ selector + " " ]&&\n\t\t\t\t(!rbuggyQSA||!rbuggyQSA.test(selector) )){\n\n\t\t\t\tnewSelector=selector;\n\t\t\t\tnewContext=context;\n\n\t\t\t\t// qSA considers elements outside a scoping root when evaluating child or\n\t\t\t\t// descendant combinators, which is not what we want.\n\t\t\t\t// In such cases, we work around the behavior by prefixing every selector in the\n\t\t\t\t// list with an ID selector referencing the scope context.\n\t\t\t\t// The technique has to be used as well when a leading combinator is used\n\t\t\t\t// as such selectors are not recognized by querySelectorAll.\n\t\t\t\t// Thanks to Andrew Dupont for this technique.\n\t\t\t\tif(nodeType===1&&\n\t\t\t\t\t(rdescend.test(selector)||rleadingCombinator.test(selector) )){\n\n\t\t\t\t\t// Expand context for sibling selectors\n\t\t\t\t\tnewContext=rsibling.test(selector)&&testContext(context.parentNode)||\n\t\t\t\t\t\tcontext;\n\n\t\t\t\t\t// We can use :scope instead of the ID hack if the browser\n\t\t\t\t\t// supports it & if we\'re not changing the context.\n\t\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t\t// IE/Edge sometimes throw a "Permission denied" error when\n\t\t\t\t\t// strict-comparing two documents; shallow comparisons work.\n\t\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\t\tif(newContext!=context||!support.scope){\n\n\t\t\t\t\t\t// Capture the context ID, setting it first if necessary\n\t\t\t\t\t\tif(( nid=context.getAttribute("id") )){\n\t\t\t\t\t\t\tnid=jQuery.escapeSelector(nid);\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tcontext.setAttribute("id",(nid=expando) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prefix every selector in the list\n\t\t\t\t\tgroups=tokenize(selector);\n\t\t\t\t\ti=groups.length;\n\t\t\t\t\twhile(i--){\n\t\t\t\t\t\tgroups[ i ]=(nid ? "#" + nid:":scope") + " " +\n\t\t\t\t\t\t\ttoSelector(groups[ i ]);\n\t\t\t\t\t}\n\t\t\t\t\tnewSelector=groups.join(",");\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply(results,\n\t\t\t\t\t\tnewContext.querySelectorAll(newSelector)\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError){\n\t\t\t\t\tnonnativeSelectorCache(selector, true);\n\t\t\t\t} finally {\n\t\t\t\t\tif(nid===expando){\n\t\t\t\t\t\tcontext.removeAttribute("id");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select(selector.replace(rtrimCSS, "$1"), context, results, seed);\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {function(string, object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache(){\n\tvar keys=[];\n\n\tfunction cache(key, value){\n\n\t\t// Use (key + " ") to avoid collision with native prototype properties\n\t\t// (see https://github.com/jquery/sizzle/issues/157)\n\t\tif(keys.push(key + " ") > Expr.cacheLength){\n\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn(cache[ key + " " ]=value);\n\t}\n\treturn cache;\n}\n\n\nfunction markFunction(fn){\n\tfn[ expando ]=true;\n\treturn fn;\n}\n\n\nfunction assert(fn){\n\tvar el=document.createElement("fieldset");\n\n\ttry {\n\t\treturn !!fn(el);\n\t} catch(e){\n\t\treturn false;\n\t} finally {\n\n\t\t// Remove from its parent by default\n\t\tif(el.parentNode){\n\t\t\tel.parentNode.removeChild(el);\n\t\t}\n\n\t\t// release memory in IE\n\t\tel=null;\n\t}\n}\n\n\nfunction createInputPseudo(type){\n\treturn function(elem){\n\t\treturn nodeName(elem, "input")&&elem.type===type;\n\t};\n}\n\n\nfunction createButtonPseudo(type){\n\treturn function(elem){\n\t\treturn(nodeName(elem, "input")||nodeName(elem, "button") )&&\n\t\t\telem.type===type;\n\t};\n}\n\n\nfunction createDisabledPseudo(disabled){\n\n\t// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable\n\treturn function(elem){\n\n\t\t// Only certain elements can match :enabled or :disabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled\n\t\t// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled\n\t\tif("form" in elem){\n\n\t\t\t// Check for inherited disabledness on relevant non-disabled elements:\n\t\t\t// * listed form-associated elements in a disabled fieldset\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#category-listed\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled\n\t\t\t// * option elements in a disabled optgroup\n\t\t\t//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled\n\t\t\t// All such elements have a "form" property.\n\t\t\tif(elem.parentNode&&elem.disabled===false){\n\n\t\t\t\t// Option elements defer to a parent optgroup if present\n\t\t\t\tif("label" in elem){\n\t\t\t\t\tif("label" in elem.parentNode){\n\t\t\t\t\t\treturn elem.parentNode.disabled===disabled;\n\t\t\t\t\t}else{\n\t\t\t\t\t\treturn elem.disabled===disabled;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Support: IE 6 - 11+\n\t\t\t\t// Use the isDisabled shortcut property to check for disabled fieldset ancestors\n\t\t\t\treturn elem.isDisabled===disabled||\n\n\t\t\t\t\t// Where there is no isDisabled, check manually\n\t\t\t\t\telem.isDisabled!==!disabled&&\n\t\t\t\t\t\tinDisabledFieldset(elem)===disabled;\n\t\t\t}\n\n\t\t\treturn elem.disabled===disabled;\n\n\t\t// Try to winnow out elements that can\'t be disabled before trusting the disabled property.\n\t\t// Some victims get caught in our net (label, legend, menu, track), but it shouldn\'t\n\t\t// even exist on them, let alone have a boolean value.\n\t\t}else if("label" in elem){\n\t\t\treturn elem.disabled===disabled;\n\t\t}\n\n\t\t// Remaining elements are neither :enabled nor :disabled\n\t\treturn false;\n\t};\n}\n\n\nfunction createPositionalPseudo(fn){\n\treturn markFunction(function(argument){\n\t\targument=+argument;\n\t\treturn markFunction(function(seed, matches){\n\t\t\tvar j,\n\t\t\t\tmatchIndexes=fn([], seed.length, argument),\n\t\t\t\ti=matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile(i--){\n\t\t\t\tif(seed[(j=matchIndexes[ i ]) ]){\n\t\t\t\t\tseed[ j ] = !(matches[ j ]=seed[ j ]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n\nfunction testContext(context){\n\treturn context&&typeof context.getElementsByTagName!=="undefined"&&context;\n}\n\n\nfunction setDocument(node){\n\tvar subWindow,\n\t\tdoc=node ? node.ownerDocument||node:preferredDoc;\n\n\t// Return early if doc is invalid or already selected\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif(doc==document||doc.nodeType!==9||!doc.documentElement){\n\t\treturn document;\n\t}\n\n\t// Update global variables\n\tdocument=doc;\n\tdocumentElement=document.documentElement;\n\tdocumentIsHTML = !jQuery.isXMLDoc(document);\n\n\t// Support: iOS 7 only, IE 9 - 11+\n\t// Older browsers didn\'t support unprefixed `matches`.\n\tmatches=documentElement.matches||\n\t\tdocumentElement.webkitMatchesSelector||\n\t\tdocumentElement.msMatchesSelector;\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// Accessing iframe documents after unload throws "permission denied" errors\n\t// (see trac-13936).\n\t// Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`,\n\t// all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.\n\tif(documentElement.msMatchesSelector&&\n\n\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t// two documents; shallow comparisons work.\n\t\t// eslint-disable-next-line eqeqeq\n\t\tpreferredDoc!=document&&\n\t\t(subWindow=document.defaultView)&&subWindow.top!==subWindow){\n\n\t\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t\tsubWindow.addEventListener("unload", unloadHandler);\n\t}\n\n\t// Support: IE <10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don\'t pick up programmatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById=assert(function(el){\n\t\tdocumentElement.appendChild(el).id=jQuery.expando;\n\t\treturn !document.getElementsByName||\n\t\t\t!document.getElementsByName(jQuery.expando).length;\n\t});\n\n\t// Support: IE 9 only\n\t// Check to see if it\'s possible to do matchesSelector\n\t// on a disconnected node.\n\tsupport.disconnectedMatch=assert(function(el){\n\t\treturn matches.call(el, "*");\n\t});\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+\n\t// IE/Edge don\'t support the :scope pseudo-class.\n\tsupport.scope=assert(function(){\n\t\treturn document.querySelectorAll(":scope");\n\t});\n\n\t// Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only\n\t// Make sure the `:has()` argument is parsed unforgivingly.\n\t// We include `*` in the test to detect buggy implementations that are\n\t// _selectively_ forgiving (specifically when the list includes at least\n\t// one valid selector).\n\t// Note that we treat complete lack of support for `:has()` as if it were\n\t// spec-compliant support, which is fine because use of `:has()` in such\n\t// environments will fail in the qSA path and fall back to jQuery traversal\n\t// anyway.\n\tsupport.cssHas=assert(function(){\n\t\ttry {\n\t\t\tdocument.querySelector(":has(*,:jqfake)");\n\t\t\treturn false;\n\t\t} catch(e){\n\t\t\treturn true;\n\t\t}\n\t});\n\n\t// ID filter and find\n\tif(support.getById){\n\t\tExpr.filter.ID=function(id){\n\t\t\tvar attrId=id.replace(runescape, funescape);\n\t\t\treturn function(elem){\n\t\t\t\treturn elem.getAttribute("id")===attrId;\n\t\t\t};\n\t\t};\n\t\tExpr.find.ID=function(id, context){\n\t\t\tif(typeof context.getElementById!=="undefined"&&documentIsHTML){\n\t\t\t\tvar elem=context.getElementById(id);\n\t\t\t\treturn elem ? [ elem ]:[];\n\t\t\t}\n\t\t};\n\t}else{\n\t\tExpr.filter.ID=function(id){\n\t\t\tvar attrId=id.replace(runescape, funescape);\n\t\t\treturn function(elem){\n\t\t\t\tvar node=typeof elem.getAttributeNode!=="undefined"&&\n\t\t\t\t\telem.getAttributeNode("id");\n\t\t\t\treturn node&&node.value===attrId;\n\t\t\t};\n\t\t};\n\n\t\t// Support: IE 6 - 7 only\n\t\t// getElementById is not reliable as a find shortcut\n\t\tExpr.find.ID=function(id, context){\n\t\t\tif(typeof context.getElementById!=="undefined"&&documentIsHTML){\n\t\t\t\tvar node, i, elems,\n\t\t\t\t\telem=context.getElementById(id);\n\n\t\t\t\tif(elem){\n\n\t\t\t\t\t// Verify the id attribute\n\t\t\t\t\tnode=elem.getAttributeNode("id");\n\t\t\t\t\tif(node&&node.value===id){\n\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fall back on getElementsByName\n\t\t\t\t\telems=context.getElementsByName(id);\n\t\t\t\t\ti=0;\n\t\t\t\t\twhile(( elem=elems[ i++ ]) ){\n\t\t\t\t\t\tnode=elem.getAttributeNode("id");\n\t\t\t\t\t\tif(node&&node.value===id){\n\t\t\t\t\t\t\treturn [ elem ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn [];\n\t\t\t}\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find.TAG=function(tag, context){\n\t\tif(typeof context.getElementsByTagName!=="undefined"){\n\t\t\treturn context.getElementsByTagName(tag);\n\n\t\t// DocumentFragment nodes don\'t have gEBTN\n\t\t}else{\n\t\t\treturn context.querySelectorAll(tag);\n\t\t}\n\t};\n\n\t// Class\n\tExpr.find.CLASS=function(className, context){\n\t\tif(typeof context.getElementsByClassName!=="undefined"&&documentIsHTML){\n\t\t\treturn context.getElementsByClassName(className);\n\t\t}\n\t};\n\n\t\n\n\t// QSA and matchesSelector support\n\n\trbuggyQSA=[];\n\n\t// Build QSA regex\n\t// Regex strategy adopted from Diego Perini\n\tassert(function(el){\n\n\t\tvar input;\n\n\t\tdocumentElement.appendChild(el).innerHTML=\n\t\t\t"<a id=\'" + expando + "\' href=\'\' disabled=\'disabled\'></a>" +\n\t\t\t"<select id=\'" + expando + "-\\r\\\\\' disabled=\'disabled\'>" +\n\t\t\t"<option selected=\'\'></option></select>";\n\n\t\t// Support: iOS <=7 - 8 only\n\t\t// Boolean attributes and "value" are not treated correctly in some XML documents\n\t\tif(!el.querySelectorAll("[selected]").length){\n\t\t\trbuggyQSA.push("\\\\[" + whitespace + "*(?:value|" + booleans + ")");\n\t\t}\n\n\t\t// Support: iOS <=7 - 8 only\n\t\tif(!el.querySelectorAll("[id~=" + expando + "-]").length){\n\t\t\trbuggyQSA.push("~=");\n\t\t}\n\n\t\t// Support: iOS 8 only\n\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t// In-page `selector#id sibling-combinator selector` fails\n\t\tif(!el.querySelectorAll("a#" + expando + "+*").length){\n\t\t\trbuggyQSA.push(".#.+[+~]");\n\t\t}\n\n\t\t// Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+\n\t\t// In some of the document kinds, these selectors wouldn\'t work natively.\n\t\t// This is probably OK but for backwards compatibility we want to maintain\n\t\t// handling them through jQuery traversal in jQuery 3.x.\n\t\tif(!el.querySelectorAll(":checked").length){\n\t\t\trbuggyQSA.push(":checked");\n\t\t}\n\n\t\t// Support: Windows 8 Native Apps\n\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\tinput=document.createElement("input");\n\t\tinput.setAttribute("type", "hidden");\n\t\tel.appendChild(input).setAttribute("name", "D");\n\n\t\t// Support: IE 9 - 11+\n\t\t// IE\'s :disabled selector does not pick up the children of disabled fieldsets\n\t\t// Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+\n\t\t// In some of the document kinds, these selectors wouldn\'t work natively.\n\t\t// This is probably OK but for backwards compatibility we want to maintain\n\t\t// handling them through jQuery traversal in jQuery 3.x.\n\t\tdocumentElement.appendChild(el).disabled=true;\n\t\tif(el.querySelectorAll(":disabled").length!==2){\n\t\t\trbuggyQSA.push(":enabled", ":disabled");\n\t\t}\n\n\t\t// Support: IE 11+, Edge 15 - 18+\n\t\t// IE 11/Edge don\'t find elements on a `[name=\'\']` query in some cases.\n\t\t// Adding a temporary attribute to the document before the selection works\n\t\t// around the issue.\n\t\t// Interestingly, IE 10 & older don\'t seem to have the issue.\n\t\tinput=document.createElement("input");\n\t\tinput.setAttribute("name", "");\n\t\tel.appendChild(input);\n\t\tif(!el.querySelectorAll("[name=\'\']").length){\n\t\t\trbuggyQSA.push("\\\\[" + whitespace + "*name" + whitespace + "*=" +\n\t\t\t\twhitespace + "*(?:\'\'|\\"\\")");\n\t\t}\n\t});\n\n\tif(!support.cssHas){\n\n\t\t// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+\n\t\t// Our regular `try-catch` mechanism fails to detect natively-unsupported\n\t\t// pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)\n\t\t// in browsers that parse the `:has()` argument as a forgiving selector list.\n\t\t// https://drafts.csswg.org/selectors/#relational now requires the argument\n\t\t// to be parsed unforgivingly, but browsers have not yet fully adjusted.\n\t\trbuggyQSA.push(":has");\n\t}\n\n\trbuggyQSA=rbuggyQSA.length&&new RegExp(rbuggyQSA.join("|") );\n\n\t\n\n\t// Document order sorting\n\tsortOrder=function(a, b){\n\n\t\t// Flag for duplicate removal\n\t\tif(a===b){\n\t\t\thasDuplicate=true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif(compare){\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t// two documents; shallow comparisons work.\n\t\t// eslint-disable-next-line eqeqeq\n\t\tcompare=(a.ownerDocument||a)==(b.ownerDocument||b) ?\n\t\t\ta.compareDocumentPosition(b) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif(compare & 1||\n\t\t\t(!support.sortDetached&&b.compareDocumentPosition(a)===compare) ){\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif(a===document||a.ownerDocument==preferredDoc&&\n\t\t\t\tfind.contains(preferredDoc, a) ){\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tif(b===document||b.ownerDocument==preferredDoc&&\n\t\t\t\tfind.contains(preferredDoc, b) ){\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t(indexOf.call(sortInput, a) - indexOf.call(sortInput, b) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1:1;\n\t};\n\n\treturn document;\n}\n\nfind.matches=function(expr, elements){\n\treturn find(expr, null, null, elements);\n};\n\nfind.matchesSelector=function(elem, expr){\n\tsetDocument(elem);\n\n\tif(documentIsHTML&&\n\t\t!nonnativeSelectorCache[ expr + " " ]&&\n\t\t(!rbuggyQSA||!rbuggyQSA.test(expr) )){\n\n\t\ttry {\n\t\t\tvar ret=matches.call(elem, expr);\n\n\t\t\t// IE 9\'s matchesSelector returns false on disconnected nodes\n\t\t\tif(ret||support.disconnectedMatch||\n\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document&&elem.document.nodeType!==11){\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch(e){\n\t\t\tnonnativeSelectorCache(expr, true);\n\t\t}\n\t}\n\n\treturn find(expr, document, null, [ elem ]).length > 0;\n};\n\nfind.contains=function(context, elem){\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif(( context.ownerDocument||context)!=document){\n\t\tsetDocument(context);\n\t}\n\treturn jQuery.contains(context, elem);\n};\n\n\nfind.attr=function(elem, name){\n\n\t// Set document vars if needed\n\t// Support: IE 11+, Edge 17 - 18+\n\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t// two documents; shallow comparisons work.\n\t// eslint-disable-next-line eqeqeq\n\tif(( elem.ownerDocument||elem)!=document){\n\t\tsetDocument(elem);\n\t}\n\n\tvar fn=Expr.attrHandle[ name.toLowerCase() ],\n\n\t\t// Don\'t get fooled by Object.prototype properties (see trac-13807)\n\t\tval=fn&&hasOwn.call(Expr.attrHandle, name.toLowerCase()) ?\n\t\t\tfn(elem, name, !documentIsHTML) :\n\t\t\tundefined;\n\n\tif(val!==undefined){\n\t\treturn val;\n\t}\n\n\treturn elem.getAttribute(name);\n};\n\nfind.error=function(msg){\n\tthrow new Error("Syntax error, unrecognized expression: " + msg);\n};\n\n\njQuery.uniqueSort=function(results){\n\tvar elem,\n\t\tduplicates=[],\n\t\tj=0,\n\t\ti=0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\t//\n\t// Support: Android <=4.0+\n\t// Testing for detecting duplicates is unpredictable so instead assume we can\'t\n\t// depend on duplicate detection in all browsers without a stable sort.\n\thasDuplicate = !support.sortStable;\n\tsortInput = !support.sortStable&&slice.call(results, 0);\n\tsort.call(results, sortOrder);\n\n\tif(hasDuplicate){\n\t\twhile(( elem=results[ i++ ]) ){\n\t\t\tif(elem===results[ i ]){\n\t\t\t\tj=duplicates.push(i);\n\t\t\t}\n\t\t}\n\t\twhile(j--){\n\t\t\tsplice.call(results, duplicates[ j ], 1);\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput=null;\n\n\treturn results;\n};\n\njQuery.fn.uniqueSort=function(){\n\treturn this.pushStack(jQuery.uniqueSort(slice.apply(this) ));\n};\n\nExpr=jQuery.expr={\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t">": { dir: "parentNode", first: true },\n\t\t" ": { dir: "parentNode" },\n\t\t"+": { dir: "previousSibling", first: true },\n\t\t"~": { dir: "previousSibling" }\n\t},\n\n\tpreFilter: {\n\t\tATTR: function(match){\n\t\t\tmatch[ 1 ]=match[ 1 ].replace(runescape, funescape);\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[ 3 ]=(match[ 3 ]||match[ 4 ]||match[ 5 ]||"")\n\t\t\t\t.replace(runescape, funescape);\n\n\t\t\tif(match[ 2 ]==="~="){\n\t\t\t\tmatch[ 3 ]=" " + match[ 3 ] + " ";\n\t\t\t}\n\n\t\t\treturn match.slice(0, 4);\n\t\t},\n\n\t\tCHILD: function(match){\n\n\t\t\t\n\t\t\tmatch[ 1 ]=match[ 1 ].toLowerCase();\n\n\t\t\tif(match[ 1 ].slice(0, 3)==="nth"){\n\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif(!match[ 3 ]){\n\t\t\t\t\tfind.error(match[ 0 ]);\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[ 4 ]=+(match[ 4 ] ?\n\t\t\t\t\tmatch[ 5 ] +(match[ 6 ]||1) :\n\t\t\t\t\t2 *(match[ 3 ]==="even"||match[ 3 ]==="odd")\n\t\t\t\t);\n\t\t\t\tmatch[ 5 ]=+(( match[ 7 ] + match[ 8 ])||match[ 3 ]==="odd");\n\n\t\t\t// other types prohibit arguments\n\t\t\t}else if(match[ 3 ]){\n\t\t\t\tfind.error(match[ 0 ]);\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\tPSEUDO: function(match){\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[ 6 ]&&match[ 2 ];\n\n\t\t\tif(matchExpr.CHILD.test(match[ 0 ]) ){\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif(match[ 3 ]){\n\t\t\t\tmatch[ 2 ]=match[ 4 ]||match[ 5 ]||"";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t}else if(unquoted&&rpseudo.test(unquoted)&&\n\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess=tokenize(unquoted, true) )&&\n\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess=unquoted.indexOf(")", unquoted.length - excess) - unquoted.length) ){\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[ 0 ]=match[ 0 ].slice(0, excess);\n\t\t\t\tmatch[ 2 ]=unquoted.slice(0, excess);\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice(0, 3);\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\tTAG: function(nodeNameSelector){\n\t\t\tvar expectedNodeName=nodeNameSelector.replace(runescape, funescape).toLowerCase();\n\t\t\treturn nodeNameSelector==="*" ?\n\t\t\t\tfunction(){\n\t\t\t\t\treturn true;\n\t\t\t\t} :\n\t\t\t\tfunction(elem){\n\t\t\t\t\treturn nodeName(elem, expectedNodeName);\n\t\t\t\t};\n\t\t},\n\n\t\tCLASS: function(className){\n\t\t\tvar pattern=classCache[ className + " " ];\n\n\t\t\treturn pattern||\n\t\t\t\t(pattern=new RegExp("(^|" + whitespace + ")" + className +\n\t\t\t\t\t"(" + whitespace + "|$)") )&&\n\t\t\t\tclassCache(className, function(elem){\n\t\t\t\t\treturn pattern.test(\n\t\t\t\t\t\ttypeof elem.className==="string"&&elem.className||\n\t\t\t\t\t\t\ttypeof elem.getAttribute!=="undefined"&&\n\t\t\t\t\t\t\t\telem.getAttribute("class")||\n\t\t\t\t\t\t\t""\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t},\n\n\t\tATTR: function(name, operator, check){\n\t\t\treturn function(elem){\n\t\t\t\tvar result=find.attr(elem, name);\n\n\t\t\t\tif(result==null){\n\t\t\t\t\treturn operator==="!=";\n\t\t\t\t}\n\t\t\t\tif(!operator){\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult +="";\n\n\t\t\t\tif(operator==="="){\n\t\t\t\t\treturn result===check;\n\t\t\t\t}\n\t\t\t\tif(operator==="!="){\n\t\t\t\t\treturn result!==check;\n\t\t\t\t}\n\t\t\t\tif(operator==="^="){\n\t\t\t\t\treturn check&&result.indexOf(check)===0;\n\t\t\t\t}\n\t\t\t\tif(operator==="*="){\n\t\t\t\t\treturn check&&result.indexOf(check) > -1;\n\t\t\t\t}\n\t\t\t\tif(operator==="$="){\n\t\t\t\t\treturn check&&result.slice(-check.length)===check;\n\t\t\t\t}\n\t\t\t\tif(operator==="~="){\n\t\t\t\t\treturn(" " + result.replace(rwhitespace, " ") + " ")\n\t\t\t\t\t\t.indexOf(check) > -1;\n\t\t\t\t}\n\t\t\t\tif(operator==="|="){\n\t\t\t\t\treturn result===check||result.slice(0, check.length + 1)===check + "-";\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t};\n\t\t},\n\n\t\tCHILD: function(type, what, _argument, first, last){\n\t\t\tvar simple=type.slice(0, 3)!=="nth",\n\t\t\t\tforward=type.slice(-4)!=="last",\n\t\t\t\tofType=what==="of-type";\n\n\t\t\treturn first===1&&last===0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction(elem){\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction(elem, _context, xml){\n\t\t\t\t\tvar cache, outerCache, node, nodeIndex, start,\n\t\t\t\t\t\tdir=simple!==forward ? "nextSibling":"previousSibling",\n\t\t\t\t\t\tparent=elem.parentNode,\n\t\t\t\t\t\tname=ofType&&elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml&&!ofType,\n\t\t\t\t\t\tdiff=false;\n\n\t\t\t\t\tif(parent){\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif(simple){\n\t\t\t\t\t\t\twhile(dir){\n\t\t\t\t\t\t\t\tnode=elem;\n\t\t\t\t\t\t\t\twhile(( node=node[ dir ]) ){\n\t\t\t\t\t\t\t\t\tif(ofType ?\n\t\t\t\t\t\t\t\t\t\tnodeName(node, name) :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType===1){\n\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven\'t yet done so)\n\t\t\t\t\t\t\t\tstart=dir=type==="only"&&!start&&"nextSibling";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart=[ forward ? parent.firstChild:parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif(forward&&useCache){\n\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache=parent[ expando ]||(parent[ expando ]={});\n\t\t\t\t\t\t\tcache=outerCache[ type ]||[];\n\t\t\t\t\t\t\tnodeIndex=cache[ 0 ]===dirruns&&cache[ 1 ];\n\t\t\t\t\t\t\tdiff=nodeIndex&&cache[ 2 ];\n\t\t\t\t\t\t\tnode=nodeIndex&&parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile(( node=++nodeIndex&&node&&node[ dir ]||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff=nodeIndex=0)||start.pop()) ){\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif(node.nodeType===1&&++diff&&node===elem){\n\t\t\t\t\t\t\t\t\touterCache[ type ]=[ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t\tif(useCache){\n\t\t\t\t\t\t\t\touterCache=elem[ expando ]||(elem[ expando ]={});\n\t\t\t\t\t\t\t\tcache=outerCache[ type ]||[];\n\t\t\t\t\t\t\t\tnodeIndex=cache[ 0 ]===dirruns&&cache[ 1 ];\n\t\t\t\t\t\t\t\tdiff=nodeIndex;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// xml :nth-child(...)\n\t\t\t\t\t\t\t// or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t\tif(diff===false){\n\n\t\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\t\twhile(( node=++nodeIndex&&node&&node[ dir ]||\n\t\t\t\t\t\t\t\t\t(diff=nodeIndex=0)||start.pop()) ){\n\n\t\t\t\t\t\t\t\t\tif(( ofType ?\n\t\t\t\t\t\t\t\t\t\tnodeName(node, name) :\n\t\t\t\t\t\t\t\t\t\tnode.nodeType===1)&&\n\t\t\t\t\t\t\t\t\t\t++diff){\n\n\t\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\t\tif(useCache){\n\t\t\t\t\t\t\t\t\t\t\touterCache=node[ expando ]||\n\t\t\t\t\t\t\t\t\t\t\t\t(node[ expando ]={});\n\t\t\t\t\t\t\t\t\t\t\touterCache[ type ]=[ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif(node===elem){\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -=last;\n\t\t\t\t\t\treturn diff===first||(diff % first===0&&diff / first >=0);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\tPSEUDO: function(pseudo, argument){\n\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// https://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn=Expr.pseudos[ pseudo ]||Expr.setFilters[ pseudo.toLowerCase() ]||\n\t\t\t\t\tfind.error("unsupported pseudo: " + pseudo);\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as jQuery does\n\t\t\tif(fn[ expando ]){\n\t\t\t\treturn fn(argument);\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif(fn.length > 1){\n\t\t\t\targs=[ pseudo, pseudo, "", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ?\n\t\t\t\t\tmarkFunction(function(seed, matches){\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched=fn(seed, argument),\n\t\t\t\t\t\t\ti=matched.length;\n\t\t\t\t\t\twhile(i--){\n\t\t\t\t\t\t\tidx=indexOf.call(seed, matched[ i ]);\n\t\t\t\t\t\t\tseed[ idx ] = !(matches[ idx ]=matched[ i ]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction(elem){\n\t\t\t\t\t\treturn fn(elem, 0, args);\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\n\t\t// Potentially complex pseudos\n\t\tnot: markFunction(function(selector){\n\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input=[],\n\t\t\t\tresults=[],\n\t\t\t\tmatcher=compile(selector.replace(rtrimCSS, "$1") );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function(seed, matches, _context, xml){\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched=matcher(seed, null, xml, []),\n\t\t\t\t\t\ti=seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile(i--){\n\t\t\t\t\t\tif(( elem=unmatched[ i ]) ){\n\t\t\t\t\t\t\tseed[ i ] = !(matches[ i ]=elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction(elem, _context, xml){\n\t\t\t\t\tinput[ 0 ]=elem;\n\t\t\t\t\tmatcher(input, null, xml, results);\n\n\t\t\t\t\t// Don\'t keep the element\n\t\t\t\t\t// (see https://github.com/jquery/sizzle/issues/299)\n\t\t\t\t\tinput[ 0 ]=null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\thas: markFunction(function(selector){\n\t\t\treturn function(elem){\n\t\t\t\treturn find(selector, elem).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\tcontains: markFunction(function(text){\n\t\t\ttext=text.replace(runescape, funescape);\n\t\t\treturn function(elem){\n\t\t\t\treturn(elem.textContent||jQuery.text(elem) ).indexOf(text) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// "Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element\'s language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by "-".\n\t\t// The matching of C against the element\'s language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name."\n\t\t// https://www.w3.org/TR/selectors/#lang-pseudo\n\t\tlang: markFunction(function(lang){\n\n\t\t\t// lang value must be a valid identifier\n\t\t\tif(!ridentifier.test(lang||"") ){\n\t\t\t\tfind.error("unsupported lang: " + lang);\n\t\t\t}\n\t\t\tlang=lang.replace(runescape, funescape).toLowerCase();\n\t\t\treturn function(elem){\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif(( elemLang=documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute("xml:lang")||elem.getAttribute("lang") )){\n\n\t\t\t\t\t\telemLang=elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang===lang||elemLang.indexOf(lang + "-")===0;\n\t\t\t\t\t}\n\t\t\t\t} while(( elem=elem.parentNode)&&elem.nodeType===1);\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\ttarget: function(elem){\n\t\t\tvar hash=window.location&&window.location.hash;\n\t\t\treturn hash&&hash.slice(1)===elem.id;\n\t\t},\n\n\t\troot: function(elem){\n\t\t\treturn elem===documentElement;\n\t\t},\n\n\t\tfocus: function(elem){\n\t\t\treturn elem===safeActiveElement()&&\n\t\t\t\tdocument.hasFocus()&&\n\t\t\t\t!!(elem.type||elem.href||~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\tenabled: createDisabledPseudo(false),\n\t\tdisabled: createDisabledPseudo(true),\n\n\t\tchecked: function(elem){\n\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\treturn(nodeName(elem, "input")&&!!elem.checked)||\n\t\t\t\t(nodeName(elem, "option")&&!!elem.selected);\n\t\t},\n\n\t\tselected: function(elem){\n\n\t\t\t// Support: IE <=11+\n\t\t\t// Accessing the selectedIndex property\n\t\t\t// forces the browser to treat the default option as\n\t\t\t// selected when in an optgroup.\n\t\t\tif(elem.parentNode){\n\t\t\t\t// eslint-disable-next-line no-unused-expressions\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected===true;\n\t\t},\n\n\t\t// Contents\n\t\tempty: function(elem){\n\n\t\t\t// https://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor(elem=elem.firstChild; elem; elem=elem.nextSibling){\n\t\t\t\tif(elem.nodeType < 6){\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\tparent: function(elem){\n\t\t\treturn !Expr.pseudos.empty(elem);\n\t\t},\n\n\t\t// Element/input types\n\t\theader: function(elem){\n\t\t\treturn rheader.test(elem.nodeName);\n\t\t},\n\n\t\tinput: function(elem){\n\t\t\treturn rinputs.test(elem.nodeName);\n\t\t},\n\n\t\tbutton: function(elem){\n\t\t\treturn nodeName(elem, "input")&&elem.type==="button"||\n\t\t\t\tnodeName(elem, "button");\n\t\t},\n\n\t\ttext: function(elem){\n\t\t\tvar attr;\n\t\t\treturn nodeName(elem, "input")&&elem.type==="text"&&\n\n\t\t\t\t// Support: IE <10 only\n\t\t\t\t// New HTML5 attribute values (e.g., "search") appear\n\t\t\t\t// with elem.type==="text"\n\t\t\t\t(( attr=elem.getAttribute("type") )==null||\n\t\t\t\t\tattr.toLowerCase()==="text");\n\t\t},\n\n\t\t// Position-in-collection\n\t\tfirst: createPositionalPseudo(function(){\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\tlast: createPositionalPseudo(function(_matchIndexes, length){\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\teq: createPositionalPseudo(function(_matchIndexes, length, argument){\n\t\t\treturn [ argument < 0 ? argument + length:argument ];\n\t\t}),\n\n\t\teven: createPositionalPseudo(function(matchIndexes, length){\n\t\t\tvar i=0;\n\t\t\tfor(; i < length; i +=2){\n\t\t\t\tmatchIndexes.push(i);\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\todd: createPositionalPseudo(function(matchIndexes, length){\n\t\t\tvar i=1;\n\t\t\tfor(; i < length; i +=2){\n\t\t\t\tmatchIndexes.push(i);\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\tlt: createPositionalPseudo(function(matchIndexes, length, argument){\n\t\t\tvar i;\n\n\t\t\tif(argument < 0){\n\t\t\t\ti=argument + length;\n\t\t\t}else if(argument > length){\n\t\t\t\ti=length;\n\t\t\t}else{\n\t\t\t\ti=argument;\n\t\t\t}\n\n\t\t\tfor(; --i >=0;){\n\t\t\t\tmatchIndexes.push(i);\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\tgt: createPositionalPseudo(function(matchIndexes, length, argument){\n\t\t\tvar i=argument < 0 ? argument + length:argument;\n\t\t\tfor(; ++i < length;){\n\t\t\t\tmatchIndexes.push(i);\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos.nth=Expr.pseudos.eq;\n\n// Add button/input type pseudos\nfor(i in { radio: true, checkbox: true, file: true, password: true, image: true }){\n\tExpr.pseudos[ i ]=createInputPseudo(i);\n}\nfor(i in { submit: true, reset: true }){\n\tExpr.pseudos[ i ]=createButtonPseudo(i);\n}\n\n// Easy API for creating new setFilters\nfunction setFilters(){}\nsetFilters.prototype=Expr.filters=Expr.pseudos;\nExpr.setFilters=new setFilters();\n\nfunction tokenize(selector, parseOnly){\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached=tokenCache[ selector + " " ];\n\n\tif(cached){\n\t\treturn parseOnly ? 0:cached.slice(0);\n\t}\n\n\tsoFar=selector;\n\tgroups=[];\n\tpreFilters=Expr.preFilter;\n\n\twhile(soFar){\n\n\t\t// Comma and first run\n\t\tif(!matched||(match=rcomma.exec(soFar) )){\n\t\t\tif(match){\n\n\t\t\t\t// Don\'t consume trailing commas as valid\n\t\t\t\tsoFar=soFar.slice(match[ 0 ].length)||soFar;\n\t\t\t}\n\t\t\tgroups.push(( tokens=[]) );\n\t\t}\n\n\t\tmatched=false;\n\n\t\t// Combinators\n\t\tif(( match=rleadingCombinator.exec(soFar) )){\n\t\t\tmatched=match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[ 0 ].replace(rtrimCSS, " ")\n\t\t\t});\n\t\t\tsoFar=soFar.slice(matched.length);\n\t\t}\n\n\t\t// Filters\n\t\tfor(type in Expr.filter){\n\t\t\tif(( match=matchExpr[ type ].exec(soFar) )&&(!preFilters[ type ]||\n\t\t\t\t(match=preFilters[ type ](match) )) ){\n\t\t\t\tmatched=match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar=soFar.slice(matched.length);\n\t\t\t}\n\t\t}\n\n\t\tif(!matched){\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we\'re just parsing\n\t// Otherwise, throw an error or return tokens\n\tif(parseOnly){\n\t\treturn soFar.length;\n\t}\n\n\treturn soFar ?\n\t\tfind.error(selector) :\n\n\t\t// Cache the tokens\n\t\ttokenCache(selector, groups).slice(0);\n}\n\nfunction toSelector(tokens){\n\tvar i=0,\n\t\tlen=tokens.length,\n\t\tselector="";\n\tfor(; i < len; i++){\n\t\tselector +=tokens[ i ].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator(matcher, combinator, base){\n\tvar dir=combinator.dir,\n\t\tskip=combinator.next,\n\t\tkey=skip||dir,\n\t\tcheckNonElements=base&&key==="parentNode",\n\t\tdoneName=done++;\n\n\treturn combinator.first ?\n\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction(elem, context, xml){\n\t\t\twhile(( elem=elem[ dir ]) ){\n\t\t\t\tif(elem.nodeType===1||checkNonElements){\n\t\t\t\t\treturn matcher(elem, context, xml);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction(elem, context, xml){\n\t\t\tvar oldCache, outerCache,\n\t\t\t\tnewCache=[ dirruns, doneName ];\n\n\t\t\t// We can\'t set arbitrary data on XML nodes, so they don\'t benefit from combinator caching\n\t\t\tif(xml){\n\t\t\t\twhile(( elem=elem[ dir ]) ){\n\t\t\t\t\tif(elem.nodeType===1||checkNonElements){\n\t\t\t\t\t\tif(matcher(elem, context, xml) ){\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\twhile(( elem=elem[ dir ]) ){\n\t\t\t\t\tif(elem.nodeType===1||checkNonElements){\n\t\t\t\t\t\touterCache=elem[ expando ]||(elem[ expando ]={});\n\n\t\t\t\t\t\tif(skip&&nodeName(elem, skip) ){\n\t\t\t\t\t\t\telem=elem[ dir ]||elem;\n\t\t\t\t\t\t}else if(( oldCache=outerCache[ key ])&&\n\t\t\t\t\t\t\toldCache[ 0 ]===dirruns&&oldCache[ 1 ]===doneName){\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn(newCache[ 2 ]=oldCache[ 2 ]);\n\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\touterCache[ key ]=newCache;\n\n\t\t\t\t\t\t\t// A match means we\'re done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif(( newCache[ 2 ]=matcher(elem, context, xml) )){\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n}\n\nfunction elementMatcher(matchers){\n\treturn matchers.length > 1 ?\n\t\tfunction(elem, context, xml){\n\t\t\tvar i=matchers.length;\n\t\t\twhile(i--){\n\t\t\t\tif(!matchers[ i ](elem, context, xml) ){\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[ 0 ];\n}\n\nfunction multipleContexts(selector, contexts, results){\n\tvar i=0,\n\t\tlen=contexts.length;\n\tfor(; i < len; i++){\n\t\tfind(selector, contexts[ i ], results);\n\t}\n\treturn results;\n}\n\nfunction condense(unmatched, map, filter, context, xml){\n\tvar elem,\n\t\tnewUnmatched=[],\n\t\ti=0,\n\t\tlen=unmatched.length,\n\t\tmapped=map!=null;\n\n\tfor(; i < len; i++){\n\t\tif(( elem=unmatched[ i ]) ){\n\t\t\tif(!filter||filter(elem, context, xml) ){\n\t\t\t\tnewUnmatched.push(elem);\n\t\t\t\tif(mapped){\n\t\t\t\t\tmap.push(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector){\n\tif(postFilter&&!postFilter[ expando ]){\n\t\tpostFilter=setMatcher(postFilter);\n\t}\n\tif(postFinder&&!postFinder[ expando ]){\n\t\tpostFinder=setMatcher(postFinder, postSelector);\n\t}\n\treturn markFunction(function(seed, results, context, xml){\n\t\tvar temp, i, elem, matcherOut,\n\t\t\tpreMap=[],\n\t\t\tpostMap=[],\n\t\t\tpreexisting=results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems=seed||\n\t\t\t\tmultipleContexts(selector||"*",\n\t\t\t\t\tcontext.nodeType ? [ context ]:context, []),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn=preFilter&&(seed||!selector) ?\n\t\t\t\tcondense(elems, preMap, preFilter, context, xml) :\n\t\t\t\telems;\n\n\t\tif(matcher){\n\n\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter\n\t\t\t// or preexisting results,\n\t\t\tmatcherOut=postFinder||(seed ? preFilter:preexisting||postFilter) ?\n\n\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t[] :\n\n\t\t\t\t// ...otherwise use results directly\n\t\t\t\tresults;\n\n\t\t\t// Find primary matches\n\t\t\tmatcher(matcherIn, matcherOut, context, xml);\n\t\t}else{\n\t\t\tmatcherOut=matcherIn;\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif(postFilter){\n\t\t\ttemp=condense(matcherOut, postMap);\n\t\t\tpostFilter(temp, [], context, xml);\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti=temp.length;\n\t\t\twhile(i--){\n\t\t\t\tif(( elem=temp[ i ]) ){\n\t\t\t\t\tmatcherOut[ postMap[ i ] ] = !(matcherIn[ postMap[ i ] ]=elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif(seed){\n\t\t\tif(postFinder||preFilter){\n\t\t\t\tif(postFinder){\n\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp=[];\n\t\t\t\t\ti=matcherOut.length;\n\t\t\t\t\twhile(i--){\n\t\t\t\t\t\tif(( elem=matcherOut[ i ]) ){\n\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push(( matcherIn[ i ]=elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder(null,(matcherOut=[]), temp, xml);\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti=matcherOut.length;\n\t\t\t\twhile(i--){\n\t\t\t\t\tif(( elem=matcherOut[ i ])&&\n\t\t\t\t\t\t(temp=postFinder ? indexOf.call(seed, elem):preMap[ i ]) > -1){\n\n\t\t\t\t\t\tseed[ temp ] = !(results[ temp ]=elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t}else{\n\t\t\tmatcherOut=condense(\n\t\t\t\tmatcherOut===results ?\n\t\t\t\t\tmatcherOut.splice(preexisting, matcherOut.length) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif(postFinder){\n\t\t\t\tpostFinder(null, results, matcherOut, xml);\n\t\t\t}else{\n\t\t\t\tpush.apply(results, matcherOut);\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens(tokens){\n\tvar checkContext, matcher, j,\n\t\tlen=tokens.length,\n\t\tleadingRelative=Expr.relative[ tokens[ 0 ].type ],\n\t\timplicitRelative=leadingRelative||Expr.relative[ " " ],\n\t\ti=leadingRelative ? 1:0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext=addCombinator(function(elem){\n\t\t\treturn elem===checkContext;\n\t\t}, implicitRelative, true),\n\t\tmatchAnyContext=addCombinator(function(elem){\n\t\t\treturn indexOf.call(checkContext, elem) > -1;\n\t\t}, implicitRelative, true),\n\t\tmatchers=[ function(elem, context, xml){\n\n\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t\t// two documents; shallow comparisons work.\n\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\tvar ret=(!leadingRelative&&(xml||context!=outermostContext) )||(\n\t\t\t\t(checkContext=context).nodeType ?\n\t\t\t\t\tmatchContext(elem, context, xml) :\n\t\t\t\t\tmatchAnyContext(elem, context, xml) );\n\n\t\t\t// Avoid hanging onto element\n\t\t\t// (see https://github.com/jquery/sizzle/issues/299)\n\t\t\tcheckContext=null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor(; i < len; i++){\n\t\tif(( matcher=Expr.relative[ tokens[ i ].type ]) ){\n\t\t\tmatchers=[ addCombinator(elementMatcher(matchers), matcher) ];\n\t\t}else{\n\t\t\tmatcher=Expr.filter[ tokens[ i ].type ].apply(null, tokens[ i ].matches);\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif(matcher[ expando ]){\n\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj=++i;\n\t\t\t\tfor(; j < len; j++){\n\t\t\t\t\tif(Expr.relative[ tokens[ j ].type ]){\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1&&elementMatcher(matchers),\n\t\t\t\t\ti > 1&&toSelector(\n\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice(0, i - 1)\n\t\t\t\t\t\t\t.concat({ value: tokens[ i - 2 ].type===" " ? "*":"" })\n\t\t\t\t\t).replace(rtrimCSS, "$1"),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j&&matcherFromTokens(tokens.slice(i, j) ),\n\t\t\t\t\tj < len&&matcherFromTokens(( tokens=tokens.slice(j) )),\n\t\t\t\t\tj < len&&toSelector(tokens)\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push(matcher);\n\t\t}\n\t}\n\n\treturn elementMatcher(matchers);\n}\n\nfunction matcherFromGroupMatchers(elementMatchers, setMatchers){\n\tvar bySet=setMatchers.length > 0,\n\t\tbyElement=elementMatchers.length > 0,\n\t\tsuperMatcher=function(seed, context, xml, results, outermost){\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount=0,\n\t\t\t\ti="0",\n\t\t\t\tunmatched=seed&&[],\n\t\t\t\tsetMatched=[],\n\t\t\t\tcontextBackup=outermostContext,\n\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems=seed||byElement&&Expr.find.TAG("*", outermost),\n\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique=(dirruns +=contextBackup==null ? 1:Math.random()||0.1),\n\t\t\t\tlen=elems.length;\n\n\t\t\tif(outermost){\n\n\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\toutermostContext=context==document||context||outermost;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Support: iOS <=7 - 9 only\n\t\t\t// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching\n\t\t\t// elements by id. (see trac-14142)\n\t\t\tfor(; i!==len&&(elem=elems[ i ])!=null; i++){\n\t\t\t\tif(byElement&&elem){\n\t\t\t\t\tj=0;\n\n\t\t\t\t\t// Support: IE 11+, Edge 17 - 18+\n\t\t\t\t\t// IE/Edge sometimes throw a "Permission denied" error when strict-comparing\n\t\t\t\t\t// two documents; shallow comparisons work.\n\t\t\t\t\t// eslint-disable-next-line eqeqeq\n\t\t\t\t\tif(!context&&elem.ownerDocument!=document){\n\t\t\t\t\t\tsetDocument(elem);\n\t\t\t\t\t\txml = !documentIsHTML;\n\t\t\t\t\t}\n\t\t\t\t\twhile(( matcher=elementMatchers[ j++ ]) ){\n\t\t\t\t\t\tif(matcher(elem, context||document, xml) ){\n\t\t\t\t\t\t\tpush.call(results, elem);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif(outermost){\n\t\t\t\t\t\tdirruns=dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif(bySet){\n\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif(( elem = !matcher&&elem) ){\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif(seed){\n\t\t\t\t\t\tunmatched.push(elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// `i` is now the count of elements visited above, and adding it to `matchedCount`\n\t\t\t// makes the latter nonnegative.\n\t\t\tmatchedCount +=i;\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\t// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`\n\t\t\t// equals `i`), unless we didn\'t visit _any_ elements in the above loop because we have\n\t\t\t// no element matchers and no seed.\n\t\t\t// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that\n\t\t\t// case, which will result in a "00" `matchedCount` that differs from `i` but is also\n\t\t\t// numerically zero.\n\t\t\tif(bySet&&i!==matchedCount){\n\t\t\t\tj=0;\n\t\t\t\twhile(( matcher=setMatchers[ j++ ]) ){\n\t\t\t\t\tmatcher(unmatched, setMatched, context, xml);\n\t\t\t\t}\n\n\t\t\t\tif(seed){\n\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif(matchedCount > 0){\n\t\t\t\t\t\twhile(i--){\n\t\t\t\t\t\t\tif(!(unmatched[ i ]||setMatched[ i ]) ){\n\t\t\t\t\t\t\t\tsetMatched[ i ]=pop.call(results);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched=condense(setMatched);\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply(results, setMatched);\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif(outermost&&!seed&&setMatched.length > 0&&\n\t\t\t\t\t(matchedCount + setMatchers.length) > 1){\n\n\t\t\t\t\tjQuery.uniqueSort(results);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif(outermost){\n\t\t\t\tdirruns=dirrunsUnique;\n\t\t\t\toutermostContext=contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction(superMatcher) :\n\t\tsuperMatcher;\n}\n\nfunction compile(selector, match ){\n\tvar i,\n\t\tsetMatchers=[],\n\t\telementMatchers=[],\n\t\tcached=compilerCache[ selector + " " ];\n\n\tif(!cached){\n\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif(!match){\n\t\t\tmatch=tokenize(selector);\n\t\t}\n\t\ti=match.length;\n\t\twhile(i--){\n\t\t\tcached=matcherFromTokens(match[ i ]);\n\t\t\tif(cached[ expando ]){\n\t\t\t\tsetMatchers.push(cached);\n\t\t\t}else{\n\t\t\t\telementMatchers.push(cached);\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached=compilerCache(selector,\n\t\t\tmatcherFromGroupMatchers(elementMatchers, setMatchers) );\n\n\t\t// Save selector and tokenization\n\t\tcached.selector=selector;\n\t}\n\treturn cached;\n}\n\n\nfunction select(selector, context, results, seed){\n\tvar i, tokens, token, type, find,\n\t\tcompiled=typeof selector==="function"&&selector,\n\t\tmatch = !seed&&tokenize(( selector=compiled.selector||selector) );\n\n\tresults=results||[];\n\n\t// Try to minimize operations if there is only one selector in the list and no seed\n\t// (the latter of which guarantees us context)\n\tif(match.length===1){\n\n\t\t// Reduce context if the leading compound selector is an ID\n\t\ttokens=match[ 0 ]=match[ 0 ].slice(0);\n\t\tif(tokens.length > 2&&(token=tokens[ 0 ]).type==="ID"&&\n\t\t\t\tcontext.nodeType===9&&documentIsHTML&&Expr.relative[ tokens[ 1 ].type ]){\n\n\t\t\tcontext=(Expr.find.ID(\n\t\t\t\ttoken.matches[ 0 ].replace(runescape, funescape),\n\t\t\t\tcontext\n\t\t\t)||[])[ 0 ];\n\t\t\tif(!context){\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t}else if(compiled){\n\t\t\t\tcontext=context.parentNode;\n\t\t\t}\n\n\t\t\tselector=selector.slice(tokens.shift().value.length);\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti=matchExpr.needsContext.test(selector) ? 0:tokens.length;\n\t\twhile(i--){\n\t\t\ttoken=tokens[ i ];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif(Expr.relative[(type=token.type) ]){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif(( find=Expr.find[ type ]) ){\n\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif(( seed=find(\n\t\t\t\t\ttoken.matches[ 0 ].replace(runescape, funescape),\n\t\t\t\t\trsibling.test(tokens[ 0 ].type)&&\n\t\t\t\t\t\ttestContext(context.parentNode)||context\n\t\t\t\t)) ){\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice(i, 1);\n\t\t\t\t\tselector=seed.length&&toSelector(tokens);\n\t\t\t\t\tif(!selector){\n\t\t\t\t\t\tpush.apply(results, seed);\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t(compiled||compile(selector, match) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\t!context||rsibling.test(selector)&&testContext(context.parentNode)||context\n\t);\n\treturn results;\n}\n\n// One-time assignments\n\n// Support: Android <=4.0 - 4.1+\n// Sort stability\nsupport.sortStable=expando.split("").sort(sortOrder).join("")===expando;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Android <=4.0 - 4.1+\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached=assert(function(el){\n\n\t// Should return 1, but returns 4 (following)\n\treturn el.compareDocumentPosition(document.createElement("fieldset") ) & 1;\n});\n\njQuery.find=find;\n\n// Deprecated\njQuery.expr[ ":" ]=jQuery.expr.pseudos;\njQuery.unique=jQuery.uniqueSort;\n\n// These have always been private, but they used to be documented as part of\n// Sizzle so let\'s maintain them for now for backwards compatibility purposes.\nfind.compile=compile;\nfind.select=select;\nfind.setDocument=setDocument;\nfind.tokenize=tokenize;\n\nfind.escape=jQuery.escapeSelector;\nfind.getText=jQuery.text;\nfind.isXML=jQuery.isXMLDoc;\nfind.selectors=jQuery.expr;\nfind.support=jQuery.support;\nfind.uniqueSort=jQuery.uniqueSort;\n\n\t\n\n})();\n\n\nvar dir=function(elem, dir, until){\n\tvar matched=[],\n\t\ttruncate=until!==undefined;\n\n\twhile(( elem=elem[ dir ])&&elem.nodeType!==9){\n\t\tif(elem.nodeType===1){\n\t\t\tif(truncate&&jQuery(elem).is(until) ){\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmatched.push(elem);\n\t\t}\n\t}\n\treturn matched;\n};\n\n\nvar siblings=function(n, elem){\n\tvar matched=[];\n\n\tfor(; n; n=n.nextSibling){\n\t\tif(n.nodeType===1&&n!==elem){\n\t\t\tmatched.push(n);\n\t\t}\n\t}\n\n\treturn matched;\n};\n\n\nvar rneedsContext=jQuery.expr.match.needsContext;\n\nvar rsingleTag=(/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i);\n\n\n\n// Implement the identical functionality for filter and not\nfunction winnow(elements, qualifier, not){\n\tif(isFunction(qualifier) ){\n\t\treturn jQuery.grep(elements, function(elem, i){\n\t\t\treturn !!qualifier.call(elem, i, elem)!==not;\n\t\t});\n\t}\n\n\t// Single element\n\tif(qualifier.nodeType){\n\t\treturn jQuery.grep(elements, function(elem){\n\t\t\treturn(elem===qualifier)!==not;\n\t\t});\n\t}\n\n\t// Arraylike of elements (jQuery, arguments, Array)\n\tif(typeof qualifier!=="string"){\n\t\treturn jQuery.grep(elements, function(elem){\n\t\t\treturn(indexOf.call(qualifier, elem) > -1)!==not;\n\t\t});\n\t}\n\n\t// Filtered directly for both simple and complex selectors\n\treturn jQuery.filter(qualifier, elements, not);\n}\n\njQuery.filter=function(expr, elems, not){\n\tvar elem=elems[ 0 ];\n\n\tif(not){\n\t\texpr=":not(" + expr + ")";\n\t}\n\n\tif(elems.length===1&&elem.nodeType===1){\n\t\treturn jQuery.find.matchesSelector(elem, expr) ? [ elem ]:[];\n\t}\n\n\treturn jQuery.find.matches(expr, jQuery.grep(elems, function(elem){\n\t\treturn elem.nodeType===1;\n\t}) );\n};\n\njQuery.fn.extend({\n\tfind: function(selector){\n\t\tvar i, ret,\n\t\t\tlen=this.length,\n\t\t\tself=this;\n\n\t\tif(typeof selector!=="string"){\n\t\t\treturn this.pushStack(jQuery(selector).filter(function(){\n\t\t\t\tfor(i=0; i < len; i++){\n\t\t\t\t\tif(jQuery.contains(self[ i ], this) ){\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tret=this.pushStack([]);\n\n\t\tfor(i=0; i < len; i++){\n\t\t\tjQuery.find(selector, self[ i ], ret);\n\t\t}\n\n\t\treturn len > 1 ? jQuery.uniqueSort(ret):ret;\n\t},\n\tfilter: function(selector){\n\t\treturn this.pushStack(winnow(this, selector||[], false) );\n\t},\n\tnot: function(selector){\n\t\treturn this.pushStack(winnow(this, selector||[], true) );\n\t},\n\tis: function(selector){\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $("p:first").is("p:last") won\'t return true for a doc with two "p".\n\t\t\ttypeof selector==="string"&&rneedsContext.test(selector) ?\n\t\t\t\tjQuery(selector) :\n\t\t\t\tselector||[],\n\t\t\tfalse\n\t\t).length;\n\t}\n});\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)\n\t// Strict HTML recognition (trac-11290: must start with <)\n\t// Shortcut simple #id case for speed\n\trquickExpr=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/,\n\n\tinit=jQuery.fn.init=function(selector, context, root){\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(""), $(null), $(undefined), $(false)\n\t\tif(!selector){\n\t\t\treturn this;\n\t\t}\n\n\t\t// Method init() accepts an alternate rootjQuery\n\t\t// so migrate can support jQuery.sub (gh-2101)\n\t\troot=root||rootjQuery;\n\n\t\t// Handle HTML strings\n\t\tif(typeof selector==="string"){\n\t\t\tif(selector[ 0 ]==="<"&&\n\t\t\t\tselector[ selector.length - 1 ]===">"&&\n\t\t\t\tselector.length >=3){\n\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch=[ null, selector, null ];\n\n\t\t\t}else{\n\t\t\t\tmatch=rquickExpr.exec(selector);\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif(match&&(match[ 1 ]||!context) ){\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif(match[ 1 ]){\n\t\t\t\t\tcontext=context instanceof jQuery ? context[ 0 ]:context;\n\n\t\t\t\t\t// Option to run scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge(this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[ 1 ],\n\t\t\t\t\t\tcontext&&context.nodeType ? context.ownerDocument||context:document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t));\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif(rsingleTag.test(match[ 1 ])&&jQuery.isPlainObject(context) ){\n\t\t\t\t\t\tfor(match in context){\n\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif(isFunction(this[ match ]) ){\n\t\t\t\t\t\t\t\tthis[ match ](context[ match ]);\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tthis.attr(match, context[ match ]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t}else{\n\t\t\t\t\telem=document.getElementById(match[ 2 ]);\n\n\t\t\t\t\tif(elem){\n\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis[ 0 ]=elem;\n\t\t\t\t\t\tthis.length=1;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t}else if(!context||context.jquery){\n\t\t\t\treturn(context||root).find(selector);\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t}else{\n\t\t\t\treturn this.constructor(context).find(selector);\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t}else if(selector.nodeType){\n\t\t\tthis[ 0 ]=selector;\n\t\t\tthis.length=1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t}else if(isFunction(selector) ){\n\t\t\treturn root.ready!==undefined ?\n\t\t\t\troot.ready(selector) :\n\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector(jQuery);\n\t\t}\n\n\t\treturn jQuery.makeArray(selector, this);\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype=jQuery.fn;\n\n// Initialize central reference\nrootjQuery=jQuery(document);\n\n\nvar rparentsprev=/^(?:parents|prev(?:Until|All))/,\n\n\t// Methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique={\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend({\n\thas: function(target){\n\t\tvar targets=jQuery(target, this),\n\t\t\tl=targets.length;\n\n\t\treturn this.filter(function(){\n\t\t\tvar i=0;\n\t\t\tfor(; i < l; i++){\n\t\t\t\tif(jQuery.contains(this, targets[ i ]) ){\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tclosest: function(selectors, context){\n\t\tvar cur,\n\t\t\ti=0,\n\t\t\tl=this.length,\n\t\t\tmatched=[],\n\t\t\ttargets=typeof selectors!=="string"&&jQuery(selectors);\n\n\t\t// Positional selectors never match, since there\'s no _selection_ context\n\t\tif(!rneedsContext.test(selectors) ){\n\t\t\tfor(; i < l; i++){\n\t\t\t\tfor(cur=this[ i ]; cur&&cur!==context; cur=cur.parentNode){\n\n\t\t\t\t\t// Always skip document fragments\n\t\t\t\t\tif(cur.nodeType < 11&&(targets ?\n\t\t\t\t\t\ttargets.index(cur) > -1 :\n\n\t\t\t\t\t\t// Don\'t pass non-elements to jQuery#find\n\t\t\t\t\t\tcur.nodeType===1&&\n\t\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors) )){\n\n\t\t\t\t\t\tmatched.push(cur);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack(matched.length > 1 ? jQuery.uniqueSort(matched):matched);\n\t},\n\n\t// Determine the position of an element within the set\n\tindex: function(elem){\n\n\t\t// No argument, return index in parent\n\t\tif(!elem){\n\t\t\treturn(this[ 0 ]&&this[ 0 ].parentNode) ? this.first().prevAll().length:-1;\n\t\t}\n\n\t\t// Index in selector\n\t\tif(typeof elem==="string"){\n\t\t\treturn indexOf.call(jQuery(elem), this[ 0 ]);\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call(this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ]:elem\n\t\t);\n\t},\n\n\tadd: function(selector, context){\n\t\treturn this.pushStack(\n\t\t\tjQuery.uniqueSort(\n\t\t\t\tjQuery.merge(this.get(), jQuery(selector, context) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function(selector){\n\t\treturn this.add(selector==null ?\n\t\t\tthis.prevObject:this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling(cur, dir){\n\twhile(( cur=cur[ dir ])&&cur.nodeType!==1){}\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function(elem){\n\t\tvar parent=elem.parentNode;\n\t\treturn parent&&parent.nodeType!==11 ? parent:null;\n\t},\n\tparents: function(elem){\n\t\treturn dir(elem, "parentNode");\n\t},\n\tparentsUntil: function(elem, _i, until){\n\t\treturn dir(elem, "parentNode", until);\n\t},\n\tnext: function(elem){\n\t\treturn sibling(elem, "nextSibling");\n\t},\n\tprev: function(elem){\n\t\treturn sibling(elem, "previousSibling");\n\t},\n\tnextAll: function(elem){\n\t\treturn dir(elem, "nextSibling");\n\t},\n\tprevAll: function(elem){\n\t\treturn dir(elem, "previousSibling");\n\t},\n\tnextUntil: function(elem, _i, until){\n\t\treturn dir(elem, "nextSibling", until);\n\t},\n\tprevUntil: function(elem, _i, until){\n\t\treturn dir(elem, "previousSibling", until);\n\t},\n\tsiblings: function(elem){\n\t\treturn siblings(( elem.parentNode||{}).firstChild, elem);\n\t},\n\tchildren: function(elem){\n\t\treturn siblings(elem.firstChild);\n\t},\n\tcontents: function(elem){\n\t\tif(elem.contentDocument!=null&&\n\n\t\t\t// Support: IE 11+\n\t\t\t// <object> elements with no `data` attribute has an object\n\t\t\t// `contentDocument` with a `null` prototype.\n\t\t\tgetProto(elem.contentDocument) ){\n\n\t\t\treturn elem.contentDocument;\n\t\t}\n\n\t\t// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only\n\t\t// Treat the template element as a regular one in browsers that\n\t\t// don\'t support it.\n\t\tif(nodeName(elem, "template") ){\n\t\t\telem=elem.content||elem;\n\t\t}\n\n\t\treturn jQuery.merge([], elem.childNodes);\n\t}\n}, function(name, fn){\n\tjQuery.fn[ name ]=function(until, selector){\n\t\tvar matched=jQuery.map(this, fn, until);\n\n\t\tif(name.slice(-5)!=="Until"){\n\t\t\tselector=until;\n\t\t}\n\n\t\tif(selector&&typeof selector==="string"){\n\t\t\tmatched=jQuery.filter(selector, matched);\n\t\t}\n\n\t\tif(this.length > 1){\n\n\t\t\t// Remove duplicates\n\t\t\tif(!guaranteedUnique[ name ]){\n\t\t\t\tjQuery.uniqueSort(matched);\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif(rparentsprev.test(name) ){\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack(matched);\n\t};\n});\nvar rnothtmlwhite=(/[^\\x20\\t\\r\\n\\f]+/g);\n\n\n\n// Convert String-formatted options into Object-formatted ones\nfunction createOptions(options){\n\tvar object={};\n\tjQuery.each(options.match(rnothtmlwhite)||[], function(_, flag){\n\t\tobject[ flag ]=true;\n\t});\n\treturn object;\n}\n\n\njQuery.Callbacks=function(options){\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions=typeof options==="string" ?\n\t\tcreateOptions(options) :\n\t\tjQuery.extend({}, options);\n\n\tvar // Flag to know if list is currently firing\n\t\tfiring,\n\n\t\t// Last fire value for non-forgettable lists\n\t\tmemory,\n\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\n\t\t// Flag to prevent firing\n\t\tlocked,\n\n\t\t// Actual callback list\n\t\tlist=[],\n\n\t\t// Queue of execution data for repeatable lists\n\t\tqueue=[],\n\n\t\t// Index of currently firing callback (modified by add/remove as needed)\n\t\tfiringIndex=-1,\n\n\t\t// Fire callbacks\n\t\tfire=function(){\n\n\t\t\t// Enforce single-firing\n\t\t\tlocked=locked||options.once;\n\n\t\t\t// Execute callbacks for all pending executions,\n\t\t\t// respecting firingIndex overrides and runtime changes\n\t\t\tfired=firing=true;\n\t\t\tfor(; queue.length; firingIndex=-1){\n\t\t\t\tmemory=queue.shift();\n\t\t\t\twhile ( ++firingIndex < list.length){\n\n\t\t\t\t\t// Run callback and check for early termination\n\t\t\t\t\tif(list[ firingIndex ].apply(memory[ 0 ], memory[ 1 ])===false&&\n\t\t\t\t\t\toptions.stopOnFalse){\n\n\t\t\t\t\t\t// Jump to end and forget the data so .add doesn\'t re-fire\n\t\t\t\t\t\tfiringIndex=list.length;\n\t\t\t\t\t\tmemory=false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Forget the data if we\'re done with it\n\t\t\tif(!options.memory){\n\t\t\t\tmemory=false;\n\t\t\t}\n\n\t\t\tfiring=false;\n\n\t\t\t// Clean up if we\'re done firing for good\n\t\t\tif(locked){\n\n\t\t\t\t// Keep an empty list if we have data for future add calls\n\t\t\t\tif(memory){\n\t\t\t\t\tlist=[];\n\n\t\t\t\t// Otherwise, this object is spent\n\t\t\t\t}else{\n\t\t\t\t\tlist="";\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t// Actual Callbacks object\n\t\tself={\n\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function(){\n\t\t\t\tif(list){\n\n\t\t\t\t\t// If we have memory from a past run, we should fire after adding\n\t\t\t\t\tif(memory&&!firing){\n\t\t\t\t\t\tfiringIndex=list.length - 1;\n\t\t\t\t\t\tqueue.push(memory);\n\t\t\t\t\t}\n\n\t\t\t\t\t(function add(args){\n\t\t\t\t\t\tjQuery.each(args, function(_, arg){\n\t\t\t\t\t\t\tif(isFunction(arg) ){\n\t\t\t\t\t\t\t\tif(!options.unique||!self.has(arg) ){\n\t\t\t\t\t\t\t\t\tlist.push(arg);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}else if(arg&&arg.length&&toType(arg)!=="string"){\n\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd(arg);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})(arguments);\n\n\t\t\t\t\tif(memory&&!firing){\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function(){\n\t\t\t\tjQuery.each(arguments, function(_, arg){\n\t\t\t\t\tvar index;\n\t\t\t\t\twhile(( index=jQuery.inArray(arg, list, index) ) > -1){\n\t\t\t\t\t\tlist.splice(index, 1);\n\n\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\tif(index <=firingIndex){\n\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function(fn){\n\t\t\t\treturn fn ?\n\t\t\t\t\tjQuery.inArray(fn, list) > -1 :\n\t\t\t\t\tlist.length > 0;\n\t\t\t},\n\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function(){\n\t\t\t\tif(list){\n\t\t\t\t\tlist=[];\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Disable .fire and .add\n\t\t\t// Abort any current/pending executions\n\t\t\t// Clear all callbacks and values\n\t\t\tdisable: function(){\n\t\t\t\tlocked=queue=[];\n\t\t\t\tlist=memory="";\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tdisabled: function(){\n\t\t\t\treturn !list;\n\t\t\t},\n\n\t\t\t// Disable .fire\n\t\t\t// Also disable .add unless we have memory (since it would have no effect)\n\t\t\t// Abort any pending executions\n\t\t\tlock: function(){\n\t\t\t\tlocked=queue=[];\n\t\t\t\tif(!memory&&!firing){\n\t\t\t\t\tlist=memory="";\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\tlocked: function(){\n\t\t\t\treturn !!locked;\n\t\t\t},\n\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function(context, args){\n\t\t\t\tif(!locked){\n\t\t\t\t\targs=args||[];\n\t\t\t\t\targs=[ context, args.slice ? args.slice():args ];\n\t\t\t\t\tqueue.push(args);\n\t\t\t\t\tif(!firing){\n\t\t\t\t\t\tfire();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function(){\n\t\t\t\tself.fireWith(this, arguments);\n\t\t\t\treturn this;\n\t\t\t},\n\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function(){\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\nfunction Identity(v){\n\treturn v;\n}\nfunction Thrower(ex){\n\tthrow ex;\n}\n\nfunction adoptValue(value, resolve, reject, noValue){\n\tvar method;\n\n\ttry {\n\n\t\t// Check for promise aspect first to privilege synchronous behavior\n\t\tif(value&&isFunction(( method=value.promise) )){\n\t\t\tmethod.call(value).done(resolve).fail(reject);\n\n\t\t// Other thenables\n\t\t}else if(value&&isFunction(( method=value.then) )){\n\t\t\tmethod.call(value, resolve, reject);\n\n\t\t// Other non-thenables\n\t\t}else{\n\n\t\t\t// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:\n\t\t\t// * false: [ value ].slice(0)=> resolve(value)\n\t\t\t// * true: [ value ].slice(1)=> resolve()\n\t\t\tresolve.apply(undefined, [ value ].slice(noValue) );\n\t\t}\n\n\t// For Promises/A+, convert exceptions into rejections\n\t// Since jQuery.when doesn\'t unwrap thenables, we can skip the extra checks appearing in\n\t// Deferred#then to conditionally suppress rejection.\n\t} catch(value){\n\n\t\t// Support: Android 4.0 only\n\t\t// Strict mode functions invoked without .call/.apply get global-object context\n\t\treject.apply(undefined, [ value ]);\n\t}\n}\n\njQuery.extend({\n\n\tDeferred: function(func){\n\t\tvar tuples=[\n\n\t\t\t\t// action, add listener, callbacks,\n\t\t\t\t// ... .then handlers, argument index, [final state]\n\t\t\t\t[ "notify", "progress", jQuery.Callbacks("memory"),\n\t\t\t\t\tjQuery.Callbacks("memory"), 2 ],\n\t\t\t\t[ "resolve", "done", jQuery.Callbacks("once memory"),\n\t\t\t\t\tjQuery.Callbacks("once memory"), 0, "resolved" ],\n\t\t\t\t[ "reject", "fail", jQuery.Callbacks("once memory"),\n\t\t\t\t\tjQuery.Callbacks("once memory"), 1, "rejected" ]\n\t\t\t],\n\t\t\tstate="pending",\n\t\t\tpromise={\n\t\t\t\tstate: function(){\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function(){\n\t\t\t\t\tdeferred.done(arguments).fail(arguments);\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\t"catch": function(fn){\n\t\t\t\t\treturn promise.then(null, fn);\n\t\t\t\t},\n\n\t\t\t\t// Keep pipe for back-compat\n\t\t\t\tpipe: function(){\n\t\t\t\t\tvar fns=arguments;\n\n\t\t\t\t\treturn jQuery.Deferred(function(newDefer){\n\t\t\t\t\t\tjQuery.each(tuples, function(_i, tuple){\n\n\t\t\t\t\t\t\t// Map tuples (progress, done, fail) to arguments (done, fail, progress)\n\t\t\t\t\t\t\tvar fn=isFunction(fns[ tuple[ 4 ] ])&&fns[ tuple[ 4 ] ];\n\n\t\t\t\t\t\t\t// deferred.progress(function(){ bind to newDefer or newDefer.notify })\n\t\t\t\t\t\t\t// deferred.done(function(){ bind to newDefer or newDefer.resolve })\n\t\t\t\t\t\t\t// deferred.fail(function(){ bind to newDefer or newDefer.reject })\n\t\t\t\t\t\t\tdeferred[ tuple[ 1 ] ](function(){\n\t\t\t\t\t\t\t\tvar returned=fn&&fn.apply(this, arguments);\n\t\t\t\t\t\t\t\tif(returned&&isFunction(returned.promise) ){\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.progress(newDefer.notify)\n\t\t\t\t\t\t\t\t\t\t.done(newDefer.resolve)\n\t\t\t\t\t\t\t\t\t\t.fail(newDefer.reject);\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + "With" ](\n\t\t\t\t\t\t\t\t\t\tthis,\n\t\t\t\t\t\t\t\t\t\tfn ? [ returned ]:arguments\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns=null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\tthen: function(onFulfilled, onRejected, onProgress){\n\t\t\t\t\tvar maxDepth=0;\n\t\t\t\t\tfunction resolve(depth, deferred, handler, special){\n\t\t\t\t\t\treturn function(){\n\t\t\t\t\t\t\tvar that=this,\n\t\t\t\t\t\t\t\targs=arguments,\n\t\t\t\t\t\t\t\tmightThrow=function(){\n\t\t\t\t\t\t\t\t\tvar returned, then;\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.3\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-59\n\t\t\t\t\t\t\t\t\t// Ignore double-resolution attempts\n\t\t\t\t\t\t\t\t\tif(depth < maxDepth){\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturned=handler.apply(that, args);\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.1\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-48\n\t\t\t\t\t\t\t\t\tif(returned===deferred.promise()){\n\t\t\t\t\t\t\t\t\t\tthrow new TypeError("Thenable self-resolution");\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Support: Promises/A+ sections 2.3.3.1, 3.5\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-54\n\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-75\n\t\t\t\t\t\t\t\t\t// Retrieve `then` only once\n\t\t\t\t\t\t\t\t\tthen=returned&&\n\n\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.4\n\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-64\n\t\t\t\t\t\t\t\t\t\t// Only check objects and functions for thenability\n\t\t\t\t\t\t\t\t\t\t(typeof returned==="object"||\n\t\t\t\t\t\t\t\t\t\t\ttypeof returned==="function")&&\n\t\t\t\t\t\t\t\t\t\treturned.then;\n\n\t\t\t\t\t\t\t\t\t// Handle a returned thenable\n\t\t\t\t\t\t\t\t\tif(isFunction(then) ){\n\n\t\t\t\t\t\t\t\t\t\t// Special processors (notify) just wait for resolution\n\t\t\t\t\t\t\t\t\t\tif(special){\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(maxDepth, deferred, Identity, special),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(maxDepth, deferred, Thrower, special)\n\t\t\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\t\t// Normal processors (resolve) also hook into progress\n\t\t\t\t\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t\t\t\t\t// ...and disregard older resolution values\n\t\t\t\t\t\t\t\t\t\t\tmaxDepth++;\n\n\t\t\t\t\t\t\t\t\t\t\tthen.call(\n\t\t\t\t\t\t\t\t\t\t\t\treturned,\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(maxDepth, deferred, Identity, special),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(maxDepth, deferred, Thrower, special),\n\t\t\t\t\t\t\t\t\t\t\t\tresolve(maxDepth, deferred, Identity,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdeferred.notifyWith)\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Handle all other returned values\n\t\t\t\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\tif(handler!==Identity){\n\t\t\t\t\t\t\t\t\t\t\tthat=undefined;\n\t\t\t\t\t\t\t\t\t\t\targs=[ returned ];\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t// Process the value(s)\n\t\t\t\t\t\t\t\t\t\t// Default process is resolve\n\t\t\t\t\t\t\t\t\t\t(special||deferred.resolveWith)(that, args);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\n\t\t\t\t\t\t\t\t// Only normal processors (resolve) catch and reject exceptions\n\t\t\t\t\t\t\t\tprocess=special ?\n\t\t\t\t\t\t\t\t\tmightThrow :\n\t\t\t\t\t\t\t\t\tfunction(){\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tmightThrow();\n\t\t\t\t\t\t\t\t\t\t} catch(e){\n\n\t\t\t\t\t\t\t\t\t\t\tif(jQuery.Deferred.exceptionHook){\n\t\t\t\t\t\t\t\t\t\t\t\tjQuery.Deferred.exceptionHook(e,\n\t\t\t\t\t\t\t\t\t\t\t\t\tprocess.error);\n\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.4.1\n\t\t\t\t\t\t\t\t\t\t\t// https://promisesaplus.com/#point-61\n\t\t\t\t\t\t\t\t\t\t\t// Ignore post-resolution exceptions\n\t\t\t\t\t\t\t\t\t\t\tif(depth + 1 >=maxDepth){\n\n\t\t\t\t\t\t\t\t\t\t\t\t// Only substitute handlers pass on context\n\t\t\t\t\t\t\t\t\t\t\t\t// and multiple values (non-spec behavior)\n\t\t\t\t\t\t\t\t\t\t\t\tif(handler!==Thrower){\n\t\t\t\t\t\t\t\t\t\t\t\t\tthat=undefined;\n\t\t\t\t\t\t\t\t\t\t\t\t\targs=[ e ];\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\tdeferred.rejectWith(that, args);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Support: Promises/A+ section 2.3.3.3.1\n\t\t\t\t\t\t\t// https://promisesaplus.com/#point-57\n\t\t\t\t\t\t\t// Re-resolve promises immediately to dodge false rejection from\n\t\t\t\t\t\t\t// subsequent errors\n\t\t\t\t\t\t\tif(depth){\n\t\t\t\t\t\t\t\tprocess();\n\t\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t\t// Call an optional hook to record the error, in case of exception\n\t\t\t\t\t\t\t\t// since it\'s otherwise lost when execution goes async\n\t\t\t\t\t\t\t\tif(jQuery.Deferred.getErrorHook){\n\t\t\t\t\t\t\t\t\tprocess.error=jQuery.Deferred.getErrorHook();\n\n\t\t\t\t\t\t\t\t// The deprecated alias of the above. While the name suggests\n\t\t\t\t\t\t\t\t// returning the stack, not an error instance, jQuery just passes\n\t\t\t\t\t\t\t\t// it directly to `console.warn` so both will work; an instance\n\t\t\t\t\t\t\t\t// just better cooperates with source maps.\n\t\t\t\t\t\t\t\t}else if(jQuery.Deferred.getStackHook){\n\t\t\t\t\t\t\t\t\tprocess.error=jQuery.Deferred.getStackHook();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twindow.setTimeout(process);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn jQuery.Deferred(function(newDefer){\n\n\t\t\t\t\t\t// progress_handlers.add(...)\n\t\t\t\t\t\ttuples[ 0 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction(onProgress) ?\n\t\t\t\t\t\t\t\t\tonProgress :\n\t\t\t\t\t\t\t\t\tIdentity,\n\t\t\t\t\t\t\t\tnewDefer.notifyWith\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// fulfilled_handlers.add(...)\n\t\t\t\t\t\ttuples[ 1 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction(onFulfilled) ?\n\t\t\t\t\t\t\t\t\tonFulfilled :\n\t\t\t\t\t\t\t\t\tIdentity\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// rejected_handlers.add(...)\n\t\t\t\t\t\ttuples[ 2 ][ 3 ].add(\n\t\t\t\t\t\t\tresolve(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tnewDefer,\n\t\t\t\t\t\t\t\tisFunction(onRejected) ?\n\t\t\t\t\t\t\t\t\tonRejected :\n\t\t\t\t\t\t\t\t\tThrower\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function(obj){\n\t\t\t\t\treturn obj!=null ? jQuery.extend(obj, promise):promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred={};\n\n\t\t// Add list-specific methods\n\t\tjQuery.each(tuples, function(i, tuple){\n\t\t\tvar list=tuple[ 2 ],\n\t\t\t\tstateString=tuple[ 5 ];\n\n\t\t\t// promise.progress=list.add\n\t\t\t// promise.done=list.add\n\t\t\t// promise.fail=list.add\n\t\t\tpromise[ tuple[ 1 ] ]=list.add;\n\n\t\t\t// Handle state\n\t\t\tif(stateString){\n\t\t\t\tlist.add(\n\t\t\t\t\tfunction(){\n\n\t\t\t\t\t\t// state="resolved" (i.e., fulfilled)\n\t\t\t\t\t\t// state="rejected"\n\t\t\t\t\t\tstate=stateString;\n\t\t\t\t\t},\n\n\t\t\t\t\t// rejected_callbacks.disable\n\t\t\t\t\t// fulfilled_callbacks.disable\n\t\t\t\t\ttuples[ 3 - i ][ 2 ].disable,\n\n\t\t\t\t\t// rejected_handlers.disable\n\t\t\t\t\t// fulfilled_handlers.disable\n\t\t\t\t\ttuples[ 3 - i ][ 3 ].disable,\n\n\t\t\t\t\t// progress_callbacks.lock\n\t\t\t\t\ttuples[ 0 ][ 2 ].lock,\n\n\t\t\t\t\t// progress_handlers.lock\n\t\t\t\t\ttuples[ 0 ][ 3 ].lock\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// progress_handlers.fire\n\t\t\t// fulfilled_handlers.fire\n\t\t\t// rejected_handlers.fire\n\t\t\tlist.add(tuple[ 3 ].fire);\n\n\t\t\t// deferred.notify=function(){ deferred.notifyWith(...) }\n\t\t\t// deferred.resolve=function(){ deferred.resolveWith(...) }\n\t\t\t// deferred.reject=function(){ deferred.rejectWith(...) }\n\t\t\tdeferred[ tuple[ 0 ] ]=function(){\n\t\t\t\tdeferred[ tuple[ 0 ] + "With" ](this===deferred ? undefined:this, arguments);\n\t\t\t\treturn this;\n\t\t\t};\n\n\t\t\t// deferred.notifyWith=list.fireWith\n\t\t\t// deferred.resolveWith=list.fireWith\n\t\t\t// deferred.rejectWith=list.fireWith\n\t\t\tdeferred[ tuple[ 0 ] + "With" ]=list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise(deferred);\n\n\t\t// Call given func if any\n\t\tif(func){\n\t\t\tfunc.call(deferred, deferred);\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function(singleValue){\n\t\tvar\n\n\t\t\t// count of uncompleted subordinates\n\t\t\tremaining=arguments.length,\n\n\t\t\t// count of unprocessed arguments\n\t\t\ti=remaining,\n\n\t\t\t// subordinate fulfillment data\n\t\t\tresolveContexts=Array(i),\n\t\t\tresolveValues=slice.call(arguments),\n\n\t\t\t// the primary Deferred\n\t\t\tprimary=jQuery.Deferred(),\n\n\t\t\t// subordinate callback factory\n\t\t\tupdateFunc=function(i){\n\t\t\t\treturn function(value){\n\t\t\t\t\tresolveContexts[ i ]=this;\n\t\t\t\t\tresolveValues[ i ]=arguments.length > 1 ? slice.call(arguments):value;\n\t\t\t\t\tif(!(--remaining) ){\n\t\t\t\t\t\tprimary.resolveWith(resolveContexts, resolveValues);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t};\n\n\t\t// Single- and empty arguments are adopted like Promise.resolve\n\t\tif(remaining <=1){\n\t\t\tadoptValue(singleValue, primary.done(updateFunc(i) ).resolve, primary.reject,\n\t\t\t\t!remaining);\n\n\t\t\t// Use .then() to unwrap secondary thenables (cf. gh-3000)\n\t\t\tif(primary.state()==="pending"||\n\t\t\t\tisFunction(resolveValues[ i ]&&resolveValues[ i ].then) ){\n\n\t\t\t\treturn primary.then();\n\t\t\t}\n\t\t}\n\n\t\t// Multiple arguments are aggregated like Promise.all array elements\n\t\twhile(i--){\n\t\t\tadoptValue(resolveValues[ i ], updateFunc(i), primary.reject);\n\t\t}\n\n\t\treturn primary.promise();\n\t}\n});\n\n\n// These usually indicate a programmer mistake during development,\n// warn about them ASAP rather than swallowing them by default.\nvar rerrorNames=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;\n\n// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error\n// captured before the async barrier to get the original error cause\n// which may otherwise be hidden.\njQuery.Deferred.exceptionHook=function(error, asyncError){\n\n\t// Support: IE 8 - 9 only\n\t// Console exists when dev tools are open, which can happen at any time\n\tif(window.console&&window.console.warn&&error&&rerrorNames.test(error.name) ){\n\t\twindow.console.warn("jQuery.Deferred exception: " + error.message,\n\t\t\terror.stack, asyncError);\n\t}\n};\n\n\n\n\njQuery.readyException=function(error){\n\twindow.setTimeout(function(){\n\t\tthrow error;\n\t});\n};\n\n\n\n\n// The deferred used on DOM ready\nvar readyList=jQuery.Deferred();\n\njQuery.fn.ready=function(fn){\n\n\treadyList\n\t\t.then(fn)\n\n\t\t// Wrap jQuery.readyException in a function so that the lookup\n\t\t// happens at the time of error handling instead of callback\n\t\t// registration.\n\t\t.catch(function(error){\n\t\t\tjQuery.readyException(error);\n\t\t});\n\n\treturn this;\n};\n\njQuery.extend({\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See trac-6781\n\treadyWait: 1,\n\n\t// Handle when the DOM is ready\n\tready: function(wait){\n\n\t\t// Abort if there are pending holds or we\'re already ready\n\t\tif(wait===true ? --jQuery.readyWait:jQuery.isReady){\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady=true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif(wait!==true&&--jQuery.readyWait > 0){\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith(document, [ jQuery ]);\n\t}\n});\n\njQuery.ready.then=readyList.then;\n\n// The ready event handler and self cleanup method\nfunction completed(){\n\tdocument.removeEventListener("DOMContentLoaded", completed);\n\twindow.removeEventListener("load", completed);\n\tjQuery.ready();\n}\n\n// Catch cases where $(document).ready() is called\n// after the browser event has already occurred.\n// Support: IE <=9 - 10 only\n// Older IE sometimes signals "interactive" too soon\nif(document.readyState==="complete"||\n\t(document.readyState!=="loading"&&!document.documentElement.doScroll) ){\n\n\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\twindow.setTimeout(jQuery.ready);\n\n}else{\n\n\t// Use the handy event callback\n\tdocument.addEventListener("DOMContentLoaded", completed);\n\n\t// A fallback to window.onload, that will always work\n\twindow.addEventListener("load", completed);\n}\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it\'s a function\nvar access=function(elems, fn, key, value, chainable, emptyGet, raw){\n\tvar i=0,\n\t\tlen=elems.length,\n\t\tbulk=key==null;\n\n\t// Sets many values\n\tif(toType(key)==="object"){\n\t\tchainable=true;\n\t\tfor(i in key){\n\t\t\taccess(elems, fn, i, key[ i ], true, emptyGet, raw);\n\t\t}\n\n\t// Sets one value\n\t}else if(value!==undefined){\n\t\tchainable=true;\n\n\t\tif(!isFunction(value) ){\n\t\t\traw=true;\n\t\t}\n\n\t\tif(bulk){\n\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif(raw){\n\t\t\t\tfn.call(elems, value);\n\t\t\t\tfn=null;\n\n\t\t\t// ...except when executing function values\n\t\t\t}else{\n\t\t\t\tbulk=fn;\n\t\t\t\tfn=function(elem, _key, value){\n\t\t\t\t\treturn bulk.call(jQuery(elem), value);\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif(fn){\n\t\t\tfor(; i < len; i++){\n\t\t\t\tfn(\n\t\t\t\t\telems[ i ], key, raw ?\n\t\t\t\t\t\tvalue :\n\t\t\t\t\t\tvalue.call(elems[ i ], i, fn(elems[ i ], key) )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif(chainable){\n\t\treturn elems;\n\t}\n\n\t// Gets\n\tif(bulk){\n\t\treturn fn.call(elems);\n\t}\n\n\treturn len ? fn(elems[ 0 ], key):emptyGet;\n};\n\n\n// Matches dashed string for camelizing\nvar rmsPrefix=/^-ms-/,\n\trdashAlpha=/-([a-z])/g;\n\n// Used by camelCase as callback to replace()\nfunction fcamelCase(_all, letter){\n\treturn letter.toUpperCase();\n}\n\n// Convert dashed to camelCase; used by the css and data modules\n// Support: IE <=9 - 11, Edge 12 - 15\n// Microsoft forgot to hump their vendor prefix (trac-9572)\nfunction camelCase(string){\n\treturn string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase);\n}\nvar acceptData=function(owner){\n\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\treturn owner.nodeType===1||owner.nodeType===9||!( +owner.nodeType);\n};\n\n\n\n\nfunction Data(){\n\tthis.expando=jQuery.expando + Data.uid++;\n}\n\nData.uid=1;\n\nData.prototype={\n\n\tcache: function(owner){\n\n\t\t// Check if the owner object already has a cache\n\t\tvar value=owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif(!value){\n\t\t\tvalue={};\n\n\t\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t\t// but we should not, see trac-8335.\n\t\t\t// Always return an empty object.\n\t\t\tif(acceptData(owner) ){\n\n\t\t\t\t// If it is a node unlikely to be stringify-ed or looped over\n\t\t\t\t// use plain assignment\n\t\t\t\tif(owner.nodeType){\n\t\t\t\t\towner[ this.expando ]=value;\n\n\t\t\t\t// Otherwise secure it in a non-enumerable property\n\t\t\t\t// configurable must be true to allow the property to be\n\t\t\t\t// deleted when data is removed\n\t\t\t\t}else{\n\t\t\t\t\tObject.defineProperty(owner, this.expando, {\n\t\t\t\t\t\tvalue: value,\n\t\t\t\t\t\tconfigurable: true\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn value;\n\t},\n\tset: function(owner, data, value){\n\t\tvar prop,\n\t\t\tcache=this.cache(owner);\n\n\t\t// Handle: [ owner, key, value ] args\n\t\t// Always use camelCase key (gh-2257)\n\t\tif(typeof data==="string"){\n\t\t\tcache[ camelCase(data) ]=value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t}else{\n\n\t\t\t// Copy the properties one-by-one to the cache object\n\t\t\tfor(prop in data){\n\t\t\t\tcache[ camelCase(prop) ]=data[ prop ];\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function(owner, key){\n\t\treturn key===undefined ?\n\t\t\tthis.cache(owner) :\n\n\t\t\t// Always use camelCase key (gh-2257)\n\t\t\towner[ this.expando ]&&owner[ this.expando ][ camelCase(key) ];\n\t},\n\taccess: function(owner, key, value){\n\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the "read" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif(key===undefined||\n\t\t\t\t(( key&&typeof key==="string")&&value===undefined) ){\n\n\t\t\treturn this.get(owner, key);\n\t\t}\n\n\t\t// When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set(owner, key, value);\n\n\t\t// Since the "set" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value!==undefined ? value:key;\n\t},\n\tremove: function(owner, key){\n\t\tvar i,\n\t\t\tcache=owner[ this.expando ];\n\n\t\tif(cache===undefined){\n\t\t\treturn;\n\t\t}\n\n\t\tif(key!==undefined){\n\n\t\t\t// Support array or space separated string of keys\n\t\t\tif(Array.isArray(key) ){\n\n\t\t\t\t// If key is an array of keys...\n\t\t\t\t// We always set camelCase keys, so remove that.\n\t\t\t\tkey=key.map(camelCase);\n\t\t\t}else{\n\t\t\t\tkey=camelCase(key);\n\n\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\tkey=key in cache ?\n\t\t\t\t\t[ key ] :\n\t\t\t\t\t(key.match(rnothtmlwhite)||[]);\n\t\t\t}\n\n\t\t\ti=key.length;\n\n\t\t\twhile(i--){\n\t\t\t\tdelete cache[ key[ i ] ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if there\'s no more data\n\t\tif(key===undefined||jQuery.isEmptyObject(cache) ){\n\n\t\t\t// Support: Chrome <=35 - 45\n\t\t\t// Webkit & Blink performance suffers when deleting properties\n\t\t\t// from DOM nodes, so set to undefined instead\n\t\t\t// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)\n\t\t\tif(owner.nodeType){\n\t\t\t\towner[ this.expando ]=undefined;\n\t\t\t}else{\n\t\t\t\tdelete owner[ this.expando ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function(owner){\n\t\tvar cache=owner[ this.expando ];\n\t\treturn cache!==undefined&&!jQuery.isEmptyObject(cache);\n\t}\n};\nvar dataPriv=new Data();\n\nvar dataUser=new Data();\n\n\n\n//\tImplementation Summary\n//\n//\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n//\t2. Improve the module\'s maintainability by reducing the storage\n//\t\tpaths to a single mechanism.\n//\t3. Use the same single mechanism to support "private" and "user" data.\n//\t4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)\n//\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n//\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n\nvar rbrace=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash=/[A-Z]/g;\n\nfunction getData(data){\n\tif(data==="true"){\n\t\treturn true;\n\t}\n\n\tif(data==="false"){\n\t\treturn false;\n\t}\n\n\tif(data==="null"){\n\t\treturn null;\n\t}\n\n\t// Only convert to a number if it doesn\'t change the string\n\tif(data===+data + ""){\n\t\treturn +data;\n\t}\n\n\tif(rbrace.test(data) ){\n\t\treturn JSON.parse(data);\n\t}\n\n\treturn data;\n}\n\nfunction dataAttr(elem, key, data){\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif(data===undefined&&elem.nodeType===1){\n\t\tname="data-" + key.replace(rmultiDash, "-$&").toLowerCase();\n\t\tdata=elem.getAttribute(name);\n\n\t\tif(typeof data==="string"){\n\t\t\ttry {\n\t\t\t\tdata=getData(data);\n\t\t\t} catch(e){}\n\n\t\t\t// Make sure we set the data so it isn\'t changed later\n\t\t\tdataUser.set(elem, key, data);\n\t\t}else{\n\t\t\tdata=undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend({\n\thasData: function(elem){\n\t\treturn dataUser.hasData(elem)||dataPriv.hasData(elem);\n\t},\n\n\tdata: function(elem, name, data){\n\t\treturn dataUser.access(elem, name, data);\n\t},\n\n\tremoveData: function(elem, name){\n\t\tdataUser.remove(elem, name);\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to dataPriv methods, these can be deprecated.\n\t_data: function(elem, name, data){\n\t\treturn dataPriv.access(elem, name, data);\n\t},\n\n\t_removeData: function(elem, name){\n\t\tdataPriv.remove(elem, name);\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function(key, value){\n\t\tvar i, name, data,\n\t\t\telem=this[ 0 ],\n\t\t\tattrs=elem&&elem.attributes;\n\n\t\t// Gets all values\n\t\tif(key===undefined){\n\t\t\tif(this.length){\n\t\t\t\tdata=dataUser.get(elem);\n\n\t\t\t\tif(elem.nodeType===1&&!dataPriv.get(elem, "hasDataAttrs") ){\n\t\t\t\t\ti=attrs.length;\n\t\t\t\t\twhile(i--){\n\n\t\t\t\t\t\t// Support: IE 11 only\n\t\t\t\t\t\t// The attrs elements can be null (trac-14894)\n\t\t\t\t\t\tif(attrs[ i ]){\n\t\t\t\t\t\t\tname=attrs[ i ].name;\n\t\t\t\t\t\t\tif(name.indexOf("data-")===0){\n\t\t\t\t\t\t\t\tname=camelCase(name.slice(5) );\n\t\t\t\t\t\t\t\tdataAttr(elem, name, data[ name ]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdataPriv.set(elem, "hasDataAttrs", true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif(typeof key==="object"){\n\t\t\treturn this.each(function(){\n\t\t\t\tdataUser.set(this, key);\n\t\t\t});\n\t\t}\n\n\t\treturn access(this, function(value){\n\t\t\tvar data;\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem=this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif(elem&&value===undefined){\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// The key will always be camelCased in Data\n\t\t\t\tdata=dataUser.get(elem, key);\n\t\t\t\tif(data!==undefined){\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to "discover" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata=dataAttr(elem, key);\n\t\t\t\tif(data!==undefined){\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn\'t exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each(function(){\n\n\t\t\t\t// We always store the camelCased key\n\t\t\t\tdataUser.set(this, key, value);\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, true);\n\t},\n\n\tremoveData: function(key){\n\t\treturn this.each(function(){\n\t\t\tdataUser.remove(this, key);\n\t\t});\n\t}\n});\n\n\njQuery.extend({\n\tqueue: function(elem, type, data){\n\t\tvar queue;\n\n\t\tif(elem){\n\t\t\ttype=(type||"fx") + "queue";\n\t\t\tqueue=dataPriv.get(elem, type);\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif(data){\n\t\t\t\tif(!queue||Array.isArray(data) ){\n\t\t\t\t\tqueue=dataPriv.access(elem, type, jQuery.makeArray(data) );\n\t\t\t\t}else{\n\t\t\t\t\tqueue.push(data);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue||[];\n\t\t}\n\t},\n\n\tdequeue: function(elem, type){\n\t\ttype=type||"fx";\n\n\t\tvar queue=jQuery.queue(elem, type),\n\t\t\tstartLength=queue.length,\n\t\t\tfn=queue.shift(),\n\t\t\thooks=jQuery._queueHooks(elem, type),\n\t\t\tnext=function(){\n\t\t\t\tjQuery.dequeue(elem, type);\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif(fn==="inprogress"){\n\t\t\tfn=queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif(fn){\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif(type==="fx"){\n\t\t\t\tqueue.unshift("inprogress");\n\t\t\t}\n\n\t\t\t// Clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call(elem, next, hooks);\n\t\t}\n\n\t\tif(!startLength&&hooks){\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// Not public - generate a queueHooks object, or return the current one\n\t_queueHooks: function(elem, type){\n\t\tvar key=type + "queueHooks";\n\t\treturn dataPriv.get(elem, key)||dataPriv.access(elem, key, {\n\t\t\tempty: jQuery.Callbacks("once memory").add(function(){\n\t\t\t\tdataPriv.remove(elem, [ type + "queue", key ]);\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function(type, data){\n\t\tvar setter=2;\n\n\t\tif(typeof type!=="string"){\n\t\t\tdata=type;\n\t\t\ttype="fx";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif(arguments.length < setter){\n\t\t\treturn jQuery.queue(this[ 0 ], type);\n\t\t}\n\n\t\treturn data===undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function(){\n\t\t\t\tvar queue=jQuery.queue(this, type, data);\n\n\t\t\t\t// Ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks(this, type);\n\n\t\t\t\tif(type==="fx"&&queue[ 0 ]!=="inprogress"){\n\t\t\t\t\tjQuery.dequeue(this, type);\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function(type){\n\t\treturn this.each(function(){\n\t\t\tjQuery.dequeue(this, type);\n\t\t});\n\t},\n\tclearQueue: function(type){\n\t\treturn this.queue(type||"fx", []);\n\t},\n\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function(type, obj){\n\t\tvar tmp,\n\t\t\tcount=1,\n\t\t\tdefer=jQuery.Deferred(),\n\t\t\telements=this,\n\t\t\ti=this.length,\n\t\t\tresolve=function(){\n\t\t\t\tif(!(--count) ){\n\t\t\t\t\tdefer.resolveWith(elements, [ elements ]);\n\t\t\t\t}\n\t\t\t};\n\n\t\tif(typeof type!=="string"){\n\t\t\tobj=type;\n\t\t\ttype=undefined;\n\t\t}\n\t\ttype=type||"fx";\n\n\t\twhile(i--){\n\t\t\ttmp=dataPriv.get(elements[ i ], type + "queueHooks");\n\t\t\tif(tmp&&tmp.empty){\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add(resolve);\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise(obj);\n\t}\n});\nvar pnum=(/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\nvar rcssNum=new RegExp("^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i");\n\n\nvar cssExpand=[ "Top", "Right", "Bottom", "Left" ];\n\nvar documentElement=document.documentElement;\n\n\n\n\tvar isAttached=function(elem){\n\t\t\treturn jQuery.contains(elem.ownerDocument, elem);\n\t\t},\n\t\tcomposed={ composed: true };\n\n\t// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only\n\t// Check attachment across shadow DOM boundaries when possible (gh-3504)\n\t// Support: iOS 10.0-10.2 only\n\t// Early iOS 10 versions support `attachShadow` but not `getRootNode`,\n\t// leading to errors. We need to check for `getRootNode`.\n\tif(documentElement.getRootNode){\n\t\tisAttached=function(elem){\n\t\t\treturn jQuery.contains(elem.ownerDocument, elem)||\n\t\t\t\telem.getRootNode(composed)===elem.ownerDocument;\n\t\t};\n\t}\nvar isHiddenWithinTree=function(elem, el){\n\n\t\t// isHiddenWithinTree might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem=el||elem;\n\n\t\t// Inline style trumps all\n\t\treturn elem.style.display==="none"||\n\t\t\telem.style.display===""&&\n\n\t\t\t// Otherwise, check computed style\n\t\t\t// Support: Firefox <=43 - 45\n\t\t\t// Disconnected elements can have computed display: none, so first confirm that elem is\n\t\t\t// in the document.\n\t\t\tisAttached(elem)&&\n\n\t\t\tjQuery.css(elem, "display")==="none";\n\t};\n\n\n\nfunction adjustCSS(elem, prop, valueParts, tween){\n\tvar adjusted, scale,\n\t\tmaxIterations=20,\n\t\tcurrentValue=tween ?\n\t\t\tfunction(){\n\t\t\t\treturn tween.cur();\n\t\t\t} :\n\t\t\tfunction(){\n\t\t\t\treturn jQuery.css(elem, prop, "");\n\t\t\t},\n\t\tinitial=currentValue(),\n\t\tunit=valueParts&&valueParts[ 3 ]||(jQuery.cssNumber[ prop ] ? "":"px"),\n\n\t\t// Starting value computation is required for potential unit mismatches\n\t\tinitialInUnit=elem.nodeType&&\n\t\t\t(jQuery.cssNumber[ prop ]||unit!=="px"&&+initial)&&\n\t\t\trcssNum.exec(jQuery.css(elem, prop) );\n\n\tif(initialInUnit&&initialInUnit[ 3 ]!==unit){\n\n\t\t// Support: Firefox <=54\n\t\t// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)\n\t\tinitial=initial / 2;\n\n\t\t// Trust units reported by jQuery.css\n\t\tunit=unit||initialInUnit[ 3 ];\n\n\t\t// Iteratively approximate from a nonzero starting point\n\t\tinitialInUnit=+initial||1;\n\n\t\twhile(maxIterations--){\n\n\t\t\t// Evaluate and update our best guess (doubling guesses that zero out).\n\t\t\t// Finish if the scale equals or crosses 1 (making the old*new product non-positive).\n\t\t\tjQuery.style(elem, prop, initialInUnit + unit);\n\t\t\tif(( 1 - scale) *(1 -(scale=currentValue() / initial||0.5) ) <=0){\n\t\t\t\tmaxIterations=0;\n\t\t\t}\n\t\t\tinitialInUnit=initialInUnit / scale;\n\n\t\t}\n\n\t\tinitialInUnit=initialInUnit * 2;\n\t\tjQuery.style(elem, prop, initialInUnit + unit);\n\n\t\t// Make sure we update the tween properties later on\n\t\tvalueParts=valueParts||[];\n\t}\n\n\tif(valueParts){\n\t\tinitialInUnit=+initialInUnit||+initial||0;\n\n\t\t// Apply relative offset (+=/-=) if specified\n\t\tadjusted=valueParts[ 1 ] ?\n\t\t\tinitialInUnit +(valueParts[ 1 ] + 1) * valueParts[ 2 ] :\n\t\t\t+valueParts[ 2 ];\n\t\tif(tween){\n\t\t\ttween.unit=unit;\n\t\t\ttween.start=initialInUnit;\n\t\t\ttween.end=adjusted;\n\t\t}\n\t}\n\treturn adjusted;\n}\n\n\nvar defaultDisplayMap={};\n\nfunction getDefaultDisplay(elem){\n\tvar temp,\n\t\tdoc=elem.ownerDocument,\n\t\tnodeName=elem.nodeName,\n\t\tdisplay=defaultDisplayMap[ nodeName ];\n\n\tif(display){\n\t\treturn display;\n\t}\n\n\ttemp=doc.body.appendChild(doc.createElement(nodeName) );\n\tdisplay=jQuery.css(temp, "display");\n\n\ttemp.parentNode.removeChild(temp);\n\n\tif(display==="none"){\n\t\tdisplay="block";\n\t}\n\tdefaultDisplayMap[ nodeName ]=display;\n\n\treturn display;\n}\n\nfunction showHide(elements, show){\n\tvar display, elem,\n\t\tvalues=[],\n\t\tindex=0,\n\t\tlength=elements.length;\n\n\t// Determine new display value for elements that need to change\n\tfor(; index < length; index++){\n\t\telem=elements[ index ];\n\t\tif(!elem.style){\n\t\t\tcontinue;\n\t\t}\n\n\t\tdisplay=elem.style.display;\n\t\tif(show){\n\n\t\t\t// Since we force visibility upon cascade-hidden elements, an immediate (and slow)\n\t\t\t// check is required in this first loop unless we have a nonempty display value (either\n\t\t\t// inline or about-to-be-restored)\n\t\t\tif(display==="none"){\n\t\t\t\tvalues[ index ]=dataPriv.get(elem, "display")||null;\n\t\t\t\tif(!values[ index ]){\n\t\t\t\t\telem.style.display="";\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(elem.style.display===""&&isHiddenWithinTree(elem) ){\n\t\t\t\tvalues[ index ]=getDefaultDisplay(elem);\n\t\t\t}\n\t\t}else{\n\t\t\tif(display!=="none"){\n\t\t\t\tvalues[ index ]="none";\n\n\t\t\t\t// Remember what we\'re overwriting\n\t\t\t\tdataPriv.set(elem, "display", display);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of the elements in a second loop to avoid constant reflow\n\tfor(index=0; index < length; index++){\n\t\tif(values[ index ]!=null){\n\t\t\telements[ index ].style.display=values[ index ];\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend({\n\tshow: function(){\n\t\treturn showHide(this, true);\n\t},\n\thide: function(){\n\t\treturn showHide(this);\n\t},\n\ttoggle: function(state){\n\t\tif(typeof state==="boolean"){\n\t\t\treturn state ? this.show():this.hide();\n\t\t}\n\n\t\treturn this.each(function(){\n\t\t\tif(isHiddenWithinTree(this) ){\n\t\t\t\tjQuery(this).show();\n\t\t\t}else{\n\t\t\t\tjQuery(this).hide();\n\t\t\t}\n\t\t});\n\t}\n});\nvar rcheckableType=(/^(?:checkbox|radio)$/i);\n\nvar rtagName=(/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i);\n\nvar rscriptType=(/^$|^module$|\\/(?:java|ecma)script/i);\n\n\n\n(function(){\n\tvar fragment=document.createDocumentFragment(),\n\t\tdiv=fragment.appendChild(document.createElement("div") ),\n\t\tinput=document.createElement("input");\n\n\t// Support: Android 4.0 - 4.3 only\n\t// Check state lost if the name is set (trac-11217)\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (trac-14901)\n\tinput.setAttribute("type", "radio");\n\tinput.setAttribute("checked", "checked");\n\tinput.setAttribute("name", "t");\n\n\tdiv.appendChild(input);\n\n\t// Support: Android <=4.1 only\n\t// Older WebKit doesn\'t clone checked state correctly in fragments\n\tsupport.checkClone=div.cloneNode(true).cloneNode(true).lastChild.checked;\n\n\t// Support: IE <=11 only\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\tdiv.innerHTML="<textarea>x</textarea>";\n\tsupport.noCloneChecked = !!div.cloneNode(true).lastChild.defaultValue;\n\n\t// Support: IE <=9 only\n\t// IE <=9 replaces <option> tags with their contents when inserted outside of\n\t// the select element.\n\tdiv.innerHTML="<option></option>";\n\tsupport.option = !!div.lastChild;\n})();\n\n\n// We have to close these tags to support XHTML (trac-13200)\nvar wrapMap={\n\n\t// XHTML parsers do not magically insert elements in the\n\t// same way that tag soup parsers do. So we cannot shorten\n\t// this by omitting <tbody> or other required elements.\n\tthead: [ 1, "<table>", "</table>" ],\n\tcol: [ 2, "<table><colgroup>", "</colgroup></table>" ],\n\ttr: [ 2, "<table><tbody>", "</tbody></table>" ],\n\ttd: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],\n\n\t_default: [ 0, "", "" ]\n};\n\nwrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead;\nwrapMap.th=wrapMap.td;\n\n// Support: IE <=9 only\nif(!support.option){\n\twrapMap.optgroup=wrapMap.option=[ 1, "<select multiple=\'multiple\'>", "</select>" ];\n}\n\n\nfunction getAll(context, tag){\n\n\t// Support: IE <=9 - 11 only\n\t// Use typeof to avoid zero-argument method invocation on host objects (trac-15151)\n\tvar ret;\n\n\tif(typeof context.getElementsByTagName!=="undefined"){\n\t\tret=context.getElementsByTagName(tag||"*");\n\n\t}else if(typeof context.querySelectorAll!=="undefined"){\n\t\tret=context.querySelectorAll(tag||"*");\n\n\t}else{\n\t\tret=[];\n\t}\n\n\tif(tag===undefined||tag&&nodeName(context, tag) ){\n\t\treturn jQuery.merge([ context ], ret);\n\t}\n\n\treturn ret;\n}\n\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval(elems, refElements){\n\tvar i=0,\n\t\tl=elems.length;\n\n\tfor(; i < l; i++){\n\t\tdataPriv.set(\n\t\t\telems[ i ],\n\t\t\t"globalEval",\n\t\t\t!refElements||dataPriv.get(refElements[ i ], "globalEval")\n\t\t);\n\t}\n}\n\n\nvar rhtml=/<|&#?\\w+;/;\n\nfunction buildFragment(elems, context, scripts, selection, ignored){\n\tvar elem, tmp, tag, wrap, attached, j,\n\t\tfragment=context.createDocumentFragment(),\n\t\tnodes=[],\n\t\ti=0,\n\t\tl=elems.length;\n\n\tfor(; i < l; i++){\n\t\telem=elems[ i ];\n\n\t\tif(elem||elem===0){\n\n\t\t\t// Add nodes directly\n\t\t\tif(toType(elem)==="object"){\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge(nodes, elem.nodeType ? [ elem ]:elem);\n\n\t\t\t// Convert non-html into a text node\n\t\t\t}else if(!rhtml.test(elem) ){\n\t\t\t\tnodes.push(context.createTextNode(elem) );\n\n\t\t\t// Convert html into DOM nodes\n\t\t\t}else{\n\t\t\t\ttmp=tmp||fragment.appendChild(context.createElement("div") );\n\n\t\t\t\t// Deserialize a standard representation\n\t\t\t\ttag=(rtagName.exec(elem)||[ "", "" ])[ 1 ].toLowerCase();\n\t\t\t\twrap=wrapMap[ tag ]||wrapMap._default;\n\t\t\t\ttmp.innerHTML=wrap[ 1 ] + jQuery.htmlPrefilter(elem) + wrap[ 2 ];\n\n\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\tj=wrap[ 0 ];\n\t\t\t\twhile(j--){\n\t\t\t\t\ttmp=tmp.lastChild;\n\t\t\t\t}\n\n\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\tjQuery.merge(nodes, tmp.childNodes);\n\n\t\t\t\t// Remember the top-level container\n\t\t\t\ttmp=fragment.firstChild;\n\n\t\t\t\t// Ensure the created nodes are orphaned (trac-12392)\n\t\t\t\ttmp.textContent="";\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove wrapper from fragment\n\tfragment.textContent="";\n\n\ti=0;\n\twhile(( elem=nodes[ i++ ]) ){\n\n\t\t// Skip elements already in the context collection (trac-4087)\n\t\tif(selection&&jQuery.inArray(elem, selection) > -1){\n\t\t\tif(ignored){\n\t\t\t\tignored.push(elem);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tattached=isAttached(elem);\n\n\t\t// Append to fragment\n\t\ttmp=getAll(fragment.appendChild(elem), "script");\n\n\t\t// Preserve script evaluation history\n\t\tif(attached){\n\t\t\tsetGlobalEval(tmp);\n\t\t}\n\n\t\t// Capture executables\n\t\tif(scripts){\n\t\t\tj=0;\n\t\t\twhile(( elem=tmp[ j++ ]) ){\n\t\t\t\tif(rscriptType.test(elem.type||"") ){\n\t\t\t\t\tscripts.push(elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fragment;\n}\n\n\nvar rtypenamespace=/^([^.]*)(?:\\.(.+)|)/;\n\nfunction returnTrue(){\n\treturn true;\n}\n\nfunction returnFalse(){\n\treturn false;\n}\n\nfunction on(elem, types, selector, data, fn, one){\n\tvar origFn, type;\n\n\t// Types can be a map of types/handlers\n\tif(typeof types==="object"){\n\n\t\t//(types-Object, selector, data)\n\t\tif(typeof selector!=="string"){\n\n\t\t\t//(types-Object, data)\n\t\t\tdata=data||selector;\n\t\t\tselector=undefined;\n\t\t}\n\t\tfor(type in types){\n\t\t\ton(elem, type, selector, data, types[ type ], one);\n\t\t}\n\t\treturn elem;\n\t}\n\n\tif(data==null&&fn==null){\n\n\t\t//(types, fn)\n\t\tfn=selector;\n\t\tdata=selector=undefined;\n\t}else if(fn==null){\n\t\tif(typeof selector==="string"){\n\n\t\t\t//(types, selector, fn)\n\t\t\tfn=data;\n\t\t\tdata=undefined;\n\t\t}else{\n\n\t\t\t//(types, data, fn)\n\t\t\tfn=data;\n\t\t\tdata=selector;\n\t\t\tselector=undefined;\n\t\t}\n\t}\n\tif(fn===false){\n\t\tfn=returnFalse;\n\t}else if(!fn){\n\t\treturn elem;\n\t}\n\n\tif(one===1){\n\t\torigFn=fn;\n\t\tfn=function(event){\n\n\t\t\t// Can use an empty set, since event contains the info\n\t\t\tjQuery().off(event);\n\t\t\treturn origFn.apply(this, arguments);\n\t\t};\n\n\t\t// Use same guid so caller can remove using origFn\n\t\tfn.guid=origFn.guid||(origFn.guid=jQuery.guid++);\n\t}\n\treturn elem.each(function(){\n\t\tjQuery.event.add(this, types, fn, data, selector);\n\t});\n}\n\n\njQuery.event={\n\n\tglobal: {},\n\n\tadd: function(elem, types, handler, data, selector){\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData=dataPriv.get(elem);\n\n\t\t// Only attach events to objects that accept data\n\t\tif(!acceptData(elem) ){\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif(handler.handler){\n\t\t\thandleObjIn=handler;\n\t\t\thandler=handleObjIn.handler;\n\t\t\tselector=handleObjIn.selector;\n\t\t}\n\n\t\t// Ensure that invalid selectors throw exceptions at attach time\n\t\t// Evaluate against documentElement in case elem is a non-element node (e.g., document)\n\t\tif(selector){\n\t\t\tjQuery.find.matchesSelector(documentElement, selector);\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif(!handler.guid){\n\t\t\thandler.guid=jQuery.guid++;\n\t\t}\n\n\t\t// Init the element\'s event structure and main handler, if this is the first\n\t\tif(!(events=elemData.events) ){\n\t\t\tevents=elemData.events=Object.create(null);\n\t\t}\n\t\tif(!(eventHandle=elemData.handle) ){\n\t\t\teventHandle=elemData.handle=function(e){\n\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery!=="undefined"&&jQuery.event.triggered!==e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply(elem, arguments):undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes=(types||"").match(rnothtmlwhite)||[ "" ];\n\t\tt=types.length;\n\t\twhile(t--){\n\t\t\ttmp=rtypenamespace.exec(types[ t ])||[];\n\t\t\ttype=origType=tmp[ 1 ];\n\t\t\tnamespaces=(tmp[ 2 ]||"").split(".").sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif(!type){\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial=jQuery.event.special[ type ]||{};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype=(selector ? special.delegateType:special.bindType)||type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial=jQuery.event.special[ type ]||{};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj=jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector&&jQuery.expr.match.needsContext.test(selector),\n\t\t\t\tnamespace: namespaces.join(".")\n\t\t\t}, handleObjIn);\n\n\t\t\t// Init the event handler queue if we\'re the first\n\t\t\tif(!(handlers=events[ type ]) ){\n\t\t\t\thandlers=events[ type ]=[];\n\t\t\t\thandlers.delegateCount=0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif(!special.setup||\n\t\t\t\t\tspecial.setup.call(elem, data, namespaces, eventHandle)===false){\n\n\t\t\t\t\tif(elem.addEventListener){\n\t\t\t\t\t\telem.addEventListener(type, eventHandle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(special.add){\n\t\t\t\tspecial.add.call(elem, handleObj);\n\n\t\t\t\tif(!handleObj.handler.guid){\n\t\t\t\t\thandleObj.handler.guid=handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element\'s handler list, delegates in front\n\t\t\tif(selector){\n\t\t\t\thandlers.splice(handlers.delegateCount++, 0, handleObj);\n\t\t\t}else{\n\t\t\t\thandlers.push(handleObj);\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ]=true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function(elem, types, handler, selector, mappedTypes){\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData=dataPriv.hasData(elem)&&dataPriv.get(elem);\n\n\t\tif(!elemData||!(events=elemData.events) ){\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes=(types||"").match(rnothtmlwhite)||[ "" ];\n\t\tt=types.length;\n\t\twhile(t--){\n\t\t\ttmp=rtypenamespace.exec(types[ t ])||[];\n\t\t\ttype=origType=tmp[ 1 ];\n\t\t\tnamespaces=(tmp[ 2 ]||"").split(".").sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif(!type){\n\t\t\t\tfor(type in events){\n\t\t\t\t\tjQuery.event.remove(elem, type + types[ t ], handler, selector, true);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial=jQuery.event.special[ type ]||{};\n\t\t\ttype=(selector ? special.delegateType:special.bindType)||type;\n\t\t\thandlers=events[ type ]||[];\n\t\t\ttmp=tmp[ 2 ]&&\n\t\t\t\tnew RegExp("(^|\\\\.)" + namespaces.join("\\\\.(?:.*\\\\.|)") + "(\\\\.|$)");\n\n\t\t\t// Remove matching events\n\t\t\torigCount=j = handlers.length;\n\t\t\twhile(j--){\n\t\t\t\thandleObj=handlers[ j ];\n\n\t\t\t\tif(( mappedTypes||origType===handleObj.origType)&&\n\t\t\t\t\t(!handler||handler.guid===handleObj.guid)&&\n\t\t\t\t\t(!tmp||tmp.test(handleObj.namespace) )&&\n\t\t\t\t\t(!selector||selector===handleObj.selector||\n\t\t\t\t\t\tselector==="**"&&handleObj.selector) ){\n\t\t\t\t\thandlers.splice(j, 1);\n\n\t\t\t\t\tif(handleObj.selector){\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif(special.remove){\n\t\t\t\t\t\tspecial.remove.call(elem, handleObj);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif(origCount&&!handlers.length){\n\t\t\t\tif(!special.teardown||\n\t\t\t\t\tspecial.teardown.call(elem, namespaces, elemData.handle)===false){\n\n\t\t\t\t\tjQuery.removeEvent(elem, type, elemData.handle);\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove data and the expando if it\'s no longer used\n\t\tif(jQuery.isEmptyObject(events) ){\n\t\t\tdataPriv.remove(elem, "handle events");\n\t\t}\n\t},\n\n\tdispatch: function(nativeEvent){\n\n\t\tvar i, j, ret, matched, handleObj, handlerQueue,\n\t\t\targs=new Array(arguments.length),\n\n\t\t\t// Make a writable jQuery.Event from the native event object\n\t\t\tevent=jQuery.event.fix(nativeEvent),\n\n\t\t\thandlers=(\n\t\t\t\tdataPriv.get(this, "events")||Object.create(null)\n\t\t\t)[ event.type ]||[],\n\t\t\tspecial=jQuery.event.special[ event.type ]||{};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[ 0 ]=event;\n\n\t\tfor(i=1; i < arguments.length; i++){\n\t\t\targs[ i ]=arguments[ i ];\n\t\t}\n\n\t\tevent.delegateTarget=this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif(special.preDispatch&&special.preDispatch.call(this, event)===false){\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue=jQuery.event.handlers.call(this, event, handlers);\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti=0;\n\t\twhile(( matched=handlerQueue[ i++ ])&&!event.isPropagationStopped()){\n\t\t\tevent.currentTarget=matched.elem;\n\n\t\t\tj=0;\n\t\t\twhile(( handleObj=matched.handlers[ j++ ])&&\n\t\t\t\t!event.isImmediatePropagationStopped()){\n\n\t\t\t\t// If the event is namespaced, then each handler is only invoked if it is\n\t\t\t\t// specially universal or its namespaces are a superset of the event\'s.\n\t\t\t\tif(!event.rnamespace||handleObj.namespace===false||\n\t\t\t\t\tevent.rnamespace.test(handleObj.namespace) ){\n\n\t\t\t\t\tevent.handleObj=handleObj;\n\t\t\t\t\tevent.data=handleObj.data;\n\n\t\t\t\t\tret=(( jQuery.event.special[ handleObj.origType ]||{}).handle||\n\t\t\t\t\t\thandleObj.handler).apply(matched.elem, args);\n\n\t\t\t\t\tif(ret!==undefined){\n\t\t\t\t\t\tif(( event.result=ret)===false){\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif(special.postDispatch){\n\t\t\tspecial.postDispatch.call(this, event);\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function(event, handlers){\n\t\tvar i, handleObj, sel, matchedHandlers, matchedSelectors,\n\t\t\thandlerQueue=[],\n\t\t\tdelegateCount=handlers.delegateCount,\n\t\t\tcur=event.target;\n\n\t\t// Find delegate handlers\n\t\tif(delegateCount&&\n\n\t\t\t// Support: IE <=9\n\t\t\t// Black-hole SVG <use> instance trees (trac-13180)\n\t\t\tcur.nodeType&&\n\n\t\t\t// Support: Firefox <=42\n\t\t\t// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)\n\t\t\t// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click\n\t\t\t// Support: IE 11 only\n\t\t\t// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)\n\t\t\t!(event.type==="click"&&event.button >=1) ){\n\n\t\t\tfor(; cur!==this; cur=cur.parentNode||this){\n\n\t\t\t\t// Don\'t check non-elements (trac-13208)\n\t\t\t\t// Don\'t process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)\n\t\t\t\tif(cur.nodeType===1&&!(event.type==="click"&&cur.disabled===true) ){\n\t\t\t\t\tmatchedHandlers=[];\n\t\t\t\t\tmatchedSelectors={};\n\t\t\t\t\tfor(i=0; i < delegateCount; i++){\n\t\t\t\t\t\thandleObj=handlers[ i ];\n\n\t\t\t\t\t\t// Don\'t conflict with Object.prototype properties (trac-13203)\n\t\t\t\t\t\tsel=handleObj.selector + " ";\n\n\t\t\t\t\t\tif(matchedSelectors[ sel ]===undefined){\n\t\t\t\t\t\t\tmatchedSelectors[ sel ]=handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery(sel, this).index(cur) > -1 :\n\t\t\t\t\t\t\t\tjQuery.find(sel, this, null, [ cur ]).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif(matchedSelectors[ sel ]){\n\t\t\t\t\t\t\tmatchedHandlers.push(handleObj);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif(matchedHandlers.length){\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matchedHandlers });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tcur=this;\n\t\tif(delegateCount < handlers.length){\n\t\t\thandlerQueue.push({ elem: cur, handlers: handlers.slice(delegateCount) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\taddProp: function(name, hook){\n\t\tObject.defineProperty(jQuery.Event.prototype, name, {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\n\t\t\tget: isFunction(hook) ?\n\t\t\t\tfunction(){\n\t\t\t\t\tif(this.originalEvent){\n\t\t\t\t\t\treturn hook(this.originalEvent);\n\t\t\t\t\t}\n\t\t\t\t} :\n\t\t\t\tfunction(){\n\t\t\t\t\tif(this.originalEvent){\n\t\t\t\t\t\treturn this.originalEvent[ name ];\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\tset: function(value){\n\t\t\t\tObject.defineProperty(this, name, {\n\t\t\t\t\tenumerable: true,\n\t\t\t\t\tconfigurable: true,\n\t\t\t\t\twritable: true,\n\t\t\t\t\tvalue: value\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t},\n\n\tfix: function(originalEvent){\n\t\treturn originalEvent[ jQuery.expando ] ?\n\t\t\toriginalEvent :\n\t\t\tnew jQuery.Event(originalEvent);\n\t},\n\n\tspecial: {\n\t\tload: {\n\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tclick: {\n\n\t\t\t// Utilize native event to ensure correct state for checkable inputs\n\t\t\tsetup: function(data){\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `||data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el=this||data;\n\n\t\t\t\t// Claim the first handler\n\t\t\t\tif(rcheckableType.test(el.type)&&\n\t\t\t\t\tel.click&&nodeName(el, "input") ){\n\n\t\t\t\t\t// dataPriv.set(el, "click", ...)\n\t\t\t\t\tleverageNative(el, "click", true);\n\t\t\t\t}\n\n\t\t\t\t// Return false to allow normal processing in the caller\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\ttrigger: function(data){\n\n\t\t\t\t// For mutual compressibility with _default, replace `this` access with a local var.\n\t\t\t\t// `||data` is dead code meant only to preserve the variable through minification.\n\t\t\t\tvar el=this||data;\n\n\t\t\t\t// Force setup before triggering a click\n\t\t\t\tif(rcheckableType.test(el.type)&&\n\t\t\t\t\tel.click&&nodeName(el, "input") ){\n\n\t\t\t\t\tleverageNative(el, "click");\n\t\t\t\t}\n\n\t\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, suppress native .click() on links\n\t\t\t// Also prevent it if we\'re currently inside a leveraged native-event stack\n\t\t\t_default: function(event){\n\t\t\t\tvar target=event.target;\n\t\t\t\treturn rcheckableType.test(target.type)&&\n\t\t\t\t\ttarget.click&&nodeName(target, "input")&&\n\t\t\t\t\tdataPriv.get(target, "click")||\n\t\t\t\t\tnodeName(target, "a");\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function(event){\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn\'t alert if the returnValue field is not set.\n\t\t\t\tif(event.result!==undefined&&event.originalEvent){\n\t\t\t\t\tevent.originalEvent.returnValue=event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Ensure the presence of an event listener that handles manually-triggered\n// synthetic events by interrupting progress until reinvoked in response to\n// *native* events that it fires directly, ensuring that state changes have\n// already occurred before other listeners are invoked.\nfunction leverageNative(el, type, isSetup){\n\n\t// Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add\n\tif(!isSetup){\n\t\tif(dataPriv.get(el, type)===undefined){\n\t\t\tjQuery.event.add(el, type, returnTrue);\n\t\t}\n\t\treturn;\n\t}\n\n\t// Register the controller as a special universal handler for all event namespaces\n\tdataPriv.set(el, type, false);\n\tjQuery.event.add(el, type, {\n\t\tnamespace: false,\n\t\thandler: function(event){\n\t\t\tvar result,\n\t\t\t\tsaved=dataPriv.get(this, type);\n\n\t\t\tif(( event.isTrigger & 1)&&this[ type ]){\n\n\t\t\t\t// Interrupt processing of the outer synthetic .trigger()ed event\n\t\t\t\tif(!saved){\n\n\t\t\t\t\t// Store arguments for use when handling the inner native event\n\t\t\t\t\t// There will always be at least one argument (an event object), so this array\n\t\t\t\t\t// will not be confused with a leftover capture object.\n\t\t\t\t\tsaved=slice.call(arguments);\n\t\t\t\t\tdataPriv.set(this, type, saved);\n\n\t\t\t\t\t// Trigger the native event and capture its result\n\t\t\t\t\tthis[ type ]();\n\t\t\t\t\tresult=dataPriv.get(this, type);\n\t\t\t\t\tdataPriv.set(this, type, false);\n\n\t\t\t\t\tif(saved!==result){\n\n\t\t\t\t\t\t// Cancel the outer synthetic event\n\t\t\t\t\t\tevent.stopImmediatePropagation();\n\t\t\t\t\t\tevent.preventDefault();\n\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\n\t\t\t\t// If this is an inner synthetic event for an event with a bubbling surrogate\n\t\t\t\t// (focus or blur), assume that the surrogate already propagated from triggering\n\t\t\t\t// the native event and prevent that from happening again here.\n\t\t\t\t// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the\n\t\t\t\t// bubbling surrogate propagates *after* the non-bubbling base), but that seems\n\t\t\t\t// less bad than duplication.\n\t\t\t\t}else if(( jQuery.event.special[ type ]||{}).delegateType){\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}\n\n\t\t\t// If this is a native event triggered above, everything is now in order\n\t\t\t// Fire an inner synthetic event with the original arguments\n\t\t\t}else if(saved){\n\n\t\t\t\t// ...and capture the result\n\t\t\t\tdataPriv.set(this, type, jQuery.event.trigger(\n\t\t\t\t\tsaved[ 0 ],\n\t\t\t\t\tsaved.slice(1),\n\t\t\t\t\tthis\n\t\t\t\t));\n\n\t\t\t\t// Abort handling of the native event by all jQuery handlers while allowing\n\t\t\t\t// native handlers on the same element to run. On target, this is achieved\n\t\t\t\t// by stopping immediate propagation just on the jQuery event. However,\n\t\t\t\t// the native event is re-wrapped by a jQuery one on each level of the\n\t\t\t\t// propagation so the only way to stop it for jQuery is to stop it for\n\t\t\t\t// everyone via native `stopPropagation()`. This is not a problem for\n\t\t\t\t// focus/blur which don\'t bubble, but it does also stop click on checkboxes\n\t\t\t\t// and radios. We accept this limitation.\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tevent.isImmediatePropagationStopped=returnTrue;\n\t\t\t}\n\t\t}\n\t});\n}\n\njQuery.removeEvent=function(elem, type, handle){\n\n\t// This "if" is needed for plain objects\n\tif(elem.removeEventListener){\n\t\telem.removeEventListener(type, handle);\n\t}\n};\n\njQuery.Event=function(src, props){\n\n\t// Allow instantiation without the \'new\' keyword\n\tif(!(this instanceof jQuery.Event) ){\n\t\treturn new jQuery.Event(src, props);\n\t}\n\n\t// Event object\n\tif(src&&src.type){\n\t\tthis.originalEvent=src;\n\t\tthis.type=src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented=src.defaultPrevented||\n\t\t\t\tsrc.defaultPrevented===undefined&&\n\n\t\t\t\t// Support: Android <=2.3 only\n\t\t\t\tsrc.returnValue===false ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t\t// Create target properties\n\t\t// Support: Safari <=6 - 7 only\n\t\t// Target should not be a text node (trac-504, trac-13143)\n\t\tthis.target=(src.target&&src.target.nodeType===3) ?\n\t\t\tsrc.target.parentNode :\n\t\t\tsrc.target;\n\n\t\tthis.currentTarget=src.currentTarget;\n\t\tthis.relatedTarget=src.relatedTarget;\n\n\t// Event type\n\t}else{\n\t\tthis.type=src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif(props){\n\t\tjQuery.extend(this, props);\n\t}\n\n\t// Create a timestamp if incoming event doesn\'t have one\n\tthis.timeStamp=src&&src.timeStamp||Date.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ]=true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype={\n\tconstructor: jQuery.Event,\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\tisSimulated: false,\n\n\tpreventDefault: function(){\n\t\tvar e=this.originalEvent;\n\n\t\tthis.isDefaultPrevented=returnTrue;\n\n\t\tif(e&&!this.isSimulated){\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function(){\n\t\tvar e=this.originalEvent;\n\n\t\tthis.isPropagationStopped=returnTrue;\n\n\t\tif(e&&!this.isSimulated){\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function(){\n\t\tvar e=this.originalEvent;\n\n\t\tthis.isImmediatePropagationStopped=returnTrue;\n\n\t\tif(e&&!this.isSimulated){\n\t\t\te.stopImmediatePropagation();\n\t\t}\n\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Includes all common event props including KeyEvent and MouseEvent specific props\njQuery.each({\n\taltKey: true,\n\tbubbles: true,\n\tcancelable: true,\n\tchangedTouches: true,\n\tctrlKey: true,\n\tdetail: true,\n\teventPhase: true,\n\tmetaKey: true,\n\tpageX: true,\n\tpageY: true,\n\tshiftKey: true,\n\tview: true,\n\t"char": true,\n\tcode: true,\n\tcharCode: true,\n\tkey: true,\n\tkeyCode: true,\n\tbutton: true,\n\tbuttons: true,\n\tclientX: true,\n\tclientY: true,\n\toffsetX: true,\n\toffsetY: true,\n\tpointerId: true,\n\tpointerType: true,\n\tscreenX: true,\n\tscreenY: true,\n\ttargetTouches: true,\n\ttoElement: true,\n\ttouches: true,\n\twhich: true\n}, jQuery.event.addProp);\n\njQuery.each({ focus: "focusin", blur: "focusout" }, function(type, delegateType){\n\n\tfunction focusMappedHandler(nativeEvent){\n\t\tif(document.documentMode){\n\n\t\t\t// Support: IE 11+\n\t\t\t// Attach a single focusin/focusout handler on the document while someone wants\n\t\t\t// focus/blur. This is because the former are synchronous in IE while the latter\n\t\t\t// are async. In other browsers, all those handlers are invoked synchronously.\n\n\t\t\t// `handle` from private data would already wrap the event, but we need\n\t\t\t// to change the `type` here.\n\t\t\tvar handle=dataPriv.get(this, "handle"),\n\t\t\t\tevent=jQuery.event.fix(nativeEvent);\n\t\t\tevent.type=nativeEvent.type==="focusin" ? "focus":"blur";\n\t\t\tevent.isSimulated=true;\n\n\t\t\t// First, handle focusin/focusout\n\t\t\thandle(nativeEvent);\n\n\t\t\t// ...then, handle focus/blur\n\t\t\t//\n\t\t\t// focus/blur don\'t bubble while focusin/focusout do; simulate the former by only\n\t\t\t// invoking the handler at the lower level.\n\t\t\tif(event.target===event.currentTarget){\n\n\t\t\t\t// The setup part calls `leverageNative`, which, in turn, calls\n\t\t\t\t// `jQuery.event.add`, so event handle will already have been set\n\t\t\t\t// by this point.\n\t\t\t\thandle(event);\n\t\t\t}\n\t\t}else{\n\n\t\t\t// For non-IE browsers, attach a single capturing handler on the document\n\t\t\t// while someone wants focusin/focusout.\n\t\t\tjQuery.event.simulate(delegateType, nativeEvent.target,\n\t\t\t\tjQuery.event.fix(nativeEvent) );\n\t\t}\n\t}\n\n\tjQuery.event.special[ type ]={\n\n\t\t// Utilize native event if possible so blur/focus sequence is correct\n\t\tsetup: function(){\n\n\t\t\tvar attaches;\n\n\t\t\t// Claim the first handler\n\t\t\t// dataPriv.set(this, "focus", ...)\n\t\t\t// dataPriv.set(this, "blur", ...)\n\t\t\tleverageNative(this, type, true);\n\n\t\t\tif(document.documentMode){\n\n\t\t\t\t// Support: IE 9 - 11+\n\t\t\t\t// We use the same native handler for focusin & focus (and focusout & blur)\n\t\t\t\t// so we need to coordinate setup & teardown parts between those events.\n\t\t\t\t// Use `delegateType` as the key as `type` is already used by `leverageNative`.\n\t\t\t\tattaches=dataPriv.get(this, delegateType);\n\t\t\t\tif(!attaches){\n\t\t\t\t\tthis.addEventListener(delegateType, focusMappedHandler);\n\t\t\t\t}\n\t\t\t\tdataPriv.set(this, delegateType,(attaches||0) + 1);\n\t\t\t}else{\n\n\t\t\t\t// Return false to allow normal processing in the caller\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\ttrigger: function(){\n\n\t\t\t// Force setup before trigger\n\t\t\tleverageNative(this, type);\n\n\t\t\t// Return non-false to allow normal event-path propagation\n\t\t\treturn true;\n\t\t},\n\n\t\tteardown: function(){\n\t\t\tvar attaches;\n\n\t\t\tif(document.documentMode){\n\t\t\t\tattaches=dataPriv.get(this, delegateType) - 1;\n\t\t\t\tif(!attaches){\n\t\t\t\t\tthis.removeEventListener(delegateType, focusMappedHandler);\n\t\t\t\t\tdataPriv.remove(this, delegateType);\n\t\t\t\t}else{\n\t\t\t\t\tdataPriv.set(this, delegateType, attaches);\n\t\t\t\t}\n\t\t\t}else{\n\n\t\t\t\t// Return false to indicate standard teardown should be applied\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\t// Suppress native focus or blur if we\'re currently inside\n\t\t// a leveraged native-event stack\n\t\t_default: function(event){\n\t\t\treturn dataPriv.get(event.target, type);\n\t\t},\n\n\t\tdelegateType: delegateType\n\t};\n\n\t// Support: Firefox <=44\n\t// Firefox doesn\'t have focus(in | out) events\n\t// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787\n\t//\n\t// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1\n\t// focus(in | out) events fire after focus & blur events,\n\t// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order\n\t// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857\n\t//\n\t// Support: IE 9 - 11+\n\t// To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch,\n\t// attach a single handler for both events in IE.\n\tjQuery.event.special[ delegateType ]={\n\t\tsetup: function(){\n\n\t\t\t// Handle: regular nodes (via `this.ownerDocument`), window\n\t\t\t// (via `this.document`) & document (via `this`).\n\t\t\tvar doc=this.ownerDocument||this.document||this,\n\t\t\t\tdataHolder=document.documentMode ? this:doc,\n\t\t\t\tattaches=dataPriv.get(dataHolder, delegateType);\n\n\t\t\t// Support: IE 9 - 11+\n\t\t\t// We use the same native handler for focusin & focus (and focusout & blur)\n\t\t\t// so we need to coordinate setup & teardown parts between those events.\n\t\t\t// Use `delegateType` as the key as `type` is already used by `leverageNative`.\n\t\t\tif(!attaches){\n\t\t\t\tif(document.documentMode){\n\t\t\t\t\tthis.addEventListener(delegateType, focusMappedHandler);\n\t\t\t\t}else{\n\t\t\t\t\tdoc.addEventListener(type, focusMappedHandler, true);\n\t\t\t\t}\n\t\t\t}\n\t\t\tdataPriv.set(dataHolder, delegateType,(attaches||0) + 1);\n\t\t},\n\t\tteardown: function(){\n\t\t\tvar doc=this.ownerDocument||this.document||this,\n\t\t\t\tdataHolder=document.documentMode ? this:doc,\n\t\t\t\tattaches=dataPriv.get(dataHolder, delegateType) - 1;\n\n\t\t\tif(!attaches){\n\t\t\t\tif(document.documentMode){\n\t\t\t\t\tthis.removeEventListener(delegateType, focusMappedHandler);\n\t\t\t\t}else{\n\t\t\t\t\tdoc.removeEventListener(type, focusMappedHandler, true);\n\t\t\t\t}\n\t\t\t\tdataPriv.remove(dataHolder, delegateType);\n\t\t\t}else{\n\t\t\t\tdataPriv.set(dataHolder, delegateType, attaches);\n\t\t\t}\n\t\t}\n\t};\n});\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// so that event delegation works in jQuery.\n// Do the same for pointerenter/pointerleave and pointerover/pointerout\n//\n// Support: Safari 7 only\n// Safari sends mouseenter too often; see:\n// https://bugs.chromium.org/p/chromium/issues/detail?id=470258\n// for the description of the bug (it existed in older Chrome versions as well).\njQuery.each({\n\tmouseenter: "mouseover",\n\tmouseleave: "mouseout",\n\tpointerenter: "pointerover",\n\tpointerleave: "pointerout"\n}, function(orig, fix){\n\tjQuery.event.special[ orig ]={\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function(event){\n\t\t\tvar ret,\n\t\t\t\ttarget=this,\n\t\t\t\trelated=event.relatedTarget,\n\t\t\t\thandleObj=event.handleObj;\n\n\t\t\t// For mouseenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif(!related||(related!==target&&!jQuery.contains(target, related) )){\n\t\t\t\tevent.type=handleObj.origType;\n\t\t\t\tret=handleObj.handler.apply(this, arguments);\n\t\t\t\tevent.type=fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\njQuery.fn.extend({\n\n\ton: function(types, selector, data, fn){\n\t\treturn on(this, types, selector, data, fn);\n\t},\n\tone: function(types, selector, data, fn){\n\t\treturn on(this, types, selector, data, fn, 1);\n\t},\n\toff: function(types, selector, fn){\n\t\tvar handleObj, type;\n\t\tif(types&&types.preventDefault&&types.handleObj){\n\n\t\t\t//(event)  dispatched jQuery.Event\n\t\t\thandleObj=types.handleObj;\n\t\t\tjQuery(types.delegateTarget).off(\n\t\t\t\thandleObj.namespace ?\n\t\t\t\t\thandleObj.origType + "." + handleObj.namespace :\n\t\t\t\t\thandleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif(typeof types==="object"){\n\n\t\t\t//(types-object [, selector])\n\t\t\tfor(type in types){\n\t\t\t\tthis.off(type, selector, types[ type ]);\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif(selector===false||typeof selector==="function"){\n\n\t\t\t//(types [, fn])\n\t\t\tfn=selector;\n\t\t\tselector=undefined;\n\t\t}\n\t\tif(fn===false){\n\t\t\tfn=returnFalse;\n\t\t}\n\t\treturn this.each(function(){\n\t\t\tjQuery.event.remove(this, types, fn, selector);\n\t\t});\n\t}\n});\n\n\nvar\n\n\t// Support: IE <=10 - 11, Edge 12 - 13 only\n\t// In IE/Edge using regex groups here causes severe slowdowns.\n\t// See https://connect.microsoft.com/IE/feedback/details/1736512/\n\trnoInnerhtml=/<script|<style|<link/i,\n\n\t// checked="checked" or checked\n\trchecked=/checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\n\trcleanScript=/^\\s*<!\\[CDATA\\[|\\]\\]>\\s*$/g;\n\n// Prefer a tbody over its parent table for containing new rows\nfunction manipulationTarget(elem, content){\n\tif(nodeName(elem, "table")&&\n\t\tnodeName(content.nodeType!==11 ? content:content.firstChild, "tr") ){\n\n\t\treturn jQuery(elem).children("tbody")[ 0 ]||elem;\n\t}\n\n\treturn elem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript(elem){\n\telem.type=(elem.getAttribute("type")!==null) + "/" + elem.type;\n\treturn elem;\n}\nfunction restoreScript(elem){\n\tif(( elem.type||"").slice(0, 5)==="true/"){\n\t\telem.type=elem.type.slice(5);\n\t}else{\n\t\telem.removeAttribute("type");\n\t}\n\n\treturn elem;\n}\n\nfunction cloneCopyEvent(src, dest){\n\tvar i, l, type, pdataOld, udataOld, udataCur, events;\n\n\tif(dest.nodeType!==1){\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif(dataPriv.hasData(src) ){\n\t\tpdataOld=dataPriv.get(src);\n\t\tevents=pdataOld.events;\n\n\t\tif(events){\n\t\t\tdataPriv.remove(dest, "handle events");\n\n\t\t\tfor(type in events){\n\t\t\t\tfor(i=0, l=events[ type ].length; i < l; i++){\n\t\t\t\t\tjQuery.event.add(dest, type, events[ type ][ i ]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif(dataUser.hasData(src) ){\n\t\tudataOld=dataUser.access(src);\n\t\tudataCur=jQuery.extend({}, udataOld);\n\n\t\tdataUser.set(dest, udataCur);\n\t}\n}\n\n// Fix IE bugs, see support tests\nfunction fixInput(src, dest){\n\tvar nodeName=dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif(nodeName==="input"&&rcheckableType.test(src.type) ){\n\t\tdest.checked=src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t}else if(nodeName==="input"||nodeName==="textarea"){\n\t\tdest.defaultValue=src.defaultValue;\n\t}\n}\n\nfunction domManip(collection, args, callback, ignored){\n\n\t// Flatten any nested arrays\n\targs=flat(args);\n\n\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\ti=0,\n\t\tl=collection.length,\n\t\tiNoClone=l - 1,\n\t\tvalue=args[ 0 ],\n\t\tvalueIsFunction=isFunction(value);\n\n\t// We can\'t cloneNode fragments that contain checked, in WebKit\n\tif(valueIsFunction||\n\t\t\t(l > 1&&typeof value==="string"&&\n\t\t\t\t!support.checkClone&&rchecked.test(value) )){\n\t\treturn collection.each(function(index){\n\t\t\tvar self=collection.eq(index);\n\t\t\tif(valueIsFunction){\n\t\t\t\targs[ 0 ]=value.call(this, index, self.html());\n\t\t\t}\n\t\t\tdomManip(self, args, callback, ignored);\n\t\t});\n\t}\n\n\tif(l){\n\t\tfragment=buildFragment(args, collection[ 0 ].ownerDocument, false, collection, ignored);\n\t\tfirst=fragment.firstChild;\n\n\t\tif(fragment.childNodes.length===1){\n\t\t\tfragment=first;\n\t\t}\n\n\t\t// Require either new content or an interest in ignored elements to invoke the callback\n\t\tif(first||ignored){\n\t\t\tscripts=jQuery.map(getAll(fragment, "script"), disableScript);\n\t\t\thasScripts=scripts.length;\n\n\t\t\t// Use the original fragment for the last item\n\t\t\t// instead of the first because it can end up\n\t\t\t// being emptied incorrectly in certain situations (trac-8070).\n\t\t\tfor(; i < l; i++){\n\t\t\t\tnode=fragment;\n\n\t\t\t\tif(i!==iNoClone){\n\t\t\t\t\tnode=jQuery.clone(node, true, true);\n\n\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\tif(hasScripts){\n\n\t\t\t\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\t\tjQuery.merge(scripts, getAll(node, "script") );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcallback.call(collection[ i ], node, i);\n\t\t\t}\n\n\t\t\tif(hasScripts){\n\t\t\t\tdoc=scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t// Re-enable scripts\n\t\t\t\tjQuery.map(scripts, restoreScript);\n\n\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\tfor(i=0; i < hasScripts; i++){\n\t\t\t\t\tnode=scripts[ i ];\n\t\t\t\t\tif(rscriptType.test(node.type||"")&&\n\t\t\t\t\t\t!dataPriv.access(node, "globalEval")&&\n\t\t\t\t\t\tjQuery.contains(doc, node) ){\n\n\t\t\t\t\t\tif(node.src&&(node.type||"").toLowerCase()!=="module"){\n\n\t\t\t\t\t\t\t// Optional AJAX dependency, but won\'t run scripts if not present\n\t\t\t\t\t\t\tif(jQuery._evalUrl&&!node.noModule){\n\t\t\t\t\t\t\t\tjQuery._evalUrl(node.src, {\n\t\t\t\t\t\t\t\t\tnonce: node.nonce||node.getAttribute("nonce")\n\t\t\t\t\t\t\t\t}, doc);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t// Unwrap a CDATA section containing script contents. This shouldn\'t be\n\t\t\t\t\t\t\t// needed as in XML documents they\'re already not visible when\n\t\t\t\t\t\t\t// inspecting element contents and in HTML documents they have no\n\t\t\t\t\t\t\t// meaning but we\'re preserving that logic for backwards compatibility.\n\t\t\t\t\t\t\t// This will be removed completely in 4.0. See gh-4904.\n\t\t\t\t\t\t\tDOMEval(node.textContent.replace(rcleanScript, ""), node, doc);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn collection;\n}\n\nfunction remove(elem, selector, keepData){\n\tvar node,\n\t\tnodes=selector ? jQuery.filter(selector, elem):elem,\n\t\ti=0;\n\n\tfor(;(node=nodes[ i ])!=null; i++){\n\t\tif(!keepData&&node.nodeType===1){\n\t\t\tjQuery.cleanData(getAll(node) );\n\t\t}\n\n\t\tif(node.parentNode){\n\t\t\tif(keepData&&isAttached(node) ){\n\t\t\t\tsetGlobalEval(getAll(node, "script") );\n\t\t\t}\n\t\t\tnode.parentNode.removeChild(node);\n\t\t}\n\t}\n\n\treturn elem;\n}\n\njQuery.extend({\n\thtmlPrefilter: function(html){\n\t\treturn html;\n\t},\n\n\tclone: function(elem, dataAndEvents, deepDataAndEvents){\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone=elem.cloneNode(true),\n\t\t\tinPage=isAttached(elem);\n\n\t\t// Fix IE cloning issues\n\t\tif(!support.noCloneChecked&&(elem.nodeType===1||elem.nodeType===11)&&\n\t\t\t\t!jQuery.isXMLDoc(elem) ){\n\n\t\t\t// We eschew jQuery#find here for performance reasons:\n\t\t\t// https://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements=getAll(clone);\n\t\t\tsrcElements=getAll(elem);\n\n\t\t\tfor(i=0, l=srcElements.length; i < l; i++){\n\t\t\t\tfixInput(srcElements[ i ], destElements[ i ]);\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif(dataAndEvents){\n\t\t\tif(deepDataAndEvents){\n\t\t\t\tsrcElements=srcElements||getAll(elem);\n\t\t\t\tdestElements=destElements||getAll(clone);\n\n\t\t\t\tfor(i=0, l=srcElements.length; i < l; i++){\n\t\t\t\t\tcloneCopyEvent(srcElements[ i ], destElements[ i ]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tcloneCopyEvent(elem, clone);\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements=getAll(clone, "script");\n\t\tif(destElements.length > 0){\n\t\t\tsetGlobalEval(destElements, !inPage&&getAll(elem, "script") );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tcleanData: function(elems){\n\t\tvar data, elem, type,\n\t\t\tspecial=jQuery.event.special,\n\t\t\ti=0;\n\n\t\tfor(;(elem=elems[ i ])!==undefined; i++){\n\t\t\tif(acceptData(elem) ){\n\t\t\t\tif(( data=elem[ dataPriv.expando ]) ){\n\t\t\t\t\tif(data.events){\n\t\t\t\t\t\tfor(type in data.events){\n\t\t\t\t\t\t\tif(special[ type ]){\n\t\t\t\t\t\t\t\tjQuery.event.remove(elem, type);\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove\'s overhead\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tjQuery.removeEvent(elem, type, data.handle);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataPriv.expando ]=undefined;\n\t\t\t\t}\n\t\t\t\tif(elem[ dataUser.expando ]){\n\n\t\t\t\t\t// Support: Chrome <=35 - 45+\n\t\t\t\t\t// Assign undefined instead of using delete, see Data#remove\n\t\t\t\t\telem[ dataUser.expando ]=undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\njQuery.fn.extend({\n\tdetach: function(selector){\n\t\treturn remove(this, selector, true);\n\t},\n\n\tremove: function(selector){\n\t\treturn remove(this, selector);\n\t},\n\n\ttext: function(value){\n\t\treturn access(this, function(value){\n\t\t\treturn value===undefined ?\n\t\t\t\tjQuery.text(this) :\n\t\t\t\tthis.empty().each(function(){\n\t\t\t\t\tif(this.nodeType===1||this.nodeType===11||this.nodeType===9){\n\t\t\t\t\t\tthis.textContent=value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, null, value, arguments.length);\n\t},\n\n\tappend: function(){\n\t\treturn domManip(this, arguments, function(elem){\n\t\t\tif(this.nodeType===1||this.nodeType===11||this.nodeType===9){\n\t\t\t\tvar target=manipulationTarget(this, elem);\n\t\t\t\ttarget.appendChild(elem);\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function(){\n\t\treturn domManip(this, arguments, function(elem){\n\t\t\tif(this.nodeType===1||this.nodeType===11||this.nodeType===9){\n\t\t\t\tvar target=manipulationTarget(this, elem);\n\t\t\t\ttarget.insertBefore(elem, target.firstChild);\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function(){\n\t\treturn domManip(this, arguments, function(elem){\n\t\t\tif(this.parentNode){\n\t\t\t\tthis.parentNode.insertBefore(elem, this);\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function(){\n\t\treturn domManip(this, arguments, function(elem){\n\t\t\tif(this.parentNode){\n\t\t\t\tthis.parentNode.insertBefore(elem, this.nextSibling);\n\t\t\t}\n\t\t});\n\t},\n\n\tempty: function(){\n\t\tvar elem,\n\t\t\ti=0;\n\n\t\tfor(;(elem=this[ i ])!=null; i++){\n\t\t\tif(elem.nodeType===1){\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData(getAll(elem, false) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent="";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function(dataAndEvents, deepDataAndEvents){\n\t\tdataAndEvents=dataAndEvents==null ? false:dataAndEvents;\n\t\tdeepDataAndEvents=deepDataAndEvents==null ? dataAndEvents:deepDataAndEvents;\n\n\t\treturn this.map(function(){\n\t\t\treturn jQuery.clone(this, dataAndEvents, deepDataAndEvents);\n\t\t});\n\t},\n\n\thtml: function(value){\n\t\treturn access(this, function(value){\n\t\t\tvar elem=this[ 0 ]||{},\n\t\t\t\ti=0,\n\t\t\t\tl=this.length;\n\n\t\t\tif(value===undefined&&elem.nodeType===1){\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif(typeof value==="string"&&!rnoInnerhtml.test(value)&&\n\t\t\t\t!wrapMap[(rtagName.exec(value)||[ "", "" ])[ 1 ].toLowerCase() ]){\n\n\t\t\t\tvalue=jQuery.htmlPrefilter(value);\n\n\t\t\t\ttry {\n\t\t\t\t\tfor(; i < l; i++){\n\t\t\t\t\t\telem=this[ i ]||{};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif(elem.nodeType===1){\n\t\t\t\t\t\t\tjQuery.cleanData(getAll(elem, false) );\n\t\t\t\t\t\t\telem.innerHTML=value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem=0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch(e){}\n\t\t\t}\n\n\t\t\tif(elem){\n\t\t\t\tthis.empty().append(value);\n\t\t\t}\n\t\t}, null, value, arguments.length);\n\t},\n\n\treplaceWith: function(){\n\t\tvar ignored=[];\n\n\t\t// Make the changes, replacing each non-ignored context element with the new content\n\t\treturn domManip(this, arguments, function(elem){\n\t\t\tvar parent=this.parentNode;\n\n\t\t\tif(jQuery.inArray(this, ignored) < 0){\n\t\t\t\tjQuery.cleanData(getAll(this) );\n\t\t\t\tif(parent){\n\t\t\t\t\tparent.replaceChild(elem, this);\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Force callback invocation\n\t\t}, ignored);\n\t}\n});\n\njQuery.each({\n\tappendTo: "append",\n\tprependTo: "prepend",\n\tinsertBefore: "before",\n\tinsertAfter: "after",\n\treplaceAll: "replaceWith"\n}, function(name, original){\n\tjQuery.fn[ name ]=function(selector){\n\t\tvar elems,\n\t\t\tret=[],\n\t\t\tinsert=jQuery(selector),\n\t\t\tlast=insert.length - 1,\n\t\t\ti=0;\n\n\t\tfor(; i <=last; i++){\n\t\t\telems=i===last ? this:this.clone(true);\n\t\t\tjQuery(insert[ i ])[ original ](elems);\n\n\t\t\t// Support: Android <=4.0 only, PhantomJS 1 only\n\t\t\t// .get() because push.apply(_, arraylike) throws on ancient WebKit\n\t\t\tpush.apply(ret, elems.get());\n\t\t}\n\n\t\treturn this.pushStack(ret);\n\t};\n});\nvar rnumnonpx=new RegExp("^(" + pnum + ")(?!px)[a-z%]+$", "i");\n\nvar rcustomProp=/^--/;\n\n\nvar getStyles=function(elem){\n\n\t\t// Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)\n\t\t// IE throws on elements created in popups\n\t\t// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"\n\t\tvar view=elem.ownerDocument.defaultView;\n\n\t\tif(!view||!view.opener){\n\t\t\tview=window;\n\t\t}\n\n\t\treturn view.getComputedStyle(elem);\n\t};\n\nvar swap=function(elem, options, callback){\n\tvar ret, name,\n\t\told={};\n\n\t// Remember the old values, and insert the new ones\n\tfor(name in options){\n\t\told[ name ]=elem.style[ name ];\n\t\telem.style[ name ]=options[ name ];\n\t}\n\n\tret=callback.call(elem);\n\n\t// Revert the old values\n\tfor(name in options){\n\t\telem.style[ name ]=old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar rboxStyle=new RegExp(cssExpand.join("|"), "i");\n\n\n\n(function(){\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they\'re executed at the same time to save the second computation.\n\tfunction computeStyleTests(){\n\n\t\t// This is a singleton, we need to execute it only once\n\t\tif(!div){\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer.style.cssText="position:absolute;left:-11111px;width:60px;" +\n\t\t\t"margin-top:1px;padding:0;border:0";\n\t\tdiv.style.cssText=\n\t\t\t"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +\n\t\t\t"margin:auto;border:1px;padding:1px;" +\n\t\t\t"width:60%;top:1%";\n\t\tdocumentElement.appendChild(container).appendChild(div);\n\n\t\tvar divStyle=window.getComputedStyle(div);\n\t\tpixelPositionVal=divStyle.top!=="1%";\n\n\t\t// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44\n\t\treliableMarginLeftVal=roundPixelMeasures(divStyle.marginLeft)===12;\n\n\t\t// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3\n\t\t// Some styles come back with percentage values, even though they shouldn\'t\n\t\tdiv.style.right="60%";\n\t\tpixelBoxStylesVal=roundPixelMeasures(divStyle.right)===36;\n\n\t\t// Support: IE 9 - 11 only\n\t\t// Detect misreporting of content dimensions for box-sizing:border-box elements\n\t\tboxSizingReliableVal=roundPixelMeasures(divStyle.width)===36;\n\n\t\t// Support: IE 9 only\n\t\t// Detect overflow:scroll screwiness (gh-3699)\n\t\t// Support: Chrome <=64\n\t\t// Don\'t get tricked when zoom affects offsetWidth (gh-4029)\n\t\tdiv.style.position="absolute";\n\t\tscrollboxSizeVal=roundPixelMeasures(div.offsetWidth / 3)===12;\n\n\t\tdocumentElement.removeChild(container);\n\n\t\t// Nullify the div so it wouldn\'t be stored in the memory and\n\t\t// it will also be a sign that checks already performed\n\t\tdiv=null;\n\t}\n\n\tfunction roundPixelMeasures(measure){\n\t\treturn Math.round(parseFloat(measure) );\n\t}\n\n\tvar pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,\n\t\treliableTrDimensionsVal, reliableMarginLeftVal,\n\t\tcontainer=document.createElement("div"),\n\t\tdiv=document.createElement("div");\n\n\t// Finish early in limited (non-browser) environments\n\tif(!div.style){\n\t\treturn;\n\t}\n\n\t// Support: IE <=9 - 11 only\n\t// Style of cloned element affects source element cloned (trac-8908)\n\tdiv.style.backgroundClip="content-box";\n\tdiv.cloneNode(true).style.backgroundClip="";\n\tsupport.clearCloneStyle=div.style.backgroundClip==="content-box";\n\n\tjQuery.extend(support, {\n\t\tboxSizingReliable: function(){\n\t\t\tcomputeStyleTests();\n\t\t\treturn boxSizingReliableVal;\n\t\t},\n\t\tpixelBoxStyles: function(){\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelBoxStylesVal;\n\t\t},\n\t\tpixelPosition: function(){\n\t\t\tcomputeStyleTests();\n\t\t\treturn pixelPositionVal;\n\t\t},\n\t\treliableMarginLeft: function(){\n\t\t\tcomputeStyleTests();\n\t\t\treturn reliableMarginLeftVal;\n\t\t},\n\t\tscrollboxSize: function(){\n\t\t\tcomputeStyleTests();\n\t\t\treturn scrollboxSizeVal;\n\t\t},\n\n\t\t// Support: IE 9 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Behavior in IE 9 is more subtle than in newer versions & it passes\n\t\t// some versions of this test; make sure not to make it pass there!\n\t\t//\n\t\t// Support: Firefox 70+\n\t\t// Only Firefox includes border widths\n\t\t// in computed dimensions. (gh-4529)\n\t\treliableTrDimensions: function(){\n\t\t\tvar table, tr, trChild, trStyle;\n\t\t\tif(reliableTrDimensionsVal==null){\n\t\t\t\ttable=document.createElement("table");\n\t\t\t\ttr=document.createElement("tr");\n\t\t\t\ttrChild=document.createElement("div");\n\n\t\t\t\ttable.style.cssText="position:absolute;left:-11111px;border-collapse:separate";\n\t\t\t\ttr.style.cssText="box-sizing:content-box;border:1px solid";\n\n\t\t\t\t// Support: Chrome 86+\n\t\t\t\t// Height set through cssText does not get applied.\n\t\t\t\t// Computed height then comes back as 0.\n\t\t\t\ttr.style.height="1px";\n\t\t\t\ttrChild.style.height="9px";\n\n\t\t\t\t// Support: Android 8 Chrome 86+\n\t\t\t\t// In our bodyBackground.html iframe,\n\t\t\t\t// display for all div elements is set to "inline",\n\t\t\t\t// which causes a problem only in Android 8 Chrome 86.\n\t\t\t\t// Ensuring the div is `display: block`\n\t\t\t\t// gets around this issue.\n\t\t\t\ttrChild.style.display="block";\n\n\t\t\t\tdocumentElement\n\t\t\t\t\t.appendChild(table)\n\t\t\t\t\t.appendChild(tr)\n\t\t\t\t\t.appendChild(trChild);\n\n\t\t\t\ttrStyle=window.getComputedStyle(tr);\n\t\t\t\treliableTrDimensionsVal=(parseInt(trStyle.height, 10) +\n\t\t\t\t\tparseInt(trStyle.borderTopWidth, 10) +\n\t\t\t\t\tparseInt(trStyle.borderBottomWidth, 10) )===tr.offsetHeight;\n\n\t\t\t\tdocumentElement.removeChild(table);\n\t\t\t}\n\t\t\treturn reliableTrDimensionsVal;\n\t\t}\n\t});\n})();\n\n\nfunction curCSS(elem, name, computed){\n\tvar width, minWidth, maxWidth, ret,\n\t\tisCustomProp=rcustomProp.test(name),\n\n\t\t// Support: Firefox 51+\n\t\t// Retrieving style before computed somehow\n\t\t// fixes an issue with getting wrong values\n\t\t// on detached elements\n\t\tstyle=elem.style;\n\n\tcomputed=computed||getStyles(elem);\n\n\t// getPropertyValue is needed for:\n\t//   .css(\'filter\') (IE 9 only, trac-12537)\n\t//   .css(\'--customProperty) (gh-3144)\n\tif(computed){\n\n\t\t// Support: IE <=9 - 11+\n\t\t// IE only supports `"float"` in `getPropertyValue`; in computed styles\n\t\t// it\'s only available as `"cssFloat"`. We no longer modify properties\n\t\t// sent to `.css()` apart from camelCasing, so we need to check both.\n\t\t// Normally, this would create difference in behavior: if\n\t\t// `getPropertyValue` returns an empty string, the value returned\n\t\t// by `.css()` would be `undefined`. This is usually the case for\n\t\t// disconnected elements. However, in IE even disconnected elements\n\t\t// with no styles return `"none"` for `getPropertyValue("float")`\n\t\tret=computed.getPropertyValue(name)||computed[ name ];\n\n\t\tif(isCustomProp&&ret){\n\n\t\t\t// Support: Firefox 105+, Chrome <=105+\n\t\t\t// Spec requires trimming whitespace for custom properties (gh-4926).\n\t\t\t// Firefox only trims leading whitespace. Chrome just collapses\n\t\t\t// both leading & trailing whitespace to a single space.\n\t\t\t//\n\t\t\t// Fall back to `undefined` if empty string returned.\n\t\t\t// This collapses a missing definition with property defined\n\t\t\t// and set to an empty string but there\'s no standard API\n\t\t\t// allowing us to differentiate them without a performance penalty\n\t\t\t// and returning `undefined` aligns with older jQuery.\n\t\t\t//\n\t\t\t// rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED\n\t\t\t// as whitespace while CSS does not, but this is not a problem\n\t\t\t// because CSS preprocessing replaces them with U+000A LINE FEED\n\t\t\t// (which *is* CSS whitespace)\n\t\t\t// https://www.w3.org/TR/css-syntax-3/#input-preprocessing\n\t\t\tret=ret.replace(rtrimCSS, "$1")||undefined;\n\t\t}\n\n\t\tif(ret===""&&!isAttached(elem) ){\n\t\t\tret=jQuery.style(elem, name);\n\t\t}\n\n\t\t// A tribute to the "awesome hack by Dean Edwards"\n\t\t// Android Browser returns percentage for some values,\n\t\t// but width seems to be reliably pixels.\n\t\t// This is against the CSSOM draft spec:\n\t\t// https://drafts.csswg.org/cssom/#resolved-values\n\t\tif(!support.pixelBoxStyles()&&rnumnonpx.test(ret)&&rboxStyle.test(name) ){\n\n\t\t\t// Remember the original values\n\t\t\twidth=style.width;\n\t\t\tminWidth=style.minWidth;\n\t\t\tmaxWidth=style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth=style.maxWidth=style.width=ret;\n\t\t\tret=computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width=width;\n\t\t\tstyle.minWidth=minWidth;\n\t\t\tstyle.maxWidth=maxWidth;\n\t\t}\n\t}\n\n\treturn ret!==undefined ?\n\n\t\t// Support: IE <=9 - 11 only\n\t\t// IE returns zIndex value as an integer.\n\t\tret + "" :\n\t\tret;\n}\n\n\nfunction addGetHookIf(conditionFn, hookFn){\n\n\t// Define the hook, we\'ll check on the first run if it\'s really needed.\n\treturn {\n\t\tget: function(){\n\t\t\tif(conditionFn()){\n\n\t\t\t\t// Hook not needed (or it\'s not possible to use it due\n\t\t\t\t// to missing dependency), remove it.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\t\t\treturn(this.get=hookFn).apply(this, arguments);\n\t\t}\n\t};\n}\n\n\nvar cssPrefixes=[ "Webkit", "Moz", "ms" ],\n\temptyStyle=document.createElement("div").style,\n\tvendorProps={};\n\n// Return a vendor-prefixed property or undefined\nfunction vendorPropName(name){\n\n\t// Check for vendor prefixed names\n\tvar capName=name[ 0 ].toUpperCase() + name.slice(1),\n\t\ti=cssPrefixes.length;\n\n\twhile(i--){\n\t\tname=cssPrefixes[ i ] + capName;\n\t\tif(name in emptyStyle){\n\t\t\treturn name;\n\t\t}\n\t}\n}\n\n// Return a potentially-mapped jQuery.cssProps or vendor prefixed property\nfunction finalPropName(name){\n\tvar final=jQuery.cssProps[ name ]||vendorProps[ name ];\n\n\tif(final){\n\t\treturn final;\n\t}\n\tif(name in emptyStyle){\n\t\treturn name;\n\t}\n\treturn vendorProps[ name ]=vendorPropName(name)||name;\n}\n\n\nvar\n\n\t// Swappable if display is none or starts with table\n\t// except "table", "table-cell", or "table-caption"\n\t// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap=/^(none|table(?!-c[ea]).+)/,\n\tcssShow={ position: "absolute", visibility: "hidden", display: "block" },\n\tcssNormalTransform={\n\t\tletterSpacing: "0",\n\t\tfontWeight: "400"\n\t};\n\nfunction setPositiveNumber(_elem, value, subtract){\n\n\t// Any relative (+/-) values have already been\n\t// normalized at this point\n\tvar matches=rcssNum.exec(value);\n\treturn matches ?\n\n\t\t// Guard against undefined "subtract", e.g., when used as in cssHooks\n\t\tMath.max(0, matches[ 2 ] -(subtract||0) ) +(matches[ 3 ]||"px") :\n\t\tvalue;\n}\n\nfunction boxModelAdjustment(elem, dimension, box, isBorderBox, styles, computedVal){\n\tvar i=dimension==="width" ? 1:0,\n\t\textra=0,\n\t\tdelta=0,\n\t\tmarginDelta=0;\n\n\t// Adjustment may not be necessary\n\tif(box===(isBorderBox ? "border":"content") ){\n\t\treturn 0;\n\t}\n\n\tfor(; i < 4; i +=2){\n\n\t\t// Both box models exclude margin\n\t\t// Count margin delta separately to only add it after scroll gutter adjustment.\n\t\t// This is needed to make negative margins work with `outerHeight(true)` (gh-3982).\n\t\tif(box==="margin"){\n\t\t\tmarginDelta +=jQuery.css(elem, box + cssExpand[ i ], true, styles);\n\t\t}\n\n\t\t// If we get here with a content-box, we\'re seeking "padding" or "border" or "margin"\n\t\tif(!isBorderBox){\n\n\t\t\t// Add padding\n\t\t\tdelta +=jQuery.css(elem, "padding" + cssExpand[ i ], true, styles);\n\n\t\t\t// For "border" or "margin", add border\n\t\t\tif(box!=="padding"){\n\t\t\t\tdelta +=jQuery.css(elem, "border" + cssExpand[ i ] + "Width", true, styles);\n\n\t\t\t// But still keep track of it otherwise\n\t\t\t}else{\n\t\t\t\textra +=jQuery.css(elem, "border" + cssExpand[ i ] + "Width", true, styles);\n\t\t\t}\n\n\t\t// If we get here with a border-box (content + padding + border), we\'re seeking "content" or\n\t\t// "padding" or "margin"\n\t\t}else{\n\n\t\t\t// For "content", subtract padding\n\t\t\tif(box==="content"){\n\t\t\t\tdelta -=jQuery.css(elem, "padding" + cssExpand[ i ], true, styles);\n\t\t\t}\n\n\t\t\t// For "content" or "padding", subtract border\n\t\t\tif(box!=="margin"){\n\t\t\t\tdelta -=jQuery.css(elem, "border" + cssExpand[ i ] + "Width", true, styles);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Account for positive content-box scroll gutter when requested by providing computedVal\n\tif(!isBorderBox&&computedVal >=0){\n\n\t\t// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border\n\t\t// Assuming integer scroll gutter, subtract the rest and round down\n\t\tdelta +=Math.max(0, Math.ceil(\n\t\t\telem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice(1) ] -\n\t\t\tcomputedVal -\n\t\t\tdelta -\n\t\t\textra -\n\t\t\t0.5\n\n\t\t// If offsetWidth/offsetHeight is unknown, then we can\'t determine content-box scroll gutter\n\t\t// Use an explicit zero to avoid NaN (gh-3964)\n\t\t))||0;\n\t}\n\n\treturn delta + marginDelta;\n}\n\nfunction getWidthOrHeight(elem, dimension, extra){\n\n\t// Start with computed style\n\tvar styles=getStyles(elem),\n\n\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).\n\t\t// Fake content-box until we know it\'s needed to know the true value.\n\t\tboxSizingNeeded = !support.boxSizingReliable()||extra,\n\t\tisBorderBox=boxSizingNeeded&&\n\t\t\tjQuery.css(elem, "boxSizing", false, styles)==="border-box",\n\t\tvalueIsBorderBox=isBorderBox,\n\n\t\tval=curCSS(elem, dimension, styles),\n\t\toffsetProp="offset" + dimension[ 0 ].toUpperCase() + dimension.slice(1);\n\n\t// Support: Firefox <=54\n\t// Return a confounding non-pixel value or feign ignorance, as appropriate.\n\tif(rnumnonpx.test(val) ){\n\t\tif(!extra){\n\t\t\treturn val;\n\t\t}\n\t\tval="auto";\n\t}\n\n\n\t// Support: IE 9 - 11 only\n\t// Use offsetWidth/offsetHeight for when box sizing is unreliable.\n\t// In those cases, the computed value can be trusted to be border-box.\n\tif(( !support.boxSizingReliable()&&isBorderBox||\n\n\t\t// Support: IE 10 - 11+, Edge 15 - 18+\n\t\t// IE/Edge misreport `getComputedStyle` of table rows with width/height\n\t\t// set in CSS while `offset*` properties report correct values.\n\t\t// Interestingly, in some cases IE 9 doesn\'t suffer from this issue.\n\t\t!support.reliableTrDimensions()&&nodeName(elem, "tr")||\n\n\t\t// Fall back to offsetWidth/offsetHeight when value is "auto"\n\t\t// This happens for inline elements with no explicit setting (gh-3571)\n\t\tval==="auto"||\n\n\t\t// Support: Android <=4.1 - 4.3 only\n\t\t// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)\n\t\t!parseFloat(val)&&jQuery.css(elem, "display", false, styles)==="inline")&&\n\n\t\t// Make sure the element is visible & connected\n\t\telem.getClientRects().length){\n\n\t\tisBorderBox=jQuery.css(elem, "boxSizing", false, styles)==="border-box";\n\n\t\t// Where available, offsetWidth/offsetHeight approximate border box dimensions.\n\t\t// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the\n\t\t// retrieved value as a content box dimension.\n\t\tvalueIsBorderBox=offsetProp in elem;\n\t\tif(valueIsBorderBox){\n\t\t\tval=elem[ offsetProp ];\n\t\t}\n\t}\n\n\t// Normalize "" and auto\n\tval=parseFloat(val)||0;\n\n\t// Adjust for the element\'s box model\n\treturn(val +\n\t\tboxModelAdjustment(\n\t\t\telem,\n\t\t\tdimension,\n\t\t\textra||(isBorderBox ? "border":"content"),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles,\n\n\t\t\t// Provide the current computed size to request scroll gutter calculation (gh-3589)\n\t\t\tval\n\t\t)\n\t) + "px";\n}\n\njQuery.extend({\n\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function(elem, computed){\n\t\t\t\tif(computed){\n\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret=curCSS(elem, "opacity");\n\t\t\t\t\treturn ret==="" ? "1":ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don\'t automatically add "px" to these possibly-unitless properties\n\tcssNumber: {\n\t\tanimationIterationCount: true,\n\t\taspectRatio: true,\n\t\tborderImageSlice: true,\n\t\tcolumnCount: true,\n\t\tflexGrow: true,\n\t\tflexShrink: true,\n\t\tfontWeight: true,\n\t\tgridArea: true,\n\t\tgridColumn: true,\n\t\tgridColumnEnd: true,\n\t\tgridColumnStart: true,\n\t\tgridRow: true,\n\t\tgridRowEnd: true,\n\t\tgridRowStart: true,\n\t\tlineHeight: true,\n\t\topacity: true,\n\t\torder: true,\n\t\torphans: true,\n\t\tscale: true,\n\t\twidows: true,\n\t\tzIndex: true,\n\t\tzoom: true,\n\n\t\t// SVG-related\n\t\tfillOpacity: true,\n\t\tfloodOpacity: true,\n\t\tstopOpacity: true,\n\t\tstrokeMiterlimit: true,\n\t\tstrokeOpacity: true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function(elem, name, value, extra){\n\n\t\t// Don\'t set styles on text and comment nodes\n\t\tif(!elem||elem.nodeType===3||elem.nodeType===8||!elem.style){\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we\'re working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName=camelCase(name),\n\t\t\tisCustomProp=rcustomProp.test(name),\n\t\t\tstyle=elem.style;\n\n\t\t// Make sure that we\'re working with the right name. We don\'t\n\t\t// want to query the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif(!isCustomProp){\n\t\t\tname=finalPropName(origName);\n\t\t}\n\n\t\t// Gets hook for the prefixed version, then unprefixed version\n\t\thooks=jQuery.cssHooks[ name ]||jQuery.cssHooks[ origName ];\n\n\t\t// Check if we\'re setting a value\n\t\tif(value!==undefined){\n\t\t\ttype=typeof value;\n\n\t\t\t// Convert "+=" or "-=" to relative numbers (trac-7345)\n\t\t\tif(type==="string"&&(ret=rcssNum.exec(value) )&&ret[ 1 ]){\n\t\t\t\tvalue=adjustCSS(elem, name, ret);\n\n\t\t\t\t// Fixes bug trac-9237\n\t\t\t\ttype="number";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren\'t set (trac-7116)\n\t\t\tif(value==null||value!==value){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add the unit (except for certain CSS properties)\n\t\t\t// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append\n\t\t\t// "px" to a few hardcoded values.\n\t\t\tif(type==="number"&&!isCustomProp){\n\t\t\t\tvalue +=ret&&ret[ 3 ]||(jQuery.cssNumber[ origName ] ? "":"px");\n\t\t\t}\n\n\t\t\t// background-* props affect original clone\'s values\n\t\t\tif(!support.clearCloneStyle&&value===""&&name.indexOf("background")===0){\n\t\t\t\tstyle[ name ]="inherit";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif(!hooks||!("set" in hooks)||\n\t\t\t\t(value=hooks.set(elem, value, extra) )!==undefined){\n\n\t\t\t\tif(isCustomProp){\n\t\t\t\t\tstyle.setProperty(name, value);\n\t\t\t\t}else{\n\t\t\t\t\tstyle[ name ]=value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}else{\n\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif(hooks&&"get" in hooks&&\n\t\t\t\t(ret=hooks.get(elem, false, extra) )!==undefined){\n\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function(elem, name, extra, styles){\n\t\tvar val, num, hooks,\n\t\t\torigName=camelCase(name),\n\t\t\tisCustomProp=rcustomProp.test(name);\n\n\t\t// Make sure that we\'re working with the right name. We don\'t\n\t\t// want to modify the value if it is a CSS custom property\n\t\t// since they are user-defined.\n\t\tif(!isCustomProp){\n\t\t\tname=finalPropName(origName);\n\t\t}\n\n\t\t// Try prefixed name followed by the unprefixed name\n\t\thooks=jQuery.cssHooks[ name ]||jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif(hooks&&"get" in hooks){\n\t\t\tval=hooks.get(elem, true, extra);\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif(val===undefined){\n\t\t\tval=curCSS(elem, name, styles);\n\t\t}\n\n\t\t// Convert "normal" to computed value\n\t\tif(val==="normal"&&name in cssNormalTransform){\n\t\t\tval=cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Make numeric if forced or a qualifier was provided and val looks numeric\n\t\tif(extra===""||extra){\n\t\t\tnum=parseFloat(val);\n\t\t\treturn extra===true||isFinite(num) ? num||0:val;\n\t\t}\n\n\t\treturn val;\n\t}\n});\n\njQuery.each([ "height", "width" ], function(_i, dimension){\n\tjQuery.cssHooks[ dimension ]={\n\t\tget: function(elem, computed, extra){\n\t\t\tif(computed){\n\n\t\t\t\t// Certain elements can have dimension info if we invisibly show them\n\t\t\t\t// but it must have a current display style that would benefit\n\t\t\t\treturn rdisplayswap.test(jQuery.css(elem, "display") )&&\n\n\t\t\t\t\t// Support: Safari 8+\n\t\t\t\t\t// Table columns in Safari have non-zero offsetWidth & zero\n\t\t\t\t\t// getBoundingClientRect().width unless display is changed.\n\t\t\t\t\t// Support: IE <=11 only\n\t\t\t\t\t// Running getBoundingClientRect on a disconnected node\n\t\t\t\t\t// in IE throws an error.\n\t\t\t\t\t(!elem.getClientRects().length||!elem.getBoundingClientRect().width) ?\n\t\t\t\t\tswap(elem, cssShow, function(){\n\t\t\t\t\t\treturn getWidthOrHeight(elem, dimension, extra);\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight(elem, dimension, extra);\n\t\t\t}\n\t\t},\n\n\t\tset: function(elem, value, extra){\n\t\t\tvar matches,\n\t\t\t\tstyles=getStyles(elem),\n\n\t\t\t\t// Only read styles.position if the test has a chance to fail\n\t\t\t\t// to avoid forcing a reflow.\n\t\t\t\tscrollboxSizeBuggy = !support.scrollboxSize()&&\n\t\t\t\t\tstyles.position==="absolute",\n\n\t\t\t\t// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)\n\t\t\t\tboxSizingNeeded=scrollboxSizeBuggy||extra,\n\t\t\t\tisBorderBox=boxSizingNeeded&&\n\t\t\t\t\tjQuery.css(elem, "boxSizing", false, styles)==="border-box",\n\t\t\t\tsubtract=extra ?\n\t\t\t\t\tboxModelAdjustment(\n\t\t\t\t\t\telem,\n\t\t\t\t\t\tdimension,\n\t\t\t\t\t\textra,\n\t\t\t\t\t\tisBorderBox,\n\t\t\t\t\t\tstyles\n\t\t\t\t\t) :\n\t\t\t\t\t0;\n\n\t\t\t// Account for unreliable border-box dimensions by comparing offset* to computed and\n\t\t\t// faking a content-box to get border and padding (gh-3699)\n\t\t\tif(isBorderBox&&scrollboxSizeBuggy){\n\t\t\t\tsubtract -=Math.ceil(\n\t\t\t\t\telem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice(1) ] -\n\t\t\t\t\tparseFloat(styles[ dimension ]) -\n\t\t\t\t\tboxModelAdjustment(elem, dimension, "border", false, styles) -\n\t\t\t\t\t0.5\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Convert to pixels if value adjustment is needed\n\t\t\tif(subtract&&(matches=rcssNum.exec(value) )&&\n\t\t\t\t(matches[ 3 ]||"px")!=="px"){\n\n\t\t\t\telem.style[ dimension ]=value;\n\t\t\t\tvalue=jQuery.css(elem, dimension);\n\t\t\t}\n\n\t\t\treturn setPositiveNumber(elem, value, subtract);\n\t\t}\n\t};\n});\n\njQuery.cssHooks.marginLeft=addGetHookIf(support.reliableMarginLeft,\n\tfunction(elem, computed){\n\t\tif(computed){\n\t\t\treturn(parseFloat(curCSS(elem, "marginLeft") )||\n\t\t\t\telem.getBoundingClientRect().left -\n\t\t\t\t\tswap(elem, { marginLeft: 0 }, function(){\n\t\t\t\t\t\treturn elem.getBoundingClientRect().left;\n\t\t\t\t\t})\n\t\t\t) + "px";\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: "",\n\tpadding: "",\n\tborder: "Width"\n}, function(prefix, suffix){\n\tjQuery.cssHooks[ prefix + suffix ]={\n\t\texpand: function(value){\n\t\t\tvar i=0,\n\t\t\t\texpanded={},\n\n\t\t\t\t// Assumes a single number if not a string\n\t\t\t\tparts=typeof value==="string" ? value.split(" "):[ value ];\n\n\t\t\tfor(; i < 4; i++){\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ]=\n\t\t\t\t\tparts[ i ]||parts[ i - 2 ]||parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif(prefix!=="margin"){\n\t\tjQuery.cssHooks[ prefix + suffix ].set=setPositiveNumber;\n\t}\n});\n\njQuery.fn.extend({\n\tcss: function(name, value){\n\t\treturn access(this, function(elem, name, value){\n\t\t\tvar styles, len,\n\t\t\t\tmap={},\n\t\t\t\ti=0;\n\n\t\t\tif(Array.isArray(name) ){\n\t\t\t\tstyles=getStyles(elem);\n\t\t\t\tlen=name.length;\n\n\t\t\t\tfor(; i < len; i++){\n\t\t\t\t\tmap[ name[ i ] ]=jQuery.css(elem, name[ i ], false, styles);\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value!==undefined ?\n\t\t\t\tjQuery.style(elem, name, value) :\n\t\t\t\tjQuery.css(elem, name);\n\t\t}, name, value, arguments.length > 1);\n\t}\n});\n\n\nfunction Tween(elem, options, prop, end, easing){\n\treturn new Tween.prototype.init(elem, options, prop, end, easing);\n}\njQuery.Tween=Tween;\n\nTween.prototype={\n\tconstructor: Tween,\n\tinit: function(elem, options, prop, end, easing, unit){\n\t\tthis.elem=elem;\n\t\tthis.prop=prop;\n\t\tthis.easing=easing||jQuery.easing._default;\n\t\tthis.options=options;\n\t\tthis.start=this.now=this.cur();\n\t\tthis.end=end;\n\t\tthis.unit=unit||(jQuery.cssNumber[ prop ] ? "":"px");\n\t},\n\tcur: function(){\n\t\tvar hooks=Tween.propHooks[ this.prop ];\n\n\t\treturn hooks&&hooks.get ?\n\t\t\thooks.get(this) :\n\t\t\tTween.propHooks._default.get(this);\n\t},\n\trun: function(percent){\n\t\tvar eased,\n\t\t\thooks=Tween.propHooks[ this.prop ];\n\n\t\tif(this.options.duration){\n\t\t\tthis.pos=eased=jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t}else{\n\t\t\tthis.pos=eased=percent;\n\t\t}\n\t\tthis.now=(this.end - this.start) * eased + this.start;\n\n\t\tif(this.options.step){\n\t\t\tthis.options.step.call(this.elem, this.now, this);\n\t\t}\n\n\t\tif(hooks&&hooks.set){\n\t\t\thooks.set(this);\n\t\t}else{\n\t\t\tTween.propHooks._default.set(this);\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype=Tween.prototype;\n\nTween.propHooks={\n\t_default: {\n\t\tget: function(tween){\n\t\t\tvar result;\n\n\t\t\t// Use a property on the element directly when it is not a DOM element,\n\t\t\t// or when there is no matching style property that exists.\n\t\t\tif(tween.elem.nodeType!==1||\n\t\t\t\ttween.elem[ tween.prop ]!=null&&tween.elem.style[ tween.prop ]==null){\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// Passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails.\n\t\t\t// Simple values such as "10px" are parsed to Float;\n\t\t\t// complex values such as "rotate(1rad)" are returned as-is.\n\t\t\tresult=jQuery.css(tween.elem, tween.prop, "");\n\n\t\t\t// Empty strings, null, undefined and "auto" are converted to 0.\n\t\t\treturn !result||result==="auto" ? 0:result;\n\t\t},\n\t\tset: function(tween){\n\n\t\t\t// Use step hook for back compat.\n\t\t\t// Use cssHook if its there.\n\t\t\t// Use .style if available and use plain properties where available.\n\t\t\tif(jQuery.fx.step[ tween.prop ]){\n\t\t\t\tjQuery.fx.step[ tween.prop ](tween);\n\t\t\t}else if(tween.elem.nodeType===1&&(\n\t\t\t\tjQuery.cssHooks[ tween.prop ]||\n\t\t\t\t\ttween.elem.style[ finalPropName(tween.prop) ]!=null) ){\n\t\t\t\tjQuery.style(tween.elem, tween.prop, tween.now + tween.unit);\n\t\t\t}else{\n\t\t\t\ttween.elem[ tween.prop ]=tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE <=9 only\n// Panic based approach to setting things on disconnected nodes\nTween.propHooks.scrollTop=Tween.propHooks.scrollLeft={\n\tset: function(tween){\n\t\tif(tween.elem.nodeType&&tween.elem.parentNode){\n\t\t\ttween.elem[ tween.prop ]=tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing={\n\tlinear: function(p){\n\t\treturn p;\n\t},\n\tswing: function(p){\n\t\treturn 0.5 - Math.cos(p * Math.PI) / 2;\n\t},\n\t_default: "swing"\n};\n\njQuery.fx=Tween.prototype.init;\n\n// Back compat <1.8 extension point\njQuery.fx.step={};\n\n\n\n\nvar\n\tfxNow, inProgress,\n\trfxtypes=/^(?:toggle|show|hide)$/,\n\trrun=/queueHooks$/;\n\nfunction schedule(){\n\tif(inProgress){\n\t\tif(document.hidden===false&&window.requestAnimationFrame){\n\t\t\twindow.requestAnimationFrame(schedule);\n\t\t}else{\n\t\t\twindow.setTimeout(schedule, jQuery.fx.interval);\n\t\t}\n\n\t\tjQuery.fx.tick();\n\t}\n}\n\n// Animations created synchronously will run synchronously\nfunction createFxNow(){\n\twindow.setTimeout(function(){\n\t\tfxNow=undefined;\n\t});\n\treturn(fxNow=Date.now());\n}\n\n// Generate parameters to create a standard animation\nfunction genFx(type, includeWidth){\n\tvar which,\n\t\ti=0,\n\t\tattrs={ height: type };\n\n\t// If we include width, step value is 1 to do all cssExpand values,\n\t// otherwise step value is 2 to skip over Left and Right\n\tincludeWidth=includeWidth ? 1:0;\n\tfor(; i < 4; i +=2 - includeWidth){\n\t\twhich=cssExpand[ i ];\n\t\tattrs[ "margin" + which ]=attrs[ "padding" + which ]=type;\n\t}\n\n\tif(includeWidth){\n\t\tattrs.opacity=attrs.width=type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween(value, prop, animation){\n\tvar tween,\n\t\tcollection=(Animation.tweeners[ prop ]||[]).concat(Animation.tweeners[ "*" ]),\n\t\tindex=0,\n\t\tlength=collection.length;\n\tfor(; index < length; index++){\n\t\tif(( tween=collection[ index ].call(animation, prop, value) )){\n\n\t\t\t// We\'re done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter(elem, props, opts){\n\tvar prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,\n\t\tisBox="width" in props||"height" in props,\n\t\tanim=this,\n\t\torig={},\n\t\tstyle=elem.style,\n\t\thidden=elem.nodeType&&isHiddenWithinTree(elem),\n\t\tdataShow=dataPriv.get(elem, "fxshow");\n\n\t// Queue-skipping animations hijack the fx hooks\n\tif(!opts.queue){\n\t\thooks=jQuery._queueHooks(elem, "fx");\n\t\tif(hooks.unqueued==null){\n\t\t\thooks.unqueued=0;\n\t\t\toldfire=hooks.empty.fire;\n\t\t\thooks.empty.fire=function(){\n\t\t\t\tif(!hooks.unqueued){\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function(){\n\n\t\t\t// Ensure the complete handler is called before this completes\n\t\t\tanim.always(function(){\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif(!jQuery.queue(elem, "fx").length){\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// Detect show/hide animations\n\tfor(prop in props){\n\t\tvalue=props[ prop ];\n\t\tif(rfxtypes.test(value) ){\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle=toggle||value==="toggle";\n\t\t\tif(value===(hidden ? "hide":"show") ){\n\n\t\t\t\t// Pretend to be hidden if this is a "show" and\n\t\t\t\t// there is still data from a stopped show/hide\n\t\t\t\tif(value==="show"&&dataShow&&dataShow[ prop ]!==undefined){\n\t\t\t\t\thidden=true;\n\n\t\t\t\t// Ignore all other no-op show/hide data\n\t\t\t\t}else{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ]=dataShow&&dataShow[ prop ]||jQuery.style(elem, prop);\n\t\t}\n\t}\n\n\t// Bail out if this is a no-op like .hide().hide()\n\tpropTween = !jQuery.isEmptyObject(props);\n\tif(!propTween&&jQuery.isEmptyObject(orig) ){\n\t\treturn;\n\t}\n\n\t// Restrict "overflow" and "display" styles during box animations\n\tif(isBox&&elem.nodeType===1){\n\n\t\t// Support: IE <=9 - 11, Edge 12 - 15\n\t\t// Record all 3 overflow attributes because IE does not infer the shorthand\n\t\t// from identically-valued overflowX and overflowY and Edge just mirrors\n\t\t// the overflowX value there.\n\t\topts.overflow=[ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Identify a display type, preferring old show/hide data over the CSS cascade\n\t\trestoreDisplay=dataShow&&dataShow.display;\n\t\tif(restoreDisplay==null){\n\t\t\trestoreDisplay=dataPriv.get(elem, "display");\n\t\t}\n\t\tdisplay=jQuery.css(elem, "display");\n\t\tif(display==="none"){\n\t\t\tif(restoreDisplay){\n\t\t\t\tdisplay=restoreDisplay;\n\t\t\t}else{\n\n\t\t\t\t// Get nonempty value(s) by temporarily forcing visibility\n\t\t\t\tshowHide([ elem ], true);\n\t\t\t\trestoreDisplay=elem.style.display||restoreDisplay;\n\t\t\t\tdisplay=jQuery.css(elem, "display");\n\t\t\t\tshowHide([ elem ]);\n\t\t\t}\n\t\t}\n\n\t\t// Animate inline elements as inline-block\n\t\tif(display==="inline"||display==="inline-block"&&restoreDisplay!=null){\n\t\t\tif(jQuery.css(elem, "float")==="none"){\n\n\t\t\t\t// Restore the original display value at the end of pure show/hide animations\n\t\t\t\tif(!propTween){\n\t\t\t\t\tanim.done(function(){\n\t\t\t\t\t\tstyle.display=restoreDisplay;\n\t\t\t\t\t});\n\t\t\t\t\tif(restoreDisplay==null){\n\t\t\t\t\t\tdisplay=style.display;\n\t\t\t\t\t\trestoreDisplay=display==="none" ? "":display;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstyle.display="inline-block";\n\t\t\t}\n\t\t}\n\t}\n\n\tif(opts.overflow){\n\t\tstyle.overflow="hidden";\n\t\tanim.always(function(){\n\t\t\tstyle.overflow=opts.overflow[ 0 ];\n\t\t\tstyle.overflowX=opts.overflow[ 1 ];\n\t\t\tstyle.overflowY=opts.overflow[ 2 ];\n\t\t});\n\t}\n\n\t// Implement show/hide animations\n\tpropTween=false;\n\tfor(prop in orig){\n\n\t\t// General show/hide setup for this element animation\n\t\tif(!propTween){\n\t\t\tif(dataShow){\n\t\t\t\tif("hidden" in dataShow){\n\t\t\t\t\thidden=dataShow.hidden;\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tdataShow=dataPriv.access(elem, "fxshow", { display: restoreDisplay });\n\t\t\t}\n\n\t\t\t// Store hidden/visible for toggle so `.stop().toggle()` "reverses"\n\t\t\tif(toggle){\n\t\t\t\tdataShow.hidden = !hidden;\n\t\t\t}\n\n\t\t\t// Show elements before animating them\n\t\t\tif(hidden){\n\t\t\t\tshowHide([ elem ], true);\n\t\t\t}\n\n\t\t\t\n\n\t\t\tanim.done(function(){\n\n\t\t\t\t\n\n\t\t\t\t// The final step of a "hide" animation is actually hiding the element\n\t\t\t\tif(!hidden){\n\t\t\t\t\tshowHide([ elem ]);\n\t\t\t\t}\n\t\t\t\tdataPriv.remove(elem, "fxshow");\n\t\t\t\tfor(prop in orig){\n\t\t\t\t\tjQuery.style(elem, prop, orig[ prop ]);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// Per-property setup\n\t\tpropTween=createTween(hidden ? dataShow[ prop ]:0, prop, anim);\n\t\tif(!(prop in dataShow) ){\n\t\t\tdataShow[ prop ]=propTween.start;\n\t\t\tif(hidden){\n\t\t\t\tpropTween.end=propTween.start;\n\t\t\t\tpropTween.start=0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction propFilter(props, specialEasing){\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor(index in props){\n\t\tname=camelCase(index);\n\t\teasing=specialEasing[ name ];\n\t\tvalue=props[ index ];\n\t\tif(Array.isArray(value) ){\n\t\t\teasing=value[ 1 ];\n\t\t\tvalue=props[ index ]=value[ 0 ];\n\t\t}\n\n\t\tif(index!==name){\n\t\t\tprops[ name ]=value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks=jQuery.cssHooks[ name ];\n\t\tif(hooks&&"expand" in hooks){\n\t\t\tvalue=hooks.expand (value);\n\t\t\tdelete props[ name ];\n\n\t\t\t// Not quite $.extend, this won\'t overwrite existing keys.\n\t\t\t// Reusing \'index\' because we have the correct "name"\n\t\t\tfor(index in value){\n\t\t\t\tif(!(index in props) ){\n\t\t\t\t\tprops[ index ]=value[ index ];\n\t\t\t\t\tspecialEasing[ index ]=easing;\n\t\t\t\t}\n\t\t\t}\n\t\t}else{\n\t\t\tspecialEasing[ name ]=easing;\n\t\t}\n\t}\n}\n\nfunction Animation(elem, properties, options){\n\tvar result,\n\t\tstopped,\n\t\tindex=0,\n\t\tlength=Animation.prefilters.length,\n\t\tdeferred=jQuery.Deferred().always(function(){\n\n\t\t\t// Don\'t match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick=function(){\n\t\t\tif(stopped){\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime=fxNow||createFxNow(),\n\t\t\t\tremaining=Math.max(0, animation.startTime + animation.duration - currentTime),\n\n\t\t\t\t// Support: Android 2.3 only\n\t\t\t\t// Archaic crash bug won\'t allow us to use `1 -(0.5||0)` (trac-12497)\n\t\t\t\ttemp=remaining / animation.duration||0,\n\t\t\t\tpercent=1 - temp,\n\t\t\t\tindex=0,\n\t\t\t\tlength=animation.tweens.length;\n\n\t\t\tfor(; index < length; index++){\n\t\t\t\tanimation.tweens[ index ].run(percent);\n\t\t\t}\n\n\t\t\tdeferred.notifyWith(elem, [ animation, percent, remaining ]);\n\n\t\t\t// If there\'s more to do, yield\n\t\t\tif(percent < 1&&length){\n\t\t\t\treturn remaining;\n\t\t\t}\n\n\t\t\t// If this was an empty animation, synthesize a final progress notification\n\t\t\tif(!length){\n\t\t\t\tdeferred.notifyWith(elem, [ animation, 1, 0 ]);\n\t\t\t}\n\n\t\t\t// Resolve the animation and report its conclusion\n\t\t\tdeferred.resolveWith(elem, [ animation ]);\n\t\t\treturn false;\n\t\t},\n\t\tanimation=deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend({}, properties),\n\t\t\topts: jQuery.extend(true, {\n\t\t\t\tspecialEasing: {},\n\t\t\t\teasing: jQuery.easing._default\n\t\t\t}, options),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow||createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function(prop, end){\n\t\t\t\tvar tween=jQuery.Tween(elem, animation.opts, prop, end,\n\t\t\t\t\tanimation.opts.specialEasing[ prop ]||animation.opts.easing);\n\t\t\t\tanimation.tweens.push(tween);\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function(gotoEnd){\n\t\t\t\tvar index=0,\n\n\t\t\t\t\t// If we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength=gotoEnd ? animation.tweens.length:0;\n\t\t\t\tif(stopped){\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped=true;\n\t\t\t\tfor(; index < length; index++){\n\t\t\t\t\tanimation.tweens[ index ].run(1);\n\t\t\t\t}\n\n\t\t\t\t// Resolve when we played the last frame; otherwise, reject\n\t\t\t\tif(gotoEnd){\n\t\t\t\t\tdeferred.notifyWith(elem, [ animation, 1, 0 ]);\n\t\t\t\t\tdeferred.resolveWith(elem, [ animation, gotoEnd ]);\n\t\t\t\t}else{\n\t\t\t\t\tdeferred.rejectWith(elem, [ animation, gotoEnd ]);\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops=animation.props;\n\n\tpropFilter(props, animation.opts.specialEasing);\n\n\tfor(; index < length; index++){\n\t\tresult=Animation.prefilters[ index ].call(animation, elem, props, animation.opts);\n\t\tif(result){\n\t\t\tif(isFunction(result.stop) ){\n\t\t\t\tjQuery._queueHooks(animation.elem, animation.opts.queue).stop=\n\t\t\t\t\tresult.stop.bind(result);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map(props, createTween, animation);\n\n\tif(isFunction(animation.opts.start) ){\n\t\tanimation.opts.start.call(elem, animation);\n\t}\n\n\t// Attach callbacks from options\n\tanimation\n\t\t.progress(animation.opts.progress)\n\t\t.done(animation.opts.done, animation.opts.complete)\n\t\t.fail(animation.opts.fail)\n\t\t.always(animation.opts.always);\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend(tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\treturn animation;\n}\n\njQuery.Animation=jQuery.extend(Animation, {\n\n\ttweeners: {\n\t\t"*": [ function(prop, value){\n\t\t\tvar tween=this.createTween(prop, value);\n\t\t\tadjustCSS(tween.elem, prop, rcssNum.exec(value), tween);\n\t\t\treturn tween;\n\t\t} ]\n\t},\n\n\ttweener: function(props, callback){\n\t\tif(isFunction(props) ){\n\t\t\tcallback=props;\n\t\t\tprops=[ "*" ];\n\t\t}else{\n\t\t\tprops=props.match(rnothtmlwhite);\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex=0,\n\t\t\tlength=props.length;\n\n\t\tfor(; index < length; index++){\n\t\t\tprop=props[ index ];\n\t\t\tAnimation.tweeners[ prop ]=Animation.tweeners[ prop ]||[];\n\t\t\tAnimation.tweeners[ prop ].unshift(callback);\n\t\t}\n\t},\n\n\tprefilters: [ defaultPrefilter ],\n\n\tprefilter: function(callback, prepend){\n\t\tif(prepend){\n\t\t\tAnimation.prefilters.unshift(callback);\n\t\t}else{\n\t\t\tAnimation.prefilters.push(callback);\n\t\t}\n\t}\n});\n\njQuery.speed=function(speed, easing, fn){\n\tvar opt=speed&&typeof speed==="object" ? jQuery.extend({}, speed):{\n\t\tcomplete: fn||!fn&&easing||\n\t\t\tisFunction(speed)&&speed,\n\t\tduration: speed,\n\t\teasing: fn&&easing||easing&&!isFunction(easing)&&easing\n\t};\n\n\t// Go to the end state if fx are off\n\tif(jQuery.fx.off){\n\t\topt.duration=0;\n\n\t}else{\n\t\tif(typeof opt.duration!=="number"){\n\t\t\tif(opt.duration in jQuery.fx.speeds){\n\t\t\t\topt.duration=jQuery.fx.speeds[ opt.duration ];\n\n\t\t\t}else{\n\t\t\t\topt.duration=jQuery.fx.speeds._default;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Normalize opt.queue - true/undefined/null -> "fx"\n\tif(opt.queue==null||opt.queue===true){\n\t\topt.queue="fx";\n\t}\n\n\t// Queueing\n\topt.old=opt.complete;\n\n\topt.complete=function(){\n\t\tif(isFunction(opt.old) ){\n\t\t\topt.old.call(this);\n\t\t}\n\n\t\tif(opt.queue){\n\t\t\tjQuery.dequeue(this, opt.queue);\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend({\n\tfadeTo: function(speed, to, easing, callback){\n\n\t\t// Show any hidden elements after setting opacity to 0\n\t\treturn this.filter(isHiddenWithinTree).css("opacity", 0).show()\n\n\t\t\t// Animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback);\n\t},\n\tanimate: function(prop, speed, easing, callback){\n\t\tvar empty=jQuery.isEmptyObject(prop),\n\t\t\toptall=jQuery.speed(speed, easing, callback),\n\t\t\tdoAnimation=function(){\n\n\t\t\t\t// Operate on a copy of prop so per-property easing won\'t be lost\n\t\t\t\tvar anim=Animation(this, jQuery.extend({}, prop), optall);\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif(empty||dataPriv.get(this, "finish") ){\n\t\t\t\t\tanim.stop(true);\n\t\t\t\t}\n\t\t\t};\n\n\t\tdoAnimation.finish=doAnimation;\n\n\t\treturn empty||optall.queue===false ?\n\t\t\tthis.each(doAnimation) :\n\t\t\tthis.queue(optall.queue, doAnimation);\n\t},\n\tstop: function(type, clearQueue, gotoEnd){\n\t\tvar stopQueue=function(hooks){\n\t\t\tvar stop=hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop(gotoEnd);\n\t\t};\n\n\t\tif(typeof type!=="string"){\n\t\t\tgotoEnd=clearQueue;\n\t\t\tclearQueue=type;\n\t\t\ttype=undefined;\n\t\t}\n\t\tif(clearQueue){\n\t\t\tthis.queue(type||"fx", []);\n\t\t}\n\n\t\treturn this.each(function(){\n\t\t\tvar dequeue=true,\n\t\t\t\tindex=type!=null&&type + "queueHooks",\n\t\t\t\ttimers=jQuery.timers,\n\t\t\t\tdata=dataPriv.get(this);\n\n\t\t\tif(index){\n\t\t\t\tif(data[ index ]&&data[ index ].stop){\n\t\t\t\t\tstopQueue(data[ index ]);\n\t\t\t\t}\n\t\t\t}else{\n\t\t\t\tfor(index in data){\n\t\t\t\t\tif(data[ index ]&&data[ index ].stop&&rrun.test(index) ){\n\t\t\t\t\t\tstopQueue(data[ index ]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor(index=timers.length; index--;){\n\t\t\t\tif(timers[ index ].elem===this&&\n\t\t\t\t\t(type==null||timers[ index ].queue===type) ){\n\n\t\t\t\t\ttimers[ index ].anim.stop(gotoEnd);\n\t\t\t\t\tdequeue=false;\n\t\t\t\t\ttimers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start the next in the queue if the last step wasn\'t forced.\n\t\t\t// Timers currently will call their complete callbacks, which\n\t\t\t// will dequeue but only if they were gotoEnd.\n\t\t\tif(dequeue||!gotoEnd){\n\t\t\t\tjQuery.dequeue(this, type);\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function(type){\n\t\tif(type!==false){\n\t\t\ttype=type||"fx";\n\t\t}\n\t\treturn this.each(function(){\n\t\t\tvar index,\n\t\t\t\tdata=dataPriv.get(this),\n\t\t\t\tqueue=data[ type + "queue" ],\n\t\t\t\thooks=data[ type + "queueHooks" ],\n\t\t\t\ttimers=jQuery.timers,\n\t\t\t\tlength=queue ? queue.length:0;\n\n\t\t\t// Enable finishing flag on private data\n\t\t\tdata.finish=true;\n\n\t\t\t// Empty the queue first\n\t\t\tjQuery.queue(this, type, []);\n\n\t\t\tif(hooks&&hooks.stop){\n\t\t\t\thooks.stop.call(this, true);\n\t\t\t}\n\n\t\t\t// Look for any active animations, and finish them\n\t\t\tfor(index=timers.length; index--;){\n\t\t\t\tif(timers[ index ].elem===this&&timers[ index ].queue===type){\n\t\t\t\t\ttimers[ index ].anim.stop(true);\n\t\t\t\t\ttimers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Look for any animations in the old queue and finish them\n\t\t\tfor(index=0; index < length; index++){\n\t\t\t\tif(queue[ index ]&&queue[ index ].finish){\n\t\t\t\t\tqueue[ index ].finish.call(this);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\njQuery.each([ "toggle", "show", "hide" ], function(_i, name){\n\tvar cssFn=jQuery.fn[ name ];\n\tjQuery.fn[ name ]=function(speed, easing, callback){\n\t\treturn speed==null||typeof speed==="boolean" ?\n\t\t\tcssFn.apply(this, arguments) :\n\t\t\tthis.animate(genFx(name, true), speed, easing, callback);\n\t};\n});\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx("show"),\n\tslideUp: genFx("hide"),\n\tslideToggle: genFx("toggle"),\n\tfadeIn: { opacity: "show" },\n\tfadeOut: { opacity: "hide" },\n\tfadeToggle: { opacity: "toggle" }\n}, function(name, props){\n\tjQuery.fn[ name ]=function(speed, easing, callback){\n\t\treturn this.animate(props, speed, easing, callback);\n\t};\n});\n\njQuery.timers=[];\njQuery.fx.tick=function(){\n\tvar timer,\n\t\ti=0,\n\t\ttimers=jQuery.timers;\n\n\tfxNow=Date.now();\n\n\tfor(; i < timers.length; i++){\n\t\ttimer=timers[ i ];\n\n\t\t// Run the timer and safely remove it when done (allowing for external removal)\n\t\tif(!timer()&&timers[ i ]===timer){\n\t\t\ttimers.splice(i--, 1);\n\t\t}\n\t}\n\n\tif(!timers.length){\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow=undefined;\n};\n\njQuery.fx.timer=function(timer){\n\tjQuery.timers.push(timer);\n\tjQuery.fx.start();\n};\n\njQuery.fx.interval=13;\njQuery.fx.start=function(){\n\tif(inProgress){\n\t\treturn;\n\t}\n\n\tinProgress=true;\n\tschedule();\n};\n\njQuery.fx.stop=function(){\n\tinProgress=null;\n};\n\njQuery.fx.speeds={\n\tslow: 600,\n\tfast: 200,\n\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\njQuery.fn.delay=function(time, type){\n\ttime=jQuery.fx ? jQuery.fx.speeds[ time ]||time:time;\n\ttype=type||"fx";\n\n\treturn this.queue(type, function(next, hooks){\n\t\tvar timeout=window.setTimeout(next, time);\n\t\thooks.stop=function(){\n\t\t\twindow.clearTimeout(timeout);\n\t\t};\n\t});\n};\n\n\n(function(){\n\tvar input=document.createElement("input"),\n\t\tselect=document.createElement("select"),\n\t\topt=select.appendChild(document.createElement("option") );\n\n\tinput.type="checkbox";\n\n\t// Support: Android <=4.3 only\n\t// Default value for a checkbox should be "on"\n\tsupport.checkOn=input.value!=="";\n\n\t// Support: IE <=11 only\n\t// Must access selectedIndex to make default options select\n\tsupport.optSelected=opt.selected;\n\n\t// Support: IE <=11 only\n\t// An input loses its value after becoming a radio\n\tinput=document.createElement("input");\n\tinput.value="t";\n\tinput.type="radio";\n\tsupport.radioValue=input.value==="t";\n})();\n\n\nvar boolHook,\n\tattrHandle=jQuery.expr.attrHandle;\n\njQuery.fn.extend({\n\tattr: function(name, value){\n\t\treturn access(this, jQuery.attr, name, value, arguments.length > 1);\n\t},\n\n\tremoveAttr: function(name){\n\t\treturn this.each(function(){\n\t\t\tjQuery.removeAttr(this, name);\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tattr: function(elem, name, value){\n\t\tvar ret, hooks,\n\t\t\tnType=elem.nodeType;\n\n\t\t// Don\'t get/set attributes on text, comment and attribute nodes\n\t\tif(nType===3||nType===8||nType===2){\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif(typeof elem.getAttribute==="undefined"){\n\t\t\treturn jQuery.prop(elem, name, value);\n\t\t}\n\n\t\t// Attribute hooks are determined by the lowercase version\n\t\t// Grab necessary hook if one is defined\n\t\tif(nType!==1||!jQuery.isXMLDoc(elem) ){\n\t\t\thooks=jQuery.attrHooks[ name.toLowerCase() ]||\n\t\t\t\t(jQuery.expr.match.bool.test(name) ? boolHook:undefined);\n\t\t}\n\n\t\tif(value!==undefined){\n\t\t\tif(value===null){\n\t\t\t\tjQuery.removeAttr(elem, name);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif(hooks&&"set" in hooks&&\n\t\t\t\t(ret=hooks.set(elem, value, name) )!==undefined){\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\telem.setAttribute(name, value + "");\n\t\t\treturn value;\n\t\t}\n\n\t\tif(hooks&&"get" in hooks&&(ret=hooks.get(elem, name) )!==null){\n\t\t\treturn ret;\n\t\t}\n\n\t\tret=jQuery.find.attr(elem, name);\n\n\t\t// Non-existent attributes return null, we normalize to undefined\n\t\treturn ret==null ? undefined:ret;\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function(elem, value){\n\t\t\t\tif(!support.radioValue&&value==="radio"&&\n\t\t\t\t\tnodeName(elem, "input") ){\n\t\t\t\t\tvar val=elem.value;\n\t\t\t\t\telem.setAttribute("type", value);\n\t\t\t\t\tif(val){\n\t\t\t\t\t\telem.value=val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tremoveAttr: function(elem, value){\n\t\tvar name,\n\t\t\ti=0,\n\n\t\t\t// Attribute names can contain non-HTML whitespace characters\n\t\t\t// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2\n\t\t\tattrNames=value&&value.match(rnothtmlwhite);\n\n\t\tif(attrNames&&elem.nodeType===1){\n\t\t\twhile(( name=attrNames[ i++ ]) ){\n\t\t\t\telem.removeAttribute(name);\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook={\n\tset: function(elem, value, name){\n\t\tif(value===false){\n\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr(elem, name);\n\t\t}else{\n\t\t\telem.setAttribute(name, name);\n\t\t}\n\t\treturn name;\n\t}\n};\n\njQuery.each(jQuery.expr.match.bool.source.match(/\\w+/g), function(_i, name){\n\tvar getter=attrHandle[ name ]||jQuery.find.attr;\n\n\tattrHandle[ name ]=function(elem, name, isXML){\n\t\tvar ret, handle,\n\t\t\tlowercaseName=name.toLowerCase();\n\n\t\tif(!isXML){\n\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle=attrHandle[ lowercaseName ];\n\t\t\tattrHandle[ lowercaseName ]=ret;\n\t\t\tret=getter(elem, name, isXML)!=null ?\n\t\t\t\tlowercaseName :\n\t\t\t\tnull;\n\t\t\tattrHandle[ lowercaseName ]=handle;\n\t\t}\n\t\treturn ret;\n\t};\n});\n\n\n\n\nvar rfocusable=/^(?:input|select|textarea|button)$/i,\n\trclickable=/^(?:a|area)$/i;\n\njQuery.fn.extend({\n\tprop: function(name, value){\n\t\treturn access(this, jQuery.prop, name, value, arguments.length > 1);\n\t},\n\n\tremoveProp: function(name){\n\t\treturn this.each(function(){\n\t\t\tdelete this[ jQuery.propFix[ name ]||name ];\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tprop: function(elem, name, value){\n\t\tvar ret, hooks,\n\t\t\tnType=elem.nodeType;\n\n\t\t// Don\'t get/set properties on text, comment and attribute nodes\n\t\tif(nType===3||nType===8||nType===2){\n\t\t\treturn;\n\t\t}\n\n\t\tif(nType!==1||!jQuery.isXMLDoc(elem) ){\n\n\t\t\t// Fix name and attach hooks\n\t\t\tname=jQuery.propFix[ name ]||name;\n\t\t\thooks=jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif(value!==undefined){\n\t\t\tif(hooks&&"set" in hooks&&\n\t\t\t\t(ret=hooks.set(elem, value, name) )!==undefined){\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\treturn(elem[ name ]=value);\n\t\t}\n\n\t\tif(hooks&&"get" in hooks&&(ret=hooks.get(elem, name) )!==null){\n\t\t\treturn ret;\n\t\t}\n\n\t\treturn elem[ name ];\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function(elem){\n\n\t\t\t\t// Support: IE <=9 - 11 only\n\t\t\t\t// elem.tabIndex doesn\'t always return the\n\t\t\t\t// correct value when it hasn\'t been explicitly set\n\t\t\t\t// Use proper attribute retrieval (trac-12072)\n\t\t\t\tvar tabindex=jQuery.find.attr(elem, "tabindex");\n\n\t\t\t\tif(tabindex){\n\t\t\t\t\treturn parseInt(tabindex, 10);\n\t\t\t\t}\n\n\t\t\t\tif(\n\t\t\t\t\trfocusable.test(elem.nodeName)||\n\t\t\t\t\trclickable.test(elem.nodeName)&&\n\t\t\t\t\telem.href\n\t\t\t\t){\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\t"for": "htmlFor",\n\t\t"class": "className"\n\t}\n});\n\n// Support: IE <=11 only\n// Accessing the selectedIndex property\n// forces the browser to respect setting selected\n// on the option\n// The getter ensures a default option is selected\n// when in an optgroup\n// eslint rule "no-unused-expressions" is disabled for this code\n// since it considers such accessions noop\nif(!support.optSelected){\n\tjQuery.propHooks.selected={\n\t\tget: function(elem){\n\n\t\t\t\n\n\t\t\tvar parent=elem.parentNode;\n\t\t\tif(parent&&parent.parentNode){\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t\tset: function(elem){\n\n\t\t\t\n\n\t\t\tvar parent=elem.parentNode;\n\t\t\tif(parent){\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\tif(parent.parentNode){\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\njQuery.each([\n\t"tabIndex",\n\t"readOnly",\n\t"maxLength",\n\t"cellSpacing",\n\t"cellPadding",\n\t"rowSpan",\n\t"colSpan",\n\t"useMap",\n\t"frameBorder",\n\t"contentEditable"\n], function(){\n\tjQuery.propFix[ this.toLowerCase() ]=this;\n});\n\n\n\n\n\t// Strip and collapse whitespace according to HTML spec\n\t// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace\n\tfunction stripAndCollapse(value){\n\t\tvar tokens=value.match(rnothtmlwhite)||[];\n\t\treturn tokens.join(" ");\n\t}\n\n\nfunction getClass(elem){\n\treturn elem.getAttribute&&elem.getAttribute("class")||"";\n}\n\nfunction classesToArray(value){\n\tif(Array.isArray(value) ){\n\t\treturn value;\n\t}\n\tif(typeof value==="string"){\n\t\treturn value.match(rnothtmlwhite)||[];\n\t}\n\treturn [];\n}\n\njQuery.fn.extend({\n\taddClass: function(value){\n\t\tvar classNames, cur, curValue, className, i, finalValue;\n\n\t\tif(isFunction(value) ){\n\t\t\treturn this.each(function(j){\n\t\t\t\tjQuery(this).addClass(value.call(this, j, getClass(this) ));\n\t\t\t});\n\t\t}\n\n\t\tclassNames=classesToArray(value);\n\n\t\tif(classNames.length){\n\t\t\treturn this.each(function(){\n\t\t\t\tcurValue=getClass(this);\n\t\t\t\tcur=this.nodeType===1&&(" " + stripAndCollapse(curValue) + " ");\n\n\t\t\t\tif(cur){\n\t\t\t\t\tfor(i=0; i < classNames.length; i++){\n\t\t\t\t\t\tclassName=classNames[ i ];\n\t\t\t\t\t\tif(cur.indexOf(" " + className + " ") < 0){\n\t\t\t\t\t\t\tcur +=className + " ";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue=stripAndCollapse(cur);\n\t\t\t\t\tif(curValue!==finalValue){\n\t\t\t\t\t\tthis.setAttribute("class", finalValue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function(value){\n\t\tvar classNames, cur, curValue, className, i, finalValue;\n\n\t\tif(isFunction(value) ){\n\t\t\treturn this.each(function(j){\n\t\t\t\tjQuery(this).removeClass(value.call(this, j, getClass(this) ));\n\t\t\t});\n\t\t}\n\n\t\tif(!arguments.length){\n\t\t\treturn this.attr("class", "");\n\t\t}\n\n\t\tclassNames=classesToArray(value);\n\n\t\tif(classNames.length){\n\t\t\treturn this.each(function(){\n\t\t\t\tcurValue=getClass(this);\n\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur=this.nodeType===1&&(" " + stripAndCollapse(curValue) + " ");\n\n\t\t\t\tif(cur){\n\t\t\t\t\tfor(i=0; i < classNames.length; i++){\n\t\t\t\t\t\tclassName=classNames[ i ];\n\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile(cur.indexOf(" " + className + " ") > -1){\n\t\t\t\t\t\t\tcur=cur.replace(" " + className + " ", " ");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue=stripAndCollapse(cur);\n\t\t\t\t\tif(curValue!==finalValue){\n\t\t\t\t\t\tthis.setAttribute("class", finalValue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function(value, stateVal){\n\t\tvar classNames, className, i, self,\n\t\t\ttype=typeof value,\n\t\t\tisValidValue=type==="string"||Array.isArray(value);\n\n\t\tif(isFunction(value) ){\n\t\t\treturn this.each(function(i){\n\t\t\t\tjQuery(this).toggleClass(\n\t\t\t\t\tvalue.call(this, i, getClass(this), stateVal),\n\t\t\t\t\tstateVal\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\n\t\tif(typeof stateVal==="boolean"&&isValidValue){\n\t\t\treturn stateVal ? this.addClass(value):this.removeClass(value);\n\t\t}\n\n\t\tclassNames=classesToArray(value);\n\n\t\treturn this.each(function(){\n\t\t\tif(isValidValue){\n\n\t\t\t\t// Toggle individual class names\n\t\t\t\tself=jQuery(this);\n\n\t\t\t\tfor(i=0; i < classNames.length; i++){\n\t\t\t\t\tclassName=classNames[ i ];\n\n\t\t\t\t\t// Check each className given, space separated list\n\t\t\t\t\tif(self.hasClass(className) ){\n\t\t\t\t\t\tself.removeClass(className);\n\t\t\t\t\t}else{\n\t\t\t\t\t\tself.addClass(className);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t}else if(value===undefined||type==="boolean"){\n\t\t\t\tclassName=getClass(this);\n\t\t\t\tif(className){\n\n\t\t\t\t\t// Store className if set\n\t\t\t\t\tdataPriv.set(this, "__className__", className);\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we\'re passed `false`,\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tif(this.setAttribute){\n\t\t\t\t\tthis.setAttribute("class",\n\t\t\t\t\t\tclassName||value===false ?\n\t\t\t\t\t\t\t"" :\n\t\t\t\t\t\t\tdataPriv.get(this, "__className__")||""\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function(selector){\n\t\tvar className, elem,\n\t\t\ti=0;\n\n\t\tclassName=" " + selector + " ";\n\t\twhile(( elem=this[ i++ ]) ){\n\t\t\tif(elem.nodeType===1&&\n\t\t\t\t(" " + stripAndCollapse(getClass(elem) ) + " ").indexOf(className) > -1){\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n});\n\n\n\n\nvar rreturn=/\\r/g;\n\njQuery.fn.extend({\n\tval: function(value){\n\t\tvar hooks, ret, valueIsFunction,\n\t\t\telem=this[ 0 ];\n\n\t\tif(!arguments.length){\n\t\t\tif(elem){\n\t\t\t\thooks=jQuery.valHooks[ elem.type ]||\n\t\t\t\t\tjQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif(hooks&&\n\t\t\t\t\t"get" in hooks&&\n\t\t\t\t\t(ret=hooks.get(elem, "value") )!==undefined\n\t\t\t\t){\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret=elem.value;\n\n\t\t\t\t// Handle most common string cases\n\t\t\t\tif(typeof ret==="string"){\n\t\t\t\t\treturn ret.replace(rreturn, "");\n\t\t\t\t}\n\n\t\t\t\t// Handle cases where value is null/undef or number\n\t\t\t\treturn ret==null ? "":ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tvalueIsFunction=isFunction(value);\n\n\t\treturn this.each(function(i){\n\t\t\tvar val;\n\n\t\t\tif(this.nodeType!==1){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif(valueIsFunction){\n\t\t\t\tval=value.call(this, i, jQuery(this).val());\n\t\t\t}else{\n\t\t\t\tval=value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as ""; convert numbers to string\n\t\t\tif(val==null){\n\t\t\t\tval="";\n\n\t\t\t}else if(typeof val==="number"){\n\t\t\t\tval +="";\n\n\t\t\t}else if(Array.isArray(val) ){\n\t\t\t\tval=jQuery.map(val, function(value){\n\t\t\t\t\treturn value==null ? "":value + "";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks=jQuery.valHooks[ this.type ]||jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif(!hooks||!("set" in hooks)||hooks.set(this, val, "value")===undefined){\n\t\t\t\tthis.value=val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function(elem){\n\n\t\t\t\tvar val=jQuery.find.attr(elem, "value");\n\t\t\t\treturn val!=null ?\n\t\t\t\t\tval :\n\n\t\t\t\t\t// Support: IE <=10 - 11 only\n\t\t\t\t\t// option.text throws exceptions (trac-14686, trac-14858)\n\t\t\t\t\t// Strip and collapse whitespace\n\t\t\t\t\t// https://html.spec.whatwg.org/#strip-and-collapse-whitespace\n\t\t\t\t\tstripAndCollapse(jQuery.text(elem) );\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function(elem){\n\t\t\t\tvar value, option, i,\n\t\t\t\t\toptions=elem.options,\n\t\t\t\t\tindex=elem.selectedIndex,\n\t\t\t\t\tone=elem.type==="select-one",\n\t\t\t\t\tvalues=one ? null:[],\n\t\t\t\t\tmax=one ? index + 1:options.length;\n\n\t\t\t\tif(index < 0){\n\t\t\t\t\ti=max;\n\n\t\t\t\t}else{\n\t\t\t\t\ti=one ? index:0;\n\t\t\t\t}\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor(; i < max; i++){\n\t\t\t\t\toption=options[ i ];\n\n\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t// IE8-9 doesn\'t update selected after form reset (trac-2551)\n\t\t\t\t\tif(( option.selected||i===index)&&\n\n\t\t\t\t\t\t\t// Don\'t return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t!option.disabled&&\n\t\t\t\t\t\t\t(!option.parentNode.disabled||\n\t\t\t\t\t\t\t\t!nodeName(option.parentNode, "optgroup") )){\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue=jQuery(option).val();\n\n\t\t\t\t\t\t// We don\'t need an array for one selects\n\t\t\t\t\t\tif(one){\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push(value);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function(elem, value){\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions=elem.options,\n\t\t\t\t\tvalues=jQuery.makeArray(value),\n\t\t\t\t\ti=options.length;\n\n\t\t\t\twhile(i--){\n\t\t\t\t\toption=options[ i ];\n\n\t\t\t\t\t\n\n\t\t\t\t\tif(option.selected=\n\t\t\t\t\t\tjQuery.inArray(jQuery.valHooks.option.get(option), values) > -1\n\t\t\t\t\t){\n\t\t\t\t\t\toptionSet=true;\n\t\t\t\t\t}\n\n\t\t\t\t\t\n\t\t\t\t}\n\n\t\t\t\t// Force browsers to behave consistently when non-matching value is set\n\t\t\t\tif(!optionSet){\n\t\t\t\t\telem.selectedIndex=-1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Radios and checkboxes getter/setter\njQuery.each([ "radio", "checkbox" ], function(){\n\tjQuery.valHooks[ this ]={\n\t\tset: function(elem, value){\n\t\t\tif(Array.isArray(value) ){\n\t\t\t\treturn(elem.checked=jQuery.inArray(jQuery(elem).val(), value) > -1);\n\t\t\t}\n\t\t}\n\t};\n\tif(!support.checkOn){\n\t\tjQuery.valHooks[ this ].get=function(elem){\n\t\t\treturn elem.getAttribute("value")===null ? "on":elem.value;\n\t\t};\n\t}\n});\n\n\n\n\n// Return jQuery for attributes-only inclusion\nvar location=window.location;\n\nvar nonce={ guid: Date.now() };\n\nvar rquery=(/\\?/);\n\n\n\n// Cross-browser xml parsing\njQuery.parseXML=function(data){\n\tvar xml, parserErrorElem;\n\tif(!data||typeof data!=="string"){\n\t\treturn null;\n\t}\n\n\t// Support: IE 9 - 11 only\n\t// IE throws on parseFromString with invalid input.\n\ttry {\n\t\txml=(new window.DOMParser()).parseFromString(data, "text/xml");\n\t} catch(e){}\n\n\tparserErrorElem=xml&&xml.getElementsByTagName("parsererror")[ 0 ];\n\tif(!xml||parserErrorElem){\n\t\tjQuery.error("Invalid XML: " + (\n\t\t\tparserErrorElem ?\n\t\t\t\tjQuery.map(parserErrorElem.childNodes, function(el){\n\t\t\t\t\treturn el.textContent;\n\t\t\t\t}).join("\\n") :\n\t\t\t\tdata\n\t\t));\n\t}\n\treturn xml;\n};\n\n\nvar rfocusMorph=/^(?:focusinfocus|focusoutblur)$/,\n\tstopPropagationCallback=function(e){\n\t\te.stopPropagation();\n\t};\n\njQuery.extend(jQuery.event, {\n\n\ttrigger: function(event, data, elem, onlyHandlers){\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special, lastElement,\n\t\t\teventPath=[ elem||document ],\n\t\t\ttype=hasOwn.call(event, "type") ? event.type:event,\n\t\t\tnamespaces=hasOwn.call(event, "namespace") ? event.namespace.split("."):[];\n\n\t\tcur=lastElement=tmp=elem=elem||document;\n\n\t\t// Don\'t do events on text and comment nodes\n\t\tif(elem.nodeType===3||elem.nodeType===8){\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we\'re not firing them right now\n\t\tif(rfocusMorph.test(type + jQuery.event.triggered) ){\n\t\t\treturn;\n\t\t}\n\n\t\tif(type.indexOf(".") > -1){\n\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces=type.split(".");\n\t\t\ttype=namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype=type.indexOf(":") < 0&&"on" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent=event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event(type, typeof event==="object"&&event);\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger=onlyHandlers ? 2:3;\n\t\tevent.namespace=namespaces.join(".");\n\t\tevent.rnamespace=event.namespace ?\n\t\t\tnew RegExp("(^|\\\\.)" + namespaces.join("\\\\.(?:.*\\\\.|)") + "(\\\\.|$)") :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result=undefined;\n\t\tif(!event.target){\n\t\t\tevent.target=elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata=data==null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray(data, [ event ]);\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial=jQuery.event.special[ type ]||{};\n\t\tif(!onlyHandlers&&special.trigger&&special.trigger.apply(elem, data)===false){\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (trac-9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)\n\t\tif(!onlyHandlers&&!special.noBubble&&!isWindow(elem) ){\n\n\t\t\tbubbleType=special.delegateType||type;\n\t\t\tif(!rfocusMorph.test(bubbleType + type) ){\n\t\t\t\tcur=cur.parentNode;\n\t\t\t}\n\t\t\tfor(; cur; cur=cur.parentNode){\n\t\t\t\teventPath.push(cur);\n\t\t\t\ttmp=cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif(tmp===(elem.ownerDocument||document) ){\n\t\t\t\teventPath.push(tmp.defaultView||tmp.parentWindow||window);\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti=0;\n\t\twhile(( cur=eventPath[ i++ ])&&!event.isPropagationStopped()){\n\t\t\tlastElement=cur;\n\t\t\tevent.type=i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType||type;\n\n\t\t\t// jQuery handler\n\t\t\thandle=(dataPriv.get(cur, "events")||Object.create(null) )[ event.type ]&&\n\t\t\t\tdataPriv.get(cur, "handle");\n\t\t\tif(handle){\n\t\t\t\thandle.apply(cur, data);\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle=ontype&&cur[ ontype ];\n\t\t\tif(handle&&handle.apply&&acceptData(cur) ){\n\t\t\t\tevent.result=handle.apply(cur, data);\n\t\t\t\tif(event.result===false){\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type=type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif(!onlyHandlers&&!event.isDefaultPrevented()){\n\n\t\t\tif(( !special._default||\n\t\t\t\tspecial._default.apply(eventPath.pop(), data)===false)&&\n\t\t\t\tacceptData(elem) ){\n\n\t\t\t\t// Call a native DOM method on the target with the same name as the event.\n\t\t\t\t// Don\'t do default actions on window, that\'s where global variables be (trac-6170)\n\t\t\t\tif(ontype&&isFunction(elem[ type ])&&!isWindow(elem) ){\n\n\t\t\t\t\t// Don\'t re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp=elem[ ontype ];\n\n\t\t\t\t\tif(tmp){\n\t\t\t\t\t\telem[ ontype ]=null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered=type;\n\n\t\t\t\t\tif(event.isPropagationStopped()){\n\t\t\t\t\t\tlastElement.addEventListener(type, stopPropagationCallback);\n\t\t\t\t\t}\n\n\t\t\t\t\telem[ type ]();\n\n\t\t\t\t\tif(event.isPropagationStopped()){\n\t\t\t\t\t\tlastElement.removeEventListener(type, stopPropagationCallback);\n\t\t\t\t\t}\n\n\t\t\t\t\tjQuery.event.triggered=undefined;\n\n\t\t\t\t\tif(tmp){\n\t\t\t\t\t\telem[ ontype ]=tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\t// Piggyback on a donor event to simulate a different one\n\t// Used only for `focus(in | out)` events\n\tsimulate: function(type, elem, event){\n\t\tvar e=jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true\n\t\t\t}\n\t\t);\n\n\t\tjQuery.event.trigger(e, null, elem);\n\t}\n\n});\n\njQuery.fn.extend({\n\n\ttrigger: function(type, data){\n\t\treturn this.each(function(){\n\t\t\tjQuery.event.trigger(type, data, this);\n\t\t});\n\t},\n\ttriggerHandler: function(type, data){\n\t\tvar elem=this[ 0 ];\n\t\tif(elem){\n\t\t\treturn jQuery.event.trigger(type, data, elem, true);\n\t\t}\n\t}\n});\n\n\nvar\n\trbracket=/\\[\\]$/,\n\trCRLF=/\\r?\\n/g,\n\trsubmitterTypes=/^(?:submit|button|image|reset|file)$/i,\n\trsubmittable=/^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams(prefix, obj, traditional, add){\n\tvar name;\n\n\tif(Array.isArray(obj) ){\n\n\t\t// Serialize array item.\n\t\tjQuery.each(obj, function(i, v){\n\t\t\tif(traditional||rbracket.test(prefix) ){\n\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd(prefix, v);\n\n\t\t\t}else{\n\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams(\n\t\t\t\t\tprefix + "[" +(typeof v==="object"&&v!=null ? i:"") + "]",\n\t\t\t\t\tv,\n\t\t\t\t\ttraditional,\n\t\t\t\t\tadd\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t}else if(!traditional&&toType(obj)==="object"){\n\n\t\t// Serialize object item.\n\t\tfor(name in obj){\n\t\t\tbuildParams(prefix + "[" + name + "]", obj[ name ], traditional, add);\n\t\t}\n\n\t}else{\n\n\t\t// Serialize scalar item.\n\t\tadd(prefix, obj);\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param=function(a, traditional){\n\tvar prefix,\n\t\ts=[],\n\t\tadd=function(key, valueOrFunction){\n\n\t\t\t// If value is a function, invoke it and use its return value\n\t\t\tvar value=isFunction(valueOrFunction) ?\n\t\t\t\tvalueOrFunction() :\n\t\t\t\tvalueOrFunction;\n\n\t\t\ts[ s.length ]=encodeURIComponent(key) + "=" +\n\t\t\t\tencodeURIComponent(value==null ? "":value);\n\t\t};\n\n\tif(a==null){\n\t\treturn "";\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif(Array.isArray(a)||(a.jquery&&!jQuery.isPlainObject(a) )){\n\n\t\t// Serialize the form elements\n\t\tjQuery.each(a, function(){\n\t\t\tadd(this.name, this.value);\n\t\t});\n\n\t}else{\n\n\t\t// If traditional, encode the "old" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor(prefix in a){\n\t\t\tbuildParams(prefix, a[ prefix ], traditional, add);\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join("&");\n};\n\njQuery.fn.extend({\n\tserialize: function(){\n\t\treturn jQuery.param(this.serializeArray());\n\t},\n\tserializeArray: function(){\n\t\treturn this.map(function(){\n\n\t\t\t// Can add propHook for "elements" to filter or add form elements\n\t\t\tvar elements=jQuery.prop(this, "elements");\n\t\t\treturn elements ? jQuery.makeArray(elements):this;\n\t\t}).filter(function(){\n\t\t\tvar type=this.type;\n\n\t\t\t// Use .is(":disabled") so that fieldset[disabled] works\n\t\t\treturn this.name&&!jQuery(this).is(":disabled")&&\n\t\t\t\trsubmittable.test(this.nodeName)&&!rsubmitterTypes.test(type)&&\n\t\t\t\t(this.checked||!rcheckableType.test(type) );\n\t\t}).map(function(_i, elem){\n\t\t\tvar val=jQuery(this).val();\n\n\t\t\tif(val==null){\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif(Array.isArray(val) ){\n\t\t\t\treturn jQuery.map(val, function(val){\n\t\t\t\t\treturn { name: elem.name, value: val.replace(rCRLF, "\\r\\n") };\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn { name: elem.name, value: val.replace(rCRLF, "\\r\\n") };\n\t\t}).get();\n\t}\n});\n\n\nvar\n\tr20=/%20/g,\n\trhash=/#.*$/,\n\trantiCache=/([?&])_=[^&]*/,\n\trheaders=/^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\n\t// trac-7653, trac-8125, trac-8152: local protocol detection\n\trlocalProtocol=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent=/^(?:GET|HEAD)$/,\n\trprotocol=/^\\/\\//,\n\n\t\n\tprefilters={},\n\n\t\n\ttransports={},\n\n\t// Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression\n\tallTypes="*/".concat("*"),\n\n\t// Anchor tag for parsing the document origin\n\toriginAnchor=document.createElement("a");\n\noriginAnchor.href=location.href;\n\n// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports(structure){\n\n\t// dataTypeExpression is optional and defaults to "*"\n\treturn function(dataTypeExpression, func){\n\n\t\tif(typeof dataTypeExpression!=="string"){\n\t\t\tfunc=dataTypeExpression;\n\t\t\tdataTypeExpression="*";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti=0,\n\t\t\tdataTypes=dataTypeExpression.toLowerCase().match(rnothtmlwhite)||[];\n\n\t\tif(isFunction(func) ){\n\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile(( dataType=dataTypes[ i++ ]) ){\n\n\t\t\t\t// Prepend if requested\n\t\t\t\tif(dataType[ 0 ]==="+"){\n\t\t\t\t\tdataType=dataType.slice(1)||"*";\n\t\t\t\t\t(structure[ dataType ]=structure[ dataType ]||[]).unshift(func);\n\n\t\t\t\t// Otherwise append\n\t\t\t\t}else{\n\t\t\t\t\t(structure[ dataType ]=structure[ dataType ]||[]).push(func);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports(structure, options, originalOptions, jqXHR){\n\n\tvar inspected={},\n\t\tseekingTransport=(structure===transports);\n\n\tfunction inspect(dataType){\n\t\tvar selected;\n\t\tinspected[ dataType ]=true;\n\t\tjQuery.each(structure[ dataType ]||[], function(_, prefilterOrFactory){\n\t\t\tvar dataTypeOrTransport=prefilterOrFactory(options, originalOptions, jqXHR);\n\t\t\tif(typeof dataTypeOrTransport==="string"&&\n\t\t\t\t!seekingTransport&&!inspected[ dataTypeOrTransport ]){\n\n\t\t\t\toptions.dataTypes.unshift(dataTypeOrTransport);\n\t\t\t\tinspect(dataTypeOrTransport);\n\t\t\t\treturn false;\n\t\t\t}else if(seekingTransport){\n\t\t\t\treturn !(selected=dataTypeOrTransport);\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect(options.dataTypes[ 0 ])||!inspected[ "*" ]&&inspect("*");\n}\n\n// A special extend for ajax options\n// that takes "flat" options (not to be deep extended)\n// Fixes trac-9887\nfunction ajaxExtend(target, src){\n\tvar key, deep,\n\t\tflatOptions=jQuery.ajaxSettings.flatOptions||{};\n\n\tfor(key in src){\n\t\tif(src[ key ]!==undefined){\n\t\t\t(flatOptions[ key ] ? target:(deep||(deep={}) ))[ key ]=src[ key ];\n\t\t}\n\t}\n\tif(deep){\n\t\tjQuery.extend(true, target, deep);\n\t}\n\n\treturn target;\n}\n\n\nfunction ajaxHandleResponses(s, jqXHR, responses){\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents=s.contents,\n\t\tdataTypes=s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile(dataTypes[ 0 ]==="*"){\n\t\tdataTypes.shift();\n\t\tif(ct===undefined){\n\t\t\tct=s.mimeType||jqXHR.getResponseHeader("Content-Type");\n\t\t}\n\t}\n\n\t// Check if we\'re dealing with a known content-type\n\tif(ct){\n\t\tfor(type in contents){\n\t\t\tif(contents[ type ]&&contents[ type ].test(ct) ){\n\t\t\t\tdataTypes.unshift(type);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif(dataTypes[ 0 ] in responses){\n\t\tfinalDataType=dataTypes[ 0 ];\n\t}else{\n\n\t\t// Try convertible dataTypes\n\t\tfor(type in responses){\n\t\t\tif(!dataTypes[ 0 ]||s.converters[ type + " " + dataTypes[ 0 ] ]){\n\t\t\t\tfinalDataType=type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif(!firstDataType){\n\t\t\t\tfirstDataType=type;\n\t\t\t}\n\t\t}\n\n\t\t// Or just use first one\n\t\tfinalDataType=finalDataType||firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif(finalDataType){\n\t\tif(finalDataType!==dataTypes[ 0 ]){\n\t\t\tdataTypes.unshift(finalDataType);\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n\nfunction ajaxConvert(s, response, jqXHR, isSuccess){\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters={},\n\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes=s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif(dataTypes[ 1 ]){\n\t\tfor(conv in s.converters){\n\t\t\tconverters[ conv.toLowerCase() ]=s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent=dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile(current){\n\n\t\tif(s.responseFields[ current ]){\n\t\t\tjqXHR[ s.responseFields[ current ] ]=response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif(!prev&&isSuccess&&s.dataFilter){\n\t\t\tresponse=s.dataFilter(response, s.dataType);\n\t\t}\n\n\t\tprev=current;\n\t\tcurrent=dataTypes.shift();\n\n\t\tif(current){\n\n\t\t\t// There\'s only work to do if current dataType is non-auto\n\t\t\tif(current==="*"){\n\n\t\t\t\tcurrent=prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t}else if(prev!=="*"&&prev!==current){\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv=converters[ prev + " " + current ]||converters[ "* " + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif(!conv){\n\t\t\t\t\tfor(conv2 in converters){\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp=conv2.split(" ");\n\t\t\t\t\t\tif(tmp[ 1 ]===current){\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv=converters[ prev + " " + tmp[ 0 ] ]||\n\t\t\t\t\t\t\t\tconverters[ "* " + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif(conv){\n\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif(conv===true){\n\t\t\t\t\t\t\t\t\tconv=converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t}else if(converters[ conv2 ]!==true){\n\t\t\t\t\t\t\t\t\tcurrent=tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift(tmp[ 1 ]);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif(conv!==true){\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif(conv&&s.throws){\n\t\t\t\t\t\tresponse=conv(response);\n\t\t\t\t\t}else{\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse=conv(response);\n\t\t\t\t\t\t} catch(e){\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tstate: "parsererror",\n\t\t\t\t\t\t\t\terror: conv ? e:"No conversion from " + prev + " to " + current\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: "success", data: response };\n}\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: location.href,\n\t\ttype: "GET",\n\t\tisLocal: rlocalProtocol.test(location.protocol),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: "application/x-www-form-urlencoded; charset=UTF-8",\n\n\t\t\n\n\t\taccepts: {\n\t\t\t"*": allTypes,\n\t\t\ttext: "text/plain",\n\t\t\thtml: "text/html",\n\t\t\txml: "application/xml, text/xml",\n\t\t\tjson: "application/json, text/javascript"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /\\bxml\\b/,\n\t\t\thtml: /\\bhtml/,\n\t\t\tjson: /\\bjson\\b/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: "responseXML",\n\t\t\ttext: "responseText",\n\t\t\tjson: "responseJSON"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall "*") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t"* text": String,\n\n\t\t\t// Text to html (true=no transformation)\n\t\t\t"text html": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t"text json": JSON.parse,\n\n\t\t\t// Parse text as xml\n\t\t\t"text xml": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn\'t be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn\'t be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function(target, settings){\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend(ajaxExtend(target, jQuery.ajaxSettings), settings) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend(jQuery.ajaxSettings, target);\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports(prefilters),\n\tajaxTransport: addToPrefiltersOrTransports(transports),\n\n\t// Main method\n\tajax: function(url, options){\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif(typeof url==="object"){\n\t\t\toptions=url;\n\t\t\turl=undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions=options||{};\n\n\t\tvar transport,\n\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\n\t\t\t// Url cleanup var\n\t\t\turlAnchor,\n\n\t\t\t// Request state (becomes false upon send and true upon completion)\n\t\t\tcompleted,\n\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\n\t\t\t// Loop variable\n\t\t\ti,\n\n\t\t\t// uncached part of the url\n\t\t\tuncached,\n\n\t\t\t// Create the final options object\n\t\t\ts=jQuery.ajaxSetup({}, options),\n\n\t\t\t// Callbacks context\n\t\t\tcallbackContext=s.context||s,\n\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext=s.context&&\n\t\t\t\t(callbackContext.nodeType||callbackContext.jquery) ?\n\t\t\t\tjQuery(callbackContext) :\n\t\t\t\tjQuery.event,\n\n\t\t\t// Deferreds\n\t\t\tdeferred=jQuery.Deferred(),\n\t\t\tcompleteDeferred=jQuery.Callbacks("once memory"),\n\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode=s.statusCode||{},\n\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders={},\n\t\t\trequestHeadersNames={},\n\n\t\t\t// Default abort message\n\t\t\tstrAbort="canceled",\n\n\t\t\t// Fake xhr\n\t\t\tjqXHR={\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function(key){\n\t\t\t\t\tvar match;\n\t\t\t\t\tif(completed){\n\t\t\t\t\t\tif(!responseHeaders){\n\t\t\t\t\t\t\tresponseHeaders={};\n\t\t\t\t\t\t\twhile(( match=rheaders.exec(responseHeadersString) )){\n\t\t\t\t\t\t\t\tresponseHeaders[ match[ 1 ].toLowerCase() + " " ]=\n\t\t\t\t\t\t\t\t\t(responseHeaders[ match[ 1 ].toLowerCase() + " " ]||[])\n\t\t\t\t\t\t\t\t\t\t.concat(match[ 2 ]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch=responseHeaders[ key.toLowerCase() + " " ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match==null ? null:match.join(", ");\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function(){\n\t\t\t\t\treturn completed ? responseHeadersString:null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function(name, value){\n\t\t\t\t\tif(completed==null){\n\t\t\t\t\t\tname=requestHeadersNames[ name.toLowerCase() ]=\n\t\t\t\t\t\t\trequestHeadersNames[ name.toLowerCase() ]||name;\n\t\t\t\t\t\trequestHeaders[ name ]=value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function(type){\n\t\t\t\t\tif(completed==null){\n\t\t\t\t\t\ts.mimeType=type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function(map){\n\t\t\t\t\tvar code;\n\t\t\t\t\tif(map){\n\t\t\t\t\t\tif(completed){\n\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always(map[ jqXHR.status ]);\n\t\t\t\t\t\t}else{\n\n\t\t\t\t\t\t\t// Lazy-add the new callbacks in a way that preserves old ones\n\t\t\t\t\t\t\tfor(code in map){\n\t\t\t\t\t\t\t\tstatusCode[ code ]=[ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function(statusText){\n\t\t\t\t\tvar finalText=statusText||strAbort;\n\t\t\t\t\tif(transport){\n\t\t\t\t\t\ttransport.abort(finalText);\n\t\t\t\t\t}\n\t\t\t\t\tdone(0, finalText);\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise(jqXHR);\n\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (trac-10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url=(( url||s.url||location.href) + "")\n\t\t\t.replace(rprotocol, location.protocol + "//");\n\n\t\t// Alias method option to type as per ticket trac-12004\n\t\ts.type=options.method||options.type||s.method||s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes=(s.dataType||"*").toLowerCase().match(rnothtmlwhite)||[ "" ];\n\n\t\t// A cross-domain request is in order when the origin doesn\'t match the current origin.\n\t\tif(s.crossDomain==null){\n\t\t\turlAnchor=document.createElement("a");\n\n\t\t\t// Support: IE <=8 - 11, Edge 12 - 15\n\t\t\t// IE throws exception on accessing the href property if url is malformed,\n\t\t\t// e.g. http://example.com:80x/\n\t\t\ttry {\n\t\t\t\turlAnchor.href=s.url;\n\n\t\t\t\t// Support: IE <=8 - 11 only\n\t\t\t\t// Anchor\'s host property isn\'t correctly set when s.url is relative\n\t\t\t\turlAnchor.href=urlAnchor.href;\n\t\t\t\ts.crossDomain=originAnchor.protocol + "//" + originAnchor.host!==\n\t\t\t\t\turlAnchor.protocol + "//" + urlAnchor.host;\n\t\t\t} catch(e){\n\n\t\t\t\t// If there is an error parsing the URL, assume it is crossDomain,\n\t\t\t\t// it can be rejected by the transport if it is invalid\n\t\t\t\ts.crossDomain=true;\n\t\t\t}\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif(s.data&&s.processData&&typeof s.data!=="string"){\n\t\t\ts.data=jQuery.param(s.data, s.traditional);\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports(prefilters, s, options, jqXHR);\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif(completed){\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\t// Don\'t fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118)\n\t\tfireGlobals=jQuery.event&&s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif(fireGlobals&&jQuery.active++===0){\n\t\t\tjQuery.event.trigger("ajaxStart");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type=s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test(s.type);\n\n\t\t// Save the URL in case we\'re toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\t// Remove hash to simplify url manipulation\n\t\tcacheURL=s.url.replace(rhash, "");\n\n\t\t// More options handling for requests with no content\n\t\tif(!s.hasContent){\n\n\t\t\t// Remember the hash so we can put it back\n\t\t\tuncached=s.url.slice(cacheURL.length);\n\n\t\t\t// If data is available and should be processed, append data to url\n\t\t\tif(s.data&&(s.processData||typeof s.data==="string") ){\n\t\t\t\tcacheURL +=(rquery.test(cacheURL) ? "&":"?") + s.data;\n\n\t\t\t\t// trac-9682: remove data so that it\'s not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add or update anti-cache param if needed\n\t\t\tif(s.cache===false){\n\t\t\t\tcacheURL=cacheURL.replace(rantiCache, "$1");\n\t\t\t\tuncached=(rquery.test(cacheURL) ? "&":"?") + "_=" +(nonce.guid++) +\n\t\t\t\t\tuncached;\n\t\t\t}\n\n\t\t\t// Put hash and anti-cache on the URL that will be requested (gh-1732)\n\t\t\ts.url=cacheURL + uncached;\n\n\t\t// Change \'%20\' to \'+\' if this is encoded form body content (gh-2658)\n\t\t}else if(s.data&&s.processData&&\n\t\t\t(s.contentType||"").indexOf("application/x-www-form-urlencoded")===0){\n\t\t\ts.data=s.data.replace(r20, "+");\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif(s.ifModified){\n\t\t\tif(jQuery.lastModified[ cacheURL ]){\n\t\t\t\tjqXHR.setRequestHeader("If-Modified-Since", jQuery.lastModified[ cacheURL ]);\n\t\t\t}\n\t\t\tif(jQuery.etag[ cacheURL ]){\n\t\t\t\tjqXHR.setRequestHeader("If-None-Match", jQuery.etag[ cacheURL ]);\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif(s.data&&s.hasContent&&s.contentType!==false||options.contentType){\n\t\t\tjqXHR.setRequestHeader("Content-Type", s.contentType);\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t"Accept",\n\t\t\ts.dataTypes[ 0 ]&&s.accepts[ s.dataTypes[ 0 ] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[ 0 ] ] +\n\t\t\t\t\t(s.dataTypes[ 0 ]!=="*" ? ", " + allTypes + "; q=0.01":"") :\n\t\t\t\ts.accepts[ "*" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor(i in s.headers){\n\t\t\tjqXHR.setRequestHeader(i, s.headers[ i ]);\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif(s.beforeSend&&\n\t\t\t(s.beforeSend.call(callbackContext, jqXHR, s)===false||completed) ){\n\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// Aborting is no longer a cancellation\n\t\tstrAbort="abort";\n\n\t\t// Install callbacks on deferreds\n\t\tcompleteDeferred.add(s.complete);\n\t\tjqXHR.done(s.success);\n\t\tjqXHR.fail(s.error);\n\n\t\t// Get transport\n\t\ttransport=inspectPrefiltersOrTransports(transports, s, options, jqXHR);\n\n\t\t// If no transport, we auto-abort\n\t\tif(!transport){\n\t\t\tdone(-1, "No Transport");\n\t\t}else{\n\t\t\tjqXHR.readyState=1;\n\n\t\t\t// Send global event\n\t\t\tif(fireGlobals){\n\t\t\t\tglobalEventContext.trigger("ajaxSend", [ jqXHR, s ]);\n\t\t\t}\n\n\t\t\t// If request was aborted inside ajaxSend, stop there\n\t\t\tif(completed){\n\t\t\t\treturn jqXHR;\n\t\t\t}\n\n\t\t\t// Timeout\n\t\t\tif(s.async&&s.timeout > 0){\n\t\t\t\ttimeoutTimer=window.setTimeout(function(){\n\t\t\t\t\tjqXHR.abort("timeout");\n\t\t\t\t}, s.timeout);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tcompleted=false;\n\t\t\t\ttransport.send(requestHeaders, done);\n\t\t\t} catch(e){\n\n\t\t\t\t// Rethrow post-completion exceptions\n\t\t\t\tif(completed){\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\n\t\t\t\t// Propagate others as results\n\t\t\t\tdone(-1, e);\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done(status, nativeStatusText, responses, headers){\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText=nativeStatusText;\n\n\t\t\t// Ignore repeat invocations\n\t\t\tif(completed){\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcompleted=true;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif(timeoutTimer){\n\t\t\t\twindow.clearTimeout(timeoutTimer);\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport=undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString=headers||"";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState=status > 0 ? 4:0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess=status >=200&&status < 300||status===304;\n\n\t\t\t// Get response data\n\t\t\tif(responses){\n\t\t\t\tresponse=ajaxHandleResponses(s, jqXHR, responses);\n\t\t\t}\n\n\t\t\t// Use a noop converter for missing script but not if jsonp\n\t\t\tif(!isSuccess&&\n\t\t\t\tjQuery.inArray("script", s.dataTypes) > -1&&\n\t\t\t\tjQuery.inArray("json", s.dataTypes) < 0){\n\t\t\t\ts.converters[ "text script" ]=function(){};\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse=ajaxConvert(s, response, jqXHR, isSuccess);\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif(isSuccess){\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif(s.ifModified){\n\t\t\t\t\tmodified=jqXHR.getResponseHeader("Last-Modified");\n\t\t\t\t\tif(modified){\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ]=modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified=jqXHR.getResponseHeader("etag");\n\t\t\t\t\tif(modified){\n\t\t\t\t\t\tjQuery.etag[ cacheURL ]=modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif(status===204||s.type==="HEAD"){\n\t\t\t\t\tstatusText="nocontent";\n\n\t\t\t\t// if not modified\n\t\t\t\t}else if(status===304){\n\t\t\t\t\tstatusText="notmodified";\n\n\t\t\t\t// If we have data, let\'s convert it\n\t\t\t\t}else{\n\t\t\t\t\tstatusText=response.state;\n\t\t\t\t\tsuccess=response.data;\n\t\t\t\t\terror=response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t}else{\n\n\t\t\t\t// Extract error from statusText and normalize for non-aborts\n\t\t\t\terror=statusText;\n\t\t\t\tif(status||!statusText){\n\t\t\t\t\tstatusText="error";\n\t\t\t\t\tif(status < 0){\n\t\t\t\t\t\tstatus=0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status=status;\n\t\t\tjqXHR.statusText=(nativeStatusText||statusText) + "";\n\n\t\t\t// Success/Error\n\t\t\tif(isSuccess){\n\t\t\t\tdeferred.resolveWith(callbackContext, [ success, statusText, jqXHR ]);\n\t\t\t}else{\n\t\t\t\tdeferred.rejectWith(callbackContext, [ jqXHR, statusText, error ]);\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode(statusCode);\n\t\t\tstatusCode=undefined;\n\n\t\t\tif(fireGlobals){\n\t\t\t\tglobalEventContext.trigger(isSuccess ? "ajaxSuccess":"ajaxError",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success:error ]);\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith(callbackContext, [ jqXHR, statusText ]);\n\n\t\t\tif(fireGlobals){\n\t\t\t\tglobalEventContext.trigger("ajaxComplete", [ jqXHR, s ]);\n\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif(!(--jQuery.active) ){\n\t\t\t\t\tjQuery.event.trigger("ajaxStop");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function(url, data, callback){\n\t\treturn jQuery.get(url, data, callback, "json");\n\t},\n\n\tgetScript: function(url, callback){\n\t\treturn jQuery.get(url, undefined, callback, "script");\n\t}\n});\n\njQuery.each([ "get", "post" ], function(_i, method){\n\tjQuery[ method ]=function(url, data, callback, type){\n\n\t\t// Shift arguments if data argument was omitted\n\t\tif(isFunction(data) ){\n\t\t\ttype=type||callback;\n\t\t\tcallback=data;\n\t\t\tdata=undefined;\n\t\t}\n\n\t\t// The url can be an options object (which then must have .url)\n\t\treturn jQuery.ajax(jQuery.extend({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t}, jQuery.isPlainObject(url)&&url) );\n\t};\n});\n\njQuery.ajaxPrefilter(function(s){\n\tvar i;\n\tfor(i in s.headers){\n\t\tif(i.toLowerCase()==="content-type"){\n\t\t\ts.contentType=s.headers[ i ]||"";\n\t\t}\n\t}\n});\n\n\njQuery._evalUrl=function(url, options, doc){\n\treturn jQuery.ajax({\n\t\turl: url,\n\n\t\t// Make this explicit, since user can override this through ajaxSetup (trac-11264)\n\t\ttype: "GET",\n\t\tdataType: "script",\n\t\tcache: true,\n\t\tasync: false,\n\t\tglobal: false,\n\n\t\t// Only evaluate the response if it is successful (gh-4126)\n\t\t// dataFilter is not invoked for failure responses, so using it instead\n\t\t// of the default converter is kludgy but it works.\n\t\tconverters: {\n\t\t\t"text script": function(){}\n\t\t},\n\t\tdataFilter: function(response){\n\t\t\tjQuery.globalEval(response, options, doc);\n\t\t}\n\t});\n};\n\n\njQuery.fn.extend({\n\twrapAll: function(html){\n\t\tvar wrap;\n\n\t\tif(this[ 0 ]){\n\t\t\tif(isFunction(html) ){\n\t\t\t\thtml=html.call(this[ 0 ]);\n\t\t\t}\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap=jQuery(html, this[ 0 ].ownerDocument).eq(0).clone(true);\n\n\t\t\tif(this[ 0 ].parentNode){\n\t\t\t\twrap.insertBefore(this[ 0 ]);\n\t\t\t}\n\n\t\t\twrap.map(function(){\n\t\t\t\tvar elem=this;\n\n\t\t\t\twhile(elem.firstElementChild){\n\t\t\t\t\telem=elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append(this);\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function(html){\n\t\tif(isFunction(html) ){\n\t\t\treturn this.each(function(i){\n\t\t\t\tjQuery(this).wrapInner(html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function(){\n\t\t\tvar self=jQuery(this),\n\t\t\t\tcontents=self.contents();\n\n\t\t\tif(contents.length){\n\t\t\t\tcontents.wrapAll(html);\n\n\t\t\t}else{\n\t\t\t\tself.append(html);\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function(html){\n\t\tvar htmlIsFunction=isFunction(html);\n\n\t\treturn this.each(function(i){\n\t\t\tjQuery(this).wrapAll(htmlIsFunction ? html.call(this, i):html);\n\t\t});\n\t},\n\n\tunwrap: function(selector){\n\t\tthis.parent(selector).not("body").each(function(){\n\t\t\tjQuery(this).replaceWith(this.childNodes);\n\t\t});\n\t\treturn this;\n\t}\n});\n\n\njQuery.expr.pseudos.hidden=function(elem){\n\treturn !jQuery.expr.pseudos.visible(elem);\n};\njQuery.expr.pseudos.visible=function(elem){\n\treturn !!(elem.offsetWidth||elem.offsetHeight||elem.getClientRects().length);\n};\n\n\n\n\njQuery.ajaxSettings.xhr=function(){\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch(e){}\n};\n\nvar xhrSuccessStatus={\n\n\t\t// File protocol always yields status code 0, assume 200\n\t\t0: 200,\n\n\t\t// Support: IE <=9 only\n\t\t// trac-1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported=jQuery.ajaxSettings.xhr();\n\nsupport.cors = !!xhrSupported&&("withCredentials" in xhrSupported);\nsupport.ajax=xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport(function(options){\n\tvar callback, errorCallback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif(support.cors||xhrSupported&&!options.crossDomain){\n\t\treturn {\n\t\t\tsend: function(headers, complete){\n\t\t\t\tvar i,\n\t\t\t\t\txhr=options.xhr();\n\n\t\t\t\txhr.open(\n\t\t\t\t\toptions.type,\n\t\t\t\t\toptions.url,\n\t\t\t\t\toptions.async,\n\t\t\t\t\toptions.username,\n\t\t\t\t\toptions.password\n\t\t\t\t);\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif(options.xhrFields){\n\t\t\t\t\tfor(i in options.xhrFields){\n\t\t\t\t\t\txhr[ i ]=options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif(options.mimeType&&xhr.overrideMimeType){\n\t\t\t\t\txhr.overrideMimeType(options.mimeType);\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won\'t change header if already provided.\n\t\t\t\tif(!options.crossDomain&&!headers[ "X-Requested-With" ]){\n\t\t\t\t\theaders[ "X-Requested-With" ]="XMLHttpRequest";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor(i in headers){\n\t\t\t\t\txhr.setRequestHeader(i, headers[ i ]);\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback=function(type){\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\tif(callback){\n\t\t\t\t\t\t\tcallback=errorCallback=xhr.onload=\n\t\t\t\t\t\t\t\txhr.onerror=xhr.onabort=xhr.ontimeout=\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange=null;\n\n\t\t\t\t\t\t\tif(type==="abort"){\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t}else if(type==="error"){\n\n\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t// On a manual native abort, IE9 throws\n\t\t\t\t\t\t\t\t// errors on any property access that is not readyState\n\t\t\t\t\t\t\t\tif(typeof xhr.status!=="number"){\n\t\t\t\t\t\t\t\t\tcomplete(0, "error");\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tcomplete(\n\n\t\t\t\t\t\t\t\t\t\t// File: protocol always yields status 0; see trac-8605, trac-14207\n\t\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ]||xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\n\t\t\t\t\t\t\t\t\t// Support: IE <=9 only\n\t\t\t\t\t\t\t\t\t// IE9 has no XHR2 but throws on binary (trac-11426)\n\t\t\t\t\t\t\t\t\t// For XHR2 non-text, let the caller handle it (gh-2498)\n\t\t\t\t\t\t\t\t\t(xhr.responseType||"text")!=="text"||\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText!=="string" ?\n\t\t\t\t\t\t\t\t\t\t{ binary: xhr.response } :\n\t\t\t\t\t\t\t\t\t\t{ text: xhr.responseText },\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload=callback();\n\t\t\t\terrorCallback=xhr.onerror=xhr.ontimeout=callback("error");\n\n\t\t\t\t// Support: IE 9 only\n\t\t\t\t// Use onreadystatechange to replace onabort\n\t\t\t\t// to handle uncaught aborts\n\t\t\t\tif(xhr.onabort!==undefined){\n\t\t\t\t\txhr.onabort=errorCallback;\n\t\t\t\t}else{\n\t\t\t\t\txhr.onreadystatechange=function(){\n\n\t\t\t\t\t\t// Check readyState before timeout as it changes\n\t\t\t\t\t\tif(xhr.readyState===4){\n\n\t\t\t\t\t\t\t// Allow onerror to be called first,\n\t\t\t\t\t\t\t// but that will not handle a native abort\n\t\t\t\t\t\t\t// Also, save errorCallback to a variable\n\t\t\t\t\t\t\t// as xhr.onerror cannot be accessed\n\t\t\t\t\t\t\twindow.setTimeout(function(){\n\t\t\t\t\t\t\t\tif(callback){\n\t\t\t\t\t\t\t\t\terrorCallback();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback=callback("abort");\n\n\t\t\t\ttry {\n\n\t\t\t\t\t// Do send the request (this may raise an exception)\n\t\t\t\t\txhr.send(options.hasContent&&options.data||null);\n\t\t\t\t} catch(e){\n\n\t\t\t\t\t// trac-14683: Only rethrow if this hasn\'t been notified as an error yet\n\t\t\t\t\tif(callback){\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tabort: function(){\n\t\t\t\tif(callback){\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\n// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)\njQuery.ajaxPrefilter(function(s){\n\tif(s.crossDomain){\n\t\ts.contents.script=false;\n\t}\n});\n\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: "text/javascript, application/javascript, " +\n\t\t\t"application/ecmascript, application/x-ecmascript"\n\t},\n\tcontents: {\n\t\tscript: /\\b(?:java|ecma)script\\b/\n\t},\n\tconverters: {\n\t\t"text script": function(text){\n\t\t\tjQuery.globalEval(text);\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache\'s special case and crossDomain\njQuery.ajaxPrefilter("script", function(s){\n\tif(s.cache===undefined){\n\t\ts.cache=false;\n\t}\n\tif(s.crossDomain){\n\t\ts.type="GET";\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport("script", function(s){\n\n\t// This transport only deals with cross domain or forced-by-attrs requests\n\tif(s.crossDomain||s.scriptAttrs){\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function(_, complete){\n\t\t\t\tscript=jQuery("<script>")\n\t\t\t\t\t.attr(s.scriptAttrs||{})\n\t\t\t\t\t.prop({ charset: s.scriptCharset, src: s.url })\n\t\t\t\t\t.on("load error", callback=function(evt){\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback=null;\n\t\t\t\t\t\tif(evt){\n\t\t\t\t\t\t\tcomplete(evt.type==="error" ? 404:200, evt.type);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\n\t\t\t\tdocument.head.appendChild(script[ 0 ]);\n\t\t\t},\n\t\t\tabort: function(){\n\t\t\t\tif(callback){\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\nvar oldCallbacks=[],\n\trjsonp=/(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: "callback",\n\tjsonpCallback: function(){\n\t\tvar callback=oldCallbacks.pop()||(jQuery.expando + "_" +(nonce.guid++) );\n\t\tthis[ callback ]=true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter("json jsonp", function(s, originalSettings, jqXHR){\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp=s.jsonp!==false&&(rjsonp.test(s.url) ?\n\t\t\t"url" :\n\t\t\ttypeof s.data==="string"&&\n\t\t\t\t(s.contentType||"")\n\t\t\t\t\t.indexOf("application/x-www-form-urlencoded")===0&&\n\t\t\t\trjsonp.test(s.data)&&"data"\n\t\t);\n\n\t// Handle iff the expected data type is "jsonp" or we have a parameter to set\n\tif(jsonProp||s.dataTypes[ 0 ]==="jsonp"){\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName=s.jsonpCallback=isFunction(s.jsonpCallback) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif(jsonProp){\n\t\t\ts[ jsonProp ]=s[ jsonProp ].replace(rjsonp, "$1" + callbackName);\n\t\t}else if(s.jsonp!==false){\n\t\t\ts.url +=(rquery.test(s.url) ? "&":"?") + s.jsonp + "=" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[ "script json" ]=function(){\n\t\t\tif(!responseContainer){\n\t\t\t\tjQuery.error(callbackName + " was not called");\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// Force json dataType\n\t\ts.dataTypes[ 0 ]="json";\n\n\t\t// Install callback\n\t\toverwritten=window[ callbackName ];\n\t\twindow[ callbackName ]=function(){\n\t\t\tresponseContainer=arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function(){\n\n\t\t\t// If previous value didn\'t exist - remove it\n\t\t\tif(overwritten===undefined){\n\t\t\t\tjQuery(window).removeProp(callbackName);\n\n\t\t\t// Otherwise restore preexisting value\n\t\t\t}else{\n\t\t\t\twindow[ callbackName ]=overwritten;\n\t\t\t}\n\n\t\t\t// Save back as free\n\t\t\tif(s[ callbackName ]){\n\n\t\t\t\t// Make sure that re-using the options doesn\'t screw things around\n\t\t\t\ts.jsonpCallback=originalSettings.jsonpCallback;\n\n\t\t\t\t// Save the callback name for future use\n\t\t\t\toldCallbacks.push(callbackName);\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif(responseContainer&&isFunction(overwritten) ){\n\t\t\t\toverwritten(responseContainer[ 0 ]);\n\t\t\t}\n\n\t\t\tresponseContainer=overwritten=undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn "script";\n\t}\n});\n\n\n\n\n// Support: Safari 8 only\n// In Safari 8 documents created via document.implementation.createHTMLDocument\n// collapse sibling forms: the second one becomes a child of the first one.\n// Because of that, this security measure has to be disabled in Safari 8.\n// https://bugs.webkit.org/show_bug.cgi?id=137337\nsupport.createHTMLDocument=(function(){\n\tvar body=document.implementation.createHTMLDocument("").body;\n\tbody.innerHTML="<form></form><form></form>";\n\treturn body.childNodes.length===2;\n})();\n\n\n// Argument "data" should be string of html\n// context (optional): If specified, the fragment will be created in this context,\n// defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML=function(data, context, keepScripts){\n\tif(typeof data!=="string"){\n\t\treturn [];\n\t}\n\tif(typeof context==="boolean"){\n\t\tkeepScripts=context;\n\t\tcontext=false;\n\t}\n\n\tvar base, parsed, scripts;\n\n\tif(!context){\n\n\t\t// Stop scripts or inline event handlers from being executed immediately\n\t\t// by using document.implementation\n\t\tif(support.createHTMLDocument){\n\t\t\tcontext=document.implementation.createHTMLDocument("");\n\n\t\t\t// Set the base href for the created document\n\t\t\t// so any parsed elements with URLs\n\t\t\t// are based on the document\'s URL (gh-2965)\n\t\t\tbase=context.createElement("base");\n\t\t\tbase.href=document.location.href;\n\t\t\tcontext.head.appendChild(base);\n\t\t}else{\n\t\t\tcontext=document;\n\t\t}\n\t}\n\n\tparsed=rsingleTag.exec(data);\n\tscripts = !keepScripts&&[];\n\n\t// Single tag\n\tif(parsed){\n\t\treturn [ context.createElement(parsed[ 1 ]) ];\n\t}\n\n\tparsed=buildFragment([ data ], context, scripts);\n\n\tif(scripts&&scripts.length){\n\t\tjQuery(scripts).remove();\n\t}\n\n\treturn jQuery.merge([], parsed.childNodes);\n};\n\n\n\njQuery.fn.load=function(url, params, callback){\n\tvar selector, type, response,\n\t\tself=this,\n\t\toff=url.indexOf(" ");\n\n\tif(off > -1){\n\t\tselector=stripAndCollapse(url.slice(off) );\n\t\turl=url.slice(0, off);\n\t}\n\n\t// If it\'s a function\n\tif(isFunction(params) ){\n\n\t\t// We assume that it\'s the callback\n\t\tcallback=params;\n\t\tparams=undefined;\n\n\t// Otherwise, build a param string\n\t}else if(params&&typeof params==="object"){\n\t\ttype="POST";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif(self.length > 0){\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// If "type" variable is undefined, then "GET" method will be used.\n\t\t\t// Make value of this field explicit since\n\t\t\t// user can override it through ajaxSetup method\n\t\t\ttype: type||"GET",\n\t\t\tdataType: "html",\n\t\t\tdata: params\n\t\t}).done(function(responseText){\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse=arguments;\n\n\t\t\tself.html(selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE \'Permission Denied\' errors\n\t\t\t\tjQuery("<div>").append(jQuery.parseHTML(responseText) ).find(selector) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText);\n\n\t\t// If the request succeeds, this function gets "data", "status", "jqXHR"\n\t\t// but they are ignored because response was set above.\n\t\t// If it fails, this function gets "jqXHR", "status", "error"\n\t\t}).always(callback&&function(jqXHR, status){\n\t\t\tself.each(function(){\n\t\t\t\tcallback.apply(this, response||[ jqXHR.responseText, status, jqXHR ]);\n\t\t\t});\n\t\t});\n\t}\n\n\treturn this;\n};\n\n\n\n\njQuery.expr.pseudos.animated=function(elem){\n\treturn jQuery.grep(jQuery.timers, function(fn){\n\t\treturn elem===fn.elem;\n\t}).length;\n};\n\n\n\n\njQuery.offset={\n\tsetOffset: function(elem, options, i){\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition=jQuery.css(elem, "position"),\n\t\t\tcurElem=jQuery(elem),\n\t\t\tprops={};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif(position==="static"){\n\t\t\telem.style.position="relative";\n\t\t}\n\n\t\tcurOffset=curElem.offset();\n\t\tcurCSSTop=jQuery.css(elem, "top");\n\t\tcurCSSLeft=jQuery.css(elem, "left");\n\t\tcalculatePosition=(position==="absolute"||position==="fixed")&&\n\t\t\t(curCSSTop + curCSSLeft).indexOf("auto") > -1;\n\n\t\t// Need to be able to calculate position if either\n\t\t// top or left is auto and position is either absolute or fixed\n\t\tif(calculatePosition){\n\t\t\tcurPosition=curElem.position();\n\t\t\tcurTop=curPosition.top;\n\t\t\tcurLeft=curPosition.left;\n\n\t\t}else{\n\t\t\tcurTop=parseFloat(curCSSTop)||0;\n\t\t\tcurLeft=parseFloat(curCSSLeft)||0;\n\t\t}\n\n\t\tif(isFunction(options) ){\n\n\t\t\t// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)\n\t\t\toptions=options.call(elem, i, jQuery.extend({}, curOffset) );\n\t\t}\n\n\t\tif(options.top!=null){\n\t\t\tprops.top=(options.top - curOffset.top) + curTop;\n\t\t}\n\t\tif(options.left!=null){\n\t\t\tprops.left=(options.left - curOffset.left) + curLeft;\n\t\t}\n\n\t\tif("using" in options){\n\t\t\toptions.using.call(elem, props);\n\n\t\t}else{\n\t\t\tcurElem.css(props);\n\t\t}\n\t}\n};\n\njQuery.fn.extend({\n\n\t// offset() relates an element\'s border box to the document origin\n\toffset: function(options){\n\n\t\t// Preserve chaining for setter\n\t\tif(arguments.length){\n\t\t\treturn options===undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each(function(i){\n\t\t\t\t\tjQuery.offset.setOffset(this, options, i);\n\t\t\t\t});\n\t\t}\n\n\t\tvar rect, win,\n\t\t\telem=this[ 0 ];\n\n\t\tif(!elem){\n\t\t\treturn;\n\t\t}\n\n\t\t// Return zeros for disconnected and hidden (display: none) elements (gh-2310)\n\t\t// Support: IE <=11 only\n\t\t// Running getBoundingClientRect on a\n\t\t// disconnected node in IE throws an error\n\t\tif(!elem.getClientRects().length){\n\t\t\treturn { top: 0, left: 0 };\n\t\t}\n\n\t\t// Get document-relative position by adding viewport scroll to viewport-relative gBCR\n\t\trect=elem.getBoundingClientRect();\n\t\twin=elem.ownerDocument.defaultView;\n\t\treturn {\n\t\t\ttop: rect.top + win.pageYOffset,\n\t\t\tleft: rect.left + win.pageXOffset\n\t\t};\n\t},\n\n\t// position() relates an element\'s margin box to its offset parent\'s padding box\n\t// This corresponds to the behavior of CSS absolute positioning\n\tposition: function(){\n\t\tif(!this[ 0 ]){\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset, doc,\n\t\t\telem=this[ 0 ],\n\t\t\tparentOffset={ top: 0, left: 0 };\n\n\t\t// position:fixed elements are offset from the viewport, which itself always has zero offset\n\t\tif(jQuery.css(elem, "position")==="fixed"){\n\n\t\t\t// Assume position:fixed implies availability of getBoundingClientRect\n\t\t\toffset=elem.getBoundingClientRect();\n\n\t\t}else{\n\t\t\toffset=this.offset();\n\n\t\t\t// Account for the *real* offset parent, which can be the document or its root element\n\t\t\t// when a statically positioned element is identified\n\t\t\tdoc=elem.ownerDocument;\n\t\t\toffsetParent=elem.offsetParent||doc.documentElement;\n\t\t\twhile(offsetParent&&\n\t\t\t\t(offsetParent===doc.body||offsetParent===doc.documentElement)&&\n\t\t\t\tjQuery.css(offsetParent, "position")==="static"){\n\n\t\t\t\toffsetParent=offsetParent.parentNode;\n\t\t\t}\n\t\t\tif(offsetParent&&offsetParent!==elem&&offsetParent.nodeType===1){\n\n\t\t\t\t// Incorporate borders into its offset, since they are outside its content origin\n\t\t\t\tparentOffset=jQuery(offsetParent).offset();\n\t\t\t\tparentOffset.top +=jQuery.css(offsetParent, "borderTopWidth", true);\n\t\t\t\tparentOffset.left +=jQuery.css(offsetParent, "borderLeftWidth", true);\n\t\t\t}\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css(elem, "marginTop", true),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css(elem, "marginLeft", true)\n\t\t};\n\t},\n\n\t// This method will return documentElement in the following cases:\n\t// 1) For the element inside the iframe without offsetParent, this method will return\n\t//    documentElement of the parent window\n\t// 2) For the hidden or detached element\n\t// 3) For body or html element, i.e. in case of the html node - it will return itself\n\t//\n\t// but those exceptions were never presented as a real life use-cases\n\t// and might be considered as more preferable results.\n\t//\n\t// This logic, however, is not guaranteed and can change at any point in the future\n\toffsetParent: function(){\n\t\treturn this.map(function(){\n\t\t\tvar offsetParent=this.offsetParent;\n\n\t\t\twhile(offsetParent&&jQuery.css(offsetParent, "position")==="static"){\n\t\t\t\toffsetParent=offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent||documentElement;\n\t\t});\n\t}\n});\n\n// Create scrollLeft and scrollTop methods\njQuery.each({ scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function(method, prop){\n\tvar top="pageYOffset"===prop;\n\n\tjQuery.fn[ method ]=function(val){\n\t\treturn access(this, function(elem, method, val){\n\n\t\t\t// Coalesce documents and windows\n\t\t\tvar win;\n\t\t\tif(isWindow(elem) ){\n\t\t\t\twin=elem;\n\t\t\t}else if(elem.nodeType===9){\n\t\t\t\twin=elem.defaultView;\n\t\t\t}\n\n\t\t\tif(val===undefined){\n\t\t\t\treturn win ? win[ prop ]:elem[ method ];\n\t\t\t}\n\n\t\t\tif(win){\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val:win.pageXOffset,\n\t\t\t\t\ttop ? val:win.pageYOffset\n\t\t\t\t);\n\n\t\t\t}else{\n\t\t\t\telem[ method ]=val;\n\t\t\t}\n\t\t}, method, val, arguments.length);\n\t};\n});\n\n// Support: Safari <=7 - 9.1, Chrome <=37 - 49\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347\n// getComputedStyle returns percent when specified for top/left/bottom/right;\n// rather than make the css module depend on the offset module, just check for it here\njQuery.each([ "top", "left" ], function(_i, prop){\n\tjQuery.cssHooks[ prop ]=addGetHookIf(support.pixelPosition,\n\t\tfunction(elem, computed){\n\t\t\tif(computed){\n\t\t\t\tcomputed=curCSS(elem, prop);\n\n\t\t\t\t// If curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test(computed) ?\n\t\t\t\t\tjQuery(elem).position()[ prop ] + "px" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n});\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each({ Height: "height", Width: "width" }, function(name, type){\n\tjQuery.each({\n\t\tpadding: "inner" + name,\n\t\tcontent: type,\n\t\t"": "outer" + name\n\t}, function(defaultExtra, funcName){\n\n\t\t// Margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ]=function(margin, value){\n\t\t\tvar chainable=arguments.length&&(defaultExtra||typeof margin!=="boolean"),\n\t\t\t\textra=defaultExtra||(margin===true||value===true ? "margin":"border");\n\n\t\t\treturn access(this, function(elem, type, value){\n\t\t\t\tvar doc;\n\n\t\t\t\tif(isWindow(elem) ){\n\n\t\t\t\t\t// $(window).outerWidth/Height return w/h including scrollbars (gh-1729)\n\t\t\t\t\treturn funcName.indexOf("outer")===0 ?\n\t\t\t\t\t\telem[ "inner" + name ] :\n\t\t\t\t\t\telem.document.documentElement[ "client" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif(elem.nodeType===9){\n\t\t\t\t\tdoc=elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ "scroll" + name ], doc[ "scroll" + name ],\n\t\t\t\t\t\telem.body[ "offset" + name ], doc[ "offset" + name ],\n\t\t\t\t\t\tdoc[ "client" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value===undefined ?\n\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css(elem, type, extra) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style(elem, type, value, extra);\n\t\t\t}, type, chainable ? margin:undefined, chainable);\n\t\t};\n\t});\n});\n\n\njQuery.each([\n\t"ajaxStart",\n\t"ajaxStop",\n\t"ajaxComplete",\n\t"ajaxError",\n\t"ajaxSuccess",\n\t"ajaxSend"\n], function(_i, type){\n\tjQuery.fn[ type ]=function(fn){\n\t\treturn this.on(type, fn);\n\t};\n});\n\n\n\n\njQuery.fn.extend({\n\n\tbind: function(types, data, fn){\n\t\treturn this.on(types, null, data, fn);\n\t},\n\tunbind: function(types, fn){\n\t\treturn this.off(types, null, fn);\n\t},\n\n\tdelegate: function(selector, types, data, fn){\n\t\treturn this.on(types, selector, data, fn);\n\t},\n\tundelegate: function(selector, types, fn){\n\n\t\t//(namespace) or(selector, types [, fn])\n\t\treturn arguments.length===1 ?\n\t\t\tthis.off(selector, "**") :\n\t\t\tthis.off(types, selector||"**", fn);\n\t},\n\n\thover: function(fnOver, fnOut){\n\t\treturn this\n\t\t\t.on("mouseenter", fnOver)\n\t\t\t.on("mouseleave", fnOut||fnOver);\n\t}\n});\n\njQuery.each(\n\t("blur focus focusin focusout resize scroll click dblclick " +\n\t"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +\n\t"change select submit keydown keypress keyup contextmenu").split(" "),\n\tfunction(_i, name){\n\n\t\t// Handle event binding\n\t\tjQuery.fn[ name ]=function(data, fn){\n\t\t\treturn arguments.length > 0 ?\n\t\t\t\tthis.on(name, null, data, fn) :\n\t\t\t\tthis.trigger(name);\n\t\t};\n\t}\n);\n\n\n\n\n// Support: Android <=4.0 only\n// Make sure we trim BOM and NBSP\n// Require that the "whitespace run" starts from a non-whitespace\n// to avoid O(N^2) behavior when the engine would try matching "\\s+$" at each space position.\nvar rtrim=/^[\\s\\uFEFF\\xA0]+|([^\\s\\uFEFF\\xA0])[\\s\\uFEFF\\xA0]+$/g;\n\n// Bind a function to a context, optionally partially applying any\n// arguments.\n// jQuery.proxy is deprecated to promote standards (specifically Function#bind)\n// However, it is not slated for removal any time soon\njQuery.proxy=function(fn, context){\n\tvar tmp, args, proxy;\n\n\tif(typeof context==="string"){\n\t\ttmp=fn[ context ];\n\t\tcontext=fn;\n\t\tfn=tmp;\n\t}\n\n\t// Quick check to determine if target is callable, in the spec\n\t// this throws a TypeError, but we will just return undefined.\n\tif(!isFunction(fn) ){\n\t\treturn undefined;\n\t}\n\n\t// Simulated bind\n\targs=slice.call(arguments, 2);\n\tproxy=function(){\n\t\treturn fn.apply(context||this, args.concat(slice.call(arguments) ));\n\t};\n\n\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\tproxy.guid=fn.guid=fn.guid||jQuery.guid++;\n\n\treturn proxy;\n};\n\njQuery.holdReady=function(hold){\n\tif(hold){\n\t\tjQuery.readyWait++;\n\t}else{\n\t\tjQuery.ready(true);\n\t}\n};\njQuery.isArray=Array.isArray;\njQuery.parseJSON=JSON.parse;\njQuery.nodeName=nodeName;\njQuery.isFunction=isFunction;\njQuery.isWindow=isWindow;\njQuery.camelCase=camelCase;\njQuery.type=toType;\n\njQuery.now=Date.now;\n\njQuery.isNumeric=function(obj){\n\n\t// As of jQuery 3.0, isNumeric is limited to\n\t// strings and numbers (primitives or objects)\n\t// that can be coerced to finite numbers (gh-2662)\n\tvar type=jQuery.type(obj);\n\treturn(type==="number"||type==="string")&&\n\n\t\t// parseFloat NaNs numeric-cast false positives ("")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals ("0x...")\n\t\t// subtraction forces infinities to NaN\n\t\t!isNaN(obj - parseFloat(obj) );\n};\n\njQuery.trim=function(text){\n\treturn text==null ?\n\t\t"" :\n\t\t(text + "").replace(rtrim, "$1");\n};\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n\n// Note that for maximum portability, libraries that are not jQuery should\n// declare themselves as anonymous modules, and avoid setting a global if an\n// AMD loader is present. jQuery is a special case. For more information, see\n// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\n\nif(true){\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__=[], __WEBPACK_AMD_DEFINE_RESULT__=(function(){\n\t\treturn jQuery;\n\t}).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),\n\t\t__WEBPACK_AMD_DEFINE_RESULT__!==undefined&&(module.exports=__WEBPACK_AMD_DEFINE_RESULT__));\n}\n\n\n\n\nvar\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery=window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$=window.$;\n\njQuery.noConflict=function(deep){\n\tif(window.$===jQuery){\n\t\twindow.$=_$;\n\t}\n\n\tif(deep&&window.jQuery===jQuery){\n\t\twindow.jQuery=_jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (trac-13566)\nif(typeof noGlobal==="undefined"){\n\twindow.jQuery=window.$=jQuery;\n}\n\n\n\n\nreturn jQuery;\n});\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/jquery/dist/jquery.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   VideoTrack: ()=> ( VideoTrack),\n   VideoTrackKind: ()=> ( VideoTrackKind)\n });\n var _video_track_list_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 3);\n var _video_rendition_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 9);\n var _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__( 1);\n\n\n\nconst VideoTrackKind={\n  alternative: "alternative",\n  captions: "captions",\n  main: "main",\n  sign: "sign",\n  subtitles: "subtitles",\n  commentary: "commentary"\n};\nclass VideoTrack {\n  id;\n  kind;\n  label="";\n  language="";\n  sourceBuffer;\n  #selected=false;\n  addRendition(src, width, height, codec, bitrate, frameRate){\n    const rendition=new _video_rendition_js__WEBPACK_IMPORTED_MODULE_1__.VideoRendition();\n    rendition.src=src;\n    rendition.width=width;\n    rendition.height=height;\n    rendition.frameRate=frameRate;\n    rendition.bitrate=bitrate;\n    rendition.codec=codec;\n    (0,_video_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__.addRendition)(this, rendition);\n    return rendition;\n  }\n  removeRendition(rendition){\n    (0,_video_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__.removeRendition)(rendition);\n  }\n  get selected(){\n    return this.#selected;\n  }\n  set selected(val){\n    if(this.#selected===val) return;\n    this.#selected=val;\n    if(val!==true) return;\n    (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_0__.selectedChanged)(this);\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/video-track.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   VideoRendition: ()=> ( VideoRendition)\n });\n var _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 1);\n\nclass VideoRendition {\n  src;\n  id;\n  width;\n  height;\n  bitrate;\n  frameRate;\n  codec;\n  #selected=false;\n  get selected(){\n    return this.#selected;\n  }\n  set selected(val){\n    if(this.#selected===val) return;\n    this.#selected=val;\n    (0,_video_rendition_list_js__WEBPACK_IMPORTED_MODULE_0__.selectedChanged)(this);\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/video-rendition.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AudioTrack: ()=> ( AudioTrack),\n   AudioTrackKind: ()=> ( AudioTrackKind)\n });\n var _audio_rendition_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 11);\n var _audio_track_list_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 6);\n var _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__( 2);\n\n\n\nconst AudioTrackKind={\n  alternative: "alternative",\n  descriptions: "descriptions",\n  main: "main",\n  "main-desc": "main-desc",\n  translation: "translation",\n  commentary: "commentary"\n};\nclass AudioTrack {\n  id;\n  kind;\n  label="";\n  language="";\n  sourceBuffer;\n  #enabled=false;\n  addRendition(src, codec, bitrate){\n    const rendition=new _audio_rendition_js__WEBPACK_IMPORTED_MODULE_0__.AudioRendition();\n    rendition.src=src;\n    rendition.codec=codec;\n    rendition.bitrate=bitrate;\n    (0,_audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__.addRendition)(this, rendition);\n    return rendition;\n  }\n  removeRendition(rendition){\n    (0,_audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_2__.removeRendition)(rendition);\n  }\n  get enabled(){\n    return this.#enabled;\n  }\n  set enabled(val){\n    if(this.#enabled===val) return;\n    this.#enabled=val;\n    (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_1__.enabledChanged)(this);\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/audio-track.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AudioRendition: ()=> ( AudioRendition)\n });\n var _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 2);\n\nclass AudioRendition {\n  src;\n  id;\n  bitrate;\n  codec;\n  #selected=false;\n  get selected(){\n    return this.#selected;\n  }\n  set selected(val){\n    if(this.#selected===val) return;\n    this.#selected=val;\n    (0,_audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_0__.selectedChanged)(this);\n  }\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/audio-rendition.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   MediaTracksMixin: ()=> ( MediaTracksMixin)\n });\n var _video_track_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 8);\n var _video_track_list_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 3);\n var _audio_track_js__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__( 10);\n var _audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__( 6);\n var _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__( 1);\n var _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__( 2);\n var _utils_js__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__( 0);\n\n\n\n\n\n\n\nconst nativeVideoTracksFn=getBaseMediaTracksFn(globalThis.HTMLMediaElement, "video");\nconst nativeAudioTracksFn=getBaseMediaTracksFn(globalThis.HTMLMediaElement, "audio");\nfunction MediaTracksMixin(MediaElementClass){\n  if(!MediaElementClass?.prototype) return MediaElementClass;\n  const videoTracksFn=getBaseMediaTracksFn(MediaElementClass, "video");\n  if(!videoTracksFn||`${videoTracksFn}`.includes("[native code]")){\n    Object.defineProperty(MediaElementClass.prototype, "videoTracks", {\n      get(){\n        return getVideoTracks(this);\n      }\n    });\n  }\n  const audioTracksFn=getBaseMediaTracksFn(MediaElementClass, "audio");\n  if(!audioTracksFn||`${audioTracksFn}`.includes("[native code]")){\n    Object.defineProperty(MediaElementClass.prototype, "audioTracks", {\n      get(){\n        return getAudioTracks(this);\n      }\n    });\n  }\n  if(!("addVideoTrack" in MediaElementClass.prototype)){\n    MediaElementClass.prototype.addVideoTrack=function(kind, label="", language=""){\n      const track=new _video_track_js__WEBPACK_IMPORTED_MODULE_0__.VideoTrack();\n      track.kind=kind;\n      track.label=label;\n      track.language=language;\n      (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.addVideoTrack)(this, track);\n      return track;\n    };\n  }\n  if(!("removeVideoTrack" in MediaElementClass.prototype)){\n    MediaElementClass.prototype.removeVideoTrack=_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.removeVideoTrack;\n  }\n  if(!("addAudioTrack" in MediaElementClass.prototype)){\n    MediaElementClass.prototype.addAudioTrack=function(kind, label="", language=""){\n      const track=new _audio_track_js__WEBPACK_IMPORTED_MODULE_2__.AudioTrack();\n      track.kind=kind;\n      track.label=label;\n      track.language=language;\n      (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.addAudioTrack)(this, track);\n      return track;\n    };\n  }\n  if(!("removeAudioTrack" in MediaElementClass.prototype)){\n    MediaElementClass.prototype.removeAudioTrack=_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.removeAudioTrack;\n  }\n  if(!("videoRenditions" in MediaElementClass.prototype)){\n    Object.defineProperty(MediaElementClass.prototype, "videoRenditions", {\n      get(){\n        return initVideoRenditions(this);\n      }\n    });\n  }\n  const initVideoRenditions=(media)=> {\n    let renditions=(0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).videoRenditions;\n    if(!renditions){\n      renditions=new _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_4__.VideoRenditionList();\n      (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(renditions).media=media;\n      (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).videoRenditions=renditions;\n    }\n    return renditions;\n  };\n  if(!("audioRenditions" in MediaElementClass.prototype)){\n    Object.defineProperty(MediaElementClass.prototype, "audioRenditions", {\n      get(){\n        return initAudioRenditions(this);\n      }\n    });\n  }\n  const initAudioRenditions=(media)=> {\n    let renditions=(0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).audioRenditions;\n    if(!renditions){\n      renditions=new _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_5__.AudioRenditionList();\n      (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(renditions).media=media;\n      (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).audioRenditions=renditions;\n    }\n    return renditions;\n  };\n  return MediaElementClass;\n}\nfunction getBaseMediaTracksFn(MediaElementClass, type){\n  if(MediaElementClass?.prototype){\n    return Object.getOwnPropertyDescriptor(MediaElementClass.prototype, `${type}Tracks`)?.get;\n  }\n}\nfunction getVideoTracks(media){\n  let tracks=(0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).videoTracks;\n  if(!tracks){\n    tracks=new _video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.VideoTrackList();\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).videoTracks=tracks;\n    if(nativeVideoTracksFn){\n      const nativeTracks=nativeVideoTracksFn.call(media.nativeEl ?? media);\n      for (const nativeTrack of nativeTracks){\n        (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.addVideoTrack)(media, nativeTrack);\n      }\n      nativeTracks.addEventListener("change", ()=> {\n        tracks.dispatchEvent(new Event("change"));\n      });\n      nativeTracks.addEventListener("addtrack", (event)=> {\n        if([...tracks].some((t)=> t instanceof _video_track_js__WEBPACK_IMPORTED_MODULE_0__.VideoTrack)){\n          for (const nativeTrack of nativeTracks){\n            (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.removeVideoTrack)(nativeTrack);\n          }\n          return;\n        }\n        (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.addVideoTrack)(media, event.track);\n      });\n      nativeTracks.addEventListener("removetrack", (event)=> {\n        (0,_video_track_list_js__WEBPACK_IMPORTED_MODULE_1__.removeVideoTrack)(event.track);\n      });\n    }\n  }\n  return tracks;\n}\nfunction getAudioTracks(media){\n  let tracks=(0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).audioTracks;\n  if(!tracks){\n    tracks=new _audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.AudioTrackList();\n    (0,_utils_js__WEBPACK_IMPORTED_MODULE_6__.getPrivate)(media).audioTracks=tracks;\n    if(nativeAudioTracksFn){\n      const nativeTracks=nativeAudioTracksFn.call(media.nativeEl ?? media);\n      for (const nativeTrack of nativeTracks){\n        (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.addAudioTrack)(media, nativeTrack);\n      }\n      nativeTracks.addEventListener("change", ()=> {\n        tracks.dispatchEvent(new Event("change"));\n      });\n      nativeTracks.addEventListener("addtrack", (event)=> {\n        if([...tracks].some((t)=> t instanceof _audio_track_js__WEBPACK_IMPORTED_MODULE_2__.AudioTrack)){\n          for (const nativeTrack of nativeTracks){\n            (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.removeAudioTrack)(nativeTrack);\n          }\n          return;\n        }\n        (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.addAudioTrack)(media, event.track);\n      });\n      nativeTracks.addEventListener("removetrack", (event)=> {\n        (0,_audio_track_list_js__WEBPACK_IMPORTED_MODULE_3__.removeAudioTrack)(event.track);\n      });\n    }\n  }\n  return tracks;\n}\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/mixin.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AbrController: ()=> ( AbrController),\n   AttrList: ()=> ( AttrList),\n   AudioStreamController: ()=> ( AudioStreamController),\n   AudioTrackController: ()=> ( AudioTrackController),\n   BasePlaylistController: ()=> ( BasePlaylistController),\n   BaseSegment: ()=> ( BaseSegment),\n   BaseStreamController: ()=> ( BaseStreamController),\n   BufferController: ()=> ( BufferController),\n   CMCDController: ()=> ( CMCDController),\n   CapLevelController: ()=> ( CapLevelController),\n   ChunkMetadata: ()=> ( ChunkMetadata),\n   ContentSteeringController: ()=> ( ContentSteeringController),\n   DateRange: ()=> ( DateRange),\n   EMEController: ()=> ( EMEController),\n   ErrorActionFlags: ()=> ( ErrorActionFlags),\n   ErrorController: ()=> ( ErrorController),\n   ErrorDetails: ()=> ( ErrorDetails),\n   ErrorTypes: ()=> ( ErrorTypes),\n   Events: ()=> ( Events),\n   FPSController: ()=> ( FPSController),\n   Fragment: ()=> ( Fragment),\n   Hls: ()=> ( Hls),\n   HlsSkip: ()=> ( HlsSkip),\n   HlsUrlParameters: ()=> ( HlsUrlParameters),\n   KeySystemFormats: ()=> ( KeySystemFormats),\n   KeySystems: ()=> ( KeySystems),\n   Level: ()=> ( Level),\n   LevelDetails: ()=> ( LevelDetails),\n   LevelKey: ()=> ( LevelKey),\n   LoadStats: ()=> ( LoadStats),\n   MetadataSchema: ()=> ( MetadataSchema),\n   NetworkErrorAction: ()=> ( NetworkErrorAction),\n   Part: ()=> ( Part),\n   PlaylistLevelType: ()=> ( PlaylistLevelType),\n   SubtitleStreamController: ()=> ( SubtitleStreamController),\n   SubtitleTrackController: ()=> ( SubtitleTrackController),\n   TimelineController: ()=> ( TimelineController),\n   \"default\": ()=> ( Hls),\n   getMediaSource: ()=> ( getMediaSource),\n   isMSESupported: ()=> ( isMSESupported),\n   isSupported: ()=> ( isSupported)\n });\nfunction getDefaultExportFromCjs (x){\n\treturn x&&x.__esModule&&Object.prototype.hasOwnProperty.call(x, 'default') ? x['default']:x;\n}\n\nvar urlToolkit={exports: {}};\n\n(function (module, exports){\n\t// see https://tools.ietf.org/html/rfc1808\n\n\t(function (root){\n\t  var URL_REGEX=\n\t    /^(?=((?:[a-zA-Z0-9+\\-.]+:)?))\\1(?=((?:\\/\\/[^\\/?#]*)?))\\2(?=((?:(?:[^?#\\/]*\\/)*[^;?#\\/]*)?))\\3((?:;[^?#]*)?)(\\?[^#]*)?(#[^]*)?$/;\n\t  var FIRST_SEGMENT_REGEX=/^(?=([^\\/?#]*))\\1([^]*)$/;\n\t  var SLASH_DOT_REGEX=/(?:\\/|^)\\.(?=\\/)/g;\n\t  var SLASH_DOT_DOT_REGEX=/(?:\\/|^)\\.\\.\\/(?!\\.\\.\\/)[^\\/]*(?=\\/)/g;\n\n\t  var URLToolkit={\n\t    // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //\n\t    // E.g\n\t    // With opts.alwaysNormalize=false (default, spec compliant)\n\t    // http://a.com/b/cd + /e/f/../g=> http://a.com/e/f/../g\n\t    // With opts.alwaysNormalize=true (not spec compliant)\n\t    // http://a.com/b/cd + /e/f/../g=> http://a.com/e/g\n\t    buildAbsoluteURL: function (baseURL, relativeURL, opts){\n\t      opts=opts||{};\n\t      // remove any remaining space and CRLF\n\t      baseURL=baseURL.trim();\n\t      relativeURL=relativeURL.trim();\n\t      if(!relativeURL){\n\t        // 2a) If the embedded URL is entirely empty, it inherits the\n\t        // entire base URL (i.e., is set equal to the base URL)\n\t        // and we are done.\n\t        if(!opts.alwaysNormalize){\n\t          return baseURL;\n\t        }\n\t        var basePartsForNormalise=URLToolkit.parseURL(baseURL);\n\t        if(!basePartsForNormalise){\n\t          throw new Error('Error trying to parse base URL.');\n\t        }\n\t        basePartsForNormalise.path=URLToolkit.normalizePath(\n\t          basePartsForNormalise.path\n\t);\n\t        return URLToolkit.buildURLFromParts(basePartsForNormalise);\n\t      }\n\t      var relativeParts=URLToolkit.parseURL(relativeURL);\n\t      if(!relativeParts){\n\t        throw new Error('Error trying to parse relative URL.');\n\t      }\n\t      if(relativeParts.scheme){\n\t        // 2b) If the embedded URL starts with a scheme name, it is\n\t        // interpreted as an absolute URL and we are done.\n\t        if(!opts.alwaysNormalize){\n\t          return relativeURL;\n\t        }\n\t        relativeParts.path=URLToolkit.normalizePath(relativeParts.path);\n\t        return URLToolkit.buildURLFromParts(relativeParts);\n\t      }\n\t      var baseParts=URLToolkit.parseURL(baseURL);\n\t      if(!baseParts){\n\t        throw new Error('Error trying to parse base URL.');\n\t      }\n\t      if(!baseParts.netLoc&&baseParts.path&&baseParts.path[0]!=='/'){\n\t        // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc\n\t        // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'\n\t        var pathParts=FIRST_SEGMENT_REGEX.exec(baseParts.path);\n\t        baseParts.netLoc=pathParts[1];\n\t        baseParts.path=pathParts[2];\n\t      }\n\t      if(baseParts.netLoc&&!baseParts.path){\n\t        baseParts.path='/';\n\t      }\n\t      var builtParts={\n\t        // 2c) Otherwise, the embedded URL inherits the scheme of\n\t        // the base URL.\n\t        scheme: baseParts.scheme,\n\t        netLoc: relativeParts.netLoc,\n\t        path: null,\n\t        params: relativeParts.params,\n\t        query: relativeParts.query,\n\t        fragment: relativeParts.fragment,\n\t      };\n\t      if(!relativeParts.netLoc){\n\t        // 3) If the embedded URL's <net_loc> is non-empty, we skip to\n\t        // Step 7.  Otherwise, the embedded URL inherits the <net_loc>\n\t        // (if any) of the base URL.\n\t        builtParts.netLoc=baseParts.netLoc;\n\t        // 4) If the embedded URL path is preceded by a slash \"/\", the\n\t        // path is not relative and we skip to Step 7.\n\t        if(relativeParts.path[0]!=='/'){\n\t          if(!relativeParts.path){\n\t            // 5) If the embedded URL path is empty (and not preceded by a\n\t            // slash), then the embedded URL inherits the base URL path\n\t            builtParts.path=baseParts.path;\n\t            // 5a) if the embedded URL's <params> is non-empty, we skip to\n\t            // step 7; otherwise, it inherits the <params> of the base\n\t            // URL (if any) and\n\t            if(!relativeParts.params){\n\t              builtParts.params=baseParts.params;\n\t              // 5b) if the embedded URL's <query> is non-empty, we skip to\n\t              // step 7; otherwise, it inherits the <query> of the base\n\t              // URL (if any) and we skip to step 7.\n\t              if(!relativeParts.query){\n\t                builtParts.query=baseParts.query;\n\t              }\n\t            }\n\t          }else{\n\t            // 6) The last segment of the base URL's path (anything\n\t            // following the rightmost slash \"/\", or the entire path if no\n\t            // slash is present) is removed and the embedded URL's path is\n\t            // appended in its place.\n\t            var baseURLPath=baseParts.path;\n\t            var newPath=\n\t              baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) +\n\t              relativeParts.path;\n\t            builtParts.path=URLToolkit.normalizePath(newPath);\n\t          }\n\t        }\n\t      }\n\t      if(builtParts.path===null){\n\t        builtParts.path=opts.alwaysNormalize\n\t          ? URLToolkit.normalizePath(relativeParts.path)\n\t:relativeParts.path;\n\t      }\n\t      return URLToolkit.buildURLFromParts(builtParts);\n\t    },\n\t    parseURL: function (url){\n\t      var parts=URL_REGEX.exec(url);\n\t      if(!parts){\n\t        return null;\n\t      }\n\t      return {\n\t        scheme: parts[1]||'',\n\t        netLoc: parts[2]||'',\n\t        path: parts[3]||'',\n\t        params: parts[4]||'',\n\t        query: parts[5]||'',\n\t        fragment: parts[6]||'',\n\t      };\n\t    },\n\t    normalizePath: function (path){\n\t      // The following operations are\n\t      // then applied, in order, to the new path:\n\t      // 6a) All occurrences of \"./\", where \".\" is a complete path\n\t      // segment, are removed.\n\t      // 6b) If the path ends with \".\" as a complete path segment,\n\t      // that \".\" is removed.\n\t      path=path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');\n\t      // 6c) All occurrences of \"<segment>/../\", where <segment> is a\n\t      // complete path segment not equal to \"..\", are removed.\n\t      // Removal of these path segments is performed iteratively,\n\t      // removing the leftmost matching pattern on each iteration,\n\t      // until no matching pattern remains.\n\t      // 6d) If the path ends with \"<segment>/..\", where <segment> is a\n\t      // complete path segment not equal to \"..\", that\n\t      // \"<segment>/..\" is removed.\n\t      while (\n\t        path.length!==(path=path.replace(SLASH_DOT_DOT_REGEX, '')).length\n\t){}\n\t      return path.split('').reverse().join('');\n\t    },\n\t    buildURLFromParts: function (parts){\n\t      return (\n\t        parts.scheme +\n\t        parts.netLoc +\n\t        parts.path +\n\t        parts.params +\n\t        parts.query +\n\t        parts.fragment\n\t);\n\t    },\n\t  };\n\n\t  module.exports=URLToolkit;\n\t})(); \n} (urlToolkit));\n\nvar urlToolkitExports=urlToolkit.exports;\n\nfunction ownKeys(e, r){\n  var t=Object.keys(e);\n  if(Object.getOwnPropertySymbols){\n    var o=Object.getOwnPropertySymbols(e);\n    r&&(o=o.filter(function (r){\n      return Object.getOwnPropertyDescriptor(e, r).enumerable;\n    })), t.push.apply(t, o);\n  }\n  return t;\n}\nfunction _objectSpread2(e){\n  for (var r=1; r < arguments.length; r++){\n    var t=null!=arguments[r] ? arguments[r]:{};\n    r % 2 ? ownKeys(Object(t), !0).forEach(function (r){\n      _defineProperty(e, r, t[r]);\n    }):Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)):ownKeys(Object(t)).forEach(function (r){\n      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));\n    });\n  }\n  return e;\n}\nfunction _toPrimitive(t, r){\n  if(\"object\"!=typeof t||!t) return t;\n  var e=t[Symbol.toPrimitive];\n  if(void 0!==e){\n    var i=e.call(t, r||\"default\");\n    if(\"object\"!=typeof i) return i;\n    throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n  }\n  return (\"string\"===r ? String:Number)(t);\n}\nfunction _toPropertyKey(t){\n  var i=_toPrimitive(t, \"string\");\n  return \"symbol\"==typeof i ? i:String(i);\n}\nfunction _defineProperty(obj, key, value){\n  key=_toPropertyKey(key);\n  if(key in obj){\n    Object.defineProperty(obj, key, {\n      value: value,\n      enumerable: true,\n      configurable: true,\n      writable: true\n    });\n  }else{\n    obj[key]=value;\n  }\n  return obj;\n}\nfunction _extends(){\n  _extends=Object.assign ? Object.assign.bind():function (target){\n    for (var i=1; i < arguments.length; i++){\n      var source=arguments[i];\n      for (var key in source){\n        if(Object.prototype.hasOwnProperty.call(source, key)){\n          target[key]=source[key];\n        }\n      }\n    }\n    return target;\n  };\n  return _extends.apply(this, arguments);\n}\n\n// https://caniuse.com/mdn-javascript_builtins_number_isfinite\nconst isFiniteNumber=Number.isFinite||function (value){\n  return typeof value==='number'&&isFinite(value);\n};\n\n// https://caniuse.com/mdn-javascript_builtins_number_issafeinteger\nconst isSafeInteger=Number.isSafeInteger||function (value){\n  return typeof value==='number'&&Math.abs(value) <=MAX_SAFE_INTEGER;\n};\nconst MAX_SAFE_INTEGER=Number.MAX_SAFE_INTEGER||9007199254740991;\n\nlet Events=function (Events){\n  Events[\"MEDIA_ATTACHING\"]=\"hlsMediaAttaching\";\n  Events[\"MEDIA_ATTACHED\"]=\"hlsMediaAttached\";\n  Events[\"MEDIA_DETACHING\"]=\"hlsMediaDetaching\";\n  Events[\"MEDIA_DETACHED\"]=\"hlsMediaDetached\";\n  Events[\"BUFFER_RESET\"]=\"hlsBufferReset\";\n  Events[\"BUFFER_CODECS\"]=\"hlsBufferCodecs\";\n  Events[\"BUFFER_CREATED\"]=\"hlsBufferCreated\";\n  Events[\"BUFFER_APPENDING\"]=\"hlsBufferAppending\";\n  Events[\"BUFFER_APPENDED\"]=\"hlsBufferAppended\";\n  Events[\"BUFFER_EOS\"]=\"hlsBufferEos\";\n  Events[\"BUFFER_FLUSHING\"]=\"hlsBufferFlushing\";\n  Events[\"BUFFER_FLUSHED\"]=\"hlsBufferFlushed\";\n  Events[\"MANIFEST_LOADING\"]=\"hlsManifestLoading\";\n  Events[\"MANIFEST_LOADED\"]=\"hlsManifestLoaded\";\n  Events[\"MANIFEST_PARSED\"]=\"hlsManifestParsed\";\n  Events[\"LEVEL_SWITCHING\"]=\"hlsLevelSwitching\";\n  Events[\"LEVEL_SWITCHED\"]=\"hlsLevelSwitched\";\n  Events[\"LEVEL_LOADING\"]=\"hlsLevelLoading\";\n  Events[\"LEVEL_LOADED\"]=\"hlsLevelLoaded\";\n  Events[\"LEVEL_UPDATED\"]=\"hlsLevelUpdated\";\n  Events[\"LEVEL_PTS_UPDATED\"]=\"hlsLevelPtsUpdated\";\n  Events[\"LEVELS_UPDATED\"]=\"hlsLevelsUpdated\";\n  Events[\"AUDIO_TRACKS_UPDATED\"]=\"hlsAudioTracksUpdated\";\n  Events[\"AUDIO_TRACK_SWITCHING\"]=\"hlsAudioTrackSwitching\";\n  Events[\"AUDIO_TRACK_SWITCHED\"]=\"hlsAudioTrackSwitched\";\n  Events[\"AUDIO_TRACK_LOADING\"]=\"hlsAudioTrackLoading\";\n  Events[\"AUDIO_TRACK_LOADED\"]=\"hlsAudioTrackLoaded\";\n  Events[\"SUBTITLE_TRACKS_UPDATED\"]=\"hlsSubtitleTracksUpdated\";\n  Events[\"SUBTITLE_TRACKS_CLEARED\"]=\"hlsSubtitleTracksCleared\";\n  Events[\"SUBTITLE_TRACK_SWITCH\"]=\"hlsSubtitleTrackSwitch\";\n  Events[\"SUBTITLE_TRACK_LOADING\"]=\"hlsSubtitleTrackLoading\";\n  Events[\"SUBTITLE_TRACK_LOADED\"]=\"hlsSubtitleTrackLoaded\";\n  Events[\"SUBTITLE_FRAG_PROCESSED\"]=\"hlsSubtitleFragProcessed\";\n  Events[\"CUES_PARSED\"]=\"hlsCuesParsed\";\n  Events[\"NON_NATIVE_TEXT_TRACKS_FOUND\"]=\"hlsNonNativeTextTracksFound\";\n  Events[\"INIT_PTS_FOUND\"]=\"hlsInitPtsFound\";\n  Events[\"FRAG_LOADING\"]=\"hlsFragLoading\";\n  Events[\"FRAG_LOAD_EMERGENCY_ABORTED\"]=\"hlsFragLoadEmergencyAborted\";\n  Events[\"FRAG_LOADED\"]=\"hlsFragLoaded\";\n  Events[\"FRAG_DECRYPTED\"]=\"hlsFragDecrypted\";\n  Events[\"FRAG_PARSING_INIT_SEGMENT\"]=\"hlsFragParsingInitSegment\";\n  Events[\"FRAG_PARSING_USERDATA\"]=\"hlsFragParsingUserdata\";\n  Events[\"FRAG_PARSING_METADATA\"]=\"hlsFragParsingMetadata\";\n  Events[\"FRAG_PARSED\"]=\"hlsFragParsed\";\n  Events[\"FRAG_BUFFERED\"]=\"hlsFragBuffered\";\n  Events[\"FRAG_CHANGED\"]=\"hlsFragChanged\";\n  Events[\"FPS_DROP\"]=\"hlsFpsDrop\";\n  Events[\"FPS_DROP_LEVEL_CAPPING\"]=\"hlsFpsDropLevelCapping\";\n  Events[\"MAX_AUTO_LEVEL_UPDATED\"]=\"hlsMaxAutoLevelUpdated\";\n  Events[\"ERROR\"]=\"hlsError\";\n  Events[\"DESTROYING\"]=\"hlsDestroying\";\n  Events[\"KEY_LOADING\"]=\"hlsKeyLoading\";\n  Events[\"KEY_LOADED\"]=\"hlsKeyLoaded\";\n  Events[\"LIVE_BACK_BUFFER_REACHED\"]=\"hlsLiveBackBufferReached\";\n  Events[\"BACK_BUFFER_REACHED\"]=\"hlsBackBufferReached\";\n  Events[\"STEERING_MANIFEST_LOADED\"]=\"hlsSteeringManifestLoaded\";\n  return Events;\n}({});\n\n\n\nlet ErrorTypes=function (ErrorTypes){\n  ErrorTypes[\"NETWORK_ERROR\"]=\"networkError\";\n  ErrorTypes[\"MEDIA_ERROR\"]=\"mediaError\";\n  ErrorTypes[\"KEY_SYSTEM_ERROR\"]=\"keySystemError\";\n  ErrorTypes[\"MUX_ERROR\"]=\"muxError\";\n  ErrorTypes[\"OTHER_ERROR\"]=\"otherError\";\n  return ErrorTypes;\n}({});\nlet ErrorDetails=function (ErrorDetails){\n  ErrorDetails[\"KEY_SYSTEM_NO_KEYS\"]=\"keySystemNoKeys\";\n  ErrorDetails[\"KEY_SYSTEM_NO_ACCESS\"]=\"keySystemNoAccess\";\n  ErrorDetails[\"KEY_SYSTEM_NO_SESSION\"]=\"keySystemNoSession\";\n  ErrorDetails[\"KEY_SYSTEM_NO_CONFIGURED_LICENSE\"]=\"keySystemNoConfiguredLicense\";\n  ErrorDetails[\"KEY_SYSTEM_LICENSE_REQUEST_FAILED\"]=\"keySystemLicenseRequestFailed\";\n  ErrorDetails[\"KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED\"]=\"keySystemServerCertificateRequestFailed\";\n  ErrorDetails[\"KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED\"]=\"keySystemServerCertificateUpdateFailed\";\n  ErrorDetails[\"KEY_SYSTEM_SESSION_UPDATE_FAILED\"]=\"keySystemSessionUpdateFailed\";\n  ErrorDetails[\"KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED\"]=\"keySystemStatusOutputRestricted\";\n  ErrorDetails[\"KEY_SYSTEM_STATUS_INTERNAL_ERROR\"]=\"keySystemStatusInternalError\";\n  ErrorDetails[\"MANIFEST_LOAD_ERROR\"]=\"manifestLoadError\";\n  ErrorDetails[\"MANIFEST_LOAD_TIMEOUT\"]=\"manifestLoadTimeOut\";\n  ErrorDetails[\"MANIFEST_PARSING_ERROR\"]=\"manifestParsingError\";\n  ErrorDetails[\"MANIFEST_INCOMPATIBLE_CODECS_ERROR\"]=\"manifestIncompatibleCodecsError\";\n  ErrorDetails[\"LEVEL_EMPTY_ERROR\"]=\"levelEmptyError\";\n  ErrorDetails[\"LEVEL_LOAD_ERROR\"]=\"levelLoadError\";\n  ErrorDetails[\"LEVEL_LOAD_TIMEOUT\"]=\"levelLoadTimeOut\";\n  ErrorDetails[\"LEVEL_PARSING_ERROR\"]=\"levelParsingError\";\n  ErrorDetails[\"LEVEL_SWITCH_ERROR\"]=\"levelSwitchError\";\n  ErrorDetails[\"AUDIO_TRACK_LOAD_ERROR\"]=\"audioTrackLoadError\";\n  ErrorDetails[\"AUDIO_TRACK_LOAD_TIMEOUT\"]=\"audioTrackLoadTimeOut\";\n  ErrorDetails[\"SUBTITLE_LOAD_ERROR\"]=\"subtitleTrackLoadError\";\n  ErrorDetails[\"SUBTITLE_TRACK_LOAD_TIMEOUT\"]=\"subtitleTrackLoadTimeOut\";\n  ErrorDetails[\"FRAG_LOAD_ERROR\"]=\"fragLoadError\";\n  ErrorDetails[\"FRAG_LOAD_TIMEOUT\"]=\"fragLoadTimeOut\";\n  ErrorDetails[\"FRAG_DECRYPT_ERROR\"]=\"fragDecryptError\";\n  ErrorDetails[\"FRAG_PARSING_ERROR\"]=\"fragParsingError\";\n  ErrorDetails[\"FRAG_GAP\"]=\"fragGap\";\n  ErrorDetails[\"REMUX_ALLOC_ERROR\"]=\"remuxAllocError\";\n  ErrorDetails[\"KEY_LOAD_ERROR\"]=\"keyLoadError\";\n  ErrorDetails[\"KEY_LOAD_TIMEOUT\"]=\"keyLoadTimeOut\";\n  ErrorDetails[\"BUFFER_ADD_CODEC_ERROR\"]=\"bufferAddCodecError\";\n  ErrorDetails[\"BUFFER_INCOMPATIBLE_CODECS_ERROR\"]=\"bufferIncompatibleCodecsError\";\n  ErrorDetails[\"BUFFER_APPEND_ERROR\"]=\"bufferAppendError\";\n  ErrorDetails[\"BUFFER_APPENDING_ERROR\"]=\"bufferAppendingError\";\n  ErrorDetails[\"BUFFER_STALLED_ERROR\"]=\"bufferStalledError\";\n  ErrorDetails[\"BUFFER_FULL_ERROR\"]=\"bufferFullError\";\n  ErrorDetails[\"BUFFER_SEEK_OVER_HOLE\"]=\"bufferSeekOverHole\";\n  ErrorDetails[\"BUFFER_NUDGE_ON_STALL\"]=\"bufferNudgeOnStall\";\n  ErrorDetails[\"INTERNAL_EXCEPTION\"]=\"internalException\";\n  ErrorDetails[\"INTERNAL_ABORTED\"]=\"aborted\";\n  ErrorDetails[\"UNKNOWN\"]=\"unknown\";\n  return ErrorDetails;\n}({});\n\nconst noop=function noop(){};\nconst fakeLogger={\n  trace: noop,\n  debug: noop,\n  log: noop,\n  warn: noop,\n  info: noop,\n  error: noop\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 consolePrintFn(type){\n  const func=self.console[type];\n  if(func){\n    return func.bind(self.console, `[${type}] >`);\n  }\n  return noop;\n}\nfunction exportLoggerFunctions(debugConfig, ...functions){\n  functions.forEach(function (type){\n    exportedLogger[type]=debugConfig[type] ? debugConfig[type].bind(debugConfig):consolePrintFn(type);\n  });\n}\nfunction enableLogs(debugConfig, id){\n  // check that console is available\n  if(typeof console==='object'&&debugConfig===true||typeof debugConfig==='object'){\n    exportLoggerFunctions(debugConfig,\n    // Remove out from list here to hard-disable a log-level\n    // 'trace',\n    'debug', 'log', 'info', 'warn', 'error');\n    // Some browsers don't allow to use bind on console object anyway\n    // fallback to default if needed\n    try {\n      exportedLogger.log(`Debug logs enabled for \"${id}\" in hls.js version ${\"1.5.19\"}`);\n    } catch (e){\n      exportedLogger=fakeLogger;\n    }\n  }else{\n    exportedLogger=fakeLogger;\n  }\n}\nconst logger=exportedLogger;\n\nconst DECIMAL_RESOLUTION_REGEX=/^(\\d+)x(\\d+)$/;\nconst ATTR_LIST_REGEX=/(.+?)=(\".*?\"|.*?)(?:,|$)/g;\n\n// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js\nclass AttrList {\n  constructor(attrs){\n    if(typeof attrs==='string'){\n      attrs=AttrList.parseAttrList(attrs);\n    }\n    _extends(this, attrs);\n  }\n  get clientAttrs(){\n    return Object.keys(this).filter(attr=> attr.substring(0, 2)==='X-');\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  hexadecimalInteger(attrName){\n    if(this[attrName]){\n      let stringValue=(this[attrName]||'0x').slice(2);\n      stringValue=(stringValue.length & 1 ? '0':'') + stringValue;\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  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  decimalFloatingPoint(attrName){\n    return parseFloat(this[attrName]);\n  }\n  optionalFloat(attrName, defaultValue){\n    const value=this[attrName];\n    return value ? parseFloat(value):defaultValue;\n  }\n  enumeratedString(attrName){\n    return this[attrName];\n  }\n  bool(attrName){\n    return this[attrName]==='YES';\n  }\n  decimalResolution(attrName){\n    const res=DECIMAL_RESOLUTION_REGEX.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  static parseAttrList(input){\n    let match;\n    const attrs={};\n    const quote='\"';\n    ATTR_LIST_REGEX.lastIndex=0;\n    while ((match=ATTR_LIST_REGEX.exec(input))!==null){\n      let value=match[2];\n      if(value.indexOf(quote)===0&&value.lastIndexOf(quote)===value.length - 1){\n        value=value.slice(1, -1);\n      }\n      const name=match[1].trim();\n      attrs[name]=value;\n    }\n    return attrs;\n  }\n}\n\n// Avoid exporting const enum so that these values can be inlined\n\nfunction isDateRangeCueAttribute(attrName){\n  return attrName!==\"ID\"&&attrName!==\"CLASS\"&&attrName!==\"START-DATE\"&&attrName!==\"DURATION\"&&attrName!==\"END-DATE\"&&attrName!==\"END-ON-NEXT\";\n}\nfunction isSCTE35Attribute(attrName){\n  return attrName===\"SCTE35-OUT\"||attrName===\"SCTE35-IN\";\n}\nclass DateRange {\n  constructor(dateRangeAttr, dateRangeWithSameId){\n    this.attr=void 0;\n    this._startDate=void 0;\n    this._endDate=void 0;\n    this._badValueForSameId=void 0;\n    if(dateRangeWithSameId){\n      const previousAttr=dateRangeWithSameId.attr;\n      for (const key in previousAttr){\n        if(Object.prototype.hasOwnProperty.call(dateRangeAttr, key)&&dateRangeAttr[key]!==previousAttr[key]){\n          logger.warn(`DATERANGE tag attribute: \"${key}\" does not match for tags with ID: \"${dateRangeAttr.ID}\"`);\n          this._badValueForSameId=key;\n          break;\n        }\n      }\n      // Merge DateRange tags with the same ID\n      dateRangeAttr=_extends(new AttrList({}), previousAttr, dateRangeAttr);\n    }\n    this.attr=dateRangeAttr;\n    this._startDate=new Date(dateRangeAttr[\"START-DATE\"]);\n    if(\"END-DATE\" in this.attr){\n      const endDate=new Date(this.attr[\"END-DATE\"]);\n      if(isFiniteNumber(endDate.getTime())){\n        this._endDate=endDate;\n      }\n    }\n  }\n  get id(){\n    return this.attr.ID;\n  }\n  get class(){\n    return this.attr.CLASS;\n  }\n  get startDate(){\n    return this._startDate;\n  }\n  get endDate(){\n    if(this._endDate){\n      return this._endDate;\n    }\n    const duration=this.duration;\n    if(duration!==null){\n      return new Date(this._startDate.getTime() + duration * 1000);\n    }\n    return null;\n  }\n  get duration(){\n    if(\"DURATION\" in this.attr){\n      const duration=this.attr.decimalFloatingPoint(\"DURATION\");\n      if(isFiniteNumber(duration)){\n        return duration;\n      }\n    }else if(this._endDate){\n      return (this._endDate.getTime() - this._startDate.getTime()) / 1000;\n    }\n    return null;\n  }\n  get plannedDuration(){\n    if(\"PLANNED-DURATION\" in this.attr){\n      return this.attr.decimalFloatingPoint(\"PLANNED-DURATION\");\n    }\n    return null;\n  }\n  get endOnNext(){\n    return this.attr.bool(\"END-ON-NEXT\");\n  }\n  get isValid(){\n    return !!this.id&&!this._badValueForSameId&&isFiniteNumber(this.startDate.getTime())&&(this.duration===null||this.duration >=0)&&(!this.endOnNext||!!this.class);\n  }\n}\n\nclass LoadStats {\n  constructor(){\n    this.aborted=false;\n    this.loaded=0;\n    this.retry=0;\n    this.total=0;\n    this.chunkCount=0;\n    this.bwEstimate=0;\n    this.loading={\n      start: 0,\n      first: 0,\n      end: 0\n    };\n    this.parsing={\n      start: 0,\n      end: 0\n    };\n    this.buffering={\n      start: 0,\n      first: 0,\n      end: 0\n    };\n  }\n}\n\nvar ElementaryStreamTypes={\n  AUDIO: \"audio\",\n  VIDEO: \"video\",\n  AUDIOVIDEO: \"audiovideo\"\n};\nclass BaseSegment {\n  constructor(baseurl){\n    this._byteRange=null;\n    this._url=null;\n    // baseurl is the URL to the playlist\n    this.baseurl=void 0;\n    // relurl is the portion of the URL that comes from inside the playlist.\n    this.relurl=void 0;\n    // Holds the types of data this fragment supports\n    this.elementaryStreams={\n      [ElementaryStreamTypes.AUDIO]: null,\n      [ElementaryStreamTypes.VIDEO]: null,\n      [ElementaryStreamTypes.AUDIOVIDEO]: null\n    };\n    this.baseurl=baseurl;\n  }\n\n  // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array\n  setByteRange(value, previous){\n    const params=value.split('@', 2);\n    let start;\n    if(params.length===1){\n      start=(previous==null ? void 0:previous.byteRangeEndOffset)||0;\n    }else{\n      start=parseInt(params[1]);\n    }\n    this._byteRange=[start, parseInt(params[0]) + start];\n  }\n  get byteRange(){\n    if(!this._byteRange){\n      return [];\n    }\n    return this._byteRange;\n  }\n  get byteRangeStartOffset(){\n    return this.byteRange[0];\n  }\n  get byteRangeEndOffset(){\n    return this.byteRange[1];\n  }\n  get url(){\n    if(!this._url&&this.baseurl&&this.relurl){\n      this._url=urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, {\n        alwaysNormalize: true\n      });\n    }\n    return this._url||'';\n  }\n  set url(value){\n    this._url=value;\n  }\n}\n\n\nclass Fragment extends BaseSegment {\n  constructor(type, baseurl){\n    super(baseurl);\n    this._decryptdata=null;\n    this.rawProgramDateTime=null;\n    this.programDateTime=null;\n    this.tagList=[];\n    // EXTINF has to be present for a m3u8 to be considered valid\n    this.duration=0;\n    // sn notates the sequence number for a segment, and if set to a string can be 'initSegment'\n    this.sn=0;\n    // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption\n    // core difference from the private field _decryptdata is the lack of the initialized IV\n    // _decryptdata will set the IV for this segment based on the segment number in the fragment\n    this.levelkeys=void 0;\n    // A string representing the fragment type\n    this.type=void 0;\n    // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading\n    this.loader=null;\n    // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading\n    this.keyLoader=null;\n    // The level/track index to which the fragment belongs\n    this.level=-1;\n    // The continuity counter of the fragment\n    this.cc=0;\n    // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.\n    this.startPTS=void 0;\n    // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.\n    this.endPTS=void 0;\n    // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.\n    this.startDTS=void 0;\n    // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.\n    this.endDTS=void 0;\n    // The start time of the fragment, as listed in the manifest. Updated after transmux complete.\n    this.start=0;\n    // Set by `updateFragPTSDTS` in level-helper\n    this.deltaPTS=void 0;\n    // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.\n    this.maxStartPTS=void 0;\n    // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.\n    this.minEndPTS=void 0;\n    // Load/parse timing information\n    this.stats=new LoadStats();\n    // Init Segment bytes (unset for media segments)\n    this.data=void 0;\n    // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered\n    this.bitrateTest=false;\n    // #EXTINF  segment title\n    this.title=null;\n    // The Media Initialization Section for this segment\n    this.initSegment=null;\n    // Fragment is the last fragment in the media playlist\n    this.endList=void 0;\n    // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded\n    this.gap=void 0;\n    // Deprecated\n    this.urlId=0;\n    this.type=type;\n  }\n  get decryptdata(){\n    const {\n      levelkeys\n    }=this;\n    if(!levelkeys&&!this._decryptdata){\n      return null;\n    }\n    if(!this._decryptdata&&this.levelkeys&&!this.levelkeys.NONE){\n      const key=this.levelkeys.identity;\n      if(key){\n        this._decryptdata=key.getDecryptData(this.sn);\n      }else{\n        const keyFormats=Object.keys(this.levelkeys);\n        if(keyFormats.length===1){\n          return this._decryptdata=this.levelkeys[keyFormats[0]].getDecryptData(this.sn);\n        }\n      }\n    }\n    return this._decryptdata;\n  }\n  get end(){\n    return this.start + this.duration;\n  }\n  get endProgramDateTime(){\n    if(this.programDateTime===null){\n      return null;\n    }\n    if(!isFiniteNumber(this.programDateTime)){\n      return null;\n    }\n    const duration = !isFiniteNumber(this.duration) ? 0:this.duration;\n    return this.programDateTime + duration * 1000;\n  }\n  get encrypted(){\n    var _this$_decryptdata;\n    // At the m3u8-parser level we need to add support for manifest signalled keyformats\n    // when we want the fragment to start reporting that it is encrypted.\n    // Currently, keyFormat will only be set for identity keys\n    if((_this$_decryptdata=this._decryptdata)!=null&&_this$_decryptdata.encrypted){\n      return true;\n    }else if(this.levelkeys){\n      const keyFormats=Object.keys(this.levelkeys);\n      const len=keyFormats.length;\n      if(len > 1||len===1&&this.levelkeys[keyFormats[0]].encrypted){\n        return true;\n      }\n    }\n    return false;\n  }\n  setKeyFormat(keyFormat){\n    if(this.levelkeys){\n      const key=this.levelkeys[keyFormat];\n      if(key&&!this._decryptdata){\n        this._decryptdata=key.getDecryptData(this.sn);\n      }\n    }\n  }\n  abortRequests(){\n    var _this$loader, _this$keyLoader;\n    (_this$loader=this.loader)==null ? void 0:_this$loader.abort();\n    (_this$keyLoader=this.keyLoader)==null ? void 0:_this$keyLoader.abort();\n  }\n  setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial=false){\n    const {\n      elementaryStreams\n    }=this;\n    const info=elementaryStreams[type];\n    if(!info){\n      elementaryStreams[type]={\n        startPTS,\n        endPTS,\n        startDTS,\n        endDTS,\n        partial\n      };\n      return;\n    }\n    info.startPTS=Math.min(info.startPTS, startPTS);\n    info.endPTS=Math.max(info.endPTS, endPTS);\n    info.startDTS=Math.min(info.startDTS, startDTS);\n    info.endDTS=Math.max(info.endDTS, endDTS);\n  }\n  clearElementaryStreamInfo(){\n    const {\n      elementaryStreams\n    }=this;\n    elementaryStreams[ElementaryStreamTypes.AUDIO]=null;\n    elementaryStreams[ElementaryStreamTypes.VIDEO]=null;\n    elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]=null;\n  }\n}\n\n\nclass Part extends BaseSegment {\n  constructor(partAttrs, frag, baseurl, index, previous){\n    super(baseurl);\n    this.fragOffset=0;\n    this.duration=0;\n    this.gap=false;\n    this.independent=false;\n    this.relurl=void 0;\n    this.fragment=void 0;\n    this.index=void 0;\n    this.stats=new LoadStats();\n    this.duration=partAttrs.decimalFloatingPoint('DURATION');\n    this.gap=partAttrs.bool('GAP');\n    this.independent=partAttrs.bool('INDEPENDENT');\n    this.relurl=partAttrs.enumeratedString('URI');\n    this.fragment=frag;\n    this.index=index;\n    const byteRange=partAttrs.enumeratedString('BYTERANGE');\n    if(byteRange){\n      this.setByteRange(byteRange, previous);\n    }\n    if(previous){\n      this.fragOffset=previous.fragOffset + previous.duration;\n    }\n  }\n  get start(){\n    return this.fragment.start + this.fragOffset;\n  }\n  get end(){\n    return this.start + this.duration;\n  }\n  get loaded(){\n    const {\n      elementaryStreams\n    }=this;\n    return !!(elementaryStreams.audio||elementaryStreams.video||elementaryStreams.audiovideo);\n  }\n}\n\nconst DEFAULT_TARGET_DURATION=10;\n\n\nclass LevelDetails {\n  constructor(baseUrl){\n    this.PTSKnown=false;\n    this.alignedSliding=false;\n    this.averagetargetduration=void 0;\n    this.endCC=0;\n    this.endSN=0;\n    this.fragments=void 0;\n    this.fragmentHint=void 0;\n    this.partList=null;\n    this.dateRanges=void 0;\n    this.live=true;\n    this.ageHeader=0;\n    this.advancedDateTime=void 0;\n    this.updated=true;\n    this.advanced=true;\n    this.availabilityDelay=void 0;\n    // Manifest reload synchronization\n    this.misses=0;\n    this.startCC=0;\n    this.startSN=0;\n    this.startTimeOffset=null;\n    this.targetduration=0;\n    this.totalduration=0;\n    this.type=null;\n    this.url=void 0;\n    this.m3u8='';\n    this.version=null;\n    this.canBlockReload=false;\n    this.canSkipUntil=0;\n    this.canSkipDateRanges=false;\n    this.skippedSegments=0;\n    this.recentlyRemovedDateranges=void 0;\n    this.partHoldBack=0;\n    this.holdBack=0;\n    this.partTarget=0;\n    this.preloadHint=void 0;\n    this.renditionReports=void 0;\n    this.tuneInGoal=0;\n    this.deltaUpdateFailed=void 0;\n    this.driftStartTime=0;\n    this.driftEndTime=0;\n    this.driftStart=0;\n    this.driftEnd=0;\n    this.encryptedFragments=void 0;\n    this.playlistParsingError=null;\n    this.variableList=null;\n    this.hasVariableRefs=false;\n    this.fragments=[];\n    this.encryptedFragments=[];\n    this.dateRanges={};\n    this.url=baseUrl;\n  }\n  reloaded(previous){\n    if(!previous){\n      this.advanced=true;\n      this.updated=true;\n      return;\n    }\n    const partSnDiff=this.lastPartSn - previous.lastPartSn;\n    const partIndexDiff=this.lastPartIndex - previous.lastPartIndex;\n    this.updated=this.endSN!==previous.endSN||!!partIndexDiff||!!partSnDiff||!this.live;\n    this.advanced=this.endSN > previous.endSN||partSnDiff > 0||partSnDiff===0&&partIndexDiff > 0;\n    if(this.updated||this.advanced){\n      this.misses=Math.floor(previous.misses * 0.6);\n    }else{\n      this.misses=previous.misses + 1;\n    }\n    this.availabilityDelay=previous.availabilityDelay;\n  }\n  get hasProgramDateTime(){\n    if(this.fragments.length){\n      return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime);\n    }\n    return false;\n  }\n  get levelTargetDuration(){\n    return this.averagetargetduration||this.targetduration||DEFAULT_TARGET_DURATION;\n  }\n  get drift(){\n    const runTime=this.driftEndTime - this.driftStartTime;\n    if(runTime > 0){\n      const runDuration=this.driftEnd - this.driftStart;\n      return runDuration * 1000 / runTime;\n    }\n    return 1;\n  }\n  get edge(){\n    return this.partEnd||this.fragmentEnd;\n  }\n  get partEnd(){\n    var _this$partList;\n    if((_this$partList=this.partList)!=null&&_this$partList.length){\n      return this.partList[this.partList.length - 1].end;\n    }\n    return this.fragmentEnd;\n  }\n  get fragmentEnd(){\n    var _this$fragments;\n    if((_this$fragments=this.fragments)!=null&&_this$fragments.length){\n      return this.fragments[this.fragments.length - 1].end;\n    }\n    return 0;\n  }\n  get age(){\n    if(this.advancedDateTime){\n      return Math.max(Date.now() - this.advancedDateTime, 0) / 1000;\n    }\n    return 0;\n  }\n  get lastPartIndex(){\n    var _this$partList2;\n    if((_this$partList2=this.partList)!=null&&_this$partList2.length){\n      return this.partList[this.partList.length - 1].index;\n    }\n    return -1;\n  }\n  get lastPartSn(){\n    var _this$partList3;\n    if((_this$partList3=this.partList)!=null&&_this$partList3.length){\n      return this.partList[this.partList.length - 1].fragment.sn;\n    }\n    return this.endSN;\n  }\n}\n\nfunction base64Decode(base64encodedStr){\n  return Uint8Array.from(atob(base64encodedStr), c=> c.charCodeAt(0));\n}\n\nfunction getKeyIdBytes(str){\n  const keyIdbytes=strToUtf8array(str).subarray(0, 16);\n  const paddedkeyIdbytes=new Uint8Array(16);\n  paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length);\n  return paddedkeyIdbytes;\n}\nfunction changeEndianness(keyId){\n  const swap=function swap(array, from, to){\n    const cur=array[from];\n    array[from]=array[to];\n    array[to]=cur;\n  };\n  swap(keyId, 0, 3);\n  swap(keyId, 1, 2);\n  swap(keyId, 4, 5);\n  swap(keyId, 6, 7);\n}\nfunction convertDataUriToArrayBytes(uri){\n  // data:[<media type][;attribute=value][;base64],<data>\n  const colonsplit=uri.split(':');\n  let keydata=null;\n  if(colonsplit[0]==='data'&&colonsplit.length===2){\n    const semicolonsplit=colonsplit[1].split(';');\n    const commasplit=semicolonsplit[semicolonsplit.length - 1].split(',');\n    if(commasplit.length===2){\n      const isbase64=commasplit[0]==='base64';\n      const data=commasplit[1];\n      if(isbase64){\n        semicolonsplit.splice(-1, 1); // remove from processing\n        keydata=base64Decode(data);\n      }else{\n        keydata=getKeyIdBytes(data);\n      }\n    }\n  }\n  return keydata;\n}\nfunction strToUtf8array(str){\n  return Uint8Array.from(unescape(encodeURIComponent(str)), c=> c.charCodeAt(0));\n}\n\n\nconst optionalSelf=typeof self!=='undefined' ? self:undefined;\n\n\nvar KeySystems={\n  CLEARKEY: \"org.w3.clearkey\",\n  FAIRPLAY: \"com.apple.fps\",\n  PLAYREADY: \"com.microsoft.playready\",\n  WIDEVINE: \"com.widevine.alpha\"\n};\n\n// Playlist #EXT-X-KEY KEYFORMAT values\nvar KeySystemFormats={\n  CLEARKEY: \"org.w3.clearkey\",\n  FAIRPLAY: \"com.apple.streamingkeydelivery\",\n  PLAYREADY: \"com.microsoft.playready\",\n  WIDEVINE: \"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\"\n};\nfunction keySystemFormatToKeySystemDomain(format){\n  switch (format){\n    case KeySystemFormats.FAIRPLAY:\n      return KeySystems.FAIRPLAY;\n    case KeySystemFormats.PLAYREADY:\n      return KeySystems.PLAYREADY;\n    case KeySystemFormats.WIDEVINE:\n      return KeySystems.WIDEVINE;\n    case KeySystemFormats.CLEARKEY:\n      return KeySystems.CLEARKEY;\n  }\n}\n\n// System IDs for which we can extract a key ID from \"encrypted\" event PSSH\nvar KeySystemIds={\n  CENC: \"1077efecc0b24d02ace33c1e52e2fb4b\",\n  CLEARKEY: \"e2719d58a985b3c9781ab030af78d30e\",\n  FAIRPLAY: \"94ce86fb07ff4f43adb893d2fa968ca2\",\n  PLAYREADY: \"9a04f07998404286ab92e65be0885f95\",\n  WIDEVINE: \"edef8ba979d64acea3c827dcd51d21ed\"\n};\nfunction keySystemIdToKeySystemDomain(systemId){\n  if(systemId===KeySystemIds.WIDEVINE){\n    return KeySystems.WIDEVINE;\n  }else if(systemId===KeySystemIds.PLAYREADY){\n    return KeySystems.PLAYREADY;\n  }else if(systemId===KeySystemIds.CENC||systemId===KeySystemIds.CLEARKEY){\n    return KeySystems.CLEARKEY;\n  }\n}\nfunction keySystemDomainToKeySystemFormat(keySystem){\n  switch (keySystem){\n    case KeySystems.FAIRPLAY:\n      return KeySystemFormats.FAIRPLAY;\n    case KeySystems.PLAYREADY:\n      return KeySystemFormats.PLAYREADY;\n    case KeySystems.WIDEVINE:\n      return KeySystemFormats.WIDEVINE;\n    case KeySystems.CLEARKEY:\n      return KeySystemFormats.CLEARKEY;\n  }\n}\nfunction getKeySystemsForConfig(config){\n  const {\n    drmSystems,\n    widevineLicenseUrl\n  }=config;\n  const keySystemsToAttempt=drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem=> !!drmSystems[keySystem]):[];\n  if(!keySystemsToAttempt[KeySystems.WIDEVINE]&&widevineLicenseUrl){\n    keySystemsToAttempt.push(KeySystems.WIDEVINE);\n  }\n  return keySystemsToAttempt;\n}\nconst requestMediaKeySystemAccess=function (_optionalSelf$navigat){\n  if(optionalSelf!=null&&(_optionalSelf$navigat=optionalSelf.navigator)!=null&&_optionalSelf$navigat.requestMediaKeySystemAccess){\n    return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);\n  }else{\n    return null;\n  }\n}();\n\n\nfunction getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions){\n  let initDataTypes;\n  switch (keySystem){\n    case KeySystems.FAIRPLAY:\n      initDataTypes=['cenc', 'sinf'];\n      break;\n    case KeySystems.WIDEVINE:\n    case KeySystems.PLAYREADY:\n      initDataTypes=['cenc'];\n      break;\n    case KeySystems.CLEARKEY:\n      initDataTypes=['cenc', 'keyids'];\n      break;\n    default:\n      throw new Error(`Unknown key-system: ${keySystem}`);\n  }\n  return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions);\n}\nfunction createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions){\n  const baseConfig={\n    initDataTypes: initDataTypes,\n    persistentState: drmSystemOptions.persistentState||'optional',\n    distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier||'optional',\n    sessionTypes: drmSystemOptions.sessionTypes||[drmSystemOptions.sessionType||'temporary'],\n    audioCapabilities: audioCodecs.map(codec=> ({\n      contentType: `audio/mp4; codecs=\"${codec}\"`,\n      robustness: drmSystemOptions.audioRobustness||'',\n      encryptionScheme: drmSystemOptions.audioEncryptionScheme||null\n    })),\n    videoCapabilities: videoCodecs.map(codec=> ({\n      contentType: `video/mp4; codecs=\"${codec}\"`,\n      robustness: drmSystemOptions.videoRobustness||'',\n      encryptionScheme: drmSystemOptions.videoEncryptionScheme||null\n    }))\n  };\n  return [baseConfig];\n}\nfunction parsePlayReadyWRM(keyBytes){\n  const keyBytesUtf16=new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);\n  const keyByteStr=String.fromCharCode.apply(null, Array.from(keyBytesUtf16));\n\n  // Parse Playready WRMHeader XML\n  const xmlKeyBytes=keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);\n  const parser=new DOMParser();\n  const xmlDoc=parser.parseFromString(xmlKeyBytes, 'text/xml');\n  const keyData=xmlDoc.getElementsByTagName('KID')[0];\n  if(keyData){\n    const keyId=keyData.childNodes[0] ? keyData.childNodes[0].nodeValue:keyData.getAttribute('VALUE');\n    if(keyId){\n      const keyIdArray=base64Decode(keyId).subarray(0, 16);\n      // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID\n      // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID\n      changeEndianness(keyIdArray);\n      return keyIdArray;\n    }\n  }\n  return null;\n}\n\nfunction sliceUint8(array, start, end){\n  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.\n  // It always exists in the TypeScript definition so fails, but it fails at runtime on IE11.\n  return Uint8Array.prototype.slice ? array.slice(start, end):new Uint8Array(Array.prototype.slice.call(array, start, end));\n}\n\n// breaking up those two types in order to clarify what is happening in the decoding path.\n\n\nconst isHeader$2=(data, offset)=> {\n  \n  if(offset + 10 <=data.length){\n    // look for 'ID3' identifier\n    if(data[offset]===0x49&&data[offset + 1]===0x44&&data[offset + 2]===0x33){\n      // check version is within range\n      if(data[offset + 3] < 0xff&&data[offset + 4] < 0xff){\n        // check size is within range\n        if(data[offset + 6] < 0x80&&data[offset + 7] < 0x80&&data[offset + 8] < 0x80&&data[offset + 9] < 0x80){\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n};\n\n\nconst isFooter=(data, offset)=> {\n  \n  if(offset + 10 <=data.length){\n    // look for '3DI' identifier\n    if(data[offset]===0x33&&data[offset + 1]===0x44&&data[offset + 2]===0x49){\n      // check version is within range\n      if(data[offset + 3] < 0xff&&data[offset + 4] < 0xff){\n        // check size is within range\n        if(data[offset + 6] < 0x80&&data[offset + 7] < 0x80&&data[offset + 8] < 0x80&&data[offset + 9] < 0x80){\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n};\n\n\nconst getID3Data=(data, offset)=> {\n  const front=offset;\n  let length=0;\n  while (isHeader$2(data, offset)){\n    // ID3 header is 10 bytes\n    length +=10;\n    const size=readSize(data, offset + 6);\n    length +=size;\n    if(isFooter(data, offset + 10)){\n      // ID3 footer is 10 bytes\n      length +=10;\n    }\n    offset +=length;\n  }\n  if(length > 0){\n    return data.subarray(front, front + length);\n  }\n  return undefined;\n};\nconst readSize=(data, offset)=> {\n  let size=0;\n  size=(data[offset] & 0x7f) << 21;\n  size |=(data[offset + 1] & 0x7f) << 14;\n  size |=(data[offset + 2] & 0x7f) << 7;\n  size |=data[offset + 3] & 0x7f;\n  return size;\n};\nconst canParse$2=(data, offset)=> {\n  return isHeader$2(data, offset)&&readSize(data, offset + 6) + 10 <=data.length - offset;\n};\n\n\nconst getTimeStamp=data=> {\n  const frames=getID3Frames(data);\n  for (let i=0; i < frames.length; i++){\n    const frame=frames[i];\n    if(isTimeStampFrame(frame)){\n      return readTimeStamp(frame);\n    }\n  }\n  return undefined;\n};\n\n\nconst isTimeStampFrame=frame=> {\n  return frame&&frame.key==='PRIV'&&frame.info==='com.apple.streaming.transportStreamTimestamp';\n};\nconst getFrameData=data=> {\n  \n  const type=String.fromCharCode(data[0], data[1], data[2], data[3]);\n  const size=readSize(data, 4);\n\n  // skip frame id, size, and flags\n  const offset=10;\n  return {\n    type,\n    size,\n    data: data.subarray(offset, offset + size)\n  };\n};\n\n\nconst getID3Frames=id3Data=> {\n  let offset=0;\n  const frames=[];\n  while (isHeader$2(id3Data, offset)){\n    const size=readSize(id3Data, offset + 6);\n    // skip past ID3 header\n    offset +=10;\n    const end=offset + size;\n    // loop through frames in the ID3 tag\n    while (offset + 8 < end){\n      const frameData=getFrameData(id3Data.subarray(offset));\n      const frame=decodeFrame(frameData);\n      if(frame){\n        frames.push(frame);\n      }\n\n      // skip frame header and frame data\n      offset +=frameData.size + 10;\n    }\n    if(isFooter(id3Data, offset)){\n      offset +=10;\n    }\n  }\n  return frames;\n};\nconst decodeFrame=frame=> {\n  if(frame.type==='PRIV'){\n    return decodePrivFrame(frame);\n  }else if(frame.type[0]==='W'){\n    return decodeURLFrame(frame);\n  }\n  return decodeTextFrame(frame);\n};\nconst decodePrivFrame=frame=> {\n  \n  if(frame.size < 2){\n    return undefined;\n  }\n  const owner=utf8ArrayToStr(frame.data, true);\n  const privateData=new Uint8Array(frame.data.subarray(owner.length + 1));\n  return {\n    key: frame.type,\n    info: owner,\n    data: privateData.buffer\n  };\n};\nconst decodeTextFrame=frame=> {\n  if(frame.size < 2){\n    return undefined;\n  }\n  if(frame.type==='TXXX'){\n    \n    let index=1;\n    const description=utf8ArrayToStr(frame.data.subarray(index), true);\n    index +=description.length + 1;\n    const value=utf8ArrayToStr(frame.data.subarray(index));\n    return {\n      key: frame.type,\n      info: description,\n      data: value\n    };\n  }\n  \n  const text=utf8ArrayToStr(frame.data.subarray(1));\n  return {\n    key: frame.type,\n    data: text\n  };\n};\nconst decodeURLFrame=frame=> {\n  if(frame.type==='WXXX'){\n    \n    if(frame.size < 2){\n      return undefined;\n    }\n    let index=1;\n    const description=utf8ArrayToStr(frame.data.subarray(index), true);\n    index +=description.length + 1;\n    const value=utf8ArrayToStr(frame.data.subarray(index));\n    return {\n      key: frame.type,\n      info: description,\n      data: value\n    };\n  }\n  \n  const url=utf8ArrayToStr(frame.data);\n  return {\n    key: frame.type,\n    data: url\n  };\n};\nconst readTimeStamp=timeStampFrame=> {\n  if(timeStampFrame.data.byteLength===8){\n    const data=new Uint8Array(timeStampFrame.data);\n    // timestamp is 33 bit expressed as a big-endian eight-octet number,\n    // with the upper 31 bits set to zero.\n    const pts33Bit=data[3] & 0x1;\n    let timestamp=(data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];\n    timestamp /=45;\n    if(pts33Bit){\n      timestamp +=47721858.84;\n    } // 2^32 / 90\n\n    return Math.round(timestamp);\n  }\n  return undefined;\n};\n\n// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197\n// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt\n\nconst utf8ArrayToStr=(array, exitOnNull=false)=> {\n  const decoder=getTextDecoder();\n  if(decoder){\n    const decoded=decoder.decode(array);\n    if(exitOnNull){\n      // grab up to the first null\n      const idx=decoded.indexOf('\\0');\n      return idx!==-1 ? decoded.substring(0, idx):decoded;\n    }\n\n    // remove any null characters\n    return decoded.replace(/\\0/g, '');\n  }\n  const len=array.length;\n  let c;\n  let char2;\n  let char3;\n  let out='';\n  let i=0;\n  while (i < len){\n    c=array[i++];\n    if(c===0x00&&exitOnNull){\n      return out;\n    }else if(c===0x00||c===0x03){\n      // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it\n      continue;\n    }\n    switch (c >> 4){\n      case 0:\n      case 1:\n      case 2:\n      case 3:\n      case 4:\n      case 5:\n      case 6:\n      case 7:\n        // 0xxxxxxx\n        out +=String.fromCharCode(c);\n        break;\n      case 12:\n      case 13:\n        // 110x xxxx   10xx xxxx\n        char2=array[i++];\n        out +=String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f);\n        break;\n      case 14:\n        // 1110 xxxx  10xx xxxx  10xx xxxx\n        char2=array[i++];\n        char3=array[i++];\n        out +=String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0);\n        break;\n    }\n  }\n  return out;\n};\nlet decoder;\nfunction getTextDecoder(){\n  // On Play Station 4, TextDecoder is defined but partially implemented.\n  // Manual decoding option is preferable\n  if(navigator.userAgent.includes('PlayStation 4')){\n    return;\n  }\n  if(!decoder&&typeof self.TextDecoder!=='undefined'){\n    decoder=new self.TextDecoder('utf-8');\n  }\n  return decoder;\n}\n\n\n\nconst Hex={\n  hexDump: function (array){\n    let str='';\n    for (let i=0; i < array.length; i++){\n      let h=array[i].toString(16);\n      if(h.length < 2){\n        h='0' + h;\n      }\n      str +=h;\n    }\n    return str;\n  }\n};\n\nconst UINT32_MAX$1=Math.pow(2, 32) - 1;\nconst push=[].push;\n\n// We are using fixed track IDs for driving the MP4 remuxer\n// instead of following the TS PIDs.\n// There is no reason not to do this and some browsers/SourceBuffer-demuxers\n// may not like if there are TrackID \"switches\"\n// See https://github.com/video-dev/hls.js/issues/1331\n// Here we are mapping our internal track types to constant MP4 track IDs\n// With MSE currently one can only have one track of each, and we are muxing\n// whatever video/audio rendition in them.\nconst RemuxerTrackIdConfig={\n  video: 1,\n  audio: 2,\n  id3: 3,\n  text: 4\n};\nfunction bin2str(data){\n  return String.fromCharCode.apply(null, data);\n}\nfunction readUint16(buffer, offset){\n  const val=buffer[offset] << 8 | buffer[offset + 1];\n  return val < 0 ? 65536 + val:val;\n}\nfunction readUint32(buffer, offset){\n  const val=readSint32(buffer, offset);\n  return val < 0 ? 4294967296 + val:val;\n}\nfunction readUint64(buffer, offset){\n  let result=readUint32(buffer, offset);\n  result *=Math.pow(2, 32);\n  result +=readUint32(buffer, offset + 4);\n  return result;\n}\nfunction readSint32(buffer, offset){\n  return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];\n}\nfunction writeUint32(buffer, offset, value){\n  buffer[offset]=value >> 24;\n  buffer[offset + 1]=value >> 16 & 0xff;\n  buffer[offset + 2]=value >> 8 & 0xff;\n  buffer[offset + 3]=value & 0xff;\n}\n\n// Find \"moof\" box\nfunction hasMoofData(data){\n  const end=data.byteLength;\n  for (let i=0; i < end;){\n    const size=readUint32(data, i);\n    if(size > 8&&data[i + 4]===0x6d&&data[i + 5]===0x6f&&data[i + 6]===0x6f&&data[i + 7]===0x66){\n      return true;\n    }\n    i=size > 1 ? i + size:end;\n  }\n  return false;\n}\n\n// Find the data for a box specified by its path\nfunction findBox(data, path){\n  const results=[];\n  if(!path.length){\n    // short-circuit the search for empty paths\n    return results;\n  }\n  const end=data.byteLength;\n  for (let i=0; i < end;){\n    const size=readUint32(data, i);\n    const type=bin2str(data.subarray(i + 4, i + 8));\n    const endbox=size > 1 ? i + size:end;\n    if(type===path[0]){\n      if(path.length===1){\n        // this is the end of the path and we've found the box we were\n        // looking for\n        results.push(data.subarray(i + 8, endbox));\n      }else{\n        // recursively search for the next box along the path\n        const subresults=findBox(data.subarray(i + 8, endbox), path.slice(1));\n        if(subresults.length){\n          push.apply(results, subresults);\n        }\n      }\n    }\n    i=endbox;\n  }\n\n  // we've finished searching all of data\n  return results;\n}\nfunction parseSegmentIndex(sidx){\n  const references=[];\n  const version=sidx[0];\n\n  // set initial offset, we skip the reference ID (not needed)\n  let index=8;\n  const timescale=readUint32(sidx, index);\n  index +=4;\n  let earliestPresentationTime=0;\n  let firstOffset=0;\n  if(version===0){\n    earliestPresentationTime=readUint32(sidx, index);\n    firstOffset=readUint32(sidx, index + 4);\n    index +=8;\n  }else{\n    earliestPresentationTime=readUint64(sidx, index);\n    firstOffset=readUint64(sidx, index + 8);\n    index +=16;\n  }\n\n  // skip reserved\n  index +=2;\n  let startByte=sidx.length + firstOffset;\n  const referencesCount=readUint16(sidx, index);\n  index +=2;\n  for (let i=0; i < referencesCount; i++){\n    let referenceIndex=index;\n    const referenceInfo=readUint32(sidx, referenceIndex);\n    referenceIndex +=4;\n    const referenceSize=referenceInfo & 0x7fffffff;\n    const referenceType=(referenceInfo & 0x80000000) >>> 31;\n    if(referenceType===1){\n      logger.warn('SIDX has hierarchical references (not supported)');\n      return null;\n    }\n    const subsegmentDuration=readUint32(sidx, referenceIndex);\n    referenceIndex +=4;\n    references.push({\n      referenceSize,\n      subsegmentDuration,\n      // unscaled\n      info: {\n        duration: subsegmentDuration / timescale,\n        start: startByte,\n        end: startByte + referenceSize - 1\n      }\n    });\n    startByte +=referenceSize;\n\n    // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits\n    // for |sapDelta|.\n    referenceIndex +=4;\n\n    // skip to next ref\n    index=referenceIndex;\n  }\n  return {\n    earliestPresentationTime,\n    timescale,\n    version,\n    referencesCount,\n    references\n  };\n}\n\n\n\nfunction parseInitSegment(initSegment){\n  const result=[];\n  const traks=findBox(initSegment, ['moov', 'trak']);\n  for (let i=0; i < traks.length; i++){\n    const trak=traks[i];\n    const tkhd=findBox(trak, ['tkhd'])[0];\n    if(tkhd){\n      let version=tkhd[0];\n      const trackId=readUint32(tkhd, version===0 ? 12:20);\n      const mdhd=findBox(trak, ['mdia', 'mdhd'])[0];\n      if(mdhd){\n        version=mdhd[0];\n        const timescale=readUint32(mdhd, version===0 ? 12:20);\n        const hdlr=findBox(trak, ['mdia', 'hdlr'])[0];\n        if(hdlr){\n          const hdlrType=bin2str(hdlr.subarray(8, 12));\n          const type={\n            soun: ElementaryStreamTypes.AUDIO,\n            vide: ElementaryStreamTypes.VIDEO\n          }[hdlrType];\n          if(type){\n            // Parse codec details\n            const stsd=findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];\n            const stsdData=parseStsd(stsd);\n            result[trackId]={\n              timescale,\n              type\n            };\n            result[type]=_objectSpread2({\n              timescale,\n              id: trackId\n            }, stsdData);\n          }\n        }\n      }\n    }\n  }\n  const trex=findBox(initSegment, ['moov', 'mvex', 'trex']);\n  trex.forEach(trex=> {\n    const trackId=readUint32(trex, 4);\n    const track=result[trackId];\n    if(track){\n      track.default={\n        duration: readUint32(trex, 12),\n        flags: readUint32(trex, 20)\n      };\n    }\n  });\n  return result;\n}\nfunction parseStsd(stsd){\n  const sampleEntries=stsd.subarray(8);\n  const sampleEntriesEnd=sampleEntries.subarray(8 + 78);\n  const fourCC=bin2str(sampleEntries.subarray(4, 8));\n  let codec=fourCC;\n  const encrypted=fourCC==='enca'||fourCC==='encv';\n  if(encrypted){\n    const encBox=findBox(sampleEntries, [fourCC])[0];\n    const encBoxChildren=encBox.subarray(fourCC==='enca' ? 28:78);\n    const sinfs=findBox(encBoxChildren, ['sinf']);\n    sinfs.forEach(sinf=> {\n      const schm=findBox(sinf, ['schm'])[0];\n      if(schm){\n        const scheme=bin2str(schm.subarray(4, 8));\n        if(scheme==='cbcs'||scheme==='cenc'){\n          const frma=findBox(sinf, ['frma'])[0];\n          if(frma){\n            // for encrypted content codec fourCC will be in frma\n            codec=bin2str(frma);\n          }\n        }\n      }\n    });\n  }\n  switch (codec){\n    case 'avc1':\n    case 'avc2':\n    case 'avc3':\n    case 'avc4':\n      {\n        // extract profile + compatibility + level out of avcC box\n        const avcCBox=findBox(sampleEntriesEnd, ['avcC'])[0];\n        codec +='.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);\n        break;\n      }\n    case 'mp4a':\n      {\n        const codecBox=findBox(sampleEntries, [fourCC])[0];\n        const esdsBox=findBox(codecBox.subarray(28), ['esds'])[0];\n        if(esdsBox&&esdsBox.length > 12){\n          let i=4;\n          // ES Descriptor tag\n          if(esdsBox[i++]!==0x03){\n            break;\n          }\n          i=skipBERInteger(esdsBox, i);\n          i +=2; // skip es_id;\n          const flags=esdsBox[i++];\n          if(flags & 0x80){\n            i +=2; // skip dependency es_id\n          }\n          if(flags & 0x40){\n            i +=esdsBox[i++]; // skip URL\n          }\n          // Decoder config descriptor\n          if(esdsBox[i++]!==0x04){\n            break;\n          }\n          i=skipBERInteger(esdsBox, i);\n          const objectType=esdsBox[i++];\n          if(objectType===0x40){\n            codec +='.' + toHex(objectType);\n          }else{\n            break;\n          }\n          i +=12;\n          // Decoder specific info\n          if(esdsBox[i++]!==0x05){\n            break;\n          }\n          i=skipBERInteger(esdsBox, i);\n          const firstByte=esdsBox[i++];\n          let audioObjectType=(firstByte & 0xf8) >> 3;\n          if(audioObjectType===31){\n            audioObjectType +=1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5);\n          }\n          codec +='.' + audioObjectType;\n        }\n        break;\n      }\n    case 'hvc1':\n    case 'hev1':\n      {\n        const hvcCBox=findBox(sampleEntriesEnd, ['hvcC'])[0];\n        const profileByte=hvcCBox[1];\n        const profileSpace=['', 'A', 'B', 'C'][profileByte >> 6];\n        const generalProfileIdc=profileByte & 0x1f;\n        const profileCompat=readUint32(hvcCBox, 2);\n        const tierFlag=(profileByte & 0x20) >> 5 ? 'H':'L';\n        const levelIDC=hvcCBox[12];\n        const constraintIndicator=hvcCBox.subarray(6, 12);\n        codec +='.' + profileSpace + generalProfileIdc;\n        codec +='.' + profileCompat.toString(16).toUpperCase();\n        codec +='.' + tierFlag + levelIDC;\n        let constraintString='';\n        for (let i=constraintIndicator.length; i--;){\n          const byte=constraintIndicator[i];\n          if(byte||constraintString){\n            const encodedByte=byte.toString(16).toUpperCase();\n            constraintString='.' + encodedByte + constraintString;\n          }\n        }\n        codec +=constraintString;\n        break;\n      }\n    case 'dvh1':\n    case 'dvhe':\n      {\n        const dvcCBox=findBox(sampleEntriesEnd, ['dvcC'])[0];\n        const profile=dvcCBox[2] >> 1 & 0x7f;\n        const level=dvcCBox[2] << 5 & 0x20 | dvcCBox[3] >> 3 & 0x1f;\n        codec +='.' + addLeadingZero(profile) + '.' + addLeadingZero(level);\n        break;\n      }\n    case 'vp09':\n      {\n        const vpcCBox=findBox(sampleEntriesEnd, ['vpcC'])[0];\n        const profile=vpcCBox[4];\n        const level=vpcCBox[5];\n        const bitDepth=vpcCBox[6] >> 4 & 0x0f;\n        codec +='.' + addLeadingZero(profile) + '.' + addLeadingZero(level) + '.' + addLeadingZero(bitDepth);\n        break;\n      }\n    case 'av01':\n      {\n        const av1CBox=findBox(sampleEntriesEnd, ['av1C'])[0];\n        const profile=av1CBox[1] >>> 5;\n        const level=av1CBox[1] & 0x1f;\n        const tierFlag=av1CBox[2] >>> 7 ? 'H':'M';\n        const highBitDepth=(av1CBox[2] & 0x40) >> 6;\n        const twelveBit=(av1CBox[2] & 0x20) >> 5;\n        const bitDepth=profile===2&&highBitDepth ? twelveBit ? 12:10:highBitDepth ? 10:8;\n        const monochrome=(av1CBox[2] & 0x10) >> 4;\n        const chromaSubsamplingX=(av1CBox[2] & 0x08) >> 3;\n        const chromaSubsamplingY=(av1CBox[2] & 0x04) >> 2;\n        const chromaSamplePosition=av1CBox[2] & 0x03;\n        // TODO: parse color_description_present_flag\n        // default it to BT.709/limited range for now\n        // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax\n        const colorPrimaries=1;\n        const transferCharacteristics=1;\n        const matrixCoefficients=1;\n        const videoFullRangeFlag=0;\n        codec +='.' + profile + '.' + addLeadingZero(level) + tierFlag + '.' + addLeadingZero(bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag;\n        break;\n      }\n  }\n  return {\n    codec,\n    encrypted\n  };\n}\nfunction skipBERInteger(bytes, i){\n  const limit=i + 5;\n  while (bytes[i++] & 0x80&&i < limit){}\n  return i;\n}\nfunction toHex(x){\n  return ('0' + x.toString(16).toUpperCase()).slice(-2);\n}\nfunction addLeadingZero(num){\n  return (num < 10 ? '0':'') + num;\n}\nfunction patchEncyptionData(initSegment, decryptdata){\n  if(!initSegment||!decryptdata){\n    return initSegment;\n  }\n  const keyId=decryptdata.keyId;\n  if(keyId&&decryptdata.isCommonEncryption){\n    const traks=findBox(initSegment, ['moov', 'trak']);\n    traks.forEach(trak=> {\n      const stsd=findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];\n\n      // skip the sample entry count\n      const sampleEntries=stsd.subarray(8);\n      let encBoxes=findBox(sampleEntries, ['enca']);\n      const isAudio=encBoxes.length > 0;\n      if(!isAudio){\n        encBoxes=findBox(sampleEntries, ['encv']);\n      }\n      encBoxes.forEach(enc=> {\n        const encBoxChildren=isAudio ? enc.subarray(28):enc.subarray(78);\n        const sinfBoxes=findBox(encBoxChildren, ['sinf']);\n        sinfBoxes.forEach(sinf=> {\n          const tenc=parseSinf(sinf);\n          if(tenc){\n            // Look for default key id (keyID offset is always 8 within the tenc box):\n            const tencKeyId=tenc.subarray(8, 24);\n            if(!tencKeyId.some(b=> b!==0)){\n              logger.log(`[eme] Patching keyId in 'enc${isAudio ? 'a':'v'}>sinf>>tenc' box: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump(keyId)}`);\n              tenc.set(keyId, 8);\n            }\n          }\n        });\n      });\n    });\n  }\n  return initSegment;\n}\nfunction parseSinf(sinf){\n  const schm=findBox(sinf, ['schm'])[0];\n  if(schm){\n    const scheme=bin2str(schm.subarray(4, 8));\n    if(scheme==='cbcs'||scheme==='cenc'){\n      return findBox(sinf, ['schi', 'tenc'])[0];\n    }\n  }\n  return null;\n}\n\n\nfunction getStartDTS(initData, fmp4){\n  // we need info from two children of each track fragment box\n  return findBox(fmp4, ['moof', 'traf']).reduce((result, traf)=> {\n    const tfdt=findBox(traf, ['tfdt'])[0];\n    const version=tfdt[0];\n    const start=findBox(traf, ['tfhd']).reduce((result, tfhd)=> {\n      // get the track id from the tfhd\n      const id=readUint32(tfhd, 4);\n      const track=initData[id];\n      if(track){\n        let baseTime=readUint32(tfdt, 4);\n        if(version===1){\n          // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.\n          // This prevents large values from being used for initPTS, which can cause playlist sync issues.\n          // https://github.com/video-dev/hls.js/issues/5303\n          if(baseTime===UINT32_MAX$1){\n            logger.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`);\n            return result;\n          }\n          baseTime *=UINT32_MAX$1 + 1;\n          baseTime +=readUint32(tfdt, 8);\n        }\n        // assume a 90kHz clock if no timescale was specified\n        const scale=track.timescale||90e3;\n        // convert base time to seconds\n        const startTime=baseTime / scale;\n        if(isFiniteNumber(startTime)&&(result===null||startTime < result)){\n          return startTime;\n        }\n      }\n      return result;\n    }, null);\n    if(start!==null&&isFiniteNumber(start)&&(result===null||start < result)){\n      return start;\n    }\n    return result;\n  }, null);\n}\n\n\nfunction getDuration(data, initData){\n  let rawDuration=0;\n  let videoDuration=0;\n  let audioDuration=0;\n  const trafs=findBox(data, ['moof', 'traf']);\n  for (let i=0; i < trafs.length; i++){\n    const traf=trafs[i];\n    // There is only one tfhd & trun per traf\n    // This is true for CMAF style content, and we should perhaps check the ftyp\n    // and only look for a single trun then, but for ISOBMFF we should check\n    // for multiple track runs.\n    const tfhd=findBox(traf, ['tfhd'])[0];\n    // get the track id from the tfhd\n    const id=readUint32(tfhd, 4);\n    const track=initData[id];\n    if(!track){\n      continue;\n    }\n    const trackDefault=track.default;\n    const tfhdFlags=readUint32(tfhd, 0) | (trackDefault==null ? void 0:trackDefault.flags);\n    let sampleDuration=trackDefault==null ? void 0:trackDefault.duration;\n    if(tfhdFlags & 0x000008){\n      // 0x000008 indicates the presence of the default_sample_duration field\n      if(tfhdFlags & 0x000002){\n        // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration\n        // If present, the default_sample_duration exists at byte offset 12\n        sampleDuration=readUint32(tfhd, 12);\n      }else{\n        // Otherwise, the duration is at byte offset 8\n        sampleDuration=readUint32(tfhd, 8);\n      }\n    }\n    // assume a 90kHz clock if no timescale was specified\n    const timescale=track.timescale||90e3;\n    const truns=findBox(traf, ['trun']);\n    for (let j=0; j < truns.length; j++){\n      rawDuration=computeRawDurationFromSamples(truns[j]);\n      if(!rawDuration&&sampleDuration){\n        const sampleCount=readUint32(truns[j], 4);\n        rawDuration=sampleDuration * sampleCount;\n      }\n      if(track.type===ElementaryStreamTypes.VIDEO){\n        videoDuration +=rawDuration / timescale;\n      }else if(track.type===ElementaryStreamTypes.AUDIO){\n        audioDuration +=rawDuration / timescale;\n      }\n    }\n  }\n  if(videoDuration===0&&audioDuration===0){\n    // If duration samples are not available in the traf use sidx subsegment_duration\n    let sidxMinStart=Infinity;\n    let sidxMaxEnd=0;\n    let sidxDuration=0;\n    const sidxs=findBox(data, ['sidx']);\n    for (let i=0; i < sidxs.length; i++){\n      const sidx=parseSegmentIndex(sidxs[i]);\n      if(sidx!=null&&sidx.references){\n        sidxMinStart=Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale);\n        const subSegmentDuration=sidx.references.reduce((dur, ref)=> dur + ref.info.duration||0, 0);\n        sidxMaxEnd=Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale);\n        sidxDuration=sidxMaxEnd - sidxMinStart;\n      }\n    }\n    if(sidxDuration&&isFiniteNumber(sidxDuration)){\n      return sidxDuration;\n    }\n  }\n  if(videoDuration){\n    return videoDuration;\n  }\n  return audioDuration;\n}\n\n\nfunction computeRawDurationFromSamples(trun){\n  const flags=readUint32(trun, 0);\n  // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in.\n  // Each field is an int32, which is 4 bytes\n  let offset=8;\n  // data-offset-present flag\n  if(flags & 0x000001){\n    offset +=4;\n  }\n  // first-sample-flags-present flag\n  if(flags & 0x000004){\n    offset +=4;\n  }\n  let duration=0;\n  const sampleCount=readUint32(trun, 4);\n  for (let i=0; i < sampleCount; i++){\n    // sample-duration-present flag\n    if(flags & 0x000100){\n      const sampleDuration=readUint32(trun, offset);\n      duration +=sampleDuration;\n      offset +=4;\n    }\n    // sample-size-present flag\n    if(flags & 0x000200){\n      offset +=4;\n    }\n    // sample-flags-present flag\n    if(flags & 0x000400){\n      offset +=4;\n    }\n    // sample-composition-time-offsets-present flag\n    if(flags & 0x000800){\n      offset +=4;\n    }\n  }\n  return duration;\n}\nfunction offsetStartDTS(initData, fmp4, timeOffset){\n  findBox(fmp4, ['moof', 'traf']).forEach(traf=> {\n    findBox(traf, ['tfhd']).forEach(tfhd=> {\n      // get the track id from the tfhd\n      const id=readUint32(tfhd, 4);\n      const track=initData[id];\n      if(!track){\n        return;\n      }\n      // assume a 90kHz clock if no timescale was specified\n      const timescale=track.timescale||90e3;\n      // get the base media decode time from the tfdt\n      findBox(traf, ['tfdt']).forEach(tfdt=> {\n        const version=tfdt[0];\n        const offset=timeOffset * timescale;\n        if(offset){\n          let baseMediaDecodeTime=readUint32(tfdt, 4);\n          if(version===0){\n            baseMediaDecodeTime -=offset;\n            baseMediaDecodeTime=Math.max(baseMediaDecodeTime, 0);\n            writeUint32(tfdt, 4, baseMediaDecodeTime);\n          }else{\n            baseMediaDecodeTime *=Math.pow(2, 32);\n            baseMediaDecodeTime +=readUint32(tfdt, 8);\n            baseMediaDecodeTime -=offset;\n            baseMediaDecodeTime=Math.max(baseMediaDecodeTime, 0);\n            const upper=Math.floor(baseMediaDecodeTime / (UINT32_MAX$1 + 1));\n            const lower=Math.floor(baseMediaDecodeTime % (UINT32_MAX$1 + 1));\n            writeUint32(tfdt, 4, upper);\n            writeUint32(tfdt, 8, lower);\n          }\n        }\n      });\n    });\n  });\n}\n\n// TODO: Check if the last moof+mdat pair is part of the valid range\nfunction segmentValidRange(data){\n  const segmentedRange={\n    valid: null,\n    remainder: null\n  };\n  const moofs=findBox(data, ['moof']);\n  if(moofs.length < 2){\n    segmentedRange.remainder=data;\n    return segmentedRange;\n  }\n  const last=moofs[moofs.length - 1];\n  // Offset by 8 bytes; findBox offsets the start by as much\n  segmentedRange.valid=sliceUint8(data, 0, last.byteOffset - 8);\n  segmentedRange.remainder=sliceUint8(data, last.byteOffset - 8);\n  return segmentedRange;\n}\nfunction appendUint8Array(data1, data2){\n  const temp=new Uint8Array(data1.length + data2.length);\n  temp.set(data1);\n  temp.set(data2, data1.length);\n  return temp;\n}\nfunction parseSamples(timeOffset, track){\n  const seiSamples=[];\n  const videoData=track.samples;\n  const timescale=track.timescale;\n  const trackId=track.id;\n  let isHEVCFlavor=false;\n  const moofs=findBox(videoData, ['moof']);\n  moofs.map(moof=> {\n    const moofOffset=moof.byteOffset - 8;\n    const trafs=findBox(moof, ['traf']);\n    trafs.map(traf=> {\n      // get the base media decode time from the tfdt\n      const baseTime=findBox(traf, ['tfdt']).map(tfdt=> {\n        const version=tfdt[0];\n        let result=readUint32(tfdt, 4);\n        if(version===1){\n          result *=Math.pow(2, 32);\n          result +=readUint32(tfdt, 8);\n        }\n        return result / timescale;\n      })[0];\n      if(baseTime!==undefined){\n        timeOffset=baseTime;\n      }\n      return findBox(traf, ['tfhd']).map(tfhd=> {\n        const id=readUint32(tfhd, 4);\n        const tfhdFlags=readUint32(tfhd, 0) & 0xffffff;\n        const baseDataOffsetPresent=(tfhdFlags & 0x000001)!==0;\n        const sampleDescriptionIndexPresent=(tfhdFlags & 0x000002)!==0;\n        const defaultSampleDurationPresent=(tfhdFlags & 0x000008)!==0;\n        let defaultSampleDuration=0;\n        const defaultSampleSizePresent=(tfhdFlags & 0x000010)!==0;\n        let defaultSampleSize=0;\n        const defaultSampleFlagsPresent=(tfhdFlags & 0x000020)!==0;\n        let tfhdOffset=8;\n        if(id===trackId){\n          if(baseDataOffsetPresent){\n            tfhdOffset +=8;\n          }\n          if(sampleDescriptionIndexPresent){\n            tfhdOffset +=4;\n          }\n          if(defaultSampleDurationPresent){\n            defaultSampleDuration=readUint32(tfhd, tfhdOffset);\n            tfhdOffset +=4;\n          }\n          if(defaultSampleSizePresent){\n            defaultSampleSize=readUint32(tfhd, tfhdOffset);\n            tfhdOffset +=4;\n          }\n          if(defaultSampleFlagsPresent){\n            tfhdOffset +=4;\n          }\n          if(track.type==='video'){\n            isHEVCFlavor=isHEVC(track.codec);\n          }\n          findBox(traf, ['trun']).map(trun=> {\n            const version=trun[0];\n            const flags=readUint32(trun, 0) & 0xffffff;\n            const dataOffsetPresent=(flags & 0x000001)!==0;\n            let dataOffset=0;\n            const firstSampleFlagsPresent=(flags & 0x000004)!==0;\n            const sampleDurationPresent=(flags & 0x000100)!==0;\n            let sampleDuration=0;\n            const sampleSizePresent=(flags & 0x000200)!==0;\n            let sampleSize=0;\n            const sampleFlagsPresent=(flags & 0x000400)!==0;\n            const sampleCompositionOffsetsPresent=(flags & 0x000800)!==0;\n            let compositionOffset=0;\n            const sampleCount=readUint32(trun, 4);\n            let trunOffset=8; // past version, flags, and sample count\n\n            if(dataOffsetPresent){\n              dataOffset=readUint32(trun, trunOffset);\n              trunOffset +=4;\n            }\n            if(firstSampleFlagsPresent){\n              trunOffset +=4;\n            }\n            let sampleOffset=dataOffset + moofOffset;\n            for (let ix=0; ix < sampleCount; ix++){\n              if(sampleDurationPresent){\n                sampleDuration=readUint32(trun, trunOffset);\n                trunOffset +=4;\n              }else{\n                sampleDuration=defaultSampleDuration;\n              }\n              if(sampleSizePresent){\n                sampleSize=readUint32(trun, trunOffset);\n                trunOffset +=4;\n              }else{\n                sampleSize=defaultSampleSize;\n              }\n              if(sampleFlagsPresent){\n                trunOffset +=4;\n              }\n              if(sampleCompositionOffsetsPresent){\n                if(version===0){\n                  compositionOffset=readUint32(trun, trunOffset);\n                }else{\n                  compositionOffset=readSint32(trun, trunOffset);\n                }\n                trunOffset +=4;\n              }\n              if(track.type===ElementaryStreamTypes.VIDEO){\n                let naluTotalSize=0;\n                while (naluTotalSize < sampleSize){\n                  const naluSize=readUint32(videoData, sampleOffset);\n                  sampleOffset +=4;\n                  if(isSEIMessage(isHEVCFlavor, videoData[sampleOffset])){\n                    const data=videoData.subarray(sampleOffset, sampleOffset + naluSize);\n                    parseSEIMessageFromNALu(data, isHEVCFlavor ? 2:1, timeOffset + compositionOffset / timescale, seiSamples);\n                  }\n                  sampleOffset +=naluSize;\n                  naluTotalSize +=naluSize + 4;\n                }\n              }\n              timeOffset +=sampleDuration / timescale;\n            }\n          });\n        }\n      });\n    });\n  });\n  return seiSamples;\n}\nfunction isHEVC(codec){\n  if(!codec){\n    return false;\n  }\n  const delimit=codec.indexOf('.');\n  const baseCodec=delimit < 0 ? codec:codec.substring(0, delimit);\n  return baseCodec==='hvc1'||baseCodec==='hev1'||\n  // Dolby Vision\n  baseCodec==='dvh1'||baseCodec==='dvhe';\n}\nfunction isSEIMessage(isHEVCFlavor, naluHeader){\n  if(isHEVCFlavor){\n    const naluType=naluHeader >> 1 & 0x3f;\n    return naluType===39||naluType===40;\n  }else{\n    const naluType=naluHeader & 0x1f;\n    return naluType===6;\n  }\n}\nfunction parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples){\n  const data=discardEPB(unescapedData);\n  let seiPtr=0;\n  // skip nal header\n  seiPtr +=headerSize;\n  let payloadType=0;\n  let payloadSize=0;\n  let b=0;\n  while (seiPtr < data.length){\n    payloadType=0;\n    do {\n      if(seiPtr >=data.length){\n        break;\n      }\n      b=data[seiPtr++];\n      payloadType +=b;\n    } while (b===0xff);\n\n    // Parse payload size.\n    payloadSize=0;\n    do {\n      if(seiPtr >=data.length){\n        break;\n      }\n      b=data[seiPtr++];\n      payloadSize +=b;\n    } while (b===0xff);\n    const leftOver=data.length - seiPtr;\n    // Create a variable to process the payload\n    let payPtr=seiPtr;\n\n    // Increment the seiPtr to the end of the payload\n    if(payloadSize < leftOver){\n      seiPtr +=payloadSize;\n    }else if(payloadSize > leftOver){\n      // Some type of corruption has happened?\n      logger.error(`Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`);\n      // We might be able to parse some data, but let's be safe and ignore it.\n      break;\n    }\n    if(payloadType===4){\n      const countryCode=data[payPtr++];\n      if(countryCode===181){\n        const providerCode=readUint16(data, payPtr);\n        payPtr +=2;\n        if(providerCode===49){\n          const userStructure=readUint32(data, payPtr);\n          payPtr +=4;\n          if(userStructure===0x47413934){\n            const userDataType=data[payPtr++];\n\n            // Raw CEA-608 bytes wrapped in CEA-708 packet\n            if(userDataType===3){\n              const firstByte=data[payPtr++];\n              const totalCCs=0x1f & firstByte;\n              const enabled=0x40 & firstByte;\n              const totalBytes=enabled ? 2 + totalCCs * 3:0;\n              const byteArray=new Uint8Array(totalBytes);\n              if(enabled){\n                byteArray[0]=firstByte;\n                for (let i=1; i < totalBytes; i++){\n                  byteArray[i]=data[payPtr++];\n                }\n              }\n              samples.push({\n                type: userDataType,\n                payloadType,\n                pts,\n                bytes: byteArray\n              });\n            }\n          }\n        }\n      }\n    }else if(payloadType===5){\n      if(payloadSize > 16){\n        const uuidStrArray=[];\n        for (let i=0; i < 16; i++){\n          const _b=data[payPtr++].toString(16);\n          uuidStrArray.push(_b.length==1 ? '0' + _b:_b);\n          if(i===3||i===5||i===7||i===9){\n            uuidStrArray.push('-');\n          }\n        }\n        const length=payloadSize - 16;\n        const userDataBytes=new Uint8Array(length);\n        for (let i=0; i < length; i++){\n          userDataBytes[i]=data[payPtr++];\n        }\n        samples.push({\n          payloadType,\n          pts,\n          uuid: uuidStrArray.join(''),\n          userData: utf8ArrayToStr(userDataBytes),\n          userDataBytes\n        });\n      }\n    }\n  }\n}\n\n\nfunction discardEPB(data){\n  const length=data.byteLength;\n  const EPBPositions=[];\n  let i=1;\n\n  // Find all `Emulation Prevention Bytes`\n  while (i < length - 2){\n    if(data[i]===0&&data[i + 1]===0&&data[i + 2]===0x03){\n      EPBPositions.push(i + 2);\n      i +=2;\n    }else{\n      i++;\n    }\n  }\n\n  // If no Emulation Prevention Bytes were found just return the original\n  // array\n  if(EPBPositions.length===0){\n    return data;\n  }\n\n  // Create a new array to hold the NAL unit data\n  const newLength=length - EPBPositions.length;\n  const newData=new Uint8Array(newLength);\n  let sourceIndex=0;\n  for (i=0; i < newLength; sourceIndex++, i++){\n    if(sourceIndex===EPBPositions[0]){\n      // Skip this byte\n      sourceIndex++;\n      // Remove this position index\n      EPBPositions.shift();\n    }\n    newData[i]=data[sourceIndex];\n  }\n  return newData;\n}\nfunction parseEmsg(data){\n  const version=data[0];\n  let schemeIdUri='';\n  let value='';\n  let timeScale=0;\n  let presentationTimeDelta=0;\n  let presentationTime=0;\n  let eventDuration=0;\n  let id=0;\n  let offset=0;\n  if(version===0){\n    while (bin2str(data.subarray(offset, offset + 1))!=='\\0'){\n      schemeIdUri +=bin2str(data.subarray(offset, offset + 1));\n      offset +=1;\n    }\n    schemeIdUri +=bin2str(data.subarray(offset, offset + 1));\n    offset +=1;\n    while (bin2str(data.subarray(offset, offset + 1))!=='\\0'){\n      value +=bin2str(data.subarray(offset, offset + 1));\n      offset +=1;\n    }\n    value +=bin2str(data.subarray(offset, offset + 1));\n    offset +=1;\n    timeScale=readUint32(data, 12);\n    presentationTimeDelta=readUint32(data, 16);\n    eventDuration=readUint32(data, 20);\n    id=readUint32(data, 24);\n    offset=28;\n  }else if(version===1){\n    offset +=4;\n    timeScale=readUint32(data, offset);\n    offset +=4;\n    const leftPresentationTime=readUint32(data, offset);\n    offset +=4;\n    const rightPresentationTime=readUint32(data, offset);\n    offset +=4;\n    presentationTime=2 ** 32 * leftPresentationTime + rightPresentationTime;\n    if(!isSafeInteger(presentationTime)){\n      presentationTime=Number.MAX_SAFE_INTEGER;\n      logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box');\n    }\n    eventDuration=readUint32(data, offset);\n    offset +=4;\n    id=readUint32(data, offset);\n    offset +=4;\n    while (bin2str(data.subarray(offset, offset + 1))!=='\\0'){\n      schemeIdUri +=bin2str(data.subarray(offset, offset + 1));\n      offset +=1;\n    }\n    schemeIdUri +=bin2str(data.subarray(offset, offset + 1));\n    offset +=1;\n    while (bin2str(data.subarray(offset, offset + 1))!=='\\0'){\n      value +=bin2str(data.subarray(offset, offset + 1));\n      offset +=1;\n    }\n    value +=bin2str(data.subarray(offset, offset + 1));\n    offset +=1;\n  }\n  const payload=data.subarray(offset, data.byteLength);\n  return {\n    schemeIdUri,\n    value,\n    timeScale,\n    presentationTime,\n    presentationTimeDelta,\n    eventDuration,\n    id,\n    payload\n  };\n}\nfunction mp4Box(type, ...payload){\n  const len=payload.length;\n  let size=8;\n  let i=len;\n  while (i--){\n    size +=payload[i].byteLength;\n  }\n  const 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  for (i=0, size=8; i < len; i++){\n    result.set(payload[i], size);\n    size +=payload[i].byteLength;\n  }\n  return result;\n}\nfunction mp4pssh(systemId, keyids, data){\n  if(systemId.byteLength!==16){\n    throw new RangeError('Invalid system id');\n  }\n  let version;\n  let kids;\n  if(keyids){\n    version=1;\n    kids=new Uint8Array(keyids.length * 16);\n    for (let ix=0; ix < keyids.length; ix++){\n      const k=keyids[ix]; // uint8array\n      if(k.byteLength!==16){\n        throw new RangeError('Invalid key');\n      }\n      kids.set(k, ix * 16);\n    }\n  }else{\n    version=0;\n    kids=new Uint8Array();\n  }\n  let kidCount;\n  if(version > 0){\n    kidCount=new Uint8Array(4);\n    if(keyids.length > 0){\n      new DataView(kidCount.buffer).setUint32(0, keyids.length, false);\n    }\n  }else{\n    kidCount=new Uint8Array();\n  }\n  const dataSize=new Uint8Array(4);\n  if(data&&data.byteLength > 0){\n    new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);\n  }\n  return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags\n  ]), systemId,\n  // 16 bytes\n  kidCount, kids, dataSize, data||new Uint8Array());\n}\nfunction parseMultiPssh(initData){\n  const results=[];\n  if(initData instanceof ArrayBuffer){\n    const length=initData.byteLength;\n    let offset=0;\n    while (offset + 32 < length){\n      const view=new DataView(initData, offset);\n      const pssh=parsePssh(view);\n      results.push(pssh);\n      offset +=pssh.size;\n    }\n  }\n  return results;\n}\nfunction parsePssh(view){\n  const size=view.getUint32(0);\n  const offset=view.byteOffset;\n  const length=view.byteLength;\n  if(length < size){\n    return {\n      offset,\n      size: length\n    };\n  }\n  const type=view.getUint32(4);\n  if(type!==0x70737368){\n    return {\n      offset,\n      size\n    };\n  }\n  const version=view.getUint32(8) >>> 24;\n  if(version!==0&&version!==1){\n    return {\n      offset,\n      size\n    };\n  }\n  const buffer=view.buffer;\n  const systemId=Hex.hexDump(new Uint8Array(buffer, offset + 12, 16));\n  const dataSizeOrKidCount=view.getUint32(28);\n  let kids=null;\n  let data=null;\n  if(version===0){\n    if(size - 32 < dataSizeOrKidCount||dataSizeOrKidCount < 22){\n      return {\n        offset,\n        size\n      };\n    }\n    data=new Uint8Array(buffer, offset + 32, dataSizeOrKidCount);\n  }else if(version===1){\n    if(!dataSizeOrKidCount||length < offset + 32 + dataSizeOrKidCount * 16 + 16){\n      return {\n        offset,\n        size\n      };\n    }\n    kids=[];\n    for (let i=0; i < dataSizeOrKidCount; i++){\n      kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));\n    }\n  }\n  return {\n    version,\n    systemId,\n    kids,\n    data,\n    offset,\n    size\n  };\n}\n\nlet keyUriToKeyIdMap={};\nclass LevelKey {\n  static clearKeyUriToKeyIdMap(){\n    keyUriToKeyIdMap={};\n  }\n  constructor(method, uri, format, formatversions=[1], iv=null){\n    this.uri=void 0;\n    this.method=void 0;\n    this.keyFormat=void 0;\n    this.keyFormatVersions=void 0;\n    this.encrypted=void 0;\n    this.isCommonEncryption=void 0;\n    this.iv=null;\n    this.key=null;\n    this.keyId=null;\n    this.pssh=null;\n    this.method=method;\n    this.uri=uri;\n    this.keyFormat=format;\n    this.keyFormatVersions=formatversions;\n    this.iv=iv;\n    this.encrypted=method ? method!=='NONE':false;\n    this.isCommonEncryption=this.encrypted&&method!=='AES-128';\n  }\n  isSupported(){\n    // If it's Segment encryption or No encryption, just select that key system\n    if(this.method){\n      if(this.method==='AES-128'||this.method==='NONE'){\n        return true;\n      }\n      if(this.keyFormat==='identity'){\n        // Maintain support for clear SAMPLE-AES with MPEG-3 TS\n        return this.method==='SAMPLE-AES';\n      }else{\n        switch (this.keyFormat){\n          case KeySystemFormats.FAIRPLAY:\n          case KeySystemFormats.WIDEVINE:\n          case KeySystemFormats.PLAYREADY:\n          case KeySystemFormats.CLEARKEY:\n            return ['ISO-23001-7', 'SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method)!==-1;\n        }\n      }\n    }\n    return false;\n  }\n  getDecryptData(sn){\n    if(!this.encrypted||!this.uri){\n      return null;\n    }\n    if(this.method==='AES-128'&&this.uri&&!this.iv){\n      if(typeof sn!=='number'){\n        // We are fetching decryption data for a initialization segment\n        // If the segment was encrypted with AES-128\n        // It must have an IV defined. We cannot substitute the Segment Number in.\n        if(this.method==='AES-128'&&!this.iv){\n          logger.warn(`missing IV for initialization segment with method=\"${this.method}\" - compliance issue`);\n        }\n        // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.\n        sn=0;\n      }\n      const iv=createInitializationVector(sn);\n      const decryptdata=new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv);\n      return decryptdata;\n    }\n\n    // Initialize keyId if possible\n    const keyBytes=convertDataUriToArrayBytes(this.uri);\n    if(keyBytes){\n      switch (this.keyFormat){\n        case KeySystemFormats.WIDEVINE:\n          // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using\n          // the playlist-key before the \"encrypted\" event. (Comment out to only use \"encrypted\" path.)\n          this.pssh=keyBytes;\n          // In case of widevine keyID is embedded in PSSH box. Read Key ID.\n          if(keyBytes.length >=22){\n            this.keyId=keyBytes.subarray(keyBytes.length - 22, keyBytes.length - 6);\n          }\n          break;\n        case KeySystemFormats.PLAYREADY:\n          {\n            const PlayReadyKeySystemUUID=new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);\n\n            // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using\n            // the playlist-key before the \"encrypted\" event. (Comment out to only use \"encrypted\" path.)\n            this.pssh=mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);\n            this.keyId=parsePlayReadyWRM(keyBytes);\n            break;\n          }\n        default:\n          {\n            let keydata=keyBytes.subarray(0, 16);\n            if(keydata.length!==16){\n              const padded=new Uint8Array(16);\n              padded.set(keydata, 16 - keydata.length);\n              keydata=padded;\n            }\n            this.keyId=keydata;\n            break;\n          }\n      }\n    }\n\n    // Default behavior: assign a new keyId for each uri\n    if(!this.keyId||this.keyId.byteLength!==16){\n      let keyId=keyUriToKeyIdMap[this.uri];\n      if(!keyId){\n        const val=Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;\n        keyId=new Uint8Array(16);\n        const dv=new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes\n        dv.setUint32(0, val);\n        keyUriToKeyIdMap[this.uri]=keyId;\n      }\n      this.keyId=keyId;\n    }\n    return this;\n  }\n}\nfunction createInitializationVector(segmentNumber){\n  const uint8View=new Uint8Array(16);\n  for (let i=12; i < 16; i++){\n    uint8View[i]=segmentNumber >> 8 * (15 - i) & 0xff;\n  }\n  return uint8View;\n}\n\nconst VARIABLE_REPLACEMENT_REGEX=/\\{\\$([a-zA-Z0-9-_]+)\\}/g;\nfunction hasVariableReferences(str){\n  return VARIABLE_REPLACEMENT_REGEX.test(str);\n}\nfunction substituteVariablesInAttributes(parsed, attr, attributeNames){\n  if(parsed.variableList!==null||parsed.hasVariableRefs){\n    for (let i=attributeNames.length; i--;){\n      const name=attributeNames[i];\n      const value=attr[name];\n      if(value){\n        attr[name]=substituteVariables(parsed, value);\n      }\n    }\n  }\n}\nfunction substituteVariables(parsed, value){\n  if(parsed.variableList!==null||parsed.hasVariableRefs){\n    const variableList=parsed.variableList;\n    return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference=> {\n      const variableName=variableReference.substring(2, variableReference.length - 1);\n      const variableValue=variableList==null ? void 0:variableList[variableName];\n      if(variableValue===undefined){\n        parsed.playlistParsingError||(parsed.playlistParsingError=new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: \"${variableName}\"`));\n        return variableReference;\n      }\n      return variableValue;\n    });\n  }\n  return value;\n}\nfunction addVariableDefinition(parsed, attr, parentUrl){\n  let variableList=parsed.variableList;\n  if(!variableList){\n    parsed.variableList=variableList={};\n  }\n  let NAME;\n  let VALUE;\n  if('QUERYPARAM' in attr){\n    NAME=attr.QUERYPARAM;\n    try {\n      const searchParams=new self.URL(parentUrl).searchParams;\n      if(searchParams.has(NAME)){\n        VALUE=searchParams.get(NAME);\n      }else{\n        throw new Error(`\"${NAME}\" does not match any query parameter in URI: \"${parentUrl}\"`);\n      }\n    } catch (error){\n      parsed.playlistParsingError||(parsed.playlistParsingError=new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));\n    }\n  }else{\n    NAME=attr.NAME;\n    VALUE=attr.VALUE;\n  }\n  if(NAME in variableList){\n    parsed.playlistParsingError||(parsed.playlistParsingError=new Error(`EXT-X-DEFINE duplicate Variable Name declarations: \"${NAME}\"`));\n  }else{\n    variableList[NAME]=VALUE||'';\n  }\n}\nfunction importVariableDefinition(parsed, attr, sourceVariableList){\n  const IMPORT=attr.IMPORT;\n  if(sourceVariableList&&IMPORT in sourceVariableList){\n    let variableList=parsed.variableList;\n    if(!variableList){\n      parsed.variableList=variableList={};\n    }\n    variableList[IMPORT]=sourceVariableList[IMPORT];\n  }else{\n    parsed.playlistParsingError||(parsed.playlistParsingError=new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: \"${IMPORT}\"`));\n  }\n}\n\n\n\nfunction getMediaSource(preferManagedMediaSource=true){\n  if(typeof self==='undefined') return undefined;\n  const mms=(preferManagedMediaSource||!self.MediaSource)&&self.ManagedMediaSource;\n  return mms||self.MediaSource||self.WebKitMediaSource;\n}\nfunction isManagedMediaSource(source){\n  return typeof self!=='undefined'&&source===self.ManagedMediaSource;\n}\n\n// from http://mp4ra.org/codecs.html\n// values indicate codec selection preference (lower is higher priority)\nconst sampleEntryCodesISO={\n  audio: {\n    a3ds: 1,\n    'ac-3': 0.95,\n    'ac-4': 1,\n    alac: 0.9,\n    alaw: 1,\n    dra1: 1,\n    'dts+': 1,\n    'dts-': 1,\n    dtsc: 1,\n    dtse: 1,\n    dtsh: 1,\n    'ec-3': 0.9,\n    enca: 1,\n    fLaC: 0.9,\n    // MP4-RA listed codec entry for FLAC\n    flac: 0.9,\n    // legacy browser codec name for FLAC\n    FLAC: 0.9,\n    // some manifests may list \"FLAC\" with Apple's tools\n    g719: 1,\n    g726: 1,\n    m4ae: 1,\n    mha1: 1,\n    mha2: 1,\n    mhm1: 1,\n    mhm2: 1,\n    mlpa: 1,\n    mp4a: 1,\n    'raw ': 1,\n    Opus: 1,\n    opus: 1,\n    // browsers expect this to be lowercase despite MP4RA says 'Opus'\n    samr: 1,\n    sawb: 1,\n    sawp: 1,\n    sevc: 1,\n    sqcp: 1,\n    ssmv: 1,\n    twos: 1,\n    ulaw: 1\n  },\n  video: {\n    avc1: 1,\n    avc2: 1,\n    avc3: 1,\n    avc4: 1,\n    avcp: 1,\n    av01: 0.8,\n    drac: 1,\n    dva1: 1,\n    dvav: 1,\n    dvh1: 0.7,\n    dvhe: 0.7,\n    encv: 1,\n    hev1: 0.75,\n    hvc1: 0.75,\n    mjp2: 1,\n    mp4v: 1,\n    mvc1: 1,\n    mvc2: 1,\n    mvc3: 1,\n    mvc4: 1,\n    resv: 1,\n    rv60: 1,\n    s263: 1,\n    svc1: 1,\n    svc2: 1,\n    'vc-1': 1,\n    vp08: 1,\n    vp09: 0.9\n  },\n  text: {\n    stpp: 1,\n    wvtt: 1\n  }\n};\nfunction isCodecType(codec, type){\n  const typeCodes=sampleEntryCodesISO[type];\n  return !!typeCodes&&!!typeCodes[codec.slice(0, 4)];\n}\nfunction areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource=true){\n  return !codecs.split(',').some(codec=> !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource));\n}\nfunction isCodecMediaSourceSupported(codec, type, preferManagedMediaSource=true){\n  var _MediaSource$isTypeSu;\n  const MediaSource=getMediaSource(preferManagedMediaSource);\n  return (_MediaSource$isTypeSu=MediaSource==null ? void 0:MediaSource.isTypeSupported(mimeTypeForCodec(codec, type)))!=null ? _MediaSource$isTypeSu:false;\n}\nfunction mimeTypeForCodec(codec, type){\n  return `${type}/mp4;codecs=\"${codec}\"`;\n}\nfunction videoCodecPreferenceValue(videoCodec){\n  if(videoCodec){\n    const fourCC=videoCodec.substring(0, 4);\n    return sampleEntryCodesISO.video[fourCC];\n  }\n  return 2;\n}\nfunction codecsSetSelectionPreferenceValue(codecSet){\n  return codecSet.split(',').reduce((num, fourCC)=> {\n    const preferenceValue=sampleEntryCodesISO.video[fourCC];\n    if(preferenceValue){\n      return (preferenceValue * 2 + num) / (num ? 3:2);\n    }\n    return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2:1);\n  }, 0);\n}\nconst CODEC_COMPATIBLE_NAMES={};\nfunction getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource=true){\n  if(CODEC_COMPATIBLE_NAMES[lowerCaseCodec]){\n    return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];\n  }\n\n  // Idealy fLaC and Opus would be first (spec-compliant) but\n  // some browsers will report that fLaC is supported then fail.\n  // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728\n  const codecsToCheck={\n    flac: ['flac', 'fLaC', 'FLAC'],\n    opus: ['opus', 'Opus']\n  }[lowerCaseCodec];\n  for (let i=0; i < codecsToCheck.length; i++){\n    if(isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)){\n      CODEC_COMPATIBLE_NAMES[lowerCaseCodec]=codecsToCheck[i];\n      return codecsToCheck[i];\n    }\n  }\n  return lowerCaseCodec;\n}\nconst AUDIO_CODEC_REGEXP=/flac|opus/i;\nfunction getCodecCompatibleName(codec, preferManagedMediaSource=true){\n  return codec.replace(AUDIO_CODEC_REGEXP, m=> getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));\n}\nfunction pickMostCompleteCodecName(parsedCodec, levelCodec){\n  // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a\n  // so use level codec is parsed codec is unavailable or incomplete\n  if(parsedCodec&&parsedCodec!=='mp4a'){\n    return parsedCodec;\n  }\n  return levelCodec ? levelCodec.split(',')[0]:levelCodec;\n}\nfunction convertAVC1ToAVCOTI(codec){\n  // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported\n  // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e.\n  const codecs=codec.split(',');\n  for (let i=0; i < codecs.length; i++){\n    const avcdata=codecs[i].split('.');\n    if(avcdata.length > 2){\n      let result=avcdata.shift() + '.';\n      result +=parseInt(avcdata.shift()).toString(16);\n      result +=('000' + parseInt(avcdata.shift()).toString(16)).slice(-4);\n      codecs[i]=result;\n    }\n  }\n  return codecs.join(',');\n}\n\nconst MASTER_PLAYLIST_REGEX=/#EXT-X-STREAM-INF:([^\\r\\n]*)(?:[\\r\\n](?:#[^\\r\\n]*)?)*([^\\r\\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\\r\\n]*)[\\r\\n]+/g;\nconst MASTER_PLAYLIST_MEDIA_REGEX=/#EXT-X-MEDIA:(.*)/g;\nconst IS_MEDIA_PLAYLIST=/^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present)\n\nconst LEVEL_PLAYLIST_REGEX_FAST=new RegExp([/#EXTINF:\\s*(\\d*(?:\\.\\d+)?)(?:,(.*)\\s+)?/.source,\n// duration (#EXTINF:<duration>,<title>), group 1=> duration, group 2=> title\n/(?!#) *(\\S[^\\r\\n]*)/.source,\n// segment URI, group 3=> the URI (note newline is not eaten)\n/#EXT-X-BYTERANGE:*(.+)/.source,\n// next segment's byterange, group 4=> range spec (x@y)\n/#EXT-X-PROGRAM-DATE-TIME:(.+)/.source,\n// next segment's program date/time group 5=> the datetime spec\n/#.*/.source // All other non-segment oriented tags will match with all groups empty\n].join('|'), 'g');\nconst LEVEL_PLAYLIST_REGEX_SLOW=new RegExp([/#(EXTM3U)/.source, /#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\\r?\\n?/.source].join('|'));\nclass M3U8Parser {\n  static findGroup(groups, mediaGroupId){\n    for (let i=0; i < groups.length; i++){\n      const group=groups[i];\n      if(group.id===mediaGroupId){\n        return group;\n      }\n    }\n  }\n  static resolve(url, baseUrl){\n    return urlToolkitExports.buildAbsoluteURL(baseUrl, url, {\n      alwaysNormalize: true\n    });\n  }\n  static isMediaPlaylist(str){\n    return IS_MEDIA_PLAYLIST.test(str);\n  }\n  static parseMasterPlaylist(string, baseurl){\n    const hasVariableRefs=hasVariableReferences(string) ;\n    const parsed={\n      contentSteering: null,\n      levels: [],\n      playlistParsingError: null,\n      sessionData: null,\n      sessionKeys: null,\n      startTimeOffset: null,\n      variableList: null,\n      hasVariableRefs\n    };\n    const levelsWithKnownCodecs=[];\n    MASTER_PLAYLIST_REGEX.lastIndex=0;\n    let result;\n    while ((result=MASTER_PLAYLIST_REGEX.exec(string))!=null){\n      if(result[1]){\n        var _level$unknownCodecs;\n        // '#EXT-X-STREAM-INF' is found, parse level tag  in group 1\n        const attrs=new AttrList(result[1]);\n        {\n          substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']);\n        }\n        const uri=substituteVariables(parsed, result[2]) ;\n        const level={\n          attrs,\n          bitrate: attrs.decimalInteger('BANDWIDTH')||attrs.decimalInteger('AVERAGE-BANDWIDTH'),\n          name: attrs.NAME,\n          url: M3U8Parser.resolve(uri, baseurl)\n        };\n        const resolution=attrs.decimalResolution('RESOLUTION');\n        if(resolution){\n          level.width=resolution.width;\n          level.height=resolution.height;\n        }\n        setCodecs(attrs.CODECS, level);\n        if(!((_level$unknownCodecs=level.unknownCodecs)!=null&&_level$unknownCodecs.length)){\n          levelsWithKnownCodecs.push(level);\n        }\n        parsed.levels.push(level);\n      }else if(result[3]){\n        const tag=result[3];\n        const attributes=result[4];\n        switch (tag){\n          case 'SESSION-DATA':\n            {\n              // #EXT-X-SESSION-DATA\n              const sessionAttrs=new AttrList(attributes);\n              {\n                substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']);\n              }\n              const dataId=sessionAttrs['DATA-ID'];\n              if(dataId){\n                if(parsed.sessionData===null){\n                  parsed.sessionData={};\n                }\n                parsed.sessionData[dataId]=sessionAttrs;\n              }\n              break;\n            }\n          case 'SESSION-KEY':\n            {\n              // #EXT-X-SESSION-KEY\n              const sessionKey=parseKey(attributes, baseurl, parsed);\n              if(sessionKey.encrypted&&sessionKey.isSupported()){\n                if(parsed.sessionKeys===null){\n                  parsed.sessionKeys=[];\n                }\n                parsed.sessionKeys.push(sessionKey);\n              }else{\n                logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: \"${attributes}\"`);\n              }\n              break;\n            }\n          case 'DEFINE':\n            {\n              // #EXT-X-DEFINE\n              {\n                const variableAttributes=new AttrList(attributes);\n                substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']);\n                addVariableDefinition(parsed, variableAttributes, baseurl);\n              }\n              break;\n            }\n          case 'CONTENT-STEERING':\n            {\n              // #EXT-X-CONTENT-STEERING\n              const contentSteeringAttributes=new AttrList(attributes);\n              {\n                substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']);\n              }\n              parsed.contentSteering={\n                uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),\n                pathwayId: contentSteeringAttributes['PATHWAY-ID']||'.'\n              };\n              break;\n            }\n          case 'START':\n            {\n              // #EXT-X-START\n              parsed.startTimeOffset=parseStartTimeOffset(attributes);\n              break;\n            }\n        }\n      }\n    }\n    // Filter out levels with unknown codecs if it does not remove all levels\n    const stripUnknownCodecLevels=levelsWithKnownCodecs.length > 0&&levelsWithKnownCodecs.length < parsed.levels.length;\n    parsed.levels=stripUnknownCodecLevels ? levelsWithKnownCodecs:parsed.levels;\n    if(parsed.levels.length===0){\n      parsed.playlistParsingError=new Error('no levels found in manifest');\n    }\n    return parsed;\n  }\n  static parseMasterPlaylistMedia(string, baseurl, parsed){\n    let result;\n    const results={};\n    const levels=parsed.levels;\n    const groupsByType={\n      AUDIO: levels.map(level=> ({\n        id: level.attrs.AUDIO,\n        audioCodec: level.audioCodec\n      })),\n      SUBTITLES: levels.map(level=> ({\n        id: level.attrs.SUBTITLES,\n        textCodec: level.textCodec\n      })),\n      'CLOSED-CAPTIONS': []\n    };\n    let id=0;\n    MASTER_PLAYLIST_MEDIA_REGEX.lastIndex=0;\n    while ((result=MASTER_PLAYLIST_MEDIA_REGEX.exec(string))!==null){\n      const attrs=new AttrList(result[1]);\n      const type=attrs.TYPE;\n      if(type){\n        const groups=groupsByType[type];\n        const medias=results[type]||[];\n        results[type]=medias;\n        {\n          substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']);\n        }\n        const lang=attrs.LANGUAGE;\n        const assocLang=attrs['ASSOC-LANGUAGE'];\n        const channels=attrs.CHANNELS;\n        const characteristics=attrs.CHARACTERISTICS;\n        const instreamId=attrs['INSTREAM-ID'];\n        const media={\n          attrs,\n          bitrate: 0,\n          id: id++,\n          groupId: attrs['GROUP-ID']||'',\n          name: attrs.NAME||lang||'',\n          type,\n          default: attrs.bool('DEFAULT'),\n          autoselect: attrs.bool('AUTOSELECT'),\n          forced: attrs.bool('FORCED'),\n          lang,\n          url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl):''\n        };\n        if(assocLang){\n          media.assocLang=assocLang;\n        }\n        if(channels){\n          media.channels=channels;\n        }\n        if(characteristics){\n          media.characteristics=characteristics;\n        }\n        if(instreamId){\n          media.instreamId=instreamId;\n        }\n        if(groups!=null&&groups.length){\n          // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track\n          // If we don't find the track signalled, lets use the first audio groups codec we have\n          // Acting as a best guess\n          const groupCodec=M3U8Parser.findGroup(groups, media.groupId)||groups[0];\n          assignCodec(media, groupCodec, 'audioCodec');\n          assignCodec(media, groupCodec, 'textCodec');\n        }\n        medias.push(media);\n      }\n    }\n    return results;\n  }\n  static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList){\n    const level=new LevelDetails(baseurl);\n    const fragments=level.fragments;\n    // The most recent init segment seen (applies to all subsequent segments)\n    let currentInitSegment=null;\n    let currentSN=0;\n    let currentPart=0;\n    let totalduration=0;\n    let discontinuityCounter=0;\n    let prevFrag=null;\n    let frag=new Fragment(type, baseurl);\n    let result;\n    let i;\n    let levelkeys;\n    let firstPdtIndex=-1;\n    let createNextFrag=false;\n    let nextByteRange=null;\n    LEVEL_PLAYLIST_REGEX_FAST.lastIndex=0;\n    level.m3u8=string;\n    level.hasVariableRefs=hasVariableReferences(string) ;\n    while ((result=LEVEL_PLAYLIST_REGEX_FAST.exec(string))!==null){\n      if(createNextFrag){\n        createNextFrag=false;\n        frag=new Fragment(type, baseurl);\n        // setup the next fragment for part loading\n        frag.start=totalduration;\n        frag.sn=currentSN;\n        frag.cc=discontinuityCounter;\n        frag.level=id;\n        if(currentInitSegment){\n          frag.initSegment=currentInitSegment;\n          frag.rawProgramDateTime=currentInitSegment.rawProgramDateTime;\n          currentInitSegment.rawProgramDateTime=null;\n          if(nextByteRange){\n            frag.setByteRange(nextByteRange);\n            nextByteRange=null;\n          }\n        }\n      }\n      const duration=result[1];\n      if(duration){\n        // INF\n        frag.duration=parseFloat(duration);\n        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939\n        const title=(' ' + result[2]).slice(1);\n        frag.title=title||null;\n        frag.tagList.push(title ? ['INF', duration, title]:['INF', duration]);\n      }else if(result[3]){\n        // url\n        if(isFiniteNumber(frag.duration)){\n          frag.start=totalduration;\n          if(levelkeys){\n            setFragLevelKeys(frag, levelkeys, level);\n          }\n          frag.sn=currentSN;\n          frag.level=id;\n          frag.cc=discontinuityCounter;\n          fragments.push(frag);\n          // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939\n          const uri=(' ' + result[3]).slice(1);\n          frag.relurl=substituteVariables(level, uri) ;\n          assignProgramDateTime(frag, prevFrag);\n          prevFrag=frag;\n          totalduration +=frag.duration;\n          currentSN++;\n          currentPart=0;\n          createNextFrag=true;\n        }\n      }else if(result[4]){\n        // X-BYTERANGE\n        const data=(' ' + result[4]).slice(1);\n        if(prevFrag){\n          frag.setByteRange(data, prevFrag);\n        }else{\n          frag.setByteRange(data);\n        }\n      }else if(result[5]){\n        // PROGRAM-DATE-TIME\n        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939\n        frag.rawProgramDateTime=(' ' + result[5]).slice(1);\n        frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]);\n        if(firstPdtIndex===-1){\n          firstPdtIndex=fragments.length;\n        }\n      }else{\n        result=result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);\n        if(!result){\n          logger.warn('No matches on slow regex match for level playlist!');\n          continue;\n        }\n        for (i=1; i < result.length; i++){\n          if(typeof result[i]!=='undefined'){\n            break;\n          }\n        }\n\n        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939\n        const tag=(' ' + result[i]).slice(1);\n        const value1=(' ' + result[i + 1]).slice(1);\n        const value2=result[i + 2] ? (' ' + result[i + 2]).slice(1):'';\n        switch (tag){\n          case 'PLAYLIST-TYPE':\n            level.type=value1.toUpperCase();\n            break;\n          case 'MEDIA-SEQUENCE':\n            currentSN=level.startSN=parseInt(value1);\n            break;\n          case 'SKIP':\n            {\n              const skipAttrs=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']);\n              }\n              const skippedSegments=skipAttrs.decimalInteger('SKIPPED-SEGMENTS');\n              if(isFiniteNumber(skippedSegments)){\n                level.skippedSegments=skippedSegments;\n                // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`\n                for (let _i=skippedSegments; _i--;){\n                  fragments.unshift(null);\n                }\n                currentSN +=skippedSegments;\n              }\n              const recentlyRemovedDateranges=skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');\n              if(recentlyRemovedDateranges){\n                level.recentlyRemovedDateranges=recentlyRemovedDateranges.split('\\t');\n              }\n              break;\n            }\n          case 'TARGETDURATION':\n            level.targetduration=Math.max(parseInt(value1), 1);\n            break;\n          case 'VERSION':\n            level.version=parseInt(value1);\n            break;\n          case 'INDEPENDENT-SEGMENTS':\n          case 'EXTM3U':\n            break;\n          case 'ENDLIST':\n            level.live=false;\n            break;\n          case '#':\n            if(value1||value2){\n              frag.tagList.push(value2 ? [value1, value2]:[value1]);\n            }\n            break;\n          case 'DISCONTINUITY':\n            discontinuityCounter++;\n            frag.tagList.push(['DIS']);\n            break;\n          case 'GAP':\n            frag.gap=true;\n            frag.tagList.push([tag]);\n            break;\n          case 'BITRATE':\n            frag.tagList.push([tag, value1]);\n            break;\n          case 'DATERANGE':\n            {\n              const dateRangeAttr=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']);\n                substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs);\n              }\n              const dateRange=new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]);\n              if(dateRange.isValid||level.skippedSegments){\n                level.dateRanges[dateRange.id]=dateRange;\n              }else{\n                logger.warn(`Ignoring invalid DATERANGE tag: \"${value1}\"`);\n              }\n              // Add to fragment tag list for backwards compatibility (< v1.2.0)\n              frag.tagList.push(['EXT-X-DATERANGE', value1]);\n              break;\n            }\n          case 'DEFINE':\n            {\n              {\n                const variableAttributes=new AttrList(value1);\n                substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']);\n                if('IMPORT' in variableAttributes){\n                  importVariableDefinition(level, variableAttributes, multivariantVariableList);\n                }else{\n                  addVariableDefinition(level, variableAttributes, baseurl);\n                }\n              }\n              break;\n            }\n          case 'DISCONTINUITY-SEQUENCE':\n            discontinuityCounter=parseInt(value1);\n            break;\n          case 'KEY':\n            {\n              const levelKey=parseKey(value1, baseurl, level);\n              if(levelKey.isSupported()){\n                if(levelKey.method==='NONE'){\n                  levelkeys=undefined;\n                  break;\n                }\n                if(!levelkeys){\n                  levelkeys={};\n                }\n                if(levelkeys[levelKey.keyFormat]){\n                  levelkeys=_extends({}, levelkeys);\n                }\n                levelkeys[levelKey.keyFormat]=levelKey;\n              }else{\n                logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: \"${value1}\"`);\n              }\n              break;\n            }\n          case 'START':\n            level.startTimeOffset=parseStartTimeOffset(value1);\n            break;\n          case 'MAP':\n            {\n              const mapAttrs=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']);\n              }\n              if(frag.duration){\n                // Initial segment tag is after segment duration tag.\n                //   #EXTINF: 6.0\n                //   #EXT-X-MAP:URI=\"init.mp4\n                const init=new Fragment(type, baseurl);\n                setInitSegment(init, mapAttrs, id, levelkeys);\n                currentInitSegment=init;\n                frag.initSegment=currentInitSegment;\n                if(currentInitSegment.rawProgramDateTime&&!frag.rawProgramDateTime){\n                  frag.rawProgramDateTime=currentInitSegment.rawProgramDateTime;\n                }\n              }else{\n                // Initial segment tag is before segment duration tag\n                // Handle case where EXT-X-MAP is declared after EXT-X-BYTERANGE\n                const end=frag.byteRangeEndOffset;\n                if(end){\n                  const start=frag.byteRangeStartOffset;\n                  nextByteRange=`${end - start}@${start}`;\n                }else{\n                  nextByteRange=null;\n                }\n                setInitSegment(frag, mapAttrs, id, levelkeys);\n                currentInitSegment=frag;\n                createNextFrag=true;\n              }\n              break;\n            }\n          case 'SERVER-CONTROL':\n            {\n              const serverControlAttrs=new AttrList(value1);\n              level.canBlockReload=serverControlAttrs.bool('CAN-BLOCK-RELOAD');\n              level.canSkipUntil=serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0);\n              level.canSkipDateRanges=level.canSkipUntil > 0&&serverControlAttrs.bool('CAN-SKIP-DATERANGES');\n              level.partHoldBack=serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0);\n              level.holdBack=serverControlAttrs.optionalFloat('HOLD-BACK', 0);\n              break;\n            }\n          case 'PART-INF':\n            {\n              const partInfAttrs=new AttrList(value1);\n              level.partTarget=partInfAttrs.decimalFloatingPoint('PART-TARGET');\n              break;\n            }\n          case 'PART':\n            {\n              let partList=level.partList;\n              if(!partList){\n                partList=level.partList=[];\n              }\n              const previousFragmentPart=currentPart > 0 ? partList[partList.length - 1]:undefined;\n              const index=currentPart++;\n              const partAttrs=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']);\n              }\n              const part=new Part(partAttrs, frag, baseurl, index, previousFragmentPart);\n              partList.push(part);\n              frag.duration +=part.duration;\n              break;\n            }\n          case 'PRELOAD-HINT':\n            {\n              const preloadHintAttrs=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']);\n              }\n              level.preloadHint=preloadHintAttrs;\n              break;\n            }\n          case 'RENDITION-REPORT':\n            {\n              const renditionReportAttrs=new AttrList(value1);\n              {\n                substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']);\n              }\n              level.renditionReports=level.renditionReports||[];\n              level.renditionReports.push(renditionReportAttrs);\n              break;\n            }\n          default:\n            logger.warn(`line parsed but not handled: ${result}`);\n            break;\n        }\n      }\n    }\n    if(prevFrag&&!prevFrag.relurl){\n      fragments.pop();\n      totalduration -=prevFrag.duration;\n      if(level.partList){\n        level.fragmentHint=prevFrag;\n      }\n    }else if(level.partList){\n      assignProgramDateTime(frag, prevFrag);\n      frag.cc=discontinuityCounter;\n      level.fragmentHint=frag;\n      if(levelkeys){\n        setFragLevelKeys(frag, levelkeys, level);\n      }\n    }\n    const fragmentLength=fragments.length;\n    const firstFragment=fragments[0];\n    const lastFragment=fragments[fragmentLength - 1];\n    totalduration +=level.skippedSegments * level.targetduration;\n    if(totalduration > 0&&fragmentLength&&lastFragment){\n      level.averagetargetduration=totalduration / fragmentLength;\n      const lastSn=lastFragment.sn;\n      level.endSN=lastSn!=='initSegment' ? lastSn:0;\n      if(!level.live){\n        lastFragment.endList=true;\n      }\n      if(firstFragment){\n        level.startCC=firstFragment.cc;\n      }\n    }else{\n      level.endSN=0;\n      level.startCC=0;\n    }\n    if(level.fragmentHint){\n      totalduration +=level.fragmentHint.duration;\n    }\n    level.totalduration=totalduration;\n    level.endCC=discontinuityCounter;\n\n    \n    if(firstPdtIndex > 0){\n      backfillProgramDateTimes(fragments, firstPdtIndex);\n    }\n    return level;\n  }\n}\nfunction parseKey(keyTagAttributes, baseurl, parsed){\n  var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;\n  // https://tools.ietf.org/html/rfc8216#section-4.3.2.4\n  const keyAttrs=new AttrList(keyTagAttributes);\n  {\n    substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']);\n  }\n  const decryptmethod=(_keyAttrs$METHOD=keyAttrs.METHOD)!=null ? _keyAttrs$METHOD:'';\n  const decrypturi=keyAttrs.URI;\n  const decryptiv=keyAttrs.hexadecimalInteger('IV');\n  const decryptkeyformatversions=keyAttrs.KEYFORMATVERSIONS;\n  // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of \"identity\".\n  const decryptkeyformat=(_keyAttrs$KEYFORMAT=keyAttrs.KEYFORMAT)!=null ? _keyAttrs$KEYFORMAT:'identity';\n  if(decrypturi&&keyAttrs.IV&&!decryptiv){\n    logger.error(`Invalid IV: ${keyAttrs.IV}`);\n  }\n  // If decrypturi is a URI with a scheme, then baseurl will be ignored\n  // No uri is allowed when METHOD is NONE\n  const resolvedUri=decrypturi ? M3U8Parser.resolve(decrypturi, baseurl):'';\n  const keyFormatVersions=(decryptkeyformatversions ? decryptkeyformatversions:'1').split('/').map(Number).filter(Number.isFinite);\n  return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv);\n}\nfunction parseStartTimeOffset(startAttributes){\n  const startAttrs=new AttrList(startAttributes);\n  const startTimeOffset=startAttrs.decimalFloatingPoint('TIME-OFFSET');\n  if(isFiniteNumber(startTimeOffset)){\n    return startTimeOffset;\n  }\n  return null;\n}\nfunction setCodecs(codecsAttributeValue, level){\n  let codecs=(codecsAttributeValue||'').split(/[ ,]+/).filter(c=> c);\n  ['video', 'audio', 'text'].forEach(type=> {\n    const filtered=codecs.filter(codec=> isCodecType(codec, type));\n    if(filtered.length){\n      // Comma separated list of all codecs for type\n      level[`${type}Codec`]=filtered.join(',');\n      // Remove known codecs so that only unknownCodecs are left after iterating through each type\n      codecs=codecs.filter(codec=> filtered.indexOf(codec)===-1);\n    }\n  });\n  level.unknownCodecs=codecs;\n}\nfunction assignCodec(media, groupItem, codecProperty){\n  const codecValue=groupItem[codecProperty];\n  if(codecValue){\n    media[codecProperty]=codecValue;\n  }\n}\nfunction backfillProgramDateTimes(fragments, firstPdtIndex){\n  let fragPrev=fragments[firstPdtIndex];\n  for (let i=firstPdtIndex; i--;){\n    const frag=fragments[i];\n    // Exit on delta-playlist skipped segments\n    if(!frag){\n      return;\n    }\n    frag.programDateTime=fragPrev.programDateTime - frag.duration * 1000;\n    fragPrev=frag;\n  }\n}\nfunction assignProgramDateTime(frag, prevFrag){\n  if(frag.rawProgramDateTime){\n    frag.programDateTime=Date.parse(frag.rawProgramDateTime);\n  }else if(prevFrag!=null&&prevFrag.programDateTime){\n    frag.programDateTime=prevFrag.endProgramDateTime;\n  }\n  if(!isFiniteNumber(frag.programDateTime)){\n    frag.programDateTime=null;\n    frag.rawProgramDateTime=null;\n  }\n}\nfunction setInitSegment(frag, mapAttrs, id, levelkeys){\n  frag.relurl=mapAttrs.URI;\n  if(mapAttrs.BYTERANGE){\n    frag.setByteRange(mapAttrs.BYTERANGE);\n  }\n  frag.level=id;\n  frag.sn='initSegment';\n  if(levelkeys){\n    frag.levelkeys=levelkeys;\n  }\n  frag.initSegment=null;\n}\nfunction setFragLevelKeys(frag, levelkeys, level){\n  frag.levelkeys=levelkeys;\n  const {\n    encryptedFragments\n  }=level;\n  if((!encryptedFragments.length||encryptedFragments[encryptedFragments.length - 1].levelkeys!==levelkeys)&&Object.keys(levelkeys).some(format=> levelkeys[format].isCommonEncryption)){\n    encryptedFragments.push(frag);\n  }\n}\n\nvar PlaylistContextType={\n  MANIFEST: \"manifest\",\n  LEVEL: \"level\",\n  AUDIO_TRACK: \"audioTrack\",\n  SUBTITLE_TRACK: \"subtitleTrack\"\n};\nvar PlaylistLevelType={\n  MAIN: \"main\",\n  AUDIO: \"audio\",\n  SUBTITLE: \"subtitle\"\n};\n\nfunction mapContextToLevelType(context){\n  const {\n    type\n  }=context;\n  switch (type){\n    case PlaylistContextType.AUDIO_TRACK:\n      return PlaylistLevelType.AUDIO;\n    case PlaylistContextType.SUBTITLE_TRACK:\n      return PlaylistLevelType.SUBTITLE;\n    default:\n      return PlaylistLevelType.MAIN;\n  }\n}\nfunction getResponseUrl(response, context){\n  let url=response.url;\n  // responseURL not supported on some browsers (it is used to detect URL redirection)\n  // data-uri mode also not supported (but no need to detect redirection)\n  if(url===undefined||url.indexOf('data:')===0){\n    // fallback to initial URL\n    url=context.url;\n  }\n  return url;\n}\nclass PlaylistLoader {\n  constructor(hls){\n    this.hls=void 0;\n    this.loaders=Object.create(null);\n    this.variableList=null;\n    this.hls=hls;\n    this.registerListeners();\n  }\n  startLoad(startPosition){}\n  stopLoad(){\n    this.destroyInternalLoaders();\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);\n    hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);\n  }\n  unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);\n    hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);\n  }\n\n  \n  createInternalLoader(context){\n    const config=this.hls.config;\n    const PLoader=config.pLoader;\n    const Loader=config.loader;\n    const InternalLoader=PLoader||Loader;\n    const loader=new InternalLoader(config);\n    this.loaders[context.type]=loader;\n    return loader;\n  }\n  getInternalLoader(context){\n    return this.loaders[context.type];\n  }\n  resetInternalLoader(contextType){\n    if(this.loaders[contextType]){\n      delete this.loaders[contextType];\n    }\n  }\n\n  \n  destroyInternalLoaders(){\n    for (const contextType in this.loaders){\n      const loader=this.loaders[contextType];\n      if(loader){\n        loader.destroy();\n      }\n      this.resetInternalLoader(contextType);\n    }\n  }\n  destroy(){\n    this.variableList=null;\n    this.unregisterListeners();\n    this.destroyInternalLoaders();\n  }\n  onManifestLoading(event, data){\n    const {\n      url\n    }=data;\n    this.variableList=null;\n    this.load({\n      id: null,\n      level: 0,\n      responseType: 'text',\n      type: PlaylistContextType.MANIFEST,\n      url,\n      deliveryDirectives: null\n    });\n  }\n  onLevelLoading(event, data){\n    const {\n      id,\n      level,\n      pathwayId,\n      url,\n      deliveryDirectives\n    }=data;\n    this.load({\n      id,\n      level,\n      pathwayId,\n      responseType: 'text',\n      type: PlaylistContextType.LEVEL,\n      url,\n      deliveryDirectives\n    });\n  }\n  onAudioTrackLoading(event, data){\n    const {\n      id,\n      groupId,\n      url,\n      deliveryDirectives\n    }=data;\n    this.load({\n      id,\n      groupId,\n      level: null,\n      responseType: 'text',\n      type: PlaylistContextType.AUDIO_TRACK,\n      url,\n      deliveryDirectives\n    });\n  }\n  onSubtitleTrackLoading(event, data){\n    const {\n      id,\n      groupId,\n      url,\n      deliveryDirectives\n    }=data;\n    this.load({\n      id,\n      groupId,\n      level: null,\n      responseType: 'text',\n      type: PlaylistContextType.SUBTITLE_TRACK,\n      url,\n      deliveryDirectives\n    });\n  }\n  load(context){\n    var _context$deliveryDire;\n    const config=this.hls.config;\n\n    // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`);\n\n    // Check if a loader for this context already exists\n    let loader=this.getInternalLoader(context);\n    if(loader){\n      const loaderContext=loader.context;\n      if(loaderContext&&loaderContext.url===context.url&&loaderContext.level===context.level){\n        // same URL can't overlap\n        logger.trace('[playlist-loader]: playlist request ongoing');\n        return;\n      }\n      logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);\n      loader.abort();\n    }\n\n    // apply different configs for retries depending on\n    // context (manifest, level, audio/subs playlist)\n    let loadPolicy;\n    if(context.type===PlaylistContextType.MANIFEST){\n      loadPolicy=config.manifestLoadPolicy.default;\n    }else{\n      loadPolicy=_extends({}, config.playlistLoadPolicy.default, {\n        timeoutRetry: null,\n        errorRetry: null\n      });\n    }\n    loader=this.createInternalLoader(context);\n\n    // Override level/track timeout for LL-HLS requests\n    // (the default of 10000ms is counter productive to blocking playlist reload requests)\n    if(isFiniteNumber((_context$deliveryDire=context.deliveryDirectives)==null ? void 0:_context$deliveryDire.part)){\n      let levelDetails;\n      if(context.type===PlaylistContextType.LEVEL&&context.level!==null){\n        levelDetails=this.hls.levels[context.level].details;\n      }else if(context.type===PlaylistContextType.AUDIO_TRACK&&context.id!==null){\n        levelDetails=this.hls.audioTracks[context.id].details;\n      }else if(context.type===PlaylistContextType.SUBTITLE_TRACK&&context.id!==null){\n        levelDetails=this.hls.subtitleTracks[context.id].details;\n      }\n      if(levelDetails){\n        const partTarget=levelDetails.partTarget;\n        const targetDuration=levelDetails.targetduration;\n        if(partTarget&&targetDuration){\n          const maxLowLatencyPlaylistRefresh=Math.max(partTarget * 3, targetDuration * 0.8) * 1000;\n          loadPolicy=_extends({}, loadPolicy, {\n            maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs),\n            maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs)\n          });\n        }\n      }\n    }\n    const legacyRetryCompatibility=loadPolicy.errorRetry||loadPolicy.timeoutRetry||{};\n    const loaderConfig={\n      loadPolicy,\n      timeout: loadPolicy.maxLoadTimeMs,\n      maxRetry: legacyRetryCompatibility.maxNumRetry||0,\n      retryDelay: legacyRetryCompatibility.retryDelayMs||0,\n      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs||0\n    };\n    const loaderCallbacks={\n      onSuccess: (response, stats, context, networkDetails)=> {\n        const loader=this.getInternalLoader(context);\n        this.resetInternalLoader(context.type);\n        const string=response.data;\n\n        // Validate if it is an M3U8 at all\n        if(string.indexOf('#EXTM3U')!==0){\n          this.handleManifestParsingError(response, context, new Error('no EXTM3U delimiter'), networkDetails||null, stats);\n          return;\n        }\n        stats.parsing.start=performance.now();\n        if(M3U8Parser.isMediaPlaylist(string)){\n          this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails||null, loader);\n        }else{\n          this.handleMasterPlaylist(response, stats, context, networkDetails);\n        }\n      },\n      onError: (response, context, networkDetails, stats)=> {\n        this.handleNetworkError(context, networkDetails, false, response, stats);\n      },\n      onTimeout: (stats, context, networkDetails)=> {\n        this.handleNetworkError(context, networkDetails, true, undefined, stats);\n      }\n    };\n\n    // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`);\n\n    loader.load(context, loaderConfig, loaderCallbacks);\n  }\n  handleMasterPlaylist(response, stats, context, networkDetails){\n    const hls=this.hls;\n    const string=response.data;\n    const url=getResponseUrl(response, context);\n    const parsedResult=M3U8Parser.parseMasterPlaylist(string, url);\n    if(parsedResult.playlistParsingError){\n      this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats);\n      return;\n    }\n    const {\n      contentSteering,\n      levels,\n      sessionData,\n      sessionKeys,\n      startTimeOffset,\n      variableList\n    }=parsedResult;\n    this.variableList=variableList;\n    const {\n      AUDIO: audioTracks=[],\n      SUBTITLES: subtitles,\n      'CLOSED-CAPTIONS': captions\n    }=M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult);\n    if(audioTracks.length){\n      // check if we have found an audio track embedded in main playlist (audio track without URI attribute)\n      const embeddedAudioFound=audioTracks.some(audioTrack=> !audioTrack.url);\n\n      // if no embedded audio track defined, but audio codec signaled in quality level,\n      // we need to signal this main audio track this could happen with playlists with\n      // alt audio rendition in which quality levels (main)\n      // contains both audio+video. but with mixed audio track not signaled\n      if(!embeddedAudioFound&&levels[0].audioCodec&&!levels[0].attrs.AUDIO){\n        logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');\n        audioTracks.unshift({\n          type: 'main',\n          name: 'main',\n          groupId: 'main',\n          default: false,\n          autoselect: false,\n          forced: false,\n          id: -1,\n          attrs: new AttrList({}),\n          bitrate: 0,\n          url: ''\n        });\n      }\n    }\n    hls.trigger(Events.MANIFEST_LOADED, {\n      levels,\n      audioTracks,\n      subtitles,\n      captions,\n      contentSteering,\n      url,\n      stats,\n      networkDetails,\n      sessionData,\n      sessionKeys,\n      startTimeOffset,\n      variableList\n    });\n  }\n  handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader){\n    const hls=this.hls;\n    const {\n      id,\n      level,\n      type\n    }=context;\n    const url=getResponseUrl(response, context);\n    const levelUrlId=0;\n    const levelId=isFiniteNumber(level) ? level:isFiniteNumber(id) ? id:0;\n    const levelType=mapContextToLevelType(context);\n    const levelDetails=M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList);\n\n    // We have done our first request (Manifest-type) and receive\n    // not a master playlist but a chunk-list (track/level)\n    // We fire the manifest-loaded event anyway with the parsed level-details\n    // by creating a single-level structure for it.\n    if(type===PlaylistContextType.MANIFEST){\n      const singleLevel={\n        attrs: new AttrList({}),\n        bitrate: 0,\n        details: levelDetails,\n        name: '',\n        url\n      };\n      hls.trigger(Events.MANIFEST_LOADED, {\n        levels: [singleLevel],\n        audioTracks: [],\n        url,\n        stats,\n        networkDetails,\n        sessionData: null,\n        sessionKeys: null,\n        contentSteering: null,\n        startTimeOffset: null,\n        variableList: null\n      });\n    }\n\n    // save parsing time\n    stats.parsing.end=performance.now();\n\n    // extend the context with the new levelDetails property\n    context.levelDetails=levelDetails;\n    this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader);\n  }\n  handleManifestParsingError(response, context, error, networkDetails, stats){\n    this.hls.trigger(Events.ERROR, {\n      type: ErrorTypes.NETWORK_ERROR,\n      details: ErrorDetails.MANIFEST_PARSING_ERROR,\n      fatal: context.type===PlaylistContextType.MANIFEST,\n      url: response.url,\n      err: error,\n      error,\n      reason: error.message,\n      response,\n      context,\n      networkDetails,\n      stats\n    });\n  }\n  handleNetworkError(context, networkDetails, timeout=false, response, stats){\n    let message=`A network ${timeout ? 'timeout':'error' + (response ? ' (status ' + response.code + ')':'')} occurred while loading ${context.type}`;\n    if(context.type===PlaylistContextType.LEVEL){\n      message +=`: ${context.level} id: ${context.id}`;\n    }else if(context.type===PlaylistContextType.AUDIO_TRACK||context.type===PlaylistContextType.SUBTITLE_TRACK){\n      message +=` id: ${context.id} group-id: \"${context.groupId}\"`;\n    }\n    const error=new Error(message);\n    logger.warn(`[playlist-loader]: ${message}`);\n    let details=ErrorDetails.UNKNOWN;\n    let fatal=false;\n    const loader=this.getInternalLoader(context);\n    switch (context.type){\n      case PlaylistContextType.MANIFEST:\n        details=timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT:ErrorDetails.MANIFEST_LOAD_ERROR;\n        fatal=true;\n        break;\n      case PlaylistContextType.LEVEL:\n        details=timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT:ErrorDetails.LEVEL_LOAD_ERROR;\n        fatal=false;\n        break;\n      case PlaylistContextType.AUDIO_TRACK:\n        details=timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:ErrorDetails.AUDIO_TRACK_LOAD_ERROR;\n        fatal=false;\n        break;\n      case PlaylistContextType.SUBTITLE_TRACK:\n        details=timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:ErrorDetails.SUBTITLE_LOAD_ERROR;\n        fatal=false;\n        break;\n    }\n    if(loader){\n      this.resetInternalLoader(context.type);\n    }\n    const errorData={\n      type: ErrorTypes.NETWORK_ERROR,\n      details,\n      fatal,\n      url: context.url,\n      loader,\n      context,\n      error,\n      networkDetails,\n      stats\n    };\n    if(response){\n      const url=(networkDetails==null ? void 0:networkDetails.url)||context.url;\n      errorData.response=_objectSpread2({\n        url,\n        data: undefined\n      }, response);\n    }\n    this.hls.trigger(Events.ERROR, errorData);\n  }\n  handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader){\n    const hls=this.hls;\n    const {\n      type,\n      level,\n      id,\n      groupId,\n      deliveryDirectives\n    }=context;\n    const url=getResponseUrl(response, context);\n    const parent=mapContextToLevelType(context);\n    const levelIndex=typeof context.level==='number'&&parent===PlaylistLevelType.MAIN ? level:undefined;\n    if(!levelDetails.fragments.length){\n      const _error=new Error('No Segments found in Playlist');\n      hls.trigger(Events.ERROR, {\n        type: ErrorTypes.NETWORK_ERROR,\n        details: ErrorDetails.LEVEL_EMPTY_ERROR,\n        fatal: false,\n        url,\n        error: _error,\n        reason: _error.message,\n        response,\n        context,\n        level: levelIndex,\n        parent,\n        networkDetails,\n        stats\n      });\n      return;\n    }\n    if(!levelDetails.targetduration){\n      levelDetails.playlistParsingError=new Error('Missing Target Duration');\n    }\n    const error=levelDetails.playlistParsingError;\n    if(error){\n      hls.trigger(Events.ERROR, {\n        type: ErrorTypes.NETWORK_ERROR,\n        details: ErrorDetails.LEVEL_PARSING_ERROR,\n        fatal: false,\n        url,\n        error,\n        reason: error.message,\n        response,\n        context,\n        level: levelIndex,\n        parent,\n        networkDetails,\n        stats\n      });\n      return;\n    }\n    if(levelDetails.live&&loader){\n      if(loader.getCacheAge){\n        levelDetails.ageHeader=loader.getCacheAge()||0;\n      }\n      if(!loader.getCacheAge||isNaN(levelDetails.ageHeader)){\n        levelDetails.ageHeader=0;\n      }\n    }\n    switch (type){\n      case PlaylistContextType.MANIFEST:\n      case PlaylistContextType.LEVEL:\n        hls.trigger(Events.LEVEL_LOADED, {\n          details: levelDetails,\n          level: levelIndex||0,\n          id: id||0,\n          stats,\n          networkDetails,\n          deliveryDirectives\n        });\n        break;\n      case PlaylistContextType.AUDIO_TRACK:\n        hls.trigger(Events.AUDIO_TRACK_LOADED, {\n          details: levelDetails,\n          id: id||0,\n          groupId: groupId||'',\n          stats,\n          networkDetails,\n          deliveryDirectives\n        });\n        break;\n      case PlaylistContextType.SUBTITLE_TRACK:\n        hls.trigger(Events.SUBTITLE_TRACK_LOADED, {\n          details: levelDetails,\n          id: id||0,\n          groupId: groupId||'',\n          stats,\n          networkDetails,\n          deliveryDirectives\n        });\n        break;\n    }\n  }\n}\n\nfunction sendAddTrackEvent(track, videoEl){\n  let event;\n  try {\n    event=new Event('addtrack');\n  } catch (err){\n    // for IE11\n    event=document.createEvent('Event');\n    event.initEvent('addtrack', false, false);\n  }\n  event.track=track;\n  videoEl.dispatchEvent(event);\n}\nfunction addCueToTrack(track, cue){\n  // Sometimes there are cue overlaps on segmented vtts so the same\n  // cue can appear more than once in different vtt files.\n  // This avoid showing duplicated cues with same timecode and text.\n  const mode=track.mode;\n  if(mode==='disabled'){\n    track.mode='hidden';\n  }\n  if(track.cues&&!track.cues.getCueById(cue.id)){\n    try {\n      track.addCue(cue);\n      if(!track.cues.getCueById(cue.id)){\n        throw new Error(`addCue is failed for: ${cue}`);\n      }\n    } catch (err){\n      logger.debug(`[texttrack-utils]: ${err}`);\n      try {\n        const textTrackCue=new self.TextTrackCue(cue.startTime, cue.endTime, cue.text);\n        textTrackCue.id=cue.id;\n        track.addCue(textTrackCue);\n      } catch (err2){\n        logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`);\n      }\n    }\n  }\n  if(mode==='disabled'){\n    track.mode=mode;\n  }\n}\nfunction clearCurrentCues(track){\n  // When track.mode is disabled, track.cues will be null.\n  // To guarantee the removal of cues, we need to temporarily\n  // change the mode to hidden\n  const mode=track.mode;\n  if(mode==='disabled'){\n    track.mode='hidden';\n  }\n  if(track.cues){\n    for (let i=track.cues.length; i--;){\n      track.removeCue(track.cues[i]);\n    }\n  }\n  if(mode==='disabled'){\n    track.mode=mode;\n  }\n}\nfunction removeCuesInRange(track, start, end, predicate){\n  const mode=track.mode;\n  if(mode==='disabled'){\n    track.mode='hidden';\n  }\n  if(track.cues&&track.cues.length > 0){\n    const cues=getCuesInRange(track.cues, start, end);\n    for (let i=0; i < cues.length; i++){\n      if(!predicate||predicate(cues[i])){\n        track.removeCue(cues[i]);\n      }\n    }\n  }\n  if(mode==='disabled'){\n    track.mode=mode;\n  }\n}\n\n// Find first cue starting after given time.\n// Modified version of binary search O(log(n)).\nfunction getFirstCueIndexAfterTime(cues, time){\n  // If first cue starts after time, start there\n  if(time < cues[0].startTime){\n    return 0;\n  }\n  // If the last cue ends before time there is no overlap\n  const len=cues.length - 1;\n  if(time > cues[len].endTime){\n    return -1;\n  }\n  let left=0;\n  let right=len;\n  while (left <=right){\n    const mid=Math.floor((right + left) / 2);\n    if(time < cues[mid].startTime){\n      right=mid - 1;\n    }else if(time > cues[mid].startTime&&left < len){\n      left=mid + 1;\n    }else{\n      // If it's not lower or higher, it must be equal.\n      return mid;\n    }\n  }\n  // At this point, left and right have swapped.\n  // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.\n  return cues[left].startTime - time < time - cues[right].startTime ? left:right;\n}\nfunction getCuesInRange(cues, start, end){\n  const cuesFound=[];\n  const firstCueInRange=getFirstCueIndexAfterTime(cues, start);\n  if(firstCueInRange > -1){\n    for (let i=firstCueInRange, len=cues.length; i < len; i++){\n      const cue=cues[i];\n      if(cue.startTime >=start&&cue.endTime <=end){\n        cuesFound.push(cue);\n      }else if(cue.startTime > end){\n        return cuesFound;\n      }\n    }\n  }\n  return cuesFound;\n}\nfunction filterSubtitleTracks(textTrackList){\n  const tracks=[];\n  for (let i=0; i < textTrackList.length; i++){\n    const track=textTrackList[i];\n    // Edge adds a track without a label; we don't want to use it\n    if((track.kind==='subtitles'||track.kind==='captions')&&track.label){\n      tracks.push(textTrackList[i]);\n    }\n  }\n  return tracks;\n}\n\nvar MetadataSchema={\n  audioId3: \"org.id3\",\n  dateRange: \"com.apple.quicktime.HLS\",\n  emsg: \"https://aomedia.org/emsg/ID3\"\n};\n\nconst MIN_CUE_DURATION=0.25;\nfunction getCueClass(){\n  if(typeof self==='undefined') return undefined;\n  return self.VTTCue||self.TextTrackCue;\n}\nfunction createCueWithDataFields(Cue, startTime, endTime, data, type){\n  let cue=new Cue(startTime, endTime, '');\n  try {\n    cue.value=data;\n    if(type){\n      cue.type=type;\n    }\n  } catch (e){\n    cue=new Cue(startTime, endTime, JSON.stringify(type ? _objectSpread2({\n      type\n    }, data):data));\n  }\n  return cue;\n}\n\n// VTTCue latest draft allows an infinite duration, fallback\n// to MAX_VALUE if necessary\nconst MAX_CUE_ENDTIME=(()=> {\n  const Cue=getCueClass();\n  try {\n    Cue&&new Cue(0, Number.POSITIVE_INFINITY, '');\n  } catch (e){\n    return Number.MAX_VALUE;\n  }\n  return Number.POSITIVE_INFINITY;\n})();\nfunction dateRangeDateToTimelineSeconds(date, offset){\n  return date.getTime() / 1000 - offset;\n}\nfunction hexToArrayBuffer(str){\n  return Uint8Array.from(str.replace(/^0x/, '').replace(/([\\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;\n}\nclass ID3TrackController {\n  constructor(hls){\n    this.hls=void 0;\n    this.id3Track=null;\n    this.media=null;\n    this.dateRangeCuesAppended={};\n    this.hls=hls;\n    this._registerListeners();\n  }\n  destroy(){\n    this._unregisterListeners();\n    this.id3Track=null;\n    this.media=null;\n    this.dateRangeCuesAppended={};\n    // @ts-ignore\n    this.hls=null;\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);\n    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);\n    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n  }\n\n  // Add ID3 metatadata text track.\n  onMediaAttached(event, data){\n    this.media=data.media;\n  }\n  onMediaDetaching(){\n    if(!this.id3Track){\n      return;\n    }\n    clearCurrentCues(this.id3Track);\n    this.id3Track=null;\n    this.media=null;\n    this.dateRangeCuesAppended={};\n  }\n  onManifestLoading(){\n    this.dateRangeCuesAppended={};\n  }\n  createTrack(media){\n    const track=this.getID3Track(media.textTracks);\n    track.mode='hidden';\n    return track;\n  }\n  getID3Track(textTracks){\n    if(!this.media){\n      return;\n    }\n    for (let i=0; i < textTracks.length; i++){\n      const textTrack=textTracks[i];\n      if(textTrack.kind==='metadata'&&textTrack.label==='id3'){\n        // send 'addtrack' when reusing the textTrack for metadata,\n        // same as what we do for captions\n        sendAddTrackEvent(textTrack, this.media);\n        return textTrack;\n      }\n    }\n    return this.media.addTextTrack('metadata', 'id3');\n  }\n  onFragParsingMetadata(event, data){\n    if(!this.media){\n      return;\n    }\n    const {\n      hls: {\n        config: {\n          enableEmsgMetadataCues,\n          enableID3MetadataCues\n        }\n      }\n    }=this;\n    if(!enableEmsgMetadataCues&&!enableID3MetadataCues){\n      return;\n    }\n    const {\n      samples\n    }=data;\n\n    // create track dynamically\n    if(!this.id3Track){\n      this.id3Track=this.createTrack(this.media);\n    }\n    const Cue=getCueClass();\n    if(!Cue){\n      return;\n    }\n    for (let i=0; i < samples.length; i++){\n      const type=samples[i].type;\n      if(type===MetadataSchema.emsg&&!enableEmsgMetadataCues||!enableID3MetadataCues){\n        continue;\n      }\n      const frames=getID3Frames(samples[i].data);\n      if(frames){\n        const startTime=samples[i].pts;\n        let endTime=startTime + samples[i].duration;\n        if(endTime > MAX_CUE_ENDTIME){\n          endTime=MAX_CUE_ENDTIME;\n        }\n        const timeDiff=endTime - startTime;\n        if(timeDiff <=0){\n          endTime=startTime + MIN_CUE_DURATION;\n        }\n        for (let j=0; j < frames.length; j++){\n          const frame=frames[j];\n          // Safari doesn't put the timestamp frame in the TextTrack\n          if(!isTimeStampFrame(frame)){\n            // add a bounds to any unbounded cues\n            this.updateId3CueEnds(startTime, type);\n            const cue=createCueWithDataFields(Cue, startTime, endTime, frame, type);\n            if(cue){\n              this.id3Track.addCue(cue);\n            }\n          }\n        }\n      }\n    }\n  }\n  updateId3CueEnds(startTime, type){\n    var _this$id3Track;\n    const cues=(_this$id3Track=this.id3Track)==null ? void 0:_this$id3Track.cues;\n    if(cues){\n      for (let i=cues.length; i--;){\n        const cue=cues[i];\n        if(cue.type===type&&cue.startTime < startTime&&cue.endTime===MAX_CUE_ENDTIME){\n          cue.endTime=startTime;\n        }\n      }\n    }\n  }\n  onBufferFlushing(event, {\n    startOffset,\n    endOffset,\n    type\n  }){\n    const {\n      id3Track,\n      hls\n    }=this;\n    if(!hls){\n      return;\n    }\n    const {\n      config: {\n        enableEmsgMetadataCues,\n        enableID3MetadataCues\n      }\n    }=hls;\n    if(id3Track&&(enableEmsgMetadataCues||enableID3MetadataCues)){\n      let predicate;\n      if(type==='audio'){\n        predicate=cue=> cue.type===MetadataSchema.audioId3&&enableID3MetadataCues;\n      }else if(type==='video'){\n        predicate=cue=> cue.type===MetadataSchema.emsg&&enableEmsgMetadataCues;\n      }else{\n        predicate=cue=> cue.type===MetadataSchema.audioId3&&enableID3MetadataCues||cue.type===MetadataSchema.emsg&&enableEmsgMetadataCues;\n      }\n      removeCuesInRange(id3Track, startOffset, endOffset, predicate);\n    }\n  }\n  onLevelUpdated(event, {\n    details\n  }){\n    if(!this.media||!details.hasProgramDateTime||!this.hls.config.enableDateRangeMetadataCues){\n      return;\n    }\n    const {\n      dateRangeCuesAppended,\n      id3Track\n    }=this;\n    const {\n      dateRanges\n    }=details;\n    const ids=Object.keys(dateRanges);\n    // Remove cues from track not found in details.dateRanges\n    if(id3Track){\n      const idsToRemove=Object.keys(dateRangeCuesAppended).filter(id=> !ids.includes(id));\n      for (let i=idsToRemove.length; i--;){\n        const id=idsToRemove[i];\n        Object.keys(dateRangeCuesAppended[id].cues).forEach(key=> {\n          id3Track.removeCue(dateRangeCuesAppended[id].cues[key]);\n        });\n        delete dateRangeCuesAppended[id];\n      }\n    }\n    // Exit if the playlist does not have Date Ranges or does not have Program Date Time\n    const lastFragment=details.fragments[details.fragments.length - 1];\n    if(ids.length===0||!isFiniteNumber(lastFragment==null ? void 0:lastFragment.programDateTime)){\n      return;\n    }\n    if(!this.id3Track){\n      this.id3Track=this.createTrack(this.media);\n    }\n    const dateTimeOffset=lastFragment.programDateTime / 1000 - lastFragment.start;\n    const Cue=getCueClass();\n    for (let i=0; i < ids.length; i++){\n      const id=ids[i];\n      const dateRange=dateRanges[id];\n      const startTime=dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset);\n\n      // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)\n      const appendedDateRangeCues=dateRangeCuesAppended[id];\n      const cues=(appendedDateRangeCues==null ? void 0:appendedDateRangeCues.cues)||{};\n      let durationKnown=(appendedDateRangeCues==null ? void 0:appendedDateRangeCues.durationKnown)||false;\n      let endTime=MAX_CUE_ENDTIME;\n      const endDate=dateRange.endDate;\n      if(endDate){\n        endTime=dateRangeDateToTimelineSeconds(endDate, dateTimeOffset);\n        durationKnown=true;\n      }else if(dateRange.endOnNext&&!durationKnown){\n        const nextDateRangeWithSameClass=ids.reduce((candidateDateRange, id)=> {\n          if(id!==dateRange.id){\n            const otherDateRange=dateRanges[id];\n            if(otherDateRange.class===dateRange.class&&otherDateRange.startDate > dateRange.startDate&&(!candidateDateRange||dateRange.startDate < candidateDateRange.startDate)){\n              return otherDateRange;\n            }\n          }\n          return candidateDateRange;\n        }, null);\n        if(nextDateRangeWithSameClass){\n          endTime=dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset);\n          durationKnown=true;\n        }\n      }\n\n      // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute)\n      // This is to emulate Safari HLS playback handling of DateRange tags\n      const attributes=Object.keys(dateRange.attr);\n      for (let j=0; j < attributes.length; j++){\n        const key=attributes[j];\n        if(!isDateRangeCueAttribute(key)){\n          continue;\n        }\n        const cue=cues[key];\n        if(cue){\n          if(durationKnown&&!appendedDateRangeCues.durationKnown){\n            cue.endTime=endTime;\n          }\n        }else if(Cue){\n          let data=dateRange.attr[key];\n          if(isSCTE35Attribute(key)){\n            data=hexToArrayBuffer(data);\n          }\n          const _cue=createCueWithDataFields(Cue, startTime, endTime, {\n            key,\n            data\n          }, MetadataSchema.dateRange);\n          if(_cue){\n            _cue.id=id;\n            this.id3Track.addCue(_cue);\n            cues[key]=_cue;\n          }\n        }\n      }\n\n      // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes\n      dateRangeCuesAppended[id]={\n        cues,\n        dateRange,\n        durationKnown\n      };\n    }\n  }\n}\n\nclass LatencyController {\n  constructor(hls){\n    this.hls=void 0;\n    this.config=void 0;\n    this.media=null;\n    this.levelDetails=null;\n    this.currentTime=0;\n    this.stallCount=0;\n    this._latency=null;\n    this.timeupdateHandler=()=> this.timeupdate();\n    this.hls=hls;\n    this.config=hls.config;\n    this.registerListeners();\n  }\n  get latency(){\n    return this._latency||0;\n  }\n  get maxLatency(){\n    const {\n      config,\n      levelDetails\n    }=this;\n    if(config.liveMaxLatencyDuration!==undefined){\n      return config.liveMaxLatencyDuration;\n    }\n    return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration:0;\n  }\n  get targetLatency(){\n    const {\n      levelDetails\n    }=this;\n    if(levelDetails===null){\n      return null;\n    }\n    const {\n      holdBack,\n      partHoldBack,\n      targetduration\n    }=levelDetails;\n    const {\n      liveSyncDuration,\n      liveSyncDurationCount,\n      lowLatencyMode\n    }=this.config;\n    const userConfig=this.hls.userConfig;\n    let targetLatency=lowLatencyMode ? partHoldBack||holdBack:holdBack;\n    if(userConfig.liveSyncDuration||userConfig.liveSyncDurationCount||targetLatency===0){\n      targetLatency=liveSyncDuration!==undefined ? liveSyncDuration:liveSyncDurationCount * targetduration;\n    }\n    const maxLiveSyncOnStallIncrease=targetduration;\n    const liveSyncOnStallIncrease=1.0;\n    return targetLatency + Math.min(this.stallCount * liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease);\n  }\n  get liveSyncPosition(){\n    const liveEdge=this.estimateLiveEdge();\n    const targetLatency=this.targetLatency;\n    const levelDetails=this.levelDetails;\n    if(liveEdge===null||targetLatency===null||levelDetails===null){\n      return null;\n    }\n    const edge=levelDetails.edge;\n    const syncPosition=liveEdge - targetLatency - this.edgeStalled;\n    const min=edge - levelDetails.totalduration;\n    const max=edge - (this.config.lowLatencyMode&&levelDetails.partTarget||levelDetails.targetduration);\n    return Math.min(Math.max(min, syncPosition), max);\n  }\n  get drift(){\n    const {\n      levelDetails\n    }=this;\n    if(levelDetails===null){\n      return 1;\n    }\n    return levelDetails.drift;\n  }\n  get edgeStalled(){\n    const {\n      levelDetails\n    }=this;\n    if(levelDetails===null){\n      return 0;\n    }\n    const maxLevelUpdateAge=(this.config.lowLatencyMode&&levelDetails.partTarget||levelDetails.targetduration) * 3;\n    return Math.max(levelDetails.age - maxLevelUpdateAge, 0);\n  }\n  get forwardBufferLength(){\n    const {\n      media,\n      levelDetails\n    }=this;\n    if(!media||!levelDetails){\n      return 0;\n    }\n    const bufferedRanges=media.buffered.length;\n    return (bufferedRanges ? media.buffered.end(bufferedRanges - 1):levelDetails.edge) - this.currentTime;\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.onMediaDetaching();\n    this.levelDetails=null;\n    // @ts-ignore\n    this.hls=this.timeupdateHandler=null;\n  }\n  registerListeners(){\n    this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    this.hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n    this.hls.on(Events.ERROR, this.onError, this);\n  }\n  unregisterListeners(){\n    this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    this.hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n    this.hls.off(Events.ERROR, this.onError, this);\n  }\n  onMediaAttached(event, data){\n    this.media=data.media;\n    this.media.addEventListener('timeupdate', this.timeupdateHandler);\n  }\n  onMediaDetaching(){\n    if(this.media){\n      this.media.removeEventListener('timeupdate', this.timeupdateHandler);\n      this.media=null;\n    }\n  }\n  onManifestLoading(){\n    this.levelDetails=null;\n    this._latency=null;\n    this.stallCount=0;\n  }\n  onLevelUpdated(event, {\n    details\n  }){\n    this.levelDetails=details;\n    if(details.advanced){\n      this.timeupdate();\n    }\n    if(!details.live&&this.media){\n      this.media.removeEventListener('timeupdate', this.timeupdateHandler);\n    }\n  }\n  onError(event, data){\n    var _this$levelDetails;\n    if(data.details!==ErrorDetails.BUFFER_STALLED_ERROR){\n      return;\n    }\n    this.stallCount++;\n    if((_this$levelDetails=this.levelDetails)!=null&&_this$levelDetails.live){\n      logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');\n    }\n  }\n  timeupdate(){\n    const {\n      media,\n      levelDetails\n    }=this;\n    if(!media||!levelDetails){\n      return;\n    }\n    this.currentTime=media.currentTime;\n    const latency=this.computeLatency();\n    if(latency===null){\n      return;\n    }\n    this._latency=latency;\n\n    // Adapt playbackRate to meet target latency in low-latency mode\n    const {\n      lowLatencyMode,\n      maxLiveSyncPlaybackRate\n    }=this.config;\n    if(!lowLatencyMode||maxLiveSyncPlaybackRate===1||!levelDetails.live){\n      return;\n    }\n    const targetLatency=this.targetLatency;\n    if(targetLatency===null){\n      return;\n    }\n    const distanceFromTarget=latency - targetLatency;\n    // Only adjust playbackRate when within one target duration of targetLatency\n    // and more than one second from under-buffering.\n    // Playback further than one target duration from target can be considered DVR playback.\n    const liveMinLatencyDuration=Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);\n    const inLiveRange=distanceFromTarget < liveMinLatencyDuration;\n    if(inLiveRange&&distanceFromTarget > 0.05&&this.forwardBufferLength > 1){\n      const max=Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));\n      const rate=Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;\n      media.playbackRate=Math.min(max, Math.max(1, rate));\n    }else if(media.playbackRate!==1&&media.playbackRate!==0){\n      media.playbackRate=1;\n    }\n  }\n  estimateLiveEdge(){\n    const {\n      levelDetails\n    }=this;\n    if(levelDetails===null){\n      return null;\n    }\n    return levelDetails.edge + levelDetails.age;\n  }\n  computeLatency(){\n    const liveEdge=this.estimateLiveEdge();\n    if(liveEdge===null){\n      return null;\n    }\n    return liveEdge - this.currentTime;\n  }\n}\n\nconst HdcpLevels=['NONE', 'TYPE-0', 'TYPE-1', null];\nfunction isHdcpLevel(value){\n  return HdcpLevels.indexOf(value) > -1;\n}\nconst VideoRangeValues=['SDR', 'PQ', 'HLG'];\nfunction isVideoRange(value){\n  return !!value&&VideoRangeValues.indexOf(value) > -1;\n}\nvar HlsSkip={\n  No: \"\",\n  Yes: \"YES\",\n  v2: \"v2\"\n};\nfunction getSkipValue(details){\n  const {\n    canSkipUntil,\n    canSkipDateRanges,\n    age\n  }=details;\n  // A Client SHOULD NOT request a Playlist Delta Update unless it already\n  // has a version of the Playlist that is no older than one-half of the Skip Boundary.\n  // @see: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.7\n  const playlistRecentEnough=age < canSkipUntil / 2;\n  if(canSkipUntil&&playlistRecentEnough){\n    if(canSkipDateRanges){\n      return HlsSkip.v2;\n    }\n    return HlsSkip.Yes;\n  }\n  return HlsSkip.No;\n}\nclass HlsUrlParameters {\n  constructor(msn, part, skip){\n    this.msn=void 0;\n    this.part=void 0;\n    this.skip=void 0;\n    this.msn=msn;\n    this.part=part;\n    this.skip=skip;\n  }\n  addDirectives(uri){\n    const url=new self.URL(uri);\n    if(this.msn!==undefined){\n      url.searchParams.set('_HLS_msn', this.msn.toString());\n    }\n    if(this.part!==undefined){\n      url.searchParams.set('_HLS_part', this.part.toString());\n    }\n    if(this.skip){\n      url.searchParams.set('_HLS_skip', this.skip);\n    }\n    return url.href;\n  }\n}\nclass Level {\n  constructor(data){\n    this._attrs=void 0;\n    this.audioCodec=void 0;\n    this.bitrate=void 0;\n    this.codecSet=void 0;\n    this.url=void 0;\n    this.frameRate=void 0;\n    this.height=void 0;\n    this.id=void 0;\n    this.name=void 0;\n    this.videoCodec=void 0;\n    this.width=void 0;\n    this.details=void 0;\n    this.fragmentError=0;\n    this.loadError=0;\n    this.loaded=void 0;\n    this.realBitrate=0;\n    this.supportedPromise=void 0;\n    this.supportedResult=void 0;\n    this._avgBitrate=0;\n    this._audioGroups=void 0;\n    this._subtitleGroups=void 0;\n    // Deprecated (retained for backwards compatibility)\n    this._urlId=0;\n    this.url=[data.url];\n    this._attrs=[data.attrs];\n    this.bitrate=data.bitrate;\n    if(data.details){\n      this.details=data.details;\n    }\n    this.id=data.id||0;\n    this.name=data.name;\n    this.width=data.width||0;\n    this.height=data.height||0;\n    this.frameRate=data.attrs.optionalFloat('FRAME-RATE', 0);\n    this._avgBitrate=data.attrs.decimalInteger('AVERAGE-BANDWIDTH');\n    this.audioCodec=data.audioCodec;\n    this.videoCodec=data.videoCodec;\n    this.codecSet=[data.videoCodec, data.audioCodec].filter(c=> !!c).map(s=> s.substring(0, 4)).join(',');\n    this.addGroupId('audio', data.attrs.AUDIO);\n    this.addGroupId('text', data.attrs.SUBTITLES);\n  }\n  get maxBitrate(){\n    return Math.max(this.realBitrate, this.bitrate);\n  }\n  get averageBitrate(){\n    return this._avgBitrate||this.realBitrate||this.bitrate;\n  }\n  get attrs(){\n    return this._attrs[0];\n  }\n  get codecs(){\n    return this.attrs.CODECS||'';\n  }\n  get pathwayId(){\n    return this.attrs['PATHWAY-ID']||'.';\n  }\n  get videoRange(){\n    return this.attrs['VIDEO-RANGE']||'SDR';\n  }\n  get score(){\n    return this.attrs.optionalFloat('SCORE', 0);\n  }\n  get uri(){\n    return this.url[0]||'';\n  }\n  hasAudioGroup(groupId){\n    return hasGroup(this._audioGroups, groupId);\n  }\n  hasSubtitleGroup(groupId){\n    return hasGroup(this._subtitleGroups, groupId);\n  }\n  get audioGroups(){\n    return this._audioGroups;\n  }\n  get subtitleGroups(){\n    return this._subtitleGroups;\n  }\n  addGroupId(type, groupId){\n    if(!groupId){\n      return;\n    }\n    if(type==='audio'){\n      let audioGroups=this._audioGroups;\n      if(!audioGroups){\n        audioGroups=this._audioGroups=[];\n      }\n      if(audioGroups.indexOf(groupId)===-1){\n        audioGroups.push(groupId);\n      }\n    }else if(type==='text'){\n      let subtitleGroups=this._subtitleGroups;\n      if(!subtitleGroups){\n        subtitleGroups=this._subtitleGroups=[];\n      }\n      if(subtitleGroups.indexOf(groupId)===-1){\n        subtitleGroups.push(groupId);\n      }\n    }\n  }\n\n  // Deprecated methods (retained for backwards compatibility)\n  get urlId(){\n    return 0;\n  }\n  set urlId(value){}\n  get audioGroupIds(){\n    return this.audioGroups ? [this.audioGroupId]:undefined;\n  }\n  get textGroupIds(){\n    return this.subtitleGroups ? [this.textGroupId]:undefined;\n  }\n  get audioGroupId(){\n    var _this$audioGroups;\n    return (_this$audioGroups=this.audioGroups)==null ? void 0:_this$audioGroups[0];\n  }\n  get textGroupId(){\n    var _this$subtitleGroups;\n    return (_this$subtitleGroups=this.subtitleGroups)==null ? void 0:_this$subtitleGroups[0];\n  }\n  addFallback(){}\n}\nfunction hasGroup(groups, groupId){\n  if(!groupId||!groups){\n    return false;\n  }\n  return groups.indexOf(groupId)!==-1;\n}\n\nfunction updateFromToPTS(fragFrom, fragTo){\n  const fragToPTS=fragTo.startPTS;\n  // if we know startPTS[toIdx]\n  if(isFiniteNumber(fragToPTS)){\n    // update fragment duration.\n    // it helps to fix drifts between playlist reported duration and fragment real duration\n    let duration=0;\n    let frag;\n    if(fragTo.sn > fragFrom.sn){\n      duration=fragToPTS - fragFrom.start;\n      frag=fragFrom;\n    }else{\n      duration=fragFrom.start - fragToPTS;\n      frag=fragTo;\n    }\n    if(frag.duration!==duration){\n      frag.duration=duration;\n    }\n    // we dont know startPTS[toIdx]\n  }else if(fragTo.sn > fragFrom.sn){\n    const contiguous=fragFrom.cc===fragTo.cc;\n    // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS\n    if(contiguous&&fragFrom.minEndPTS){\n      fragTo.start=fragFrom.start + (fragFrom.minEndPTS - fragFrom.start);\n    }else{\n      fragTo.start=fragFrom.start + fragFrom.duration;\n    }\n  }else{\n    fragTo.start=Math.max(fragFrom.start - fragTo.duration, 0);\n  }\n}\nfunction updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS){\n  const parsedMediaDuration=endPTS - startPTS;\n  if(parsedMediaDuration <=0){\n    logger.warn('Fragment should have a positive duration', frag);\n    endPTS=startPTS + frag.duration;\n    endDTS=startDTS + frag.duration;\n  }\n  let maxStartPTS=startPTS;\n  let minEndPTS=endPTS;\n  const fragStartPts=frag.startPTS;\n  const fragEndPts=frag.endPTS;\n  if(isFiniteNumber(fragStartPts)){\n    // delta PTS between audio and video\n    const deltaPTS=Math.abs(fragStartPts - startPTS);\n    if(!isFiniteNumber(frag.deltaPTS)){\n      frag.deltaPTS=deltaPTS;\n    }else{\n      frag.deltaPTS=Math.max(deltaPTS, frag.deltaPTS);\n    }\n    maxStartPTS=Math.max(startPTS, fragStartPts);\n    startPTS=Math.min(startPTS, fragStartPts);\n    startDTS=Math.min(startDTS, frag.startDTS);\n    minEndPTS=Math.min(endPTS, fragEndPts);\n    endPTS=Math.max(endPTS, fragEndPts);\n    endDTS=Math.max(endDTS, frag.endDTS);\n  }\n  const drift=startPTS - frag.start;\n  if(frag.start!==0){\n    frag.start=startPTS;\n  }\n  frag.duration=endPTS - frag.start;\n  frag.startPTS=startPTS;\n  frag.maxStartPTS=maxStartPTS;\n  frag.startDTS=startDTS;\n  frag.endPTS=endPTS;\n  frag.minEndPTS=minEndPTS;\n  frag.endDTS=endDTS;\n  const sn=frag.sn; // 'initSegment'\n  // exit if sn out of range\n  if(!details||sn < details.startSN||sn > details.endSN){\n    return 0;\n  }\n  let i;\n  const fragIdx=sn - details.startSN;\n  const fragments=details.fragments;\n  // update frag reference in fragments array\n  // rationale is that fragments array might not contain this frag object.\n  // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS()\n  // if we don't update frag, we won't be able to propagate PTS info on the playlist\n  // resulting in invalid sliding computation\n  fragments[fragIdx]=frag;\n  // adjust fragment PTS/duration from seqnum-1 to frag 0\n  for (i=fragIdx; i > 0; i--){\n    updateFromToPTS(fragments[i], fragments[i - 1]);\n  }\n\n  // adjust fragment PTS/duration from seqnum to last frag\n  for (i=fragIdx; i < fragments.length - 1; i++){\n    updateFromToPTS(fragments[i], fragments[i + 1]);\n  }\n  if(details.fragmentHint){\n    updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint);\n  }\n  details.PTSKnown=details.alignedSliding=true;\n  return drift;\n}\nfunction mergeDetails(oldDetails, newDetails){\n  // Track the last initSegment processed. Initialize it to the last one on the timeline.\n  let currentInitSegment=null;\n  const oldFragments=oldDetails.fragments;\n  for (let i=oldFragments.length - 1; i >=0; i--){\n    const oldInit=oldFragments[i].initSegment;\n    if(oldInit){\n      currentInitSegment=oldInit;\n      break;\n    }\n  }\n  if(oldDetails.fragmentHint){\n    // prevent PTS and duration from being adjusted on the next hint\n    delete oldDetails.fragmentHint.endPTS;\n  }\n  // check if old/new playlists have fragments in common\n  // loop through overlapping SN and update startPTS, cc, and duration if any found\n  let PTSFrag;\n  mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments)=> {\n    if(newDetails.skippedSegments){\n      if(newFrag.cc!==oldFrag.cc){\n        const ccOffset=oldFrag.cc - newFrag.cc;\n        for (let i=newFragIndex; i < newFragments.length; i++){\n          newFragments[i].cc +=ccOffset;\n        }\n      }\n    }\n    if(isFiniteNumber(oldFrag.startPTS)&&isFiniteNumber(oldFrag.endPTS)){\n      newFrag.start=newFrag.startPTS=oldFrag.startPTS;\n      newFrag.startDTS=oldFrag.startDTS;\n      newFrag.maxStartPTS=oldFrag.maxStartPTS;\n      newFrag.endPTS=oldFrag.endPTS;\n      newFrag.endDTS=oldFrag.endDTS;\n      newFrag.minEndPTS=oldFrag.minEndPTS;\n      newFrag.duration=oldFrag.endPTS - oldFrag.startPTS;\n      if(newFrag.duration){\n        PTSFrag=newFrag;\n      }\n\n      // PTS is known when any segment has startPTS and endPTS\n      newDetails.PTSKnown=newDetails.alignedSliding=true;\n    }\n    newFrag.elementaryStreams=oldFrag.elementaryStreams;\n    newFrag.loader=oldFrag.loader;\n    newFrag.stats=oldFrag.stats;\n    if(oldFrag.initSegment){\n      newFrag.initSegment=oldFrag.initSegment;\n      currentInitSegment=oldFrag.initSegment;\n    }\n  });\n  const newFragments=newDetails.fragments;\n  if(currentInitSegment){\n    const fragmentsToCheck=newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint):newFragments;\n    fragmentsToCheck.forEach(frag=> {\n      var _currentInitSegment;\n      if(frag&&(!frag.initSegment||frag.initSegment.relurl===((_currentInitSegment=currentInitSegment)==null ? void 0:_currentInitSegment.relurl))){\n        frag.initSegment=currentInitSegment;\n      }\n    });\n  }\n  if(newDetails.skippedSegments){\n    newDetails.deltaUpdateFailed=newFragments.some(frag=> !frag);\n    if(newDetails.deltaUpdateFailed){\n      logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');\n      for (let i=newDetails.skippedSegments; i--;){\n        newFragments.shift();\n      }\n      newDetails.startSN=newFragments[0].sn;\n    }else{\n      if(newDetails.canSkipDateRanges){\n        newDetails.dateRanges=mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);\n      }\n    }\n    newDetails.startCC=newDetails.fragments[0].cc;\n    newDetails.endCC=newFragments[newFragments.length - 1].cc;\n  }\n\n  // Merge parts\n  mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart)=> {\n    newPart.elementaryStreams=oldPart.elementaryStreams;\n    newPart.stats=oldPart.stats;\n  });\n\n  // if at least one fragment contains PTS info, recompute PTS information for all fragments\n  if(PTSFrag){\n    updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS);\n  }else{\n    // ensure that delta is within oldFragments range\n    // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61])\n    // in that case we also need to adjust start offset of all fragments\n    adjustSliding(oldDetails, newDetails);\n  }\n  if(newFragments.length){\n    newDetails.totalduration=newDetails.edge - newFragments[0].start;\n  }\n  newDetails.driftStartTime=oldDetails.driftStartTime;\n  newDetails.driftStart=oldDetails.driftStart;\n  const advancedDateTime=newDetails.advancedDateTime;\n  if(newDetails.advanced&&advancedDateTime){\n    const edge=newDetails.edge;\n    if(!newDetails.driftStart){\n      newDetails.driftStartTime=advancedDateTime;\n      newDetails.driftStart=edge;\n    }\n    newDetails.driftEndTime=advancedDateTime;\n    newDetails.driftEnd=edge;\n  }else{\n    newDetails.driftEndTime=oldDetails.driftEndTime;\n    newDetails.driftEnd=oldDetails.driftEnd;\n    newDetails.advancedDateTime=oldDetails.advancedDateTime;\n  }\n}\nfunction mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges){\n  const dateRanges=_extends({}, oldDateRanges);\n  if(recentlyRemovedDateranges){\n    recentlyRemovedDateranges.forEach(id=> {\n      delete dateRanges[id];\n    });\n  }\n  Object.keys(deltaDateRanges).forEach(id=> {\n    const dateRange=new DateRange(deltaDateRanges[id].attr, dateRanges[id]);\n    if(dateRange.isValid){\n      dateRanges[id]=dateRange;\n    }else{\n      logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: \"${JSON.stringify(deltaDateRanges[id].attr)}\"`);\n    }\n  });\n  return dateRanges;\n}\nfunction mapPartIntersection(oldParts, newParts, intersectionFn){\n  if(oldParts&&newParts){\n    let delta=0;\n    for (let i=0, len=oldParts.length; i <=len; i++){\n      const oldPart=oldParts[i];\n      const newPart=newParts[i + delta];\n      if(oldPart&&newPart&&oldPart.index===newPart.index&&oldPart.fragment.sn===newPart.fragment.sn){\n        intersectionFn(oldPart, newPart);\n      }else{\n        delta--;\n      }\n    }\n  }\n}\nfunction mapFragmentIntersection(oldDetails, newDetails, intersectionFn){\n  const skippedSegments=newDetails.skippedSegments;\n  const start=Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;\n  const end=(oldDetails.fragmentHint ? 1:0) + (skippedSegments ? newDetails.endSN:Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN;\n  const delta=newDetails.startSN - oldDetails.startSN;\n  const newFrags=newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint):newDetails.fragments;\n  const oldFrags=oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint):oldDetails.fragments;\n  for (let i=start; i <=end; i++){\n    const oldFrag=oldFrags[delta + i];\n    let newFrag=newFrags[i];\n    if(skippedSegments&&!newFrag&&i < skippedSegments){\n      // Fill in skipped segments in delta playlist\n      newFrag=newDetails.fragments[i]=oldFrag;\n    }\n    if(oldFrag&&newFrag){\n      intersectionFn(oldFrag, newFrag, i, newFrags);\n    }\n  }\n}\nfunction adjustSliding(oldDetails, newDetails){\n  const delta=newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;\n  const oldFragments=oldDetails.fragments;\n  if(delta < 0||delta >=oldFragments.length){\n    return;\n  }\n  addSliding(newDetails, oldFragments[delta].start);\n}\nfunction addSliding(details, start){\n  if(start){\n    const fragments=details.fragments;\n    for (let i=details.skippedSegments; i < fragments.length; i++){\n      fragments[i].start +=start;\n    }\n    if(details.fragmentHint){\n      details.fragmentHint.start +=start;\n    }\n  }\n}\nfunction computeReloadInterval(newDetails, distanceToLiveEdgeMs=Infinity){\n  let reloadInterval=1000 * newDetails.targetduration;\n  if(newDetails.updated){\n    // Use last segment duration when shorter than target duration and near live edge\n    const fragments=newDetails.fragments;\n    const liveEdgeMaxTargetDurations=4;\n    if(fragments.length&&reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs){\n      const lastSegmentDuration=fragments[fragments.length - 1].duration * 1000;\n      if(lastSegmentDuration < reloadInterval){\n        reloadInterval=lastSegmentDuration;\n      }\n    }\n  }else{\n    // estimate='miss half average';\n    // follow HLS Spec, If the client reloads a Playlist file and finds that it has not\n    // changed then it MUST wait for a period of one-half the target\n    // duration before retrying.\n    reloadInterval /=2;\n  }\n  return Math.round(reloadInterval);\n}\nfunction getFragmentWithSN(level, sn, fragCurrent){\n  if(!(level!=null&&level.details)){\n    return null;\n  }\n  const levelDetails=level.details;\n  let fragment=levelDetails.fragments[sn - levelDetails.startSN];\n  if(fragment){\n    return fragment;\n  }\n  fragment=levelDetails.fragmentHint;\n  if(fragment&&fragment.sn===sn){\n    return fragment;\n  }\n  if(sn < levelDetails.startSN&&fragCurrent&&fragCurrent.sn===sn){\n    return fragCurrent;\n  }\n  return null;\n}\nfunction getPartWith(level, sn, partIndex){\n  var _level$details;\n  if(!(level!=null&&level.details)){\n    return null;\n  }\n  return findPart((_level$details=level.details)==null ? void 0:_level$details.partList, sn, partIndex);\n}\nfunction findPart(partList, sn, partIndex){\n  if(partList){\n    for (let i=partList.length; i--;){\n      const part=partList[i];\n      if(part.index===partIndex&&part.fragment.sn===sn){\n        return part;\n      }\n    }\n  }\n  return null;\n}\nfunction reassignFragmentLevelIndexes(levels){\n  levels.forEach((level, index)=> {\n    const {\n      details\n    }=level;\n    if(details!=null&&details.fragments){\n      details.fragments.forEach(fragment=> {\n        fragment.level=index;\n      });\n    }\n  });\n}\n\nfunction isTimeoutError(error){\n  switch (error.details){\n    case ErrorDetails.FRAG_LOAD_TIMEOUT:\n    case ErrorDetails.KEY_LOAD_TIMEOUT:\n    case ErrorDetails.LEVEL_LOAD_TIMEOUT:\n    case ErrorDetails.MANIFEST_LOAD_TIMEOUT:\n      return true;\n  }\n  return false;\n}\nfunction getRetryConfig(loadPolicy, error){\n  const isTimeout=isTimeoutError(error);\n  return loadPolicy.default[`${isTimeout ? 'timeout':'error'}Retry`];\n}\nfunction getRetryDelay(retryConfig, retryCount){\n  // exponential backoff capped to max retry delay\n  const backoffFactor=retryConfig.backoff==='linear' ? 1:Math.pow(2, retryCount);\n  return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs);\n}\nfunction getLoaderConfigWithoutReties(loderConfig){\n  return _objectSpread2(_objectSpread2({}, loderConfig), {\n    errorRetry: null,\n    timeoutRetry: null\n  });\n}\nfunction shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse){\n  if(!retryConfig){\n    return false;\n  }\n  const httpStatus=loaderResponse==null ? void 0:loaderResponse.code;\n  const retry=retryCount < retryConfig.maxNumRetry&&(retryForHttpStatus(httpStatus)||!!isTimeout);\n  return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry):retry;\n}\nfunction retryForHttpStatus(httpStatus){\n  // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error)\n  return httpStatus===0&&navigator.onLine===false||!!httpStatus&&(httpStatus < 400||httpStatus > 499);\n}\n\nconst BinarySearch={\n  \n  search: function (list, comparisonFn){\n    let minIndex=0;\n    let maxIndex=list.length - 1;\n    let currentIndex=null;\n    let currentElement=null;\n    while (minIndex <=maxIndex){\n      currentIndex=(minIndex + maxIndex) / 2 | 0;\n      currentElement=list[currentIndex];\n      const comparisonResult=comparisonFn(currentElement);\n      if(comparisonResult > 0){\n        minIndex=currentIndex + 1;\n      }else if(comparisonResult < 0){\n        maxIndex=currentIndex - 1;\n      }else{\n        return currentElement;\n      }\n    }\n    return null;\n  }\n};\n\n\nfunction findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance){\n  if(PDTValue===null||!Array.isArray(fragments)||!fragments.length||!isFiniteNumber(PDTValue)){\n    return null;\n  }\n\n  // if less than start\n  const startPDT=fragments[0].programDateTime;\n  if(PDTValue < (startPDT||0)){\n    return null;\n  }\n  const endPDT=fragments[fragments.length - 1].endProgramDateTime;\n  if(PDTValue >=(endPDT||0)){\n    return null;\n  }\n  maxFragLookUpTolerance=maxFragLookUpTolerance||0;\n  for (let seg=0; seg < fragments.length; ++seg){\n    const frag=fragments[seg];\n    if(pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)){\n      return frag;\n    }\n  }\n  return null;\n}\n\n\nfunction findFragmentByPTS(fragPrevious, fragments, bufferEnd=0, maxFragLookUpTolerance=0, nextFragLookupTolerance=0.005){\n  let fragNext=null;\n  if(fragPrevious){\n    fragNext=fragments[fragPrevious.sn - fragments[0].sn + 1]||null;\n    // check for buffer-end rounding error\n    const bufferEdgeError=fragPrevious.endDTS - bufferEnd;\n    if(bufferEdgeError > 0&&bufferEdgeError < 0.0000015){\n      bufferEnd +=0.0000015;\n    }\n  }else if(bufferEnd===0&&fragments[0].start===0){\n    fragNext=fragments[0];\n  }\n  // Prefer the next fragment if it's within tolerance\n  if(fragNext&&((!fragPrevious||fragPrevious.level===fragNext.level)&&fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext)===0||fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))){\n    return fragNext;\n  }\n  // We might be seeking past the tolerance so find the best match\n  const foundFragment=BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));\n  if(foundFragment&&(foundFragment!==fragPrevious||!fragNext)){\n    return foundFragment;\n  }\n  // If no match was found return the next fragment after fragPrevious, or null\n  return fragNext;\n}\nfunction fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance){\n  if(fragPrevious&&fragPrevious.start===0&&fragPrevious.level < fragNext.level&&(fragPrevious.endPTS||0) > 0){\n    const firstDuration=fragPrevious.tagList.reduce((duration, tag)=> {\n      if(tag[0]==='INF'){\n        duration +=parseFloat(tag[1]);\n      }\n      return duration;\n    }, nextFragLookupTolerance);\n    return fragNext.start <=firstDuration;\n  }\n  return false;\n}\n\n\nfunction fragmentWithinToleranceTest(bufferEnd=0, maxFragLookUpTolerance=0, candidate){\n  // eagerly accept an accurate match (no tolerance)\n  if(candidate.start <=bufferEnd&&candidate.start + candidate.duration > bufferEnd){\n    return 0;\n  }\n  // offset should be within fragment boundary - config.maxFragLookUpTolerance\n  // this is to cope with situations like\n  // bufferEnd=9.991\n  // frag[Ø]:[0,10]\n  // frag[1]:[10,20]\n  // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here\n  //              frag start               frag start+duration\n  //                  |-----------------------------|\n  //              <---\x3e                         <---\x3e\n  //  ...--------\x3e<-----------------------------\x3e<---------....\n  // previous frag         matching fragment         next frag\n  //  return -1             return 0                 return 1\n  // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);\n  // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments\n  const candidateLookupTolerance=Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS:0));\n  if(candidate.start + candidate.duration - candidateLookupTolerance <=bufferEnd){\n    return 1;\n  }else if(candidate.start - candidateLookupTolerance > bufferEnd&&candidate.start){\n    // if maxFragLookUpTolerance will have negative value then don't return -1 for first element\n    return -1;\n  }\n  return 0;\n}\n\n\nfunction pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate){\n  const candidateLookupTolerance=Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS:0)) * 1000;\n\n  // endProgramDateTime can be null, default to zero\n  const endProgramDateTime=candidate.endProgramDateTime||0;\n  return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;\n}\nfunction findFragWithCC(fragments, cc){\n  return BinarySearch.search(fragments, candidate=> {\n    if(candidate.cc < cc){\n      return 1;\n    }else if(candidate.cc > cc){\n      return -1;\n    }else{\n      return 0;\n    }\n  });\n}\n\nvar NetworkErrorAction={\n  DoNothing: 0,\n  SendEndCallback: 1,\n  SendAlternateToPenaltyBox: 2,\n  RemoveAlternatePermanently: 3,\n  InsertDiscontinuity: 4,\n  RetryRequest: 5\n};\nvar ErrorActionFlags={\n  None: 0,\n  MoveAllAlternatesMatchingHost: 1,\n  MoveAllAlternatesMatchingHDCP: 2,\n  SwitchToSDR: 4\n}; // Reserved for future use\nclass ErrorController {\n  constructor(hls){\n    this.hls=void 0;\n    this.playlistError=0;\n    this.penalizedRenditions={};\n    this.log=void 0;\n    this.warn=void 0;\n    this.error=void 0;\n    this.hls=hls;\n    this.log=logger.log.bind(logger, `[info]:`);\n    this.warn=logger.warn.bind(logger, `[warning]:`);\n    this.error=logger.error.bind(logger, `[error]:`);\n    this.registerListeners();\n  }\n  registerListeners(){\n    const hls=this.hls;\n    hls.on(Events.ERROR, this.onError, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n  }\n  unregisterListeners(){\n    const hls=this.hls;\n    if(!hls){\n      return;\n    }\n    hls.off(Events.ERROR, this.onError, this);\n    hls.off(Events.ERROR, this.onErrorOut, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n  }\n  destroy(){\n    this.unregisterListeners();\n    // @ts-ignore\n    this.hls=null;\n    this.penalizedRenditions={};\n  }\n  startLoad(startPosition){}\n  stopLoad(){\n    this.playlistError=0;\n  }\n  getVariantLevelIndex(frag){\n    return (frag==null ? void 0:frag.type)===PlaylistLevelType.MAIN ? frag.level:this.hls.loadLevel;\n  }\n  onManifestLoading(){\n    this.playlistError=0;\n    this.penalizedRenditions={};\n  }\n  onLevelUpdated(){\n    this.playlistError=0;\n  }\n  onError(event, data){\n    var _data$frag, _data$level;\n    if(data.fatal){\n      return;\n    }\n    const hls=this.hls;\n    const context=data.context;\n    switch (data.details){\n      case ErrorDetails.FRAG_LOAD_ERROR:\n      case ErrorDetails.FRAG_LOAD_TIMEOUT:\n      case ErrorDetails.KEY_LOAD_ERROR:\n      case ErrorDetails.KEY_LOAD_TIMEOUT:\n        data.errorAction=this.getFragRetryOrSwitchAction(data);\n        return;\n      case ErrorDetails.FRAG_PARSING_ERROR:\n        // ignore empty segment errors marked as gap\n        if((_data$frag=data.frag)!=null&&_data$frag.gap){\n          data.errorAction={\n            action: NetworkErrorAction.DoNothing,\n            flags: ErrorActionFlags.None\n          };\n          return;\n        }\n      // falls through\n      case ErrorDetails.FRAG_GAP:\n      case ErrorDetails.FRAG_DECRYPT_ERROR:\n        {\n          // Switch level if possible, otherwise allow retry count to reach max error retries\n          data.errorAction=this.getFragRetryOrSwitchAction(data);\n          data.errorAction.action=NetworkErrorAction.SendAlternateToPenaltyBox;\n          return;\n        }\n      case ErrorDetails.LEVEL_EMPTY_ERROR:\n      case ErrorDetails.LEVEL_PARSING_ERROR:\n        {\n          var _data$context, _data$context$levelDe;\n          // Only retry when empty and live\n          const levelIndex=data.parent===PlaylistLevelType.MAIN ? data.level:hls.loadLevel;\n          if(data.details===ErrorDetails.LEVEL_EMPTY_ERROR&&!!((_data$context=data.context)!=null&&(_data$context$levelDe=_data$context.levelDetails)!=null&&_data$context$levelDe.live)){\n            data.errorAction=this.getPlaylistRetryOrSwitchAction(data, levelIndex);\n          }else{\n            // Escalate to fatal if not retrying or switching\n            data.levelRetry=false;\n            data.errorAction=this.getLevelSwitchAction(data, levelIndex);\n          }\n        }\n        return;\n      case ErrorDetails.LEVEL_LOAD_ERROR:\n      case ErrorDetails.LEVEL_LOAD_TIMEOUT:\n        if(typeof (context==null ? void 0:context.level)==='number'){\n          data.errorAction=this.getPlaylistRetryOrSwitchAction(data, context.level);\n        }\n        return;\n      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:\n      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:\n      case ErrorDetails.SUBTITLE_LOAD_ERROR:\n      case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:\n        if(context){\n          const level=hls.levels[hls.loadLevel];\n          if(level&&(context.type===PlaylistContextType.AUDIO_TRACK&&level.hasAudioGroup(context.groupId)||context.type===PlaylistContextType.SUBTITLE_TRACK&&level.hasSubtitleGroup(context.groupId))){\n            // Perform Pathway switch or Redundant failover if possible for fastest recovery\n            // otherwise allow playlist retry count to reach max error retries\n            data.errorAction=this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel);\n            data.errorAction.action=NetworkErrorAction.SendAlternateToPenaltyBox;\n            data.errorAction.flags=ErrorActionFlags.MoveAllAlternatesMatchingHost;\n            return;\n          }\n        }\n        return;\n      case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:\n        {\n          const level=hls.levels[hls.loadLevel];\n          const restrictedHdcpLevel=level==null ? void 0:level.attrs['HDCP-LEVEL'];\n          if(restrictedHdcpLevel){\n            data.errorAction={\n              action: NetworkErrorAction.SendAlternateToPenaltyBox,\n              flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP,\n              hdcpLevel: restrictedHdcpLevel\n            };\n          }else{\n            this.keySystemError(data);\n          }\n        }\n        return;\n      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:\n      case ErrorDetails.REMUX_ALLOC_ERROR:\n      case ErrorDetails.BUFFER_APPEND_ERROR:\n        data.errorAction=this.getLevelSwitchAction(data, (_data$level=data.level)!=null ? _data$level:hls.loadLevel);\n        return;\n      case ErrorDetails.INTERNAL_EXCEPTION:\n      case ErrorDetails.BUFFER_APPENDING_ERROR:\n      case ErrorDetails.BUFFER_FULL_ERROR:\n      case ErrorDetails.LEVEL_SWITCH_ERROR:\n      case ErrorDetails.BUFFER_STALLED_ERROR:\n      case ErrorDetails.BUFFER_SEEK_OVER_HOLE:\n      case ErrorDetails.BUFFER_NUDGE_ON_STALL:\n        data.errorAction={\n          action: NetworkErrorAction.DoNothing,\n          flags: ErrorActionFlags.None\n        };\n        return;\n    }\n    if(data.type===ErrorTypes.KEY_SYSTEM_ERROR){\n      this.keySystemError(data);\n    }\n  }\n  keySystemError(data){\n    const levelIndex=this.getVariantLevelIndex(data.frag);\n    // Do not retry level. Escalate to fatal if switching levels fails.\n    data.levelRetry=false;\n    data.errorAction=this.getLevelSwitchAction(data, levelIndex);\n  }\n  getPlaylistRetryOrSwitchAction(data, levelIndex){\n    const hls=this.hls;\n    const retryConfig=getRetryConfig(hls.config.playlistLoadPolicy, data);\n    const retryCount=this.playlistError++;\n    const retry=shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response);\n    if(retry){\n      return {\n        action: NetworkErrorAction.RetryRequest,\n        flags: ErrorActionFlags.None,\n        retryConfig,\n        retryCount\n      };\n    }\n    const errorAction=this.getLevelSwitchAction(data, levelIndex);\n    if(retryConfig){\n      errorAction.retryConfig=retryConfig;\n      errorAction.retryCount=retryCount;\n    }\n    return errorAction;\n  }\n  getFragRetryOrSwitchAction(data){\n    const hls=this.hls;\n    // Share fragment error count accross media options (main, audio, subs)\n    // This allows for level based rendition switching when media option assets fail\n    const variantLevelIndex=this.getVariantLevelIndex(data.frag);\n    const level=hls.levels[variantLevelIndex];\n    const {\n      fragLoadPolicy,\n      keyLoadPolicy\n    }=hls.config;\n    const retryConfig=getRetryConfig(data.details.startsWith('key') ? keyLoadPolicy:fragLoadPolicy, data);\n    const fragmentErrors=hls.levels.reduce((acc, level)=> acc + level.fragmentError, 0);\n    // Switch levels when out of retried or level index out of bounds\n    if(level){\n      if(data.details!==ErrorDetails.FRAG_GAP){\n        level.fragmentError++;\n      }\n      const retry=shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response);\n      if(retry){\n        return {\n          action: NetworkErrorAction.RetryRequest,\n          flags: ErrorActionFlags.None,\n          retryConfig,\n          retryCount: fragmentErrors\n        };\n      }\n    }\n    // Reach max retry count, or Missing level reference\n    // Switch to valid index\n    const errorAction=this.getLevelSwitchAction(data, variantLevelIndex);\n    // Add retry details to allow skipping of FRAG_PARSING_ERROR\n    if(retryConfig){\n      errorAction.retryConfig=retryConfig;\n      errorAction.retryCount=fragmentErrors;\n    }\n    return errorAction;\n  }\n  getLevelSwitchAction(data, levelIndex){\n    const hls=this.hls;\n    if(levelIndex===null||levelIndex===undefined){\n      levelIndex=hls.loadLevel;\n    }\n    const level=this.hls.levels[levelIndex];\n    if(level){\n      var _data$frag2, _data$context2;\n      const errorDetails=data.details;\n      level.loadError++;\n      if(errorDetails===ErrorDetails.BUFFER_APPEND_ERROR){\n        level.fragmentError++;\n      }\n      // Search for next level to retry\n      let nextLevel=-1;\n      const {\n        levels,\n        loadLevel,\n        minAutoLevel,\n        maxAutoLevel\n      }=hls;\n      if(!hls.autoLevelEnabled){\n        hls.loadLevel=-1;\n      }\n      const fragErrorType=(_data$frag2=data.frag)==null ? void 0:_data$frag2.type;\n      // Find alternate audio codec if available on audio codec error\n      const isAudioCodecError=fragErrorType===PlaylistLevelType.AUDIO&&errorDetails===ErrorDetails.FRAG_PARSING_ERROR||data.sourceBufferName==='audio'&&(errorDetails===ErrorDetails.BUFFER_ADD_CODEC_ERROR||errorDetails===ErrorDetails.BUFFER_APPEND_ERROR);\n      const findAudioCodecAlternate=isAudioCodecError&&levels.some(({\n        audioCodec\n      })=> level.audioCodec!==audioCodec);\n      // Find alternate video codec if available on video codec error\n      const isVideoCodecError=data.sourceBufferName==='video'&&(errorDetails===ErrorDetails.BUFFER_ADD_CODEC_ERROR||errorDetails===ErrorDetails.BUFFER_APPEND_ERROR);\n      const findVideoCodecAlternate=isVideoCodecError&&levels.some(({\n        codecSet,\n        audioCodec\n      })=> level.codecSet!==codecSet&&level.audioCodec===audioCodec);\n      const {\n        type: playlistErrorType,\n        groupId: playlistErrorGroupId\n      }=(_data$context2=data.context)!=null ? _data$context2:{};\n      for (let i=levels.length; i--;){\n        const candidate=(i + loadLevel) % levels.length;\n        if(candidate!==loadLevel&&candidate >=minAutoLevel&&candidate <=maxAutoLevel&&levels[candidate].loadError===0){\n          var _level$audioGroups, _level$subtitleGroups;\n          const levelCandidate=levels[candidate];\n          // Skip level switch if GAP tag is found in next level at same position\n          if(errorDetails===ErrorDetails.FRAG_GAP&&fragErrorType===PlaylistLevelType.MAIN&&data.frag){\n            const levelDetails=levels[candidate].details;\n            if(levelDetails){\n              const fragCandidate=findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start);\n              if(fragCandidate!=null&&fragCandidate.gap){\n                continue;\n              }\n            }\n          }else if(playlistErrorType===PlaylistContextType.AUDIO_TRACK&&levelCandidate.hasAudioGroup(playlistErrorGroupId)||playlistErrorType===PlaylistContextType.SUBTITLE_TRACK&&levelCandidate.hasSubtitleGroup(playlistErrorGroupId)){\n            // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over\n            continue;\n          }else if(fragErrorType===PlaylistLevelType.AUDIO&&(_level$audioGroups=level.audioGroups)!=null&&_level$audioGroups.some(groupId=> levelCandidate.hasAudioGroup(groupId))||fragErrorType===PlaylistLevelType.SUBTITLE&&(_level$subtitleGroups=level.subtitleGroups)!=null&&_level$subtitleGroups.some(groupId=> levelCandidate.hasSubtitleGroup(groupId))||findAudioCodecAlternate&&level.audioCodec===levelCandidate.audioCodec||!findAudioCodecAlternate&&level.audioCodec!==levelCandidate.audioCodec||findVideoCodecAlternate&&level.codecSet===levelCandidate.codecSet){\n            // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over\n            continue;\n          }\n          nextLevel=candidate;\n          break;\n        }\n      }\n      if(nextLevel > -1&&hls.loadLevel!==nextLevel){\n        data.levelRetry=true;\n        this.playlistError=0;\n        return {\n          action: NetworkErrorAction.SendAlternateToPenaltyBox,\n          flags: ErrorActionFlags.None,\n          nextAutoLevel: nextLevel\n        };\n      }\n    }\n    // No levels to switch / Manual level selection / Level not found\n    // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level\n    return {\n      action: NetworkErrorAction.SendAlternateToPenaltyBox,\n      flags: ErrorActionFlags.MoveAllAlternatesMatchingHost\n    };\n  }\n  onErrorOut(event, data){\n    var _data$errorAction;\n    switch ((_data$errorAction=data.errorAction)==null ? void 0:_data$errorAction.action){\n      case NetworkErrorAction.DoNothing:\n        break;\n      case NetworkErrorAction.SendAlternateToPenaltyBox:\n        this.sendAlternateToPenaltyBox(data);\n        if(!data.errorAction.resolved&&data.details!==ErrorDetails.FRAG_GAP){\n          data.fatal=true;\n        }else if(/MediaSource readyState: ended/.test(data.error.message)){\n          this.warn(`MediaSource ended after \"${data.sourceBufferName}\" sourceBuffer append error. Attempting to recover from media error.`);\n          this.hls.recoverMediaError();\n        }\n        break;\n      case NetworkErrorAction.RetryRequest:\n        // handled by stream and playlist/level controllers\n        break;\n    }\n    if(data.fatal){\n      this.hls.stopLoad();\n      return;\n    }\n  }\n  sendAlternateToPenaltyBox(data){\n    const hls=this.hls;\n    const errorAction=data.errorAction;\n    if(!errorAction){\n      return;\n    }\n    const {\n      flags,\n      hdcpLevel,\n      nextAutoLevel\n    }=errorAction;\n    switch (flags){\n      case ErrorActionFlags.None:\n        this.switchLevel(data, nextAutoLevel);\n        break;\n      case ErrorActionFlags.MoveAllAlternatesMatchingHDCP:\n        if(hdcpLevel){\n          hls.maxHdcpLevel=HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1];\n          errorAction.resolved=true;\n        }\n        this.warn(`Restricting playback to HDCP-LEVEL of \"${hls.maxHdcpLevel}\" or lower`);\n        break;\n    }\n    // If not resolved by previous actions try to switch to next level\n    if(!errorAction.resolved){\n      this.switchLevel(data, nextAutoLevel);\n    }\n  }\n  switchLevel(data, levelIndex){\n    if(levelIndex!==undefined&&data.errorAction){\n      this.warn(`switching to level ${levelIndex} after ${data.details}`);\n      this.hls.nextAutoLevel=levelIndex;\n      data.errorAction.resolved=true;\n      // Stream controller is responsible for this but won't switch on false start\n      this.hls.nextLoadLevel=this.hls.nextAutoLevel;\n    }\n  }\n}\n\nclass BasePlaylistController {\n  constructor(hls, logPrefix){\n    this.hls=void 0;\n    this.timer=-1;\n    this.requestScheduled=-1;\n    this.canLoad=false;\n    this.log=void 0;\n    this.warn=void 0;\n    this.log=logger.log.bind(logger, `${logPrefix}:`);\n    this.warn=logger.warn.bind(logger, `${logPrefix}:`);\n    this.hls=hls;\n  }\n  destroy(){\n    this.clearTimer();\n    // @ts-ignore\n    this.hls=this.log=this.warn=null;\n  }\n  clearTimer(){\n    if(this.timer!==-1){\n      self.clearTimeout(this.timer);\n      this.timer=-1;\n    }\n  }\n  startLoad(){\n    this.canLoad=true;\n    this.requestScheduled=-1;\n    this.loadPlaylist();\n  }\n  stopLoad(){\n    this.canLoad=false;\n    this.clearTimer();\n  }\n  switchParams(playlistUri, previous, current){\n    const renditionReports=previous==null ? void 0:previous.renditionReports;\n    if(renditionReports){\n      let foundIndex=-1;\n      for (let i=0; i < renditionReports.length; i++){\n        const attr=renditionReports[i];\n        let uri;\n        try {\n          uri=new self.URL(attr.URI, previous.url).href;\n        } catch (error){\n          logger.warn(`Could not construct new URL for Rendition Report: ${error}`);\n          uri=attr.URI||'';\n        }\n        // Use exact match. Otherwise, the last partial match, if any, will be used\n        // (Playlist URI includes a query string that the Rendition Report does not)\n        if(uri===playlistUri){\n          foundIndex=i;\n          break;\n        }else if(uri===playlistUri.substring(0, uri.length)){\n          foundIndex=i;\n        }\n      }\n      if(foundIndex!==-1){\n        const attr=renditionReports[foundIndex];\n        const msn=parseInt(attr['LAST-MSN'])||(previous==null ? void 0:previous.lastPartSn);\n        let part=parseInt(attr['LAST-PART'])||(previous==null ? void 0:previous.lastPartIndex);\n        if(this.hls.config.lowLatencyMode){\n          const currentGoal=Math.min(previous.age - previous.partTarget, previous.targetduration);\n          if(part >=0&&currentGoal > previous.partTarget){\n            part +=1;\n          }\n        }\n        const skip=current&&getSkipValue(current);\n        return new HlsUrlParameters(msn, part >=0 ? part:undefined, skip);\n      }\n    }\n  }\n  loadPlaylist(hlsUrlParameters){\n    if(this.requestScheduled===-1){\n      this.requestScheduled=self.performance.now();\n    }\n    // Loading is handled by the subclasses\n  }\n  shouldLoadPlaylist(playlist){\n    return this.canLoad&&!!playlist&&!!playlist.url&&(!playlist.details||playlist.details.live);\n  }\n  shouldReloadPlaylist(playlist){\n    return this.timer===-1&&this.requestScheduled===-1&&this.shouldLoadPlaylist(playlist);\n  }\n  playlistLoaded(index, data, previousDetails){\n    const {\n      details,\n      stats\n    }=data;\n\n    // Set last updated date-time\n    const now=self.performance.now();\n    const elapsed=stats.loading.first ? Math.max(0, now - stats.loading.first):0;\n    details.advancedDateTime=Date.now() - elapsed;\n\n    // if current playlist is a live playlist, arm a timer to reload it\n    if(details.live||previousDetails!=null&&previousDetails.live){\n      details.reloaded(previousDetails);\n      if(previousDetails){\n        this.log(`live playlist ${index} ${details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex:details.updated ? 'UPDATED':'MISSED'}`);\n      }\n      // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments\n      if(previousDetails&&details.fragments.length > 0){\n        mergeDetails(previousDetails, details);\n      }\n      if(!this.canLoad||!details.live){\n        return;\n      }\n      let deliveryDirectives;\n      let msn=undefined;\n      let part=undefined;\n      if(details.canBlockReload&&details.endSN&&details.advanced){\n        // Load level with LL-HLS delivery directives\n        const lowLatencyMode=this.hls.config.lowLatencyMode;\n        const lastPartSn=details.lastPartSn;\n        const endSn=details.endSN;\n        const lastPartIndex=details.lastPartIndex;\n        const hasParts=lastPartIndex!==-1;\n        const lastPart=lastPartSn===endSn;\n        // When low latency mode is disabled, we'll skip part requests once the last part index is found\n        const nextSnStartIndex=lowLatencyMode ? 0:lastPartIndex;\n        if(hasParts){\n          msn=lastPart ? endSn + 1:lastPartSn;\n          part=lastPart ? nextSnStartIndex:lastPartIndex + 1;\n        }else{\n          msn=endSn + 1;\n        }\n        // Low-Latency CDN Tune-in: \"age\" header and time since load indicates we're behind by more than one part\n        // Update directives to obtain the Playlist that has the estimated additional duration of media\n        const lastAdvanced=details.age;\n        const cdnAge=lastAdvanced + details.ageHeader;\n        let currentGoal=Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);\n        if(currentGoal > 0){\n          if(previousDetails&&currentGoal > previousDetails.tuneInGoal){\n            // If we attempted to get the next or latest playlist update, but currentGoal increased,\n            // then we either can't catchup, or the \"age\" header cannot be trusted.\n            this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);\n            currentGoal=0;\n          }else{\n            const segments=Math.floor(currentGoal / details.targetduration);\n            msn +=segments;\n            if(part!==undefined){\n              const parts=Math.round(currentGoal % details.targetduration / details.partTarget);\n              part +=parts;\n            }\n            this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`);\n          }\n          details.tuneInGoal=currentGoal;\n        }\n        deliveryDirectives=this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);\n        if(lowLatencyMode||!lastPart){\n          this.loadPlaylist(deliveryDirectives);\n          return;\n        }\n      }else if(details.canBlockReload||details.canSkipUntil){\n        deliveryDirectives=this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);\n      }\n      const bufferInfo=this.hls.mainForwardBufferInfo;\n      const position=bufferInfo ? bufferInfo.end - bufferInfo.len:0;\n      const distanceToLiveEdgeMs=(details.edge - position) * 1000;\n      const reloadInterval=computeReloadInterval(details, distanceToLiveEdgeMs);\n      if(details.updated&&now > this.requestScheduled + reloadInterval){\n        this.requestScheduled=stats.loading.start;\n      }\n      if(msn!==undefined&&details.canBlockReload){\n        this.requestScheduled=stats.loading.first + reloadInterval - (details.partTarget * 1000||1000);\n      }else if(this.requestScheduled===-1||this.requestScheduled + reloadInterval < now){\n        this.requestScheduled=now;\n      }else if(this.requestScheduled - now <=0){\n        this.requestScheduled +=reloadInterval;\n      }\n      let estimatedTimeUntilUpdate=this.requestScheduled - now;\n      estimatedTimeUntilUpdate=Math.max(0, estimatedTimeUntilUpdate);\n      this.log(`reload live playlist ${index} in ${Math.round(estimatedTimeUntilUpdate)} ms`);\n      // this.log(\n      //   `live reload ${details.updated ? 'REFRESHED':'MISSED'}\n      // reload in ${estimatedTimeUntilUpdate / 1000}\n      // round trip ${(stats.loading.end - stats.loading.start) / 1000}\n      // diff ${\n      //   (reloadInterval -\n      //     (estimatedTimeUntilUpdate +\n      //       stats.loading.end -\n      //       stats.loading.start)) /\n      //   1000\n      // }\n      // reload interval ${reloadInterval / 1000}\n      // target duration ${details.targetduration}\n      // distance to edge ${distanceToLiveEdgeMs / 1000}`\n      //);\n\n      this.timer=self.setTimeout(()=> this.loadPlaylist(deliveryDirectives), estimatedTimeUntilUpdate);\n    }else{\n      this.clearTimer();\n    }\n  }\n  getDeliveryDirectives(details, previousDeliveryDirectives, msn, part){\n    let skip=getSkipValue(details);\n    if(previousDeliveryDirectives!=null&&previousDeliveryDirectives.skip&&details.deltaUpdateFailed){\n      msn=previousDeliveryDirectives.msn;\n      part=previousDeliveryDirectives.part;\n      skip=HlsSkip.No;\n    }\n    return new HlsUrlParameters(msn, part, skip);\n  }\n  checkRetry(errorEvent){\n    const errorDetails=errorEvent.details;\n    const isTimeout=isTimeoutError(errorEvent);\n    const errorAction=errorEvent.errorAction;\n    const {\n      action,\n      retryCount=0,\n      retryConfig\n    }=errorAction||{};\n    const retry = !!errorAction&&!!retryConfig&&(action===NetworkErrorAction.RetryRequest||!errorAction.resolved&&action===NetworkErrorAction.SendAlternateToPenaltyBox);\n    if(retry){\n      var _errorEvent$context;\n      this.requestScheduled=-1;\n      if(retryCount >=retryConfig.maxNumRetry){\n        return false;\n      }\n      if(isTimeout&&(_errorEvent$context=errorEvent.context)!=null&&_errorEvent$context.deliveryDirectives){\n        // The LL-HLS request already timed out so retry immediately\n        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after \"${errorDetails}\" without delivery-directives`);\n        this.loadPlaylist();\n      }else{\n        const delay=getRetryDelay(retryConfig, retryCount);\n        // Schedule level/track reload\n        this.timer=self.setTimeout(()=> this.loadPlaylist(), delay);\n        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after \"${errorDetails}\" in ${delay}ms`);\n      }\n      // `levelRetry=true` used to inform other controllers that a retry is happening\n      errorEvent.levelRetry=true;\n      errorAction.resolved=true;\n    }\n    return retry;\n  }\n}\n\n\n\nclass EWMA {\n  //  About half of the estimated value will be from the last |halfLife| samples by weight.\n  constructor(halfLife, estimate=0, weight=0){\n    this.halfLife=void 0;\n    this.alpha_=void 0;\n    this.estimate_=void 0;\n    this.totalWeight_=void 0;\n    this.halfLife=halfLife;\n    // Larger values of alpha expire historical data more slowly.\n    this.alpha_=halfLife ? Math.exp(Math.log(0.5) / halfLife):0;\n    this.estimate_=estimate;\n    this.totalWeight_=weight;\n  }\n  sample(weight, value){\n    const adjAlpha=Math.pow(this.alpha_, weight);\n    this.estimate_=value * (1 - adjAlpha) + adjAlpha * this.estimate_;\n    this.totalWeight_ +=weight;\n  }\n  getTotalWeight(){\n    return this.totalWeight_;\n  }\n  getEstimate(){\n    if(this.alpha_){\n      const zeroFactor=1 - Math.pow(this.alpha_, this.totalWeight_);\n      if(zeroFactor){\n        return this.estimate_ / zeroFactor;\n      }\n    }\n    return this.estimate_;\n  }\n}\n\n\n\nclass EwmaBandWidthEstimator {\n  constructor(slow, fast, defaultEstimate, defaultTTFB=100){\n    this.defaultEstimate_=void 0;\n    this.minWeight_=void 0;\n    this.minDelayMs_=void 0;\n    this.slow_=void 0;\n    this.fast_=void 0;\n    this.defaultTTFB_=void 0;\n    this.ttfb_=void 0;\n    this.defaultEstimate_=defaultEstimate;\n    this.minWeight_=0.001;\n    this.minDelayMs_=50;\n    this.slow_=new EWMA(slow);\n    this.fast_=new EWMA(fast);\n    this.defaultTTFB_=defaultTTFB;\n    this.ttfb_=new EWMA(slow);\n  }\n  update(slow, fast){\n    const {\n      slow_,\n      fast_,\n      ttfb_\n    }=this;\n    if(slow_.halfLife!==slow){\n      this.slow_=new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());\n    }\n    if(fast_.halfLife!==fast){\n      this.fast_=new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());\n    }\n    if(ttfb_.halfLife!==slow){\n      this.ttfb_=new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());\n    }\n  }\n  sample(durationMs, numBytes){\n    durationMs=Math.max(durationMs, this.minDelayMs_);\n    const numBits=8 * numBytes;\n    // weight is duration in seconds\n    const durationS=durationMs / 1000;\n    // value is bandwidth in bits/s\n    const bandwidthInBps=numBits / durationS;\n    this.fast_.sample(durationS, bandwidthInBps);\n    this.slow_.sample(durationS, bandwidthInBps);\n  }\n  sampleTTFB(ttfb){\n    // weight is frequency curve applied to TTFB in seconds\n    // (longer times have less weight with expected input under 1 second)\n    const seconds=ttfb / 1000;\n    const weight=Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);\n    this.ttfb_.sample(weight, Math.max(ttfb, 5));\n  }\n  canEstimate(){\n    return this.fast_.getTotalWeight() >=this.minWeight_;\n  }\n  getEstimate(){\n    if(this.canEstimate()){\n      // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));\n      // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate()));\n      // Take the minimum of these two estimates.  This should have the effect of\n      // adapting down quickly, but up more slowly.\n      return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());\n    }else{\n      return this.defaultEstimate_;\n    }\n  }\n  getEstimateTTFB(){\n    if(this.ttfb_.getTotalWeight() >=this.minWeight_){\n      return this.ttfb_.getEstimate();\n    }else{\n      return this.defaultTTFB_;\n    }\n  }\n  destroy(){}\n}\n\nconst SUPPORTED_INFO_DEFAULT={\n  supported: true,\n  configurations: [],\n  decodingInfoResults: [{\n    supported: true,\n    powerEfficient: true,\n    smooth: true\n  }]\n};\nconst SUPPORTED_INFO_CACHE={};\nfunction requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference){\n  // Only test support when configuration is exceeds minimum options\n  const audioGroups=level.audioCodec ? level.audioGroups:null;\n  const audioCodecPreference=audioPreference==null ? void 0:audioPreference.audioCodec;\n  const channelsPreference=audioPreference==null ? void 0:audioPreference.channels;\n  const maxChannels=channelsPreference ? parseInt(channelsPreference):audioCodecPreference ? Infinity:2;\n  let audioChannels=null;\n  if(audioGroups!=null&&audioGroups.length){\n    try {\n      if(audioGroups.length===1&&audioGroups[0]){\n        audioChannels=audioTracksByGroup.groups[audioGroups[0]].channels;\n      }else{\n        audioChannels=audioGroups.reduce((acc, groupId)=> {\n          if(groupId){\n            const audioTrackGroup=audioTracksByGroup.groups[groupId];\n            if(!audioTrackGroup){\n              throw new Error(`Audio track group ${groupId} not found`);\n            }\n            // Sum all channel key values\n            Object.keys(audioTrackGroup.channels).forEach(key=> {\n              acc[key]=(acc[key]||0) + audioTrackGroup.channels[key];\n            });\n          }\n          return acc;\n        }, {\n          2: 0\n        });\n      }\n    } catch (error){\n      return true;\n    }\n  }\n  return level.videoCodec!==undefined&&(level.width > 1920&&level.height > 1088||level.height > 1920&&level.width > 1088||level.frameRate > Math.max(currentFrameRate, 30)||level.videoRange!=='SDR'&&level.videoRange!==currentVideoRange||level.bitrate > Math.max(currentBw, 8e6))||!!audioChannels&&isFiniteNumber(maxChannels)&&Object.keys(audioChannels).some(channels=> parseInt(channels) > maxChannels);\n}\nfunction getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities){\n  const videoCodecs=level.videoCodec;\n  const audioCodecs=level.audioCodec;\n  if(!videoCodecs||!audioCodecs||!mediaCapabilities){\n    return Promise.resolve(SUPPORTED_INFO_DEFAULT);\n  }\n  const baseVideoConfiguration={\n    width: level.width,\n    height: level.height,\n    bitrate: Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate)),\n    // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.\n    framerate: level.frameRate||30\n  };\n  const videoRange=level.videoRange;\n  if(videoRange!=='SDR'){\n    baseVideoConfiguration.transferFunction=videoRange.toLowerCase();\n  }\n  const configurations=videoCodecs.split(',').map(videoCodec=> ({\n    type: 'media-source',\n    video: _objectSpread2(_objectSpread2({}, baseVideoConfiguration), {}, {\n      contentType: mimeTypeForCodec(videoCodec, 'video')\n    })\n  }));\n  if(audioCodecs&&level.audioGroups){\n    level.audioGroups.forEach(audioGroupId=> {\n      var _audioTracksByGroup$g;\n      if(!audioGroupId){\n        return;\n      }\n      (_audioTracksByGroup$g=audioTracksByGroup.groups[audioGroupId])==null ? void 0:_audioTracksByGroup$g.tracks.forEach(audioTrack=> {\n        if(audioTrack.groupId===audioGroupId){\n          const channels=audioTrack.channels||'';\n          const channelsNumber=parseFloat(channels);\n          if(isFiniteNumber(channelsNumber)&&channelsNumber > 2){\n            configurations.push.apply(configurations, audioCodecs.split(',').map(audioCodec=> ({\n              type: 'media-source',\n              audio: {\n                contentType: mimeTypeForCodec(audioCodec, 'audio'),\n                channels: '' + channelsNumber\n                // spatialRendering:\n                //   audioCodec==='ec-3'&&channels.indexOf('JOC'),\n              }\n            })));\n          }\n        }\n      });\n    });\n  }\n  return Promise.all(configurations.map(configuration=> {\n    // Cache MediaCapabilities promises\n    const decodingInfoKey=getMediaDecodingInfoKey(configuration);\n    return SUPPORTED_INFO_CACHE[decodingInfoKey]||(SUPPORTED_INFO_CACHE[decodingInfoKey]=mediaCapabilities.decodingInfo(configuration));\n  })).then(decodingInfoResults=> ({\n    supported: !decodingInfoResults.some(info=> !info.supported),\n    configurations,\n    decodingInfoResults\n  })).catch(error=> ({\n    supported: false,\n    configurations,\n    decodingInfoResults: [],\n    error\n  }));\n}\nfunction getMediaDecodingInfoKey(config){\n  const {\n    audio,\n    video\n  }=config;\n  const mediaConfig=video||audio;\n  if(mediaConfig){\n    const codec=mediaConfig.contentType.split('\"')[1];\n    if(video){\n      return `r${video.height}x${video.width}f${Math.ceil(video.framerate)}${video.transferFunction||'sd'}_${codec}_${Math.ceil(video.bitrate / 1e5)}`;\n    }\n    if(audio){\n      return `c${audio.channels}${audio.spatialRendering ? 's':'n'}_${codec}`;\n    }\n  }\n  return '';\n}\n\n\nfunction isHdrSupported(){\n  if(typeof matchMedia==='function'){\n    const mediaQueryList=matchMedia('(dynamic-range: high)');\n    const badQuery=matchMedia('bad query');\n    if(mediaQueryList.media!==badQuery.media){\n      return mediaQueryList.matches===true;\n    }\n  }\n  return false;\n}\n\n\nfunction getVideoSelectionOptions(currentVideoRange, videoPreference){\n  let preferHDR=false;\n  let allowedVideoRanges=[];\n  if(currentVideoRange){\n    preferHDR=currentVideoRange!=='SDR';\n    allowedVideoRanges=[currentVideoRange];\n  }\n  if(videoPreference){\n    allowedVideoRanges=videoPreference.allowedVideoRanges||VideoRangeValues.slice(0);\n    preferHDR=videoPreference.preferHDR!==undefined ? videoPreference.preferHDR:isHdrSupported();\n    if(preferHDR){\n      allowedVideoRanges=allowedVideoRanges.filter(range=> range!=='SDR');\n    }else{\n      allowedVideoRanges=['SDR'];\n    }\n  }\n  return {\n    preferHDR,\n    allowedVideoRanges\n  };\n}\n\nfunction getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference){\n  const codecSets=Object.keys(codecTiers);\n  const channelsPreference=audioPreference==null ? void 0:audioPreference.channels;\n  const audioCodecPreference=audioPreference==null ? void 0:audioPreference.audioCodec;\n  const preferStereo=channelsPreference&&parseInt(channelsPreference)===2;\n  // Use first level set to determine stereo, and minimum resolution and framerate\n  let hasStereo=true;\n  let hasCurrentVideoRange=false;\n  let minHeight=Infinity;\n  let minFramerate=Infinity;\n  let minBitrate=Infinity;\n  let selectedScore=0;\n  let videoRanges=[];\n  const {\n    preferHDR,\n    allowedVideoRanges\n  }=getVideoSelectionOptions(currentVideoRange, videoPreference);\n  for (let i=codecSets.length; i--;){\n    const tier=codecTiers[codecSets[i]];\n    hasStereo=tier.channels[2] > 0;\n    minHeight=Math.min(minHeight, tier.minHeight);\n    minFramerate=Math.min(minFramerate, tier.minFramerate);\n    minBitrate=Math.min(minBitrate, tier.minBitrate);\n    const matchingVideoRanges=allowedVideoRanges.filter(range=> tier.videoRanges[range] > 0);\n    if(matchingVideoRanges.length > 0){\n      hasCurrentVideoRange=true;\n      videoRanges=matchingVideoRanges;\n    }\n  }\n  minHeight=isFiniteNumber(minHeight) ? minHeight:0;\n  minFramerate=isFiniteNumber(minFramerate) ? minFramerate:0;\n  const maxHeight=Math.max(1080, minHeight);\n  const maxFramerate=Math.max(30, minFramerate);\n  minBitrate=isFiniteNumber(minBitrate) ? minBitrate:currentBw;\n  currentBw=Math.max(minBitrate, currentBw);\n  // If there are no variants with matching preference, set currentVideoRange to undefined\n  if(!hasCurrentVideoRange){\n    currentVideoRange=undefined;\n    videoRanges=[];\n  }\n  const codecSet=codecSets.reduce((selected, candidate)=> {\n    // Remove candiates which do not meet bitrate, default audio, stereo or channels preference, 1080p or lower, 30fps or lower, or SDR/HDR selection if present\n    const candidateTier=codecTiers[candidate];\n    if(candidate===selected){\n      return selected;\n    }\n    if(candidateTier.minBitrate > currentBw){\n      logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);\n      return selected;\n    }\n    if(!candidateTier.hasDefaultAudio){\n      logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);\n      return selected;\n    }\n    if(audioCodecPreference&&candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5!==0){\n      logStartCodecCandidateIgnored(candidate, `audio codec preference \"${audioCodecPreference}\" not found`);\n      return selected;\n    }\n    if(channelsPreference&&!preferStereo){\n      if(!candidateTier.channels[channelsPreference]){\n        logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);\n        return selected;\n      }\n    }else if((!audioCodecPreference||preferStereo)&&hasStereo&&candidateTier.channels['2']===0){\n      logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);\n      return selected;\n    }\n    if(candidateTier.minHeight > maxHeight){\n      logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);\n      return selected;\n    }\n    if(candidateTier.minFramerate > maxFramerate){\n      logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);\n      return selected;\n    }\n    if(!videoRanges.some(range=> candidateTier.videoRanges[range] > 0)){\n      logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);\n      return selected;\n    }\n    if(candidateTier.maxScore < selectedScore){\n      logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);\n      return selected;\n    }\n    // Remove candiates with less preferred codecs or more errors\n    if(selected&&(codecsSetSelectionPreferenceValue(candidate) >=codecsSetSelectionPreferenceValue(selected)||candidateTier.fragmentError > codecTiers[selected].fragmentError)){\n      return selected;\n    }\n    selectedScore=candidateTier.maxScore;\n    return candidate;\n  }, undefined);\n  return {\n    codecSet,\n    videoRanges,\n    preferHDR,\n    minFramerate,\n    minBitrate\n  };\n}\nfunction logStartCodecCandidateIgnored(codeSet, reason){\n  logger.log(`[abr] start candidates with \"${codeSet}\" ignored because ${reason}`);\n}\nfunction getAudioTracksByGroup(allAudioTracks){\n  return allAudioTracks.reduce((audioTracksByGroup, track)=> {\n    let trackGroup=audioTracksByGroup.groups[track.groupId];\n    if(!trackGroup){\n      trackGroup=audioTracksByGroup.groups[track.groupId]={\n        tracks: [],\n        channels: {\n          2: 0\n        },\n        hasDefault: false,\n        hasAutoSelect: false\n      };\n    }\n    trackGroup.tracks.push(track);\n    const channelsKey=track.channels||'2';\n    trackGroup.channels[channelsKey]=(trackGroup.channels[channelsKey]||0) + 1;\n    trackGroup.hasDefault=trackGroup.hasDefault||track.default;\n    trackGroup.hasAutoSelect=trackGroup.hasAutoSelect||track.autoselect;\n    if(trackGroup.hasDefault){\n      audioTracksByGroup.hasDefaultAudio=true;\n    }\n    if(trackGroup.hasAutoSelect){\n      audioTracksByGroup.hasAutoSelectAudio=true;\n    }\n    return audioTracksByGroup;\n  }, {\n    hasDefaultAudio: false,\n    hasAutoSelectAudio: false,\n    groups: {}\n  });\n}\nfunction getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel){\n  return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce((tiers, level)=> {\n    if(!level.codecSet){\n      return tiers;\n    }\n    const audioGroups=level.audioGroups;\n    let tier=tiers[level.codecSet];\n    if(!tier){\n      tiers[level.codecSet]=tier={\n        minBitrate: Infinity,\n        minHeight: Infinity,\n        minFramerate: Infinity,\n        maxScore: 0,\n        videoRanges: {\n          SDR: 0\n        },\n        channels: {\n          '2': 0\n        },\n        hasDefaultAudio: !audioGroups,\n        fragmentError: 0\n      };\n    }\n    tier.minBitrate=Math.min(tier.minBitrate, level.bitrate);\n    const lesserWidthOrHeight=Math.min(level.height, level.width);\n    tier.minHeight=Math.min(tier.minHeight, lesserWidthOrHeight);\n    tier.minFramerate=Math.min(tier.minFramerate, level.frameRate);\n    tier.maxScore=Math.max(tier.maxScore, level.score);\n    tier.fragmentError +=level.fragmentError;\n    tier.videoRanges[level.videoRange]=(tier.videoRanges[level.videoRange]||0) + 1;\n    if(audioGroups){\n      audioGroups.forEach(audioGroupId=> {\n        if(!audioGroupId){\n          return;\n        }\n        const audioGroup=audioTracksByGroup.groups[audioGroupId];\n        if(!audioGroup){\n          return;\n        }\n        // Default audio is any group with DEFAULT=YES, or if missing then any group with AUTOSELECT=YES, or all variants\n        tier.hasDefaultAudio=tier.hasDefaultAudio||audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault:audioGroup.hasAutoSelect||!audioTracksByGroup.hasDefaultAudio&&!audioTracksByGroup.hasAutoSelectAudio;\n        Object.keys(audioGroup.channels).forEach(channels=> {\n          tier.channels[channels]=(tier.channels[channels]||0) + audioGroup.channels[channels];\n        });\n      });\n    }\n    return tiers;\n  }, {});\n}\nfunction findMatchingOption(option, tracks, matchPredicate){\n  if('attrs' in option){\n    const index=tracks.indexOf(option);\n    if(index!==-1){\n      return index;\n    }\n  }\n  for (let i=0; i < tracks.length; i++){\n    const track=tracks[i];\n    if(matchesOption(option, track, matchPredicate)){\n      return i;\n    }\n  }\n  return -1;\n}\nfunction matchesOption(option, track, matchPredicate){\n  const {\n    groupId,\n    name,\n    lang,\n    assocLang,\n    characteristics,\n    default: isDefault\n  }=option;\n  const forced=option.forced;\n  return (groupId===undefined||track.groupId===groupId)&&(name===undefined||track.name===name)&&(lang===undefined||track.lang===lang)&&(lang===undefined||track.assocLang===assocLang)&&(isDefault===undefined||track.default===isDefault)&&(forced===undefined||track.forced===forced)&&(characteristics===undefined||characteristicsMatch(characteristics, track.characteristics))&&(matchPredicate===undefined||matchPredicate(option, track));\n}\nfunction characteristicsMatch(characteristicsA, characteristicsB=''){\n  const arrA=characteristicsA.split(',');\n  const arrB=characteristicsB.split(',');\n  // Expects each item to be unique:\n  return arrA.length===arrB.length&&!arrA.some(el=> arrB.indexOf(el)===-1);\n}\nfunction audioMatchPredicate(option, track){\n  const {\n    audioCodec,\n    channels\n  }=option;\n  return (audioCodec===undefined||(track.audioCodec||'').substring(0, 4)===audioCodec.substring(0, 4))&&(channels===undefined||channels===(track.channels||'2'));\n}\nfunction findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate){\n  const currentLevel=levels[searchIndex];\n  // Are there variants with same URI as current level?\n  // If so, find a match that does not require any level URI change\n  const variants=levels.reduce((variantMap, level, index)=> {\n    const uri=level.uri;\n    const renditions=variantMap[uri]||(variantMap[uri]=[]);\n    renditions.push(index);\n    return variantMap;\n  }, {});\n  const renditions=variants[currentLevel.uri];\n  if(renditions.length > 1){\n    searchIndex=Math.max.apply(Math, renditions);\n  }\n  // Find best match\n  const currentVideoRange=currentLevel.videoRange;\n  const currentFrameRate=currentLevel.frameRate;\n  const currentVideoCodec=currentLevel.codecSet.substring(0, 4);\n  const matchingVideo=searchDownAndUpList(levels, searchIndex, level=> {\n    if(level.videoRange!==currentVideoRange||level.frameRate!==currentFrameRate||level.codecSet.substring(0, 4)!==currentVideoCodec){\n      return false;\n    }\n    const audioGroups=level.audioGroups;\n    const tracks=allAudioTracks.filter(track=> !audioGroups||audioGroups.indexOf(track.groupId)!==-1);\n    return findMatchingOption(option, tracks, matchPredicate) > -1;\n  });\n  if(matchingVideo > -1){\n    return matchingVideo;\n  }\n  return searchDownAndUpList(levels, searchIndex, level=> {\n    const audioGroups=level.audioGroups;\n    const tracks=allAudioTracks.filter(track=> !audioGroups||audioGroups.indexOf(track.groupId)!==-1);\n    return findMatchingOption(option, tracks, matchPredicate) > -1;\n  });\n}\nfunction searchDownAndUpList(arr, searchIndex, predicate){\n  for (let i=searchIndex; i > -1; i--){\n    if(predicate(arr[i])){\n      return i;\n    }\n  }\n  for (let i=searchIndex + 1; i < arr.length; i++){\n    if(predicate(arr[i])){\n      return i;\n    }\n  }\n  return -1;\n}\n\nclass AbrController {\n  constructor(_hls){\n    this.hls=void 0;\n    this.lastLevelLoadSec=0;\n    this.lastLoadedFragLevel=-1;\n    this.firstSelection=-1;\n    this._nextAutoLevel=-1;\n    this.nextAutoLevelKey='';\n    this.audioTracksByGroup=null;\n    this.codecTiers=null;\n    this.timer=-1;\n    this.fragCurrent=null;\n    this.partCurrent=null;\n    this.bitrateTestDelay=0;\n    this.bwEstimator=void 0;\n    \n    this._abandonRulesCheck=()=> {\n      const {\n        fragCurrent: frag,\n        partCurrent: part,\n        hls\n      }=this;\n      const {\n        autoLevelEnabled,\n        media\n      }=hls;\n      if(!frag||!media){\n        return;\n      }\n      const now=performance.now();\n      const stats=part ? part.stats:frag.stats;\n      const duration=part ? part.duration:frag.duration;\n      const timeLoading=now - stats.loading.start;\n      const minAutoLevel=hls.minAutoLevel;\n      // If frag loading is aborted, complete, or from lowest level, stop timer and return\n      if(stats.aborted||stats.loaded&&stats.loaded===stats.total||frag.level <=minAutoLevel){\n        this.clearTimer();\n        // reset forced auto level value so that next level will be selected\n        this._nextAutoLevel=-1;\n        return;\n      }\n\n      // This check only runs if we're in ABR mode and actually playing\n      if(!autoLevelEnabled||media.paused||!media.playbackRate||!media.readyState){\n        return;\n      }\n      const bufferInfo=hls.mainForwardBufferInfo;\n      if(bufferInfo===null){\n        return;\n      }\n      const ttfbEstimate=this.bwEstimator.getEstimateTTFB();\n      const playbackRate=Math.abs(media.playbackRate);\n      // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed\n      if(timeLoading <=Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))){\n        return;\n      }\n\n      // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer\n      const bufferStarvationDelay=bufferInfo.len / playbackRate;\n      const ttfb=stats.loading.first ? stats.loading.first - stats.loading.start:-1;\n      const loadedFirstByte=stats.loaded&&ttfb > -1;\n      const bwEstimate=this.getBwEstimate();\n      const levels=hls.levels;\n      const level=levels[frag.level];\n      const expectedLen=stats.total||Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));\n      let timeStreaming=loadedFirstByte ? timeLoading - ttfb:timeLoading;\n      if(timeStreaming < 1&&loadedFirstByte){\n        timeStreaming=Math.min(timeLoading, stats.loaded * 8 / bwEstimate);\n      }\n      const loadRate=loadedFirstByte ? stats.loaded * 1000 / timeStreaming:0;\n      // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment\n      const fragLoadedDelay=loadRate ? (expectedLen - stats.loaded) / loadRate:expectedLen * 8 / bwEstimate + ttfbEstimate / 1000;\n      // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left\n      if(fragLoadedDelay <=bufferStarvationDelay){\n        return;\n      }\n      const bwe=loadRate ? loadRate * 8:bwEstimate;\n      let fragLevelNextLoadedDelay=Number.POSITIVE_INFINITY;\n      let nextLoadLevel;\n      // Iterate through lower level and try to find the largest one that avoids rebuffering\n      for (nextLoadLevel=frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--){\n        // compute time to load next fragment at lower level\n        // 8=bits per byte (bps/Bps)\n        const levelNextBitrate=levels[nextLoadLevel].maxBitrate;\n        fragLevelNextLoadedDelay=this.getTimeToLoadFrag(ttfbEstimate / 1000, bwe, duration * levelNextBitrate, !levels[nextLoadLevel].details);\n        if(fragLevelNextLoadedDelay < bufferStarvationDelay){\n          break;\n        }\n      }\n      // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing\n      // to load the current one\n      if(fragLevelNextLoadedDelay >=fragLoadedDelay){\n        return;\n      }\n\n      // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down\n      if(fragLevelNextLoadedDelay > duration * 10){\n        return;\n      }\n      hls.nextLoadLevel=hls.nextAutoLevel=nextLoadLevel;\n      if(loadedFirstByte){\n        // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time\n        this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);\n      }else{\n        // If there has been no loading progress, sample TTFB\n        this.bwEstimator.sampleTTFB(timeLoading);\n      }\n      const nextLoadLevelBitrate=levels[nextLoadLevel].maxBitrate;\n      if(this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate){\n        this.resetEstimator(nextLoadLevelBitrate);\n      }\n      this.clearTimer();\n      logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index:''} of level ${frag.level} is loading too slowly;\n      Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s\n      Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s\n      Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s\n      TTFB estimate: ${ttfb | 0} ms\n      Current BW estimate: ${isFiniteNumber(bwEstimate) ? bwEstimate | 0:'Unknown'} bps\n      New BW estimate: ${this.getBwEstimate() | 0} bps\n      Switching to level ${nextLoadLevel} @ ${nextLoadLevelBitrate | 0} bps`);\n      hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, {\n        frag,\n        part,\n        stats\n      });\n    };\n    this.hls=_hls;\n    this.bwEstimator=this.initEstimator();\n    this.registerListeners();\n  }\n  resetEstimator(abrEwmaDefaultEstimate){\n    if(abrEwmaDefaultEstimate){\n      logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);\n      this.hls.config.abrEwmaDefaultEstimate=abrEwmaDefaultEstimate;\n    }\n    this.firstSelection=-1;\n    this.bwEstimator=this.initEstimator();\n  }\n  initEstimator(){\n    const config=this.hls.config;\n    return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate);\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);\n    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);\n    hls.on(Events.ERROR, this.onError, this);\n  }\n  unregisterListeners(){\n    const {\n      hls\n    }=this;\n    if(!hls){\n      return;\n    }\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);\n    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);\n    hls.off(Events.ERROR, this.onError, this);\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.clearTimer();\n    // @ts-ignore\n    this.hls=this._abandonRulesCheck=null;\n    this.fragCurrent=this.partCurrent=null;\n  }\n  onManifestLoading(event, data){\n    this.lastLoadedFragLevel=-1;\n    this.firstSelection=-1;\n    this.lastLevelLoadSec=0;\n    this.fragCurrent=this.partCurrent=null;\n    this.onLevelsUpdated();\n    this.clearTimer();\n  }\n  onLevelsUpdated(){\n    if(this.lastLoadedFragLevel > -1&&this.fragCurrent){\n      this.lastLoadedFragLevel=this.fragCurrent.level;\n    }\n    this._nextAutoLevel=-1;\n    this.onMaxAutoLevelUpdated();\n    this.codecTiers=null;\n    this.audioTracksByGroup=null;\n  }\n  onMaxAutoLevelUpdated(){\n    this.firstSelection=-1;\n    this.nextAutoLevelKey='';\n  }\n  onFragLoading(event, data){\n    const frag=data.frag;\n    if(this.ignoreFragment(frag)){\n      return;\n    }\n    if(!frag.bitrateTest){\n      var _data$part;\n      this.fragCurrent=frag;\n      this.partCurrent=(_data$part=data.part)!=null ? _data$part:null;\n    }\n    this.clearTimer();\n    this.timer=self.setInterval(this._abandonRulesCheck, 100);\n  }\n  onLevelSwitching(event, data){\n    this.clearTimer();\n  }\n  onError(event, data){\n    if(data.fatal){\n      return;\n    }\n    switch (data.details){\n      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:\n      case ErrorDetails.BUFFER_APPEND_ERROR:\n        // Reset last loaded level so that a new selection can be made after calling recoverMediaError\n        this.lastLoadedFragLevel=-1;\n        this.firstSelection=-1;\n        break;\n      case ErrorDetails.FRAG_LOAD_TIMEOUT:\n        {\n          const frag=data.frag;\n          const {\n            fragCurrent,\n            partCurrent: part\n          }=this;\n          if(frag&&fragCurrent&&frag.sn===fragCurrent.sn&&frag.level===fragCurrent.level){\n            const now=performance.now();\n            const stats=part ? part.stats:frag.stats;\n            const timeLoading=now - stats.loading.start;\n            const ttfb=stats.loading.first ? stats.loading.first - stats.loading.start:-1;\n            const loadedFirstByte=stats.loaded&&ttfb > -1;\n            if(loadedFirstByte){\n              const ttfbEstimate=this.bwEstimator.getEstimateTTFB();\n              this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);\n            }else{\n              this.bwEstimator.sampleTTFB(timeLoading);\n            }\n          }\n          break;\n        }\n    }\n  }\n  getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch){\n    const fragLoadSec=timeToFirstByteSec + fragSizeBits / bandwidth;\n    const playlistLoadSec=isSwitch ? this.lastLevelLoadSec:0;\n    return fragLoadSec + playlistLoadSec;\n  }\n  onLevelLoaded(event, data){\n    const config=this.hls.config;\n    const {\n      loading\n    }=data.stats;\n    const timeLoadingMs=loading.end - loading.start;\n    if(isFiniteNumber(timeLoadingMs)){\n      this.lastLevelLoadSec=timeLoadingMs / 1000;\n    }\n    if(data.details.live){\n      this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive);\n    }else{\n      this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD);\n    }\n  }\n  onFragLoaded(event, {\n    frag,\n    part\n  }){\n    const stats=part ? part.stats:frag.stats;\n    if(frag.type===PlaylistLevelType.MAIN){\n      this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start);\n    }\n    if(this.ignoreFragment(frag)){\n      return;\n    }\n    // stop monitoring bw once frag loaded\n    this.clearTimer();\n    // reset forced auto level value so that next level will be selected\n    if(frag.level===this._nextAutoLevel){\n      this._nextAutoLevel=-1;\n    }\n    this.firstSelection=-1;\n\n    // compute level average bitrate\n    if(this.hls.config.abrMaxWithRealBitrate){\n      const duration=part ? part.duration:frag.duration;\n      const level=this.hls.levels[frag.level];\n      const loadedBytes=(level.loaded ? level.loaded.bytes:0) + stats.loaded;\n      const loadedDuration=(level.loaded ? level.loaded.duration:0) + duration;\n      level.loaded={\n        bytes: loadedBytes,\n        duration: loadedDuration\n      };\n      level.realBitrate=Math.round(8 * loadedBytes / loadedDuration);\n    }\n    if(frag.bitrateTest){\n      const fragBufferedData={\n        stats,\n        frag,\n        part,\n        id: frag.type\n      };\n      this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData);\n      frag.bitrateTest=false;\n    }else{\n      // store level id after successful fragment load for playback\n      this.lastLoadedFragLevel=frag.level;\n    }\n  }\n  onFragBuffered(event, data){\n    const {\n      frag,\n      part\n    }=data;\n    const stats=part!=null&&part.stats.loaded ? part.stats:frag.stats;\n    if(stats.aborted){\n      return;\n    }\n    if(this.ignoreFragment(frag)){\n      return;\n    }\n    // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing;\n    // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch\n    // is used. If we used buffering in that case, our BW estimate sample will be very large.\n    const processingMs=stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB());\n    this.bwEstimator.sample(processingMs, stats.loaded);\n    stats.bwEstimate=this.getBwEstimate();\n    if(frag.bitrateTest){\n      this.bitrateTestDelay=processingMs / 1000;\n    }else{\n      this.bitrateTestDelay=0;\n    }\n  }\n  ignoreFragment(frag){\n    // Only count non-alt-audio frags which were actually buffered in our BW calculations\n    return frag.type!==PlaylistLevelType.MAIN||frag.sn==='initSegment';\n  }\n  clearTimer(){\n    if(this.timer > -1){\n      self.clearInterval(this.timer);\n      this.timer=-1;\n    }\n  }\n  get firstAutoLevel(){\n    const {\n      maxAutoLevel,\n      minAutoLevel\n    }=this.hls;\n    const bwEstimate=this.getBwEstimate();\n    const maxStartDelay=this.hls.config.maxStarvationDelay;\n    const abrAutoLevel=this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1);\n    if(abrAutoLevel > -1){\n      return abrAutoLevel;\n    }\n    const firstLevel=this.hls.firstLevel;\n    const clamped=Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);\n    logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);\n    return clamped;\n  }\n  get forcedAutoLevel(){\n    if(this.nextAutoLevelKey){\n      return -1;\n    }\n    return this._nextAutoLevel;\n  }\n\n  // return next auto level\n  get nextAutoLevel(){\n    const forcedAutoLevel=this.forcedAutoLevel;\n    const bwEstimator=this.bwEstimator;\n    const useEstimate=bwEstimator.canEstimate();\n    const loadedFirstFrag=this.lastLoadedFragLevel > -1;\n    // in case next auto level has been forced, and bw not available or not reliable, return forced value\n    if(forcedAutoLevel!==-1&&(!useEstimate||!loadedFirstFrag||this.nextAutoLevelKey===this.getAutoLevelKey())){\n      return forcedAutoLevel;\n    }\n\n    // compute next level using ABR logic\n    const nextABRAutoLevel=useEstimate&&loadedFirstFrag ? this.getNextABRAutoLevel():this.firstAutoLevel;\n\n    // use forced auto level while it hasn't errored more than ABR selection\n    if(forcedAutoLevel!==-1){\n      const levels=this.hls.levels;\n      if(levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel)&&levels[forcedAutoLevel].loadError <=levels[nextABRAutoLevel].loadError){\n        return forcedAutoLevel;\n      }\n    }\n\n    // save result until state has changed\n    this._nextAutoLevel=nextABRAutoLevel;\n    this.nextAutoLevelKey=this.getAutoLevelKey();\n    return nextABRAutoLevel;\n  }\n  getAutoLevelKey(){\n    return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;\n  }\n  getNextABRAutoLevel(){\n    const {\n      fragCurrent,\n      partCurrent,\n      hls\n    }=this;\n    const {\n      maxAutoLevel,\n      config,\n      minAutoLevel\n    }=hls;\n    const currentFragDuration=partCurrent ? partCurrent.duration:fragCurrent ? fragCurrent.duration:0;\n    const avgbw=this.getBwEstimate();\n    // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.\n    const bufferStarvationDelay=this.getStarvationDelay();\n    let bwFactor=config.abrBandWidthFactor;\n    let bwUpFactor=config.abrBandWidthUpFactor;\n\n    // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all\n    if(bufferStarvationDelay){\n      const _bestLevel=this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor);\n      if(_bestLevel >=0){\n        return _bestLevel;\n      }\n    }\n    // not possible to get rid of rebuffering... try to find level that will guarantee less than maxStarvationDelay of rebuffering\n    let maxStarvationDelay=currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay):config.maxStarvationDelay;\n    if(!bufferStarvationDelay){\n      // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test\n      const bitrateTestDelay=this.bitrateTestDelay;\n      if(bitrateTestDelay){\n        // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value\n        // max video loading delay used in  automatic start level selection :\n        // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level +\n        // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay```)\n        // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration\n        const maxLoadingDelay=currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay):config.maxLoadingDelay;\n        maxStarvationDelay=maxLoadingDelay - bitrateTestDelay;\n        logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);\n        // don't use conservative factor on bitrate test\n        bwFactor=bwUpFactor=1;\n      }\n    }\n    const bestLevel=this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);\n    logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected':'buffer is empty'}, optimal quality level ${bestLevel}`);\n    if(bestLevel > -1){\n      return bestLevel;\n    }\n    // If no matching level found, see if min auto level would be a better option\n    const minLevel=hls.levels[minAutoLevel];\n    const autoLevel=hls.levels[hls.loadLevel];\n    if((minLevel==null ? void 0:minLevel.bitrate) < (autoLevel==null ? void 0:autoLevel.bitrate)){\n      return minAutoLevel;\n    }\n    // or if bitrate is not lower, continue to use loadLevel\n    return hls.loadLevel;\n  }\n  getStarvationDelay(){\n    const hls=this.hls;\n    const media=hls.media;\n    if(!media){\n      return Infinity;\n    }\n    // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as\n    // if we're playing back at the normal rate.\n    const playbackRate=media&&media.playbackRate!==0 ? Math.abs(media.playbackRate):1.0;\n    const bufferInfo=hls.mainForwardBufferInfo;\n    return (bufferInfo ? bufferInfo.len:0) / playbackRate;\n  }\n  getBwEstimate(){\n    return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate():this.hls.config.abrEwmaDefaultEstimate;\n  }\n  findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor){\n    var _level$details;\n    const maxFetchDuration=bufferStarvationDelay + maxStarvationDelay;\n    const lastLoadedFragLevel=this.lastLoadedFragLevel;\n    const selectionBaseLevel=lastLoadedFragLevel===-1 ? this.hls.firstLevel:lastLoadedFragLevel;\n    const {\n      fragCurrent,\n      partCurrent\n    }=this;\n    const {\n      levels,\n      allAudioTracks,\n      loadLevel,\n      config\n    }=this.hls;\n    if(levels.length===1){\n      return 0;\n    }\n    const level=levels[selectionBaseLevel];\n    const live = !!(level!=null&&(_level$details=level.details)!=null&&_level$details.live);\n    const firstSelection=loadLevel===-1||lastLoadedFragLevel===-1;\n    let currentCodecSet;\n    let currentVideoRange='SDR';\n    let currentFrameRate=(level==null ? void 0:level.frameRate)||0;\n    const {\n      audioPreference,\n      videoPreference\n    }=config;\n    const audioTracksByGroup=this.audioTracksByGroup||(this.audioTracksByGroup=getAudioTracksByGroup(allAudioTracks));\n    if(firstSelection){\n      if(this.firstSelection!==-1){\n        return this.firstSelection;\n      }\n      const codecTiers=this.codecTiers||(this.codecTiers=getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel));\n      const startTier=getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference);\n      const {\n        codecSet,\n        videoRanges,\n        minFramerate,\n        minBitrate,\n        preferHDR\n      }=startTier;\n      currentCodecSet=codecSet;\n      currentVideoRange=preferHDR ? videoRanges[videoRanges.length - 1]:videoRanges[0];\n      currentFrameRate=minFramerate;\n      currentBw=Math.max(currentBw, minBitrate);\n      logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);\n    }else{\n      currentCodecSet=level==null ? void 0:level.codecSet;\n      currentVideoRange=level==null ? void 0:level.videoRange;\n    }\n    const currentFragDuration=partCurrent ? partCurrent.duration:fragCurrent ? fragCurrent.duration:0;\n    const ttfbEstimateSec=this.bwEstimator.getEstimateTTFB() / 1000;\n    const levelsSkipped=[];\n    for (let i=maxAutoLevel; i >=minAutoLevel; i--){\n      var _levelInfo$supportedR;\n      const levelInfo=levels[i];\n      const upSwitch=i > selectionBaseLevel;\n      if(!levelInfo){\n        continue;\n      }\n      if(config.useMediaCapabilities&&!levelInfo.supportedResult&&!levelInfo.supportedPromise){\n        const mediaCapabilities=navigator.mediaCapabilities;\n        if(typeof (mediaCapabilities==null ? void 0:mediaCapabilities.decodingInfo)==='function'&&requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)){\n          levelInfo.supportedPromise=getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities);\n          levelInfo.supportedPromise.then(decodingInfo=> {\n            if(!this.hls){\n              return;\n            }\n            levelInfo.supportedResult=decodingInfo;\n            const levels=this.hls.levels;\n            const index=levels.indexOf(levelInfo);\n            if(decodingInfo.error){\n              logger.warn(`[abr] MediaCapabilities decodingInfo error: \"${decodingInfo.error}\" for level ${index} ${JSON.stringify(decodingInfo)}`);\n            }else if(!decodingInfo.supported){\n              logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);\n              if(index > -1&&levels.length > 1){\n                logger.log(`[abr] Removing unsupported level ${index}`);\n                this.hls.removeLevel(index);\n              }\n            }\n          });\n        }else{\n          levelInfo.supportedResult=SUPPORTED_INFO_DEFAULT;\n        }\n      }\n\n      // skip candidates which change codec-family or video-range,\n      // and which decrease or increase frame-rate for up and down-switch respectfully\n      if(currentCodecSet&&levelInfo.codecSet!==currentCodecSet||currentVideoRange&&levelInfo.videoRange!==currentVideoRange||upSwitch&&currentFrameRate > levelInfo.frameRate||!upSwitch&&currentFrameRate > 0&&currentFrameRate < levelInfo.frameRate||levelInfo.supportedResult&&!((_levelInfo$supportedR=levelInfo.supportedResult.decodingInfoResults)!=null&&_levelInfo$supportedR[0].smooth)){\n        levelsSkipped.push(i);\n        continue;\n      }\n      const levelDetails=levelInfo.details;\n      const avgDuration=(partCurrent ? levelDetails==null ? void 0:levelDetails.partTarget:levelDetails==null ? void 0:levelDetails.averagetargetduration)||currentFragDuration;\n      let adjustedbw;\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      // 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(!upSwitch){\n        adjustedbw=bwFactor * currentBw;\n      }else{\n        adjustedbw=bwUpFactor * currentBw;\n      }\n\n      // Use average bitrate when starvation delay (buffer length) is gt or eq two segment durations and rebuffering is not expected (maxStarvationDelay > 0)\n      const bitrate=currentFragDuration&&bufferStarvationDelay >=currentFragDuration * 2&&maxStarvationDelay===0 ? levels[i].averageBitrate:levels[i].maxBitrate;\n      const fetchDuration=this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails===undefined);\n      const canSwitchWithinTolerance=\n      // if adjusted bw is greater than level bitrate AND\n      adjustedbw >=bitrate&&(\n      // no level change, or new level has no error history\n      i===lastLoadedFragLevel||levelInfo.loadError===0&&levelInfo.fragmentError===0)&&(\n      // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches\n      // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ...\n      // special case to support startLevel=-1 (bitrateTest) on live streams:in that case we should not exit loop so that findBestLevel will return -1\n      fetchDuration <=ttfbEstimateSec||!isFiniteNumber(fetchDuration)||live&&!this.bitrateTestDelay||fetchDuration < maxFetchDuration);\n      if(canSwitchWithinTolerance){\n        const forcedAutoLevel=this.forcedAutoLevel;\n        if(i!==loadLevel&&(forcedAutoLevel===-1||forcedAutoLevel!==loadLevel)){\n          if(levelsSkipped.length){\n            logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:\"${levels[levelsSkipped[0]].codecs}\" ${levels[levelsSkipped[0]].videoRange}; not compatible with \"${level.codecs}\" ${currentVideoRange}`);\n          }\n          logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);\n        }\n        if(firstSelection){\n          this.firstSelection=i;\n        }\n        // as we are looping from highest to lowest, this will return the best achievable quality level\n        return i;\n      }\n    }\n    // not enough time budget even with quality level 0 ... rebuffering might happen\n    return -1;\n  }\n  set nextAutoLevel(nextLevel){\n    const {\n      maxAutoLevel,\n      minAutoLevel\n    }=this.hls;\n    const value=Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel);\n    if(this._nextAutoLevel!==value){\n      this.nextAutoLevelKey='';\n      this._nextAutoLevel=value;\n    }\n  }\n}\n\n\nclass TaskLoop {\n  constructor(){\n    this._boundTick=void 0;\n    this._tickTimer=null;\n    this._tickInterval=null;\n    this._tickCallCount=0;\n    this._boundTick=this.tick.bind(this);\n  }\n  destroy(){\n    this.onHandlerDestroying();\n    this.onHandlerDestroyed();\n  }\n  onHandlerDestroying(){\n    // clear all timers before unregistering from event bus\n    this.clearNextTick();\n    this.clearInterval();\n  }\n  onHandlerDestroyed(){}\n  hasInterval(){\n    return !!this._tickInterval;\n  }\n  hasNextTick(){\n    return !!this._tickTimer;\n  }\n\n  \n  setInterval(millis){\n    if(!this._tickInterval){\n      this._tickCallCount=0;\n      this._tickInterval=self.setInterval(this._boundTick, millis);\n      return true;\n    }\n    return false;\n  }\n\n  \n  clearInterval(){\n    if(this._tickInterval){\n      self.clearInterval(this._tickInterval);\n      this._tickInterval=null;\n      return true;\n    }\n    return false;\n  }\n\n  \n  clearNextTick(){\n    if(this._tickTimer){\n      self.clearTimeout(this._tickTimer);\n      this._tickTimer=null;\n      return true;\n    }\n    return false;\n  }\n\n  \n  tick(){\n    this._tickCallCount++;\n    if(this._tickCallCount===1){\n      this.doTick();\n      // re-entrant call to tick from previous doTick call stack\n      // -> schedule a call on the next main loop iteration to process this task processing request\n      if(this._tickCallCount > 1){\n        // make sure only one timer exists at any time at max\n        this.tickImmediate();\n      }\n      this._tickCallCount=0;\n    }\n  }\n  tickImmediate(){\n    this.clearNextTick();\n    this._tickTimer=self.setTimeout(this._boundTick, 0);\n  }\n\n  \n  doTick(){}\n}\n\nvar FragmentState={\n  NOT_LOADED: \"NOT_LOADED\",\n  APPENDING: \"APPENDING\",\n  PARTIAL: \"PARTIAL\",\n  OK: \"OK\"\n};\nclass FragmentTracker {\n  constructor(hls){\n    this.activePartLists=Object.create(null);\n    this.endListFragments=Object.create(null);\n    this.fragments=Object.create(null);\n    this.timeRanges=Object.create(null);\n    this.bufferPadding=0.2;\n    this.hls=void 0;\n    this.hasGaps=false;\n    this.hls=hls;\n    this._registerListeners();\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);\n  }\n  destroy(){\n    this._unregisterListeners();\n    // @ts-ignore\n    this.fragments=\n    // @ts-ignore\n    this.activePartLists=\n    // @ts-ignore\n    this.endListFragments=this.timeRanges=null;\n  }\n\n  \n  getAppendedFrag(position, levelType){\n    const activeParts=this.activePartLists[levelType];\n    if(activeParts){\n      for (let i=activeParts.length; i--;){\n        const activePart=activeParts[i];\n        if(!activePart){\n          break;\n        }\n        const appendedPTS=activePart.end;\n        if(activePart.start <=position&&appendedPTS!==null&&position <=appendedPTS){\n          return activePart;\n        }\n      }\n    }\n    return this.getBufferedFrag(position, levelType);\n  }\n\n  \n  getBufferedFrag(position, levelType){\n    const {\n      fragments\n    }=this;\n    const keys=Object.keys(fragments);\n    for (let i=keys.length; i--;){\n      const fragmentEntity=fragments[keys[i]];\n      if((fragmentEntity==null ? void 0:fragmentEntity.body.type)===levelType&&fragmentEntity.buffered){\n        const frag=fragmentEntity.body;\n        if(frag.start <=position&&position <=frag.end){\n          return frag;\n        }\n      }\n    }\n    return null;\n  }\n\n  \n  detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart){\n    if(this.timeRanges){\n      this.timeRanges[elementaryStream]=timeRange;\n    }\n    // Check if any flagged fragments have been unloaded\n    // excluding anything newer than appendedPartSn\n    const appendedPartSn=(appendedPart==null ? void 0:appendedPart.fragment.sn)||-1;\n    Object.keys(this.fragments).forEach(key=> {\n      const fragmentEntity=this.fragments[key];\n      if(!fragmentEntity){\n        return;\n      }\n      if(appendedPartSn >=fragmentEntity.body.sn){\n        return;\n      }\n      if(!fragmentEntity.buffered&&!fragmentEntity.loaded){\n        if(fragmentEntity.body.type===playlistType){\n          this.removeFragment(fragmentEntity.body);\n        }\n        return;\n      }\n      const esData=fragmentEntity.range[elementaryStream];\n      if(!esData){\n        return;\n      }\n      esData.time.some(time=> {\n        const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);\n        if(isNotBuffered){\n          // Unregister partial fragment as it needs to load again to be reused\n          this.removeFragment(fragmentEntity.body);\n        }\n        return isNotBuffered;\n      });\n    });\n  }\n\n  \n  detectPartialFragments(data){\n    const timeRanges=this.timeRanges;\n    const {\n      frag,\n      part\n    }=data;\n    if(!timeRanges||frag.sn==='initSegment'){\n      return;\n    }\n    const fragKey=getFragmentKey(frag);\n    const fragmentEntity=this.fragments[fragKey];\n    if(!fragmentEntity||fragmentEntity.buffered&&frag.gap){\n      return;\n    }\n    const isFragHint = !frag.relurl;\n    Object.keys(timeRanges).forEach(elementaryStream=> {\n      const streamInfo=frag.elementaryStreams[elementaryStream];\n      if(!streamInfo){\n        return;\n      }\n      const timeRange=timeRanges[elementaryStream];\n      const partial=isFragHint||streamInfo.partial===true;\n      fragmentEntity.range[elementaryStream]=this.getBufferedTimes(frag, part, partial, timeRange);\n    });\n    fragmentEntity.loaded=null;\n    if(Object.keys(fragmentEntity.range).length){\n      fragmentEntity.buffered=true;\n      const endList=fragmentEntity.body.endList=frag.endList||fragmentEntity.body.endList;\n      if(endList){\n        this.endListFragments[fragmentEntity.body.type]=fragmentEntity;\n      }\n      if(!isPartial(fragmentEntity)){\n        // Remove older fragment parts from lookup after frag is tracked as buffered\n        this.removeParts(frag.sn - 1, frag.type);\n      }\n    }else{\n      // remove fragment if nothing was appended\n      this.removeFragment(fragmentEntity.body);\n    }\n  }\n  removeParts(snToKeep, levelType){\n    const activeParts=this.activePartLists[levelType];\n    if(!activeParts){\n      return;\n    }\n    this.activePartLists[levelType]=activeParts.filter(part=> part.fragment.sn >=snToKeep);\n  }\n  fragBuffered(frag, force){\n    const fragKey=getFragmentKey(frag);\n    let fragmentEntity=this.fragments[fragKey];\n    if(!fragmentEntity&&force){\n      fragmentEntity=this.fragments[fragKey]={\n        body: frag,\n        appendedPTS: null,\n        loaded: null,\n        buffered: false,\n        range: Object.create(null)\n      };\n      if(frag.gap){\n        this.hasGaps=true;\n      }\n    }\n    if(fragmentEntity){\n      fragmentEntity.loaded=null;\n      fragmentEntity.buffered=true;\n    }\n  }\n  getBufferedTimes(fragment, part, partial, timeRange){\n    const buffered={\n      time: [],\n      partial\n    };\n    const startPTS=fragment.start;\n    const endPTS=fragment.end;\n    const minEndPTS=fragment.minEndPTS||endPTS;\n    const maxStartPTS=fragment.maxStartPTS||startPTS;\n    for (let i=0; i < timeRange.length; i++){\n      const startTime=timeRange.start(i) - this.bufferPadding;\n      const endTime=timeRange.end(i) + this.bufferPadding;\n      if(maxStartPTS >=startTime&&minEndPTS <=endTime){\n        // Fragment is entirely contained in buffer\n        // No need to check the other timeRange times since it's completely playable\n        buffered.time.push({\n          startPTS: Math.max(startPTS, timeRange.start(i)),\n          endPTS: Math.min(endPTS, timeRange.end(i))\n        });\n        break;\n      }else if(startPTS < endTime&&endPTS > startTime){\n        const start=Math.max(startPTS, timeRange.start(i));\n        const end=Math.min(endPTS, timeRange.end(i));\n        if(end > start){\n          buffered.partial=true;\n          // Check for intersection with buffer\n          // Get playable sections of the fragment\n          buffered.time.push({\n            startPTS: start,\n            endPTS: end\n          });\n        }\n      }else if(endPTS <=startTime){\n        // No need to check the rest of the timeRange as it is in order\n        break;\n      }\n    }\n    return buffered;\n  }\n\n  \n  getPartialFragment(time){\n    let bestFragment=null;\n    let timePadding;\n    let startTime;\n    let endTime;\n    let bestOverlap=0;\n    const {\n      bufferPadding,\n      fragments\n    }=this;\n    Object.keys(fragments).forEach(key=> {\n      const fragmentEntity=fragments[key];\n      if(!fragmentEntity){\n        return;\n      }\n      if(isPartial(fragmentEntity)){\n        startTime=fragmentEntity.body.start - bufferPadding;\n        endTime=fragmentEntity.body.end + bufferPadding;\n        if(time >=startTime&&time <=endTime){\n          // Use the fragment that has the most padding from start and end time\n          timePadding=Math.min(time - startTime, endTime - time);\n          if(bestOverlap <=timePadding){\n            bestFragment=fragmentEntity.body;\n            bestOverlap=timePadding;\n          }\n        }\n      }\n    });\n    return bestFragment;\n  }\n  isEndListAppended(type){\n    const lastFragmentEntity=this.endListFragments[type];\n    return lastFragmentEntity!==undefined&&(lastFragmentEntity.buffered||isPartial(lastFragmentEntity));\n  }\n  getState(fragment){\n    const fragKey=getFragmentKey(fragment);\n    const fragmentEntity=this.fragments[fragKey];\n    if(fragmentEntity){\n      if(!fragmentEntity.buffered){\n        return FragmentState.APPENDING;\n      }else if(isPartial(fragmentEntity)){\n        return FragmentState.PARTIAL;\n      }else{\n        return FragmentState.OK;\n      }\n    }\n    return FragmentState.NOT_LOADED;\n  }\n  isTimeBuffered(startPTS, endPTS, timeRange){\n    let startTime;\n    let endTime;\n    for (let i=0; i < timeRange.length; i++){\n      startTime=timeRange.start(i) - this.bufferPadding;\n      endTime=timeRange.end(i) + this.bufferPadding;\n      if(startPTS >=startTime&&endPTS <=endTime){\n        return true;\n      }\n      if(endPTS <=startTime){\n        // No need to check the rest of the timeRange as it is in order\n        return false;\n      }\n    }\n    return false;\n  }\n  onFragLoaded(event, data){\n    const {\n      frag,\n      part\n    }=data;\n    // don't track initsegment (for which sn is not a number)\n    // don't track frags used for bitrateTest, they're irrelevant.\n    if(frag.sn==='initSegment'||frag.bitrateTest){\n      return;\n    }\n\n    // Fragment entity `loaded` FragLoadedData is null when loading parts\n    const loaded=part ? null:data;\n    const fragKey=getFragmentKey(frag);\n    this.fragments[fragKey]={\n      body: frag,\n      appendedPTS: null,\n      loaded,\n      buffered: false,\n      range: Object.create(null)\n    };\n  }\n  onBufferAppended(event, data){\n    const {\n      frag,\n      part,\n      timeRanges\n    }=data;\n    if(frag.sn==='initSegment'){\n      return;\n    }\n    const playlistType=frag.type;\n    if(part){\n      let activeParts=this.activePartLists[playlistType];\n      if(!activeParts){\n        this.activePartLists[playlistType]=activeParts=[];\n      }\n      activeParts.push(part);\n    }\n    // Store the latest timeRanges loaded in the buffer\n    this.timeRanges=timeRanges;\n    Object.keys(timeRanges).forEach(elementaryStream=> {\n      const timeRange=timeRanges[elementaryStream];\n      this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);\n    });\n  }\n  onFragBuffered(event, data){\n    this.detectPartialFragments(data);\n  }\n  hasFragment(fragment){\n    const fragKey=getFragmentKey(fragment);\n    return !!this.fragments[fragKey];\n  }\n  hasParts(type){\n    var _this$activePartLists;\n    return !!((_this$activePartLists=this.activePartLists[type])!=null&&_this$activePartLists.length);\n  }\n  removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly){\n    if(withGapOnly&&!this.hasGaps){\n      return;\n    }\n    Object.keys(this.fragments).forEach(key=> {\n      const fragmentEntity=this.fragments[key];\n      if(!fragmentEntity){\n        return;\n      }\n      const frag=fragmentEntity.body;\n      if(frag.type!==playlistType||withGapOnly&&!frag.gap){\n        return;\n      }\n      if(frag.start < end&&frag.end > start&&(fragmentEntity.buffered||unbufferedOnly)){\n        this.removeFragment(frag);\n      }\n    });\n  }\n  removeFragment(fragment){\n    const fragKey=getFragmentKey(fragment);\n    fragment.stats.loaded=0;\n    fragment.clearElementaryStreamInfo();\n    const activeParts=this.activePartLists[fragment.type];\n    if(activeParts){\n      const snToRemove=fragment.sn;\n      this.activePartLists[fragment.type]=activeParts.filter(part=> part.fragment.sn!==snToRemove);\n    }\n    delete this.fragments[fragKey];\n    if(fragment.endList){\n      delete this.endListFragments[fragment.type];\n    }\n  }\n  removeAllFragments(){\n    this.fragments=Object.create(null);\n    this.endListFragments=Object.create(null);\n    this.activePartLists=Object.create(null);\n    this.hasGaps=false;\n  }\n}\nfunction isPartial(fragmentEntity){\n  var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3;\n  return fragmentEntity.buffered&&(fragmentEntity.body.gap||((_fragmentEntity$range=fragmentEntity.range.video)==null ? void 0:_fragmentEntity$range.partial)||((_fragmentEntity$range2=fragmentEntity.range.audio)==null ? void 0:_fragmentEntity$range2.partial)||((_fragmentEntity$range3=fragmentEntity.range.audiovideo)==null ? void 0:_fragmentEntity$range3.partial));\n}\nfunction getFragmentKey(fragment){\n  return `${fragment.type}_${fragment.level}_${fragment.sn}`;\n}\n\n\n\nconst noopBuffered={\n  length: 0,\n  start: ()=> 0,\n  end: ()=> 0\n};\nclass BufferHelper {\n  \n  static isBuffered(media, position){\n    try {\n      if(media){\n        const buffered=BufferHelper.getBuffered(media);\n        for (let i=0; i < buffered.length; i++){\n          if(position >=buffered.start(i)&&position <=buffered.end(i)){\n            return true;\n          }\n        }\n      }\n    } catch (error){\n      // this is to catch\n      // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':\n      // This SourceBuffer has been removed from the parent media source\n    }\n    return false;\n  }\n  static bufferInfo(media, pos, maxHoleDuration){\n    try {\n      if(media){\n        const vbuffered=BufferHelper.getBuffered(media);\n        const buffered=[];\n        let i;\n        for (i=0; i < vbuffered.length; i++){\n          buffered.push({\n            start: vbuffered.start(i),\n            end: vbuffered.end(i)\n          });\n        }\n        return this.bufferedInfo(buffered, pos, maxHoleDuration);\n      }\n    } catch (error){\n      // this is to catch\n      // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':\n      // This SourceBuffer has been removed from the parent media source\n    }\n    return {\n      len: 0,\n      start: pos,\n      end: pos,\n      nextStart: undefined\n    };\n  }\n  static bufferedInfo(buffered, pos, maxHoleDuration){\n    pos=Math.max(0, pos);\n    // sort on buffer.start/smaller end (IE does not always return sorted buffered range)\n    buffered.sort(function (a, b){\n      const diff=a.start - b.start;\n      if(diff){\n        return diff;\n      }else{\n        return b.end - a.end;\n      }\n    });\n    let buffered2=[];\n    if(maxHoleDuration){\n      // there might be some small holes between buffer time range\n      // consider that holes smaller than maxHoleDuration are irrelevant and build another\n      // buffer time range representations that discards those holes\n      for (let i=0; i < buffered.length; i++){\n        const buf2len=buffered2.length;\n        if(buf2len){\n          const buf2end=buffered2[buf2len - 1].end;\n          // if small hole (value between 0 or maxHoleDuration) or overlapping (negative)\n          if(buffered[i].start - buf2end < maxHoleDuration){\n            // merge overlapping time ranges\n            // update lastRange.end only if smaller than item.end\n            // e.g.  [ 1, 15] with  [ 2,8]=> [ 1,15] (no need to modify lastRange.end)\n            // whereas [ 1, 8] with  [ 2,15]=> [ 1,15](lastRange should switch from [1,8] to [1,15])\n            if(buffered[i].end > buf2end){\n              buffered2[buf2len - 1].end=buffered[i].end;\n            }\n          }else{\n            // big hole\n            buffered2.push(buffered[i]);\n          }\n        }else{\n          // first value\n          buffered2.push(buffered[i]);\n        }\n      }\n    }else{\n      buffered2=buffered;\n    }\n    let bufferLen=0;\n\n    // bufferStartNext can possibly be undefined based on the conditional logic below\n    let bufferStartNext;\n\n    // bufferStart and bufferEnd are buffer boundaries around current video position\n    let bufferStart=pos;\n    let bufferEnd=pos;\n    for (let i=0; i < buffered2.length; i++){\n      const start=buffered2[i].start;\n      const end=buffered2[i].end;\n      // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i));\n      if(pos + maxHoleDuration >=start&&pos < end){\n        // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length\n        bufferStart=start;\n        bufferEnd=end;\n        bufferLen=bufferEnd - pos;\n      }else if(pos + maxHoleDuration < start){\n        bufferStartNext=start;\n        break;\n      }\n    }\n    return {\n      len: bufferLen,\n      start: bufferStart||0,\n      end: bufferEnd||0,\n      nextStart: bufferStartNext\n    };\n  }\n\n  \n  static getBuffered(media){\n    try {\n      return media.buffered;\n    } catch (e){\n      logger.log('failed to get media.buffered', e);\n      return noopBuffered;\n    }\n  }\n}\n\nclass ChunkMetadata {\n  constructor(level, sn, id, size=0, part=-1, partial=false){\n    this.level=void 0;\n    this.sn=void 0;\n    this.part=void 0;\n    this.id=void 0;\n    this.size=void 0;\n    this.partial=void 0;\n    this.transmuxing=getNewPerformanceTiming();\n    this.buffering={\n      audio: getNewPerformanceTiming(),\n      video: getNewPerformanceTiming(),\n      audiovideo: getNewPerformanceTiming()\n    };\n    this.level=level;\n    this.sn=sn;\n    this.id=id;\n    this.size=size;\n    this.part=part;\n    this.partial=partial;\n  }\n}\nfunction getNewPerformanceTiming(){\n  return {\n    start: 0,\n    executeStart: 0,\n    executeEnd: 0,\n    end: 0\n  };\n}\n\nfunction findFirstFragWithCC(fragments, cc){\n  for (let i=0, len=fragments.length; i < len; i++){\n    var _fragments$i;\n    if(((_fragments$i=fragments[i])==null ? void 0:_fragments$i.cc)===cc){\n      return fragments[i];\n    }\n  }\n  return null;\n}\nfunction shouldAlignOnDiscontinuities(lastFrag, switchDetails, details){\n  if(switchDetails){\n    if(details.endCC > details.startCC||lastFrag&&lastFrag.cc < details.startCC){\n      return true;\n    }\n  }\n  return false;\n}\n\n// Find the first frag in the previous level which matches the CC of the first frag of the new level\nfunction findDiscontinuousReferenceFrag(prevDetails, curDetails){\n  const prevFrags=prevDetails.fragments;\n  const curFrags=curDetails.fragments;\n  if(!curFrags.length||!prevFrags.length){\n    logger.log('No fragments to align');\n    return;\n  }\n  const prevStartFrag=findFirstFragWithCC(prevFrags, curFrags[0].cc);\n  if(!prevStartFrag||prevStartFrag&&!prevStartFrag.startPTS){\n    logger.log('No frag in previous level to align on');\n    return;\n  }\n  return prevStartFrag;\n}\nfunction adjustFragmentStart(frag, sliding){\n  if(frag){\n    const start=frag.start + sliding;\n    frag.start=frag.startPTS=start;\n    frag.endPTS=start + frag.duration;\n  }\n}\nfunction adjustSlidingStart(sliding, details){\n  // Update segments\n  const fragments=details.fragments;\n  for (let i=0, len=fragments.length; i < len; i++){\n    adjustFragmentStart(fragments[i], sliding);\n  }\n  // Update LL-HLS parts at the end of the playlist\n  if(details.fragmentHint){\n    adjustFragmentStart(details.fragmentHint, sliding);\n  }\n  details.alignedSliding=true;\n}\n\n\nfunction alignStream(lastFrag, switchDetails, details){\n  if(!switchDetails){\n    return;\n  }\n  alignDiscontinuities(lastFrag, details, switchDetails);\n  if(!details.alignedSliding&&switchDetails){\n    // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.\n    // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same\n    // discontinuity sequence.\n    alignMediaPlaylistByPDT(details, switchDetails);\n  }\n  if(!details.alignedSliding&&switchDetails&&!details.skippedSegments){\n    // Try to align on sn so that we pick a better start fragment.\n    // Do not perform this on playlists with delta updates as this is only to align levels on switch\n    // and adjustSliding only adjusts fragments after skippedSegments.\n    adjustSliding(switchDetails, details);\n  }\n}\n\n\nfunction alignDiscontinuities(lastFrag, details, switchDetails){\n  if(shouldAlignOnDiscontinuities(lastFrag, switchDetails, details)){\n    const referenceFrag=findDiscontinuousReferenceFrag(switchDetails, details);\n    if(referenceFrag&&isFiniteNumber(referenceFrag.start)){\n      logger.log(`Adjusting PTS using last level due to CC increase within current level ${details.url}`);\n      adjustSlidingStart(referenceFrag.start, details);\n    }\n  }\n}\n\n\nfunction alignMediaPlaylistByPDT(details, refDetails){\n  if(!details.hasProgramDateTime||!refDetails.hasProgramDateTime){\n    return;\n  }\n  const fragments=details.fragments;\n  const refFragments=refDetails.fragments;\n  if(!fragments.length||!refFragments.length){\n    return;\n  }\n\n  // Calculate a delta to apply to all fragments according to the delta in PDT times and start times\n  // of a fragment in the reference details, and a fragment in the target details of the same discontinuity.\n  // If a fragment of the same discontinuity was not found use the middle fragment of both.\n  let refFrag;\n  let frag;\n  const targetCC=Math.min(refDetails.endCC, details.endCC);\n  if(refDetails.startCC < targetCC&&details.startCC < targetCC){\n    refFrag=findFirstFragWithCC(refFragments, targetCC);\n    frag=findFirstFragWithCC(fragments, targetCC);\n  }\n  if(!refFrag||!frag){\n    refFrag=refFragments[Math.floor(refFragments.length / 2)];\n    frag=findFirstFragWithCC(fragments, refFrag.cc)||fragments[Math.floor(fragments.length / 2)];\n  }\n  const refPDT=refFrag.programDateTime;\n  const targetPDT=frag.programDateTime;\n  if(!refPDT||!targetPDT){\n    return;\n  }\n  const delta=(targetPDT - refPDT) / 1000 - (frag.start - refFrag.start);\n  adjustSlidingStart(delta, details);\n}\n\nconst MIN_CHUNK_SIZE=Math.pow(2, 17); // 128kb\n\nclass FragmentLoader {\n  constructor(config){\n    this.config=void 0;\n    this.loader=null;\n    this.partLoadTimeout=-1;\n    this.config=config;\n  }\n  destroy(){\n    if(this.loader){\n      this.loader.destroy();\n      this.loader=null;\n    }\n  }\n  abort(){\n    if(this.loader){\n      // Abort the loader for current fragment. Only one may load at any given time\n      this.loader.abort();\n    }\n  }\n  load(frag, onProgress){\n    const url=frag.url;\n    if(!url){\n      return Promise.reject(new LoadError({\n        type: ErrorTypes.NETWORK_ERROR,\n        details: ErrorDetails.FRAG_LOAD_ERROR,\n        fatal: false,\n        frag,\n        error: new Error(`Fragment does not have a ${url ? 'part list':'url'}`),\n        networkDetails: null\n      }));\n    }\n    this.abort();\n    const config=this.config;\n    const FragmentILoader=config.fLoader;\n    const DefaultILoader=config.loader;\n    return new Promise((resolve, reject)=> {\n      if(this.loader){\n        this.loader.destroy();\n      }\n      if(frag.gap){\n        if(frag.tagList.some(tags=> tags[0]==='GAP')){\n          reject(createGapLoadError(frag));\n          return;\n        }else{\n          // Reset temporary treatment as GAP tag\n          frag.gap=false;\n        }\n      }\n      const loader=this.loader=frag.loader=FragmentILoader ? new FragmentILoader(config):new DefaultILoader(config);\n      const loaderContext=createLoaderContext(frag);\n      const loadPolicy=getLoaderConfigWithoutReties(config.fragLoadPolicy.default);\n      const loaderConfig={\n        loadPolicy,\n        timeout: loadPolicy.maxLoadTimeMs,\n        maxRetry: 0,\n        retryDelay: 0,\n        maxRetryDelay: 0,\n        highWaterMark: frag.sn==='initSegment' ? Infinity:MIN_CHUNK_SIZE\n      };\n      // Assign frag stats to the loader's stats reference\n      frag.stats=loader.stats;\n      loader.load(loaderContext, loaderConfig, {\n        onSuccess: (response, stats, context, networkDetails)=> {\n          this.resetLoader(frag, loader);\n          let payload=response.data;\n          if(context.resetIV&&frag.decryptdata){\n            frag.decryptdata.iv=new Uint8Array(payload.slice(0, 16));\n            payload=payload.slice(16);\n          }\n          resolve({\n            frag,\n            part: null,\n            payload,\n            networkDetails\n          });\n        },\n        onError: (response, context, networkDetails, stats)=> {\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.FRAG_LOAD_ERROR,\n            fatal: false,\n            frag,\n            response: _objectSpread2({\n              url,\n              data: undefined\n            }, response),\n            error: new Error(`HTTP Error ${response.code} ${response.text}`),\n            networkDetails,\n            stats\n          }));\n        },\n        onAbort: (stats, context, networkDetails)=> {\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.INTERNAL_ABORTED,\n            fatal: false,\n            frag,\n            error: new Error('Aborted'),\n            networkDetails,\n            stats\n          }));\n        },\n        onTimeout: (stats, context, networkDetails)=> {\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.FRAG_LOAD_TIMEOUT,\n            fatal: false,\n            frag,\n            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),\n            networkDetails,\n            stats\n          }));\n        },\n        onProgress: (stats, context, data, networkDetails)=> {\n          if(onProgress){\n            onProgress({\n              frag,\n              part: null,\n              payload: data,\n              networkDetails\n            });\n          }\n        }\n      });\n    });\n  }\n  loadPart(frag, part, onProgress){\n    this.abort();\n    const config=this.config;\n    const FragmentILoader=config.fLoader;\n    const DefaultILoader=config.loader;\n    return new Promise((resolve, reject)=> {\n      if(this.loader){\n        this.loader.destroy();\n      }\n      if(frag.gap||part.gap){\n        reject(createGapLoadError(frag, part));\n        return;\n      }\n      const loader=this.loader=frag.loader=FragmentILoader ? new FragmentILoader(config):new DefaultILoader(config);\n      const loaderContext=createLoaderContext(frag, part);\n      // Should we define another load policy for parts?\n      const loadPolicy=getLoaderConfigWithoutReties(config.fragLoadPolicy.default);\n      const loaderConfig={\n        loadPolicy,\n        timeout: loadPolicy.maxLoadTimeMs,\n        maxRetry: 0,\n        retryDelay: 0,\n        maxRetryDelay: 0,\n        highWaterMark: MIN_CHUNK_SIZE\n      };\n      // Assign part stats to the loader's stats reference\n      part.stats=loader.stats;\n      loader.load(loaderContext, loaderConfig, {\n        onSuccess: (response, stats, context, networkDetails)=> {\n          this.resetLoader(frag, loader);\n          this.updateStatsFromPart(frag, part);\n          const partLoadedData={\n            frag,\n            part,\n            payload: response.data,\n            networkDetails\n          };\n          onProgress(partLoadedData);\n          resolve(partLoadedData);\n        },\n        onError: (response, context, networkDetails, stats)=> {\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.FRAG_LOAD_ERROR,\n            fatal: false,\n            frag,\n            part,\n            response: _objectSpread2({\n              url: loaderContext.url,\n              data: undefined\n            }, response),\n            error: new Error(`HTTP Error ${response.code} ${response.text}`),\n            networkDetails,\n            stats\n          }));\n        },\n        onAbort: (stats, context, networkDetails)=> {\n          frag.stats.aborted=part.stats.aborted;\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.INTERNAL_ABORTED,\n            fatal: false,\n            frag,\n            part,\n            error: new Error('Aborted'),\n            networkDetails,\n            stats\n          }));\n        },\n        onTimeout: (stats, context, networkDetails)=> {\n          this.resetLoader(frag, loader);\n          reject(new LoadError({\n            type: ErrorTypes.NETWORK_ERROR,\n            details: ErrorDetails.FRAG_LOAD_TIMEOUT,\n            fatal: false,\n            frag,\n            part,\n            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),\n            networkDetails,\n            stats\n          }));\n        }\n      });\n    });\n  }\n  updateStatsFromPart(frag, part){\n    const fragStats=frag.stats;\n    const partStats=part.stats;\n    const partTotal=partStats.total;\n    fragStats.loaded +=partStats.loaded;\n    if(partTotal){\n      const estTotalParts=Math.round(frag.duration / part.duration);\n      const estLoadedParts=Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts);\n      const estRemainingParts=estTotalParts - estLoadedParts;\n      const estRemainingBytes=estRemainingParts * Math.round(fragStats.loaded / estLoadedParts);\n      fragStats.total=fragStats.loaded + estRemainingBytes;\n    }else{\n      fragStats.total=Math.max(fragStats.loaded, fragStats.total);\n    }\n    const fragLoading=fragStats.loading;\n    const partLoading=partStats.loading;\n    if(fragLoading.start){\n      // add to fragment loader latency\n      fragLoading.first +=partLoading.first - partLoading.start;\n    }else{\n      fragLoading.start=partLoading.start;\n      fragLoading.first=partLoading.first;\n    }\n    fragLoading.end=partLoading.end;\n  }\n  resetLoader(frag, loader){\n    frag.loader=null;\n    if(this.loader===loader){\n      self.clearTimeout(this.partLoadTimeout);\n      this.loader=null;\n    }\n    loader.destroy();\n  }\n}\nfunction createLoaderContext(frag, part=null){\n  const segment=part||frag;\n  const loaderContext={\n    frag,\n    part,\n    responseType: 'arraybuffer',\n    url: segment.url,\n    headers: {},\n    rangeStart: 0,\n    rangeEnd: 0\n  };\n  const start=segment.byteRangeStartOffset;\n  const end=segment.byteRangeEndOffset;\n  if(isFiniteNumber(start)&&isFiniteNumber(end)){\n    var _frag$decryptdata;\n    let byteRangeStart=start;\n    let byteRangeEnd=end;\n    if(frag.sn==='initSegment'&&((_frag$decryptdata=frag.decryptdata)==null ? void 0:_frag$decryptdata.method)==='AES-128'){\n      // MAP segment encrypted with method 'AES-128', when served with HTTP Range,\n      // has the unencrypted size specified in the range.\n      // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6\n      const fragmentLen=end - start;\n      if(fragmentLen % 16){\n        byteRangeEnd=end + (16 - fragmentLen % 16);\n      }\n      if(start!==0){\n        loaderContext.resetIV=true;\n        byteRangeStart=start - 16;\n      }\n    }\n    loaderContext.rangeStart=byteRangeStart;\n    loaderContext.rangeEnd=byteRangeEnd;\n  }\n  return loaderContext;\n}\nfunction createGapLoadError(frag, part){\n  const error=new Error(`GAP ${frag.gap ? 'tag':'attribute'} found`);\n  const errorData={\n    type: ErrorTypes.MEDIA_ERROR,\n    details: ErrorDetails.FRAG_GAP,\n    fatal: false,\n    frag,\n    error,\n    networkDetails: null\n  };\n  if(part){\n    errorData.part=part;\n  }\n  (part ? part:frag).stats.aborted=true;\n  return new LoadError(errorData);\n}\nclass LoadError extends Error {\n  constructor(data){\n    super(data.error.message);\n    this.data=void 0;\n    this.data=data;\n  }\n}\n\nclass AESCrypto {\n  constructor(subtle, iv){\n    this.subtle=void 0;\n    this.aesIV=void 0;\n    this.subtle=subtle;\n    this.aesIV=iv;\n  }\n  decrypt(data, key){\n    return this.subtle.decrypt({\n      name: 'AES-CBC',\n      iv: this.aesIV\n    }, key, data);\n  }\n}\n\nclass FastAESKey {\n  constructor(subtle, key){\n    this.subtle=void 0;\n    this.key=void 0;\n    this.subtle=subtle;\n    this.key=key;\n  }\n  expandKey(){\n    return this.subtle.importKey('raw', this.key, {\n      name: 'AES-CBC'\n    }, false, ['encrypt', 'decrypt']);\n  }\n}\n\n// PKCS7\nfunction removePadding(array){\n  const outputBytes=array.byteLength;\n  const paddingBytes=outputBytes&&new DataView(array.buffer).getUint8(outputBytes - 1);\n  if(paddingBytes){\n    return sliceUint8(array, 0, outputBytes - paddingBytes);\n  }\n  return array;\n}\nclass AESDecryptor {\n  constructor(){\n    this.rcon=[0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];\n    this.subMix=[new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];\n    this.invSubMix=[new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];\n    this.sBox=new Uint32Array(256);\n    this.invSBox=new Uint32Array(256);\n    this.key=new Uint32Array(0);\n    this.ksRows=0;\n    this.keySize=0;\n    this.keySchedule=void 0;\n    this.invKeySchedule=void 0;\n    this.initTable();\n  }\n\n  // Using view.getUint32() also swaps the byte order.\n  uint8ArrayToUint32Array_(arrayBuffer){\n    const view=new DataView(arrayBuffer);\n    const newArray=new Uint32Array(4);\n    for (let i=0; i < 4; i++){\n      newArray[i]=view.getUint32(i * 4);\n    }\n    return newArray;\n  }\n  initTable(){\n    const sBox=this.sBox;\n    const invSBox=this.invSBox;\n    const subMix=this.subMix;\n    const subMix0=subMix[0];\n    const subMix1=subMix[1];\n    const subMix2=subMix[2];\n    const subMix3=subMix[3];\n    const invSubMix=this.invSubMix;\n    const invSubMix0=invSubMix[0];\n    const invSubMix1=invSubMix[1];\n    const invSubMix2=invSubMix[2];\n    const invSubMix3=invSubMix[3];\n    const d=new Uint32Array(256);\n    let x=0;\n    let xi=0;\n    let i=0;\n    for (i=0; i < 256; i++){\n      if(i < 128){\n        d[i]=i << 1;\n      }else{\n        d[i]=i << 1 ^ 0x11b;\n      }\n    }\n    for (i=0; i < 256; i++){\n      let sx=xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;\n      sx=sx >>> 8 ^ sx & 0xff ^ 0x63;\n      sBox[x]=sx;\n      invSBox[sx]=x;\n\n      // Compute multiplication\n      const x2=d[x];\n      const x4=d[x2];\n      const x8=d[x4];\n\n      // Compute sub/invSub bytes, mix columns tables\n      let t=d[sx] * 0x101 ^ sx * 0x1010100;\n      subMix0[x]=t << 24 | t >>> 8;\n      subMix1[x]=t << 16 | t >>> 16;\n      subMix2[x]=t << 8 | t >>> 24;\n      subMix3[x]=t;\n\n      // Compute inv sub bytes, inv mix columns tables\n      t=x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;\n      invSubMix0[sx]=t << 24 | t >>> 8;\n      invSubMix1[sx]=t << 16 | t >>> 16;\n      invSubMix2[sx]=t << 8 | t >>> 24;\n      invSubMix3[sx]=t;\n\n      // Compute next counter\n      if(!x){\n        x=xi=1;\n      }else{\n        x=x2 ^ d[d[d[x8 ^ x2]]];\n        xi ^=d[d[xi]];\n      }\n    }\n  }\n  expandKey(keyBuffer){\n    // convert keyBuffer to Uint32Array\n    const key=this.uint8ArrayToUint32Array_(keyBuffer);\n    let sameKey=true;\n    let offset=0;\n    while (offset < key.length&&sameKey){\n      sameKey=key[offset]===this.key[offset];\n      offset++;\n    }\n    if(sameKey){\n      return;\n    }\n    this.key=key;\n    const keySize=this.keySize=key.length;\n    if(keySize!==4&&keySize!==6&&keySize!==8){\n      throw new Error('Invalid aes key size=' + keySize);\n    }\n    const ksRows=this.ksRows=(keySize + 6 + 1) * 4;\n    let ksRow;\n    let invKsRow;\n    const keySchedule=this.keySchedule=new Uint32Array(ksRows);\n    const invKeySchedule=this.invKeySchedule=new Uint32Array(ksRows);\n    const sbox=this.sBox;\n    const rcon=this.rcon;\n    const invSubMix=this.invSubMix;\n    const invSubMix0=invSubMix[0];\n    const invSubMix1=invSubMix[1];\n    const invSubMix2=invSubMix[2];\n    const invSubMix3=invSubMix[3];\n    let prev;\n    let t;\n    for (ksRow=0; ksRow < ksRows; ksRow++){\n      if(ksRow < keySize){\n        prev=keySchedule[ksRow]=key[ksRow];\n        continue;\n      }\n      t=prev;\n      if(ksRow % keySize===0){\n        // Rot word\n        t=t << 8 | t >>> 24;\n\n        // Sub word\n        t=sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];\n\n        // Mix Rcon\n        t ^=rcon[ksRow / keySize | 0] << 24;\n      }else if(keySize > 6&&ksRow % keySize===4){\n        // Sub word\n        t=sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];\n      }\n      keySchedule[ksRow]=prev=(keySchedule[ksRow - keySize] ^ t) >>> 0;\n    }\n    for (invKsRow=0; invKsRow < ksRows; invKsRow++){\n      ksRow=ksRows - invKsRow;\n      if(invKsRow & 3){\n        t=keySchedule[ksRow];\n      }else{\n        t=keySchedule[ksRow - 4];\n      }\n      if(invKsRow < 4||ksRow <=4){\n        invKeySchedule[invKsRow]=t;\n      }else{\n        invKeySchedule[invKsRow]=invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]];\n      }\n      invKeySchedule[invKsRow]=invKeySchedule[invKsRow] >>> 0;\n    }\n  }\n\n  // Adding this as a method greatly improves performance.\n  networkToHostOrderSwap(word){\n    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;\n  }\n  decrypt(inputArrayBuffer, offset, aesIV){\n    const nRounds=this.keySize + 6;\n    const invKeySchedule=this.invKeySchedule;\n    const invSBOX=this.invSBox;\n    const invSubMix=this.invSubMix;\n    const invSubMix0=invSubMix[0];\n    const invSubMix1=invSubMix[1];\n    const invSubMix2=invSubMix[2];\n    const invSubMix3=invSubMix[3];\n    const initVector=this.uint8ArrayToUint32Array_(aesIV);\n    let initVector0=initVector[0];\n    let initVector1=initVector[1];\n    let initVector2=initVector[2];\n    let initVector3=initVector[3];\n    const inputInt32=new Int32Array(inputArrayBuffer);\n    const outputInt32=new Int32Array(inputInt32.length);\n    let t0, t1, t2, t3;\n    let s0, s1, s2, s3;\n    let inputWords0, inputWords1, inputWords2, inputWords3;\n    let ksRow, i;\n    const swapWord=this.networkToHostOrderSwap;\n    while (offset < inputInt32.length){\n      inputWords0=swapWord(inputInt32[offset]);\n      inputWords1=swapWord(inputInt32[offset + 1]);\n      inputWords2=swapWord(inputInt32[offset + 2]);\n      inputWords3=swapWord(inputInt32[offset + 3]);\n      s0=inputWords0 ^ invKeySchedule[0];\n      s1=inputWords3 ^ invKeySchedule[1];\n      s2=inputWords2 ^ invKeySchedule[2];\n      s3=inputWords1 ^ invKeySchedule[3];\n      ksRow=4;\n\n      // Iterate through the rounds of decryption\n      for (i=1; i < nRounds; i++){\n        t0=invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow];\n        t1=invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1];\n        t2=invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2];\n        t3=invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3];\n        // Update state\n        s0=t0;\n        s1=t1;\n        s2=t2;\n        s3=t3;\n        ksRow=ksRow + 4;\n      }\n\n      // Shift rows, sub bytes, add round key\n      t0=invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow];\n      t1=invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1];\n      t2=invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2];\n      t3=invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3];\n\n      // Write\n      outputInt32[offset]=swapWord(t0 ^ initVector0);\n      outputInt32[offset + 1]=swapWord(t3 ^ initVector1);\n      outputInt32[offset + 2]=swapWord(t2 ^ initVector2);\n      outputInt32[offset + 3]=swapWord(t1 ^ initVector3);\n\n      // reset initVector to last 4 unsigned int\n      initVector0=inputWords0;\n      initVector1=inputWords1;\n      initVector2=inputWords2;\n      initVector3=inputWords3;\n      offset=offset + 4;\n    }\n    return outputInt32.buffer;\n  }\n}\n\nconst CHUNK_SIZE=16; // 16 bytes, 128 bits\n\nclass Decrypter {\n  constructor(config, {\n    removePKCS7Padding=true\n  }={}){\n    this.logEnabled=true;\n    this.removePKCS7Padding=void 0;\n    this.subtle=null;\n    this.softwareDecrypter=null;\n    this.key=null;\n    this.fastAesKey=null;\n    this.remainderData=null;\n    this.currentIV=null;\n    this.currentResult=null;\n    this.useSoftware=void 0;\n    this.useSoftware=config.enableSoftwareAES;\n    this.removePKCS7Padding=removePKCS7Padding;\n    // built in decryptor expects PKCS7 padding\n    if(removePKCS7Padding){\n      try {\n        const browserCrypto=self.crypto;\n        if(browserCrypto){\n          this.subtle=browserCrypto.subtle||browserCrypto.webkitSubtle;\n        }\n      } catch (e){\n        \n      }\n    }\n    this.useSoftware = !this.subtle;\n  }\n  destroy(){\n    this.subtle=null;\n    this.softwareDecrypter=null;\n    this.key=null;\n    this.fastAesKey=null;\n    this.remainderData=null;\n    this.currentIV=null;\n    this.currentResult=null;\n  }\n  isSync(){\n    return this.useSoftware;\n  }\n  flush(){\n    const {\n      currentResult,\n      remainderData\n    }=this;\n    if(!currentResult||remainderData){\n      this.reset();\n      return null;\n    }\n    const data=new Uint8Array(currentResult);\n    this.reset();\n    if(this.removePKCS7Padding){\n      return removePadding(data);\n    }\n    return data;\n  }\n  reset(){\n    this.currentResult=null;\n    this.currentIV=null;\n    this.remainderData=null;\n    if(this.softwareDecrypter){\n      this.softwareDecrypter=null;\n    }\n  }\n  decrypt(data, key, iv){\n    if(this.useSoftware){\n      return new Promise((resolve, reject)=> {\n        this.softwareDecrypt(new Uint8Array(data), key, iv);\n        const decryptResult=this.flush();\n        if(decryptResult){\n          resolve(decryptResult.buffer);\n        }else{\n          reject(new Error('[softwareDecrypt] Failed to decrypt data'));\n        }\n      });\n    }\n    return this.webCryptoDecrypt(new Uint8Array(data), key, iv);\n  }\n\n  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached\n  // data is handled in the flush() call\n  softwareDecrypt(data, key, iv){\n    const {\n      currentIV,\n      currentResult,\n      remainderData\n    }=this;\n    this.logOnce('JS AES decrypt');\n    // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call\n    // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached\n    // the end on flush(), but by that time we have already received all bytes for the segment.\n    // Progressive decryption does not work with WebCrypto\n\n    if(remainderData){\n      data=appendUint8Array(remainderData, data);\n      this.remainderData=null;\n    }\n\n    // Byte length must be a multiple of 16 (AES-128=128 bit blocks=16 bytes)\n    const currentChunk=this.getValidChunk(data);\n    if(!currentChunk.length){\n      return null;\n    }\n    if(currentIV){\n      iv=currentIV;\n    }\n    let softwareDecrypter=this.softwareDecrypter;\n    if(!softwareDecrypter){\n      softwareDecrypter=this.softwareDecrypter=new AESDecryptor();\n    }\n    softwareDecrypter.expandKey(key);\n    const result=currentResult;\n    this.currentResult=softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);\n    this.currentIV=sliceUint8(currentChunk, -16).buffer;\n    if(!result){\n      return null;\n    }\n    return result;\n  }\n  webCryptoDecrypt(data, key, iv){\n    if(this.key!==key||!this.fastAesKey){\n      if(!this.subtle){\n        return Promise.resolve(this.onWebCryptoError(data, key, iv));\n      }\n      this.key=key;\n      this.fastAesKey=new FastAESKey(this.subtle, key);\n    }\n    return this.fastAesKey.expandKey().then(aesKey=> {\n      // decrypt using web crypto\n      if(!this.subtle){\n        return Promise.reject(new Error('web crypto not initialized'));\n      }\n      this.logOnce('WebCrypto AES decrypt');\n      const crypto=new AESCrypto(this.subtle, new Uint8Array(iv));\n      return crypto.decrypt(data.buffer, aesKey);\n    }).catch(err=> {\n      logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);\n      return this.onWebCryptoError(data, key, iv);\n    });\n  }\n  onWebCryptoError(data, key, iv){\n    this.useSoftware=true;\n    this.logEnabled=true;\n    this.softwareDecrypt(data, key, iv);\n    const decryptResult=this.flush();\n    if(decryptResult){\n      return decryptResult.buffer;\n    }\n    throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');\n  }\n  getValidChunk(data){\n    let currentChunk=data;\n    const splitPoint=data.length - data.length % CHUNK_SIZE;\n    if(splitPoint!==data.length){\n      currentChunk=sliceUint8(data, 0, splitPoint);\n      this.remainderData=sliceUint8(data, splitPoint);\n    }\n    return currentChunk;\n  }\n  logOnce(msg){\n    if(!this.logEnabled){\n      return;\n    }\n    logger.log(`[decrypter]: ${msg}`);\n    this.logEnabled=false;\n  }\n}\n\n\n\nconst TimeRanges={\n  toString: function (r){\n    let log='';\n    const len=r.length;\n    for (let i=0; i < len; i++){\n      log +=`[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`;\n    }\n    return log;\n  }\n};\n\nconst State={\n  STOPPED: 'STOPPED',\n  IDLE: 'IDLE',\n  KEY_LOADING: 'KEY_LOADING',\n  FRAG_LOADING: 'FRAG_LOADING',\n  FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',\n  WAITING_TRACK: 'WAITING_TRACK',\n  PARSING: 'PARSING',\n  PARSED: 'PARSED',\n  ENDED: 'ENDED',\n  ERROR: 'ERROR',\n  WAITING_INIT_PTS: 'WAITING_INIT_PTS',\n  WAITING_LEVEL: 'WAITING_LEVEL'\n};\nclass BaseStreamController extends TaskLoop {\n  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType){\n    super();\n    this.hls=void 0;\n    this.fragPrevious=null;\n    this.fragCurrent=null;\n    this.fragmentTracker=void 0;\n    this.transmuxer=null;\n    this._state=State.STOPPED;\n    this.playlistType=void 0;\n    this.media=null;\n    this.mediaBuffer=null;\n    this.config=void 0;\n    this.bitrateTest=false;\n    this.lastCurrentTime=0;\n    this.nextLoadPosition=0;\n    this.startPosition=0;\n    this.startTimeOffset=null;\n    this.loadedmetadata=false;\n    this.retryDate=0;\n    this.levels=null;\n    this.fragmentLoader=void 0;\n    this.keyLoader=void 0;\n    this.levelLastLoaded=null;\n    this.startFragRequested=false;\n    this.decrypter=void 0;\n    this.initPTS=[];\n    this.buffering=true;\n    this.onvseeking=null;\n    this.onvended=null;\n    this.logPrefix='';\n    this.log=void 0;\n    this.warn=void 0;\n    this.playlistType=playlistType;\n    this.logPrefix=logPrefix;\n    this.log=logger.log.bind(logger, `${logPrefix}:`);\n    this.warn=logger.warn.bind(logger, `${logPrefix}:`);\n    this.hls=hls;\n    this.fragmentLoader=new FragmentLoader(hls.config);\n    this.keyLoader=keyLoader;\n    this.fragmentTracker=fragmentTracker;\n    this.config=hls.config;\n    this.decrypter=new Decrypter(hls.config);\n    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n  }\n  doTick(){\n    this.onTickEnd();\n  }\n  onTickEnd(){}\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  startLoad(startPosition){}\n  stopLoad(){\n    this.fragmentLoader.abort();\n    this.keyLoader.abort(this.playlistType);\n    const frag=this.fragCurrent;\n    if(frag!=null&&frag.loader){\n      frag.abortRequests();\n      this.fragmentTracker.removeFragment(frag);\n    }\n    this.resetTransmuxer();\n    this.fragCurrent=null;\n    this.fragPrevious=null;\n    this.clearInterval();\n    this.clearNextTick();\n    this.state=State.STOPPED;\n  }\n  pauseBuffering(){\n    this.buffering=false;\n  }\n  resumeBuffering(){\n    this.buffering=true;\n  }\n  _streamEnded(bufferInfo, levelDetails){\n    // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,\n    // of nothing loading/loaded return false\n    if(levelDetails.live||bufferInfo.nextStart||!bufferInfo.end||!this.media){\n      return false;\n    }\n    const partList=levelDetails.partList;\n    // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS,\n    // check instead if the last part is buffered.\n    if(partList!=null&&partList.length){\n      const lastPart=partList[partList.length - 1];\n\n      // Checking the midpoint of the part for potential margin of error and related issues.\n      // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0)\n      // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream\n      // part mismatches for independent audio and video playlists/segments.\n      const lastPartBuffered=BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2);\n      return lastPartBuffered;\n    }\n    const playlistType=levelDetails.fragments[levelDetails.fragments.length - 1].type;\n    return this.fragmentTracker.isEndListAppended(playlistType);\n  }\n  getLevelDetails(){\n    if(this.levels&&this.levelLastLoaded!==null){\n      var _this$levelLastLoaded;\n      return (_this$levelLastLoaded=this.levelLastLoaded)==null ? void 0:_this$levelLastLoaded.details;\n    }\n  }\n  onMediaAttached(event, data){\n    const media=this.media=this.mediaBuffer=data.media;\n    this.onvseeking=this.onMediaSeeking.bind(this);\n    this.onvended=this.onMediaEnded.bind(this);\n    media.addEventListener('seeking', this.onvseeking);\n    media.addEventListener('ended', this.onvended);\n    const config=this.config;\n    if(this.levels&&config.autoStartLoad&&this.state===State.STOPPED){\n      this.startLoad(config.startPosition);\n    }\n  }\n  onMediaDetaching(){\n    const media=this.media;\n    if(media!=null&&media.ended){\n      this.log('MSE detaching and video ended, reset startPosition');\n      this.startPosition=this.lastCurrentTime=0;\n    }\n\n    // remove video listeners\n    if(media&&this.onvseeking&&this.onvended){\n      media.removeEventListener('seeking', this.onvseeking);\n      media.removeEventListener('ended', this.onvended);\n      this.onvseeking=this.onvended=null;\n    }\n    if(this.keyLoader){\n      this.keyLoader.detach();\n    }\n    this.media=this.mediaBuffer=null;\n    this.loadedmetadata=false;\n    this.fragmentTracker.removeAllFragments();\n    this.stopLoad();\n  }\n  onMediaSeeking(){\n    const {\n      config,\n      fragCurrent,\n      media,\n      mediaBuffer,\n      state\n    }=this;\n    const currentTime=media ? media.currentTime:0;\n    const bufferInfo=BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer:media, currentTime, config.maxBufferHole);\n    this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3):currentTime}, state: ${state}`);\n    if(this.state===State.ENDED){\n      this.resetLoadingState();\n    }else if(fragCurrent){\n      // Seeking while frag load is in progress\n      const tolerance=config.maxFragLookUpTolerance;\n      const fragStartOffset=fragCurrent.start - tolerance;\n      const fragEndOffset=fragCurrent.start + fragCurrent.duration + tolerance;\n      // if seeking out of buffered range or into new one\n      if(!bufferInfo.len||fragEndOffset < bufferInfo.start||fragStartOffset > bufferInfo.end){\n        const pastFragment=currentTime > fragEndOffset;\n        // if the seek position is outside the current fragment range\n        if(currentTime < fragStartOffset||pastFragment){\n          if(pastFragment&&fragCurrent.loader){\n            this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');\n            fragCurrent.abortRequests();\n            this.resetLoadingState();\n          }\n          this.fragPrevious=null;\n        }\n      }\n    }\n    if(media){\n      // Remove gap fragments\n      this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);\n      this.lastCurrentTime=currentTime;\n    }\n\n    // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target\n    if(!this.loadedmetadata&&!bufferInfo.len){\n      this.nextLoadPosition=this.startPosition=currentTime;\n    }\n\n    // Async tick to speed up processing\n    this.tickImmediate();\n  }\n  onMediaEnded(){\n    // reset startPosition and lastCurrentTime to restart playback @ stream beginning\n    this.startPosition=this.lastCurrentTime=0;\n  }\n  onManifestLoaded(event, data){\n    this.startTimeOffset=data.startTimeOffset;\n    this.initPTS=[];\n  }\n  onHandlerDestroying(){\n    this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    this.stopLoad();\n    super.onHandlerDestroying();\n    // @ts-ignore\n    this.hls=null;\n  }\n  onHandlerDestroyed(){\n    this.state=State.STOPPED;\n    if(this.fragmentLoader){\n      this.fragmentLoader.destroy();\n    }\n    if(this.keyLoader){\n      this.keyLoader.destroy();\n    }\n    if(this.decrypter){\n      this.decrypter.destroy();\n    }\n    this.hls=this.log=this.warn=this.decrypter=this.keyLoader=this.fragmentLoader=this.fragmentTracker=null;\n    super.onHandlerDestroyed();\n  }\n  loadFragment(frag, level, targetBufferTime){\n    this._loadFragForPlayback(frag, level, targetBufferTime);\n  }\n  _loadFragForPlayback(frag, level, targetBufferTime){\n    const progressCallback=data=> {\n      if(this.fragContextChanged(frag)){\n        this.warn(`Fragment ${frag.sn}${data.part ? ' p: ' + data.part.index:''} of level ${frag.level} was dropped during download.`);\n        this.fragmentTracker.removeFragment(frag);\n        return;\n      }\n      frag.stats.chunkCount++;\n      this._handleFragmentLoadProgress(data);\n    };\n    this._doFragLoad(frag, level, targetBufferTime, progressCallback).then(data=> {\n      if(!data){\n        // if we're here we probably needed to backtrack or are waiting for more parts\n        return;\n      }\n      const state=this.state;\n      if(this.fragContextChanged(frag)){\n        if(state===State.FRAG_LOADING||!this.fragCurrent&&state===State.PARSING){\n          this.fragmentTracker.removeFragment(frag);\n          this.state=State.IDLE;\n        }\n        return;\n      }\n      if('payload' in data){\n        this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`);\n        this.hls.trigger(Events.FRAG_LOADED, data);\n      }\n\n      // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback\n      this._handleFragmentLoadComplete(data);\n    }).catch(reason=> {\n      if(this.state===State.STOPPED||this.state===State.ERROR){\n        return;\n      }\n      this.warn(`Frag error: ${(reason==null ? void 0:reason.message)||reason}`);\n      this.resetFragmentLoading(frag);\n    });\n  }\n  clearTrackerIfNeeded(frag){\n    var _this$mediaBuffer;\n    const {\n      fragmentTracker\n    }=this;\n    const fragState=fragmentTracker.getState(frag);\n    if(fragState===FragmentState.APPENDING){\n      // Lower the max buffer length and try again\n      const playlistType=frag.type;\n      const bufferedInfo=this.getFwdBufferInfo(this.mediaBuffer, playlistType);\n      const minForwardBufferLength=Math.max(frag.duration, bufferedInfo ? bufferedInfo.len:this.config.maxBufferLength);\n      // If backtracking, always remove from the tracker without reducing max buffer length\n      const backtrackFragment=this.backtrackFragment;\n      const backtracked=backtrackFragment ? frag.sn - backtrackFragment.sn:0;\n      if(backtracked===1||this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)){\n        fragmentTracker.removeFragment(frag);\n      }\n    }else if(((_this$mediaBuffer=this.mediaBuffer)==null ? void 0:_this$mediaBuffer.buffered.length)===0){\n      // Stop gap for bad tracker / buffer flush behavior\n      fragmentTracker.removeAllFragments();\n    }else if(fragmentTracker.hasParts(frag.type)){\n      // In low latency mode, remove fragments for which only some parts were buffered\n      fragmentTracker.detectPartialFragments({\n        frag,\n        part: null,\n        stats: frag.stats,\n        id: frag.type\n      });\n      if(fragmentTracker.getState(frag)===FragmentState.PARTIAL){\n        fragmentTracker.removeFragment(frag);\n      }\n    }\n  }\n  checkLiveUpdate(details){\n    if(details.updated&&!details.live){\n      // Live stream ended, update fragment tracker\n      const lastFragment=details.fragments[details.fragments.length - 1];\n      this.fragmentTracker.detectPartialFragments({\n        frag: lastFragment,\n        part: null,\n        stats: lastFragment.stats,\n        id: lastFragment.type\n      });\n    }\n    if(!details.fragments[0]){\n      details.deltaUpdateFailed=true;\n    }\n  }\n  flushMainBuffer(startOffset, endOffset, type=null){\n    if(!(startOffset - endOffset)){\n      return;\n    }\n    // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise,\n    // passing a null type flushes both buffers\n    const flushScope={\n      startOffset,\n      endOffset,\n      type\n    };\n    this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);\n  }\n  _loadInitSegment(frag, level){\n    this._doFragLoad(frag, level).then(data=> {\n      if(!data||this.fragContextChanged(frag)||!this.levels){\n        throw new Error('init load aborted');\n      }\n      return data;\n    }).then(data=> {\n      const {\n        hls\n      }=this;\n      const {\n        payload\n      }=data;\n      const decryptData=frag.decryptdata;\n\n      // check to see if the payload needs to be decrypted\n      if(payload&&payload.byteLength > 0&&decryptData!=null&&decryptData.key&&decryptData.iv&&decryptData.method==='AES-128'){\n        const startTime=self.performance.now();\n        // decrypt init segment data\n        return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err=> {\n          hls.trigger(Events.ERROR, {\n            type: ErrorTypes.MEDIA_ERROR,\n            details: ErrorDetails.FRAG_DECRYPT_ERROR,\n            fatal: false,\n            error: err,\n            reason: err.message,\n            frag\n          });\n          throw err;\n        }).then(decryptedData=> {\n          const endTime=self.performance.now();\n          hls.trigger(Events.FRAG_DECRYPTED, {\n            frag,\n            payload: decryptedData,\n            stats: {\n              tstart: startTime,\n              tdecrypt: endTime\n            }\n          });\n          data.payload=decryptedData;\n          return this.completeInitSegmentLoad(data);\n        });\n      }\n      return this.completeInitSegmentLoad(data);\n    }).catch(reason=> {\n      if(this.state===State.STOPPED||this.state===State.ERROR){\n        return;\n      }\n      this.warn(reason);\n      this.resetFragmentLoading(frag);\n    });\n  }\n  completeInitSegmentLoad(data){\n    const {\n      levels\n    }=this;\n    if(!levels){\n      throw new Error('init load aborted, missing levels');\n    }\n    const stats=data.frag.stats;\n    this.state=State.IDLE;\n    data.frag.data=new Uint8Array(data.payload);\n    stats.parsing.start=stats.buffering.start=self.performance.now();\n    stats.parsing.end=stats.buffering.end=self.performance.now();\n    this.tick();\n  }\n  fragContextChanged(frag){\n    const {\n      fragCurrent\n    }=this;\n    return !frag||!fragCurrent||frag.sn!==fragCurrent.sn||frag.level!==fragCurrent.level;\n  }\n  fragBufferedComplete(frag, part){\n    var _frag$startPTS, _frag$endPTS, _this$fragCurrent, _this$fragPrevious;\n    const media=this.mediaBuffer ? this.mediaBuffer:this.media;\n    this.log(`Buffered ${frag.type} sn: ${frag.sn}${part ? ' part: ' + part.index:''} of ${this.playlistType===PlaylistLevelType.MAIN ? 'level':'track'} ${frag.level} (frag:[${((_frag$startPTS=frag.startPTS)!=null ? _frag$startPTS:NaN).toFixed(3)}-${((_frag$endPTS=frag.endPTS)!=null ? _frag$endPTS:NaN).toFixed(3)}] > buffer:${media ? TimeRanges.toString(BufferHelper.getBuffered(media)):'(detached)'})`);\n    if(frag.sn!=='initSegment'){\n      var _this$levels;\n      if(frag.type!==PlaylistLevelType.SUBTITLE){\n        const el=frag.elementaryStreams;\n        if(!Object.keys(el).some(type=> !!el[type])){\n          // empty segment\n          this.state=State.IDLE;\n          return;\n        }\n      }\n      const level=(_this$levels=this.levels)==null ? void 0:_this$levels[frag.level];\n      if(level!=null&&level.fragmentError){\n        this.log(`Resetting level fragment error count of ${level.fragmentError} on frag buffered`);\n        level.fragmentError=0;\n      }\n    }\n    this.state=State.IDLE;\n    if(!media){\n      return;\n    }\n    if(!this.loadedmetadata&&frag.type==PlaylistLevelType.MAIN&&media.buffered.length&&((_this$fragCurrent=this.fragCurrent)==null ? void 0:_this$fragCurrent.sn)===((_this$fragPrevious=this.fragPrevious)==null ? void 0:_this$fragPrevious.sn)){\n      this.loadedmetadata=true;\n      this.seekToStartPos();\n    }\n    this.tick();\n  }\n  seekToStartPos(){}\n  _handleFragmentLoadComplete(fragLoadedEndData){\n    const {\n      transmuxer\n    }=this;\n    if(!transmuxer){\n      return;\n    }\n    const {\n      frag,\n      part,\n      partsLoaded\n    }=fragLoadedEndData;\n    // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data\n    const complete = !partsLoaded||partsLoaded.length===0||partsLoaded.some(fragLoaded=> !fragLoaded);\n    const chunkMeta=new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index:-1, !complete);\n    transmuxer.flush(chunkMeta);\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  _handleFragmentLoadProgress(frag){}\n  _doFragLoad(frag, level, targetBufferTime=null, progressCallback){\n    var _frag$decryptdata;\n    const details=level==null ? void 0:level.details;\n    if(!this.levels||!details){\n      throw new Error(`frag load aborted, missing level${details ? '':' detail'}s`);\n    }\n    let keyLoadingPromise=null;\n    if(frag.encrypted&&!((_frag$decryptdata=frag.decryptdata)!=null&&_frag$decryptdata.key)){\n      this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix==='[stream-controller]' ? 'level':'track'} ${frag.level}`);\n      this.state=State.KEY_LOADING;\n      this.fragCurrent=frag;\n      keyLoadingPromise=this.keyLoader.load(frag).then(keyLoadedData=> {\n        if(!this.fragContextChanged(keyLoadedData.frag)){\n          this.hls.trigger(Events.KEY_LOADED, keyLoadedData);\n          if(this.state===State.KEY_LOADING){\n            this.state=State.IDLE;\n          }\n          return keyLoadedData;\n        }\n      });\n      this.hls.trigger(Events.KEY_LOADING, {\n        frag\n      });\n      if(this.fragCurrent===null){\n        keyLoadingPromise=Promise.reject(new Error(`frag load aborted, context changed in KEY_LOADING`));\n      }\n    }else if(!frag.encrypted&&details.encryptedFragments.length){\n      this.keyLoader.loadClear(frag, details.encryptedFragments);\n    }\n    targetBufferTime=Math.max(frag.start, targetBufferTime||0);\n    if(this.config.lowLatencyMode&&frag.sn!=='initSegment'){\n      const partList=details.partList;\n      if(partList&&progressCallback){\n        if(targetBufferTime > frag.end&&details.fragmentHint){\n          frag=details.fragmentHint;\n        }\n        const partIndex=this.getNextPart(partList, frag, targetBufferTime);\n        if(partIndex > -1){\n          const part=partList[partIndex];\n          this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix==='[stream-controller]' ? 'level':'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);\n          this.nextLoadPosition=part.start + part.duration;\n          this.state=State.FRAG_LOADING;\n          let _result;\n          if(keyLoadingPromise){\n            _result=keyLoadingPromise.then(keyLoadedData=> {\n              if(!keyLoadedData||this.fragContextChanged(keyLoadedData.frag)){\n                return null;\n              }\n              return this.doFragPartsLoad(frag, part, level, progressCallback);\n            }).catch(error=> this.handleFragLoadError(error));\n          }else{\n            _result=this.doFragPartsLoad(frag, part, level, progressCallback).catch(error=> this.handleFragLoadError(error));\n          }\n          this.hls.trigger(Events.FRAG_LOADING, {\n            frag,\n            part,\n            targetBufferTime\n          });\n          if(this.fragCurrent===null){\n            return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`));\n          }\n          return _result;\n        }else if(!frag.url||this.loadedEndOfParts(partList, targetBufferTime)){\n          // Fragment hint has no parts\n          return Promise.resolve(null);\n        }\n      }\n    }\n    this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ':''}${this.logPrefix==='[stream-controller]' ? 'level':'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);\n    // Don't update nextLoadPosition for fragments which are not buffered\n    if(isFiniteNumber(frag.sn)&&!this.bitrateTest){\n      this.nextLoadPosition=frag.start + frag.duration;\n    }\n    this.state=State.FRAG_LOADING;\n\n    // Load key before streaming fragment data\n    const dataOnProgress=this.config.progressive;\n    let result;\n    if(dataOnProgress&&keyLoadingPromise){\n      result=keyLoadingPromise.then(keyLoadedData=> {\n        if(!keyLoadedData||this.fragContextChanged(keyLoadedData==null ? void 0:keyLoadedData.frag)){\n          return null;\n        }\n        return this.fragmentLoader.load(frag, progressCallback);\n      }).catch(error=> this.handleFragLoadError(error));\n    }else{\n      // load unencrypted fragment data with progress event,\n      // or handle fragment result after key and fragment are finished loading\n      result=Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback:undefined), keyLoadingPromise]).then(([fragLoadedData])=> {\n        if(!dataOnProgress&&fragLoadedData&&progressCallback){\n          progressCallback(fragLoadedData);\n        }\n        return fragLoadedData;\n      }).catch(error=> this.handleFragLoadError(error));\n    }\n    this.hls.trigger(Events.FRAG_LOADING, {\n      frag,\n      targetBufferTime\n    });\n    if(this.fragCurrent===null){\n      return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`));\n    }\n    return result;\n  }\n  doFragPartsLoad(frag, fromPart, level, progressCallback){\n    return new Promise((resolve, reject)=> {\n      var _level$details;\n      const partsLoaded=[];\n      const initialPartList=(_level$details=level.details)==null ? void 0:_level$details.partList;\n      const loadPart=part=> {\n        this.fragmentLoader.loadPart(frag, part, progressCallback).then(partLoadedData=> {\n          partsLoaded[part.index]=partLoadedData;\n          const loadedPart=partLoadedData.part;\n          this.hls.trigger(Events.FRAG_LOADED, partLoadedData);\n          const nextPart=getPartWith(level, frag.sn, part.index + 1)||findPart(initialPartList, frag.sn, part.index + 1);\n          if(nextPart){\n            loadPart(nextPart);\n          }else{\n            return resolve({\n              frag,\n              part: loadedPart,\n              partsLoaded\n            });\n          }\n        }).catch(reject);\n      };\n      loadPart(fromPart);\n    });\n  }\n  handleFragLoadError(error){\n    if('data' in error){\n      const data=error.data;\n      if(error.data&&data.details===ErrorDetails.INTERNAL_ABORTED){\n        this.handleFragLoadAborted(data.frag, data.part);\n      }else{\n        this.hls.trigger(Events.ERROR, data);\n      }\n    }else{\n      this.hls.trigger(Events.ERROR, {\n        type: ErrorTypes.OTHER_ERROR,\n        details: ErrorDetails.INTERNAL_EXCEPTION,\n        err: error,\n        error,\n        fatal: true\n      });\n    }\n    return null;\n  }\n  _handleTransmuxerFlush(chunkMeta){\n    const context=this.getCurrentContext(chunkMeta);\n    if(!context||this.state!==State.PARSING){\n      if(!this.fragCurrent&&this.state!==State.STOPPED&&this.state!==State.ERROR){\n        this.state=State.IDLE;\n      }\n      return;\n    }\n    const {\n      frag,\n      part,\n      level\n    }=context;\n    const now=self.performance.now();\n    frag.stats.parsing.end=now;\n    if(part){\n      part.stats.parsing.end=now;\n    }\n    this.updateLevelTiming(frag, part, level, chunkMeta.partial);\n  }\n  getCurrentContext(chunkMeta){\n    const {\n      levels,\n      fragCurrent\n    }=this;\n    const {\n      level: levelIndex,\n      sn,\n      part: partIndex\n    }=chunkMeta;\n    if(!(levels!=null&&levels[levelIndex])){\n      this.warn(`Levels object was unset while buffering fragment ${sn} of level ${levelIndex}. The current chunk will not be buffered.`);\n      return null;\n    }\n    const level=levels[levelIndex];\n    const part=partIndex > -1 ? getPartWith(level, sn, partIndex):null;\n    const frag=part ? part.fragment:getFragmentWithSN(level, sn, fragCurrent);\n    if(!frag){\n      return null;\n    }\n    if(fragCurrent&&fragCurrent!==frag){\n      frag.stats=fragCurrent.stats;\n    }\n    return {\n      frag,\n      part,\n      level\n    };\n  }\n  bufferFragmentData(data, frag, part, chunkMeta, noBacktracking){\n    var _buffer;\n    if(!data||this.state!==State.PARSING){\n      return;\n    }\n    const {\n      data1,\n      data2\n    }=data;\n    let buffer=data1;\n    if(data1&&data2){\n      // Combine the moof + mdat so that we buffer with a single append\n      buffer=appendUint8Array(data1, data2);\n    }\n    if(!((_buffer=buffer)!=null&&_buffer.length)){\n      return;\n    }\n    const segment={\n      type: data.type,\n      frag,\n      part,\n      chunkMeta,\n      parent: frag.type,\n      data: buffer\n    };\n    this.hls.trigger(Events.BUFFER_APPENDING, segment);\n    if(data.dropped&&data.independent&&!part){\n      if(noBacktracking){\n        return;\n      }\n      // Clear buffer so that we reload previous segments sequentially if required\n      this.flushBufferGap(frag);\n    }\n  }\n  flushBufferGap(frag){\n    const media=this.media;\n    if(!media){\n      return;\n    }\n    // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed\n    if(!BufferHelper.isBuffered(media, media.currentTime)){\n      this.flushMainBuffer(0, frag.start);\n      return;\n    }\n    // Remove back-buffer without interrupting playback to allow back tracking\n    const currentTime=media.currentTime;\n    const bufferInfo=BufferHelper.bufferInfo(media, currentTime, 0);\n    const fragDuration=frag.duration;\n    const segmentFraction=Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25);\n    const start=Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction);\n    if(frag.start - start > segmentFraction){\n      this.flushMainBuffer(start, frag.start);\n    }\n  }\n  getFwdBufferInfo(bufferable, type){\n    const pos=this.getLoadPosition();\n    if(!isFiniteNumber(pos)){\n      return null;\n    }\n    return this.getFwdBufferInfoAtPos(bufferable, pos, type);\n  }\n  getFwdBufferInfoAtPos(bufferable, pos, type){\n    const {\n      config: {\n        maxBufferHole\n      }\n    }=this;\n    const bufferInfo=BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);\n    // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos\n    if(bufferInfo.len===0&&bufferInfo.nextStart!==undefined){\n      const bufferedFragAtPos=this.fragmentTracker.getBufferedFrag(pos, type);\n      if(bufferedFragAtPos&&bufferInfo.nextStart < bufferedFragAtPos.end){\n        return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));\n      }\n    }\n    return bufferInfo;\n  }\n  getMaxBufferLength(levelBitrate){\n    const {\n      config\n    }=this;\n    let maxBufLen;\n    if(levelBitrate){\n      maxBufLen=Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength);\n    }else{\n      maxBufLen=config.maxBufferLength;\n    }\n    return Math.min(maxBufLen, config.maxMaxBufferLength);\n  }\n  reduceMaxBufferLength(threshold, fragDuration){\n    const config=this.config;\n    const minLength=Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration);\n    const reducedLength=Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength);\n    if(reducedLength >=minLength){\n      // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...\n      config.maxMaxBufferLength=reducedLength;\n      this.warn(`Reduce max buffer length to ${reducedLength}s`);\n      return true;\n    }\n    return false;\n  }\n  getAppendedFrag(position, playlistType=PlaylistLevelType.MAIN){\n    const fragOrPart=this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN);\n    if(fragOrPart&&'fragment' in fragOrPart){\n      return fragOrPart.fragment;\n    }\n    return fragOrPart;\n  }\n  getNextFragment(pos, levelDetails){\n    const fragments=levelDetails.fragments;\n    const fragLen=fragments.length;\n    if(!fragLen){\n      return null;\n    }\n\n    // find fragment index, contiguous with end of buffer position\n    const {\n      config\n    }=this;\n    const start=fragments[0].start;\n    let frag;\n    if(levelDetails.live){\n      const initialLiveManifestSize=config.initialLiveManifestSize;\n      if(fragLen < initialLiveManifestSize){\n        this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`);\n        return null;\n      }\n      // The real fragment start times for a live stream are only known after the PTS range for that level is known.\n      // In order to discover the range, we load the best matching fragment for that level and demux it.\n      // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that\n      // we get the fragment matching that start time\n      if(!levelDetails.PTSKnown&&!this.startFragRequested&&this.startPosition===-1||pos < start){\n        frag=this.getInitialLiveFragment(levelDetails, fragments);\n        this.startPosition=this.nextLoadPosition=frag ? this.hls.liveSyncPosition||frag.start:pos;\n      }\n    }else if(pos <=start){\n      // VoD playlist: if loadPosition before start of playlist, load first fragment\n      frag=fragments[0];\n    }\n\n    // If we haven't run into any special cases already, just load the fragment most closely matching the requested position\n    if(!frag){\n      const end=config.lowLatencyMode ? levelDetails.partEnd:levelDetails.fragmentEnd;\n      frag=this.getFragmentAtPosition(pos, end, levelDetails);\n    }\n    return this.mapToInitFragWhenRequired(frag);\n  }\n  isLoopLoading(frag, targetBufferTime){\n    const trackerState=this.fragmentTracker.getState(frag);\n    return (trackerState===FragmentState.OK||trackerState===FragmentState.PARTIAL&&!!frag.gap)&&this.nextLoadPosition > targetBufferTime;\n  }\n  getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen){\n    const gapStart=frag.gap;\n    const nextFragment=this.getNextFragment(this.nextLoadPosition, levelDetails);\n    if(nextFragment===null){\n      return nextFragment;\n    }\n    frag=nextFragment;\n    if(gapStart&&frag&&!frag.gap&&bufferInfo.nextStart){\n      // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length\n      const nextbufferInfo=this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer:this.media, bufferInfo.nextStart, playlistType);\n      if(nextbufferInfo!==null&&bufferInfo.len + nextbufferInfo.len >=maxBufLen){\n        // Returning here might result in not finding an audio and video candiate to skip to\n        this.log(`buffer full after gaps in \"${playlistType}\" playlist starting at sn: ${frag.sn}`);\n        return null;\n      }\n    }\n    return frag;\n  }\n  mapToInitFragWhenRequired(frag){\n    // If an initSegment is present, it must be buffered first\n    if(frag!=null&&frag.initSegment&&!(frag!=null&&frag.initSegment.data)&&!this.bitrateTest){\n      return frag.initSegment;\n    }\n    return frag;\n  }\n  getNextPart(partList, frag, targetBufferTime){\n    let nextPart=-1;\n    let contiguous=false;\n    let independentAttrOmitted=true;\n    for (let i=0, len=partList.length; i < len; i++){\n      const part=partList[i];\n      independentAttrOmitted=independentAttrOmitted&&!part.independent;\n      if(nextPart > -1&&targetBufferTime < part.start){\n        break;\n      }\n      const loaded=part.loaded;\n      if(loaded){\n        nextPart=-1;\n      }else if((contiguous||part.independent||independentAttrOmitted)&&part.fragment===frag){\n        nextPart=i;\n      }\n      contiguous=loaded;\n    }\n    return nextPart;\n  }\n  loadedEndOfParts(partList, targetBufferTime){\n    const lastPart=partList[partList.length - 1];\n    return lastPart&&targetBufferTime > lastPart.start&&lastPart.loaded;\n  }\n\n  \n  getInitialLiveFragment(levelDetails, fragments){\n    const fragPrevious=this.fragPrevious;\n    let frag=null;\n    if(fragPrevious){\n      if(levelDetails.hasProgramDateTime){\n        // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding\n        this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`);\n        frag=findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance);\n      }\n      if(!frag){\n        // SN does not need to be accurate between renditions, but depending on the packaging it may be so.\n        const targetSN=fragPrevious.sn + 1;\n        if(targetSN >=levelDetails.startSN&&targetSN <=levelDetails.endSN){\n          const fragNext=fragments[targetSN - levelDetails.startSN];\n          // Ensure that we're staying within the continuity range, since PTS resets upon a new range\n          if(fragPrevious.cc===fragNext.cc){\n            frag=fragNext;\n            this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`);\n          }\n        }\n        // It's important to stay within the continuity range if available; otherwise the fragments in the playlist\n        // will have the wrong start times\n        if(!frag){\n          frag=findFragWithCC(fragments, fragPrevious.cc);\n          if(frag){\n            this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`);\n          }\n        }\n      }\n    }else{\n      // Find a new start fragment when fragPrevious is null\n      const liveStart=this.hls.liveSyncPosition;\n      if(liveStart!==null){\n        frag=this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd:levelDetails.edge, levelDetails);\n      }\n    }\n    return frag;\n  }\n\n  \n  getFragmentAtPosition(bufferEnd, end, levelDetails){\n    const {\n      config\n    }=this;\n    let {\n      fragPrevious\n    }=this;\n    let {\n      fragments,\n      endSN\n    }=levelDetails;\n    const {\n      fragmentHint\n    }=levelDetails;\n    const {\n      maxFragLookUpTolerance\n    }=config;\n    const partList=levelDetails.partList;\n    const loadingParts = !!(config.lowLatencyMode&&partList!=null&&partList.length&&fragmentHint);\n    if(loadingParts&&fragmentHint&&!this.bitrateTest){\n      // Include incomplete fragment with parts at end\n      fragments=fragments.concat(fragmentHint);\n      endSN=fragmentHint.sn;\n    }\n    let frag;\n    if(bufferEnd < end){\n      const lookupTolerance=bufferEnd > end - maxFragLookUpTolerance ? 0:maxFragLookUpTolerance;\n      // Remove the tolerance if it would put the bufferEnd past the actual end of stream\n      // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)\n      frag=findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance);\n    }else{\n      // reach end of playlist\n      frag=fragments[fragments.length - 1];\n    }\n    if(frag){\n      const curSNIdx=frag.sn - levelDetails.startSN;\n      // Move fragPrevious forward to support forcing the next fragment to load\n      // when the buffer catches up to a previously buffered range.\n      const fragState=this.fragmentTracker.getState(frag);\n      if(fragState===FragmentState.OK||fragState===FragmentState.PARTIAL&&frag.gap){\n        fragPrevious=frag;\n      }\n      if(fragPrevious&&frag.sn===fragPrevious.sn&&(!loadingParts||partList[0].fragment.sn > frag.sn)){\n        // Force the next fragment to load if the previous one was already selected. This can occasionally happen with\n        // non-uniform fragment durations\n        const sameLevel=fragPrevious&&frag.level===fragPrevious.level;\n        if(sameLevel){\n          const nextFrag=fragments[curSNIdx + 1];\n          if(frag.sn < endSN&&this.fragmentTracker.getState(nextFrag)!==FragmentState.OK){\n            frag=nextFrag;\n          }else{\n            frag=null;\n          }\n        }\n      }\n    }\n    return frag;\n  }\n  synchronizeToLiveEdge(levelDetails){\n    const {\n      config,\n      media\n    }=this;\n    if(!media){\n      return;\n    }\n    const liveSyncPosition=this.hls.liveSyncPosition;\n    const currentTime=media.currentTime;\n    const start=levelDetails.fragments[0].start;\n    const end=levelDetails.edge;\n    const withinSlidingWindow=currentTime >=start - config.maxFragLookUpTolerance&&currentTime <=end;\n    // Continue if we can seek forward to sync position or if current time is outside of sliding window\n    if(liveSyncPosition!==null&&media.duration > liveSyncPosition&&(currentTime < liveSyncPosition||!withinSlidingWindow)){\n      // Continue if buffer is starving or if current time is behind max latency\n      const maxLatency=config.liveMaxLatencyDuration!==undefined ? config.liveMaxLatencyDuration:config.liveMaxLatencyDurationCount * levelDetails.targetduration;\n      if(!withinSlidingWindow&&media.readyState < 4||currentTime < end - maxLatency){\n        if(!this.loadedmetadata){\n          this.nextLoadPosition=liveSyncPosition;\n        }\n        // Only seek if ready and there is not a significant forward buffer available for playback\n        if(media.readyState){\n          this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to:${liveSyncPosition.toFixed(3)}`);\n          media.currentTime=liveSyncPosition;\n        }\n      }\n    }\n  }\n  alignPlaylists(details, previousDetails, switchDetails){\n    // FIXME: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc,\n    //  this could all go in level-helper mergeDetails()\n    const length=details.fragments.length;\n    if(!length){\n      this.warn(`No fragments in live playlist`);\n      return 0;\n    }\n    const slidingStart=details.fragments[0].start;\n    const firstLevelLoad = !previousDetails;\n    const aligned=details.alignedSliding&&isFiniteNumber(slidingStart);\n    if(firstLevelLoad||!aligned&&!slidingStart){\n      const {\n        fragPrevious\n      }=this;\n      alignStream(fragPrevious, switchDetails, details);\n      const alignedSlidingStart=details.fragments[0].start;\n      this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN:'na'}->${details.startSN} prev-sn: ${fragPrevious ? fragPrevious.sn:'na'} fragments: ${length}`);\n      return alignedSlidingStart;\n    }\n    return slidingStart;\n  }\n  waitForCdnTuneIn(details){\n    // Wait for Low-Latency CDN Tune-in to get an updated playlist\n    const advancePartLimit=3;\n    return details.live&&details.canBlockReload&&details.partTarget&&details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit);\n  }\n  setStartPosition(details, sliding){\n    // compute start position if set to -1. use it straight away if value is defined\n    let startPosition=this.startPosition;\n    if(startPosition < sliding){\n      startPosition=-1;\n    }\n    if(startPosition===-1||this.lastCurrentTime===-1){\n      // Use Playlist EXT-X-START:TIME-OFFSET when set\n      // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match\n      const offsetInMultivariantPlaylist=this.startTimeOffset!==null;\n      const startTimeOffset=offsetInMultivariantPlaylist ? this.startTimeOffset:details.startTimeOffset;\n      if(startTimeOffset!==null&&isFiniteNumber(startTimeOffset)){\n        startPosition=sliding + startTimeOffset;\n        if(startTimeOffset < 0){\n          startPosition +=details.totalduration;\n        }\n        startPosition=Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);\n        this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant':'media'} playlist, adjust startPosition to ${startPosition}`);\n        this.startPosition=startPosition;\n      }else if(details.live){\n        // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has\n        // not been specified via the config or an as an argument to startLoad (#3736).\n        startPosition=this.hls.liveSyncPosition||sliding;\n      }else{\n        this.startPosition=startPosition=0;\n      }\n      this.lastCurrentTime=startPosition;\n    }\n    this.nextLoadPosition=startPosition;\n  }\n  getLoadPosition(){\n    const {\n      media\n    }=this;\n    // if we have not yet loaded any fragment, start loading from start position\n    let pos=0;\n    if(this.loadedmetadata&&media){\n      pos=media.currentTime;\n    }else if(this.nextLoadPosition){\n      pos=this.nextLoadPosition;\n    }\n    return pos;\n  }\n  handleFragLoadAborted(frag, part){\n    if(this.transmuxer&&frag.sn!=='initSegment'&&frag.stats.aborted){\n      this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index:''} of level ${frag.level} was aborted`);\n      this.resetFragmentLoading(frag);\n    }\n  }\n  resetFragmentLoading(frag){\n    if(!this.fragCurrent||!this.fragContextChanged(frag)&&this.state!==State.FRAG_LOADING_WAITING_RETRY){\n      this.state=State.IDLE;\n    }\n  }\n  onFragmentOrKeyLoadError(filterType, data){\n    if(data.chunkMeta&&!data.frag){\n      const context=this.getCurrentContext(data.chunkMeta);\n      if(context){\n        data.frag=context.frag;\n      }\n    }\n    const frag=data.frag;\n    // Handle frag error related to caller's filterType\n    if(!frag||frag.type!==filterType||!this.levels){\n      return;\n    }\n    if(this.fragContextChanged(frag)){\n      var _this$fragCurrent2;\n      this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent2=this.fragCurrent)==null ? void 0:_this$fragCurrent2.url}`);\n      return;\n    }\n    const gapTagEncountered=data.details===ErrorDetails.FRAG_GAP;\n    if(gapTagEncountered){\n      this.fragmentTracker.fragBuffered(frag, true);\n    }\n    // keep retrying until the limit will be reached\n    const errorAction=data.errorAction;\n    const {\n      action,\n      retryCount=0,\n      retryConfig\n    }=errorAction||{};\n    if(errorAction&&action===NetworkErrorAction.RetryRequest&&retryConfig){\n      this.resetStartWhenNotLoaded(this.levelLastLoaded);\n      const delay=getRetryDelay(retryConfig, retryCount);\n      this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`);\n      errorAction.resolved=true;\n      this.retryDate=self.performance.now() + delay;\n      this.state=State.FRAG_LOADING_WAITING_RETRY;\n    }else if(retryConfig&&errorAction){\n      this.resetFragmentErrors(filterType);\n      if(retryCount < retryConfig.maxNumRetry){\n        // Network retry is skipped when level switch is preferred\n        if(!gapTagEncountered&&action!==NetworkErrorAction.RemoveAlternatePermanently){\n          errorAction.resolved=true;\n        }\n      }else{\n        logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);\n        return;\n      }\n    }else if((errorAction==null ? void 0:errorAction.action)===NetworkErrorAction.SendAlternateToPenaltyBox){\n      this.state=State.WAITING_LEVEL;\n    }else{\n      this.state=State.ERROR;\n    }\n    // Perform next async tick sooner to speed up error action resolution\n    this.tickImmediate();\n  }\n  reduceLengthAndFlushBuffer(data){\n    // if in appending state\n    if(this.state===State.PARSING||this.state===State.PARSED){\n      const frag=data.frag;\n      const playlistType=data.parent;\n      const bufferedInfo=this.getFwdBufferInfo(this.mediaBuffer, playlistType);\n      // 0.5:tolerance needed as some browsers stalls playback before reaching buffered end\n      // reduce max buf len if current position is buffered\n      const buffered=bufferedInfo&&bufferedInfo.len > 0.5;\n      if(buffered){\n        this.reduceMaxBufferLength(bufferedInfo.len, (frag==null ? void 0:frag.duration)||10);\n      }\n      const flushBuffer = !buffered;\n      if(flushBuffer){\n        // current position is not buffered, but browser is still complaining about buffer full error\n        // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708\n        // in that case flush the whole audio buffer to recover\n        this.warn(`Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`);\n      }\n      if(frag){\n        this.fragmentTracker.removeFragment(frag);\n        this.nextLoadPosition=frag.start;\n      }\n      this.resetLoadingState();\n      return flushBuffer;\n    }\n    return false;\n  }\n  resetFragmentErrors(filterType){\n    if(filterType===PlaylistLevelType.AUDIO){\n      // Reset current fragment since audio track audio is essential and may not have a fail-over track\n      this.fragCurrent=null;\n    }\n    // Fragment errors that result in a level switch or redundant fail-over\n    // should reset the stream controller state to idle\n    if(!this.loadedmetadata){\n      this.startFragRequested=false;\n    }\n    if(this.state!==State.STOPPED){\n      this.state=State.IDLE;\n    }\n  }\n  afterBufferFlushed(media, bufferType, playlistType){\n    if(!media){\n      return;\n    }\n    // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media\n    // (so that we will check against video.buffered ranges in case of alt audio track)\n    const bufferedTimeRanges=BufferHelper.getBuffered(media);\n    this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType);\n    if(this.state===State.ENDED){\n      this.resetLoadingState();\n    }\n  }\n  resetLoadingState(){\n    this.log('Reset loading state');\n    this.fragCurrent=null;\n    this.fragPrevious=null;\n    this.state=State.IDLE;\n  }\n  resetStartWhenNotLoaded(level){\n    // if loadedmetadata is not set, it means that first frag request failed\n    // in that case, reset startFragRequested flag\n    if(!this.loadedmetadata){\n      this.startFragRequested=false;\n      const details=level ? level.details:null;\n      if(details!=null&&details.live){\n        // Update the start position and return to IDLE to recover live start\n        this.startPosition=-1;\n        this.setStartPosition(details, 0);\n        this.resetLoadingState();\n      }else{\n        this.nextLoadPosition=this.startPosition;\n      }\n    }\n  }\n  resetWhenMissingContext(chunkMeta){\n    this.warn(`The loading context changed while buffering fragment ${chunkMeta.sn} of level ${chunkMeta.level}. This chunk will not be buffered.`);\n    this.removeUnbufferedFrags();\n    this.resetStartWhenNotLoaded(this.levelLastLoaded);\n    this.resetLoadingState();\n  }\n  removeUnbufferedFrags(start=0){\n    this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true);\n  }\n  updateLevelTiming(frag, part, level, partial){\n    var _this$transmuxer;\n    const details=level.details;\n    if(!details){\n      this.warn('level.details undefined');\n      return;\n    }\n    const parsed=Object.keys(frag.elementaryStreams).reduce((result, type)=> {\n      const info=frag.elementaryStreams[type];\n      if(info){\n        const parsedDuration=info.endPTS - info.startPTS;\n        if(parsedDuration <=0){\n          // Destroy the transmuxer after it's next time offset failed to advance because duration was <=0.\n          // The new transmuxer will be configured with a time offset matching the next fragment start,\n          // preventing the timeline from shifting.\n          this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`);\n          return result||false;\n        }\n        const drift=partial ? 0:updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS);\n        this.hls.trigger(Events.LEVEL_PTS_UPDATED, {\n          details,\n          level,\n          drift,\n          type,\n          frag,\n          start: info.startPTS,\n          end: info.endPTS\n        });\n        return true;\n      }\n      return result;\n    }, false);\n    if(!parsed&&((_this$transmuxer=this.transmuxer)==null ? void 0:_this$transmuxer.error)===null){\n      const error=new Error(`Found no media in fragment ${frag.sn} of level ${frag.level} resetting transmuxer to fallback to playlist timing`);\n      if(level.fragmentError===0){\n        // Mark and track the odd empty segment as a gap to avoid reloading\n        level.fragmentError++;\n        frag.gap=true;\n        this.fragmentTracker.removeFragment(frag);\n        this.fragmentTracker.fragBuffered(frag, true);\n      }\n      this.warn(error.message);\n      this.hls.trigger(Events.ERROR, {\n        type: ErrorTypes.MEDIA_ERROR,\n        details: ErrorDetails.FRAG_PARSING_ERROR,\n        fatal: false,\n        error,\n        frag,\n        reason: `Found no media in msn ${frag.sn} of level \"${level.url}\"`\n      });\n      if(!this.hls){\n        return;\n      }\n      this.resetTransmuxer();\n      // For this error fallthrough. Marking parsed will allow advancing to next fragment.\n    }\n    this.state=State.PARSED;\n    this.hls.trigger(Events.FRAG_PARSED, {\n      frag,\n      part\n    });\n  }\n  resetTransmuxer(){\n    if(this.transmuxer){\n      this.transmuxer.destroy();\n      this.transmuxer=null;\n    }\n  }\n  recoverWorkerError(data){\n    if(data.event==='demuxerWorker'){\n      this.fragmentTracker.removeAllFragments();\n      this.resetTransmuxer();\n      this.resetStartWhenNotLoaded(this.levelLastLoaded);\n      this.resetLoadingState();\n    }\n  }\n  set state(nextState){\n    const previousState=this._state;\n    if(previousState!==nextState){\n      this._state=nextState;\n      this.log(`${previousState}->${nextState}`);\n    }\n  }\n  get state(){\n    return this._state;\n  }\n}\n\nclass ChunkCache {\n  constructor(){\n    this.chunks=[];\n    this.dataLength=0;\n  }\n  push(chunk){\n    this.chunks.push(chunk);\n    this.dataLength +=chunk.length;\n  }\n  flush(){\n    const {\n      chunks,\n      dataLength\n    }=this;\n    let result;\n    if(!chunks.length){\n      return new Uint8Array(0);\n    }else if(chunks.length===1){\n      result=chunks[0];\n    }else{\n      result=concatUint8Arrays(chunks, dataLength);\n    }\n    this.reset();\n    return result;\n  }\n  reset(){\n    this.chunks.length=0;\n    this.dataLength=0;\n  }\n}\nfunction concatUint8Arrays(chunks, dataLength){\n  const result=new Uint8Array(dataLength);\n  let offset=0;\n  for (let i=0; i < chunks.length; i++){\n    const chunk=chunks[i];\n    result.set(chunk, offset);\n    offset +=chunk.length;\n  }\n  return result;\n}\n\n// ensure the worker ends up in the bundle\n// If the worker should not be included this gets aliased to empty.js\nfunction hasUMDWorker(){\n  return typeof __HLS_WORKER_BUNDLE__==='function';\n}\nfunction injectWorker(){\n  const blob=new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], {\n    type: 'text/javascript'\n  });\n  const objectURL=self.URL.createObjectURL(blob);\n  const worker=new self.Worker(objectURL);\n  return {\n    worker,\n    objectURL\n  };\n}\nfunction loadWorker(path){\n  const scriptURL=new self.URL(path, self.location.href).href;\n  const worker=new self.Worker(scriptURL);\n  return {\n    worker,\n    scriptURL\n  };\n}\n\nfunction dummyTrack(type='', inputTimeScale=90000){\n  return {\n    type,\n    id: -1,\n    pid: -1,\n    inputTimeScale,\n    sequenceNumber: -1,\n    samples: [],\n    dropped: 0\n  };\n}\n\nclass BaseAudioDemuxer {\n  constructor(){\n    this._audioTrack=void 0;\n    this._id3Track=void 0;\n    this.frameIndex=0;\n    this.cachedData=null;\n    this.basePTS=null;\n    this.initPTS=null;\n    this.lastPTS=null;\n  }\n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    this._id3Track={\n      type: 'id3',\n      id: 3,\n      pid: -1,\n      inputTimeScale: 90000,\n      sequenceNumber: 0,\n      samples: [],\n      dropped: 0\n    };\n  }\n  resetTimeStamp(deaultTimestamp){\n    this.initPTS=deaultTimestamp;\n    this.resetContiguity();\n  }\n  resetContiguity(){\n    this.basePTS=null;\n    this.lastPTS=null;\n    this.frameIndex=0;\n  }\n  canParse(data, offset){\n    return false;\n  }\n  appendFrame(track, data, offset){}\n\n  // feed incoming data to the front of the parsing pipeline\n  demux(data, timeOffset){\n    if(this.cachedData){\n      data=appendUint8Array(this.cachedData, data);\n      this.cachedData=null;\n    }\n    let id3Data=getID3Data(data, 0);\n    let offset=id3Data ? id3Data.length:0;\n    let lastDataIndex;\n    const track=this._audioTrack;\n    const id3Track=this._id3Track;\n    const timestamp=id3Data ? getTimeStamp(id3Data):undefined;\n    const length=data.length;\n    if(this.basePTS===null||this.frameIndex===0&&isFiniteNumber(timestamp)){\n      this.basePTS=initPTSFn(timestamp, timeOffset, this.initPTS);\n      this.lastPTS=this.basePTS;\n    }\n    if(this.lastPTS===null){\n      this.lastPTS=this.basePTS;\n    }\n\n    // more expressive than alternative: id3Data?.length\n    if(id3Data&&id3Data.length > 0){\n      id3Track.samples.push({\n        pts: this.lastPTS,\n        dts: this.lastPTS,\n        data: id3Data,\n        type: MetadataSchema.audioId3,\n        duration: Number.POSITIVE_INFINITY\n      });\n    }\n    while (offset < length){\n      if(this.canParse(data, offset)){\n        const frame=this.appendFrame(track, data, offset);\n        if(frame){\n          this.frameIndex++;\n          this.lastPTS=frame.sample.pts;\n          offset +=frame.length;\n          lastDataIndex=offset;\n        }else{\n          offset=length;\n        }\n      }else if(canParse$2(data, offset)){\n        // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data\n        id3Data=getID3Data(data, offset);\n        id3Track.samples.push({\n          pts: this.lastPTS,\n          dts: this.lastPTS,\n          data: id3Data,\n          type: MetadataSchema.audioId3,\n          duration: Number.POSITIVE_INFINITY\n        });\n        offset +=id3Data.length;\n        lastDataIndex=offset;\n      }else{\n        offset++;\n      }\n      if(offset===length&&lastDataIndex!==length){\n        const partialData=sliceUint8(data, lastDataIndex);\n        if(this.cachedData){\n          this.cachedData=appendUint8Array(this.cachedData, partialData);\n        }else{\n          this.cachedData=partialData;\n        }\n      }\n    }\n    return {\n      audioTrack: track,\n      videoTrack: dummyTrack(),\n      id3Track,\n      textTrack: dummyTrack()\n    };\n  }\n  demuxSampleAes(data, keyData, timeOffset){\n    return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`));\n  }\n  flush(timeOffset){\n    // Parse cache in case of remaining frames.\n    const cachedData=this.cachedData;\n    if(cachedData){\n      this.cachedData=null;\n      this.demux(cachedData, 0);\n    }\n    return {\n      audioTrack: this._audioTrack,\n      videoTrack: dummyTrack(),\n      id3Track: this._id3Track,\n      textTrack: dummyTrack()\n    };\n  }\n  destroy(){}\n}\n\n\nconst initPTSFn=(timestamp, timeOffset, initPTS)=> {\n  if(isFiniteNumber(timestamp)){\n    return timestamp * 90;\n  }\n  const init90kHz=initPTS ? initPTS.baseTime * 90000 / initPTS.timescale:0;\n  return timeOffset * 90000 + init90kHz;\n};\n\n\nfunction getAudioConfig(observer, data, offset, audioCodec){\n  let adtsObjectType;\n  let adtsExtensionSamplingIndex;\n  let adtsChannelConfig;\n  let config;\n  const userAgent=navigator.userAgent.toLowerCase();\n  const manifestCodec=audioCodec;\n  const adtsSamplingRates=[96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];\n  // byte 2\n  adtsObjectType=((data[offset + 2] & 0xc0) >>> 6) + 1;\n  const adtsSamplingIndex=(data[offset + 2] & 0x3c) >>> 2;\n  if(adtsSamplingIndex > adtsSamplingRates.length - 1){\n    const error=new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);\n    observer.emit(Events.ERROR, Events.ERROR, {\n      type: ErrorTypes.MEDIA_ERROR,\n      details: ErrorDetails.FRAG_PARSING_ERROR,\n      fatal: true,\n      error,\n      reason: error.message\n    });\n    return;\n  }\n  adtsChannelConfig=(data[offset + 2] & 0x01) << 2;\n  // byte 3\n  adtsChannelConfig |=(data[offset + 3] & 0xc0) >>> 6;\n  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);\n  // firefox: freq less than 24kHz=AAC SBR (HE-AAC)\n  if(/firefox/i.test(userAgent)){\n    if(adtsSamplingIndex >=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      adtsExtensionSamplingIndex=adtsSamplingIndex - 3;\n    }else{\n      adtsObjectType=2;\n      config=new Array(2);\n      adtsExtensionSamplingIndex=adtsSamplingIndex;\n    }\n    // Android:always use AAC\n  }else if(userAgent.indexOf('android')!==-1){\n    adtsObjectType=2;\n    config=new Array(2);\n    adtsExtensionSamplingIndex=adtsSamplingIndex;\n  }else{\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||audioCodec.indexOf('mp4a.40.5')!==-1)||!audioCodec&&adtsSamplingIndex >=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      adtsExtensionSamplingIndex=adtsSamplingIndex - 3;\n    }else{\n      // if(manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)\n      // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC.  This is not a problem with stereo.\n      if(audioCodec&&audioCodec.indexOf('mp4a.40.2')!==-1&&(adtsSamplingIndex >=6&&adtsChannelConfig===1||/vivaldi/i.test(userAgent))||!audioCodec&&adtsChannelConfig===1){\n        adtsObjectType=2;\n        config=new Array(2);\n      }\n      adtsExtensionSamplingIndex=adtsSamplingIndex;\n    }\n  }\n  \n  // audioObjectType=profile=> profile, the MPEG-4 Audio Object Type minus 1\n  config[0]=adtsObjectType << 3;\n  // samplingFrequencyIndex\n  config[0] |=(adtsSamplingIndex & 0x0e) >> 1;\n  config[1] |=(adtsSamplingIndex & 0x01) << 7;\n  // channelConfiguration\n  config[1] |=adtsChannelConfig << 3;\n  if(adtsObjectType===5){\n    // adtsExtensionSamplingIndex\n    config[1] |=(adtsExtensionSamplingIndex & 0x0e) >> 1;\n    config[2]=(adtsExtensionSamplingIndex & 0x01) << 7;\n    // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???\n    //    https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc\n    config[2] |=2 << 2;\n    config[3]=0;\n  }\n  return {\n    config,\n    samplerate: adtsSamplingRates[adtsSamplingIndex],\n    channelCount: adtsChannelConfig,\n    codec: 'mp4a.40.' + adtsObjectType,\n    manifestCodec\n  };\n}\nfunction isHeaderPattern$1(data, offset){\n  return data[offset]===0xff&&(data[offset + 1] & 0xf6)===0xf0;\n}\nfunction getHeaderLength(data, offset){\n  return data[offset + 1] & 0x01 ? 7:9;\n}\nfunction getFullFrameLength(data, offset){\n  return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5;\n}\nfunction canGetFrameLength(data, offset){\n  return offset + 5 < data.length;\n}\nfunction isHeader$1(data, offset){\n  // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1\n  // Layer bits (position 14 and 15) in header should be always 0 for ADTS\n  // More info https://wiki.multimedia.cx/index.php?title=ADTS\n  return offset + 1 < data.length&&isHeaderPattern$1(data, offset);\n}\nfunction canParse$1(data, offset){\n  return canGetFrameLength(data, offset)&&isHeaderPattern$1(data, offset)&&getFullFrameLength(data, offset) <=data.length - offset;\n}\nfunction probe$1(data, offset){\n  // same as isHeader but we also check that ADTS frame follows last ADTS frame\n  // or end of data is reached\n  if(isHeader$1(data, offset)){\n    // ADTS header Length\n    const headerLength=getHeaderLength(data, offset);\n    if(offset + headerLength >=data.length){\n      return false;\n    }\n    // ADTS frame Length\n    const frameLength=getFullFrameLength(data, offset);\n    if(frameLength <=headerLength){\n      return false;\n    }\n    const newOffset=offset + frameLength;\n    return newOffset===data.length||isHeader$1(data, newOffset);\n  }\n  return false;\n}\nfunction initTrackConfig(track, observer, data, offset, audioCodec){\n  if(!track.samplerate){\n    const config=getAudioConfig(observer, data, offset, audioCodec);\n    if(!config){\n      return;\n    }\n    track.config=config.config;\n    track.samplerate=config.samplerate;\n    track.channelCount=config.channelCount;\n    track.codec=config.codec;\n    track.manifestCodec=config.manifestCodec;\n    logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);\n  }\n}\nfunction getFrameDuration(samplerate){\n  return 1024 * 90000 / samplerate;\n}\nfunction parseFrameHeader(data, offset){\n  // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header\n  const headerLength=getHeaderLength(data, offset);\n  if(offset + headerLength <=data.length){\n    // retrieve frame size\n    const frameLength=getFullFrameLength(data, offset) - headerLength;\n    if(frameLength > 0){\n      // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`);\n      return {\n        headerLength,\n        frameLength\n      };\n    }\n  }\n}\nfunction appendFrame$2(track, data, offset, pts, frameIndex){\n  const frameDuration=getFrameDuration(track.samplerate);\n  const stamp=pts + frameIndex * frameDuration;\n  const header=parseFrameHeader(data, offset);\n  let unit;\n  if(header){\n    const {\n      frameLength,\n      headerLength\n    }=header;\n    const _length=headerLength + frameLength;\n    const missing=Math.max(0, offset + _length - data.length);\n    // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`);\n    if(missing){\n      unit=new Uint8Array(_length - headerLength);\n      unit.set(data.subarray(offset + headerLength, data.length), 0);\n    }else{\n      unit=data.subarray(offset + headerLength, offset + _length);\n    }\n    const _sample={\n      unit,\n      pts: stamp\n    };\n    if(!missing){\n      track.samples.push(_sample);\n    }\n    return {\n      sample: _sample,\n      length: _length,\n      missing\n    };\n  }\n  // overflow incomplete header\n  const length=data.length - offset;\n  unit=new Uint8Array(length);\n  unit.set(data.subarray(offset, data.length), 0);\n  const sample={\n    unit,\n    pts: stamp\n  };\n  return {\n    sample,\n    length,\n    missing: -1\n  };\n}\n\n\n\nlet chromeVersion$1=null;\nconst BitratesMap=[32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160];\nconst SamplingRateMap=[44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];\nconst SamplesCoefficients=[\n// MPEG 2.5\n[0,\n// Reserved\n72,\n// Layer3\n144,\n// Layer2\n12 // Layer1\n],\n// Reserved\n[0,\n// Reserved\n0,\n// Layer3\n0,\n// Layer2\n0 // Layer1\n],\n// MPEG 2\n[0,\n// Reserved\n72,\n// Layer3\n144,\n// Layer2\n12 // Layer1\n],\n// MPEG 1\n[0,\n// Reserved\n144,\n// Layer3\n144,\n// Layer2\n12 // Layer1\n]];\nconst BytesInSlot=[0,\n// Reserved\n1,\n// Layer3\n1,\n// Layer2\n4 // Layer1\n];\nfunction appendFrame$1(track, data, offset, pts, frameIndex){\n  // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference\n  if(offset + 24 > data.length){\n    return;\n  }\n  const header=parseHeader(data, offset);\n  if(header&&offset + header.frameLength <=data.length){\n    const frameDuration=header.samplesPerFrame * 90000 / header.sampleRate;\n    const stamp=pts + frameIndex * frameDuration;\n    const sample={\n      unit: data.subarray(offset, offset + header.frameLength),\n      pts: stamp,\n      dts: stamp\n    };\n    track.config=[];\n    track.channelCount=header.channelCount;\n    track.samplerate=header.sampleRate;\n    track.samples.push(sample);\n    return {\n      sample,\n      length: header.frameLength,\n      missing: 0\n    };\n  }\n}\nfunction parseHeader(data, offset){\n  const mpegVersion=data[offset + 1] >> 3 & 3;\n  const mpegLayer=data[offset + 1] >> 1 & 3;\n  const bitRateIndex=data[offset + 2] >> 4 & 15;\n  const sampleRateIndex=data[offset + 2] >> 2 & 3;\n  if(mpegVersion!==1&&bitRateIndex!==0&&bitRateIndex!==15&&sampleRateIndex!==3){\n    const paddingBit=data[offset + 2] >> 1 & 1;\n    const channelMode=data[offset + 3] >> 6;\n    const columnInBitrates=mpegVersion===3 ? 3 - mpegLayer:mpegLayer===3 ? 3:4;\n    const bitRate=BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000;\n    const columnInSampleRates=mpegVersion===3 ? 0:mpegVersion===2 ? 1:2;\n    const sampleRate=SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];\n    const channelCount=channelMode===3 ? 1:2; // If bits of channel mode are `11` then it is a single channel (Mono)\n    const sampleCoefficient=SamplesCoefficients[mpegVersion][mpegLayer];\n    const bytesInSlot=BytesInSlot[mpegLayer];\n    const samplesPerFrame=sampleCoefficient * 8 * bytesInSlot;\n    const frameLength=Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot;\n    if(chromeVersion$1===null){\n      const userAgent=navigator.userAgent||'';\n      const result=userAgent.match(/Chrome\\/(\\d+)/i);\n      chromeVersion$1=result ? parseInt(result[1]):0;\n    }\n    const needChromeFix = !!chromeVersion$1&&chromeVersion$1 <=87;\n    if(needChromeFix&&mpegLayer===2&&bitRate >=224000&&channelMode===0){\n      // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00)\n      data[offset + 3]=data[offset + 3] | 0x80;\n    }\n    return {\n      sampleRate,\n      channelCount,\n      frameLength,\n      samplesPerFrame\n    };\n  }\n}\nfunction isHeaderPattern(data, offset){\n  return data[offset]===0xff&&(data[offset + 1] & 0xe0)===0xe0&&(data[offset + 1] & 0x06)!==0x00;\n}\nfunction isHeader(data, offset){\n  // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1\n  // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)\n  // More info http://www.mp3-tech.org/programmer/frame_header.html\n  return offset + 1 < data.length&&isHeaderPattern(data, offset);\n}\nfunction canParse(data, offset){\n  const headerSize=4;\n  return isHeaderPattern(data, offset)&&headerSize <=data.length - offset;\n}\nfunction probe(data, offset){\n  // same as isHeader but we also check that MPEG frame follows last MPEG frame\n  // or end of data is reached\n  if(offset + 1 < data.length&&isHeaderPattern(data, offset)){\n    // MPEG header Length\n    const headerLength=4;\n    // MPEG frame Length\n    const header=parseHeader(data, offset);\n    let frameLength=headerLength;\n    if(header!=null&&header.frameLength){\n      frameLength=header.frameLength;\n    }\n    const newOffset=offset + frameLength;\n    return newOffset===data.length||isHeader(data, newOffset);\n  }\n  return false;\n}\n\n\nclass AACDemuxer extends BaseAudioDemuxer {\n  constructor(observer, config){\n    super();\n    this.observer=void 0;\n    this.config=void 0;\n    this.observer=observer;\n    this.config=config;\n  }\n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);\n    this._audioTrack={\n      container: 'audio/adts',\n      type: 'audio',\n      id: 2,\n      pid: -1,\n      sequenceNumber: 0,\n      segmentCodec: 'aac',\n      samples: [],\n      manifestCodec: audioCodec,\n      duration: trackDuration,\n      inputTimeScale: 90000,\n      dropped: 0\n    };\n  }\n\n  // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS\n  static probe(data){\n    if(!data){\n      return false;\n    }\n\n    // Check for the ADTS sync word\n    // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1\n    // Layer bits (position 14 and 15) in header should be always 0 for ADTS\n    // More info https://wiki.multimedia.cx/index.php?title=ADTS\n    const id3Data=getID3Data(data, 0);\n    let offset=(id3Data==null ? void 0:id3Data.length)||0;\n    if(probe(data, offset)){\n      return false;\n    }\n    for (let length=data.length; offset < length; offset++){\n      if(probe$1(data, offset)){\n        logger.log('ADTS sync word found !');\n        return true;\n      }\n    }\n    return false;\n  }\n  canParse(data, offset){\n    return canParse$1(data, offset);\n  }\n  appendFrame(track, data, offset){\n    initTrackConfig(track, this.observer, data, offset, track.manifestCodec);\n    const frame=appendFrame$2(track, data, offset, this.basePTS, this.frameIndex);\n    if(frame&&frame.missing===0){\n      return frame;\n    }\n  }\n}\n\nconst emsgSchemePattern=/\\/emsg[-/]ID3/i;\nclass MP4Demuxer {\n  constructor(observer, config){\n    this.remainderData=null;\n    this.timeOffset=0;\n    this.config=void 0;\n    this.videoTrack=void 0;\n    this.audioTrack=void 0;\n    this.id3Track=void 0;\n    this.txtTrack=void 0;\n    this.config=config;\n  }\n  resetTimeStamp(){}\n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    const videoTrack=this.videoTrack=dummyTrack('video', 1);\n    const audioTrack=this.audioTrack=dummyTrack('audio', 1);\n    const captionTrack=this.txtTrack=dummyTrack('text', 1);\n    this.id3Track=dummyTrack('id3', 1);\n    this.timeOffset=0;\n    if(!(initSegment!=null&&initSegment.byteLength)){\n      return;\n    }\n    const initData=parseInitSegment(initSegment);\n    if(initData.video){\n      const {\n        id,\n        timescale,\n        codec\n      }=initData.video;\n      videoTrack.id=id;\n      videoTrack.timescale=captionTrack.timescale=timescale;\n      videoTrack.codec=codec;\n    }\n    if(initData.audio){\n      const {\n        id,\n        timescale,\n        codec\n      }=initData.audio;\n      audioTrack.id=id;\n      audioTrack.timescale=timescale;\n      audioTrack.codec=codec;\n    }\n    captionTrack.id=RemuxerTrackIdConfig.text;\n    videoTrack.sampleDuration=0;\n    videoTrack.duration=audioTrack.duration=trackDuration;\n  }\n  resetContiguity(){\n    this.remainderData=null;\n  }\n  static probe(data){\n    return hasMoofData(data);\n  }\n  demux(data, timeOffset){\n    this.timeOffset=timeOffset;\n    // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter\n    let videoSamples=data;\n    const videoTrack=this.videoTrack;\n    const textTrack=this.txtTrack;\n    if(this.config.progressive){\n      // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else.\n      // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee\n      // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception.\n      if(this.remainderData){\n        videoSamples=appendUint8Array(this.remainderData, data);\n      }\n      const segmentedData=segmentValidRange(videoSamples);\n      this.remainderData=segmentedData.remainder;\n      videoTrack.samples=segmentedData.valid||new Uint8Array();\n    }else{\n      videoTrack.samples=videoSamples;\n    }\n    const id3Track=this.extractID3Track(videoTrack, timeOffset);\n    textTrack.samples=parseSamples(timeOffset, videoTrack);\n    return {\n      videoTrack,\n      audioTrack: this.audioTrack,\n      id3Track,\n      textTrack: this.txtTrack\n    };\n  }\n  flush(){\n    const timeOffset=this.timeOffset;\n    const videoTrack=this.videoTrack;\n    const textTrack=this.txtTrack;\n    videoTrack.samples=this.remainderData||new Uint8Array();\n    this.remainderData=null;\n    const id3Track=this.extractID3Track(videoTrack, this.timeOffset);\n    textTrack.samples=parseSamples(timeOffset, videoTrack);\n    return {\n      videoTrack,\n      audioTrack: dummyTrack(),\n      id3Track,\n      textTrack: dummyTrack()\n    };\n  }\n  extractID3Track(videoTrack, timeOffset){\n    const id3Track=this.id3Track;\n    if(videoTrack.samples.length){\n      const emsgs=findBox(videoTrack.samples, ['emsg']);\n      if(emsgs){\n        emsgs.forEach(data=> {\n          const emsgInfo=parseEmsg(data);\n          if(emsgSchemePattern.test(emsgInfo.schemeIdUri)){\n            const pts=isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale:timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale;\n            let duration=emsgInfo.eventDuration===0xffffffff ? Number.POSITIVE_INFINITY:emsgInfo.eventDuration / emsgInfo.timeScale;\n            // Safari takes anything <=0.001 seconds and maps it to Infinity\n            if(duration <=0.001){\n              duration=Number.POSITIVE_INFINITY;\n            }\n            const payload=emsgInfo.payload;\n            id3Track.samples.push({\n              data: payload,\n              len: payload.byteLength,\n              dts: pts,\n              pts: pts,\n              type: MetadataSchema.emsg,\n              duration: duration\n            });\n          }\n        });\n      }\n    }\n    return id3Track;\n  }\n  demuxSampleAes(data, keyData, timeOffset){\n    return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption'));\n  }\n  destroy(){}\n}\n\nconst getAudioBSID=(data, offset)=> {\n  // check the bsid to confirm ac-3 | ec-3\n  let bsid=0;\n  let numBits=5;\n  offset +=numBits;\n  const temp=new Uint32Array(1); // unsigned 32 bit for temporary storage\n  const mask=new Uint32Array(1); // unsigned 32 bit mask value\n  const byte=new Uint8Array(1); // unsigned 8 bit for temporary storage\n  while (numBits > 0){\n    byte[0]=data[offset];\n    // read remaining bits, upto 8 bits at a time\n    const bits=Math.min(numBits, 8);\n    const shift=8 - bits;\n    mask[0]=0xff000000 >>> 24 + shift << shift;\n    temp[0]=(byte[0] & mask[0]) >> shift;\n    bsid = !bsid ? temp[0]:bsid << bits | temp[0];\n    offset +=1;\n    numBits -=bits;\n  }\n  return bsid;\n};\n\nclass AC3Demuxer extends BaseAudioDemuxer {\n  constructor(observer){\n    super();\n    this.observer=void 0;\n    this.observer=observer;\n  }\n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);\n    this._audioTrack={\n      container: 'audio/ac-3',\n      type: 'audio',\n      id: 2,\n      pid: -1,\n      sequenceNumber: 0,\n      segmentCodec: 'ac3',\n      samples: [],\n      manifestCodec: audioCodec,\n      duration: trackDuration,\n      inputTimeScale: 90000,\n      dropped: 0\n    };\n  }\n  canParse(data, offset){\n    return offset + 64 < data.length;\n  }\n  appendFrame(track, data, offset){\n    const frameLength=appendFrame(track, data, offset, this.basePTS, this.frameIndex);\n    if(frameLength!==-1){\n      const sample=track.samples[track.samples.length - 1];\n      return {\n        sample,\n        length: frameLength,\n        missing: 0\n      };\n    }\n  }\n  static probe(data){\n    if(!data){\n      return false;\n    }\n    const id3Data=getID3Data(data, 0);\n    if(!id3Data){\n      return false;\n    }\n\n    // look for the ac-3 sync bytes\n    const offset=id3Data.length;\n    if(data[offset]===0x0b&&data[offset + 1]===0x77&&getTimeStamp(id3Data)!==undefined&&\n    // check the bsid to confirm ac-3\n    getAudioBSID(data, offset) < 16){\n      return true;\n    }\n    return false;\n  }\n}\nfunction appendFrame(track, data, start, pts, frameIndex){\n  if(start + 8 > data.length){\n    return -1; // not enough bytes left\n  }\n  if(data[start]!==0x0b||data[start + 1]!==0x77){\n    return -1; // invalid magic\n  }\n\n  // get sample rate\n  const samplingRateCode=data[start + 4] >> 6;\n  if(samplingRateCode >=3){\n    return -1; // invalid sampling rate\n  }\n  const samplingRateMap=[48000, 44100, 32000];\n  const sampleRate=samplingRateMap[samplingRateCode];\n\n  // get frame size\n  const frameSizeCode=data[start + 4] & 0x3f;\n  const frameSizeMap=[64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920];\n  const frameLength=frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2;\n  if(start + frameLength > data.length){\n    return -1;\n  }\n\n  // get channel count\n  const channelMode=data[start + 6] >> 5;\n  let skipCount=0;\n  if(channelMode===2){\n    skipCount +=2;\n  }else{\n    if(channelMode & 1&&channelMode!==1){\n      skipCount +=2;\n    }\n    if(channelMode & 4){\n      skipCount +=2;\n    }\n  }\n  const lfeon=(data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1;\n  const channelsMap=[2, 1, 2, 3, 3, 4, 4, 5];\n  const channelCount=channelsMap[channelMode] + lfeon;\n\n  // build dac3 box\n  const bsid=data[start + 5] >> 3;\n  const bsmod=data[start + 5] & 7;\n  const config=new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 0xe0]);\n  const frameDuration=1536 / sampleRate * 90000;\n  const stamp=pts + frameIndex * frameDuration;\n  const unit=data.subarray(start, start + frameLength);\n  track.config=config;\n  track.channelCount=channelCount;\n  track.samplerate=sampleRate;\n  track.samples.push({\n    unit,\n    pts: stamp\n  });\n  return frameLength;\n}\n\nclass BaseVideoParser {\n  constructor(){\n    this.VideoSample=null;\n  }\n  createVideoSample(key, pts, dts, debug){\n    return {\n      key,\n      frame: false,\n      pts,\n      dts,\n      units: [],\n      debug,\n      length: 0\n    };\n  }\n  getLastNalUnit(samples){\n    var _VideoSample;\n    let VideoSample=this.VideoSample;\n    let lastUnit;\n    // try to fallback to previous sample if current one is empty\n    if(!VideoSample||VideoSample.units.length===0){\n      VideoSample=samples[samples.length - 1];\n    }\n    if((_VideoSample=VideoSample)!=null&&_VideoSample.units){\n      const units=VideoSample.units;\n      lastUnit=units[units.length - 1];\n    }\n    return lastUnit;\n  }\n  pushAccessUnit(VideoSample, videoTrack){\n    if(VideoSample.units.length&&VideoSample.frame){\n      // if sample does not have PTS/DTS, patch with last sample PTS/DTS\n      if(VideoSample.pts===undefined){\n        const samples=videoTrack.samples;\n        const nbSamples=samples.length;\n        if(nbSamples){\n          const lastSample=samples[nbSamples - 1];\n          VideoSample.pts=lastSample.pts;\n          VideoSample.dts=lastSample.dts;\n        }else{\n          // dropping samples, no timestamp found\n          videoTrack.dropped++;\n          return;\n        }\n      }\n      videoTrack.samples.push(VideoSample);\n    }\n    if(VideoSample.debug.length){\n      logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);\n    }\n  }\n}\n\n\n\nclass ExpGolomb {\n  constructor(data){\n    this.data=void 0;\n    this.bytesAvailable=void 0;\n    this.word=void 0;\n    this.bitsAvailable=void 0;\n    this.data=data;\n    // the number of bytes left to examine in this.data\n    this.bytesAvailable=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    const data=this.data;\n    const bytesAvailable=this.bytesAvailable;\n    const position=data.byteLength - bytesAvailable;\n    const workingBytes=new Uint8Array(4);\n    const availableBytes=Math.min(4, bytesAvailable);\n    if(availableBytes===0){\n      throw new Error('no bytes available');\n    }\n    workingBytes.set(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    let skipBytes; // :int\n    count=Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable);\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    let bits=Math.min(this.bitsAvailable, size); // :uint\n    const 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    }else{\n      throw new Error('no bits available');\n    }\n    bits=size - bits;\n    if(bits > 0&&this.bitsAvailable){\n      return valu << bits | this.readBits(bits);\n    }else{\n      return valu;\n    }\n  }\n\n  // ():uint\n  skipLZ(){\n    let leadingZeroCount; // :uint\n    for (leadingZeroCount=0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount){\n      if((this.word & 0x80000000 >>> leadingZeroCount)!==0){\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    const clz=this.skipLZ(); // :uint\n    return this.readBits(clz + 1) - 1;\n  }\n\n  // ():int\n  readEG(){\n    const 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 this.readBits(1)===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\n  // ():int\n  readUInt(){\n    return this.readBits(32);\n  }\n\n  \n  skipScalingList(count){\n    let lastScale=8;\n    let nextScale=8;\n    let deltaScale;\n    for (let j=0; j < count; j++){\n      if(nextScale!==0){\n        deltaScale=this.readEG();\n        nextScale=(lastScale + deltaScale + 256) % 256;\n      }\n      lastScale=nextScale===0 ? lastScale:nextScale;\n    }\n  }\n\n  \n  readSPS(){\n    let frameCropLeftOffset=0;\n    let frameCropRightOffset=0;\n    let frameCropTopOffset=0;\n    let frameCropBottomOffset=0;\n    let numRefFramesInPicOrderCntCycle;\n    let scalingListCount;\n    let i;\n    const readUByte=this.readUByte.bind(this);\n    const readBits=this.readBits.bind(this);\n    const readUEG=this.readUEG.bind(this);\n    const readBoolean=this.readBoolean.bind(this);\n    const skipBits=this.skipBits.bind(this);\n    const skipEG=this.skipEG.bind(this);\n    const skipUEG=this.skipUEG.bind(this);\n    const skipScalingList=this.skipScalingList.bind(this);\n    readUByte();\n    const profileIdc=readUByte(); // profile_idc\n    readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)\n    skipBits(3); // reserved_zero_3bits u(3),\n    readUByte(); // level_idc u(8)\n    skipUEG(); // seq_parameter_set_id\n    // some profiles have more optional data we don't need\n    if(profileIdc===100||profileIdc===110||profileIdc===122||profileIdc===244||profileIdc===44||profileIdc===83||profileIdc===86||profileIdc===118||profileIdc===128){\n      const chromaFormatIdc=readUEG();\n      if(chromaFormatIdc===3){\n        skipBits(1);\n      } // separate_colour_plane_flag\n\n      skipUEG(); // bit_depth_luma_minus8\n      skipUEG(); // bit_depth_chroma_minus8\n      skipBits(1); // qpprime_y_zero_transform_bypass_flag\n      if(readBoolean()){\n        // seq_scaling_matrix_present_flag\n        scalingListCount=chromaFormatIdc!==3 ? 8:12;\n        for (i=0; i < scalingListCount; i++){\n          if(readBoolean()){\n            // seq_scaling_list_present_flag[ i ]\n            if(i < 6){\n              skipScalingList(16);\n            }else{\n              skipScalingList(64);\n            }\n          }\n        }\n      }\n    }\n    skipUEG(); // log2_max_frame_num_minus4\n    const picOrderCntType=readUEG();\n    if(picOrderCntType===0){\n      readUEG(); // log2_max_pic_order_cnt_lsb_minus4\n    }else if(picOrderCntType===1){\n      skipBits(1); // delta_pic_order_always_zero_flag\n      skipEG(); // offset_for_non_ref_pic\n      skipEG(); // offset_for_top_to_bottom_field\n      numRefFramesInPicOrderCntCycle=readUEG();\n      for (i=0; i < numRefFramesInPicOrderCntCycle; i++){\n        skipEG();\n      } // offset_for_ref_frame[ i ]\n    }\n    skipUEG(); // max_num_ref_frames\n    skipBits(1); // gaps_in_frame_num_value_allowed_flag\n    const picWidthInMbsMinus1=readUEG();\n    const picHeightInMapUnitsMinus1=readUEG();\n    const frameMbsOnlyFlag=readBits(1);\n    if(frameMbsOnlyFlag===0){\n      skipBits(1);\n    } // mb_adaptive_frame_field_flag\n\n    skipBits(1); // direct_8x8_inference_flag\n    if(readBoolean()){\n      // frame_cropping_flag\n      frameCropLeftOffset=readUEG();\n      frameCropRightOffset=readUEG();\n      frameCropTopOffset=readUEG();\n      frameCropBottomOffset=readUEG();\n    }\n    let pixelRatio=[1, 1];\n    if(readBoolean()){\n      // vui_parameters_present_flag\n      if(readBoolean()){\n        // aspect_ratio_info_present_flag\n        const aspectRatioIdc=readUByte();\n        switch (aspectRatioIdc){\n          case 1:\n            pixelRatio=[1, 1];\n            break;\n          case 2:\n            pixelRatio=[12, 11];\n            break;\n          case 3:\n            pixelRatio=[10, 11];\n            break;\n          case 4:\n            pixelRatio=[16, 11];\n            break;\n          case 5:\n            pixelRatio=[40, 33];\n            break;\n          case 6:\n            pixelRatio=[24, 11];\n            break;\n          case 7:\n            pixelRatio=[20, 11];\n            break;\n          case 8:\n            pixelRatio=[32, 11];\n            break;\n          case 9:\n            pixelRatio=[80, 33];\n            break;\n          case 10:\n            pixelRatio=[18, 11];\n            break;\n          case 11:\n            pixelRatio=[15, 11];\n            break;\n          case 12:\n            pixelRatio=[64, 33];\n            break;\n          case 13:\n            pixelRatio=[160, 99];\n            break;\n          case 14:\n            pixelRatio=[4, 3];\n            break;\n          case 15:\n            pixelRatio=[3, 2];\n            break;\n          case 16:\n            pixelRatio=[2, 1];\n            break;\n          case 255:\n            {\n              pixelRatio=[readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];\n              break;\n            }\n        }\n      }\n    }\n    return {\n      width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),\n      height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2:4) * (frameCropTopOffset + frameCropBottomOffset),\n      pixelRatio: pixelRatio\n    };\n  }\n  readSliceType(){\n    // skip NALu type\n    this.readUByte();\n    // discard first_mb_in_slice\n    this.readUEG();\n    // return slice_type\n    return this.readUEG();\n  }\n}\n\nclass AvcVideoParser extends BaseVideoParser {\n  parseAVCPES(track, textTrack, pes, last, duration){\n    const units=this.parseAVCNALu(track, pes.data);\n    let VideoSample=this.VideoSample;\n    let push;\n    let spsfound=false;\n    // free pes.data to save up some memory\n    pes.data=null;\n\n    // if new NAL units found and last sample still there, let's push ...\n    // this helps parsing streams with missing AUD (only do this if AUD never found)\n    if(VideoSample&&units.length&&!track.audFound){\n      this.pushAccessUnit(VideoSample, track);\n      VideoSample=this.VideoSample=this.createVideoSample(false, pes.pts, pes.dts, '');\n    }\n    units.forEach(unit=> {\n      var _VideoSample2;\n      switch (unit.type){\n        // NDR\n        case 1:\n          {\n            let iskey=false;\n            push=true;\n            const data=unit.data;\n            // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)\n            if(spsfound&&data.length > 4){\n              // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR\n              const sliceType=new ExpGolomb(data).readSliceType();\n              // 2:I slice, 4:SI slice, 7:I slice, 9: SI slice\n              // SI slice:A slice that is coded using intra prediction only and using quantisation of the prediction samples.\n              // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.\n              // I slice: A slice that is not an SI slice that is decoded using intra prediction only.\n              // if(sliceType===2||sliceType===7){\n              if(sliceType===2||sliceType===4||sliceType===7||sliceType===9){\n                iskey=true;\n              }\n            }\n            if(iskey){\n              var _VideoSample;\n              // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push\n              if((_VideoSample=VideoSample)!=null&&_VideoSample.frame&&!VideoSample.key){\n                this.pushAccessUnit(VideoSample, track);\n                VideoSample=this.VideoSample=null;\n              }\n            }\n            if(!VideoSample){\n              VideoSample=this.VideoSample=this.createVideoSample(true, pes.pts, pes.dts, '');\n            }\n            VideoSample.frame=true;\n            VideoSample.key=iskey;\n            break;\n            // IDR\n          }\n        case 5:\n          push=true;\n          // handle PES not starting with AUD\n          // if we have frame data already, that cannot belong to the same frame, so force a push\n          if((_VideoSample2=VideoSample)!=null&&_VideoSample2.frame&&!VideoSample.key){\n            this.pushAccessUnit(VideoSample, track);\n            VideoSample=this.VideoSample=null;\n          }\n          if(!VideoSample){\n            VideoSample=this.VideoSample=this.createVideoSample(true, pes.pts, pes.dts, '');\n          }\n          VideoSample.key=true;\n          VideoSample.frame=true;\n          break;\n        // SEI\n        case 6:\n          {\n            push=true;\n            parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);\n            break;\n            // SPS\n          }\n        case 7:\n          {\n            var _track$pixelRatio, _track$pixelRatio2;\n            push=true;\n            spsfound=true;\n            const sps=unit.data;\n            const expGolombDecoder=new ExpGolomb(sps);\n            const config=expGolombDecoder.readSPS();\n            if(!track.sps||track.width!==config.width||track.height!==config.height||((_track$pixelRatio=track.pixelRatio)==null ? void 0:_track$pixelRatio[0])!==config.pixelRatio[0]||((_track$pixelRatio2=track.pixelRatio)==null ? void 0:_track$pixelRatio2[1])!==config.pixelRatio[1]){\n              track.width=config.width;\n              track.height=config.height;\n              track.pixelRatio=config.pixelRatio;\n              track.sps=[sps];\n              track.duration=duration;\n              const codecarray=sps.subarray(1, 4);\n              let codecstring='avc1.';\n              for (let i=0; i < 3; i++){\n                let h=codecarray[i].toString(16);\n                if(h.length < 2){\n                  h='0' + h;\n                }\n                codecstring +=h;\n              }\n              track.codec=codecstring;\n            }\n            break;\n          }\n        // PPS\n        case 8:\n          push=true;\n          track.pps=[unit.data];\n          break;\n        // AUD\n        case 9:\n          push=true;\n          track.audFound=true;\n          if(VideoSample){\n            this.pushAccessUnit(VideoSample, track);\n          }\n          VideoSample=this.VideoSample=this.createVideoSample(false, pes.pts, pes.dts, '');\n          break;\n        // Filler Data\n        case 12:\n          push=true;\n          break;\n        default:\n          push=false;\n          if(VideoSample){\n            VideoSample.debug +='unknown NAL ' + unit.type + ' ';\n          }\n          break;\n      }\n      if(VideoSample&&push){\n        const units=VideoSample.units;\n        units.push(unit);\n      }\n    });\n    // if last PES packet, push samples\n    if(last&&VideoSample){\n      this.pushAccessUnit(VideoSample, track);\n      this.VideoSample=null;\n    }\n  }\n  parseAVCNALu(track, array){\n    const len=array.byteLength;\n    let state=track.naluState||0;\n    const lastState=state;\n    const units=[];\n    let i=0;\n    let value;\n    let overflow;\n    let unitType;\n    let lastUnitStart=-1;\n    let lastUnitType=0;\n    // logger.log('PES:' + Hex.hexDump(array));\n\n    if(state===-1){\n      // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet\n      lastUnitStart=0;\n      // NALu type is value read from offset 0\n      lastUnitType=array[0] & 0x1f;\n      state=0;\n      i=1;\n    }\n    while (i < len){\n      value=array[i++];\n      // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case\n      if(!state){\n        state=value ? 0:1;\n        continue;\n      }\n      if(state===1){\n        state=value ? 0:2;\n        continue;\n      }\n      // here we have state either equal to 2 or 3\n      if(!value){\n        state=3;\n      }else if(value===1){\n        overflow=i - state - 1;\n        if(lastUnitStart >=0){\n          const unit={\n            data: array.subarray(lastUnitStart, overflow),\n            type: lastUnitType\n          };\n          // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);\n          units.push(unit);\n        }else{\n          // lastUnitStart is undefined=> this is the first start code found in this PES packet\n          // first check if start code delimiter is overlapping between 2 PES packets,\n          // ie it started in last packet (lastState not zero)\n          // and ended at the beginning of this PES packet (i <=4 - lastState)\n          const lastUnit=this.getLastNalUnit(track.samples);\n          if(lastUnit){\n            if(lastState&&i <=4 - lastState){\n              // start delimiter overlapping between PES packets\n              // strip start delimiter bytes from the end of last NAL unit\n              // check if lastUnit had a state different from zero\n              if(lastUnit.state){\n                // strip last bytes\n                lastUnit.data=lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);\n              }\n            }\n            // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.\n\n            if(overflow > 0){\n              // logger.log('first NALU found with overflow:' + overflow);\n              lastUnit.data=appendUint8Array(lastUnit.data, array.subarray(0, overflow));\n              lastUnit.state=0;\n            }\n          }\n        }\n        // check if we can read unit type\n        if(i < len){\n          unitType=array[i] & 0x1f;\n          // logger.log('find NALU @ offset:' + i + ',type:' + unitType);\n          lastUnitStart=i;\n          lastUnitType=unitType;\n          state=0;\n        }else{\n          // not enough byte to read unit type. let's read it on next PES parsing\n          state=-1;\n        }\n      }else{\n        state=0;\n      }\n    }\n    if(lastUnitStart >=0&&state >=0){\n      const unit={\n        data: array.subarray(lastUnitStart, len),\n        type: lastUnitType,\n        state: state\n      };\n      units.push(unit);\n      // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);\n    }\n    // no NALu found\n    if(units.length===0){\n      // append pes.data to previous NAL unit\n      const lastUnit=this.getLastNalUnit(track.samples);\n      if(lastUnit){\n        lastUnit.data=appendUint8Array(lastUnit.data, array);\n      }\n    }\n    track.naluState=state;\n    return units;\n  }\n}\n\n\n\nclass SampleAesDecrypter {\n  constructor(observer, config, keyData){\n    this.keyData=void 0;\n    this.decrypter=void 0;\n    this.keyData=keyData;\n    this.decrypter=new Decrypter(config, {\n      removePKCS7Padding: false\n    });\n  }\n  decryptBuffer(encryptedData){\n    return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);\n  }\n\n  // AAC - encrypt all full 16 bytes blocks starting from offset 16\n  decryptAacSample(samples, sampleIndex, callback){\n    const curUnit=samples[sampleIndex].unit;\n    if(curUnit.length <=16){\n      // No encrypted portion in this sample (first 16 bytes is not\n      // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html),\n      return;\n    }\n    const encryptedData=curUnit.subarray(16, curUnit.length - curUnit.length % 16);\n    const encryptedBuffer=encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);\n    this.decryptBuffer(encryptedBuffer).then(decryptedBuffer=> {\n      const decryptedData=new Uint8Array(decryptedBuffer);\n      curUnit.set(decryptedData, 16);\n      if(!this.decrypter.isSync()){\n        this.decryptAacSamples(samples, sampleIndex + 1, callback);\n      }\n    });\n  }\n  decryptAacSamples(samples, sampleIndex, callback){\n    for (;; sampleIndex++){\n      if(sampleIndex >=samples.length){\n        callback();\n        return;\n      }\n      if(samples[sampleIndex].unit.length < 32){\n        continue;\n      }\n      this.decryptAacSample(samples, sampleIndex, callback);\n      if(!this.decrypter.isSync()){\n        return;\n      }\n    }\n  }\n\n  // AVC - encrypt one 16 bytes block out of ten, starting from offset 32\n  getAvcEncryptedData(decodedData){\n    const encryptedDataLen=Math.floor((decodedData.length - 48) / 160) * 16 + 16;\n    const encryptedData=new Int8Array(encryptedDataLen);\n    let outputPos=0;\n    for (let inputPos=32; inputPos < decodedData.length - 16; inputPos +=160, outputPos +=16){\n      encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos);\n    }\n    return encryptedData;\n  }\n  getAvcDecryptedUnit(decodedData, decryptedData){\n    const uint8DecryptedData=new Uint8Array(decryptedData);\n    let inputPos=0;\n    for (let outputPos=32; outputPos < decodedData.length - 16; outputPos +=160, inputPos +=16){\n      decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos);\n    }\n    return decodedData;\n  }\n  decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit){\n    const decodedData=discardEPB(curUnit.data);\n    const encryptedData=this.getAvcEncryptedData(decodedData);\n    this.decryptBuffer(encryptedData.buffer).then(decryptedBuffer=> {\n      curUnit.data=this.getAvcDecryptedUnit(decodedData, decryptedBuffer);\n      if(!this.decrypter.isSync()){\n        this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);\n      }\n    });\n  }\n  decryptAvcSamples(samples, sampleIndex, unitIndex, callback){\n    if(samples instanceof Uint8Array){\n      throw new Error('Cannot decrypt samples of type Uint8Array');\n    }\n    for (;; sampleIndex++, unitIndex=0){\n      if(sampleIndex >=samples.length){\n        callback();\n        return;\n      }\n      const curUnits=samples[sampleIndex].units;\n      for (;; unitIndex++){\n        if(unitIndex >=curUnits.length){\n          break;\n        }\n        const curUnit=curUnits[unitIndex];\n        if(curUnit.data.length <=48||curUnit.type!==1&&curUnit.type!==5){\n          continue;\n        }\n        this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit);\n        if(!this.decrypter.isSync()){\n          return;\n        }\n      }\n    }\n  }\n}\n\nconst PACKET_LENGTH=188;\nclass TSDemuxer {\n  constructor(observer, config, typeSupported){\n    this.observer=void 0;\n    this.config=void 0;\n    this.typeSupported=void 0;\n    this.sampleAes=null;\n    this.pmtParsed=false;\n    this.audioCodec=void 0;\n    this.videoCodec=void 0;\n    this._duration=0;\n    this._pmtId=-1;\n    this._videoTrack=void 0;\n    this._audioTrack=void 0;\n    this._id3Track=void 0;\n    this._txtTrack=void 0;\n    this.aacOverFlow=null;\n    this.remainderData=null;\n    this.videoParser=void 0;\n    this.observer=observer;\n    this.config=config;\n    this.typeSupported=typeSupported;\n    this.videoParser=new AvcVideoParser();\n  }\n  static probe(data){\n    const syncOffset=TSDemuxer.syncOffset(data);\n    if(syncOffset > 0){\n      logger.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`);\n    }\n    return syncOffset!==-1;\n  }\n  static syncOffset(data){\n    const length=data.length;\n    let scanwindow=Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;\n    let i=0;\n    while (i < scanwindow){\n      // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47\n      let foundPat=false;\n      let packetStart=-1;\n      let tsPackets=0;\n      for (let j=i; j < length; j +=PACKET_LENGTH){\n        if(data[j]===0x47&&(length - j===PACKET_LENGTH||data[j + PACKET_LENGTH]===0x47)){\n          tsPackets++;\n          if(packetStart===-1){\n            packetStart=j;\n            // First sync word found at offset, increase scan length (#5251)\n            if(packetStart!==0){\n              scanwindow=Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1;\n            }\n          }\n          if(!foundPat){\n            foundPat=parsePID(data, j)===0;\n          }\n          // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501)\n          if(foundPat&&tsPackets > 1&&(packetStart===0&&tsPackets > 2||j + PACKET_LENGTH > scanwindow)){\n            return packetStart;\n          }\n        }else if(tsPackets){\n          // Exit if sync word found, but does not contain contiguous packets\n          return -1;\n        }else{\n          break;\n        }\n      }\n      i++;\n    }\n    return -1;\n  }\n\n  \n  static createTrack(type, duration){\n    return {\n      container: type==='video'||type==='audio' ? 'video/mp2t':undefined,\n      type,\n      id: RemuxerTrackIdConfig[type],\n      pid: -1,\n      inputTimeScale: 90000,\n      sequenceNumber: 0,\n      samples: [],\n      dropped: 0,\n      duration: type==='audio' ? duration:undefined\n    };\n  }\n\n  \n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    this.pmtParsed=false;\n    this._pmtId=-1;\n    this._videoTrack=TSDemuxer.createTrack('video');\n    this._audioTrack=TSDemuxer.createTrack('audio', trackDuration);\n    this._id3Track=TSDemuxer.createTrack('id3');\n    this._txtTrack=TSDemuxer.createTrack('text');\n    this._audioTrack.segmentCodec='aac';\n\n    // flush any partial content\n    this.aacOverFlow=null;\n    this.remainderData=null;\n    this.audioCodec=audioCodec;\n    this.videoCodec=videoCodec;\n    this._duration=trackDuration;\n  }\n  resetTimeStamp(){}\n  resetContiguity(){\n    const {\n      _audioTrack,\n      _videoTrack,\n      _id3Track\n    }=this;\n    if(_audioTrack){\n      _audioTrack.pesData=null;\n    }\n    if(_videoTrack){\n      _videoTrack.pesData=null;\n    }\n    if(_id3Track){\n      _id3Track.pesData=null;\n    }\n    this.aacOverFlow=null;\n    this.remainderData=null;\n  }\n  demux(data, timeOffset, isSampleAes=false, flush=false){\n    if(!isSampleAes){\n      this.sampleAes=null;\n    }\n    let pes;\n    const videoTrack=this._videoTrack;\n    const audioTrack=this._audioTrack;\n    const id3Track=this._id3Track;\n    const textTrack=this._txtTrack;\n    let videoPid=videoTrack.pid;\n    let videoData=videoTrack.pesData;\n    let audioPid=audioTrack.pid;\n    let id3Pid=id3Track.pid;\n    let audioData=audioTrack.pesData;\n    let id3Data=id3Track.pesData;\n    let unknownPID=null;\n    let pmtParsed=this.pmtParsed;\n    let pmtId=this._pmtId;\n    let len=data.length;\n    if(this.remainderData){\n      data=appendUint8Array(this.remainderData, data);\n      len=data.length;\n      this.remainderData=null;\n    }\n    if(len < PACKET_LENGTH&&!flush){\n      this.remainderData=data;\n      return {\n        audioTrack,\n        videoTrack,\n        id3Track,\n        textTrack\n      };\n    }\n    const syncOffset=Math.max(0, TSDemuxer.syncOffset(data));\n    len -=(len - syncOffset) % PACKET_LENGTH;\n    if(len < data.byteLength&&!flush){\n      this.remainderData=new Uint8Array(data.buffer, len, data.buffer.byteLength - len);\n    }\n\n    // loop through TS packets\n    let tsPacketErrors=0;\n    for (let start=syncOffset; start < len; start +=PACKET_LENGTH){\n      if(data[start]===0x47){\n        const stt = !!(data[start + 1] & 0x40);\n        const pid=parsePID(data, start);\n        const atf=(data[start + 3] & 0x30) >> 4;\n\n        // if an adaption field is present, its length is specified by the fifth byte of the TS packet header.\n        let offset;\n        if(atf > 1){\n          offset=start + 5 + data[start + 4];\n          // continue if there is only adaptation field\n          if(offset===start + PACKET_LENGTH){\n            continue;\n          }\n        }else{\n          offset=start + 4;\n        }\n        switch (pid){\n          case videoPid:\n            if(stt){\n              if(videoData&&(pes=parsePES(videoData))){\n                this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);\n              }\n              videoData={\n                data: [],\n                size: 0\n              };\n            }\n            if(videoData){\n              videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));\n              videoData.size +=start + PACKET_LENGTH - offset;\n            }\n            break;\n          case audioPid:\n            if(stt){\n              if(audioData&&(pes=parsePES(audioData))){\n                switch (audioTrack.segmentCodec){\n                  case 'aac':\n                    this.parseAACPES(audioTrack, pes);\n                    break;\n                  case 'mp3':\n                    this.parseMPEGPES(audioTrack, pes);\n                    break;\n                  case 'ac3':\n                    {\n                      this.parseAC3PES(audioTrack, pes);\n                    }\n                    break;\n                }\n              }\n              audioData={\n                data: [],\n                size: 0\n              };\n            }\n            if(audioData){\n              audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));\n              audioData.size +=start + PACKET_LENGTH - offset;\n            }\n            break;\n          case id3Pid:\n            if(stt){\n              if(id3Data&&(pes=parsePES(id3Data))){\n                this.parseID3PES(id3Track, pes);\n              }\n              id3Data={\n                data: [],\n                size: 0\n              };\n            }\n            if(id3Data){\n              id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));\n              id3Data.size +=start + PACKET_LENGTH - offset;\n            }\n            break;\n          case 0:\n            if(stt){\n              offset +=data[offset] + 1;\n            }\n            pmtId=this._pmtId=parsePAT(data, offset);\n            // logger.log('PMT PID:'  + this._pmtId);\n            break;\n          case pmtId:\n            {\n              if(stt){\n                offset +=data[offset] + 1;\n              }\n              const parsedPIDs=parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer);\n\n              // only update track id if track PID found while parsing PMT\n              // this is to avoid resetting the PID to -1 in case\n              // track PID transiently disappears from the stream\n              // this could happen in case of transient missing audio samples for example\n              // NOTE this is only the PID of the track as found in TS,\n              // but we are not using this for MP4 track IDs.\n              videoPid=parsedPIDs.videoPid;\n              if(videoPid > 0){\n                videoTrack.pid=videoPid;\n                videoTrack.segmentCodec=parsedPIDs.segmentVideoCodec;\n              }\n              audioPid=parsedPIDs.audioPid;\n              if(audioPid > 0){\n                audioTrack.pid=audioPid;\n                audioTrack.segmentCodec=parsedPIDs.segmentAudioCodec;\n              }\n              id3Pid=parsedPIDs.id3Pid;\n              if(id3Pid > 0){\n                id3Track.pid=id3Pid;\n              }\n              if(unknownPID!==null&&!pmtParsed){\n                logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`);\n                unknownPID=null;\n                // we set it to -188, the +=188 in the for loop will reset start to 0\n                start=syncOffset - 188;\n              }\n              pmtParsed=this.pmtParsed=true;\n              break;\n            }\n          case 0x11:\n          case 0x1fff:\n            break;\n          default:\n            unknownPID=pid;\n            break;\n        }\n      }else{\n        tsPacketErrors++;\n      }\n    }\n    if(tsPacketErrors > 0){\n      emitParsingError(this.observer, new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`));\n    }\n    videoTrack.pesData=videoData;\n    audioTrack.pesData=audioData;\n    id3Track.pesData=id3Data;\n    const demuxResult={\n      audioTrack,\n      videoTrack,\n      id3Track,\n      textTrack\n    };\n    if(flush){\n      this.extractRemainingSamples(demuxResult);\n    }\n    return demuxResult;\n  }\n  flush(){\n    const {\n      remainderData\n    }=this;\n    this.remainderData=null;\n    let result;\n    if(remainderData){\n      result=this.demux(remainderData, -1, false, true);\n    }else{\n      result={\n        videoTrack: this._videoTrack,\n        audioTrack: this._audioTrack,\n        id3Track: this._id3Track,\n        textTrack: this._txtTrack\n      };\n    }\n    this.extractRemainingSamples(result);\n    if(this.sampleAes){\n      return this.decrypt(result, this.sampleAes);\n    }\n    return result;\n  }\n  extractRemainingSamples(demuxResult){\n    const {\n      audioTrack,\n      videoTrack,\n      id3Track,\n      textTrack\n    }=demuxResult;\n    const videoData=videoTrack.pesData;\n    const audioData=audioTrack.pesData;\n    const id3Data=id3Track.pesData;\n    // try to parse last PES packets\n    let pes;\n    if(videoData&&(pes=parsePES(videoData))){\n      this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);\n      videoTrack.pesData=null;\n    }else{\n      // either avcData null or PES truncated, keep it for next frag parsing\n      videoTrack.pesData=videoData;\n    }\n    if(audioData&&(pes=parsePES(audioData))){\n      switch (audioTrack.segmentCodec){\n        case 'aac':\n          this.parseAACPES(audioTrack, pes);\n          break;\n        case 'mp3':\n          this.parseMPEGPES(audioTrack, pes);\n          break;\n        case 'ac3':\n          {\n            this.parseAC3PES(audioTrack, pes);\n          }\n          break;\n      }\n      audioTrack.pesData=null;\n    }else{\n      if(audioData!=null&&audioData.size){\n        logger.log('last AAC PES packet truncated,might overlap between fragments');\n      }\n\n      // either audioData null or PES truncated, keep it for next frag parsing\n      audioTrack.pesData=audioData;\n    }\n    if(id3Data&&(pes=parsePES(id3Data))){\n      this.parseID3PES(id3Track, pes);\n      id3Track.pesData=null;\n    }else{\n      // either id3Data null or PES truncated, keep it for next frag parsing\n      id3Track.pesData=id3Data;\n    }\n  }\n  demuxSampleAes(data, keyData, timeOffset){\n    const demuxResult=this.demux(data, timeOffset, true, !this.config.progressive);\n    const sampleAes=this.sampleAes=new SampleAesDecrypter(this.observer, this.config, keyData);\n    return this.decrypt(demuxResult, sampleAes);\n  }\n  decrypt(demuxResult, sampleAes){\n    return new Promise(resolve=> {\n      const {\n        audioTrack,\n        videoTrack\n      }=demuxResult;\n      if(audioTrack.samples&&audioTrack.segmentCodec==='aac'){\n        sampleAes.decryptAacSamples(audioTrack.samples, 0, ()=> {\n          if(videoTrack.samples){\n            sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, ()=> {\n              resolve(demuxResult);\n            });\n          }else{\n            resolve(demuxResult);\n          }\n        });\n      }else if(videoTrack.samples){\n        sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, ()=> {\n          resolve(demuxResult);\n        });\n      }\n    });\n  }\n  destroy(){\n    this._duration=0;\n  }\n  parseAACPES(track, pes){\n    let startOffset=0;\n    const aacOverFlow=this.aacOverFlow;\n    let data=pes.data;\n    if(aacOverFlow){\n      this.aacOverFlow=null;\n      const frameMissingBytes=aacOverFlow.missing;\n      const sampleLength=aacOverFlow.sample.unit.byteLength;\n      // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`);\n      if(frameMissingBytes===-1){\n        data=appendUint8Array(aacOverFlow.sample.unit, data);\n      }else{\n        const frameOverflowBytes=sampleLength - frameMissingBytes;\n        aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes);\n        track.samples.push(aacOverFlow.sample);\n        startOffset=aacOverFlow.missing;\n      }\n    }\n    // look for ADTS header (0xFFFx)\n    let offset;\n    let len;\n    for (offset=startOffset, len=data.length; offset < len - 1; offset++){\n      if(isHeader$1(data, offset)){\n        break;\n      }\n    }\n    // if ADTS header does not start straight from the beginning of the PES payload, raise an error\n    if(offset!==startOffset){\n      let reason;\n      const recoverable=offset < len - 1;\n      if(recoverable){\n        reason=`AAC PES did not start with ADTS header,offset:${offset}`;\n      }else{\n        reason='No ADTS header found in AAC PES';\n      }\n      emitParsingError(this.observer, new Error(reason), recoverable);\n      if(!recoverable){\n        return;\n      }\n    }\n    initTrackConfig(track, this.observer, data, offset, this.audioCodec);\n    let pts;\n    if(pes.pts!==undefined){\n      pts=pes.pts;\n    }else if(aacOverFlow){\n      // if last AAC frame is overflowing, we should ensure timestamps are contiguous:\n      // first sample PTS should be equal to last sample PTS + frameDuration\n      const frameDuration=getFrameDuration(track.samplerate);\n      pts=aacOverFlow.sample.pts + frameDuration;\n    }else{\n      logger.warn('[tsdemuxer]: AAC PES unknown PTS');\n      return;\n    }\n\n    // scan for aac samples\n    let frameIndex=0;\n    let frame;\n    while (offset < len){\n      frame=appendFrame$2(track, data, offset, pts, frameIndex);\n      offset +=frame.length;\n      if(!frame.missing){\n        frameIndex++;\n        for (; offset < len - 1; offset++){\n          if(isHeader$1(data, offset)){\n            break;\n          }\n        }\n      }else{\n        this.aacOverFlow=frame;\n        break;\n      }\n    }\n  }\n  parseMPEGPES(track, pes){\n    const data=pes.data;\n    const length=data.length;\n    let frameIndex=0;\n    let offset=0;\n    const pts=pes.pts;\n    if(pts===undefined){\n      logger.warn('[tsdemuxer]: MPEG PES unknown PTS');\n      return;\n    }\n    while (offset < length){\n      if(isHeader(data, offset)){\n        const frame=appendFrame$1(track, data, offset, pts, frameIndex);\n        if(frame){\n          offset +=frame.length;\n          frameIndex++;\n        }else{\n          // logger.log('Unable to parse Mpeg audio frame');\n          break;\n        }\n      }else{\n        // nothing found, keep looking\n        offset++;\n      }\n    }\n  }\n  parseAC3PES(track, pes){\n    {\n      const data=pes.data;\n      const pts=pes.pts;\n      if(pts===undefined){\n        logger.warn('[tsdemuxer]: AC3 PES unknown PTS');\n        return;\n      }\n      const length=data.length;\n      let frameIndex=0;\n      let offset=0;\n      let parsed;\n      while (offset < length&&(parsed=appendFrame(track, data, offset, pts, frameIndex++)) > 0){\n        offset +=parsed;\n      }\n    }\n  }\n  parseID3PES(id3Track, pes){\n    if(pes.pts===undefined){\n      logger.warn('[tsdemuxer]: ID3 PES unknown PTS');\n      return;\n    }\n    const id3Sample=_extends({}, pes, {\n      type: this._videoTrack ? MetadataSchema.emsg:MetadataSchema.audioId3,\n      duration: Number.POSITIVE_INFINITY\n    });\n    id3Track.samples.push(id3Sample);\n  }\n}\nfunction parsePID(data, offset){\n  // pid is a 13-bit field starting at the last bit of TS[1]\n  return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2];\n}\nfunction parsePAT(data, offset){\n  // skip the PSI header and parse the first PMT entry\n  return (data[offset + 10] & 0x1f) << 8 | data[offset + 11];\n}\nfunction parsePMT(data, offset, typeSupported, isSampleAes, observer){\n  const result={\n    audioPid: -1,\n    videoPid: -1,\n    id3Pid: -1,\n    segmentVideoCodec: 'avc',\n    segmentAudioCodec: 'aac'\n  };\n  const sectionLength=(data[offset + 1] & 0x0f) << 8 | data[offset + 2];\n  const tableEnd=offset + 3 + sectionLength - 4;\n  // to determine where the table is, we have to figure out how\n  // long the program info descriptors are\n  const programInfoLength=(data[offset + 10] & 0x0f) << 8 | data[offset + 11];\n  // advance the offset to the first entry in the mapping table\n  offset +=12 + programInfoLength;\n  while (offset < tableEnd){\n    const pid=parsePID(data, offset);\n    const esInfoLength=(data[offset + 3] & 0x0f) << 8 | data[offset + 4];\n    switch (data[offset]){\n      case 0xcf:\n        // SAMPLE-AES AAC\n        if(!isSampleAes){\n          logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC');\n          break;\n        }\n      \n      case 0x0f:\n        // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)\n        // logger.log('AAC PID:'  + pid);\n        if(result.audioPid===-1){\n          result.audioPid=pid;\n        }\n        break;\n\n      // Packetized metadata (ID3)\n      case 0x15:\n        // logger.log('ID3 PID:'  + pid);\n        if(result.id3Pid===-1){\n          result.id3Pid=pid;\n        }\n        break;\n      case 0xdb:\n        // SAMPLE-AES AVC\n        if(!isSampleAes){\n          logEncryptedSamplesFoundInUnencryptedStream('H.264');\n          break;\n        }\n      \n      case 0x1b:\n        // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)\n        // logger.log('AVC PID:'  + pid);\n        if(result.videoPid===-1){\n          result.videoPid=pid;\n          result.segmentVideoCodec='avc';\n        }\n        break;\n\n      // ISO/IEC 11172-3 (MPEG-1 audio)\n      // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)\n      case 0x03:\n      case 0x04:\n        // logger.log('MPEG PID:'  + pid);\n        if(!typeSupported.mpeg&&!typeSupported.mp3){\n          logger.log('MPEG audio found, not supported in this browser');\n        }else if(result.audioPid===-1){\n          result.audioPid=pid;\n          result.segmentAudioCodec='mp3';\n        }\n        break;\n      case 0xc1:\n        // SAMPLE-AES AC3\n        if(!isSampleAes){\n          logEncryptedSamplesFoundInUnencryptedStream('AC-3');\n          break;\n        }\n      \n      case 0x81:\n        {\n          if(!typeSupported.ac3){\n            logger.log('AC-3 audio found, not supported in this browser');\n          }else if(result.audioPid===-1){\n            result.audioPid=pid;\n            result.segmentAudioCodec='ac3';\n          }\n        }\n        break;\n      case 0x06:\n        // stream_type 6 can mean a lot of different things in case of DVB.\n        // We need to look at the descriptors. Right now, we're only interested\n        // in AC-3 audio, so we do the descriptor parsing only when we don't have\n        // an audio PID yet.\n        if(result.audioPid===-1&&esInfoLength > 0){\n          let parsePos=offset + 5;\n          let remaining=esInfoLength;\n          while (remaining > 2){\n            const descriptorId=data[parsePos];\n            switch (descriptorId){\n              case 0x6a:\n                // DVB Descriptor for AC-3\n                {\n                  if(typeSupported.ac3!==true){\n                    logger.log('AC-3 audio found, not supported in this browser for now');\n                  }else{\n                    result.audioPid=pid;\n                    result.segmentAudioCodec='ac3';\n                  }\n                }\n                break;\n            }\n            const descriptorLen=data[parsePos + 1] + 2;\n            parsePos +=descriptorLen;\n            remaining -=descriptorLen;\n          }\n        }\n        break;\n      case 0xc2: // SAMPLE-AES EC3\n      \n      case 0x87:\n        emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'));\n        return result;\n      case 0x24:\n        emitParsingError(observer, new Error('Unsupported HEVC in M2TS found'));\n        return result;\n    }\n    // move to the next table entry\n    // skip past the elementary stream descriptors, if present\n    offset +=esInfoLength + 5;\n  }\n  return result;\n}\nfunction emitParsingError(observer, error, levelRetry){\n  logger.warn(`parsing error: ${error.message}`);\n  observer.emit(Events.ERROR, Events.ERROR, {\n    type: ErrorTypes.MEDIA_ERROR,\n    details: ErrorDetails.FRAG_PARSING_ERROR,\n    fatal: false,\n    levelRetry,\n    error,\n    reason: error.message\n  });\n}\nfunction logEncryptedSamplesFoundInUnencryptedStream(type){\n  logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);\n}\nfunction parsePES(stream){\n  let i=0;\n  let frag;\n  let pesLen;\n  let pesHdrLen;\n  let pesPts;\n  let pesDts;\n  const data=stream.data;\n  // safety check\n  if(!stream||stream.size===0){\n    return null;\n  }\n\n  // we might need up to 19 bytes to read PES header\n  // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes\n  // usually only one merge is needed (and this is rare ...)\n  while (data[0].length < 19&&data.length > 1){\n    data[0]=appendUint8Array(data[0], data[1]);\n    data.splice(1, 1);\n  }\n  // retrieve PTS/DTS from first fragment\n  frag=data[0];\n  const pesPrefix=(frag[0] << 16) + (frag[1] << 8) + frag[2];\n  if(pesPrefix===1){\n    pesLen=(frag[4] << 8) + frag[5];\n    // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated\n    // minus 6:PES header size\n    if(pesLen&&pesLen > stream.size - 6){\n      return null;\n    }\n    const pesFlags=frag[7];\n    if(pesFlags & 0xc0){\n      \n      pesPts=(frag[9] & 0x0e) * 536870912 +\n      // 1 << 29\n      (frag[10] & 0xff) * 4194304 +\n      // 1 << 22\n      (frag[11] & 0xfe) * 16384 +\n      // 1 << 14\n      (frag[12] & 0xff) * 128 +\n      // 1 << 7\n      (frag[13] & 0xfe) / 2;\n      if(pesFlags & 0x40){\n        pesDts=(frag[14] & 0x0e) * 536870912 +\n        // 1 << 29\n        (frag[15] & 0xff) * 4194304 +\n        // 1 << 22\n        (frag[16] & 0xfe) * 16384 +\n        // 1 << 14\n        (frag[17] & 0xff) * 128 +\n        // 1 << 7\n        (frag[18] & 0xfe) / 2;\n        if(pesPts - pesDts > 60 * 90000){\n          logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`);\n          pesPts=pesDts;\n        }\n      }else{\n        pesDts=pesPts;\n      }\n    }\n    pesHdrLen=frag[8];\n    // 9 bytes:6 bytes for PES header + 3 bytes for PES extension\n    let payloadStartOffset=pesHdrLen + 9;\n    if(stream.size <=payloadStartOffset){\n      return null;\n    }\n    stream.size -=payloadStartOffset;\n    // reassemble PES packet\n    const pesData=new Uint8Array(stream.size);\n    for (let j=0, dataLen=data.length; j < dataLen; j++){\n      frag=data[j];\n      let len=frag.byteLength;\n      if(payloadStartOffset){\n        if(payloadStartOffset > len){\n          // trim full frag if PES header bigger than frag\n          payloadStartOffset -=len;\n          continue;\n        }else{\n          // trim partial frag if PES header smaller than frag\n          frag=frag.subarray(payloadStartOffset);\n          len -=payloadStartOffset;\n          payloadStartOffset=0;\n        }\n      }\n      pesData.set(frag, i);\n      i +=len;\n    }\n    if(pesLen){\n      // payload size:remove PES header + PES extension\n      pesLen -=pesHdrLen + 3;\n    }\n    return {\n      data: pesData,\n      pts: pesPts,\n      dts: pesDts,\n      len: pesLen\n    };\n  }\n  return null;\n}\n\n\nclass MP3Demuxer extends BaseAudioDemuxer {\n  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration){\n    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);\n    this._audioTrack={\n      container: 'audio/mpeg',\n      type: 'audio',\n      id: 2,\n      pid: -1,\n      sequenceNumber: 0,\n      segmentCodec: 'mp3',\n      samples: [],\n      manifestCodec: audioCodec,\n      duration: trackDuration,\n      inputTimeScale: 90000,\n      dropped: 0\n    };\n  }\n  static probe(data){\n    if(!data){\n      return false;\n    }\n\n    // check if data contains ID3 timestamp and MPEG sync word\n    // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1\n    // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)\n    // More info http://www.mp3-tech.org/programmer/frame_header.html\n    const id3Data=getID3Data(data, 0);\n    let offset=(id3Data==null ? void 0:id3Data.length)||0;\n\n    // Check for ac-3|ec-3 sync bytes and return false if present\n    if(id3Data&&data[offset]===0x0b&&data[offset + 1]===0x77&&getTimeStamp(id3Data)!==undefined&&\n    // check the bsid to confirm ac-3 or ec-3 (not mp3)\n    getAudioBSID(data, offset) <=16){\n      return false;\n    }\n    for (let length=data.length; offset < length; offset++){\n      if(probe(data, offset)){\n        logger.log('MPEG Audio sync word found !');\n        return true;\n      }\n    }\n    return false;\n  }\n  canParse(data, offset){\n    return canParse(data, offset);\n  }\n  appendFrame(track, data, offset){\n    if(this.basePTS===null){\n      return;\n    }\n    return appendFrame$1(track, data, offset, this.basePTS, this.frameIndex);\n  }\n}\n\n\n\nclass AAC {\n  static getSilentFrame(codec, channelCount){\n    switch (codec){\n      case 'mp4a.40.2':\n        if(channelCount===1){\n          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);\n        }else if(channelCount===2){\n          return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);\n        }else if(channelCount===3){\n          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);\n        }else if(channelCount===4){\n          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);\n        }else if(channelCount===5){\n          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);\n        }else if(channelCount===6){\n          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]);\n        }\n        break;\n      // handle HE-AAC below (mp4a.40.5 / mp4a.40.29)\n      default:\n        if(channelCount===1){\n          // ffmpeg -y -f lavfi -i \"aevalsrc=0:d=0.05\" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac&&hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n          return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n        }else if(channelCount===2){\n          // ffmpeg -y -f lavfi -i \"aevalsrc=0|0:d=0.05\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac&&hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n          return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n        }else if(channelCount===3){\n          // ffmpeg -y -f lavfi -i \"aevalsrc=0|0|0:d=0.05\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac&&hexdump -v -e '16/1 \"0x%x,\" \"\\n\"' -v output.aac\n          return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);\n        }\n        break;\n    }\n    return undefined;\n  }\n}\n\n\n\nconst UINT32_MAX=Math.pow(2, 32) - 1;\nclass MP4 {\n  static init(){\n    MP4.types={\n      avc1: [],\n      // 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      '.mp3': [],\n      dac3: [],\n      'ac-3': [],\n      mvex: [],\n      mvhd: [],\n      pasp: [],\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    let i;\n    for (i in MP4.types){\n      if(MP4.types.hasOwnProperty(i)){\n        MP4.types[i]=[i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];\n      }\n    }\n    const videoHdlr=new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00,\n    // pre_defined\n    0x76, 0x69, 0x64, 0x65,\n    // handler_type: 'vide'\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'\n    ]);\n    const audioHdlr=new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00,\n    // pre_defined\n    0x73, 0x6f, 0x75, 0x6e,\n    // handler_type: 'soun'\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'\n    ]);\n    MP4.HDLR_TYPES={\n      video: videoHdlr,\n      audio: audioHdlr\n    };\n    const dref=new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x01,\n    // entry_count\n    0x00, 0x00, 0x00, 0x0c,\n    // entry_size\n    0x75, 0x72, 0x6c, 0x20,\n    // 'url' type\n    0x00,\n    // version 0\n    0x00, 0x00, 0x01 // entry_flags\n    ]);\n    const stco=new Uint8Array([0x00,\n    // version\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00 // entry_count\n    ]);\n    MP4.STTS=MP4.STSC=MP4.STCO=stco;\n    MP4.STSZ=new Uint8Array([0x00,\n    // version\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00,\n    // sample_size\n    0x00, 0x00, 0x00, 0x00 // sample_count\n    ]);\n    MP4.VMHD=new Uint8Array([0x00,\n    // version\n    0x00, 0x00, 0x01,\n    // flags\n    0x00, 0x00,\n    // graphicsmode\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor\n    ]);\n    MP4.SMHD=new Uint8Array([0x00,\n    // version\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00,\n    // balance\n    0x00, 0x00 // reserved\n    ]);\n    MP4.STSD=new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x01]); // entry_count\n\n    const majorBrand=new Uint8Array([105, 115, 111, 109]); // isom\n    const avc1Brand=new Uint8Array([97, 118, 99, 49]); // avc1\n    const minorVersion=new Uint8Array([0, 0, 0, 1]);\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  static box(type, ...payload){\n    let size=8;\n    let i=payload.length;\n    const len=i;\n    // calculate the total size we need to allocate\n    while (i--){\n      size +=payload[i].byteLength;\n    }\n    const 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      // copy payload[i] array @ offset size\n      result.set(payload[i], size);\n      size +=payload[i].byteLength;\n    }\n    return result;\n  }\n  static hdlr(type){\n    return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);\n  }\n  static mdat(data){\n    return MP4.box(MP4.types.mdat, data);\n  }\n  static mdhd(timescale, duration){\n    duration *=timescale;\n    const upperWordDuration=Math.floor(duration / (UINT32_MAX + 1));\n    const lowerWordDuration=Math.floor(duration % (UINT32_MAX + 1));\n    return MP4.box(MP4.types.mdhd, new Uint8Array([0x01,\n    // version 1\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,\n    // creation_time\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,\n    // modification_time\n    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,\n    // timescale\n    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4,\n    // 'und' language (undetermined)\n    0x00, 0x00]));\n  }\n  static mdia(track){\n    return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track));\n  }\n  static mfhd(sequenceNumber){\n    return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,\n    // flags\n    sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number\n    ]));\n  }\n  static minf(track){\n    if(track.type==='audio'){\n      return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));\n    }else{\n      return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));\n    }\n  }\n  static moof(sn, baseMediaDecodeTime, track){\n    return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));\n  }\n  static moov(tracks){\n    let i=tracks.length;\n    const boxes=[];\n    while (i--){\n      boxes[i]=MP4.trak(tracks[i]);\n    }\n    return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks)));\n  }\n  static mvex(tracks){\n    let i=tracks.length;\n    const boxes=[];\n    while (i--){\n      boxes[i]=MP4.trex(tracks[i]);\n    }\n    return MP4.box.apply(null, [MP4.types.mvex, ...boxes]);\n  }\n  static mvhd(timescale, duration){\n    duration *=timescale;\n    const upperWordDuration=Math.floor(duration / (UINT32_MAX + 1));\n    const lowerWordDuration=Math.floor(duration % (UINT32_MAX + 1));\n    const bytes=new Uint8Array([0x01,\n    // version 1\n    0x00, 0x00, 0x00,\n    // flags\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,\n    // creation_time\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,\n    // modification_time\n    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,\n    // timescale\n    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00,\n    // 1.0 rate\n    0x01, 0x00,\n    // 1.0 volume\n    0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,\n    // transformation: unity matrix\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    // pre_defined\n    0xff, 0xff, 0xff, 0xff // next_track_ID\n    ]);\n    return MP4.box(MP4.types.mvhd, bytes);\n  }\n  static sdtp(track){\n    const samples=track.samples||[];\n    const bytes=new Uint8Array(4 + samples.length);\n    let i;\n    let flags;\n    // leave the full box header (4 bytes) all zero\n    // write the sample table\n    for (i=0; i < samples.length; i++){\n      flags=samples[i].flags;\n      bytes[i + 4]=flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;\n    }\n    return MP4.box(MP4.types.sdtp, bytes);\n  }\n  static stbl(track){\n    return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO));\n  }\n  static avc1(track){\n    let sps=[];\n    let pps=[];\n    let i;\n    let data;\n    let len;\n    // assemble the SPSs\n\n    for (i=0; i < track.sps.length; i++){\n      data=track.sps[i];\n      len=data.byteLength;\n      sps.push(len >>> 8 & 0xff);\n      sps.push(len & 0xff);\n\n      // SPS\n      sps=sps.concat(Array.prototype.slice.call(data));\n    }\n\n    // assemble the PPSs\n    for (i=0; i < track.pps.length; i++){\n      data=track.pps[i];\n      len=data.byteLength;\n      pps.push(len >>> 8 & 0xff);\n      pps.push(len & 0xff);\n      pps=pps.concat(Array.prototype.slice.call(data));\n    }\n    const avcc=MP4.box(MP4.types.avcC, new Uint8Array([0x01,\n    // version\n    sps[3],\n    // profile\n    sps[4],\n    // profile compat\n    sps[5],\n    // level\n    0xfc | 3,\n    // lengthSizeMinusOne, hard-coded to 4 bytes\n    0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets\n    ].concat(sps).concat([track.pps.length // numOfPictureParameterSets\n    ]).concat(pps))); // \"PPS\"\n    const width=track.width;\n    const height=track.height;\n    const hSpacing=track.pixelRatio[0];\n    const vSpacing=track.pixelRatio[1];\n    return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x01,\n    // data_reference_index\n    0x00, 0x00,\n    // pre_defined\n    0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    // pre_defined\n    width >> 8 & 0xff, width & 0xff,\n    // width\n    height >> 8 & 0xff, height & 0xff,\n    // height\n    0x00, 0x48, 0x00, 0x00,\n    // horizresolution\n    0x00, 0x48, 0x00, 0x00,\n    // vertresolution\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x01,\n    // frame_count\n    0x12, 0x64, 0x61, 0x69, 0x6c,\n    // dailymotion/hls.js\n    0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    // compressorname\n    0x00, 0x18,\n    // depth=24\n    0x11, 0x11]),\n    // pre_defined=-1\n    avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,\n    // bufferSizeDB\n    0x00, 0x2d, 0xc6, 0xc0,\n    // maxBitrate\n    0x00, 0x2d, 0xc6, 0xc0])),\n    // avgBitrate\n    MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,\n    // hSpacing\n    hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,\n    // vSpacing\n    vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));\n  }\n  static esds(track){\n    const configlen=track.config.length;\n    return new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n\n    0x03,\n    // descriptor_type\n    0x17 + configlen,\n    // length\n    0x00, 0x01,\n    // es_id\n    0x00,\n    // stream_priority\n\n    0x04,\n    // descriptor_type\n    0x0f + configlen,\n    // length\n    0x40,\n    // codec:mpeg4_audio\n    0x15,\n    // stream_type\n    0x00, 0x00, 0x00,\n    // buffer_size\n    0x00, 0x00, 0x00, 0x00,\n    // maxBitrate\n    0x00, 0x00, 0x00, 0x00,\n    // avgBitrate\n\n    0x05 // descriptor_type\n    ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor\n  }\n  static audioStsd(track){\n    const samplerate=track.samplerate;\n    return new Uint8Array([0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x01,\n    // data_reference_index\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, track.channelCount,\n    // channelcount\n    0x00, 0x10,\n    // sampleSize:16bits\n    0x00, 0x00, 0x00, 0x00,\n    // reserved2\n    samplerate >> 8 & 0xff, samplerate & 0xff,\n    //\n    0x00, 0x00]);\n  }\n  static mp4a(track){\n    return MP4.box(MP4.types.mp4a, MP4.audioStsd(track), MP4.box(MP4.types.esds, MP4.esds(track)));\n  }\n  static mp3(track){\n    return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track));\n  }\n  static ac3(track){\n    return MP4.box(MP4.types['ac-3'], MP4.audioStsd(track), MP4.box(MP4.types.dac3, track.config));\n  }\n  static stsd(track){\n    if(track.type==='audio'){\n      if(track.segmentCodec==='mp3'&&track.codec==='mp3'){\n        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track));\n      }\n      if(track.segmentCodec==='ac3'){\n        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));\n      }\n      return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));\n    }else{\n      return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));\n    }\n  }\n  static tkhd(track){\n    const id=track.id;\n    const duration=track.duration * track.timescale;\n    const width=track.width;\n    const height=track.height;\n    const upperWordDuration=Math.floor(duration / (UINT32_MAX + 1));\n    const lowerWordDuration=Math.floor(duration % (UINT32_MAX + 1));\n    return MP4.box(MP4.types.tkhd, new Uint8Array([0x01,\n    // version 1\n    0x00, 0x00, 0x07,\n    // flags\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,\n    // creation_time\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,\n    // modification_time\n    id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,\n    // track_ID\n    0x00, 0x00, 0x00, 0x00,\n    // reserved\n    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    // reserved\n    0x00, 0x00,\n    // layer\n    0x00, 0x00,\n    // alternate_group\n    0x00, 0x00,\n    // non-audio track volume\n    0x00, 0x00,\n    // reserved\n    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,\n    // transformation: unity matrix\n    width >> 8 & 0xff, width & 0xff, 0x00, 0x00,\n    // width\n    height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height\n    ]));\n  }\n  static traf(track, baseMediaDecodeTime){\n    const sampleDependencyTable=MP4.sdtp(track);\n    const id=track.id;\n    const upperWordBaseMediaDecodeTime=Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));\n    const lowerWordBaseMediaDecodeTime=Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));\n    return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID\n    ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01,\n    // version 1\n    0x00, 0x00, 0x00,\n    // flags\n    upperWordBaseMediaDecodeTime >> 24, upperWordBaseMediaDecodeTime >> 16 & 0xff, upperWordBaseMediaDecodeTime >> 8 & 0xff, upperWordBaseMediaDecodeTime & 0xff, lowerWordBaseMediaDecodeTime >> 24, lowerWordBaseMediaDecodeTime >> 16 & 0xff, lowerWordBaseMediaDecodeTime >> 8 & 0xff, lowerWordBaseMediaDecodeTime & 0xff])), MP4.trun(track, sampleDependencyTable.length + 16 +\n    // tfhd\n    20 +\n    // tfdt\n    8 +\n    // traf header\n    16 +\n    // mfhd\n    8 +\n    // moof header\n    8),\n    // mdat header\n    sampleDependencyTable);\n  }\n\n  \n  static trak(track){\n    track.duration=track.duration||0xffffffff;\n    return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));\n  }\n  static trex(track){\n    const id=track.id;\n    return MP4.box(MP4.types.trex, new Uint8Array([0x00,\n    // version 0\n    0x00, 0x00, 0x00,\n    // flags\n    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,\n    // track_ID\n    0x00, 0x00, 0x00, 0x01,\n    // default_sample_description_index\n    0x00, 0x00, 0x00, 0x00,\n    // default_sample_duration\n    0x00, 0x00, 0x00, 0x00,\n    // default_sample_size\n    0x00, 0x01, 0x00, 0x01 // default_sample_flags\n    ]));\n  }\n  static trun(track, offset){\n    const samples=track.samples||[];\n    const len=samples.length;\n    const arraylen=12 + 16 * len;\n    const array=new Uint8Array(arraylen);\n    let i;\n    let sample;\n    let duration;\n    let size;\n    let flags;\n    let cts;\n    offset +=8 + arraylen;\n    array.set([track.type==='video' ? 0x01:0x00,\n    // version 1 for video with signed-int sample_composition_time_offset\n    0x00, 0x0f, 0x01,\n    // flags\n    len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,\n    // sample_count\n    offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset\n    ], 0);\n    for (i=0; i < len; i++){\n      sample=samples[i];\n      duration=sample.duration;\n      size=sample.size;\n      flags=sample.flags;\n      cts=sample.cts;\n      array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,\n      // sample_duration\n      size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,\n      // sample_size\n      flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,\n      // sample_flags\n      cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset\n      ], 12 + 16 * i);\n    }\n    return MP4.box(MP4.types.trun, array);\n  }\n  static initSegment(tracks){\n    if(!MP4.types){\n      MP4.init();\n    }\n    const movie=MP4.moov(tracks);\n    const result=appendUint8Array(MP4.FTYP, movie);\n    return result;\n  }\n}\nMP4.types=void 0;\nMP4.HDLR_TYPES=void 0;\nMP4.STTS=void 0;\nMP4.STSC=void 0;\nMP4.STCO=void 0;\nMP4.STSZ=void 0;\nMP4.VMHD=void 0;\nMP4.SMHD=void 0;\nMP4.STSD=void 0;\nMP4.FTYP=void 0;\nMP4.DINF=void 0;\n\nconst MPEG_TS_CLOCK_FREQ_HZ=90000;\nfunction toTimescaleFromBase(baseTime, destScale, srcBase=1, round=false){\n  const result=baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)`\n  return round ? Math.round(result):result;\n}\nfunction toTimescaleFromScale(baseTime, destScale, srcScale=1, round=false){\n  return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round);\n}\nfunction toMsFromMpegTsClock(baseTime, round=false){\n  return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round);\n}\nfunction toMpegTsClockFromTimescale(baseTime, srcScale=1){\n  return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale);\n}\n\nconst MAX_SILENT_FRAME_DURATION=10 * 1000; // 10 seconds\nconst AAC_SAMPLES_PER_FRAME=1024;\nconst MPEG_AUDIO_SAMPLE_PER_FRAME=1152;\nconst AC3_SAMPLES_PER_FRAME=1536;\nlet chromeVersion=null;\nlet safariWebkitVersion=null;\nclass MP4Remuxer {\n  constructor(observer, config, typeSupported, vendor=''){\n    this.observer=void 0;\n    this.config=void 0;\n    this.typeSupported=void 0;\n    this.ISGenerated=false;\n    this._initPTS=null;\n    this._initDTS=null;\n    this.nextAvcDts=null;\n    this.nextAudioPts=null;\n    this.videoSampleDuration=null;\n    this.isAudioContiguous=false;\n    this.isVideoContiguous=false;\n    this.videoTrackConfig=void 0;\n    this.observer=observer;\n    this.config=config;\n    this.typeSupported=typeSupported;\n    this.ISGenerated=false;\n    if(chromeVersion===null){\n      const userAgent=navigator.userAgent||'';\n      const result=userAgent.match(/Chrome\\/(\\d+)/i);\n      chromeVersion=result ? parseInt(result[1]):0;\n    }\n    if(safariWebkitVersion===null){\n      const result=navigator.userAgent.match(/Safari\\/(\\d+)/i);\n      safariWebkitVersion=result ? parseInt(result[1]):0;\n    }\n  }\n  destroy(){\n    // @ts-ignore\n    this.config=this.videoTrackConfig=this._initPTS=this._initDTS=null;\n  }\n  resetTimeStamp(defaultTimeStamp){\n    logger.log('[mp4-remuxer]: initPTS & initDTS reset');\n    this._initPTS=this._initDTS=defaultTimeStamp;\n  }\n  resetNextTimestamp(){\n    logger.log('[mp4-remuxer]: reset next timestamp');\n    this.isVideoContiguous=false;\n    this.isAudioContiguous=false;\n  }\n  resetInitSegment(){\n    logger.log('[mp4-remuxer]: ISGenerated flag reset');\n    this.ISGenerated=false;\n    this.videoTrackConfig=undefined;\n  }\n  getVideoStartPts(videoSamples){\n    // Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping\n    let rolloverDetected=false;\n    const firstPts=videoSamples[0].pts;\n    const startPTS=videoSamples.reduce((minPTS, sample)=> {\n      let pts=sample.pts;\n      let delta=pts - minPTS;\n      if(delta < -4294967296){\n        // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation\n        rolloverDetected=true;\n        pts=normalizePts(pts, firstPts);\n        delta=pts - minPTS;\n      }\n      if(delta > 0){\n        return minPTS;\n      }\n      return pts;\n    }, firstPts);\n    if(rolloverDetected){\n      logger.debug('PTS rollover detected');\n    }\n    return startPTS;\n  }\n  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType){\n    let video;\n    let audio;\n    let initSegment;\n    let text;\n    let id3;\n    let independent;\n    let audioTimeOffset=timeOffset;\n    let videoTimeOffset=timeOffset;\n\n    // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding.\n    // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the \"pid\"\n    // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list.\n    // However, if the initSegment has already been generated, or we've reached the end of a segment (flush),\n    // then we can remux one track without waiting for the other.\n    const hasAudio=audioTrack.pid > -1;\n    const hasVideo=videoTrack.pid > -1;\n    const length=videoTrack.samples.length;\n    const enoughAudioSamples=audioTrack.samples.length > 0;\n    const enoughVideoSamples=flush&&length > 0||length > 1;\n    const canRemuxAvc=(!hasAudio||enoughAudioSamples)&&(!hasVideo||enoughVideoSamples)||this.ISGenerated||flush;\n    if(canRemuxAvc){\n      if(this.ISGenerated){\n        var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;\n        const config=this.videoTrackConfig;\n        if(config&&(videoTrack.width!==config.width||videoTrack.height!==config.height||((_videoTrack$pixelRati=videoTrack.pixelRatio)==null ? void 0:_videoTrack$pixelRati[0])!==((_config$pixelRatio=config.pixelRatio)==null ? void 0:_config$pixelRatio[0])||((_videoTrack$pixelRati2=videoTrack.pixelRatio)==null ? void 0:_videoTrack$pixelRati2[1])!==((_config$pixelRatio2=config.pixelRatio)==null ? void 0:_config$pixelRatio2[1]))){\n          this.resetInitSegment();\n        }\n      }else{\n        initSegment=this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);\n      }\n      const isVideoContiguous=this.isVideoContiguous;\n      let firstKeyFrameIndex=-1;\n      let firstKeyFramePTS;\n      if(enoughVideoSamples){\n        firstKeyFrameIndex=findKeyframeIndex(videoTrack.samples);\n        if(!isVideoContiguous&&this.config.forceKeyFrameOnDiscontinuity){\n          independent=true;\n          if(firstKeyFrameIndex > 0){\n            logger.warn(`[mp4-remuxer]: Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`);\n            const startPTS=this.getVideoStartPts(videoTrack.samples);\n            videoTrack.samples=videoTrack.samples.slice(firstKeyFrameIndex);\n            videoTrack.dropped +=firstKeyFrameIndex;\n            videoTimeOffset +=(videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale;\n            firstKeyFramePTS=videoTimeOffset;\n          }else if(firstKeyFrameIndex===-1){\n            logger.warn(`[mp4-remuxer]: No keyframe found out of ${length} video samples`);\n            independent=false;\n          }\n        }\n      }\n      if(this.ISGenerated){\n        if(enoughAudioSamples&&enoughVideoSamples){\n          // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS)\n          // if first audio DTS is not aligned with first video DTS then we need to take that into account\n          // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small\n          // drift between audio and video streams\n          const startPTS=this.getVideoStartPts(videoTrack.samples);\n          const tsDelta=normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS;\n          const audiovideoTimestampDelta=tsDelta / videoTrack.inputTimeScale;\n          audioTimeOffset +=Math.max(0, audiovideoTimestampDelta);\n          videoTimeOffset +=Math.max(0, -audiovideoTimestampDelta);\n        }\n\n        // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio.\n        if(enoughAudioSamples){\n          // if initSegment was generated without audio samples, regenerate it again\n          if(!audioTrack.samplerate){\n            logger.warn('[mp4-remuxer]: regenerate InitSegment as audio detected');\n            initSegment=this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);\n          }\n          audio=this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo||enoughVideoSamples||playlistType===PlaylistLevelType.AUDIO ? videoTimeOffset:undefined);\n          if(enoughVideoSamples){\n            const audioTrackLength=audio ? audio.endPTS - audio.startPTS:0;\n            // if initSegment was generated without video samples, regenerate it again\n            if(!videoTrack.inputTimeScale){\n              logger.warn('[mp4-remuxer]: regenerate InitSegment as video detected');\n              initSegment=this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);\n            }\n            video=this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength);\n          }\n        }else if(enoughVideoSamples){\n          video=this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0);\n        }\n        if(video){\n          video.firstKeyFrame=firstKeyFrameIndex;\n          video.independent=firstKeyFrameIndex!==-1;\n          video.firstKeyFramePTS=firstKeyFramePTS;\n        }\n      }\n    }\n\n    // Allow ID3 and text to remux, even if more audio/video samples are required\n    if(this.ISGenerated&&this._initPTS&&this._initDTS){\n      if(id3Track.samples.length){\n        id3=flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS);\n      }\n      if(textTrack.samples.length){\n        text=flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS);\n      }\n    }\n    return {\n      audio,\n      video,\n      initSegment,\n      independent,\n      text,\n      id3\n    };\n  }\n  generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset){\n    const audioSamples=audioTrack.samples;\n    const videoSamples=videoTrack.samples;\n    const typeSupported=this.typeSupported;\n    const tracks={};\n    const _initPTS=this._initPTS;\n    let computePTSDTS = !_initPTS||accurateTimeOffset;\n    let container='audio/mp4';\n    let initPTS;\n    let initDTS;\n    let timescale;\n    if(computePTSDTS){\n      initPTS=initDTS=Infinity;\n    }\n    if(audioTrack.config&&audioSamples.length){\n      // let's use audio sampling rate as MP4 time scale.\n      // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC)\n      // using audio sampling rate here helps having an integer MP4 frame duration\n      // this avoids potential rounding issue and AV sync issue\n      audioTrack.timescale=audioTrack.samplerate;\n      switch (audioTrack.segmentCodec){\n        case 'mp3':\n          if(typeSupported.mpeg){\n            // Chrome and Safari\n            container='audio/mpeg';\n            audioTrack.codec='';\n          }else if(typeSupported.mp3){\n            // Firefox\n            audioTrack.codec='mp3';\n          }\n          break;\n        case 'ac3':\n          audioTrack.codec='ac-3';\n          break;\n      }\n      tracks.audio={\n        id: 'audio',\n        container: container,\n        codec: audioTrack.codec,\n        initSegment: audioTrack.segmentCodec==='mp3'&&typeSupported.mpeg ? new Uint8Array(0):MP4.initSegment([audioTrack]),\n        metadata: {\n          channelCount: audioTrack.channelCount\n        }\n      };\n      if(computePTSDTS){\n        timescale=audioTrack.inputTimeScale;\n        if(!_initPTS||timescale!==_initPTS.timescale){\n          // remember first PTS of this demuxing context. for audio, PTS=DTS\n          initPTS=initDTS=audioSamples[0].pts - Math.round(timescale * timeOffset);\n        }else{\n          computePTSDTS=false;\n        }\n      }\n    }\n    if(videoTrack.sps&&videoTrack.pps&&videoSamples.length){\n      // let's use input time scale as MP4 video timescale\n      // we use input time scale straight away to avoid rounding issues on frame duration / cts computation\n      videoTrack.timescale=videoTrack.inputTimeScale;\n      tracks.video={\n        id: 'main',\n        container: 'video/mp4',\n        codec: videoTrack.codec,\n        initSegment: MP4.initSegment([videoTrack]),\n        metadata: {\n          width: videoTrack.width,\n          height: videoTrack.height\n        }\n      };\n      if(computePTSDTS){\n        timescale=videoTrack.inputTimeScale;\n        if(!_initPTS||timescale!==_initPTS.timescale){\n          const startPTS=this.getVideoStartPts(videoSamples);\n          const startOffset=Math.round(timescale * timeOffset);\n          initDTS=Math.min(initDTS, normalizePts(videoSamples[0].dts, startPTS) - startOffset);\n          initPTS=Math.min(initPTS, startPTS - startOffset);\n        }else{\n          computePTSDTS=false;\n        }\n      }\n      this.videoTrackConfig={\n        width: videoTrack.width,\n        height: videoTrack.height,\n        pixelRatio: videoTrack.pixelRatio\n      };\n    }\n    if(Object.keys(tracks).length){\n      this.ISGenerated=true;\n      if(computePTSDTS){\n        this._initPTS={\n          baseTime: initPTS,\n          timescale: timescale\n        };\n        this._initDTS={\n          baseTime: initDTS,\n          timescale: timescale\n        };\n      }else{\n        initPTS=timescale=undefined;\n      }\n      return {\n        tracks,\n        initPTS,\n        timescale\n      };\n    }\n  }\n  remuxVideo(track, timeOffset, contiguous, audioTrackLength){\n    const timeScale=track.inputTimeScale;\n    const inputSamples=track.samples;\n    const outputSamples=[];\n    const nbSamples=inputSamples.length;\n    const initPTS=this._initPTS;\n    let nextAvcDts=this.nextAvcDts;\n    let offset=8;\n    let mp4SampleDuration=this.videoSampleDuration;\n    let firstDTS;\n    let lastDTS;\n    let minPTS=Number.POSITIVE_INFINITY;\n    let maxPTS=Number.NEGATIVE_INFINITY;\n    let sortSamples=false;\n\n    // if parsed fragment is contiguous with last one, let's use last DTS value as reference\n    if(!contiguous||nextAvcDts===null){\n      const pts=timeOffset * timeScale;\n      const cts=inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts);\n      if(chromeVersion&&nextAvcDts!==null&&Math.abs(pts - cts - nextAvcDts) < 15000){\n        // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome\n        contiguous=true;\n      }else{\n        // if not contiguous, let's use target timeOffset\n        nextAvcDts=pts - cts;\n      }\n    }\n\n    // PTS is coded on 33bits, and can loop from -2^32 to 2^32\n    // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value\n    const initTime=initPTS.baseTime * timeScale / initPTS.timescale;\n    for (let i=0; i < nbSamples; i++){\n      const sample=inputSamples[i];\n      sample.pts=normalizePts(sample.pts - initTime, nextAvcDts);\n      sample.dts=normalizePts(sample.dts - initTime, nextAvcDts);\n      if(sample.dts < inputSamples[i > 0 ? i - 1:i].dts){\n        sortSamples=true;\n      }\n    }\n\n    // sort video samples by DTS then PTS then demux id order\n    if(sortSamples){\n      inputSamples.sort(function (a, b){\n        const deltadts=a.dts - b.dts;\n        const deltapts=a.pts - b.pts;\n        return deltadts||deltapts;\n      });\n    }\n\n    // Get first/last DTS\n    firstDTS=inputSamples[0].dts;\n    lastDTS=inputSamples[inputSamples.length - 1].dts;\n\n    // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS\n    // set this constant duration as being the avg delta between consecutive DTS.\n    const inputDuration=lastDTS - firstDTS;\n    const averageSampleDuration=inputDuration ? Math.round(inputDuration / (nbSamples - 1)):mp4SampleDuration||track.inputTimeScale / 30;\n\n    // if fragment are contiguous, detect hole/overlapping between fragments\n    if(contiguous){\n      // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole)\n      const delta=firstDTS - nextAvcDts;\n      const foundHole=delta > averageSampleDuration;\n      const foundOverlap=delta < -1;\n      if(foundHole||foundOverlap){\n        if(foundHole){\n          logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);\n        }else{\n          logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);\n        }\n        if(!foundOverlap||nextAvcDts >=inputSamples[0].pts||chromeVersion){\n          firstDTS=nextAvcDts;\n          const firstPTS=inputSamples[0].pts - delta;\n          if(foundHole){\n            inputSamples[0].dts=firstDTS;\n            inputSamples[0].pts=firstPTS;\n          }else{\n            for (let i=0; i < inputSamples.length; i++){\n              if(inputSamples[i].dts > firstPTS){\n                break;\n              }\n              inputSamples[i].dts -=delta;\n              inputSamples[i].pts -=delta;\n            }\n          }\n          logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);\n        }\n      }\n    }\n    firstDTS=Math.max(0, firstDTS);\n    let nbNalu=0;\n    let naluLen=0;\n    let dtsStep=firstDTS;\n    for (let i=0; i < nbSamples; i++){\n      // compute total/avc sample length and nb of NAL units\n      const sample=inputSamples[i];\n      const units=sample.units;\n      const nbUnits=units.length;\n      let sampleLen=0;\n      for (let j=0; j < nbUnits; j++){\n        sampleLen +=units[j].data.length;\n      }\n      naluLen +=sampleLen;\n      nbNalu +=nbUnits;\n      sample.length=sampleLen;\n\n      // ensure sample monotonic DTS\n      if(sample.dts < dtsStep){\n        sample.dts=dtsStep;\n        dtsStep +=averageSampleDuration / 4 | 0||1;\n      }else{\n        dtsStep=sample.dts;\n      }\n      minPTS=Math.min(sample.pts, minPTS);\n      maxPTS=Math.max(sample.pts, maxPTS);\n    }\n    lastDTS=inputSamples[nbSamples - 1].dts;\n\n    \n    const mdatSize=naluLen + 4 * nbNalu + 8;\n    let mdat;\n    try {\n      mdat=new Uint8Array(mdatSize);\n    } catch (err){\n      this.observer.emit(Events.ERROR, Events.ERROR, {\n        type: ErrorTypes.MUX_ERROR,\n        details: ErrorDetails.REMUX_ALLOC_ERROR,\n        fatal: false,\n        error: err,\n        bytes: mdatSize,\n        reason: `fail allocating video mdat ${mdatSize}`\n      });\n      return;\n    }\n    const view=new DataView(mdat.buffer);\n    view.setUint32(0, mdatSize);\n    mdat.set(MP4.types.mdat, 4);\n    let stretchedLastFrame=false;\n    let minDtsDelta=Number.POSITIVE_INFINITY;\n    let minPtsDelta=Number.POSITIVE_INFINITY;\n    let maxDtsDelta=Number.NEGATIVE_INFINITY;\n    let maxPtsDelta=Number.NEGATIVE_INFINITY;\n    for (let i=0; i < nbSamples; i++){\n      const VideoSample=inputSamples[i];\n      const VideoSampleUnits=VideoSample.units;\n      let mp4SampleLength=0;\n      // convert NALU bitstream to MP4 format (prepend NALU with size field)\n      for (let j=0, nbUnits=VideoSampleUnits.length; j < nbUnits; j++){\n        const unit=VideoSampleUnits[j];\n        const unitData=unit.data;\n        const unitDataLen=unit.data.byteLength;\n        view.setUint32(offset, unitDataLen);\n        offset +=4;\n        mdat.set(unitData, offset);\n        offset +=unitDataLen;\n        mp4SampleLength +=4 + unitDataLen;\n      }\n\n      // expected sample duration is the Decoding Timestamp diff of consecutive samples\n      let ptsDelta;\n      if(i < nbSamples - 1){\n        mp4SampleDuration=inputSamples[i + 1].dts - VideoSample.dts;\n        ptsDelta=inputSamples[i + 1].pts - VideoSample.pts;\n      }else{\n        const config=this.config;\n        const lastFrameDuration=i > 0 ? VideoSample.dts - inputSamples[i - 1].dts:averageSampleDuration;\n        ptsDelta=i > 0 ? VideoSample.pts - inputSamples[i - 1].pts:averageSampleDuration;\n        if(config.stretchShortVideoTrack&&this.nextAudioPts!==null){\n          // In some cases, a segment's audio track duration may exceed the video track duration.\n          // Since we've already remuxed audio, and we know how long the audio track is, we look to\n          // see if the delta to the next segment is longer than maxBufferHole.\n          // If so, playback would potentially get stuck, so we artificially inflate\n          // the duration of the last frame to minimize any potential gap between segments.\n          const gapTolerance=Math.floor(config.maxBufferHole * timeScale);\n          const deltaToFrameEnd=(audioTrackLength ? minPTS + audioTrackLength * timeScale:this.nextAudioPts) - VideoSample.pts;\n          if(deltaToFrameEnd > gapTolerance){\n            // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video\n            // frame overlap. maxBufferHole should be >> lastFrameDuration anyway.\n            mp4SampleDuration=deltaToFrameEnd - lastFrameDuration;\n            if(mp4SampleDuration < 0){\n              mp4SampleDuration=lastFrameDuration;\n            }else{\n              stretchedLastFrame=true;\n            }\n            logger.log(`[mp4-remuxer]: It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`);\n          }else{\n            mp4SampleDuration=lastFrameDuration;\n          }\n        }else{\n          mp4SampleDuration=lastFrameDuration;\n        }\n      }\n      const compositionTimeOffset=Math.round(VideoSample.pts - VideoSample.dts);\n      minDtsDelta=Math.min(minDtsDelta, mp4SampleDuration);\n      maxDtsDelta=Math.max(maxDtsDelta, mp4SampleDuration);\n      minPtsDelta=Math.min(minPtsDelta, ptsDelta);\n      maxPtsDelta=Math.max(maxPtsDelta, ptsDelta);\n      outputSamples.push(new Mp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset));\n    }\n    if(outputSamples.length){\n      if(chromeVersion){\n        if(chromeVersion < 70){\n          // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue\n          // https://code.google.com/p/chromium/issues/detail?id=229412\n          const flags=outputSamples[0].flags;\n          flags.dependsOn=2;\n          flags.isNonSync=0;\n        }\n      }else if(safariWebkitVersion){\n        // Fix for \"CNN special report, with CC\" in test-streams (Safari browser only)\n        // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps.\n        if(maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta&&averageSampleDuration / maxDtsDelta < 0.025&&outputSamples[0].cts===0){\n          logger.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.');\n          let dts=firstDTS;\n          for (let i=0, len=outputSamples.length; i < len; i++){\n            const nextDts=dts + outputSamples[i].duration;\n            const pts=dts + outputSamples[i].cts;\n            if(i < len - 1){\n              const nextPts=nextDts + outputSamples[i + 1].cts;\n              outputSamples[i].duration=nextPts - pts;\n            }else{\n              outputSamples[i].duration=i ? outputSamples[i - 1].duration:averageSampleDuration;\n            }\n            outputSamples[i].cts=0;\n            dts=nextDts;\n          }\n        }\n      }\n    }\n    // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)\n    mp4SampleDuration=stretchedLastFrame||!mp4SampleDuration ? averageSampleDuration:mp4SampleDuration;\n    this.nextAvcDts=nextAvcDts=lastDTS + mp4SampleDuration;\n    this.videoSampleDuration=mp4SampleDuration;\n    this.isVideoContiguous=true;\n    const moof=MP4.moof(track.sequenceNumber++, firstDTS, _extends({}, track, {\n      samples: outputSamples\n    }));\n    const type='video';\n    const data={\n      data1: moof,\n      data2: mdat,\n      startPTS: minPTS / timeScale,\n      endPTS: (maxPTS + mp4SampleDuration) / timeScale,\n      startDTS: firstDTS / timeScale,\n      endDTS: nextAvcDts / timeScale,\n      type,\n      hasAudio: false,\n      hasVideo: true,\n      nb: outputSamples.length,\n      dropped: track.dropped\n    };\n    track.samples=[];\n    track.dropped=0;\n    return data;\n  }\n  getSamplesPerFrame(track){\n    switch (track.segmentCodec){\n      case 'mp3':\n        return MPEG_AUDIO_SAMPLE_PER_FRAME;\n      case 'ac3':\n        return AC3_SAMPLES_PER_FRAME;\n      default:\n        return AAC_SAMPLES_PER_FRAME;\n    }\n  }\n  remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset){\n    const inputTimeScale=track.inputTimeScale;\n    const mp4timeScale=track.samplerate ? track.samplerate:inputTimeScale;\n    const scaleFactor=inputTimeScale / mp4timeScale;\n    const mp4SampleDuration=this.getSamplesPerFrame(track);\n    const inputSampleDuration=mp4SampleDuration * scaleFactor;\n    const initPTS=this._initPTS;\n    const rawMPEG=track.segmentCodec==='mp3'&&this.typeSupported.mpeg;\n    const outputSamples=[];\n    const alignedWithVideo=videoTimeOffset!==undefined;\n    let inputSamples=track.samples;\n    let offset=rawMPEG ? 0:8;\n    let nextAudioPts=this.nextAudioPts||-1;\n\n    // window.audioSamples ? window.audioSamples.push(inputSamples.map(s=> s.pts)):(window.audioSamples=[inputSamples.map(s=> s.pts)]);\n\n    // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs),\n    // for sake of clarity:\n    // consecutive fragments are frags with\n    //  - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR\n    //  - less than 20 audio frames distance\n    // contiguous fragments are consecutive fragments from same quality level (same level, new SN=old SN + 1)\n    // this helps ensuring audio continuity\n    // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame\n    const timeOffsetMpegTS=timeOffset * inputTimeScale;\n    const initTime=initPTS.baseTime * inputTimeScale / initPTS.timescale;\n    this.isAudioContiguous=contiguous=contiguous||inputSamples.length&&nextAudioPts > 0&&(accurateTimeOffset&&Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000||Math.abs(normalizePts(inputSamples[0].pts - initTime, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration);\n\n    // compute normalized PTS\n    inputSamples.forEach(function (sample){\n      sample.pts=normalizePts(sample.pts - initTime, timeOffsetMpegTS);\n    });\n    if(!contiguous||nextAudioPts < 0){\n      // filter out sample with negative PTS that are not playable anyway\n      // if we don't remove these negative samples, they will shift all audio samples forward.\n      // leading to audio overlap between current / next fragment\n      inputSamples=inputSamples.filter(sample=> sample.pts >=0);\n\n      // in case all samples have negative PTS, and have been filtered out, return now\n      if(!inputSamples.length){\n        return;\n      }\n      if(videoTimeOffset===0){\n        // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence\n        nextAudioPts=0;\n      }else if(accurateTimeOffset&&!alignedWithVideo){\n        // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS\n        nextAudioPts=Math.max(0, timeOffsetMpegTS);\n      }else{\n        // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS\n        nextAudioPts=inputSamples[0].pts;\n      }\n    }\n\n    // If the audio track is missing samples, the frames seem to get \"left-shifted\" within the\n    // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment.\n    // In an effort to prevent this from happening, we inject frames here where there are gaps.\n    // When possible, we inject a silent frame; when that's not possible, we duplicate the last\n    // frame.\n\n    if(track.segmentCodec==='aac'){\n      const maxAudioFramesDrift=this.config.maxAudioFramesDrift;\n      for (let i=0, nextPts=nextAudioPts; i < inputSamples.length; i++){\n        // First, let's see how far off this frame is from where we expect it to be\n        const sample=inputSamples[i];\n        const pts=sample.pts;\n        const delta=pts - nextPts;\n        const duration=Math.abs(1000 * delta / inputTimeScale);\n\n        // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync\n        if(delta <=-maxAudioFramesDrift * inputSampleDuration&&alignedWithVideo){\n          if(i===0){\n            logger.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps nextAudioPts by ${Math.round(1000 * delta / inputTimeScale)} ms.`);\n            this.nextAudioPts=nextAudioPts=nextPts=pts;\n          }\n        } // eslint-disable-line brace-style\n\n        // Insert missing frames if:\n        // 1: We're more than maxAudioFramesDrift frame away\n        // 2: Not more than MAX_SILENT_FRAME_DURATION away\n        // 3: currentTime (aka nextPtsNorm) is not 0\n        // 4: remuxing with video (videoTimeOffset!==undefined)\n        else if(delta >=maxAudioFramesDrift * inputSampleDuration&&duration < MAX_SILENT_FRAME_DURATION&&alignedWithVideo){\n          let missing=Math.round(delta / inputSampleDuration);\n          // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from\n          // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration.\n          nextPts=pts - missing * inputSampleDuration;\n          if(nextPts < 0){\n            missing--;\n            nextPts +=inputSampleDuration;\n          }\n          if(i===0){\n            this.nextAudioPts=nextAudioPts=nextPts;\n          }\n          logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);\n          for (let j=0; j < missing; j++){\n            const newStamp=Math.max(nextPts, 0);\n            let fillFrame=AAC.getSilentFrame(track.manifestCodec||track.codec, track.channelCount);\n            if(!fillFrame){\n              logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');\n              fillFrame=sample.unit.subarray();\n            }\n            inputSamples.splice(i, 0, {\n              unit: fillFrame,\n              pts: newStamp\n            });\n            nextPts +=inputSampleDuration;\n            i++;\n          }\n        }\n        sample.pts=nextPts;\n        nextPts +=inputSampleDuration;\n      }\n    }\n    let firstPTS=null;\n    let lastPTS=null;\n    let mdat;\n    let mdatSize=0;\n    let sampleLength=inputSamples.length;\n    while (sampleLength--){\n      mdatSize +=inputSamples[sampleLength].unit.byteLength;\n    }\n    for (let j=0, _nbSamples=inputSamples.length; j < _nbSamples; j++){\n      const audioSample=inputSamples[j];\n      const unit=audioSample.unit;\n      let pts=audioSample.pts;\n      if(lastPTS!==null){\n        // If we have more than one sample, set the duration of the sample to the \"real\" duration; the PTS diff with\n        // the previous sample\n        const prevSample=outputSamples[j - 1];\n        prevSample.duration=Math.round((pts - lastPTS) / scaleFactor);\n      }else{\n        if(contiguous&&track.segmentCodec==='aac'){\n          // set PTS/DTS to expected PTS/DTS\n          pts=nextAudioPts;\n        }\n        // remember first PTS of our audioSamples\n        firstPTS=pts;\n        if(mdatSize > 0){\n          \n          mdatSize +=offset;\n          try {\n            mdat=new Uint8Array(mdatSize);\n          } catch (err){\n            this.observer.emit(Events.ERROR, Events.ERROR, {\n              type: ErrorTypes.MUX_ERROR,\n              details: ErrorDetails.REMUX_ALLOC_ERROR,\n              fatal: false,\n              error: err,\n              bytes: mdatSize,\n              reason: `fail allocating audio mdat ${mdatSize}`\n            });\n            return;\n          }\n          if(!rawMPEG){\n            const view=new DataView(mdat.buffer);\n            view.setUint32(0, mdatSize);\n            mdat.set(MP4.types.mdat, 4);\n          }\n        }else{\n          // no audio samples\n          return;\n        }\n      }\n      mdat.set(unit, offset);\n      const unitLen=unit.byteLength;\n      offset +=unitLen;\n      // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG\n      // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration\n      // becomes the PTS diff with the previous sample\n      outputSamples.push(new Mp4Sample(true, mp4SampleDuration, unitLen, 0));\n      lastPTS=pts;\n    }\n\n    // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones\n    const nbSamples=outputSamples.length;\n    if(!nbSamples){\n      return;\n    }\n\n    // The next audio sample PTS should be equal to last sample PTS + duration\n    const lastSample=outputSamples[outputSamples.length - 1];\n    this.nextAudioPts=nextAudioPts=lastPTS + scaleFactor * lastSample.duration;\n\n    // Set the track samples from inputSamples to outputSamples before remuxing\n    const moof=rawMPEG ? new Uint8Array(0):MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, {\n      samples: outputSamples\n    }));\n\n    // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared\n    track.samples=[];\n    const start=firstPTS / inputTimeScale;\n    const end=nextAudioPts / inputTimeScale;\n    const type='audio';\n    const audioData={\n      data1: moof,\n      data2: mdat,\n      startPTS: start,\n      endPTS: end,\n      startDTS: start,\n      endDTS: end,\n      type,\n      hasAudio: true,\n      hasVideo: false,\n      nb: nbSamples\n    };\n    this.isAudioContiguous=true;\n    return audioData;\n  }\n  remuxEmptyAudio(track, timeOffset, contiguous, videoData){\n    const inputTimeScale=track.inputTimeScale;\n    const mp4timeScale=track.samplerate ? track.samplerate:inputTimeScale;\n    const scaleFactor=inputTimeScale / mp4timeScale;\n    const nextAudioPts=this.nextAudioPts;\n    // sync with video's timestamp\n    const initDTS=this._initDTS;\n    const init90kHz=initDTS.baseTime * 90000 / initDTS.timescale;\n    const startDTS=(nextAudioPts!==null ? nextAudioPts:videoData.startDTS * inputTimeScale) + init90kHz;\n    const endDTS=videoData.endDTS * inputTimeScale + init90kHz;\n    // one sample's duration value\n    const frameDuration=scaleFactor * AAC_SAMPLES_PER_FRAME;\n    // samples count of this segment's duration\n    const nbSamples=Math.ceil((endDTS - startDTS) / frameDuration);\n    // silent frame\n    const silentFrame=AAC.getSilentFrame(track.manifestCodec||track.codec, track.channelCount);\n    logger.warn('[mp4-remuxer]: remux empty Audio');\n    // Can't remux if we can't generate a silent frame...\n    if(!silentFrame){\n      logger.trace('[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec');\n      return;\n    }\n    const samples=[];\n    for (let i=0; i < nbSamples; i++){\n      const stamp=startDTS + i * frameDuration;\n      samples.push({\n        unit: silentFrame,\n        pts: stamp,\n        dts: stamp\n      });\n    }\n    track.samples=samples;\n    return this.remuxAudio(track, timeOffset, contiguous, false);\n  }\n}\nfunction normalizePts(value, reference){\n  let offset;\n  if(reference===null){\n    return value;\n  }\n  if(reference < value){\n    // - 2^33\n    offset=-8589934592;\n  }else{\n    // + 2^33\n    offset=8589934592;\n  }\n  \n  while (Math.abs(value - reference) > 4294967296){\n    value +=offset;\n  }\n  return value;\n}\nfunction findKeyframeIndex(samples){\n  for (let i=0; i < samples.length; i++){\n    if(samples[i].key){\n      return i;\n    }\n  }\n  return -1;\n}\nfunction flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS){\n  const length=track.samples.length;\n  if(!length){\n    return;\n  }\n  const inputTimeScale=track.inputTimeScale;\n  for (let index=0; index < length; index++){\n    const sample=track.samples[index];\n    // setting id3 pts, dts to relative time\n    // using this._initPTS and this._initDTS to calculate relative time\n    sample.pts=normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;\n    sample.dts=normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;\n  }\n  const samples=track.samples;\n  track.samples=[];\n  return {\n    samples\n  };\n}\nfunction flushTextTrackUserdataCueSamples(track, timeOffset, initPTS){\n  const length=track.samples.length;\n  if(!length){\n    return;\n  }\n  const inputTimeScale=track.inputTimeScale;\n  for (let index=0; index < length; index++){\n    const sample=track.samples[index];\n    // setting text pts, dts to relative time\n    // using this._initPTS and this._initDTS to calculate relative time\n    sample.pts=normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;\n  }\n  track.samples.sort((a, b)=> a.pts - b.pts);\n  const samples=track.samples;\n  track.samples=[];\n  return {\n    samples\n  };\n}\nclass Mp4Sample {\n  constructor(isKeyframe, duration, size, cts){\n    this.size=void 0;\n    this.duration=void 0;\n    this.cts=void 0;\n    this.flags=void 0;\n    this.duration=duration;\n    this.size=size;\n    this.cts=cts;\n    this.flags={\n      isLeading: 0,\n      isDependedOn: 0,\n      hasRedundancy: 0,\n      degradPrio: 0,\n      dependsOn: isKeyframe ? 2:1,\n      isNonSync: isKeyframe ? 0:1\n    };\n  }\n}\n\nclass PassThroughRemuxer {\n  constructor(){\n    this.emitInitSegment=false;\n    this.audioCodec=void 0;\n    this.videoCodec=void 0;\n    this.initData=void 0;\n    this.initPTS=null;\n    this.initTracks=void 0;\n    this.lastEndTime=null;\n  }\n  destroy(){}\n  resetTimeStamp(defaultInitPTS){\n    this.initPTS=defaultInitPTS;\n    this.lastEndTime=null;\n  }\n  resetNextTimestamp(){\n    this.lastEndTime=null;\n  }\n  resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata){\n    this.audioCodec=audioCodec;\n    this.videoCodec=videoCodec;\n    this.generateInitSegment(patchEncyptionData(initSegment, decryptdata));\n    this.emitInitSegment=true;\n  }\n  generateInitSegment(initSegment){\n    let {\n      audioCodec,\n      videoCodec\n    }=this;\n    if(!(initSegment!=null&&initSegment.byteLength)){\n      this.initTracks=undefined;\n      this.initData=undefined;\n      return;\n    }\n    const initData=this.initData=parseInitSegment(initSegment);\n\n    // Get codec from initSegment or fallback to default\n    if(initData.audio){\n      audioCodec=getParsedTrackCodec(initData.audio, ElementaryStreamTypes.AUDIO);\n    }\n    if(initData.video){\n      videoCodec=getParsedTrackCodec(initData.video, ElementaryStreamTypes.VIDEO);\n    }\n    const tracks={};\n    if(initData.audio&&initData.video){\n      tracks.audiovideo={\n        container: 'video/mp4',\n        codec: audioCodec + ',' + videoCodec,\n        initSegment,\n        id: 'main'\n      };\n    }else if(initData.audio){\n      tracks.audio={\n        container: 'audio/mp4',\n        codec: audioCodec,\n        initSegment,\n        id: 'audio'\n      };\n    }else if(initData.video){\n      tracks.video={\n        container: 'video/mp4',\n        codec: videoCodec,\n        initSegment,\n        id: 'main'\n      };\n    }else{\n      logger.warn('[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.');\n    }\n    this.initTracks=tracks;\n  }\n  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset){\n    var _initData, _initData2;\n    let {\n      initPTS,\n      lastEndTime\n    }=this;\n    const result={\n      audio: undefined,\n      video: undefined,\n      text: textTrack,\n      id3: id3Track,\n      initSegment: undefined\n    };\n\n    // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the\n    // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update\n    // the media duration (which is what timeOffset is provided as) before we need to process the next chunk.\n    if(!isFiniteNumber(lastEndTime)){\n      lastEndTime=this.lastEndTime=timeOffset||0;\n    }\n\n    // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only\n    // audio or video (or both); adding it to video was an arbitrary choice.\n    const data=videoTrack.samples;\n    if(!(data!=null&&data.length)){\n      return result;\n    }\n    const initSegment={\n      initPTS: undefined,\n      timescale: 1\n    };\n    let initData=this.initData;\n    if(!((_initData=initData)!=null&&_initData.length)){\n      this.generateInitSegment(data);\n      initData=this.initData;\n    }\n    if(!((_initData2=initData)!=null&&_initData2.length)){\n      // We can't remux if the initSegment could not be generated\n      logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.');\n      return result;\n    }\n    if(this.emitInitSegment){\n      initSegment.tracks=this.initTracks;\n      this.emitInitSegment=false;\n    }\n    const duration=getDuration(data, initData);\n    const startDTS=getStartDTS(initData, data);\n    const decodeTime=startDTS===null ? timeOffset:startDTS;\n    if(isInvalidInitPts(initPTS, decodeTime, timeOffset, duration)||initSegment.timescale!==initPTS.timescale&&accurateTimeOffset){\n      initSegment.initPTS=decodeTime - timeOffset;\n      if(initPTS&&initPTS.timescale===1){\n        logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`);\n      }\n      this.initPTS=initPTS={\n        baseTime: initSegment.initPTS,\n        timescale: 1\n      };\n    }\n    const startTime=audioTrack ? decodeTime - initPTS.baseTime / initPTS.timescale:lastEndTime;\n    const endTime=startTime + duration;\n    offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale);\n    if(duration > 0){\n      this.lastEndTime=endTime;\n    }else{\n      logger.warn('Duration parsed from mp4 should be greater than zero');\n      this.resetNextTimestamp();\n    }\n    const hasAudio = !!initData.audio;\n    const hasVideo = !!initData.video;\n    let type='';\n    if(hasAudio){\n      type +='audio';\n    }\n    if(hasVideo){\n      type +='video';\n    }\n    const track={\n      data1: data,\n      startPTS: startTime,\n      startDTS: startTime,\n      endPTS: endTime,\n      endDTS: endTime,\n      type,\n      hasAudio,\n      hasVideo,\n      nb: 1,\n      dropped: 0\n    };\n    result.audio=track.type==='audio' ? track:undefined;\n    result.video=track.type!=='audio' ? track:undefined;\n    result.initSegment=initSegment;\n    result.id3=flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS);\n    if(textTrack.samples.length){\n      result.text=flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS);\n    }\n    return result;\n  }\n}\nfunction isInvalidInitPts(initPTS, startDTS, timeOffset, duration){\n  if(initPTS===null){\n    return true;\n  }\n  // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second\n  const minDuration=Math.max(duration, 1);\n  const startTime=startDTS - initPTS.baseTime / initPTS.timescale;\n  return Math.abs(startTime - timeOffset) > minDuration;\n}\nfunction getParsedTrackCodec(track, type){\n  const parsedCodec=track==null ? void 0:track.codec;\n  if(parsedCodec&&parsedCodec.length > 4){\n    return parsedCodec;\n  }\n  if(type===ElementaryStreamTypes.AUDIO){\n    if(parsedCodec==='ec-3'||parsedCodec==='ac-3'||parsedCodec==='alac'){\n      return parsedCodec;\n    }\n    if(parsedCodec==='fLaC'||parsedCodec==='Opus'){\n      // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity\n      const preferManagedMediaSource=false;\n      return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);\n    }\n    const result='mp4a.40.5';\n    logger.info(`Parsed audio codec \"${parsedCodec}\" or audio object type not handled. Using \"${result}\"`);\n    return result;\n  }\n  // Provide defaults based on codec type\n  // This allows for some playback of some fmp4 playlists without CODECS defined in manifest\n  logger.warn(`Unhandled video codec \"${parsedCodec}\"`);\n  if(parsedCodec==='hvc1'||parsedCodec==='hev1'){\n    return 'hvc1.1.6.L120.90';\n  }\n  if(parsedCodec==='av01'){\n    return 'av01.0.04M.08';\n  }\n  return 'avc1.42e01e';\n}\n\nlet now;\n// performance.now() not available on WebWorker, at least on Safari Desktop\ntry {\n  now=self.performance.now.bind(self.performance);\n} catch (err){\n  logger.debug('Unable to use Performance API on this environment');\n  now=optionalSelf==null ? void 0:optionalSelf.Date.now;\n}\nconst muxConfig=[{\n  demux: MP4Demuxer,\n  remux: PassThroughRemuxer\n}, {\n  demux: TSDemuxer,\n  remux: MP4Remuxer\n}, {\n  demux: AACDemuxer,\n  remux: MP4Remuxer\n}, {\n  demux: MP3Demuxer,\n  remux: MP4Remuxer\n}];\n{\n  muxConfig.splice(2, 0, {\n    demux: AC3Demuxer,\n    remux: MP4Remuxer\n  });\n}\nclass Transmuxer {\n  constructor(observer, typeSupported, config, vendor, id){\n    this.async=false;\n    this.observer=void 0;\n    this.typeSupported=void 0;\n    this.config=void 0;\n    this.vendor=void 0;\n    this.id=void 0;\n    this.demuxer=void 0;\n    this.remuxer=void 0;\n    this.decrypter=void 0;\n    this.probe=void 0;\n    this.decryptionPromise=null;\n    this.transmuxConfig=void 0;\n    this.currentTransmuxState=void 0;\n    this.observer=observer;\n    this.typeSupported=typeSupported;\n    this.config=config;\n    this.vendor=vendor;\n    this.id=id;\n  }\n  configure(transmuxConfig){\n    this.transmuxConfig=transmuxConfig;\n    if(this.decrypter){\n      this.decrypter.reset();\n    }\n  }\n  push(data, decryptdata, chunkMeta, state){\n    const stats=chunkMeta.transmuxing;\n    stats.executeStart=now();\n    let uintData=new Uint8Array(data);\n    const {\n      currentTransmuxState,\n      transmuxConfig\n    }=this;\n    if(state){\n      this.currentTransmuxState=state;\n    }\n    const {\n      contiguous,\n      discontinuity,\n      trackSwitch,\n      accurateTimeOffset,\n      timeOffset,\n      initSegmentChange\n    }=state||currentTransmuxState;\n    const {\n      audioCodec,\n      videoCodec,\n      defaultInitPts,\n      duration,\n      initSegmentData\n    }=transmuxConfig;\n    const keyData=getEncryptionType(uintData, decryptdata);\n    if(keyData&&keyData.method==='AES-128'){\n      const decrypter=this.getDecrypter();\n      // Software decryption is synchronous; webCrypto is not\n      if(decrypter.isSync()){\n        // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached\n        // data is handled in the flush() call\n        let decryptedData=decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);\n        // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress\n        const loadingParts=chunkMeta.part > -1;\n        if(loadingParts){\n          decryptedData=decrypter.flush();\n        }\n        if(!decryptedData){\n          stats.executeEnd=now();\n          return emptyResult(chunkMeta);\n        }\n        uintData=new Uint8Array(decryptedData);\n      }else{\n        this.decryptionPromise=decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData=> {\n          // Calling push here is important; if flush() is called while this is still resolving, this ensures that\n          // the decrypted data has been transmuxed\n          const result=this.push(decryptedData, null, chunkMeta);\n          this.decryptionPromise=null;\n          return result;\n        });\n        return this.decryptionPromise;\n      }\n    }\n    const resetMuxers=this.needsProbing(discontinuity, trackSwitch);\n    if(resetMuxers){\n      const error=this.configureTransmuxer(uintData);\n      if(error){\n        logger.warn(`[transmuxer] ${error.message}`);\n        this.observer.emit(Events.ERROR, Events.ERROR, {\n          type: ErrorTypes.MEDIA_ERROR,\n          details: ErrorDetails.FRAG_PARSING_ERROR,\n          fatal: false,\n          error,\n          reason: error.message\n        });\n        stats.executeEnd=now();\n        return emptyResult(chunkMeta);\n      }\n    }\n    if(discontinuity||trackSwitch||initSegmentChange||resetMuxers){\n      this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata);\n    }\n    if(discontinuity||initSegmentChange||resetMuxers){\n      this.resetInitialTimestamp(defaultInitPts);\n    }\n    if(!contiguous){\n      this.resetContiguity();\n    }\n    const result=this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta);\n    const currentState=this.currentTransmuxState;\n    currentState.contiguous=true;\n    currentState.discontinuity=false;\n    currentState.trackSwitch=false;\n    stats.executeEnd=now();\n    return result;\n  }\n\n  // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type)\n  flush(chunkMeta){\n    const stats=chunkMeta.transmuxing;\n    stats.executeStart=now();\n    const {\n      decrypter,\n      currentTransmuxState,\n      decryptionPromise\n    }=this;\n    if(decryptionPromise){\n      // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore\n      // only flushing is required for async decryption\n      return decryptionPromise.then(()=> {\n        return this.flush(chunkMeta);\n      });\n    }\n    const transmuxResults=[];\n    const {\n      timeOffset\n    }=currentTransmuxState;\n    if(decrypter){\n      // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults\n      // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads,\n      // or for progressive downloads with small segments)\n      const decryptedData=decrypter.flush();\n      if(decryptedData){\n        // Push always returns a TransmuxerResult if decryptdata is null\n        transmuxResults.push(this.push(decryptedData, null, chunkMeta));\n      }\n    }\n    const {\n      demuxer,\n      remuxer\n    }=this;\n    if(!demuxer||!remuxer){\n      // If probing failed, then Hls.js has been given content its not able to handle\n      stats.executeEnd=now();\n      return [emptyResult(chunkMeta)];\n    }\n    const demuxResultOrPromise=demuxer.flush(timeOffset);\n    if(isPromise(demuxResultOrPromise)){\n      // Decrypt final SAMPLE-AES samples\n      return demuxResultOrPromise.then(demuxResult=> {\n        this.flushRemux(transmuxResults, demuxResult, chunkMeta);\n        return transmuxResults;\n      });\n    }\n    this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);\n    return transmuxResults;\n  }\n  flushRemux(transmuxResults, demuxResult, chunkMeta){\n    const {\n      audioTrack,\n      videoTrack,\n      id3Track,\n      textTrack\n    }=demuxResult;\n    const {\n      accurateTimeOffset,\n      timeOffset\n    }=this.currentTransmuxState;\n    logger.log(`[transmuxer.ts]: Flushed fragment ${chunkMeta.sn}${chunkMeta.part > -1 ? ' p: ' + chunkMeta.part:''} of level ${chunkMeta.level}`);\n    const remuxResult=this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id);\n    transmuxResults.push({\n      remuxResult,\n      chunkMeta\n    });\n    chunkMeta.transmuxing.executeEnd=now();\n  }\n  resetInitialTimestamp(defaultInitPts){\n    const {\n      demuxer,\n      remuxer\n    }=this;\n    if(!demuxer||!remuxer){\n      return;\n    }\n    demuxer.resetTimeStamp(defaultInitPts);\n    remuxer.resetTimeStamp(defaultInitPts);\n  }\n  resetContiguity(){\n    const {\n      demuxer,\n      remuxer\n    }=this;\n    if(!demuxer||!remuxer){\n      return;\n    }\n    demuxer.resetContiguity();\n    remuxer.resetNextTimestamp();\n  }\n  resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata){\n    const {\n      demuxer,\n      remuxer\n    }=this;\n    if(!demuxer||!remuxer){\n      return;\n    }\n    demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration);\n    remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata);\n  }\n  destroy(){\n    if(this.demuxer){\n      this.demuxer.destroy();\n      this.demuxer=undefined;\n    }\n    if(this.remuxer){\n      this.remuxer.destroy();\n      this.remuxer=undefined;\n    }\n  }\n  transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta){\n    let result;\n    if(keyData&&keyData.method==='SAMPLE-AES'){\n      result=this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta);\n    }else{\n      result=this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta);\n    }\n    return result;\n  }\n  transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta){\n    const {\n      audioTrack,\n      videoTrack,\n      id3Track,\n      textTrack\n    }=this.demuxer.demux(data, timeOffset, false, !this.config.progressive);\n    const remuxResult=this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id);\n    return {\n      remuxResult,\n      chunkMeta\n    };\n  }\n  transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta){\n    return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(demuxResult=> {\n      const remuxResult=this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id);\n      return {\n        remuxResult,\n        chunkMeta\n      };\n    });\n  }\n  configureTransmuxer(data){\n    const {\n      config,\n      observer,\n      typeSupported,\n      vendor\n    }=this;\n    // probe for content type\n    let mux;\n    for (let i=0, len=muxConfig.length; i < len; i++){\n      var _muxConfig$i$demux;\n      if((_muxConfig$i$demux=muxConfig[i].demux)!=null&&_muxConfig$i$demux.probe(data)){\n        mux=muxConfig[i];\n        break;\n      }\n    }\n    if(!mux){\n      return new Error('Failed to find demuxer by probing fragment data');\n    }\n    // so let's check that current remuxer and demuxer are still valid\n    const demuxer=this.demuxer;\n    const remuxer=this.remuxer;\n    const Remuxer=mux.remux;\n    const Demuxer=mux.demux;\n    if(!remuxer||!(remuxer instanceof Remuxer)){\n      this.remuxer=new Remuxer(observer, config, typeSupported, vendor);\n    }\n    if(!demuxer||!(demuxer instanceof Demuxer)){\n      this.demuxer=new Demuxer(observer, config, typeSupported);\n      this.probe=Demuxer.probe;\n    }\n  }\n  needsProbing(discontinuity, trackSwitch){\n    // in case of continuity change, or track switch\n    // we might switch from content type (AAC container to TS container, or TS to fmp4 for example)\n    return !this.demuxer||!this.remuxer||discontinuity||trackSwitch;\n  }\n  getDecrypter(){\n    let decrypter=this.decrypter;\n    if(!decrypter){\n      decrypter=this.decrypter=new Decrypter(this.config);\n    }\n    return decrypter;\n  }\n}\nfunction getEncryptionType(data, decryptData){\n  let encryptionType=null;\n  if(data.byteLength > 0&&(decryptData==null ? void 0:decryptData.key)!=null&&decryptData.iv!==null&&decryptData.method!=null){\n    encryptionType=decryptData;\n  }\n  return encryptionType;\n}\nconst emptyResult=chunkMeta=> ({\n  remuxResult: {},\n  chunkMeta\n});\nfunction isPromise(p){\n  return 'then' in p&&p.then instanceof Function;\n}\nclass TransmuxConfig {\n  constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts){\n    this.audioCodec=void 0;\n    this.videoCodec=void 0;\n    this.initSegmentData=void 0;\n    this.duration=void 0;\n    this.defaultInitPts=void 0;\n    this.audioCodec=audioCodec;\n    this.videoCodec=videoCodec;\n    this.initSegmentData=initSegmentData;\n    this.duration=duration;\n    this.defaultInitPts=defaultInitPts||null;\n  }\n}\nclass TransmuxState {\n  constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange){\n    this.discontinuity=void 0;\n    this.contiguous=void 0;\n    this.accurateTimeOffset=void 0;\n    this.trackSwitch=void 0;\n    this.timeOffset=void 0;\n    this.initSegmentChange=void 0;\n    this.discontinuity=discontinuity;\n    this.contiguous=contiguous;\n    this.accurateTimeOffset=accurateTimeOffset;\n    this.trackSwitch=trackSwitch;\n    this.timeOffset=timeOffset;\n    this.initSegmentChange=initSegmentChange;\n  }\n}\n\nvar eventemitter3={exports: {}};\n\n(function (module){\n\n\tvar has=Object.prototype.hasOwnProperty\n\t  , prefix='~';\n\n\t\n\tfunction Events(){}\n\n\t//\n\t// We try to not inherit from `Object.prototype`. In some engines creating an\n\t// instance in this way is faster than calling `Object.create(null)` directly.\n\t// If `Object.create(null)` is not supported we prefix the event names with a\n\t// character to make sure that the built-in object properties are not\n\t// overridden or used as an attack vector.\n\t//\n\tif(Object.create){\n\t  Events.prototype=Object.create(null);\n\n\t  //\n\t  // This hack is needed because the `__proto__` property is still inherited in\n\t  // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.\n\t  //\n\t  if(!new Events().__proto__) prefix=false;\n\t}\n\n\t\n\tfunction EE(fn, context, once){\n\t  this.fn=fn;\n\t  this.context=context;\n\t  this.once=once||false;\n\t}\n\n\t\n\tfunction addListener(emitter, event, fn, context, once){\n\t  if(typeof fn!=='function'){\n\t    throw new TypeError('The listener must be a function');\n\t  }\n\n\t  var listener=new EE(fn, context||emitter, once)\n\t    , evt=prefix ? prefix + event:event;\n\n\t  if(!emitter._events[evt]) emitter._events[evt]=listener, emitter._eventsCount++;\n\t  else if(!emitter._events[evt].fn) emitter._events[evt].push(listener);\n\t  else emitter._events[evt]=[emitter._events[evt], listener];\n\n\t  return emitter;\n\t}\n\n\t\n\tfunction clearEvent(emitter, evt){\n\t  if(--emitter._eventsCount===0) emitter._events=new Events();\n\t  else delete emitter._events[evt];\n\t}\n\n\t\n\tfunction EventEmitter(){\n\t  this._events=new Events();\n\t  this._eventsCount=0;\n\t}\n\n\t\n\tEventEmitter.prototype.eventNames=function eventNames(){\n\t  var names=[]\n\t    , events\n\t    , name;\n\n\t  if(this._eventsCount===0) return names;\n\n\t  for (name in (events=this._events)){\n\t    if(has.call(events, name)) names.push(prefix ? name.slice(1):name);\n\t  }\n\n\t  if(Object.getOwnPropertySymbols){\n\t    return names.concat(Object.getOwnPropertySymbols(events));\n\t  }\n\n\t  return names;\n\t};\n\n\t\n\tEventEmitter.prototype.listeners=function listeners(event){\n\t  var evt=prefix ? prefix + event:event\n\t    , handlers=this._events[evt];\n\n\t  if(!handlers) return [];\n\t  if(handlers.fn) return [handlers.fn];\n\n\t  for (var i=0, l=handlers.length, ee=new Array(l); i < l; i++){\n\t    ee[i]=handlers[i].fn;\n\t  }\n\n\t  return ee;\n\t};\n\n\t\n\tEventEmitter.prototype.listenerCount=function listenerCount(event){\n\t  var evt=prefix ? prefix + event:event\n\t    , listeners=this._events[evt];\n\n\t  if(!listeners) return 0;\n\t  if(listeners.fn) return 1;\n\t  return listeners.length;\n\t};\n\n\t\n\tEventEmitter.prototype.emit=function emit(event, a1, a2, a3, a4, a5){\n\t  var evt=prefix ? prefix + event:event;\n\n\t  if(!this._events[evt]) return false;\n\n\t  var listeners=this._events[evt]\n\t    , len=arguments.length\n\t    , args\n\t    , i;\n\n\t  if(listeners.fn){\n\t    if(listeners.once) this.removeListener(event, listeners.fn, undefined, true);\n\n\t    switch (len){\n\t      case 1: return listeners.fn.call(listeners.context), true;\n\t      case 2: return listeners.fn.call(listeners.context, a1), true;\n\t      case 3: return listeners.fn.call(listeners.context, a1, a2), true;\n\t      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;\n\t      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;\n\t      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;\n\t    }\n\n\t    for (i=1, args=new Array(len -1); i < len; i++){\n\t      args[i - 1]=arguments[i];\n\t    }\n\n\t    listeners.fn.apply(listeners.context, args);\n\t  }else{\n\t    var length=listeners.length\n\t      , j;\n\n\t    for (i=0; i < length; i++){\n\t      if(listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);\n\n\t      switch (len){\n\t        case 1: listeners[i].fn.call(listeners[i].context); break;\n\t        case 2: listeners[i].fn.call(listeners[i].context, a1); break;\n\t        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;\n\t        case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;\n\t        default:\n\t          if(!args) for (j=1, args=new Array(len -1); j < len; j++){\n\t            args[j - 1]=arguments[j];\n\t          }\n\n\t          listeners[i].fn.apply(listeners[i].context, args);\n\t      }\n\t    }\n\t  }\n\n\t  return true;\n\t};\n\n\t\n\tEventEmitter.prototype.on=function on(event, fn, context){\n\t  return addListener(this, event, fn, context, false);\n\t};\n\n\t\n\tEventEmitter.prototype.once=function once(event, fn, context){\n\t  return addListener(this, event, fn, context, true);\n\t};\n\n\t\n\tEventEmitter.prototype.removeListener=function removeListener(event, fn, context, once){\n\t  var evt=prefix ? prefix + event:event;\n\n\t  if(!this._events[evt]) return this;\n\t  if(!fn){\n\t    clearEvent(this, evt);\n\t    return this;\n\t  }\n\n\t  var listeners=this._events[evt];\n\n\t  if(listeners.fn){\n\t    if(\n\t      listeners.fn===fn&&\n\t      (!once||listeners.once)&&\n\t      (!context||listeners.context===context)\n\t){\n\t      clearEvent(this, evt);\n\t    }\n\t  }else{\n\t    for (var i=0, events=[], length=listeners.length; i < length; i++){\n\t      if(\n\t        listeners[i].fn!==fn||\n\t        (once&&!listeners[i].once)||\n\t        (context&&listeners[i].context!==context)\n\t){\n\t        events.push(listeners[i]);\n\t      }\n\t    }\n\n\t    //\n\t    // Reset the array, or remove it completely if we have no more listeners.\n\t    //\n\t    if(events.length) this._events[evt]=events.length===1 ? events[0]:events;\n\t    else clearEvent(this, evt);\n\t  }\n\n\t  return this;\n\t};\n\n\t\n\tEventEmitter.prototype.removeAllListeners=function removeAllListeners(event){\n\t  var evt;\n\n\t  if(event){\n\t    evt=prefix ? prefix + event:event;\n\t    if(this._events[evt]) clearEvent(this, evt);\n\t  }else{\n\t    this._events=new Events();\n\t    this._eventsCount=0;\n\t  }\n\n\t  return this;\n\t};\n\n\t//\n\t// Alias methods names because people roll like that.\n\t//\n\tEventEmitter.prototype.off=EventEmitter.prototype.removeListener;\n\tEventEmitter.prototype.addListener=EventEmitter.prototype.on;\n\n\t//\n\t// Expose the prefix.\n\t//\n\tEventEmitter.prefixed=prefix;\n\n\t//\n\t// Allow `EventEmitter` to be imported as module namespace.\n\t//\n\tEventEmitter.EventEmitter=EventEmitter;\n\n\t//\n\t// Expose the module.\n\t//\n\t{\n\t  module.exports=EventEmitter;\n\t} \n} (eventemitter3));\n\nvar eventemitter3Exports=eventemitter3.exports;\nvar EventEmitter=getDefaultExportFromCjs(eventemitter3Exports);\n\nclass TransmuxerInterface {\n  constructor(hls, id, onTransmuxComplete, onFlush){\n    this.error=null;\n    this.hls=void 0;\n    this.id=void 0;\n    this.observer=void 0;\n    this.frag=null;\n    this.part=null;\n    this.useWorker=void 0;\n    this.workerContext=null;\n    this.onwmsg=void 0;\n    this.transmuxer=null;\n    this.onTransmuxComplete=void 0;\n    this.onFlush=void 0;\n    const config=hls.config;\n    this.hls=hls;\n    this.id=id;\n    this.useWorker = !!config.enableWorker;\n    this.onTransmuxComplete=onTransmuxComplete;\n    this.onFlush=onFlush;\n    const forwardMessage=(ev, data)=> {\n      data=data||{};\n      data.frag=this.frag;\n      data.id=this.id;\n      if(ev===Events.ERROR){\n        this.error=data.error;\n      }\n      this.hls.trigger(ev, data);\n    };\n\n    // forward events to main thread\n    this.observer=new EventEmitter();\n    this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);\n    this.observer.on(Events.ERROR, forwardMessage);\n    const MediaSource=getMediaSource(config.preferManagedMediaSource)||{\n      isTypeSupported: ()=> false\n    };\n    const m2tsTypeSupported={\n      mpeg: MediaSource.isTypeSupported('audio/mpeg'),\n      mp3: MediaSource.isTypeSupported('audio/mp4; codecs=\"mp3\"'),\n      ac3: MediaSource.isTypeSupported('audio/mp4; codecs=\"ac-3\"') \n    };\n    if(this.useWorker&&typeof Worker!=='undefined'){\n      const canCreateWorker=config.workerPath||hasUMDWorker();\n      if(canCreateWorker){\n        try {\n          if(config.workerPath){\n            logger.log(`loading Web Worker ${config.workerPath} for \"${id}\"`);\n            this.workerContext=loadWorker(config.workerPath);\n          }else{\n            logger.log(`injecting Web Worker for \"${id}\"`);\n            this.workerContext=injectWorker();\n          }\n          this.onwmsg=event=> this.onWorkerMessage(event);\n          const {\n            worker\n          }=this.workerContext;\n          worker.addEventListener('message', this.onwmsg);\n          worker.onerror=event=> {\n            const error=new Error(`${event.message}  (${event.filename}:${event.lineno})`);\n            config.enableWorker=false;\n            logger.warn(`Error in \"${id}\" Web Worker, fallback to inline`);\n            this.hls.trigger(Events.ERROR, {\n              type: ErrorTypes.OTHER_ERROR,\n              details: ErrorDetails.INTERNAL_EXCEPTION,\n              fatal: false,\n              event: 'demuxerWorker',\n              error\n            });\n          };\n          worker.postMessage({\n            cmd: 'init',\n            typeSupported: m2tsTypeSupported,\n            vendor: '',\n            id: id,\n            config: JSON.stringify(config)\n          });\n        } catch (err){\n          logger.warn(`Error setting up \"${id}\" Web Worker, fallback to inline`, err);\n          this.resetWorker();\n          this.error=null;\n          this.transmuxer=new Transmuxer(this.observer, m2tsTypeSupported, config, '', id);\n        }\n        return;\n      }\n    }\n    this.transmuxer=new Transmuxer(this.observer, m2tsTypeSupported, config, '', id);\n  }\n  resetWorker(){\n    if(this.workerContext){\n      const {\n        worker,\n        objectURL\n      }=this.workerContext;\n      if(objectURL){\n        // revoke the Object URL that was used to create transmuxer worker, so as not to leak it\n        self.URL.revokeObjectURL(objectURL);\n      }\n      worker.removeEventListener('message', this.onwmsg);\n      worker.onerror=null;\n      worker.terminate();\n      this.workerContext=null;\n    }\n  }\n  destroy(){\n    if(this.workerContext){\n      this.resetWorker();\n      this.onwmsg=undefined;\n    }else{\n      const transmuxer=this.transmuxer;\n      if(transmuxer){\n        transmuxer.destroy();\n        this.transmuxer=null;\n      }\n    }\n    const observer=this.observer;\n    if(observer){\n      observer.removeAllListeners();\n    }\n    this.frag=null;\n    // @ts-ignore\n    this.observer=null;\n    // @ts-ignore\n    this.hls=null;\n  }\n  push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS){\n    var _frag$initSegment, _lastFrag$initSegment;\n    chunkMeta.transmuxing.start=self.performance.now();\n    const {\n      transmuxer\n    }=this;\n    const timeOffset=part ? part.start:frag.start;\n    // TODO: push \"clear-lead\" decrypt data for unencrypted fragments in streams with encrypted ones\n    const decryptdata=frag.decryptdata;\n    const lastFrag=this.frag;\n    const discontinuity = !(lastFrag&&frag.cc===lastFrag.cc);\n    const trackSwitch = !(lastFrag&&chunkMeta.level===lastFrag.level);\n    const snDiff=lastFrag ? chunkMeta.sn - lastFrag.sn:-1;\n    const partDiff=this.part ? chunkMeta.part - this.part.index:-1;\n    const progressive=snDiff===0&&chunkMeta.id > 1&&chunkMeta.id===(lastFrag==null ? void 0:lastFrag.stats.chunkCount);\n    const contiguous = !trackSwitch&&(snDiff===1||snDiff===0&&(partDiff===1||progressive&&partDiff <=0));\n    const now=self.performance.now();\n    if(trackSwitch||snDiff||frag.stats.parsing.start===0){\n      frag.stats.parsing.start=now;\n    }\n    if(part&&(partDiff||!contiguous)){\n      part.stats.parsing.start=now;\n    }\n    const initSegmentChange = !(lastFrag&&((_frag$initSegment=frag.initSegment)==null ? void 0:_frag$initSegment.url)===((_lastFrag$initSegment=lastFrag.initSegment)==null ? void 0:_lastFrag$initSegment.url));\n    const state=new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange);\n    if(!contiguous||discontinuity||initSegmentChange){\n      logger.log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id}\n        discontinuity: ${discontinuity}\n        trackSwitch: ${trackSwitch}\n        contiguous: ${contiguous}\n        accurateTimeOffset: ${accurateTimeOffset}\n        timeOffset: ${timeOffset}\n        initSegmentChange: ${initSegmentChange}`);\n      const config=new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS);\n      this.configureTransmuxer(config);\n    }\n    this.frag=frag;\n    this.part=part;\n\n    // Frags with sn of 'initSegment' are not transmuxed\n    if(this.workerContext){\n      // post fragment payload as transferable objects for ArrayBuffer (no copy)\n      this.workerContext.worker.postMessage({\n        cmd: 'demux',\n        data,\n        decryptdata,\n        chunkMeta,\n        state\n      }, data instanceof ArrayBuffer ? [data]:[]);\n    }else if(transmuxer){\n      const transmuxResult=transmuxer.push(data, decryptdata, chunkMeta, state);\n      if(isPromise(transmuxResult)){\n        transmuxer.async=true;\n        transmuxResult.then(data=> {\n          this.handleTransmuxComplete(data);\n        }).catch(error=> {\n          this.transmuxerError(error, chunkMeta, 'transmuxer-interface push error');\n        });\n      }else{\n        transmuxer.async=false;\n        this.handleTransmuxComplete(transmuxResult);\n      }\n    }\n  }\n  flush(chunkMeta){\n    chunkMeta.transmuxing.start=self.performance.now();\n    const {\n      transmuxer\n    }=this;\n    if(this.workerContext){\n      this.workerContext.worker.postMessage({\n        cmd: 'flush',\n        chunkMeta\n      });\n    }else if(transmuxer){\n      let transmuxResult=transmuxer.flush(chunkMeta);\n      const asyncFlush=isPromise(transmuxResult);\n      if(asyncFlush||transmuxer.async){\n        if(!isPromise(transmuxResult)){\n          transmuxResult=Promise.resolve(transmuxResult);\n        }\n        transmuxResult.then(data=> {\n          this.handleFlushResult(data, chunkMeta);\n        }).catch(error=> {\n          this.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error');\n        });\n      }else{\n        this.handleFlushResult(transmuxResult, chunkMeta);\n      }\n    }\n  }\n  transmuxerError(error, chunkMeta, reason){\n    if(!this.hls){\n      return;\n    }\n    this.error=error;\n    this.hls.trigger(Events.ERROR, {\n      type: ErrorTypes.MEDIA_ERROR,\n      details: ErrorDetails.FRAG_PARSING_ERROR,\n      chunkMeta,\n      frag: this.frag||undefined,\n      fatal: false,\n      error,\n      err: error,\n      reason\n    });\n  }\n  handleFlushResult(results, chunkMeta){\n    results.forEach(result=> {\n      this.handleTransmuxComplete(result);\n    });\n    this.onFlush(chunkMeta);\n  }\n  onWorkerMessage(event){\n    const data=event.data;\n    if(!(data!=null&&data.event)){\n      logger.warn(`worker message received with no ${data ? 'event name':'data'}`);\n      return;\n    }\n    const hls=this.hls;\n    if(!this.hls){\n      return;\n    }\n    switch (data.event){\n      case 'init':\n        {\n          var _this$workerContext;\n          const objectURL=(_this$workerContext=this.workerContext)==null ? void 0:_this$workerContext.objectURL;\n          if(objectURL){\n            // revoke the Object URL that was used to create transmuxer worker, so as not to leak it\n            self.URL.revokeObjectURL(objectURL);\n          }\n          break;\n        }\n      case 'transmuxComplete':\n        {\n          this.handleTransmuxComplete(data.data);\n          break;\n        }\n      case 'flush':\n        {\n          this.onFlush(data.data);\n          break;\n        }\n\n      // pass logs from the worker thread to the main logger\n      case 'workerLog':\n        if(logger[data.data.logType]){\n          logger[data.data.logType](data.data.message);\n        }\n        break;\n      default:\n        {\n          data.data=data.data||{};\n          data.data.frag=this.frag;\n          data.data.id=this.id;\n          hls.trigger(data.event, data.data);\n          break;\n        }\n    }\n  }\n  configureTransmuxer(config){\n    const {\n      transmuxer\n    }=this;\n    if(this.workerContext){\n      this.workerContext.worker.postMessage({\n        cmd: 'configure',\n        config\n      });\n    }else if(transmuxer){\n      transmuxer.configure(config);\n    }\n  }\n  handleTransmuxComplete(result){\n    result.chunkMeta.transmuxing.end=self.performance.now();\n    this.onTransmuxComplete(result);\n  }\n}\n\nfunction subtitleOptionsIdentical(trackList1, trackList2){\n  if(trackList1.length!==trackList2.length){\n    return false;\n  }\n  for (let i=0; i < trackList1.length; i++){\n    if(!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)){\n      return false;\n    }\n  }\n  return true;\n}\nfunction mediaAttributesIdentical(attrs1, attrs2, customAttributes){\n  // Media options with the same rendition ID must be bit identical\n  const stableRenditionId=attrs1['STABLE-RENDITION-ID'];\n  if(stableRenditionId&&!customAttributes){\n    return stableRenditionId===attrs2['STABLE-RENDITION-ID'];\n  }\n  // When rendition ID is not present, compare attributes\n  return !(customAttributes||['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute=> attrs1[subtitleAttribute]!==attrs2[subtitleAttribute]);\n}\nfunction subtitleTrackMatchesTextTrack(subtitleTrack, textTrack){\n  return textTrack.label.toLowerCase()===subtitleTrack.name.toLowerCase()&&(!textTrack.language||textTrack.language.toLowerCase()===(subtitleTrack.lang||'').toLowerCase());\n}\n\nconst TICK_INTERVAL$2=100; // how often to tick in ms\n\nclass AudioStreamController extends BaseStreamController {\n  constructor(hls, fragmentTracker, keyLoader){\n    super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);\n    this.videoBuffer=null;\n    this.videoTrackCC=-1;\n    this.waitingVideoCC=-1;\n    this.bufferedTrack=null;\n    this.switchingTrack=null;\n    this.trackId=-1;\n    this.waitingData=null;\n    this.mainDetails=null;\n    this.flushing=false;\n    this.bufferFlushed=false;\n    this.cachedTrackLoadedData=null;\n    this._registerListeners();\n  }\n  onHandlerDestroying(){\n    this._unregisterListeners();\n    super.onHandlerDestroying();\n    this.mainDetails=null;\n    this.bufferedTrack=null;\n    this.switchingTrack=null;\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);\n    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);\n    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);\n    hls.on(Events.ERROR, this.onError, this);\n    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);\n    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);\n    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);\n    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);\n    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);\n    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);\n    hls.off(Events.ERROR, this.onError, this);\n    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);\n    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);\n    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);\n    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n\n  // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value\n  onInitPtsFound(event, {\n    frag,\n    id,\n    initPTS,\n    timescale\n  }){\n    // Always update the new INIT PTS\n    // Can change due level switch\n    if(id==='main'){\n      const cc=frag.cc;\n      this.initPTS[frag.cc]={\n        baseTime: initPTS,\n        timescale\n      };\n      this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`);\n      this.videoTrackCC=cc;\n      // If we are waiting, tick immediately to unblock audio fragment transmuxing\n      if(this.state===State.WAITING_INIT_PTS){\n        this.tick();\n      }\n    }\n  }\n  startLoad(startPosition){\n    if(!this.levels){\n      this.startPosition=startPosition;\n      this.state=State.STOPPED;\n      return;\n    }\n    const lastCurrentTime=this.lastCurrentTime;\n    this.stopLoad();\n    this.setInterval(TICK_INTERVAL$2);\n    if(lastCurrentTime > 0&&startPosition===-1){\n      this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);\n      startPosition=lastCurrentTime;\n      this.state=State.IDLE;\n    }else{\n      this.loadedmetadata=false;\n      this.state=State.WAITING_TRACK;\n    }\n    this.nextLoadPosition=this.startPosition=this.lastCurrentTime=startPosition;\n    this.tick();\n  }\n  doTick(){\n    switch (this.state){\n      case State.IDLE:\n        this.doTickIdle();\n        break;\n      case State.WAITING_TRACK:\n        {\n          var _levels$trackId;\n          const {\n            levels,\n            trackId\n          }=this;\n          const details=levels==null ? void 0:(_levels$trackId=levels[trackId])==null ? void 0:_levels$trackId.details;\n          if(details){\n            if(this.waitForCdnTuneIn(details)){\n              break;\n            }\n            this.state=State.WAITING_INIT_PTS;\n          }\n          break;\n        }\n      case State.FRAG_LOADING_WAITING_RETRY:\n        {\n          var _this$media;\n          const now=performance.now();\n          const retryDate=this.retryDate;\n          // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading\n          if(!retryDate||now >=retryDate||(_this$media=this.media)!=null&&_this$media.seeking){\n            const {\n              levels,\n              trackId\n            }=this;\n            this.log('RetryDate reached, switch back to IDLE state');\n            this.resetStartWhenNotLoaded((levels==null ? void 0:levels[trackId])||null);\n            this.state=State.IDLE;\n          }\n          break;\n        }\n      case State.WAITING_INIT_PTS:\n        {\n          // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS\n          const waitingData=this.waitingData;\n          if(waitingData){\n            const {\n              frag,\n              part,\n              cache,\n              complete\n            }=waitingData;\n            if(this.initPTS[frag.cc]!==undefined){\n              this.waitingData=null;\n              this.waitingVideoCC=-1;\n              this.state=State.FRAG_LOADING;\n              const payload=cache.flush();\n              const data={\n                frag,\n                part,\n                payload,\n                networkDetails: null\n              };\n              this._handleFragmentLoadProgress(data);\n              if(complete){\n                super._handleFragmentLoadComplete(data);\n              }\n            }else if(this.videoTrackCC!==this.waitingVideoCC){\n              // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found\n              this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`);\n              this.clearWaitingFragment();\n            }else{\n              // Drop waiting fragment if an earlier fragment is needed\n              const pos=this.getLoadPosition();\n              const bufferInfo=BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole);\n              const waitingFragmentAtPosition=fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag);\n              if(waitingFragmentAtPosition < 0){\n                this.log(`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`);\n                this.clearWaitingFragment();\n              }\n            }\n          }else{\n            this.state=State.IDLE;\n          }\n        }\n    }\n    this.onTickEnd();\n  }\n  clearWaitingFragment(){\n    const waitingData=this.waitingData;\n    if(waitingData){\n      this.fragmentTracker.removeFragment(waitingData.frag);\n      this.waitingData=null;\n      this.waitingVideoCC=-1;\n      this.state=State.IDLE;\n    }\n  }\n  resetLoadingState(){\n    this.clearWaitingFragment();\n    super.resetLoadingState();\n  }\n  onTickEnd(){\n    const {\n      media\n    }=this;\n    if(!(media!=null&&media.readyState)){\n      // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)\n      return;\n    }\n    this.lastCurrentTime=media.currentTime;\n  }\n  doTickIdle(){\n    const {\n      hls,\n      levels,\n      media,\n      trackId\n    }=this;\n    const config=hls.config;\n\n    // 1. if buffering is suspended\n    // 2. if video not attached AND\n    //    start fragment already requested OR start frag prefetch not enabled\n    // 3. if tracks or track not loaded and selected\n    // then exit loop\n    //=> if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop\n    if(!this.buffering||!media&&(this.startFragRequested||!config.startFragPrefetch)||!(levels!=null&&levels[trackId])){\n      return;\n    }\n    const levelInfo=levels[trackId];\n    const trackDetails=levelInfo.details;\n    if(!trackDetails||trackDetails.live&&this.levelLastLoaded!==levelInfo||this.waitForCdnTuneIn(trackDetails)){\n      this.state=State.WAITING_TRACK;\n      return;\n    }\n    const bufferable=this.mediaBuffer ? this.mediaBuffer:this.media;\n    if(this.bufferFlushed&&bufferable){\n      this.bufferFlushed=false;\n      this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO);\n    }\n    const bufferInfo=this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO);\n    if(bufferInfo===null){\n      return;\n    }\n    const {\n      bufferedTrack,\n      switchingTrack\n    }=this;\n    if(!switchingTrack&&this._streamEnded(bufferInfo, trackDetails)){\n      hls.trigger(Events.BUFFER_EOS, {\n        type: 'audio'\n      });\n      this.state=State.ENDED;\n      return;\n    }\n    const mainBufferInfo=this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer:this.media, PlaylistLevelType.MAIN);\n    const bufferLen=bufferInfo.len;\n    const maxBufLen=this.getMaxBufferLength(mainBufferInfo==null ? void 0:mainBufferInfo.len);\n    const fragments=trackDetails.fragments;\n    const start=fragments[0].start;\n    let targetBufferTime=this.flushing ? this.getLoadPosition():bufferInfo.end;\n    if(switchingTrack&&media){\n      const pos=this.getLoadPosition();\n      // STABLE\n      if(bufferedTrack&&!mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)){\n        targetBufferTime=pos;\n      }\n      // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime\n      if(trackDetails.PTSKnown&&pos < start){\n        // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start\n        if(bufferInfo.end > start||bufferInfo.nextStart){\n          this.log('Alt audio track ahead of main track, seek to start of alt audio track');\n          media.currentTime=start + 0.05;\n        }\n      }\n    }\n\n    // if buffer length is less than maxBufLen, or near the end, find a fragment to load\n    if(bufferLen >=maxBufLen&&!switchingTrack&&targetBufferTime < fragments[fragments.length - 1].start){\n      return;\n    }\n    let frag=this.getNextFragment(targetBufferTime, trackDetails);\n    let atGap=false;\n    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags\n    if(frag&&this.isLoopLoading(frag, targetBufferTime)){\n      atGap = !!frag.gap;\n      frag=this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);\n    }\n    if(!frag){\n      this.bufferFlushed=true;\n      return;\n    }\n\n    // Buffer audio up to one target duration ahead of main buffer\n    const atBufferSyncLimit=mainBufferInfo&&frag.start > mainBufferInfo.end + trackDetails.targetduration;\n    if(atBufferSyncLimit||\n    // Or wait for main buffer after buffing some audio\n    !(mainBufferInfo!=null&&mainBufferInfo.len)&&bufferInfo.len){\n      // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo\n      const mainFrag=this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);\n      if(mainFrag===null){\n        return;\n      }\n      // Bridge gaps in main buffer\n      atGap||(atGap = !!mainFrag.gap||!!atBufferSyncLimit&&mainBufferInfo.len===0);\n      if(atBufferSyncLimit&&!atGap||atGap&&bufferInfo.nextStart&&bufferInfo.nextStart < mainFrag.end){\n        return;\n      }\n    }\n    this.loadFragment(frag, levelInfo, targetBufferTime);\n  }\n  getMaxBufferLength(mainBufferLength){\n    const maxConfigBuffer=super.getMaxBufferLength();\n    if(!mainBufferLength){\n      return maxConfigBuffer;\n    }\n    return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength);\n  }\n  onMediaDetaching(){\n    this.videoBuffer=null;\n    this.bufferFlushed=this.flushing=false;\n    super.onMediaDetaching();\n  }\n  onAudioTracksUpdated(event, {\n    audioTracks\n  }){\n    // Reset tranxmuxer is essential for large context switches (Content Steering)\n    this.resetTransmuxer();\n    this.levels=audioTracks.map(mediaPlaylist=> new Level(mediaPlaylist));\n  }\n  onAudioTrackSwitching(event, data){\n    // if any URL found on new audio track, it is an alternate audio track\n    const altAudio = !!data.url;\n    this.trackId=data.id;\n    const {\n      fragCurrent\n    }=this;\n    if(fragCurrent){\n      fragCurrent.abortRequests();\n      this.removeUnbufferedFrags(fragCurrent.start);\n    }\n    this.resetLoadingState();\n    // destroy useless transmuxer when switching audio to main\n    if(!altAudio){\n      this.resetTransmuxer();\n    }else{\n      // switching to audio track, start timer if not already started\n      this.setInterval(TICK_INTERVAL$2);\n    }\n\n    // should we switch tracks ?\n    if(altAudio){\n      this.switchingTrack=data;\n      // main audio track are handled by stream-controller, just do something if switching to alt audio track\n      this.state=State.IDLE;\n      this.flushAudioIfNeeded(data);\n    }else{\n      this.switchingTrack=null;\n      this.bufferedTrack=data;\n      this.state=State.STOPPED;\n    }\n    this.tick();\n  }\n  onManifestLoading(){\n    this.fragmentTracker.removeAllFragments();\n    this.startPosition=this.lastCurrentTime=0;\n    this.bufferFlushed=this.flushing=false;\n    this.levels=this.mainDetails=this.waitingData=this.bufferedTrack=this.cachedTrackLoadedData=this.switchingTrack=null;\n    this.startFragRequested=false;\n    this.trackId=this.videoTrackCC=this.waitingVideoCC=-1;\n  }\n  onLevelLoaded(event, data){\n    this.mainDetails=data.details;\n    if(this.cachedTrackLoadedData!==null){\n      this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);\n      this.cachedTrackLoadedData=null;\n    }\n  }\n  onAudioTrackLoaded(event, data){\n    var _track$details;\n    if(this.mainDetails==null){\n      this.cachedTrackLoadedData=data;\n      return;\n    }\n    const {\n      levels\n    }=this;\n    const {\n      details: newDetails,\n      id: trackId\n    }=data;\n    if(!levels){\n      this.warn(`Audio tracks were reset while loading level ${trackId}`);\n      return;\n    }\n    this.log(`Audio track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`:''},duration:${newDetails.totalduration}`);\n    const track=levels[trackId];\n    let sliding=0;\n    if(newDetails.live||(_track$details=track.details)!=null&&_track$details.live){\n      this.checkLiveUpdate(newDetails);\n      const mainDetails=this.mainDetails;\n      if(newDetails.deltaUpdateFailed||!mainDetails){\n        return;\n      }\n      if(!track.details&&newDetails.hasProgramDateTime&&mainDetails.hasProgramDateTime){\n        // Make sure our audio rendition is aligned with the \"main\" rendition, using\n        // pdt as our reference times.\n        alignMediaPlaylistByPDT(newDetails, mainDetails);\n        sliding=newDetails.fragments[0].start;\n      }else{\n        var _this$levelLastLoaded;\n        sliding=this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded=this.levelLastLoaded)==null ? void 0:_this$levelLastLoaded.details);\n      }\n    }\n    track.details=newDetails;\n    this.levelLastLoaded=track;\n\n    // compute start position if we are aligned with the main playlist\n    if(!this.startFragRequested&&(this.mainDetails||!newDetails.live)){\n      this.setStartPosition(this.mainDetails||newDetails, sliding);\n    }\n    // only switch back to IDLE state if we were waiting for track to start downloading a new fragment\n    if(this.state===State.WAITING_TRACK&&!this.waitForCdnTuneIn(newDetails)){\n      this.state=State.IDLE;\n    }\n\n    // trigger handler right now\n    this.tick();\n  }\n  _handleFragmentLoadProgress(data){\n    var _frag$initSegment;\n    const {\n      frag,\n      part,\n      payload\n    }=data;\n    const {\n      config,\n      trackId,\n      levels\n    }=this;\n    if(!levels){\n      this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);\n      return;\n    }\n    const track=levels[trackId];\n    if(!track){\n      this.warn('Audio track is undefined on fragment load progress');\n      return;\n    }\n    const details=track.details;\n    if(!details){\n      this.warn('Audio track details undefined on fragment load progress');\n      this.removeUnbufferedFrags(frag.start);\n      return;\n    }\n    const audioCodec=config.defaultAudioCodec||track.audioCodec||'mp4a.40.2';\n    let transmuxer=this.transmuxer;\n    if(!transmuxer){\n      transmuxer=this.transmuxer=new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));\n    }\n\n    // Check if we have video initPTS\n    // If not we need to wait for it\n    const initPTS=this.initPTS[frag.cc];\n    const initSegmentData=(_frag$initSegment=frag.initSegment)==null ? void 0:_frag$initSegment.data;\n    if(initPTS!==undefined){\n      // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);\n      // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)\n      const accurateTimeOffset=false; // details.PTSKnown||!details.live;\n      const partIndex=part ? part.index:-1;\n      const partial=partIndex!==-1;\n      const chunkMeta=new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);\n      transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);\n    }else{\n      this.log(`Unknown video PTS for cc ${frag.cc}, waiting for video PTS before demuxing audio frag ${frag.sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);\n      const {\n        cache\n      }=this.waitingData=this.waitingData||{\n        frag,\n        part,\n        cache: new ChunkCache(),\n        complete: false\n      };\n      cache.push(new Uint8Array(payload));\n      this.waitingVideoCC=this.videoTrackCC;\n      this.state=State.WAITING_INIT_PTS;\n    }\n  }\n  _handleFragmentLoadComplete(fragLoadedData){\n    if(this.waitingData){\n      this.waitingData.complete=true;\n      return;\n    }\n    super._handleFragmentLoadComplete(fragLoadedData);\n  }\n  onBufferReset(\n){\n    // reset reference to sourcebuffers\n    this.mediaBuffer=this.videoBuffer=null;\n    this.loadedmetadata=false;\n  }\n  onBufferCreated(event, data){\n    const audioTrack=data.tracks.audio;\n    if(audioTrack){\n      this.mediaBuffer=audioTrack.buffer||null;\n    }\n    if(data.tracks.video){\n      this.videoBuffer=data.tracks.video.buffer||null;\n    }\n  }\n  onFragBuffered(event, data){\n    const {\n      frag,\n      part\n    }=data;\n    if(frag.type!==PlaylistLevelType.AUDIO){\n      if(!this.loadedmetadata&&frag.type===PlaylistLevelType.MAIN){\n        const bufferable=this.videoBuffer||this.media;\n        if(bufferable){\n          const bufferedTimeRanges=BufferHelper.getBuffered(bufferable);\n          if(bufferedTimeRanges.length){\n            this.loadedmetadata=true;\n          }\n        }\n      }\n      return;\n    }\n    if(this.fragContextChanged(frag)){\n      // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion\n      // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer\n      this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index:''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}, audioSwitch: ${this.switchingTrack ? this.switchingTrack.name:'false'}`);\n      return;\n    }\n    if(frag.sn!=='initSegment'){\n      this.fragPrevious=frag;\n      const track=this.switchingTrack;\n      if(track){\n        this.bufferedTrack=track;\n        this.switchingTrack=null;\n        this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track));\n      }\n    }\n    this.fragBufferedComplete(frag, part);\n  }\n  onError(event, data){\n    var _data$context;\n    if(data.fatal){\n      this.state=State.ERROR;\n      return;\n    }\n    switch (data.details){\n      case ErrorDetails.FRAG_GAP:\n      case ErrorDetails.FRAG_PARSING_ERROR:\n      case ErrorDetails.FRAG_DECRYPT_ERROR:\n      case ErrorDetails.FRAG_LOAD_ERROR:\n      case ErrorDetails.FRAG_LOAD_TIMEOUT:\n      case ErrorDetails.KEY_LOAD_ERROR:\n      case ErrorDetails.KEY_LOAD_TIMEOUT:\n        this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data);\n        break;\n      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:\n      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:\n      case ErrorDetails.LEVEL_PARSING_ERROR:\n        // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE\n        if(!data.levelRetry&&this.state===State.WAITING_TRACK&&((_data$context=data.context)==null ? void 0:_data$context.type)===PlaylistContextType.AUDIO_TRACK){\n          this.state=State.IDLE;\n        }\n        break;\n      case ErrorDetails.BUFFER_APPEND_ERROR:\n      case ErrorDetails.BUFFER_FULL_ERROR:\n        if(!data.parent||data.parent!=='audio'){\n          return;\n        }\n        if(data.details===ErrorDetails.BUFFER_APPEND_ERROR){\n          this.resetLoadingState();\n          return;\n        }\n        if(this.reduceLengthAndFlushBuffer(data)){\n          this.bufferedTrack=null;\n          super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');\n        }\n        break;\n      case ErrorDetails.INTERNAL_EXCEPTION:\n        this.recoverWorkerError(data);\n        break;\n    }\n  }\n  onBufferFlushing(event, {\n    type\n  }){\n    if(type!==ElementaryStreamTypes.VIDEO){\n      this.flushing=true;\n    }\n  }\n  onBufferFlushed(event, {\n    type\n  }){\n    if(type!==ElementaryStreamTypes.VIDEO){\n      this.flushing=false;\n      this.bufferFlushed=true;\n      if(this.state===State.ENDED){\n        this.state=State.IDLE;\n      }\n      const mediaBuffer=this.mediaBuffer||this.media;\n      if(mediaBuffer){\n        this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO);\n        this.tick();\n      }\n    }\n  }\n  _handleTransmuxComplete(transmuxResult){\n    var _id3$samples;\n    const id='audio';\n    const {\n      hls\n    }=this;\n    const {\n      remuxResult,\n      chunkMeta\n    }=transmuxResult;\n    const context=this.getCurrentContext(chunkMeta);\n    if(!context){\n      this.resetWhenMissingContext(chunkMeta);\n      return;\n    }\n    const {\n      frag,\n      part,\n      level\n    }=context;\n    const {\n      details\n    }=level;\n    const {\n      audio,\n      text,\n      id3,\n      initSegment\n    }=remuxResult;\n\n    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.\n    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.\n    if(this.fragContextChanged(frag)||!details){\n      this.fragmentTracker.removeFragment(frag);\n      return;\n    }\n    this.state=State.PARSING;\n    if(this.switchingTrack&&audio){\n      this.completeAudioSwitch(this.switchingTrack);\n    }\n    if(initSegment!=null&&initSegment.tracks){\n      const mapFragment=frag.initSegment||frag;\n      this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);\n      hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {\n        frag: mapFragment,\n        id,\n        tracks: initSegment.tracks\n      });\n      // Only flush audio from old audio tracks when PTS is known on new audio track\n    }\n    if(audio){\n      const {\n        startPTS,\n        endPTS,\n        startDTS,\n        endDTS\n      }=audio;\n      if(part){\n        part.elementaryStreams[ElementaryStreamTypes.AUDIO]={\n          startPTS,\n          endPTS,\n          startDTS,\n          endDTS\n        };\n      }\n      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);\n      this.bufferFragmentData(audio, frag, part, chunkMeta);\n    }\n    if(id3!=null&&(_id3$samples=id3.samples)!=null&&_id3$samples.length){\n      const emittedID3=_extends({\n        id,\n        frag,\n        details\n      }, id3);\n      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);\n    }\n    if(text){\n      const emittedText=_extends({\n        id,\n        frag,\n        details\n      }, text);\n      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);\n    }\n  }\n  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta){\n    if(this.state!==State.PARSING){\n      return;\n    }\n    // delete any video track found on audio transmuxer\n    if(tracks.video){\n      delete tracks.video;\n    }\n\n    // include levelCodec in audio and video tracks\n    const track=tracks.audio;\n    if(!track){\n      return;\n    }\n    track.id='audio';\n    const variantAudioCodecs=currentLevel.audioCodec;\n    this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`);\n    // SourceBuffer will use track.levelCodec if defined\n    if(variantAudioCodecs&&variantAudioCodecs.split(',').length===1){\n      track.levelCodec=variantAudioCodecs;\n    }\n    this.hls.trigger(Events.BUFFER_CODECS, tracks);\n    const initSegment=track.initSegment;\n    if(initSegment!=null&&initSegment.byteLength){\n      const segment={\n        type: 'audio',\n        frag,\n        part: null,\n        chunkMeta,\n        parent: frag.type,\n        data: initSegment\n      };\n      this.hls.trigger(Events.BUFFER_APPENDING, segment);\n    }\n    // trigger handler right now\n    this.tickImmediate();\n  }\n  loadFragment(frag, track, targetBufferTime){\n    // only load if fragment is not loaded or if in audio switch\n    const fragState=this.fragmentTracker.getState(frag);\n    this.fragCurrent=frag;\n\n    // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch\n    if(this.switchingTrack||fragState===FragmentState.NOT_LOADED||fragState===FragmentState.PARTIAL){\n      var _track$details2;\n      if(frag.sn==='initSegment'){\n        this._loadInitSegment(frag, track);\n      }else if((_track$details2=track.details)!=null&&_track$details2.live&&!this.initPTS[frag.cc]){\n        this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`);\n        this.state=State.WAITING_INIT_PTS;\n        const mainDetails=this.mainDetails;\n        if(mainDetails&&mainDetails.fragments[0].start!==track.details.fragments[0].start){\n          alignMediaPlaylistByPDT(track.details, mainDetails);\n        }\n      }else{\n        this.startFragRequested=true;\n        super.loadFragment(frag, track, targetBufferTime);\n      }\n    }else{\n      this.clearTrackerIfNeeded(frag);\n    }\n  }\n  flushAudioIfNeeded(switchingTrack){\n    const {\n      media,\n      bufferedTrack\n    }=this;\n    const bufferedAttributes=bufferedTrack==null ? void 0:bufferedTrack.attrs;\n    const switchAttributes=switchingTrack.attrs;\n    if(media&&bufferedAttributes&&(bufferedAttributes.CHANNELS!==switchAttributes.CHANNELS||bufferedTrack.name!==switchingTrack.name||bufferedTrack.lang!==switchingTrack.lang)){\n      this.log('Switching audio track:flushing all audio');\n      super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');\n      this.bufferedTrack=null;\n    }\n  }\n  completeAudioSwitch(switchingTrack){\n    const {\n      hls\n    }=this;\n    this.flushAudioIfNeeded(switchingTrack);\n    this.bufferedTrack=switchingTrack;\n    this.switchingTrack=null;\n    hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack));\n  }\n}\n\nclass AudioTrackController extends BasePlaylistController {\n  constructor(hls){\n    super(hls, '[audio-track-controller]');\n    this.tracks=[];\n    this.groupIds=null;\n    this.tracksInGroup=[];\n    this.trackId=-1;\n    this.currentTrack=null;\n    this.selectDefaultTrack=true;\n    this.registerListeners();\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);\n    hls.on(Events.ERROR, this.onError, this);\n  }\n  unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);\n    hls.off(Events.ERROR, this.onError, this);\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.tracks.length=0;\n    this.tracksInGroup.length=0;\n    this.currentTrack=null;\n    super.destroy();\n  }\n  onManifestLoading(){\n    this.tracks=[];\n    this.tracksInGroup=[];\n    this.groupIds=null;\n    this.currentTrack=null;\n    this.trackId=-1;\n    this.selectDefaultTrack=true;\n  }\n  onManifestParsed(event, data){\n    this.tracks=data.audioTracks||[];\n  }\n  onAudioTrackLoaded(event, data){\n    const {\n      id,\n      groupId,\n      details\n    }=data;\n    const trackInActiveGroup=this.tracksInGroup[id];\n    if(!trackInActiveGroup||trackInActiveGroup.groupId!==groupId){\n      this.warn(`Audio track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup==null ? void 0:trackInActiveGroup.groupId}`);\n      return;\n    }\n    const curDetails=trackInActiveGroup.details;\n    trackInActiveGroup.details=data.details;\n    this.log(`Audio track ${id} \"${trackInActiveGroup.name}\" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);\n    if(id===this.trackId){\n      this.playlistLoaded(id, data, curDetails);\n    }\n  }\n  onLevelLoading(event, data){\n    this.switchLevel(data.level);\n  }\n  onLevelSwitching(event, data){\n    this.switchLevel(data.level);\n  }\n  switchLevel(levelIndex){\n    const levelInfo=this.hls.levels[levelIndex];\n    if(!levelInfo){\n      return;\n    }\n    const audioGroups=levelInfo.audioGroups||null;\n    const currentGroups=this.groupIds;\n    let currentTrack=this.currentTrack;\n    if(!audioGroups||(currentGroups==null ? void 0:currentGroups.length)!==(audioGroups==null ? void 0:audioGroups.length)||audioGroups!=null&&audioGroups.some(groupId=> (currentGroups==null ? void 0:currentGroups.indexOf(groupId))===-1)){\n      this.groupIds=audioGroups;\n      this.trackId=-1;\n      this.currentTrack=null;\n      const audioTracks=this.tracks.filter(track=> !audioGroups||audioGroups.indexOf(track.groupId)!==-1);\n      if(audioTracks.length){\n        // Disable selectDefaultTrack if there are no default tracks\n        if(this.selectDefaultTrack&&!audioTracks.some(track=> track.default)){\n          this.selectDefaultTrack=false;\n        }\n        // track.id should match hls.audioTracks index\n        audioTracks.forEach((track, i)=> {\n          track.id=i;\n        });\n      }else if(!currentTrack&&!this.tracksInGroup.length){\n        // Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks\n        return;\n      }\n      this.tracksInGroup=audioTracks;\n\n      // Find preferred track\n      const audioPreference=this.hls.config.audioPreference;\n      if(!currentTrack&&audioPreference){\n        const groupIndex=findMatchingOption(audioPreference, audioTracks, audioMatchPredicate);\n        if(groupIndex > -1){\n          currentTrack=audioTracks[groupIndex];\n        }else{\n          const allIndex=findMatchingOption(audioPreference, this.tracks);\n          currentTrack=this.tracks[allIndex];\n        }\n      }\n\n      // Select initial track\n      let trackId=this.findTrackId(currentTrack);\n      if(trackId===-1&&currentTrack){\n        trackId=this.findTrackId(null);\n      }\n\n      // Dispatch events and load track if needed\n      const audioTracksUpdated={\n        audioTracks\n      };\n      this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group(s): ${audioGroups==null ? void 0:audioGroups.join(',')}`);\n      this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated);\n      const selectedTrackId=this.trackId;\n      if(trackId!==-1&&selectedTrackId===-1){\n        this.setAudioTrack(trackId);\n      }else if(audioTracks.length&&selectedTrackId===-1){\n        var _this$groupIds;\n        const error=new Error(`No audio track selected for current audio group-ID(s): ${(_this$groupIds=this.groupIds)==null ? void 0:_this$groupIds.join(',')} track count: ${audioTracks.length}`);\n        this.warn(error.message);\n        this.hls.trigger(Events.ERROR, {\n          type: ErrorTypes.MEDIA_ERROR,\n          details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR,\n          fatal: true,\n          error\n        });\n      }\n    }else if(this.shouldReloadPlaylist(currentTrack)){\n      // Retry playlist loading if no playlist is or has been loaded yet\n      this.setAudioTrack(this.trackId);\n    }\n  }\n  onError(event, data){\n    if(data.fatal||!data.context){\n      return;\n    }\n    if(data.context.type===PlaylistContextType.AUDIO_TRACK&&data.context.id===this.trackId&&(!this.groupIds||this.groupIds.indexOf(data.context.groupId)!==-1)){\n      this.requestScheduled=-1;\n      this.checkRetry(data);\n    }\n  }\n  get allAudioTracks(){\n    return this.tracks;\n  }\n  get audioTracks(){\n    return this.tracksInGroup;\n  }\n  get audioTrack(){\n    return this.trackId;\n  }\n  set audioTrack(newId){\n    // If audio track is selected from API then don't choose from the manifest default track\n    this.selectDefaultTrack=false;\n    this.setAudioTrack(newId);\n  }\n  setAudioOption(audioOption){\n    const hls=this.hls;\n    hls.config.audioPreference=audioOption;\n    if(audioOption){\n      const allAudioTracks=this.allAudioTracks;\n      this.selectDefaultTrack=false;\n      if(allAudioTracks.length){\n        // First see if current option matches (no switch op)\n        const currentTrack=this.currentTrack;\n        if(currentTrack&&matchesOption(audioOption, currentTrack, audioMatchPredicate)){\n          return currentTrack;\n        }\n        // Find option in available tracks (tracksInGroup)\n        const groupIndex=findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate);\n        if(groupIndex > -1){\n          const track=this.tracksInGroup[groupIndex];\n          this.setAudioTrack(groupIndex);\n          return track;\n        }else if(currentTrack){\n          // Find option in nearest level audio group\n          let searchIndex=hls.loadLevel;\n          if(searchIndex===-1){\n            searchIndex=hls.firstAutoLevel;\n          }\n          const switchIndex=findClosestLevelWithAudioGroup(audioOption, hls.levels, allAudioTracks, searchIndex, audioMatchPredicate);\n          if(switchIndex===-1){\n            // could not find matching variant\n            return null;\n          }\n          // and switch level to acheive the audio group switch\n          hls.nextLoadLevel=switchIndex;\n        }\n        if(audioOption.channels||audioOption.audioCodec){\n          // Could not find a match with codec / channels predicate\n          // Find a match without channels or codec\n          const withoutCodecAndChannelsMatch=findMatchingOption(audioOption, allAudioTracks);\n          if(withoutCodecAndChannelsMatch > -1){\n            return allAudioTracks[withoutCodecAndChannelsMatch];\n          }\n        }\n      }\n    }\n    return null;\n  }\n  setAudioTrack(newId){\n    const tracks=this.tracksInGroup;\n\n    // check if level idx is valid\n    if(newId < 0||newId >=tracks.length){\n      this.warn(`Invalid audio track id: ${newId}`);\n      return;\n    }\n\n    // stopping live reloading timer if any\n    this.clearTimer();\n    this.selectDefaultTrack=false;\n    const lastTrack=this.currentTrack;\n    const track=tracks[newId];\n    const trackLoaded=track.details&&!track.details.live;\n    if(newId===this.trackId&&track===lastTrack&&trackLoaded){\n      return;\n    }\n    this.log(`Switching to audio-track ${newId} \"${track.name}\" lang:${track.lang} group:${track.groupId} channels:${track.channels}`);\n    this.trackId=newId;\n    this.currentTrack=track;\n    this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track));\n    // Do not reload track unless live\n    if(trackLoaded){\n      return;\n    }\n    const hlsUrlParameters=this.switchParams(track.url, lastTrack==null ? void 0:lastTrack.details, track.details);\n    this.loadPlaylist(hlsUrlParameters);\n  }\n  findTrackId(currentTrack){\n    const audioTracks=this.tracksInGroup;\n    for (let i=0; i < audioTracks.length; i++){\n      const track=audioTracks[i];\n      if(this.selectDefaultTrack&&!track.default){\n        continue;\n      }\n      if(!currentTrack||matchesOption(currentTrack, track, audioMatchPredicate)){\n        return i;\n      }\n    }\n    if(currentTrack){\n      const {\n        name,\n        lang,\n        assocLang,\n        characteristics,\n        audioCodec,\n        channels\n      }=currentTrack;\n      for (let i=0; i < audioTracks.length; i++){\n        const track=audioTracks[i];\n        if(matchesOption({\n          name,\n          lang,\n          assocLang,\n          characteristics,\n          audioCodec,\n          channels\n        }, track, audioMatchPredicate)){\n          return i;\n        }\n      }\n      for (let i=0; i < audioTracks.length; i++){\n        const track=audioTracks[i];\n        if(mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])){\n          return i;\n        }\n      }\n      for (let i=0; i < audioTracks.length; i++){\n        const track=audioTracks[i];\n        if(mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])){\n          return i;\n        }\n      }\n    }\n    return -1;\n  }\n  loadPlaylist(hlsUrlParameters){\n    const audioTrack=this.currentTrack;\n    if(this.shouldLoadPlaylist(audioTrack)&&audioTrack){\n      super.loadPlaylist();\n      const id=audioTrack.id;\n      const groupId=audioTrack.groupId;\n      let url=audioTrack.url;\n      if(hlsUrlParameters){\n        try {\n          url=hlsUrlParameters.addDirectives(url);\n        } catch (error){\n          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);\n        }\n      }\n      // track not retrieved yet, or live playlist we need to (re)load it\n      this.log(`loading audio-track playlist ${id} \"${audioTrack.name}\" lang:${audioTrack.lang} group:${groupId}`);\n      this.clearTimer();\n      this.hls.trigger(Events.AUDIO_TRACK_LOADING, {\n        url,\n        id,\n        groupId,\n        deliveryDirectives: hlsUrlParameters||null\n      });\n    }\n  }\n}\n\nconst TICK_INTERVAL$1=500; // how often to tick in ms\n\nclass SubtitleStreamController extends BaseStreamController {\n  constructor(hls, fragmentTracker, keyLoader){\n    super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);\n    this.currentTrackId=-1;\n    this.tracksBuffered=[];\n    this.mainDetails=null;\n    this._registerListeners();\n  }\n  onHandlerDestroying(){\n    this._unregisterListeners();\n    super.onHandlerDestroying();\n    this.mainDetails=null;\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.on(Events.ERROR, this.onError, this);\n    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);\n    hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);\n    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);\n    hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);\n    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.off(Events.ERROR, this.onError, this);\n    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);\n    hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);\n    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);\n    hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);\n    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n  startLoad(startPosition){\n    this.stopLoad();\n    this.state=State.IDLE;\n    this.setInterval(TICK_INTERVAL$1);\n    this.nextLoadPosition=this.startPosition=this.lastCurrentTime=startPosition;\n    this.tick();\n  }\n  onManifestLoading(){\n    this.mainDetails=null;\n    this.fragmentTracker.removeAllFragments();\n  }\n  onMediaDetaching(){\n    this.tracksBuffered=[];\n    super.onMediaDetaching();\n  }\n  onLevelLoaded(event, data){\n    this.mainDetails=data.details;\n  }\n  onSubtitleFragProcessed(event, data){\n    const {\n      frag,\n      success\n    }=data;\n    this.fragPrevious=frag;\n    this.state=State.IDLE;\n    if(!success){\n      return;\n    }\n    const buffered=this.tracksBuffered[this.currentTrackId];\n    if(!buffered){\n      return;\n    }\n\n    // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo\n    // so we can re-use the logic used to detect how much has been buffered\n    let timeRange;\n    const fragStart=frag.start;\n    for (let i=0; i < buffered.length; i++){\n      if(fragStart >=buffered[i].start&&fragStart <=buffered[i].end){\n        timeRange=buffered[i];\n        break;\n      }\n    }\n    const fragEnd=frag.start + frag.duration;\n    if(timeRange){\n      timeRange.end=fragEnd;\n    }else{\n      timeRange={\n        start: fragStart,\n        end: fragEnd\n      };\n      buffered.push(timeRange);\n    }\n    this.fragmentTracker.fragBuffered(frag);\n    this.fragBufferedComplete(frag, null);\n  }\n  onBufferFlushing(event, data){\n    const {\n      startOffset,\n      endOffset\n    }=data;\n    if(startOffset===0&&endOffset!==Number.POSITIVE_INFINITY){\n      const endOffsetSubtitles=endOffset - 1;\n      if(endOffsetSubtitles <=0){\n        return;\n      }\n      data.endOffsetSubtitles=Math.max(0, endOffsetSubtitles);\n      this.tracksBuffered.forEach(buffered=> {\n        for (let i=0; i < buffered.length;){\n          if(buffered[i].end <=endOffsetSubtitles){\n            buffered.shift();\n            continue;\n          }else if(buffered[i].start < endOffsetSubtitles){\n            buffered[i].start=endOffsetSubtitles;\n          }else{\n            break;\n          }\n          i++;\n        }\n      });\n      this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE);\n    }\n  }\n  onFragBuffered(event, data){\n    if(!this.loadedmetadata&&data.frag.type===PlaylistLevelType.MAIN){\n      var _this$media;\n      if((_this$media=this.media)!=null&&_this$media.buffered.length){\n        this.loadedmetadata=true;\n      }\n    }\n  }\n\n  // If something goes wrong, proceed to next frag, if we were processing one.\n  onError(event, data){\n    const frag=data.frag;\n    if((frag==null ? void 0:frag.type)===PlaylistLevelType.SUBTITLE){\n      if(data.details===ErrorDetails.FRAG_GAP){\n        this.fragmentTracker.fragBuffered(frag, true);\n      }\n      if(this.fragCurrent){\n        this.fragCurrent.abortRequests();\n      }\n      if(this.state!==State.STOPPED){\n        this.state=State.IDLE;\n      }\n    }\n  }\n\n  // Got all new subtitle levels.\n  onSubtitleTracksUpdated(event, {\n    subtitleTracks\n  }){\n    if(this.levels&&subtitleOptionsIdentical(this.levels, subtitleTracks)){\n      this.levels=subtitleTracks.map(mediaPlaylist=> new Level(mediaPlaylist));\n      return;\n    }\n    this.tracksBuffered=[];\n    this.levels=subtitleTracks.map(mediaPlaylist=> {\n      const level=new Level(mediaPlaylist);\n      this.tracksBuffered[level.id]=[];\n      return level;\n    });\n    this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE);\n    this.fragPrevious=null;\n    this.mediaBuffer=null;\n  }\n  onSubtitleTrackSwitch(event, data){\n    var _this$levels;\n    this.currentTrackId=data.id;\n    if(!((_this$levels=this.levels)!=null&&_this$levels.length)||this.currentTrackId===-1){\n      this.clearInterval();\n      return;\n    }\n\n    // Check if track has the necessary details to load fragments\n    const currentTrack=this.levels[this.currentTrackId];\n    if(currentTrack!=null&&currentTrack.details){\n      this.mediaBuffer=this.mediaBufferTimeRanges;\n    }else{\n      this.mediaBuffer=null;\n    }\n    if(currentTrack){\n      this.setInterval(TICK_INTERVAL$1);\n    }\n  }\n\n  // Got a new set of subtitle fragments.\n  onSubtitleTrackLoaded(event, data){\n    var _track$details;\n    const {\n      currentTrackId,\n      levels\n    }=this;\n    const {\n      details: newDetails,\n      id: trackId\n    }=data;\n    if(!levels){\n      this.warn(`Subtitle tracks were reset while loading level ${trackId}`);\n      return;\n    }\n    const track=levels[trackId];\n    if(trackId >=levels.length||!track){\n      return;\n    }\n    this.log(`Subtitle track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`:''},duration:${newDetails.totalduration}`);\n    this.mediaBuffer=this.mediaBufferTimeRanges;\n    let sliding=0;\n    if(newDetails.live||(_track$details=track.details)!=null&&_track$details.live){\n      const mainDetails=this.mainDetails;\n      if(newDetails.deltaUpdateFailed||!mainDetails){\n        return;\n      }\n      const mainSlidingStartFragment=mainDetails.fragments[0];\n      if(!track.details){\n        if(newDetails.hasProgramDateTime&&mainDetails.hasProgramDateTime){\n          alignMediaPlaylistByPDT(newDetails, mainDetails);\n          sliding=newDetails.fragments[0].start;\n        }else if(mainSlidingStartFragment){\n          // line up live playlist with main so that fragments in range are loaded\n          sliding=mainSlidingStartFragment.start;\n          addSliding(newDetails, sliding);\n        }\n      }else{\n        var _this$levelLastLoaded;\n        sliding=this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded=this.levelLastLoaded)==null ? void 0:_this$levelLastLoaded.details);\n        if(sliding===0&&mainSlidingStartFragment){\n          // realign with main when there is no overlap with last refresh\n          sliding=mainSlidingStartFragment.start;\n          addSliding(newDetails, sliding);\n        }\n      }\n    }\n    track.details=newDetails;\n    this.levelLastLoaded=track;\n    if(trackId!==currentTrackId){\n      return;\n    }\n    if(!this.startFragRequested&&(this.mainDetails||!newDetails.live)){\n      this.setStartPosition(this.mainDetails||newDetails, sliding);\n    }\n\n    // trigger handler right now\n    this.tick();\n\n    // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload\n    if(newDetails.live&&!this.fragCurrent&&this.media&&this.state===State.IDLE){\n      const foundFrag=findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0);\n      if(!foundFrag){\n        this.warn('Subtitle playlist not aligned with playback');\n        track.details=undefined;\n      }\n    }\n  }\n  _handleFragmentLoadComplete(fragLoadedData){\n    const {\n      frag,\n      payload\n    }=fragLoadedData;\n    const decryptData=frag.decryptdata;\n    const hls=this.hls;\n    if(this.fragContextChanged(frag)){\n      return;\n    }\n    // check to see if the payload needs to be decrypted\n    if(payload&&payload.byteLength > 0&&decryptData!=null&&decryptData.key&&decryptData.iv&&decryptData.method==='AES-128'){\n      const startTime=performance.now();\n      // decrypt the subtitles\n      this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err=> {\n        hls.trigger(Events.ERROR, {\n          type: ErrorTypes.MEDIA_ERROR,\n          details: ErrorDetails.FRAG_DECRYPT_ERROR,\n          fatal: false,\n          error: err,\n          reason: err.message,\n          frag\n        });\n        throw err;\n      }).then(decryptedData=> {\n        const endTime=performance.now();\n        hls.trigger(Events.FRAG_DECRYPTED, {\n          frag,\n          payload: decryptedData,\n          stats: {\n            tstart: startTime,\n            tdecrypt: endTime\n          }\n        });\n      }).catch(err=> {\n        this.warn(`${err.name}: ${err.message}`);\n        this.state=State.IDLE;\n      });\n    }\n  }\n  doTick(){\n    if(!this.media){\n      this.state=State.IDLE;\n      return;\n    }\n    if(this.state===State.IDLE){\n      const {\n        currentTrackId,\n        levels\n      }=this;\n      const track=levels==null ? void 0:levels[currentTrackId];\n      if(!track||!levels.length||!track.details){\n        return;\n      }\n      const {\n        config\n      }=this;\n      const currentTime=this.getLoadPosition();\n      const bufferedInfo=BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId]||[], currentTime, config.maxBufferHole);\n      const {\n        end: targetBufferTime,\n        len: bufferLen\n      }=bufferedInfo;\n      const mainBufferInfo=this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);\n      const trackDetails=track.details;\n      const maxBufLen=this.getMaxBufferLength(mainBufferInfo==null ? void 0:mainBufferInfo.len) + trackDetails.levelTargetDuration;\n      if(bufferLen > maxBufLen){\n        return;\n      }\n      const fragments=trackDetails.fragments;\n      const fragLen=fragments.length;\n      const end=trackDetails.edge;\n      let foundFrag=null;\n      const fragPrevious=this.fragPrevious;\n      if(targetBufferTime < end){\n        const tolerance=config.maxFragLookUpTolerance;\n        const lookupTolerance=targetBufferTime > end - tolerance ? 0:tolerance;\n        foundFrag=findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance);\n        if(!foundFrag&&fragPrevious&&fragPrevious.start < fragments[0].start){\n          foundFrag=fragments[0];\n        }\n      }else{\n        foundFrag=fragments[fragLen - 1];\n      }\n      if(!foundFrag){\n        return;\n      }\n      foundFrag=this.mapToInitFragWhenRequired(foundFrag);\n      if(foundFrag.sn!=='initSegment'){\n        // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment\n        const curSNIdx=foundFrag.sn - trackDetails.startSN;\n        const prevFrag=fragments[curSNIdx - 1];\n        if(prevFrag&&prevFrag.cc===foundFrag.cc&&this.fragmentTracker.getState(prevFrag)===FragmentState.NOT_LOADED){\n          foundFrag=prevFrag;\n        }\n      }\n      if(this.fragmentTracker.getState(foundFrag)===FragmentState.NOT_LOADED){\n        // only load if fragment is not loaded\n        this.loadFragment(foundFrag, track, targetBufferTime);\n      }\n    }\n  }\n  getMaxBufferLength(mainBufferLength){\n    const maxConfigBuffer=super.getMaxBufferLength();\n    if(!mainBufferLength){\n      return maxConfigBuffer;\n    }\n    return Math.max(maxConfigBuffer, mainBufferLength);\n  }\n  loadFragment(frag, level, targetBufferTime){\n    this.fragCurrent=frag;\n    if(frag.sn==='initSegment'){\n      this._loadInitSegment(frag, level);\n    }else{\n      this.startFragRequested=true;\n      super.loadFragment(frag, level, targetBufferTime);\n    }\n  }\n  get mediaBufferTimeRanges(){\n    return new BufferableInstance(this.tracksBuffered[this.currentTrackId]||[]);\n  }\n}\nclass BufferableInstance {\n  constructor(timeranges){\n    this.buffered=void 0;\n    const getRange=(name, index, length)=> {\n      index=index >>> 0;\n      if(index > length - 1){\n        throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`);\n      }\n      return timeranges[index][name];\n    };\n    this.buffered={\n      get length(){\n        return timeranges.length;\n      },\n      end(index){\n        return getRange('end', index, timeranges.length);\n      },\n      start(index){\n        return getRange('start', index, timeranges.length);\n      }\n    };\n  }\n}\n\nclass SubtitleTrackController extends BasePlaylistController {\n  constructor(hls){\n    super(hls, '[subtitle-track-controller]');\n    this.media=null;\n    this.tracks=[];\n    this.groupIds=null;\n    this.tracksInGroup=[];\n    this.trackId=-1;\n    this.currentTrack=null;\n    this.selectDefaultTrack=true;\n    this.queuedDefaultTrack=-1;\n    this.asyncPollTrackChange=()=> this.pollTrackChange(0);\n    this.useTextTrackPolling=false;\n    this.subtitlePollingInterval=-1;\n    this._subtitleDisplay=true;\n    this.onTextTracksChanged=()=> {\n      if(!this.useTextTrackPolling){\n        self.clearInterval(this.subtitlePollingInterval);\n      }\n      // Media is undefined when switching streams via loadSource()\n      if(!this.media||!this.hls.config.renderTextTracksNatively){\n        return;\n      }\n      let textTrack=null;\n      const tracks=filterSubtitleTracks(this.media.textTracks);\n      for (let i=0; i < tracks.length; i++){\n        if(tracks[i].mode==='hidden'){\n          // Do not break in case there is a following track with showing.\n          textTrack=tracks[i];\n        }else if(tracks[i].mode==='showing'){\n          textTrack=tracks[i];\n          break;\n        }\n      }\n\n      // Find internal track index for TextTrack\n      const trackId=this.findTrackForTextTrack(textTrack);\n      if(this.subtitleTrack!==trackId){\n        this.setSubtitleTrack(trackId);\n      }\n    };\n    this.registerListeners();\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.tracks.length=0;\n    this.tracksInGroup.length=0;\n    this.currentTrack=null;\n    this.onTextTracksChanged=this.asyncPollTrackChange=null;\n    super.destroy();\n  }\n  get subtitleDisplay(){\n    return this._subtitleDisplay;\n  }\n  set subtitleDisplay(value){\n    this._subtitleDisplay=value;\n    if(this.trackId > -1){\n      this.toggleTrackModes();\n    }\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);\n    hls.on(Events.ERROR, this.onError, this);\n  }\n  unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);\n    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);\n    hls.off(Events.ERROR, this.onError, this);\n  }\n\n  // Listen for subtitle track change, then extract the current track ID.\n  onMediaAttached(event, data){\n    this.media=data.media;\n    if(!this.media){\n      return;\n    }\n    if(this.queuedDefaultTrack > -1){\n      this.subtitleTrack=this.queuedDefaultTrack;\n      this.queuedDefaultTrack=-1;\n    }\n    this.useTextTrackPolling = !(this.media.textTracks&&'onchange' in this.media.textTracks);\n    if(this.useTextTrackPolling){\n      this.pollTrackChange(500);\n    }else{\n      this.media.textTracks.addEventListener('change', this.asyncPollTrackChange);\n    }\n  }\n  pollTrackChange(timeout){\n    self.clearInterval(this.subtitlePollingInterval);\n    this.subtitlePollingInterval=self.setInterval(this.onTextTracksChanged, timeout);\n  }\n  onMediaDetaching(){\n    if(!this.media){\n      return;\n    }\n    self.clearInterval(this.subtitlePollingInterval);\n    if(!this.useTextTrackPolling){\n      this.media.textTracks.removeEventListener('change', this.asyncPollTrackChange);\n    }\n    if(this.trackId > -1){\n      this.queuedDefaultTrack=this.trackId;\n    }\n    const textTracks=filterSubtitleTracks(this.media.textTracks);\n    // Clear loaded cues on media detachment from tracks\n    textTracks.forEach(track=> {\n      clearCurrentCues(track);\n    });\n    // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled.\n    this.subtitleTrack=-1;\n    this.media=null;\n  }\n  onManifestLoading(){\n    this.tracks=[];\n    this.groupIds=null;\n    this.tracksInGroup=[];\n    this.trackId=-1;\n    this.currentTrack=null;\n    this.selectDefaultTrack=true;\n  }\n\n  // Fired whenever a new manifest is loaded.\n  onManifestParsed(event, data){\n    this.tracks=data.subtitleTracks;\n  }\n  onSubtitleTrackLoaded(event, data){\n    const {\n      id,\n      groupId,\n      details\n    }=data;\n    const trackInActiveGroup=this.tracksInGroup[id];\n    if(!trackInActiveGroup||trackInActiveGroup.groupId!==groupId){\n      this.warn(`Subtitle track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup==null ? void 0:trackInActiveGroup.groupId}`);\n      return;\n    }\n    const curDetails=trackInActiveGroup.details;\n    trackInActiveGroup.details=data.details;\n    this.log(`Subtitle track ${id} \"${trackInActiveGroup.name}\" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);\n    if(id===this.trackId){\n      this.playlistLoaded(id, data, curDetails);\n    }\n  }\n  onLevelLoading(event, data){\n    this.switchLevel(data.level);\n  }\n  onLevelSwitching(event, data){\n    this.switchLevel(data.level);\n  }\n  switchLevel(levelIndex){\n    const levelInfo=this.hls.levels[levelIndex];\n    if(!levelInfo){\n      return;\n    }\n    const subtitleGroups=levelInfo.subtitleGroups||null;\n    const currentGroups=this.groupIds;\n    let currentTrack=this.currentTrack;\n    if(!subtitleGroups||(currentGroups==null ? void 0:currentGroups.length)!==(subtitleGroups==null ? void 0:subtitleGroups.length)||subtitleGroups!=null&&subtitleGroups.some(groupId=> (currentGroups==null ? void 0:currentGroups.indexOf(groupId))===-1)){\n      this.groupIds=subtitleGroups;\n      this.trackId=-1;\n      this.currentTrack=null;\n      const subtitleTracks=this.tracks.filter(track=> !subtitleGroups||subtitleGroups.indexOf(track.groupId)!==-1);\n      if(subtitleTracks.length){\n        // Disable selectDefaultTrack if there are no default tracks\n        if(this.selectDefaultTrack&&!subtitleTracks.some(track=> track.default)){\n          this.selectDefaultTrack=false;\n        }\n        // track.id should match hls.audioTracks index\n        subtitleTracks.forEach((track, i)=> {\n          track.id=i;\n        });\n      }else if(!currentTrack&&!this.tracksInGroup.length){\n        // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks\n        return;\n      }\n      this.tracksInGroup=subtitleTracks;\n\n      // Find preferred track\n      const subtitlePreference=this.hls.config.subtitlePreference;\n      if(!currentTrack&&subtitlePreference){\n        this.selectDefaultTrack=false;\n        const groupIndex=findMatchingOption(subtitlePreference, subtitleTracks);\n        if(groupIndex > -1){\n          currentTrack=subtitleTracks[groupIndex];\n        }else{\n          const allIndex=findMatchingOption(subtitlePreference, this.tracks);\n          currentTrack=this.tracks[allIndex];\n        }\n      }\n\n      // Select initial track\n      let trackId=this.findTrackId(currentTrack);\n      if(trackId===-1&&currentTrack){\n        trackId=this.findTrackId(null);\n      }\n\n      // Dispatch events and load track if needed\n      const subtitleTracksUpdated={\n        subtitleTracks\n      };\n      this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in \"${subtitleGroups==null ? void 0:subtitleGroups.join(',')}\" group-id`);\n      this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated);\n      if(trackId!==-1&&this.trackId===-1){\n        this.setSubtitleTrack(trackId);\n      }\n    }else if(this.shouldReloadPlaylist(currentTrack)){\n      // Retry playlist loading if no playlist is or has been loaded yet\n      this.setSubtitleTrack(this.trackId);\n    }\n  }\n  findTrackId(currentTrack){\n    const tracks=this.tracksInGroup;\n    const selectDefault=this.selectDefaultTrack;\n    for (let i=0; i < tracks.length; i++){\n      const track=tracks[i];\n      if(selectDefault&&!track.default||!selectDefault&&!currentTrack){\n        continue;\n      }\n      if(!currentTrack||matchesOption(track, currentTrack)){\n        return i;\n      }\n    }\n    if(currentTrack){\n      for (let i=0; i < tracks.length; i++){\n        const track=tracks[i];\n        if(mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])){\n          return i;\n        }\n      }\n      for (let i=0; i < tracks.length; i++){\n        const track=tracks[i];\n        if(mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])){\n          return i;\n        }\n      }\n    }\n    return -1;\n  }\n  findTrackForTextTrack(textTrack){\n    if(textTrack){\n      const tracks=this.tracksInGroup;\n      for (let i=0; i < tracks.length; i++){\n        const track=tracks[i];\n        if(subtitleTrackMatchesTextTrack(track, textTrack)){\n          return i;\n        }\n      }\n    }\n    return -1;\n  }\n  onError(event, data){\n    if(data.fatal||!data.context){\n      return;\n    }\n    if(data.context.type===PlaylistContextType.SUBTITLE_TRACK&&data.context.id===this.trackId&&(!this.groupIds||this.groupIds.indexOf(data.context.groupId)!==-1)){\n      this.checkRetry(data);\n    }\n  }\n  get allSubtitleTracks(){\n    return this.tracks;\n  }\n\n  \n  get subtitleTracks(){\n    return this.tracksInGroup;\n  }\n\n  \n  get subtitleTrack(){\n    return this.trackId;\n  }\n  set subtitleTrack(newId){\n    this.selectDefaultTrack=false;\n    this.setSubtitleTrack(newId);\n  }\n  setSubtitleOption(subtitleOption){\n    this.hls.config.subtitlePreference=subtitleOption;\n    if(subtitleOption){\n      const allSubtitleTracks=this.allSubtitleTracks;\n      this.selectDefaultTrack=false;\n      if(allSubtitleTracks.length){\n        // First see if current option matches (no switch op)\n        const currentTrack=this.currentTrack;\n        if(currentTrack&&matchesOption(subtitleOption, currentTrack)){\n          return currentTrack;\n        }\n        // Find option in current group\n        const groupIndex=findMatchingOption(subtitleOption, this.tracksInGroup);\n        if(groupIndex > -1){\n          const track=this.tracksInGroup[groupIndex];\n          this.setSubtitleTrack(groupIndex);\n          return track;\n        }else if(currentTrack){\n          // If this is not the initial selection return null\n          // option should have matched one in active group\n          return null;\n        }else{\n          // Find the option in all tracks for initial selection\n          const allIndex=findMatchingOption(subtitleOption, allSubtitleTracks);\n          if(allIndex > -1){\n            return allSubtitleTracks[allIndex];\n          }\n        }\n      }\n    }\n    return null;\n  }\n  loadPlaylist(hlsUrlParameters){\n    super.loadPlaylist();\n    const currentTrack=this.currentTrack;\n    if(this.shouldLoadPlaylist(currentTrack)&&currentTrack){\n      const id=currentTrack.id;\n      const groupId=currentTrack.groupId;\n      let url=currentTrack.url;\n      if(hlsUrlParameters){\n        try {\n          url=hlsUrlParameters.addDirectives(url);\n        } catch (error){\n          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);\n        }\n      }\n      this.log(`Loading subtitle playlist for id ${id}`);\n      this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, {\n        url,\n        id,\n        groupId,\n        deliveryDirectives: hlsUrlParameters||null\n      });\n    }\n  }\n\n  \n  toggleTrackModes(){\n    const {\n      media\n    }=this;\n    if(!media){\n      return;\n    }\n    const textTracks=filterSubtitleTracks(media.textTracks);\n    const currentTrack=this.currentTrack;\n    let nextTrack;\n    if(currentTrack){\n      nextTrack=textTracks.filter(textTrack=> subtitleTrackMatchesTextTrack(currentTrack, textTrack))[0];\n      if(!nextTrack){\n        this.warn(`Unable to find subtitle TextTrack with name \"${currentTrack.name}\" and language \"${currentTrack.lang}\"`);\n      }\n    }\n    [].slice.call(textTracks).forEach(track=> {\n      if(track.mode!=='disabled'&&track!==nextTrack){\n        track.mode='disabled';\n      }\n    });\n    if(nextTrack){\n      const mode=this.subtitleDisplay ? 'showing':'hidden';\n      if(nextTrack.mode!==mode){\n        nextTrack.mode=mode;\n      }\n    }\n  }\n\n  \n  setSubtitleTrack(newId){\n    const tracks=this.tracksInGroup;\n\n    // setting this.subtitleTrack will trigger internal logic\n    // if media has not been attached yet, it will fail\n    // we keep a reference to the default track id\n    // and we'll set subtitleTrack when onMediaAttached is triggered\n    if(!this.media){\n      this.queuedDefaultTrack=newId;\n      return;\n    }\n\n    // exit if track id as already set or invalid\n    if(newId < -1||newId >=tracks.length||!isFiniteNumber(newId)){\n      this.warn(`Invalid subtitle track id: ${newId}`);\n      return;\n    }\n\n    // stopping live reloading timer if any\n    this.clearTimer();\n    this.selectDefaultTrack=false;\n    const lastTrack=this.currentTrack;\n    const track=tracks[newId]||null;\n    this.trackId=newId;\n    this.currentTrack=track;\n    this.toggleTrackModes();\n    if(!track){\n      // switch to -1\n      this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {\n        id: newId\n      });\n      return;\n    }\n    const trackLoaded = !!track.details&&!track.details.live;\n    if(newId===this.trackId&&track===lastTrack&&trackLoaded){\n      return;\n    }\n    this.log(`Switching to subtitle-track ${newId}` + (track ? ` \"${track.name}\" lang:${track.lang} group:${track.groupId}`:''));\n    const {\n      id,\n      groupId='',\n      name,\n      type,\n      url\n    }=track;\n    this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {\n      id,\n      groupId,\n      name,\n      type,\n      url\n    });\n    const hlsUrlParameters=this.switchParams(track.url, lastTrack==null ? void 0:lastTrack.details, track.details);\n    this.loadPlaylist(hlsUrlParameters);\n  }\n}\n\nclass BufferOperationQueue {\n  constructor(sourceBufferReference){\n    this.buffers=void 0;\n    this.queues={\n      video: [],\n      audio: [],\n      audiovideo: []\n    };\n    this.buffers=sourceBufferReference;\n  }\n  append(operation, type, pending){\n    const queue=this.queues[type];\n    queue.push(operation);\n    if(queue.length===1&&!pending){\n      this.executeNext(type);\n    }\n  }\n  insertAbort(operation, type){\n    const queue=this.queues[type];\n    queue.unshift(operation);\n    this.executeNext(type);\n  }\n  appendBlocker(type){\n    let execute;\n    const promise=new Promise(resolve=> {\n      execute=resolve;\n    });\n    const operation={\n      execute,\n      onStart: ()=> {},\n      onComplete: ()=> {},\n      onError: ()=> {}\n    };\n    this.append(operation, type);\n    return promise;\n  }\n  executeNext(type){\n    const queue=this.queues[type];\n    if(queue.length){\n      const operation=queue[0];\n      try {\n        // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations\n        // which do not end with this event must call _onSBUpdateEnd manually\n        operation.execute();\n      } catch (error){\n        logger.warn(`[buffer-operation-queue]: Exception executing \"${type}\" SourceBuffer operation: ${error}`);\n        operation.onError(error);\n\n        // Only shift the current operation off, otherwise the updateend handler will do this for us\n        const sb=this.buffers[type];\n        if(!(sb!=null&&sb.updating)){\n          this.shiftAndExecuteNext(type);\n        }\n      }\n    }\n  }\n  shiftAndExecuteNext(type){\n    this.queues[type].shift();\n    this.executeNext(type);\n  }\n  current(type){\n    return this.queues[type][0];\n  }\n}\n\nconst VIDEO_CODEC_PROFILE_REPLACE=/(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\\.[^.,]+)+/;\nclass BufferController {\n  constructor(hls){\n    // The level details used to determine duration, target-duration and live\n    this.details=null;\n    // cache the self generated object url to detect hijack of video tag\n    this._objectUrl=null;\n    // A queue of buffer operations which require the SourceBuffer to not be updating upon execution\n    this.operationQueue=void 0;\n    // References to event listeners for each SourceBuffer, so that they can be referenced for event removal\n    this.listeners=void 0;\n    this.hls=void 0;\n    // The number of BUFFER_CODEC events received before any sourceBuffers are created\n    this.bufferCodecEventsExpected=0;\n    // The total number of BUFFER_CODEC events received\n    this._bufferCodecEventsTotal=0;\n    // A reference to the attached media element\n    this.media=null;\n    // A reference to the active media source\n    this.mediaSource=null;\n    // Last MP3 audio chunk appended\n    this.lastMpegAudioChunk=null;\n    this.appendSource=void 0;\n    // counters\n    this.appendErrors={\n      audio: 0,\n      video: 0,\n      audiovideo: 0\n    };\n    this.tracks={};\n    this.pendingTracks={};\n    this.sourceBuffer=void 0;\n    this.log=void 0;\n    this.warn=void 0;\n    this.error=void 0;\n    this._onEndStreaming=event=> {\n      if(!this.hls){\n        return;\n      }\n      this.hls.pauseBuffering();\n    };\n    this._onStartStreaming=event=> {\n      if(!this.hls){\n        return;\n      }\n      this.hls.resumeBuffering();\n    };\n    // Keep as arrow functions so that we can directly reference these functions directly as event listeners\n    this._onMediaSourceOpen=()=> {\n      const {\n        media,\n        mediaSource\n      }=this;\n      this.log('Media source opened');\n      if(media){\n        media.removeEventListener('emptied', this._onMediaEmptied);\n        this.updateMediaElementDuration();\n        this.hls.trigger(Events.MEDIA_ATTACHED, {\n          media,\n          mediaSource: mediaSource\n        });\n      }\n      if(mediaSource){\n        // once received, don't listen anymore to sourceopen event\n        mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);\n      }\n      this.checkPendingTracks();\n    };\n    this._onMediaSourceClose=()=> {\n      this.log('Media source closed');\n    };\n    this._onMediaSourceEnded=()=> {\n      this.log('Media source ended');\n    };\n    this._onMediaEmptied=()=> {\n      const {\n        mediaSrc,\n        _objectUrl\n      }=this;\n      if(mediaSrc!==_objectUrl){\n        logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);\n      }\n    };\n    this.hls=hls;\n    const logPrefix='[buffer-controller]';\n    this.appendSource=isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource));\n    this.log=logger.log.bind(logger, logPrefix);\n    this.warn=logger.warn.bind(logger, logPrefix);\n    this.error=logger.error.bind(logger, logPrefix);\n    this._initSourceBuffer();\n    this.registerListeners();\n  }\n  hasSourceTypes(){\n    return this.getSourceBufferTypes().length > 0||Object.keys(this.pendingTracks).length > 0;\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.details=null;\n    this.lastMpegAudioChunk=null;\n    // @ts-ignore\n    this.hls=null;\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);\n    hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);\n    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);\n    hls.on(Events.BUFFER_EOS, this.onBufferEos, this);\n    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n    hls.on(Events.FRAG_PARSED, this.onFragParsed, this);\n    hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);\n  }\n  unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);\n    hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);\n    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);\n    hls.off(Events.BUFFER_EOS, this.onBufferEos, this);\n    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);\n    hls.off(Events.FRAG_PARSED, this.onFragParsed, this);\n    hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);\n  }\n  _initSourceBuffer(){\n    this.sourceBuffer={};\n    this.operationQueue=new BufferOperationQueue(this.sourceBuffer);\n    this.listeners={\n      audio: [],\n      video: [],\n      audiovideo: []\n    };\n    this.appendErrors={\n      audio: 0,\n      video: 0,\n      audiovideo: 0\n    };\n    this.lastMpegAudioChunk=null;\n  }\n  onManifestLoading(){\n    this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=0;\n    this.details=null;\n  }\n  onManifestParsed(event, data){\n    // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller\n    // sourcebuffers will be created all at once when the expected nb of tracks will be reached\n    // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller\n    // it will contain the expected nb of source buffers, no need to compute it\n    let codecEvents=2;\n    if(data.audio&&!data.video||!data.altAudio||!true){\n      codecEvents=1;\n    }\n    this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=codecEvents;\n    this.log(`${this.bufferCodecEventsExpected} bufferCodec event(s) expected`);\n  }\n  onMediaAttaching(event, data){\n    const media=this.media=data.media;\n    const MediaSource=getMediaSource(this.appendSource);\n    if(media&&MediaSource){\n      var _ms$constructor;\n      const ms=this.mediaSource=new MediaSource();\n      this.log(`created media source: ${(_ms$constructor=ms.constructor)==null ? void 0:_ms$constructor.name}`);\n      // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound\n      ms.addEventListener('sourceopen', this._onMediaSourceOpen);\n      ms.addEventListener('sourceended', this._onMediaSourceEnded);\n      ms.addEventListener('sourceclose', this._onMediaSourceClose);\n      if(this.appendSource){\n        ms.addEventListener('startstreaming', this._onStartStreaming);\n        ms.addEventListener('endstreaming', this._onEndStreaming);\n      }\n\n      // cache the locally generated object url\n      const objectUrl=this._objectUrl=self.URL.createObjectURL(ms);\n      // link video and media Source\n      if(this.appendSource){\n        try {\n          media.removeAttribute('src');\n          // ManagedMediaSource will not open without disableRemotePlayback set to false or source alternatives\n          const MMS=self.ManagedMediaSource;\n          media.disableRemotePlayback=media.disableRemotePlayback||MMS&&ms instanceof MMS;\n          removeSourceChildren(media);\n          addSource(media, objectUrl);\n          media.load();\n        } catch (error){\n          media.src=objectUrl;\n        }\n      }else{\n        media.src=objectUrl;\n      }\n      media.addEventListener('emptied', this._onMediaEmptied);\n    }\n  }\n  onMediaDetaching(){\n    const {\n      media,\n      mediaSource,\n      _objectUrl\n    }=this;\n    if(mediaSource){\n      this.log('media source detaching');\n      if(mediaSource.readyState==='open'){\n        try {\n          // endOfStream could trigger exception if any sourcebuffer is in updating state\n          // we don't really care about checking sourcebuffer state here,\n          // as we are anyway detaching the MediaSource\n          // let's just avoid this exception to propagate\n          mediaSource.endOfStream();\n        } catch (err){\n          this.warn(`onMediaDetaching: ${err.message} while calling endOfStream`);\n        }\n      }\n      // Clean up the SourceBuffers by invoking onBufferReset\n      this.onBufferReset();\n      mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);\n      mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);\n      mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);\n      if(this.appendSource){\n        mediaSource.removeEventListener('startstreaming', this._onStartStreaming);\n        mediaSource.removeEventListener('endstreaming', this._onEndStreaming);\n      }\n\n      // Detach properly the MediaSource from the HTMLMediaElement as\n      // suggested in https://github.com/w3c/media-source/issues/53.\n      if(media){\n        media.removeEventListener('emptied', this._onMediaEmptied);\n        if(_objectUrl){\n          self.URL.revokeObjectURL(_objectUrl);\n        }\n\n        // clean up video tag src only if it's our own url. some external libraries might\n        // hijack the video tag and change its 'src' without destroying the Hls instance first\n        if(this.mediaSrc===_objectUrl){\n          media.removeAttribute('src');\n          if(this.appendSource){\n            removeSourceChildren(media);\n          }\n          media.load();\n        }else{\n          this.warn('media|source.src was changed by a third party - skip cleanup');\n        }\n      }\n      this.mediaSource=null;\n      this.media=null;\n      this._objectUrl=null;\n      this.bufferCodecEventsExpected=this._bufferCodecEventsTotal;\n      this.pendingTracks={};\n      this.tracks={};\n    }\n    this.hls.trigger(Events.MEDIA_DETACHED, undefined);\n  }\n  onBufferReset(){\n    this.getSourceBufferTypes().forEach(type=> {\n      this.resetBuffer(type);\n    });\n    this._initSourceBuffer();\n    this.hls.resumeBuffering();\n  }\n  resetBuffer(type){\n    const sb=this.sourceBuffer[type];\n    try {\n      if(sb){\n        var _this$mediaSource;\n        this.removeBufferListeners(type);\n        // Synchronously remove the SB from the map before the next call in order to prevent an async function from\n        // accessing it\n        this.sourceBuffer[type]=undefined;\n        if((_this$mediaSource=this.mediaSource)!=null&&_this$mediaSource.sourceBuffers.length){\n          this.mediaSource.removeSourceBuffer(sb);\n        }\n      }\n    } catch (err){\n      this.warn(`onBufferReset ${type}`, err);\n    }\n  }\n  onBufferCodecs(event, data){\n    const sourceBufferCount=this.getSourceBufferTypes().length;\n    const trackNames=Object.keys(data);\n    trackNames.forEach(trackName=> {\n      if(sourceBufferCount){\n        // check if SourceBuffer codec needs to change\n        const track=this.tracks[trackName];\n        if(track&&typeof track.buffer.changeType==='function'){\n          var _trackCodec;\n          const {\n            id,\n            codec,\n            levelCodec,\n            container,\n            metadata\n          }=data[trackName];\n          const currentCodecFull=pickMostCompleteCodecName(track.codec, track.levelCodec);\n          const currentCodec=currentCodecFull==null ? void 0:currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');\n          let trackCodec=pickMostCompleteCodecName(codec, levelCodec);\n          const nextCodec=(_trackCodec=trackCodec)==null ? void 0:_trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');\n          if(trackCodec&&currentCodec!==nextCodec){\n            if(trackName.slice(0, 5)==='audio'){\n              trackCodec=getCodecCompatibleName(trackCodec, this.appendSource);\n            }\n            const mimeType=`${container};codecs=${trackCodec}`;\n            this.appendChangeType(trackName, mimeType);\n            this.log(`switching codec ${currentCodecFull} to ${trackCodec}`);\n            this.tracks[trackName]={\n              buffer: track.buffer,\n              codec,\n              container,\n              levelCodec,\n              metadata,\n              id\n            };\n          }\n        }\n      }else{\n        // if source buffer(s) not created yet, appended buffer tracks in this.pendingTracks\n        this.pendingTracks[trackName]=data[trackName];\n      }\n    });\n\n    // if sourcebuffers already created, do nothing ...\n    if(sourceBufferCount){\n      return;\n    }\n    const bufferCodecEventsExpected=Math.max(this.bufferCodecEventsExpected - 1, 0);\n    if(this.bufferCodecEventsExpected!==bufferCodecEventsExpected){\n      this.log(`${bufferCodecEventsExpected} bufferCodec event(s) expected ${trackNames.join(',')}`);\n      this.bufferCodecEventsExpected=bufferCodecEventsExpected;\n    }\n    if(this.mediaSource&&this.mediaSource.readyState==='open'){\n      this.checkPendingTracks();\n    }\n  }\n  appendChangeType(type, mimeType){\n    const {\n      operationQueue\n    }=this;\n    const operation={\n      execute: ()=> {\n        const sb=this.sourceBuffer[type];\n        if(sb){\n          this.log(`changing ${type} sourceBuffer type to ${mimeType}`);\n          sb.changeType(mimeType);\n        }\n        operationQueue.shiftAndExecuteNext(type);\n      },\n      onStart: ()=> {},\n      onComplete: ()=> {},\n      onError: error=> {\n        this.warn(`Failed to change ${type} SourceBuffer type`, error);\n      }\n    };\n    operationQueue.append(operation, type, !!this.pendingTracks[type]);\n  }\n  onBufferAppending(event, eventData){\n    const {\n      hls,\n      operationQueue,\n      tracks\n    }=this;\n    const {\n      data,\n      type,\n      frag,\n      part,\n      chunkMeta\n    }=eventData;\n    const chunkStats=chunkMeta.buffering[type];\n    const bufferAppendingStart=self.performance.now();\n    chunkStats.start=bufferAppendingStart;\n    const fragBuffering=frag.stats.buffering;\n    const partBuffering=part ? part.stats.buffering:null;\n    if(fragBuffering.start===0){\n      fragBuffering.start=bufferAppendingStart;\n    }\n    if(partBuffering&&partBuffering.start===0){\n      partBuffering.start=bufferAppendingStart;\n    }\n\n    // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended\n    // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended)\n    // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset`\n    // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos).\n    // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486\n    const audioTrack=tracks.audio;\n    let checkTimestampOffset=false;\n    if(type==='audio'&&(audioTrack==null ? void 0:audioTrack.container)==='audio/mpeg'){\n      checkTimestampOffset = !this.lastMpegAudioChunk||chunkMeta.id===1||this.lastMpegAudioChunk.sn!==chunkMeta.sn;\n      this.lastMpegAudioChunk=chunkMeta;\n    }\n    const fragStart=frag.start;\n    const operation={\n      execute: ()=> {\n        chunkStats.executeStart=self.performance.now();\n        if(checkTimestampOffset){\n          const sb=this.sourceBuffer[type];\n          if(sb){\n            const delta=fragStart - sb.timestampOffset;\n            if(Math.abs(delta) >=0.1){\n              this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);\n              sb.timestampOffset=fragStart;\n            }\n          }\n        }\n        this.appendExecutor(data, type);\n      },\n      onStart: ()=> {\n        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`);\n      },\n      onComplete: ()=> {\n        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`);\n        const end=self.performance.now();\n        chunkStats.executeEnd=chunkStats.end=end;\n        if(fragBuffering.first===0){\n          fragBuffering.first=end;\n        }\n        if(partBuffering&&partBuffering.first===0){\n          partBuffering.first=end;\n        }\n        const {\n          sourceBuffer\n        }=this;\n        const timeRanges={};\n        for (const type in sourceBuffer){\n          timeRanges[type]=BufferHelper.getBuffered(sourceBuffer[type]);\n        }\n        this.appendErrors[type]=0;\n        if(type==='audio'||type==='video'){\n          this.appendErrors.audiovideo=0;\n        }else{\n          this.appendErrors.audio=0;\n          this.appendErrors.video=0;\n        }\n        this.hls.trigger(Events.BUFFER_APPENDED, {\n          type,\n          frag,\n          part,\n          chunkMeta,\n          parent: frag.type,\n          timeRanges\n        });\n      },\n      onError: error=> {\n        // in case any error occured while appending, put back segment in segments table\n        const event={\n          type: ErrorTypes.MEDIA_ERROR,\n          parent: frag.type,\n          details: ErrorDetails.BUFFER_APPEND_ERROR,\n          sourceBufferName: type,\n          frag,\n          part,\n          chunkMeta,\n          error,\n          err: error,\n          fatal: false\n        };\n        if(error.code===DOMException.QUOTA_EXCEEDED_ERR){\n          // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror\n          // let's stop appending any segments, and report BUFFER_FULL_ERROR error\n          event.details=ErrorDetails.BUFFER_FULL_ERROR;\n        }else{\n          const appendErrorCount=++this.appendErrors[type];\n          event.details=ErrorDetails.BUFFER_APPEND_ERROR;\n          \n          this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in \"${type}\" sourceBuffer`);\n          if(appendErrorCount >=hls.config.appendErrorMaxRetry){\n            event.fatal=true;\n          }\n        }\n        hls.trigger(Events.ERROR, event);\n      }\n    };\n    operationQueue.append(operation, type, !!this.pendingTracks[type]);\n  }\n  onBufferFlushing(event, data){\n    const {\n      operationQueue\n    }=this;\n    const flushOperation=type=> ({\n      execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),\n      onStart: ()=> {\n        // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);\n      },\n      onComplete: ()=> {\n        // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);\n        this.hls.trigger(Events.BUFFER_FLUSHED, {\n          type\n        });\n      },\n      onError: error=> {\n        this.warn(`Failed to remove from ${type} SourceBuffer`, error);\n      }\n    });\n    if(data.type){\n      operationQueue.append(flushOperation(data.type), data.type);\n    }else{\n      this.getSourceBufferTypes().forEach(type=> {\n        operationQueue.append(flushOperation(type), type);\n      });\n    }\n  }\n  onFragParsed(event, data){\n    const {\n      frag,\n      part\n    }=data;\n    const buffersAppendedTo=[];\n    const elementaryStreams=part ? part.elementaryStreams:frag.elementaryStreams;\n    if(elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]){\n      buffersAppendedTo.push('audiovideo');\n    }else{\n      if(elementaryStreams[ElementaryStreamTypes.AUDIO]){\n        buffersAppendedTo.push('audio');\n      }\n      if(elementaryStreams[ElementaryStreamTypes.VIDEO]){\n        buffersAppendedTo.push('video');\n      }\n    }\n    const onUnblocked=()=> {\n      const now=self.performance.now();\n      frag.stats.buffering.end=now;\n      if(part){\n        part.stats.buffering.end=now;\n      }\n      const stats=part ? part.stats:frag.stats;\n      this.hls.trigger(Events.FRAG_BUFFERED, {\n        frag,\n        part,\n        stats,\n        id: frag.type\n      });\n    };\n    if(buffersAppendedTo.length===0){\n      this.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`);\n    }\n    this.blockBuffers(onUnblocked, buffersAppendedTo);\n  }\n  onFragChanged(event, data){\n    this.trimBuffers();\n  }\n\n  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()\n  // an undefined data.type will mark all buffers as EOS.\n  onBufferEos(event, data){\n    const ended=this.getSourceBufferTypes().reduce((acc, type)=> {\n      const sb=this.sourceBuffer[type];\n      if(sb&&(!data.type||data.type===type)){\n        sb.ending=true;\n        if(!sb.ended){\n          sb.ended=true;\n          this.log(`${type} sourceBuffer now EOS`);\n        }\n      }\n      return acc&&!!(!sb||sb.ended);\n    }, true);\n    if(ended){\n      this.log(`Queueing mediaSource.endOfStream()`);\n      this.blockBuffers(()=> {\n        this.getSourceBufferTypes().forEach(type=> {\n          const sb=this.sourceBuffer[type];\n          if(sb){\n            sb.ending=false;\n          }\n        });\n        const {\n          mediaSource\n        }=this;\n        if(!mediaSource||mediaSource.readyState!=='open'){\n          if(mediaSource){\n            this.log(`Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`);\n          }\n          return;\n        }\n        this.log(`Calling mediaSource.endOfStream()`);\n        // Allow this to throw and be caught by the enqueueing function\n        mediaSource.endOfStream();\n      });\n    }\n  }\n  onLevelUpdated(event, {\n    details\n  }){\n    if(!details.fragments.length){\n      return;\n    }\n    this.details=details;\n    if(this.getSourceBufferTypes().length){\n      this.blockBuffers(this.updateMediaElementDuration.bind(this));\n    }else{\n      this.updateMediaElementDuration();\n    }\n  }\n  trimBuffers(){\n    const {\n      hls,\n      details,\n      media\n    }=this;\n    if(!media||details===null){\n      return;\n    }\n    const sourceBufferTypes=this.getSourceBufferTypes();\n    if(!sourceBufferTypes.length){\n      return;\n    }\n    const config=hls.config;\n    const currentTime=media.currentTime;\n    const targetDuration=details.levelTargetDuration;\n\n    // Support for deprecated liveBackBufferLength\n    const backBufferLength=details.live&&config.liveBackBufferLength!==null ? config.liveBackBufferLength:config.backBufferLength;\n    if(isFiniteNumber(backBufferLength)&&backBufferLength > 0){\n      const maxBackBufferLength=Math.max(backBufferLength, targetDuration);\n      const targetBackBufferPosition=Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength;\n      this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition);\n    }\n    if(isFiniteNumber(config.frontBufferFlushThreshold)&&config.frontBufferFlushThreshold > 0){\n      const frontBufferLength=Math.max(config.maxBufferLength, config.frontBufferFlushThreshold);\n      const maxFrontBufferLength=Math.max(frontBufferLength, targetDuration);\n      const targetFrontBufferPosition=Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength;\n      this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition);\n    }\n  }\n  flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition){\n    const {\n      details,\n      sourceBuffer\n    }=this;\n    const sourceBufferTypes=this.getSourceBufferTypes();\n    sourceBufferTypes.forEach(type=> {\n      const sb=sourceBuffer[type];\n      if(sb){\n        const buffered=BufferHelper.getBuffered(sb);\n        // when target buffer start exceeds actual buffer start\n        if(buffered.length > 0&&targetBackBufferPosition > buffered.start(0)){\n          this.hls.trigger(Events.BACK_BUFFER_REACHED, {\n            bufferEnd: targetBackBufferPosition\n          });\n\n          // Support for deprecated event:\n          if(details!=null&&details.live){\n            this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, {\n              bufferEnd: targetBackBufferPosition\n            });\n          }else if(sb.ended&&buffered.end(buffered.length - 1) - currentTime < targetDuration * 2){\n            this.log(`Cannot flush ${type} back buffer while SourceBuffer is in ended state`);\n            return;\n          }\n          this.hls.trigger(Events.BUFFER_FLUSHING, {\n            startOffset: 0,\n            endOffset: targetBackBufferPosition,\n            type\n          });\n        }\n      }\n    });\n  }\n  flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition){\n    const {\n      sourceBuffer\n    }=this;\n    const sourceBufferTypes=this.getSourceBufferTypes();\n    sourceBufferTypes.forEach(type=> {\n      const sb=sourceBuffer[type];\n      if(sb){\n        const buffered=BufferHelper.getBuffered(sb);\n        const numBufferedRanges=buffered.length;\n        // The buffer is either empty or contiguous\n        if(numBufferedRanges < 2){\n          return;\n        }\n        const bufferStart=buffered.start(numBufferedRanges - 1);\n        const bufferEnd=buffered.end(numBufferedRanges - 1);\n        // No flush if we can tolerate the current buffer length or the current buffer range we would flush is contiguous with current position\n        if(targetFrontBufferPosition > bufferStart||currentTime >=bufferStart&&currentTime <=bufferEnd){\n          return;\n        }else if(sb.ended&&currentTime - bufferEnd < 2 * targetDuration){\n          this.log(`Cannot flush ${type} front buffer while SourceBuffer is in ended state`);\n          return;\n        }\n        this.hls.trigger(Events.BUFFER_FLUSHING, {\n          startOffset: bufferStart,\n          endOffset: Infinity,\n          type\n        });\n      }\n    });\n  }\n\n  \n  updateMediaElementDuration(){\n    if(!this.details||!this.media||!this.mediaSource||this.mediaSource.readyState!=='open'){\n      return;\n    }\n    const {\n      details,\n      hls,\n      media,\n      mediaSource\n    }=this;\n    const levelDuration=details.fragments[0].start + details.totalduration;\n    const mediaDuration=media.duration;\n    const msDuration=isFiniteNumber(mediaSource.duration) ? mediaSource.duration:0;\n    if(details.live&&hls.config.liveDurationInfinity){\n      // Override duration to Infinity\n      mediaSource.duration=Infinity;\n      this.updateSeekableRange(details);\n    }else if(levelDuration > msDuration&&levelDuration > mediaDuration||!isFiniteNumber(mediaDuration)){\n      // levelDuration was the last value we set.\n      // not using mediaSource.duration as the browser may tweak this value\n      // only update Media Source duration if its value increase, this is to avoid\n      // flushing already buffered portion when switching between quality level\n      this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);\n      mediaSource.duration=levelDuration;\n    }\n  }\n  updateSeekableRange(levelDetails){\n    const mediaSource=this.mediaSource;\n    const fragments=levelDetails.fragments;\n    const len=fragments.length;\n    if(len&&levelDetails.live&&mediaSource!=null&&mediaSource.setLiveSeekableRange){\n      const start=Math.max(0, fragments[0].start);\n      const end=Math.max(start, start + levelDetails.totalduration);\n      this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);\n      mediaSource.setLiveSeekableRange(start, end);\n    }\n  }\n  checkPendingTracks(){\n    const {\n      bufferCodecEventsExpected,\n      operationQueue,\n      pendingTracks\n    }=this;\n\n    // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once.\n    // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after\n    // data has been appended to existing ones.\n    // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers.\n    const pendingTracksCount=Object.keys(pendingTracks).length;\n    if(pendingTracksCount&&(!bufferCodecEventsExpected||pendingTracksCount===2||'audiovideo' in pendingTracks)){\n      // ok, let's create them now !\n      this.createSourceBuffers(pendingTracks);\n      this.pendingTracks={};\n      // append any pending segments now !\n      const buffers=this.getSourceBufferTypes();\n      if(buffers.length){\n        this.hls.trigger(Events.BUFFER_CREATED, {\n          tracks: this.tracks\n        });\n        buffers.forEach(type=> {\n          operationQueue.executeNext(type);\n        });\n      }else{\n        const error=new Error('could not create source buffer for media codec(s)');\n        this.hls.trigger(Events.ERROR, {\n          type: ErrorTypes.MEDIA_ERROR,\n          details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,\n          fatal: true,\n          error,\n          reason: error.message\n        });\n      }\n    }\n  }\n  createSourceBuffers(tracks){\n    const {\n      sourceBuffer,\n      mediaSource\n    }=this;\n    if(!mediaSource){\n      throw Error('createSourceBuffers called when mediaSource was null');\n    }\n    for (const trackName in tracks){\n      if(!sourceBuffer[trackName]){\n        var _track$levelCodec;\n        const track=tracks[trackName];\n        if(!track){\n          throw Error(`source buffer exists for track ${trackName}, however track does not`);\n        }\n        // use levelCodec as first priority unless it contains multiple comma-separated codec values\n        let codec=((_track$levelCodec=track.levelCodec)==null ? void 0:_track$levelCodec.indexOf(','))===-1 ? track.levelCodec:track.codec;\n        if(codec){\n          if(trackName.slice(0, 5)==='audio'){\n            codec=getCodecCompatibleName(codec, this.appendSource);\n          }\n        }\n        const mimeType=`${track.container};codecs=${codec}`;\n        this.log(`creating sourceBuffer(${mimeType})`);\n        try {\n          const sb=sourceBuffer[trackName]=mediaSource.addSourceBuffer(mimeType);\n          const sbName=trackName;\n          this.addBufferListener(sbName, 'updatestart', this._onSBUpdateStart);\n          this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);\n          this.addBufferListener(sbName, 'error', this._onSBUpdateError);\n          // ManagedSourceBuffer bufferedchange event\n          if(this.appendSource){\n            this.addBufferListener(sbName, 'bufferedchange', (type, event)=> {\n              // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.\n              const removedRanges=event.removedRanges;\n              if(removedRanges!=null&&removedRanges.length){\n                this.hls.trigger(Events.BUFFER_FLUSHED, {\n                  type: trackName\n                });\n              }\n            });\n          }\n          this.tracks[trackName]={\n            buffer: sb,\n            codec: codec,\n            container: track.container,\n            levelCodec: track.levelCodec,\n            metadata: track.metadata,\n            id: track.id\n          };\n        } catch (err){\n          this.error(`error while trying to add sourceBuffer: ${err.message}`);\n          this.hls.trigger(Events.ERROR, {\n            type: ErrorTypes.MEDIA_ERROR,\n            details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,\n            fatal: false,\n            error: err,\n            sourceBufferName: trackName,\n            mimeType: mimeType\n          });\n        }\n      }\n    }\n  }\n  get mediaSrc(){\n    var _this$media, _this$media$querySele;\n    const media=((_this$media=this.media)==null ? void 0:(_this$media$querySele=_this$media.querySelector)==null ? void 0:_this$media$querySele.call(_this$media, 'source'))||this.media;\n    return media==null ? void 0:media.src;\n  }\n  _onSBUpdateStart(type){\n    const {\n      operationQueue\n    }=this;\n    const operation=operationQueue.current(type);\n    operation.onStart();\n  }\n  _onSBUpdateEnd(type){\n    var _this$mediaSource2;\n    if(((_this$mediaSource2=this.mediaSource)==null ? void 0:_this$mediaSource2.readyState)==='closed'){\n      this.resetBuffer(type);\n      return;\n    }\n    const {\n      operationQueue\n    }=this;\n    const operation=operationQueue.current(type);\n    operation.onComplete();\n    operationQueue.shiftAndExecuteNext(type);\n  }\n  _onSBUpdateError(type, event){\n    var _this$mediaSource3;\n    const error=new Error(`${type} SourceBuffer error. MediaSource readyState: ${(_this$mediaSource3=this.mediaSource)==null ? void 0:_this$mediaSource3.readyState}`);\n    this.error(`${error}`, event);\n    // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error\n    // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event\n    this.hls.trigger(Events.ERROR, {\n      type: ErrorTypes.MEDIA_ERROR,\n      details: ErrorDetails.BUFFER_APPENDING_ERROR,\n      sourceBufferName: type,\n      error,\n      fatal: false\n    });\n    // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue\n    const operation=this.operationQueue.current(type);\n    if(operation){\n      operation.onError(error);\n    }\n  }\n\n  // This method must result in an updateend event; if remove is not called, _onSBUpdateEnd must be called manually\n  removeExecutor(type, startOffset, endOffset){\n    const {\n      media,\n      mediaSource,\n      operationQueue,\n      sourceBuffer\n    }=this;\n    const sb=sourceBuffer[type];\n    if(!media||!mediaSource||!sb){\n      this.warn(`Attempting to remove from the ${type} SourceBuffer, but it does not exist`);\n      operationQueue.shiftAndExecuteNext(type);\n      return;\n    }\n    const mediaDuration=isFiniteNumber(media.duration) ? media.duration:Infinity;\n    const msDuration=isFiniteNumber(mediaSource.duration) ? mediaSource.duration:Infinity;\n    const removeStart=Math.max(0, startOffset);\n    const removeEnd=Math.min(endOffset, mediaDuration, msDuration);\n    if(removeEnd > removeStart&&(!sb.ending||sb.ended)){\n      sb.ended=false;\n      this.log(`Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`);\n      sb.remove(removeStart, removeEnd);\n    }else{\n      // Cycle the queue\n      operationQueue.shiftAndExecuteNext(type);\n    }\n  }\n\n  // This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually\n  appendExecutor(data, type){\n    const sb=this.sourceBuffer[type];\n    if(!sb){\n      if(!this.pendingTracks[type]){\n        throw new Error(`Attempting to append to the ${type} SourceBuffer, but it does not exist`);\n      }\n      return;\n    }\n    sb.ended=false;\n    sb.appendBuffer(data);\n  }\n\n  // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises\n  // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue\n  // upon completion, since we already do it here\n  blockBuffers(onUnblocked, buffers=this.getSourceBufferTypes()){\n    if(!buffers.length){\n      this.log('Blocking operation requested, but no SourceBuffers exist');\n      Promise.resolve().then(onUnblocked);\n      return;\n    }\n    const {\n      operationQueue\n    }=this;\n\n    // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);\n    const blockingOperations=buffers.map(type=> operationQueue.appendBlocker(type));\n    Promise.all(blockingOperations).then(()=> {\n      // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);\n      onUnblocked();\n      buffers.forEach(type=> {\n        const sb=this.sourceBuffer[type];\n        // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to\n        // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)\n        // While this is a workaround, it's probably useful to have around\n        if(!(sb!=null&&sb.updating)){\n          operationQueue.shiftAndExecuteNext(type);\n        }\n      });\n    });\n  }\n  getSourceBufferTypes(){\n    return Object.keys(this.sourceBuffer);\n  }\n  addBufferListener(type, event, fn){\n    const buffer=this.sourceBuffer[type];\n    if(!buffer){\n      return;\n    }\n    const listener=fn.bind(this, type);\n    this.listeners[type].push({\n      event,\n      listener\n    });\n    buffer.addEventListener(event, listener);\n  }\n  removeBufferListeners(type){\n    const buffer=this.sourceBuffer[type];\n    if(!buffer){\n      return;\n    }\n    this.listeners[type].forEach(l=> {\n      buffer.removeEventListener(l.event, l.listener);\n    });\n  }\n}\nfunction removeSourceChildren(node){\n  const sourceChildren=node.querySelectorAll('source');\n  [].slice.call(sourceChildren).forEach(source=> {\n    node.removeChild(source);\n  });\n}\nfunction addSource(media, url){\n  const source=self.document.createElement('source');\n  source.type='video/mp4';\n  source.src=url;\n  media.appendChild(source);\n}\n\n\n\n\nconst specialCea608CharsCodes={\n  0x2a: 0xe1,\n  // lowercase a, acute accent\n  0x5c: 0xe9,\n  // lowercase e, acute accent\n  0x5e: 0xed,\n  // lowercase i, acute accent\n  0x5f: 0xf3,\n  // lowercase o, acute accent\n  0x60: 0xfa,\n  // lowercase u, acute accent\n  0x7b: 0xe7,\n  // lowercase c with cedilla\n  0x7c: 0xf7,\n  // division symbol\n  0x7d: 0xd1,\n  // uppercase N tilde\n  0x7e: 0xf1,\n  // lowercase n tilde\n  0x7f: 0x2588,\n  // Full block\n  // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS\n  // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F\n  // THIS MEANS THAT \\x50 MUST BE ADDED TO THE VALUES\n  0x80: 0xae,\n  // Registered symbol (R)\n  0x81: 0xb0,\n  // degree sign\n  0x82: 0xbd,\n  // 1/2 symbol\n  0x83: 0xbf,\n  // Inverted (open) question mark\n  0x84: 0x2122,\n  // Trademark symbol (TM)\n  0x85: 0xa2,\n  // Cents symbol\n  0x86: 0xa3,\n  // Pounds sterling\n  0x87: 0x266a,\n  // Music 8'th note\n  0x88: 0xe0,\n  // lowercase a, grave accent\n  0x89: 0x20,\n  // transparent space (regular)\n  0x8a: 0xe8,\n  // lowercase e, grave accent\n  0x8b: 0xe2,\n  // lowercase a, circumflex accent\n  0x8c: 0xea,\n  // lowercase e, circumflex accent\n  0x8d: 0xee,\n  // lowercase i, circumflex accent\n  0x8e: 0xf4,\n  // lowercase o, circumflex accent\n  0x8f: 0xfb,\n  // lowercase u, circumflex accent\n  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS\n  // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F\n  0x90: 0xc1,\n  // capital letter A with acute\n  0x91: 0xc9,\n  // capital letter E with acute\n  0x92: 0xd3,\n  // capital letter O with acute\n  0x93: 0xda,\n  // capital letter U with acute\n  0x94: 0xdc,\n  // capital letter U with diaresis\n  0x95: 0xfc,\n  // lowercase letter U with diaeresis\n  0x96: 0x2018,\n  // opening single quote\n  0x97: 0xa1,\n  // inverted exclamation mark\n  0x98: 0x2a,\n  // asterisk\n  0x99: 0x2019,\n  // closing single quote\n  0x9a: 0x2501,\n  // box drawings heavy horizontal\n  0x9b: 0xa9,\n  // copyright sign\n  0x9c: 0x2120,\n  // Service mark\n  0x9d: 0x2022,\n  // (round) bullet\n  0x9e: 0x201c,\n  // Left double quotation mark\n  0x9f: 0x201d,\n  // Right double quotation mark\n  0xa0: 0xc0,\n  // uppercase A, grave accent\n  0xa1: 0xc2,\n  // uppercase A, circumflex\n  0xa2: 0xc7,\n  // uppercase C with cedilla\n  0xa3: 0xc8,\n  // uppercase E, grave accent\n  0xa4: 0xca,\n  // uppercase E, circumflex\n  0xa5: 0xcb,\n  // capital letter E with diaresis\n  0xa6: 0xeb,\n  // lowercase letter e with diaresis\n  0xa7: 0xce,\n  // uppercase I, circumflex\n  0xa8: 0xcf,\n  // uppercase I, with diaresis\n  0xa9: 0xef,\n  // lowercase i, with diaresis\n  0xaa: 0xd4,\n  // uppercase O, circumflex\n  0xab: 0xd9,\n  // uppercase U, grave accent\n  0xac: 0xf9,\n  // lowercase u, grave accent\n  0xad: 0xdb,\n  // uppercase U, circumflex\n  0xae: 0xab,\n  // left-pointing double angle quotation mark\n  0xaf: 0xbb,\n  // right-pointing double angle quotation mark\n  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS\n  // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F\n  0xb0: 0xc3,\n  // Uppercase A, tilde\n  0xb1: 0xe3,\n  // Lowercase a, tilde\n  0xb2: 0xcd,\n  // Uppercase I, acute accent\n  0xb3: 0xcc,\n  // Uppercase I, grave accent\n  0xb4: 0xec,\n  // Lowercase i, grave accent\n  0xb5: 0xd2,\n  // Uppercase O, grave accent\n  0xb6: 0xf2,\n  // Lowercase o, grave accent\n  0xb7: 0xd5,\n  // Uppercase O, tilde\n  0xb8: 0xf5,\n  // Lowercase o, tilde\n  0xb9: 0x7b,\n  // Open curly brace\n  0xba: 0x7d,\n  // Closing curly brace\n  0xbb: 0x5c,\n  // Backslash\n  0xbc: 0x5e,\n  // Caret\n  0xbd: 0x5f,\n  // Underscore\n  0xbe: 0x7c,\n  // Pipe (vertical line)\n  0xbf: 0x223c,\n  // Tilde operator\n  0xc0: 0xc4,\n  // Uppercase A, umlaut\n  0xc1: 0xe4,\n  // Lowercase A, umlaut\n  0xc2: 0xd6,\n  // Uppercase O, umlaut\n  0xc3: 0xf6,\n  // Lowercase o, umlaut\n  0xc4: 0xdf,\n  // Esszett (sharp S)\n  0xc5: 0xa5,\n  // Yen symbol\n  0xc6: 0xa4,\n  // Generic currency sign\n  0xc7: 0x2503,\n  // Box drawings heavy vertical\n  0xc8: 0xc5,\n  // Uppercase A, ring\n  0xc9: 0xe5,\n  // Lowercase A, ring\n  0xca: 0xd8,\n  // Uppercase O, stroke\n  0xcb: 0xf8,\n  // Lowercase o, strok\n  0xcc: 0x250f,\n  // Box drawings heavy down and right\n  0xcd: 0x2513,\n  // Box drawings heavy down and left\n  0xce: 0x2517,\n  // Box drawings heavy up and right\n  0xcf: 0x251b // Box drawings heavy up and left\n};\n\n\nconst getCharForByte=byte=> String.fromCharCode(specialCea608CharsCodes[byte]||byte);\nconst NR_ROWS=15;\nconst NR_COLS=100;\n// Tables to look up row from PAC data\nconst rowsLowCh1={\n  0x11: 1,\n  0x12: 3,\n  0x15: 5,\n  0x16: 7,\n  0x17: 9,\n  0x10: 11,\n  0x13: 12,\n  0x14: 14\n};\nconst rowsHighCh1={\n  0x11: 2,\n  0x12: 4,\n  0x15: 6,\n  0x16: 8,\n  0x17: 10,\n  0x13: 13,\n  0x14: 15\n};\nconst rowsLowCh2={\n  0x19: 1,\n  0x1a: 3,\n  0x1d: 5,\n  0x1e: 7,\n  0x1f: 9,\n  0x18: 11,\n  0x1b: 12,\n  0x1c: 14\n};\nconst rowsHighCh2={\n  0x19: 2,\n  0x1a: 4,\n  0x1d: 6,\n  0x1e: 8,\n  0x1f: 10,\n  0x1b: 13,\n  0x1c: 15\n};\nconst backgroundColors=['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent'];\nclass CaptionsLogger {\n  constructor(){\n    this.time=null;\n    this.verboseLevel=0;\n  }\n  log(severity, msg){\n    if(this.verboseLevel >=severity){\n      const m=typeof msg==='function' ? msg():msg;\n      logger.log(`${this.time} [${severity}] ${m}`);\n    }\n  }\n}\nconst numArrayToHexArray=function numArrayToHexArray(numArray){\n  const hexArray=[];\n  for (let j=0; j < numArray.length; j++){\n    hexArray.push(numArray[j].toString(16));\n  }\n  return hexArray;\n};\nclass PenState {\n  constructor(){\n    this.foreground='white';\n    this.underline=false;\n    this.italics=false;\n    this.background='black';\n    this.flash=false;\n  }\n  reset(){\n    this.foreground='white';\n    this.underline=false;\n    this.italics=false;\n    this.background='black';\n    this.flash=false;\n  }\n  setStyles(styles){\n    const attribs=['foreground', 'underline', 'italics', 'background', 'flash'];\n    for (let i=0; i < attribs.length; i++){\n      const style=attribs[i];\n      if(styles.hasOwnProperty(style)){\n        this[style]=styles[style];\n      }\n    }\n  }\n  isDefault(){\n    return this.foreground==='white'&&!this.underline&&!this.italics&&this.background==='black'&&!this.flash;\n  }\n  equals(other){\n    return this.foreground===other.foreground&&this.underline===other.underline&&this.italics===other.italics&&this.background===other.background&&this.flash===other.flash;\n  }\n  copy(newPenState){\n    this.foreground=newPenState.foreground;\n    this.underline=newPenState.underline;\n    this.italics=newPenState.italics;\n    this.background=newPenState.background;\n    this.flash=newPenState.flash;\n  }\n  toString(){\n    return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash;\n  }\n}\n\n\nclass StyledUnicodeChar {\n  constructor(){\n    this.uchar=' ';\n    this.penState=new PenState();\n  }\n  reset(){\n    this.uchar=' ';\n    this.penState.reset();\n  }\n  setChar(uchar, newPenState){\n    this.uchar=uchar;\n    this.penState.copy(newPenState);\n  }\n  setPenState(newPenState){\n    this.penState.copy(newPenState);\n  }\n  equals(other){\n    return this.uchar===other.uchar&&this.penState.equals(other.penState);\n  }\n  copy(newChar){\n    this.uchar=newChar.uchar;\n    this.penState.copy(newChar.penState);\n  }\n  isEmpty(){\n    return this.uchar===' '&&this.penState.isDefault();\n  }\n}\n\n\nclass Row {\n  constructor(logger){\n    this.chars=[];\n    this.pos=0;\n    this.currPenState=new PenState();\n    this.cueStartTime=null;\n    this.logger=void 0;\n    for (let i=0; i < NR_COLS; i++){\n      this.chars.push(new StyledUnicodeChar());\n    }\n    this.logger=logger;\n  }\n  equals(other){\n    for (let i=0; i < NR_COLS; i++){\n      if(!this.chars[i].equals(other.chars[i])){\n        return false;\n      }\n    }\n    return true;\n  }\n  copy(other){\n    for (let i=0; i < NR_COLS; i++){\n      this.chars[i].copy(other.chars[i]);\n    }\n  }\n  isEmpty(){\n    let empty=true;\n    for (let i=0; i < NR_COLS; i++){\n      if(!this.chars[i].isEmpty()){\n        empty=false;\n        break;\n      }\n    }\n    return empty;\n  }\n\n  \n  setCursor(absPos){\n    if(this.pos!==absPos){\n      this.pos=absPos;\n    }\n    if(this.pos < 0){\n      this.logger.log(3, 'Negative cursor position ' + this.pos);\n      this.pos=0;\n    }else if(this.pos > NR_COLS){\n      this.logger.log(3, 'Too large cursor position ' + this.pos);\n      this.pos=NR_COLS;\n    }\n  }\n\n  \n  moveCursor(relPos){\n    const newPos=this.pos + relPos;\n    if(relPos > 1){\n      for (let i=this.pos + 1; i < newPos + 1; i++){\n        this.chars[i].setPenState(this.currPenState);\n      }\n    }\n    this.setCursor(newPos);\n  }\n\n  \n  backSpace(){\n    this.moveCursor(-1);\n    this.chars[this.pos].setChar(' ', this.currPenState);\n  }\n  insertChar(byte){\n    if(byte >=0x90){\n      // Extended char\n      this.backSpace();\n    }\n    const char=getCharForByte(byte);\n    if(this.pos >=NR_COLS){\n      this.logger.log(0, ()=> 'Cannot insert ' + byte.toString(16) + ' (' + char + ') at position ' + this.pos + '. Skipping it!');\n      return;\n    }\n    this.chars[this.pos].setChar(char, this.currPenState);\n    this.moveCursor(1);\n  }\n  clearFromPos(startPos){\n    let i;\n    for (i=startPos; i < NR_COLS; i++){\n      this.chars[i].reset();\n    }\n  }\n  clear(){\n    this.clearFromPos(0);\n    this.pos=0;\n    this.currPenState.reset();\n  }\n  clearToEndOfRow(){\n    this.clearFromPos(this.pos);\n  }\n  getTextString(){\n    const chars=[];\n    let empty=true;\n    for (let i=0; i < NR_COLS; i++){\n      const char=this.chars[i].uchar;\n      if(char!==' '){\n        empty=false;\n      }\n      chars.push(char);\n    }\n    if(empty){\n      return '';\n    }else{\n      return chars.join('');\n    }\n  }\n  setPenStyles(styles){\n    this.currPenState.setStyles(styles);\n    const currChar=this.chars[this.pos];\n    currChar.setPenState(this.currPenState);\n  }\n}\n\n\nclass CaptionScreen {\n  constructor(logger){\n    this.rows=[];\n    this.currRow=NR_ROWS - 1;\n    this.nrRollUpRows=null;\n    this.lastOutputScreen=null;\n    this.logger=void 0;\n    for (let i=0; i < NR_ROWS; i++){\n      this.rows.push(new Row(logger));\n    }\n    this.logger=logger;\n  }\n  reset(){\n    for (let i=0; i < NR_ROWS; i++){\n      this.rows[i].clear();\n    }\n    this.currRow=NR_ROWS - 1;\n  }\n  equals(other){\n    let equal=true;\n    for (let i=0; i < NR_ROWS; i++){\n      if(!this.rows[i].equals(other.rows[i])){\n        equal=false;\n        break;\n      }\n    }\n    return equal;\n  }\n  copy(other){\n    for (let i=0; i < NR_ROWS; i++){\n      this.rows[i].copy(other.rows[i]);\n    }\n  }\n  isEmpty(){\n    let empty=true;\n    for (let i=0; i < NR_ROWS; i++){\n      if(!this.rows[i].isEmpty()){\n        empty=false;\n        break;\n      }\n    }\n    return empty;\n  }\n  backSpace(){\n    const row=this.rows[this.currRow];\n    row.backSpace();\n  }\n  clearToEndOfRow(){\n    const row=this.rows[this.currRow];\n    row.clearToEndOfRow();\n  }\n\n  \n  insertChar(char){\n    const row=this.rows[this.currRow];\n    row.insertChar(char);\n  }\n  setPen(styles){\n    const row=this.rows[this.currRow];\n    row.setPenStyles(styles);\n  }\n  moveCursor(relPos){\n    const row=this.rows[this.currRow];\n    row.moveCursor(relPos);\n  }\n  setCursor(absPos){\n    this.logger.log(2, 'setCursor: ' + absPos);\n    const row=this.rows[this.currRow];\n    row.setCursor(absPos);\n  }\n  setPAC(pacData){\n    this.logger.log(2, ()=> 'pacData=' + JSON.stringify(pacData));\n    let newRow=pacData.row - 1;\n    if(this.nrRollUpRows&&newRow < this.nrRollUpRows - 1){\n      newRow=this.nrRollUpRows - 1;\n    }\n\n    // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows\n    if(this.nrRollUpRows&&this.currRow!==newRow){\n      // clear all rows first\n      for (let i=0; i < NR_ROWS; i++){\n        this.rows[i].clear();\n      }\n\n      // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location\n      // topRowIndex - the start of rows to copy (inclusive index)\n      const topRowIndex=this.currRow + 1 - this.nrRollUpRows;\n      // We only copy if the last position was already shown.\n      // We use the cueStartTime value to check this.\n      const lastOutputScreen=this.lastOutputScreen;\n      if(lastOutputScreen){\n        const prevLineTime=lastOutputScreen.rows[topRowIndex].cueStartTime;\n        const time=this.logger.time;\n        if(prevLineTime!==null&&time!==null&&prevLineTime < time){\n          for (let i=0; i < this.nrRollUpRows; i++){\n            this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]);\n          }\n        }\n      }\n    }\n    this.currRow=newRow;\n    const row=this.rows[this.currRow];\n    if(pacData.indent!==null){\n      const indent=pacData.indent;\n      const prevPos=Math.max(indent - 1, 0);\n      row.setCursor(pacData.indent);\n      pacData.color=row.chars[prevPos].penState.foreground;\n    }\n    const styles={\n      foreground: pacData.color,\n      underline: pacData.underline,\n      italics: pacData.italics,\n      background: 'black',\n      flash: false\n    };\n    this.setPen(styles);\n  }\n\n  \n  setBkgData(bkgData){\n    this.logger.log(2, ()=> 'bkgData=' + JSON.stringify(bkgData));\n    this.backSpace();\n    this.setPen(bkgData);\n    this.insertChar(0x20); // Space\n  }\n  setRollUpRows(nrRows){\n    this.nrRollUpRows=nrRows;\n  }\n  rollUp(){\n    if(this.nrRollUpRows===null){\n      this.logger.log(3, 'roll_up but nrRollUpRows not set yet');\n      return; // Not properly setup\n    }\n    this.logger.log(1, ()=> this.getDisplayText());\n    const topRowIndex=this.currRow + 1 - this.nrRollUpRows;\n    const topRow=this.rows.splice(topRowIndex, 1)[0];\n    topRow.clear();\n    this.rows.splice(this.currRow, 0, topRow);\n    this.logger.log(2, 'Rolling up');\n    // this.logger.log(VerboseLevel.TEXT, this.get_display_text())\n  }\n\n  \n  getDisplayText(asOneRow){\n    asOneRow=asOneRow||false;\n    const displayText=[];\n    let text='';\n    let rowNr=-1;\n    for (let i=0; i < NR_ROWS; i++){\n      const rowText=this.rows[i].getTextString();\n      if(rowText){\n        rowNr=i + 1;\n        if(asOneRow){\n          displayText.push('Row ' + rowNr + \": '\" + rowText + \"'\");\n        }else{\n          displayText.push(rowText.trim());\n        }\n      }\n    }\n    if(displayText.length > 0){\n      if(asOneRow){\n        text='[' + displayText.join(' | ') + ']';\n      }else{\n        text=displayText.join('\\n');\n      }\n    }\n    return text;\n  }\n  getTextAndFormat(){\n    return this.rows;\n  }\n}\n\n// var modes=['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT'];\n\nclass Cea608Channel {\n  constructor(channelNumber, outputFilter, logger){\n    this.chNr=void 0;\n    this.outputFilter=void 0;\n    this.mode=void 0;\n    this.verbose=void 0;\n    this.displayedMemory=void 0;\n    this.nonDisplayedMemory=void 0;\n    this.lastOutputScreen=void 0;\n    this.currRollUpRow=void 0;\n    this.writeScreen=void 0;\n    this.cueStartTime=void 0;\n    this.logger=void 0;\n    this.chNr=channelNumber;\n    this.outputFilter=outputFilter;\n    this.mode=null;\n    this.verbose=0;\n    this.displayedMemory=new CaptionScreen(logger);\n    this.nonDisplayedMemory=new CaptionScreen(logger);\n    this.lastOutputScreen=new CaptionScreen(logger);\n    this.currRollUpRow=this.displayedMemory.rows[NR_ROWS - 1];\n    this.writeScreen=this.displayedMemory;\n    this.mode=null;\n    this.cueStartTime=null; // Keeps track of where a cue started.\n    this.logger=logger;\n  }\n  reset(){\n    this.mode=null;\n    this.displayedMemory.reset();\n    this.nonDisplayedMemory.reset();\n    this.lastOutputScreen.reset();\n    this.outputFilter.reset();\n    this.currRollUpRow=this.displayedMemory.rows[NR_ROWS - 1];\n    this.writeScreen=this.displayedMemory;\n    this.mode=null;\n    this.cueStartTime=null;\n  }\n  getHandler(){\n    return this.outputFilter;\n  }\n  setHandler(newHandler){\n    this.outputFilter=newHandler;\n  }\n  setPAC(pacData){\n    this.writeScreen.setPAC(pacData);\n  }\n  setBkgData(bkgData){\n    this.writeScreen.setBkgData(bkgData);\n  }\n  setMode(newMode){\n    if(newMode===this.mode){\n      return;\n    }\n    this.mode=newMode;\n    this.logger.log(2, ()=> 'MODE=' + newMode);\n    if(this.mode==='MODE_POP-ON'){\n      this.writeScreen=this.nonDisplayedMemory;\n    }else{\n      this.writeScreen=this.displayedMemory;\n      this.writeScreen.reset();\n    }\n    if(this.mode!=='MODE_ROLL-UP'){\n      this.displayedMemory.nrRollUpRows=null;\n      this.nonDisplayedMemory.nrRollUpRows=null;\n    }\n    this.mode=newMode;\n  }\n  insertChars(chars){\n    for (let i=0; i < chars.length; i++){\n      this.writeScreen.insertChar(chars[i]);\n    }\n    const screen=this.writeScreen===this.displayedMemory ? 'DISP':'NON_DISP';\n    this.logger.log(2, ()=> screen + ': ' + this.writeScreen.getDisplayText(true));\n    if(this.mode==='MODE_PAINT-ON'||this.mode==='MODE_ROLL-UP'){\n      this.logger.log(1, ()=> 'DISPLAYED: ' + this.displayedMemory.getDisplayText(true));\n      this.outputDataUpdate();\n    }\n  }\n  ccRCL(){\n    // Resume Caption Loading (switch mode to Pop On)\n    this.logger.log(2, 'RCL - Resume Caption Loading');\n    this.setMode('MODE_POP-ON');\n  }\n  ccBS(){\n    // BackSpace\n    this.logger.log(2, 'BS - BackSpace');\n    if(this.mode==='MODE_TEXT'){\n      return;\n    }\n    this.writeScreen.backSpace();\n    if(this.writeScreen===this.displayedMemory){\n      this.outputDataUpdate();\n    }\n  }\n  ccAOF(){\n    // Reserved (formerly Alarm Off)\n  }\n  ccAON(){\n    // Reserved (formerly Alarm On)\n  }\n  ccDER(){\n    // Delete to End of Row\n    this.logger.log(2, 'DER- Delete to End of Row');\n    this.writeScreen.clearToEndOfRow();\n    this.outputDataUpdate();\n  }\n  ccRU(nrRows){\n    // Roll-Up Captions-2,3,or 4 Rows\n    this.logger.log(2, 'RU(' + nrRows + ') - Roll Up');\n    this.writeScreen=this.displayedMemory;\n    this.setMode('MODE_ROLL-UP');\n    this.writeScreen.setRollUpRows(nrRows);\n  }\n  ccFON(){\n    // Flash On\n    this.logger.log(2, 'FON - Flash On');\n    this.writeScreen.setPen({\n      flash: true\n    });\n  }\n  ccRDC(){\n    // Resume Direct Captioning (switch mode to PaintOn)\n    this.logger.log(2, 'RDC - Resume Direct Captioning');\n    this.setMode('MODE_PAINT-ON');\n  }\n  ccTR(){\n    // Text Restart in text mode (not supported, however)\n    this.logger.log(2, 'TR');\n    this.setMode('MODE_TEXT');\n  }\n  ccRTD(){\n    // Resume Text Display in Text mode (not supported, however)\n    this.logger.log(2, 'RTD');\n    this.setMode('MODE_TEXT');\n  }\n  ccEDM(){\n    // Erase Displayed Memory\n    this.logger.log(2, 'EDM - Erase Displayed Memory');\n    this.displayedMemory.reset();\n    this.outputDataUpdate(true);\n  }\n  ccCR(){\n    // Carriage Return\n    this.logger.log(2, 'CR - Carriage Return');\n    this.writeScreen.rollUp();\n    this.outputDataUpdate(true);\n  }\n  ccENM(){\n    // Erase Non-Displayed Memory\n    this.logger.log(2, 'ENM - Erase Non-displayed Memory');\n    this.nonDisplayedMemory.reset();\n  }\n  ccEOC(){\n    // End of Caption (Flip Memories)\n    this.logger.log(2, 'EOC - End Of Caption');\n    if(this.mode==='MODE_POP-ON'){\n      const tmp=this.displayedMemory;\n      this.displayedMemory=this.nonDisplayedMemory;\n      this.nonDisplayedMemory=tmp;\n      this.writeScreen=this.nonDisplayedMemory;\n      this.logger.log(1, ()=> 'DISP: ' + this.displayedMemory.getDisplayText());\n    }\n    this.outputDataUpdate(true);\n  }\n  ccTO(nrCols){\n    // Tab Offset 1,2, or 3 columns\n    this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset');\n    this.writeScreen.moveCursor(nrCols);\n  }\n  ccMIDROW(secondByte){\n    // Parse MIDROW command\n    const styles={\n      flash: false\n    };\n    styles.underline=secondByte % 2===1;\n    styles.italics=secondByte >=0x2e;\n    if(!styles.italics){\n      const colorIndex=Math.floor(secondByte / 2) - 0x10;\n      const colors=['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta'];\n      styles.foreground=colors[colorIndex];\n    }else{\n      styles.foreground='white';\n    }\n    this.logger.log(2, 'MIDROW: ' + JSON.stringify(styles));\n    this.writeScreen.setPen(styles);\n  }\n  outputDataUpdate(dispatch=false){\n    const time=this.logger.time;\n    if(time===null){\n      return;\n    }\n    if(this.outputFilter){\n      if(this.cueStartTime===null&&!this.displayedMemory.isEmpty()){\n        // Start of a new cue\n        this.cueStartTime=time;\n      }else{\n        if(!this.displayedMemory.equals(this.lastOutputScreen)){\n          this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen);\n          if(dispatch&&this.outputFilter.dispatchCue){\n            this.outputFilter.dispatchCue();\n          }\n          this.cueStartTime=this.displayedMemory.isEmpty() ? null:time;\n        }\n      }\n      this.lastOutputScreen.copy(this.displayedMemory);\n    }\n  }\n  cueSplitAtTime(t){\n    if(this.outputFilter){\n      if(!this.displayedMemory.isEmpty()){\n        if(this.outputFilter.newCue){\n          this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory);\n        }\n        this.cueStartTime=t;\n      }\n    }\n  }\n}\n\n// Will be 1 or 2 when parsing captions\n\nclass Cea608Parser {\n  constructor(field, out1, out2){\n    this.channels=void 0;\n    this.currentChannel=0;\n    this.cmdHistory=createCmdHistory();\n    this.logger=void 0;\n    const logger=this.logger=new CaptionsLogger();\n    this.channels=[null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)];\n  }\n  getHandler(channel){\n    return this.channels[channel].getHandler();\n  }\n  setHandler(channel, newHandler){\n    this.channels[channel].setHandler(newHandler);\n  }\n\n  \n  addData(time, byteList){\n    this.logger.time=time;\n    for (let i=0; i < byteList.length; i +=2){\n      const a=byteList[i] & 0x7f;\n      const b=byteList[i + 1] & 0x7f;\n      let cmdFound=false;\n      let charsFound=null;\n      if(a===0&&b===0){\n        continue;\n      }else{\n        this.logger.log(3, ()=> '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')');\n      }\n      const cmdHistory=this.cmdHistory;\n      const isControlCode=a >=0x10&&a <=0x1f;\n      if(isControlCode){\n        // Skip redundant control codes\n        if(hasCmdRepeated(a, b, cmdHistory)){\n          setLastCmd(null, null, cmdHistory);\n          this.logger.log(3, ()=> 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped');\n          continue;\n        }\n        setLastCmd(a, b, this.cmdHistory);\n        cmdFound=this.parseCmd(a, b);\n        if(!cmdFound){\n          cmdFound=this.parseMidrow(a, b);\n        }\n        if(!cmdFound){\n          cmdFound=this.parsePAC(a, b);\n        }\n        if(!cmdFound){\n          cmdFound=this.parseBackgroundAttributes(a, b);\n        }\n      }else{\n        setLastCmd(null, null, cmdHistory);\n      }\n      if(!cmdFound){\n        charsFound=this.parseChars(a, b);\n        if(charsFound){\n          const currChNr=this.currentChannel;\n          if(currChNr&&currChNr > 0){\n            const channel=this.channels[currChNr];\n            channel.insertChars(charsFound);\n          }else{\n            this.logger.log(2, 'No channel found yet. TEXT-MODE?');\n          }\n        }\n      }\n      if(!cmdFound&&!charsFound){\n        this.logger.log(2, ()=> \"Couldn't parse cleaned data \" + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]]));\n      }\n    }\n  }\n\n  \n  parseCmd(a, b){\n    const cond1=(a===0x14||a===0x1c||a===0x15||a===0x1d)&&b >=0x20&&b <=0x2f;\n    const cond2=(a===0x17||a===0x1f)&&b >=0x21&&b <=0x23;\n    if(!(cond1||cond2)){\n      return false;\n    }\n    const chNr=a===0x14||a===0x15||a===0x17 ? 1:2;\n    const channel=this.channels[chNr];\n    if(a===0x14||a===0x15||a===0x1c||a===0x1d){\n      if(b===0x20){\n        channel.ccRCL();\n      }else if(b===0x21){\n        channel.ccBS();\n      }else if(b===0x22){\n        channel.ccAOF();\n      }else if(b===0x23){\n        channel.ccAON();\n      }else if(b===0x24){\n        channel.ccDER();\n      }else if(b===0x25){\n        channel.ccRU(2);\n      }else if(b===0x26){\n        channel.ccRU(3);\n      }else if(b===0x27){\n        channel.ccRU(4);\n      }else if(b===0x28){\n        channel.ccFON();\n      }else if(b===0x29){\n        channel.ccRDC();\n      }else if(b===0x2a){\n        channel.ccTR();\n      }else if(b===0x2b){\n        channel.ccRTD();\n      }else if(b===0x2c){\n        channel.ccEDM();\n      }else if(b===0x2d){\n        channel.ccCR();\n      }else if(b===0x2e){\n        channel.ccENM();\n      }else if(b===0x2f){\n        channel.ccEOC();\n      }\n    }else{\n      // a==0x17||a==0x1F\n      channel.ccTO(b - 0x20);\n    }\n    this.currentChannel=chNr;\n    return true;\n  }\n\n  \n  parseMidrow(a, b){\n    let chNr=0;\n    if((a===0x11||a===0x19)&&b >=0x20&&b <=0x2f){\n      if(a===0x11){\n        chNr=1;\n      }else{\n        chNr=2;\n      }\n      if(chNr!==this.currentChannel){\n        this.logger.log(0, 'Mismatch channel in midrow parsing');\n        return false;\n      }\n      const channel=this.channels[chNr];\n      if(!channel){\n        return false;\n      }\n      channel.ccMIDROW(b);\n      this.logger.log(3, ()=> 'MIDROW (' + numArrayToHexArray([a, b]) + ')');\n      return true;\n    }\n    return false;\n  }\n\n  \n  parsePAC(a, b){\n    let row;\n    const case1=(a >=0x11&&a <=0x17||a >=0x19&&a <=0x1f)&&b >=0x40&&b <=0x7f;\n    const case2=(a===0x10||a===0x18)&&b >=0x40&&b <=0x5f;\n    if(!(case1||case2)){\n      return false;\n    }\n    const chNr=a <=0x17 ? 1:2;\n    if(b >=0x40&&b <=0x5f){\n      row=chNr===1 ? rowsLowCh1[a]:rowsLowCh2[a];\n    }else{\n      // 0x60 <=b <=0x7F\n      row=chNr===1 ? rowsHighCh1[a]:rowsHighCh2[a];\n    }\n    const channel=this.channels[chNr];\n    if(!channel){\n      return false;\n    }\n    channel.setPAC(this.interpretPAC(row, b));\n    this.currentChannel=chNr;\n    return true;\n  }\n\n  \n  interpretPAC(row, byte){\n    let pacIndex;\n    const pacData={\n      color: null,\n      italics: false,\n      indent: null,\n      underline: false,\n      row: row\n    };\n    if(byte > 0x5f){\n      pacIndex=byte - 0x60;\n    }else{\n      pacIndex=byte - 0x40;\n    }\n    pacData.underline=(pacIndex & 1)===1;\n    if(pacIndex <=0xd){\n      pacData.color=['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)];\n    }else if(pacIndex <=0xf){\n      pacData.italics=true;\n      pacData.color='white';\n    }else{\n      pacData.indent=Math.floor((pacIndex - 0x10) / 2) * 4;\n    }\n    return pacData; // Note that row has zero offset. The spec uses 1.\n  }\n\n  \n  parseChars(a, b){\n    let channelNr;\n    let charCodes=null;\n    let charCode1=null;\n    if(a >=0x19){\n      channelNr=2;\n      charCode1=a - 8;\n    }else{\n      channelNr=1;\n      charCode1=a;\n    }\n    if(charCode1 >=0x11&&charCode1 <=0x13){\n      // Special character\n      let oneCode;\n      if(charCode1===0x11){\n        oneCode=b + 0x50;\n      }else if(charCode1===0x12){\n        oneCode=b + 0x70;\n      }else{\n        oneCode=b + 0x90;\n      }\n      this.logger.log(2, ()=> \"Special char '\" + getCharForByte(oneCode) + \"' in channel \" + channelNr);\n      charCodes=[oneCode];\n    }else if(a >=0x20&&a <=0x7f){\n      charCodes=b===0 ? [a]:[a, b];\n    }\n    if(charCodes){\n      this.logger.log(3, ()=> 'Char codes=' + numArrayToHexArray(charCodes).join(','));\n    }\n    return charCodes;\n  }\n\n  \n  parseBackgroundAttributes(a, b){\n    const case1=(a===0x10||a===0x18)&&b >=0x20&&b <=0x2f;\n    const case2=(a===0x17||a===0x1f)&&b >=0x2d&&b <=0x2f;\n    if(!(case1||case2)){\n      return false;\n    }\n    let index;\n    const bkgData={};\n    if(a===0x10||a===0x18){\n      index=Math.floor((b - 0x20) / 2);\n      bkgData.background=backgroundColors[index];\n      if(b % 2===1){\n        bkgData.background=bkgData.background + '_semi';\n      }\n    }else if(b===0x2d){\n      bkgData.background='transparent';\n    }else{\n      bkgData.foreground='black';\n      if(b===0x2f){\n        bkgData.underline=true;\n      }\n    }\n    const chNr=a <=0x17 ? 1:2;\n    const channel=this.channels[chNr];\n    channel.setBkgData(bkgData);\n    return true;\n  }\n\n  \n  reset(){\n    for (let i=0; i < Object.keys(this.channels).length; i++){\n      const channel=this.channels[i];\n      if(channel){\n        channel.reset();\n      }\n    }\n    setLastCmd(null, null, this.cmdHistory);\n  }\n\n  \n  cueSplitAtTime(t){\n    for (let i=0; i < this.channels.length; i++){\n      const channel=this.channels[i];\n      if(channel){\n        channel.cueSplitAtTime(t);\n      }\n    }\n  }\n}\nfunction setLastCmd(a, b, cmdHistory){\n  cmdHistory.a=a;\n  cmdHistory.b=b;\n}\nfunction hasCmdRepeated(a, b, cmdHistory){\n  return cmdHistory.a===a&&cmdHistory.b===b;\n}\nfunction createCmdHistory(){\n  return {\n    a: null,\n    b: null\n  };\n}\n\nclass OutputFilter {\n  constructor(timelineController, trackName){\n    this.timelineController=void 0;\n    this.cueRanges=[];\n    this.trackName=void 0;\n    this.startTime=null;\n    this.endTime=null;\n    this.screen=null;\n    this.timelineController=timelineController;\n    this.trackName=trackName;\n  }\n  dispatchCue(){\n    if(this.startTime===null){\n      return;\n    }\n    this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges);\n    this.startTime=null;\n  }\n  newCue(startTime, endTime, screen){\n    if(this.startTime===null||this.startTime > startTime){\n      this.startTime=startTime;\n    }\n    this.endTime=endTime;\n    this.screen=screen;\n    this.timelineController.createCaptionsTrack(this.trackName);\n  }\n  reset(){\n    this.cueRanges=[];\n    this.startTime=null;\n  }\n}\n\n\n\nvar VTTCue=(function (){\n  if(optionalSelf!=null&&optionalSelf.VTTCue){\n    return self.VTTCue;\n  }\n  const AllowedDirections=['', 'lr', 'rl'];\n  const AllowedAlignments=['start', 'middle', 'end', 'left', 'right'];\n  function isAllowedValue(allowed, value){\n    if(typeof value!=='string'){\n      return false;\n    }\n    // necessary for assuring the generic conforms to the Array interface\n    if(!Array.isArray(allowed)){\n      return false;\n    }\n    // reset the type so that the next narrowing works well\n    const lcValue=value.toLowerCase();\n    // use the allow list to narrow the type to a specific subset of strings\n    if(~allowed.indexOf(lcValue)){\n      return lcValue;\n    }\n    return false;\n  }\n  function findDirectionSetting(value){\n    return isAllowedValue(AllowedDirections, value);\n  }\n  function findAlignSetting(value){\n    return isAllowedValue(AllowedAlignments, value);\n  }\n  function extend(obj, ...rest){\n    let i=1;\n    for (; i < arguments.length; i++){\n      const cobj=arguments[i];\n      for (const p in cobj){\n        obj[p]=cobj[p];\n      }\n    }\n    return obj;\n  }\n  function VTTCue(startTime, endTime, text){\n    const cue=this;\n    const baseObj={\n      enumerable: true\n    };\n    \n\n    // Lets us know when the VTTCue's data has changed in such a way that we need\n    // to recompute its display state. This lets us compute its display state\n    // lazily.\n    cue.hasBeenReset=false;\n\n    \n\n    let _id='';\n    let _pauseOnExit=false;\n    let _startTime=startTime;\n    let _endTime=endTime;\n    let _text=text;\n    let _region=null;\n    let _vertical='';\n    let _snapToLines=true;\n    let _line='auto';\n    let _lineAlign='start';\n    let _position=50;\n    let _positionAlign='middle';\n    let _size=50;\n    let _align='middle';\n    Object.defineProperty(cue, 'id', extend({}, baseObj, {\n      get: function (){\n        return _id;\n      },\n      set: function (value){\n        _id='' + value;\n      }\n    }));\n    Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, {\n      get: function (){\n        return _pauseOnExit;\n      },\n      set: function (value){\n        _pauseOnExit = !!value;\n      }\n    }));\n    Object.defineProperty(cue, 'startTime', extend({}, baseObj, {\n      get: function (){\n        return _startTime;\n      },\n      set: function (value){\n        if(typeof value!=='number'){\n          throw new TypeError('Start time must be set to a number.');\n        }\n        _startTime=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'endTime', extend({}, baseObj, {\n      get: function (){\n        return _endTime;\n      },\n      set: function (value){\n        if(typeof value!=='number'){\n          throw new TypeError('End time must be set to a number.');\n        }\n        _endTime=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'text', extend({}, baseObj, {\n      get: function (){\n        return _text;\n      },\n      set: function (value){\n        _text='' + value;\n        this.hasBeenReset=true;\n      }\n    }));\n\n    // todo: implement VTTRegion polyfill?\n    Object.defineProperty(cue, 'region', extend({}, baseObj, {\n      get: function (){\n        return _region;\n      },\n      set: function (value){\n        _region=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'vertical', extend({}, baseObj, {\n      get: function (){\n        return _vertical;\n      },\n      set: function (value){\n        const setting=findDirectionSetting(value);\n        // Have to check for false because the setting an be an empty string.\n        if(setting===false){\n          throw new SyntaxError('An invalid or illegal string was specified.');\n        }\n        _vertical=setting;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, {\n      get: function (){\n        return _snapToLines;\n      },\n      set: function (value){\n        _snapToLines = !!value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'line', extend({}, baseObj, {\n      get: function (){\n        return _line;\n      },\n      set: function (value){\n        if(typeof value!=='number'&&value!=='auto'){\n          throw new SyntaxError('An invalid number or illegal string was specified.');\n        }\n        _line=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, {\n      get: function (){\n        return _lineAlign;\n      },\n      set: function (value){\n        const setting=findAlignSetting(value);\n        if(!setting){\n          throw new SyntaxError('An invalid or illegal string was specified.');\n        }\n        _lineAlign=setting;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'position', extend({}, baseObj, {\n      get: function (){\n        return _position;\n      },\n      set: function (value){\n        if(value < 0||value > 100){\n          throw new Error('Position must be between 0 and 100.');\n        }\n        _position=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, {\n      get: function (){\n        return _positionAlign;\n      },\n      set: function (value){\n        const setting=findAlignSetting(value);\n        if(!setting){\n          throw new SyntaxError('An invalid or illegal string was specified.');\n        }\n        _positionAlign=setting;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'size', extend({}, baseObj, {\n      get: function (){\n        return _size;\n      },\n      set: function (value){\n        if(value < 0||value > 100){\n          throw new Error('Size must be between 0 and 100.');\n        }\n        _size=value;\n        this.hasBeenReset=true;\n      }\n    }));\n    Object.defineProperty(cue, 'align', extend({}, baseObj, {\n      get: function (){\n        return _align;\n      },\n      set: function (value){\n        const setting=findAlignSetting(value);\n        if(!setting){\n          throw new SyntaxError('An invalid or illegal string was specified.');\n        }\n        _align=setting;\n        this.hasBeenReset=true;\n      }\n    }));\n\n    \n\n    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state\n    cue.displayState=undefined;\n  }\n\n  \n\n  VTTCue.prototype.getCueAsHTML=function (){\n    // Assume WebVTT.convertCueToDOMTree is on the global.\n    const WebVTT=self.WebVTT;\n    return WebVTT.convertCueToDOMTree(self, this.text);\n  };\n  // this is a polyfill hack\n  return VTTCue;\n})();\n\n\n\nclass StringDecoder {\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  decode(data, options){\n    if(!data){\n      return '';\n    }\n    if(typeof data!=='string'){\n      throw new Error('Error - expected string data.');\n    }\n    return decodeURIComponent(encodeURIComponent(data));\n  }\n}\n\n// Try to parse input as a time stamp.\nfunction parseTimeStamp(input){\n  function computeSeconds(h, m, s, f){\n    return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f||0);\n  }\n  const m=input.match(/^(?:(\\d+):)?(\\d{2}):(\\d{2})(\\.\\d+)?/);\n  if(!m){\n    return null;\n  }\n  if(parseFloat(m[2]) > 59){\n    // Timestamp takes the form of [hours]:[minutes].[milliseconds]\n    // First position is hours as it's over 59.\n    return computeSeconds(m[2], m[3], 0, m[4]);\n  }\n  // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds]\n  return computeSeconds(m[1], m[2], m[3], m[4]);\n}\n\n// A settings object holds key/value pairs and will ignore anything but the first\n// assignment to a specific key.\nclass Settings {\n  constructor(){\n    this.values=Object.create(null);\n  }\n  // Only accept the first assignment to any key.\n  set(k, v){\n    if(!this.get(k)&&v!==''){\n      this.values[k]=v;\n    }\n  }\n  // Return the value for a key, or a default value.\n  // If 'defaultKey' is passed then 'dflt' is assumed to be an object with\n  // a number of possible default values as properties where 'defaultKey' is\n  // the key of the property that will be chosen; otherwise it's assumed to be\n  // a single value.\n  get(k, dflt, defaultKey){\n    if(defaultKey){\n      return this.has(k) ? this.values[k]:dflt[defaultKey];\n    }\n    return this.has(k) ? this.values[k]:dflt;\n  }\n  // Check whether we have a value for a key.\n  has(k){\n    return k in this.values;\n  }\n  // Accept a setting if its one of the given alternatives.\n  alt(k, v, a){\n    for (let n=0; n < a.length; ++n){\n      if(v===a[n]){\n        this.set(k, v);\n        break;\n      }\n    }\n  }\n  // Accept a setting if its a valid (signed) integer.\n  integer(k, v){\n    if(/^-?\\d+$/.test(v)){\n      // integer\n      this.set(k, parseInt(v, 10));\n    }\n  }\n  // Accept a setting if its a valid percentage.\n  percent(k, v){\n    if(/^([\\d]{1,3})(\\.[\\d]*)?%$/.test(v)){\n      const percent=parseFloat(v);\n      if(percent >=0&&percent <=100){\n        this.set(k, percent);\n        return true;\n      }\n    }\n    return false;\n  }\n}\n\n// Helper function to parse input into groups separated by 'groupDelim', and\n// interpret each group as a key/value pair separated by 'keyValueDelim'.\nfunction parseOptions(input, callback, keyValueDelim, groupDelim){\n  const groups=groupDelim ? input.split(groupDelim):[input];\n  for (const i in groups){\n    if(typeof groups[i]!=='string'){\n      continue;\n    }\n    const kv=groups[i].split(keyValueDelim);\n    if(kv.length!==2){\n      continue;\n    }\n    const k=kv[0];\n    const v=kv[1];\n    callback(k, v);\n  }\n}\nconst defaults=new VTTCue(0, 0, '');\n// 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244\n//  Safari doesn't yet support this change, but FF and Chrome do.\nconst center=defaults.align==='middle' ? 'middle':'center';\nfunction parseCue(input, cue, regionList){\n  // Remember the original input if we need to throw an error.\n  const oInput=input;\n  // 4.1 WebVTT timestamp\n  function consumeTimeStamp(){\n    const ts=parseTimeStamp(input);\n    if(ts===null){\n      throw new Error('Malformed timestamp: ' + oInput);\n    }\n\n    // Remove time stamp from input.\n    input=input.replace(/^[^\\sa-zA-Z-]+/, '');\n    return ts;\n  }\n\n  // 4.4.2 WebVTT cue settings\n  function consumeCueSettings(input, cue){\n    const settings=new Settings();\n    parseOptions(input, function (k, v){\n      let vals;\n      switch (k){\n        case 'region':\n          // Find the last region we parsed with the same region id.\n          for (let i=regionList.length - 1; i >=0; i--){\n            if(regionList[i].id===v){\n              settings.set(k, regionList[i].region);\n              break;\n            }\n          }\n          break;\n        case 'vertical':\n          settings.alt(k, v, ['rl', 'lr']);\n          break;\n        case 'line':\n          vals=v.split(',');\n          settings.integer(k, vals[0]);\n          if(settings.percent(k, vals[0])){\n            settings.set('snapToLines', false);\n          }\n          settings.alt(k, vals[0], ['auto']);\n          if(vals.length===2){\n            settings.alt('lineAlign', vals[1], ['start', center, 'end']);\n          }\n          break;\n        case 'position':\n          vals=v.split(',');\n          settings.percent(k, vals[0]);\n          if(vals.length===2){\n            settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']);\n          }\n          break;\n        case 'size':\n          settings.percent(k, v);\n          break;\n        case 'align':\n          settings.alt(k, v, ['start', center, 'end', 'left', 'right']);\n          break;\n      }\n    }, /:/, /\\s/);\n\n    // Apply default values for any missing fields.\n    cue.region=settings.get('region', null);\n    cue.vertical=settings.get('vertical', '');\n    let line=settings.get('line', 'auto');\n    if(line==='auto'&&defaults.line===-1){\n      // set numeric line number for Safari\n      line=-1;\n    }\n    cue.line=line;\n    cue.lineAlign=settings.get('lineAlign', 'start');\n    cue.snapToLines=settings.get('snapToLines', true);\n    cue.size=settings.get('size', 100);\n    cue.align=settings.get('align', center);\n    let position=settings.get('position', 'auto');\n    if(position==='auto'&&defaults.position===50){\n      // set numeric position for Safari\n      position=cue.align==='start'||cue.align==='left' ? 0:cue.align==='end'||cue.align==='right' ? 100:50;\n    }\n    cue.position=position;\n  }\n  function skipWhitespace(){\n    input=input.replace(/^\\s+/, '');\n  }\n\n  // 4.1 WebVTT cue timings.\n  skipWhitespace();\n  cue.startTime=consumeTimeStamp(); // (1) collect cue start time\n  skipWhitespace();\n  if(input.slice(0, 3)!=='--\x3e'){\n    // (3) next characters must match '--\x3e'\n    throw new Error(\"Malformed time stamp (time stamps must be separated by '--\x3e'): \" + oInput);\n  }\n  input=input.slice(3);\n  skipWhitespace();\n  cue.endTime=consumeTimeStamp(); // (5) collect cue end time\n\n  // 4.1 WebVTT cue settings list.\n  skipWhitespace();\n  consumeCueSettings(input, cue);\n}\nfunction fixLineBreaks(input){\n  return input.replace(/<br(?: \\/)?>/gi, '\\n');\n}\nclass VTTParser {\n  constructor(){\n    this.state='INITIAL';\n    this.buffer='';\n    this.decoder=new StringDecoder();\n    this.regionList=[];\n    this.cue=null;\n    this.oncue=void 0;\n    this.onparsingerror=void 0;\n    this.onflush=void 0;\n  }\n  parse(data){\n    const _this=this;\n\n    // If there is no data then we won't decode it, but will just try to parse\n    // whatever is in buffer already. This may occur in circumstances, for\n    // example when flush() is called.\n    if(data){\n      // Try to decode the data that we received.\n      _this.buffer +=_this.decoder.decode(data, {\n        stream: true\n      });\n    }\n    function collectNextLine(){\n      let buffer=_this.buffer;\n      let pos=0;\n      buffer=fixLineBreaks(buffer);\n      while (pos < buffer.length&&buffer[pos]!=='\\r'&&buffer[pos]!=='\\n'){\n        ++pos;\n      }\n      const line=buffer.slice(0, pos);\n      // Advance the buffer early in case we fail below.\n      if(buffer[pos]==='\\r'){\n        ++pos;\n      }\n      if(buffer[pos]==='\\n'){\n        ++pos;\n      }\n      _this.buffer=buffer.slice(pos);\n      return line;\n    }\n\n    // 3.2 WebVTT metadata header syntax\n    function parseHeader(input){\n      parseOptions(input, function (k, v){\n        // switch (k){\n        // case 'region':\n        // 3.3 WebVTT region metadata header syntax\n        // console.log('parse region', v);\n        // parseRegion(v);\n        // break;\n        // }\n      }, /:/);\n    }\n\n    // 5.1 WebVTT file parsing.\n    try {\n      let line='';\n      if(_this.state==='INITIAL'){\n        // We can't start parsing until we have the first line.\n        if(!/\\r\\n|\\n/.test(_this.buffer)){\n          return this;\n        }\n        line=collectNextLine();\n        // strip of UTF-8 BOM if any\n        // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8\n        const m=line.match(/^(ï»¿)?WEBVTT([ \\t].*)?$/);\n        if(!(m!=null&&m[0])){\n          throw new Error('Malformed WebVTT signature.');\n        }\n        _this.state='HEADER';\n      }\n      let alreadyCollectedLine=false;\n      while (_this.buffer){\n        // We can't parse a line until we have the full line.\n        if(!/\\r\\n|\\n/.test(_this.buffer)){\n          return this;\n        }\n        if(!alreadyCollectedLine){\n          line=collectNextLine();\n        }else{\n          alreadyCollectedLine=false;\n        }\n        switch (_this.state){\n          case 'HEADER':\n            // 13-18 - Allow a header (metadata) under the WEBVTT line.\n            if(/:/.test(line)){\n              parseHeader(line);\n            }else if(!line){\n              // An empty line terminates the header and starts the body (cues).\n              _this.state='ID';\n            }\n            continue;\n          case 'NOTE':\n            // Ignore NOTE blocks.\n            if(!line){\n              _this.state='ID';\n            }\n            continue;\n          case 'ID':\n            // Check for the start of NOTE blocks.\n            if(/^NOTE($|[ \\t])/.test(line)){\n              _this.state='NOTE';\n              break;\n            }\n            // 19-29 - Allow any number of line terminators, then initialize new cue values.\n            if(!line){\n              continue;\n            }\n            _this.cue=new VTTCue(0, 0, '');\n            _this.state='CUE';\n            // 30-39 - Check if self line contains an optional identifier or timing data.\n            if(line.indexOf('--\x3e')===-1){\n              _this.cue.id=line;\n              continue;\n            }\n          // Process line as start of a cue.\n          \n          case 'CUE':\n            // 40 - Collect cue timings and settings.\n            if(!_this.cue){\n              _this.state='BADCUE';\n              continue;\n            }\n            try {\n              parseCue(line, _this.cue, _this.regionList);\n            } catch (e){\n              // In case of an error ignore rest of the cue.\n              _this.cue=null;\n              _this.state='BADCUE';\n              continue;\n            }\n            _this.state='CUETEXT';\n            continue;\n          case 'CUETEXT':\n            {\n              const hasSubstring=line.indexOf('--\x3e')!==-1;\n              // 34 - If we have an empty line then report the cue.\n              // 35 - If we have the special substring '--\x3e' then report the cue,\n              // but do not collect the line as we need to process the current\n              // one as a new cue.\n              if(!line||hasSubstring&&(alreadyCollectedLine=true)){\n                // We are done parsing self cue.\n                if(_this.oncue&&_this.cue){\n                  _this.oncue(_this.cue);\n                }\n                _this.cue=null;\n                _this.state='ID';\n                continue;\n              }\n              if(_this.cue===null){\n                continue;\n              }\n              if(_this.cue.text){\n                _this.cue.text +='\\n';\n              }\n              _this.cue.text +=line;\n            }\n            continue;\n          case 'BADCUE':\n            // 54-62 - Collect and discard the remaining cue.\n            if(!line){\n              _this.state='ID';\n            }\n        }\n      }\n    } catch (e){\n      // If we are currently parsing a cue, report what we have.\n      if(_this.state==='CUETEXT'&&_this.cue&&_this.oncue){\n        _this.oncue(_this.cue);\n      }\n      _this.cue=null;\n      // Enter BADWEBVTT state if header was not parsed correctly otherwise\n      // another exception occurred so enter BADCUE state.\n      _this.state=_this.state==='INITIAL' ? 'BADWEBVTT':'BADCUE';\n    }\n    return this;\n  }\n  flush(){\n    const _this=this;\n    try {\n      // Finish decoding the stream.\n      // _this.buffer +=_this.decoder.decode();\n      // Synthesize the end of the current cue or region.\n      if(_this.cue||_this.state==='HEADER'){\n        _this.buffer +='\\n\\n';\n        _this.parse();\n      }\n      // If we've flushed, parsed, and we're still on the INITIAL state then\n      // that means we don't have enough of the stream to parse the first\n      // line.\n      if(_this.state==='INITIAL'||_this.state==='BADWEBVTT'){\n        throw new Error('Malformed WebVTT signature.');\n      }\n    } catch (e){\n      if(_this.onparsingerror){\n        _this.onparsingerror(e);\n      }\n    }\n    if(_this.onflush){\n      _this.onflush();\n    }\n    return this;\n  }\n}\n\nconst LINEBREAKS=/\\r\\n|\\n\\r|\\n|\\r/g;\n\n// String.prototype.startsWith is not supported in IE11\nconst startsWith=function startsWith(inputString, searchString, position=0){\n  return inputString.slice(position, position + searchString.length)===searchString;\n};\nconst cueString2millis=function cueString2millis(timeString){\n  let ts=parseInt(timeString.slice(-3));\n  const secs=parseInt(timeString.slice(-6, -4));\n  const mins=parseInt(timeString.slice(-9, -7));\n  const hours=timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))):0;\n  if(!isFiniteNumber(ts)||!isFiniteNumber(secs)||!isFiniteNumber(mins)||!isFiniteNumber(hours)){\n    throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`);\n  }\n  ts +=1000 * secs;\n  ts +=60 * 1000 * mins;\n  ts +=60 * 60 * 1000 * hours;\n  return ts;\n};\n\n// From https://github.com/darkskyapp/string-hash\nconst hash=function hash(text){\n  let _hash=5381;\n  let i=text.length;\n  while (i){\n    _hash=_hash * 33 ^ text.charCodeAt(--i);\n  }\n  return (_hash >>> 0).toString();\n};\n\n// Create a unique hash id for a cue based on start/end times and text.\n// This helps timeline-controller to avoid showing repeated captions.\nfunction generateCueId(startTime, endTime, text){\n  return hash(startTime.toString()) + hash(endTime.toString()) + hash(text);\n}\nconst calculateOffset=function calculateOffset(vttCCs, cc, presentationTime){\n  let currCC=vttCCs[cc];\n  let prevCC=vttCCs[currCC.prevCC];\n\n  // This is the first discontinuity or cues have been processed since the last discontinuity\n  // Offset=current discontinuity time\n  if(!prevCC||!prevCC.new&&currCC.new){\n    vttCCs.ccOffset=vttCCs.presentationOffset=currCC.start;\n    currCC.new=false;\n    return;\n  }\n\n  // There have been discontinuities since cues were last parsed.\n  // Offset=time elapsed\n  while ((_prevCC=prevCC)!=null&&_prevCC.new){\n    var _prevCC;\n    vttCCs.ccOffset +=currCC.start - prevCC.start;\n    currCC.new=false;\n    currCC=prevCC;\n    prevCC=vttCCs[currCC.prevCC];\n  }\n  vttCCs.presentationOffset=presentationTime;\n};\nfunction parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack){\n  const parser=new VTTParser();\n  // Convert byteArray into string, replacing any somewhat exotic linefeeds with \"\\n\", then split on that character.\n  // Uint8Array.prototype.reduce is not implemented in IE11\n  const vttLines=utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\\n').split('\\n');\n  const cues=[];\n  const init90kHz=initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale):0;\n  let cueTime='00:00.000';\n  let timestampMapMPEGTS=0;\n  let timestampMapLOCAL=0;\n  let parsingError;\n  let inHeader=true;\n  parser.oncue=function (cue){\n    // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline.\n    const currCC=vttCCs[cc];\n    let cueOffset=vttCCs.ccOffset;\n\n    // Calculate subtitle PTS offset\n    const webVttMpegTsMapOffset=(timestampMapMPEGTS - init90kHz) / 90000;\n\n    // Update offsets for new discontinuities\n    if(currCC!=null&&currCC.new){\n      if(timestampMapLOCAL!==undefined){\n        // When local time is provided, offset=discontinuity start time - local time\n        cueOffset=vttCCs.ccOffset=currCC.start;\n      }else{\n        calculateOffset(vttCCs, cc, webVttMpegTsMapOffset);\n      }\n    }\n    if(webVttMpegTsMapOffset){\n      if(!initPTS){\n        parsingError=new Error('Missing initPTS for VTT MPEGTS');\n        return;\n      }\n      // If we have MPEGTS, offset=presentation time + discontinuity offset\n      cueOffset=webVttMpegTsMapOffset - vttCCs.presentationOffset;\n    }\n    const duration=cue.endTime - cue.startTime;\n    const startTime=normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000;\n    cue.startTime=Math.max(startTime, 0);\n    cue.endTime=Math.max(startTime + duration, 0);\n\n    //trim trailing webvtt block whitespaces\n    const text=cue.text.trim();\n\n    // Fix encoding of special characters\n    cue.text=decodeURIComponent(encodeURIComponent(text));\n\n    // If the cue was not assigned an id from the VTT file (line above the content), create one.\n    if(!cue.id){\n      cue.id=generateCueId(cue.startTime, cue.endTime, text);\n    }\n    if(cue.endTime > 0){\n      cues.push(cue);\n    }\n  };\n  parser.onparsingerror=function (error){\n    parsingError=error;\n  };\n  parser.onflush=function (){\n    if(parsingError){\n      errorCallBack(parsingError);\n      return;\n    }\n    callBack(cues);\n  };\n\n  // Go through contents line by line.\n  vttLines.forEach(line=> {\n    if(inHeader){\n      // Look for X-TIMESTAMP-MAP in header.\n      if(startsWith(line, 'X-TIMESTAMP-MAP=')){\n        // Once found, no more are allowed anyway, so stop searching.\n        inHeader=false;\n        // Extract LOCAL and MPEGTS.\n        line.slice(16).split(',').forEach(timestamp=> {\n          if(startsWith(timestamp, 'LOCAL:')){\n            cueTime=timestamp.slice(6);\n          }else if(startsWith(timestamp, 'MPEGTS:')){\n            timestampMapMPEGTS=parseInt(timestamp.slice(7));\n          }\n        });\n        try {\n          // Convert cue time to seconds\n          timestampMapLOCAL=cueString2millis(cueTime) / 1000;\n        } catch (error){\n          parsingError=error;\n        }\n        // Return without parsing X-TIMESTAMP-MAP line.\n        return;\n      }else if(line===''){\n        inHeader=false;\n      }\n    }\n    // Parse line by default.\n    parser.parse(line + '\\n');\n  });\n  parser.flush();\n}\n\nconst IMSC1_CODEC='stpp.ttml.im1t';\n\n// Time format: h:m:s:frames(.subframes)\nconst HMSF_REGEX=/^(\\d{2,}):(\\d{2}):(\\d{2}):(\\d{2})\\.?(\\d+)?$/;\n\n// Time format: hours, minutes, seconds, milliseconds, frames, ticks\nconst TIME_UNIT_REGEX=/^(\\d*(?:\\.\\d*)?)(h|m|s|ms|f|t)$/;\nconst textAlignToLineAlign={\n  left: 'start',\n  center: 'center',\n  right: 'end',\n  start: 'start',\n  end: 'end'\n};\nfunction parseIMSC1(payload, initPTS, callBack, errorCallBack){\n  const results=findBox(new Uint8Array(payload), ['mdat']);\n  if(results.length===0){\n    errorCallBack(new Error('Could not parse IMSC1 mdat'));\n    return;\n  }\n  const ttmlList=results.map(mdat=> utf8ArrayToStr(mdat));\n  const syncTime=toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale);\n  try {\n    ttmlList.forEach(ttml=> callBack(parseTTML(ttml, syncTime)));\n  } catch (error){\n    errorCallBack(error);\n  }\n}\nfunction parseTTML(ttml, syncTime){\n  const parser=new DOMParser();\n  const xmlDoc=parser.parseFromString(ttml, 'text/xml');\n  const tt=xmlDoc.getElementsByTagName('tt')[0];\n  if(!tt){\n    throw new Error('Invalid ttml');\n  }\n  const defaultRateInfo={\n    frameRate: 30,\n    subFrameRate: 1,\n    frameRateMultiplier: 0,\n    tickRate: 0\n  };\n  const rateInfo=Object.keys(defaultRateInfo).reduce((result, key)=> {\n    result[key]=tt.getAttribute(`ttp:${key}`)||defaultRateInfo[key];\n    return result;\n  }, {});\n  const trim=tt.getAttribute('xml:space')!=='preserve';\n  const styleElements=collectionToDictionary(getElementCollection(tt, 'styling', 'style'));\n  const regionElements=collectionToDictionary(getElementCollection(tt, 'layout', 'region'));\n  const cueElements=getElementCollection(tt, 'body', '[begin]');\n  return [].map.call(cueElements, cueElement=> {\n    const cueText=getTextContent(cueElement, trim);\n    if(!cueText||!cueElement.hasAttribute('begin')){\n      return null;\n    }\n    const startTime=parseTtmlTime(cueElement.getAttribute('begin'), rateInfo);\n    const duration=parseTtmlTime(cueElement.getAttribute('dur'), rateInfo);\n    let endTime=parseTtmlTime(cueElement.getAttribute('end'), rateInfo);\n    if(startTime===null){\n      throw timestampParsingError(cueElement);\n    }\n    if(endTime===null){\n      if(duration===null){\n        throw timestampParsingError(cueElement);\n      }\n      endTime=startTime + duration;\n    }\n    const cue=new VTTCue(startTime - syncTime, endTime - syncTime, cueText);\n    cue.id=generateCueId(cue.startTime, cue.endTime, cue.text);\n    const region=regionElements[cueElement.getAttribute('region')];\n    const style=styleElements[cueElement.getAttribute('style')];\n\n    // Apply styles to cue\n    const styles=getTtmlStyles(region, style, styleElements);\n    const {\n      textAlign\n    }=styles;\n    if(textAlign){\n      // cue.positionAlign not settable in FF~2016\n      const lineAlign=textAlignToLineAlign[textAlign];\n      if(lineAlign){\n        cue.lineAlign=lineAlign;\n      }\n      cue.align=textAlign;\n    }\n    _extends(cue, styles);\n    return cue;\n  }).filter(cue=> cue!==null);\n}\nfunction getElementCollection(fromElement, parentName, childName){\n  const parent=fromElement.getElementsByTagName(parentName)[0];\n  if(parent){\n    return [].slice.call(parent.querySelectorAll(childName));\n  }\n  return [];\n}\nfunction collectionToDictionary(elementsWithId){\n  return elementsWithId.reduce((dict, element)=> {\n    const id=element.getAttribute('xml:id');\n    if(id){\n      dict[id]=element;\n    }\n    return dict;\n  }, {});\n}\nfunction getTextContent(element, trim){\n  return [].slice.call(element.childNodes).reduce((str, node, i)=> {\n    var _node$childNodes;\n    if(node.nodeName==='br'&&i){\n      return str + '\\n';\n    }\n    if((_node$childNodes=node.childNodes)!=null&&_node$childNodes.length){\n      return getTextContent(node, trim);\n    }else if(trim){\n      return str + node.textContent.trim().replace(/\\s+/g, ' ');\n    }\n    return str + node.textContent;\n  }, '');\n}\nfunction getTtmlStyles(region, style, styleElements){\n  const ttsNs='http://www.w3.org/ns/ttml#styling';\n  let regionStyle=null;\n  const styleAttributes=['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily'\n  // 'fontWeight',\n  // 'lineHeight',\n  // 'wrapOption',\n  // 'fontStyle',\n  // 'direction',\n  // 'writingMode'\n  ];\n  const regionStyleName=region!=null&&region.hasAttribute('style') ? region.getAttribute('style'):null;\n  if(regionStyleName&&styleElements.hasOwnProperty(regionStyleName)){\n    regionStyle=styleElements[regionStyleName];\n  }\n  return styleAttributes.reduce((styles, name)=> {\n    const value=getAttributeNS(style, ttsNs, name)||getAttributeNS(region, ttsNs, name)||getAttributeNS(regionStyle, ttsNs, name);\n    if(value){\n      styles[name]=value;\n    }\n    return styles;\n  }, {});\n}\nfunction getAttributeNS(element, ns, name){\n  if(!element){\n    return null;\n  }\n  return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name):null;\n}\nfunction timestampParsingError(node){\n  return new Error(`Could not parse ttml timestamp ${node}`);\n}\nfunction parseTtmlTime(timeAttributeValue, rateInfo){\n  if(!timeAttributeValue){\n    return null;\n  }\n  let seconds=parseTimeStamp(timeAttributeValue);\n  if(seconds===null){\n    if(HMSF_REGEX.test(timeAttributeValue)){\n      seconds=parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo);\n    }else if(TIME_UNIT_REGEX.test(timeAttributeValue)){\n      seconds=parseTimeUnits(timeAttributeValue, rateInfo);\n    }\n  }\n  return seconds;\n}\nfunction parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo){\n  const m=HMSF_REGEX.exec(timeAttributeValue);\n  const frames=(m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate;\n  return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate;\n}\nfunction parseTimeUnits(timeAttributeValue, rateInfo){\n  const m=TIME_UNIT_REGEX.exec(timeAttributeValue);\n  const value=Number(m[1]);\n  const unit=m[2];\n  switch (unit){\n    case 'h':\n      return value * 3600;\n    case 'm':\n      return value * 60;\n    case 'ms':\n      return value * 1000;\n    case 'f':\n      return value / rateInfo.frameRate;\n    case 't':\n      return value / rateInfo.tickRate;\n  }\n  return value;\n}\n\nclass TimelineController {\n  constructor(hls){\n    this.hls=void 0;\n    this.media=null;\n    this.config=void 0;\n    this.enabled=true;\n    this.Cues=void 0;\n    this.textTracks=[];\n    this.tracks=[];\n    this.initPTS=[];\n    this.unparsedVttFrags=[];\n    this.captionsTracks={};\n    this.nonNativeCaptionsTracks={};\n    this.cea608Parser1=void 0;\n    this.cea608Parser2=void 0;\n    this.lastCc=-1;\n    // Last video (CEA-608) fragment CC\n    this.lastSn=-1;\n    // Last video (CEA-608) fragment MSN\n    this.lastPartIndex=-1;\n    // Last video (CEA-608) fragment Part Index\n    this.prevCC=-1;\n    // Last subtitle fragment CC\n    this.vttCCs=newVTTCCs();\n    this.captionsProperties=void 0;\n    this.hls=hls;\n    this.config=hls.config;\n    this.Cues=hls.config.cueHandler;\n    this.captionsProperties={\n      textTrack1: {\n        label: this.config.captionsTextTrack1Label,\n        languageCode: this.config.captionsTextTrack1LanguageCode\n      },\n      textTrack2: {\n        label: this.config.captionsTextTrack2Label,\n        languageCode: this.config.captionsTextTrack2LanguageCode\n      },\n      textTrack3: {\n        label: this.config.captionsTextTrack3Label,\n        languageCode: this.config.captionsTextTrack3LanguageCode\n      },\n      textTrack4: {\n        label: this.config.captionsTextTrack4Label,\n        languageCode: this.config.captionsTextTrack4LanguageCode\n      }\n    };\n    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);\n    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);\n    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);\n    hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);\n    hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);\n    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);\n    hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);\n    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n  }\n  destroy(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);\n    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);\n    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);\n    hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);\n    hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);\n    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);\n    hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);\n    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);\n    // @ts-ignore\n    this.hls=this.config=null;\n    this.cea608Parser1=this.cea608Parser2=undefined;\n  }\n  initCea608Parsers(){\n    if(this.config.enableCEA708Captions&&(!this.cea608Parser1||!this.cea608Parser2)){\n      const channel1=new OutputFilter(this, 'textTrack1');\n      const channel2=new OutputFilter(this, 'textTrack2');\n      const channel3=new OutputFilter(this, 'textTrack3');\n      const channel4=new OutputFilter(this, 'textTrack4');\n      this.cea608Parser1=new Cea608Parser(1, channel1, channel2);\n      this.cea608Parser2=new Cea608Parser(3, channel3, channel4);\n    }\n  }\n  addCues(trackName, startTime, endTime, screen, cueRanges){\n    // skip cues which overlap more than 50% with previously parsed time ranges\n    let merged=false;\n    for (let i=cueRanges.length; i--;){\n      const cueRange=cueRanges[i];\n      const overlap=intersection(cueRange[0], cueRange[1], startTime, endTime);\n      if(overlap >=0){\n        cueRange[0]=Math.min(cueRange[0], startTime);\n        cueRange[1]=Math.max(cueRange[1], endTime);\n        merged=true;\n        if(overlap / (endTime - startTime) > 0.5){\n          return;\n        }\n      }\n    }\n    if(!merged){\n      cueRanges.push([startTime, endTime]);\n    }\n    if(this.config.renderTextTracksNatively){\n      const track=this.captionsTracks[trackName];\n      this.Cues.newCue(track, startTime, endTime, screen);\n    }else{\n      const cues=this.Cues.newCue(null, startTime, endTime, screen);\n      this.hls.trigger(Events.CUES_PARSED, {\n        type: 'captions',\n        cues,\n        track: trackName\n      });\n    }\n  }\n\n  // Triggered when an initial PTS is found; used for synchronisation of WebVTT.\n  onInitPtsFound(event, {\n    frag,\n    id,\n    initPTS,\n    timescale\n  }){\n    const {\n      unparsedVttFrags\n    }=this;\n    if(id==='main'){\n      this.initPTS[frag.cc]={\n        baseTime: initPTS,\n        timescale\n      };\n    }\n\n    // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded.\n    // Parse any unparsed fragments upon receiving the initial PTS.\n    if(unparsedVttFrags.length){\n      this.unparsedVttFrags=[];\n      unparsedVttFrags.forEach(frag=> {\n        this.onFragLoaded(Events.FRAG_LOADED, frag);\n      });\n    }\n  }\n  getExistingTrack(label, language){\n    const {\n      media\n    }=this;\n    if(media){\n      for (let i=0; i < media.textTracks.length; i++){\n        const textTrack=media.textTracks[i];\n        if(canReuseVttTextTrack(textTrack, {\n          name: label,\n          lang: language,\n          attrs: {}\n        })){\n          return textTrack;\n        }\n      }\n    }\n    return null;\n  }\n  createCaptionsTrack(trackName){\n    if(this.config.renderTextTracksNatively){\n      this.createNativeTrack(trackName);\n    }else{\n      this.createNonNativeTrack(trackName);\n    }\n  }\n  createNativeTrack(trackName){\n    if(this.captionsTracks[trackName]){\n      return;\n    }\n    const {\n      captionsProperties,\n      captionsTracks,\n      media\n    }=this;\n    const {\n      label,\n      languageCode\n    }=captionsProperties[trackName];\n    // Enable reuse of existing text track.\n    const existingTrack=this.getExistingTrack(label, languageCode);\n    if(!existingTrack){\n      const textTrack=this.createTextTrack('captions', label, languageCode);\n      if(textTrack){\n        // Set a special property on the track so we know it's managed by Hls.js\n        textTrack[trackName]=true;\n        captionsTracks[trackName]=textTrack;\n      }\n    }else{\n      captionsTracks[trackName]=existingTrack;\n      clearCurrentCues(captionsTracks[trackName]);\n      sendAddTrackEvent(captionsTracks[trackName], media);\n    }\n  }\n  createNonNativeTrack(trackName){\n    if(this.nonNativeCaptionsTracks[trackName]){\n      return;\n    }\n    // Create a list of a single track for the provider to consume\n    const trackProperties=this.captionsProperties[trackName];\n    if(!trackProperties){\n      return;\n    }\n    const label=trackProperties.label;\n    const track={\n      _id: trackName,\n      label,\n      kind: 'captions',\n      default: trackProperties.media ? !!trackProperties.media.default:false,\n      closedCaptions: trackProperties.media\n    };\n    this.nonNativeCaptionsTracks[trackName]=track;\n    this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {\n      tracks: [track]\n    });\n  }\n  createTextTrack(kind, label, lang){\n    const media=this.media;\n    if(!media){\n      return;\n    }\n    return media.addTextTrack(kind, label, lang);\n  }\n  onMediaAttaching(event, data){\n    this.media=data.media;\n    this._cleanTracks();\n  }\n  onMediaDetaching(){\n    const {\n      captionsTracks\n    }=this;\n    Object.keys(captionsTracks).forEach(trackName=> {\n      clearCurrentCues(captionsTracks[trackName]);\n      delete captionsTracks[trackName];\n    });\n    this.nonNativeCaptionsTracks={};\n  }\n  onManifestLoading(){\n    // Detect discontinuity in video fragment (CEA-608) parsing\n    this.lastCc=-1;\n    this.lastSn=-1;\n    this.lastPartIndex=-1;\n    // Detect discontinuity in subtitle manifests\n    this.prevCC=-1;\n    this.vttCCs=newVTTCCs();\n    // Reset tracks\n    this._cleanTracks();\n    this.tracks=[];\n    this.captionsTracks={};\n    this.nonNativeCaptionsTracks={};\n    this.textTracks=[];\n    this.unparsedVttFrags=[];\n    this.initPTS=[];\n    if(this.cea608Parser1&&this.cea608Parser2){\n      this.cea608Parser1.reset();\n      this.cea608Parser2.reset();\n    }\n  }\n  _cleanTracks(){\n    // clear outdated subtitles\n    const {\n      media\n    }=this;\n    if(!media){\n      return;\n    }\n    const textTracks=media.textTracks;\n    if(textTracks){\n      for (let i=0; i < textTracks.length; i++){\n        clearCurrentCues(textTracks[i]);\n      }\n    }\n  }\n  onSubtitleTracksUpdated(event, data){\n    const tracks=data.subtitleTracks||[];\n    const hasIMSC1=tracks.some(track=> track.textCodec===IMSC1_CODEC);\n    if(this.config.enableWebVTT||hasIMSC1&&this.config.enableIMSC1){\n      const listIsIdentical=subtitleOptionsIdentical(this.tracks, tracks);\n      if(listIsIdentical){\n        this.tracks=tracks;\n        return;\n      }\n      this.textTracks=[];\n      this.tracks=tracks;\n      if(this.config.renderTextTracksNatively){\n        const media=this.media;\n        const inUseTracks=media ? filterSubtitleTracks(media.textTracks):null;\n        this.tracks.forEach((track, index)=> {\n          // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks\n          let textTrack;\n          if(inUseTracks){\n            let inUseTrack=null;\n            for (let i=0; i < inUseTracks.length; i++){\n              if(inUseTracks[i]&&canReuseVttTextTrack(inUseTracks[i], track)){\n                inUseTrack=inUseTracks[i];\n                inUseTracks[i]=null;\n                break;\n              }\n            }\n            if(inUseTrack){\n              textTrack=inUseTrack;\n            }\n          }\n          if(textTrack){\n            clearCurrentCues(textTrack);\n          }else{\n            const textTrackKind=captionsOrSubtitlesFromCharacteristics(track);\n            textTrack=this.createTextTrack(textTrackKind, track.name, track.lang);\n            if(textTrack){\n              textTrack.mode='disabled';\n            }\n          }\n          if(textTrack){\n            this.textTracks.push(textTrack);\n          }\n        });\n        // Warn when video element has captions or subtitle TextTracks carried over from another source\n        if(inUseTracks!=null&&inUseTracks.length){\n          const unusedTextTracks=inUseTracks.filter(t=> t!==null).map(t=> t.label);\n          if(unusedTextTracks.length){\n            logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);\n          }\n        }\n      }else if(this.tracks.length){\n        // Create a list of tracks for the provider to consume\n        const tracksList=this.tracks.map(track=> {\n          return {\n            label: track.name,\n            kind: track.type.toLowerCase(),\n            default: track.default,\n            subtitleTrack: track\n          };\n        });\n        this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {\n          tracks: tracksList\n        });\n      }\n    }\n  }\n  onManifestLoaded(event, data){\n    if(this.config.enableCEA708Captions&&data.captions){\n      data.captions.forEach(captionsTrack=> {\n        const instreamIdMatch=/(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId);\n        if(!instreamIdMatch){\n          return;\n        }\n        const trackName=`textTrack${instreamIdMatch[1]}`;\n        const trackProperties=this.captionsProperties[trackName];\n        if(!trackProperties){\n          return;\n        }\n        trackProperties.label=captionsTrack.name;\n        if(captionsTrack.lang){\n          // optional attribute\n          trackProperties.languageCode=captionsTrack.lang;\n        }\n        trackProperties.media=captionsTrack;\n      });\n    }\n  }\n  closedCaptionsForLevel(frag){\n    const level=this.hls.levels[frag.level];\n    return level==null ? void 0:level.attrs['CLOSED-CAPTIONS'];\n  }\n  onFragLoading(event, data){\n    // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack\n    if(this.enabled&&data.frag.type===PlaylistLevelType.MAIN){\n      var _data$part$index, _data$part;\n      const {\n        cea608Parser1,\n        cea608Parser2,\n        lastSn\n      }=this;\n      const {\n        cc,\n        sn\n      }=data.frag;\n      const partIndex=(_data$part$index=(_data$part=data.part)==null ? void 0:_data$part.index)!=null ? _data$part$index:-1;\n      if(cea608Parser1&&cea608Parser2){\n        if(sn!==lastSn + 1||sn===lastSn&&partIndex!==this.lastPartIndex + 1||cc!==this.lastCc){\n          cea608Parser1.reset();\n          cea608Parser2.reset();\n        }\n      }\n      this.lastCc=cc;\n      this.lastSn=sn;\n      this.lastPartIndex=partIndex;\n    }\n  }\n  onFragLoaded(event, data){\n    const {\n      frag,\n      payload\n    }=data;\n    if(frag.type===PlaylistLevelType.SUBTITLE){\n      // If fragment is subtitle type, parse as WebVTT.\n      if(payload.byteLength){\n        const decryptData=frag.decryptdata;\n        // fragment after decryption has a stats object\n        const decrypted=('stats' in data);\n        // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait.\n        if(decryptData==null||!decryptData.encrypted||decrypted){\n          const trackPlaylistMedia=this.tracks[frag.level];\n          const vttCCs=this.vttCCs;\n          if(!vttCCs[frag.cc]){\n            vttCCs[frag.cc]={\n              start: frag.start,\n              prevCC: this.prevCC,\n              new: true\n            };\n            this.prevCC=frag.cc;\n          }\n          if(trackPlaylistMedia&&trackPlaylistMedia.textCodec===IMSC1_CODEC){\n            this._parseIMSC1(frag, payload);\n          }else{\n            this._parseVTTs(data);\n          }\n        }\n      }else{\n        // In case there is no payload, finish unsuccessfully.\n        this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {\n          success: false,\n          frag,\n          error: new Error('Empty subtitle payload')\n        });\n      }\n    }\n  }\n  _parseIMSC1(frag, payload){\n    const hls=this.hls;\n    parseIMSC1(payload, this.initPTS[frag.cc], cues=> {\n      this._appendCues(cues, frag.level);\n      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {\n        success: true,\n        frag: frag\n      });\n    }, error=> {\n      logger.log(`Failed to parse IMSC1: ${error}`);\n      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {\n        success: false,\n        frag: frag,\n        error\n      });\n    });\n  }\n  _parseVTTs(data){\n    var _frag$initSegment;\n    const {\n      frag,\n      payload\n    }=data;\n    // We need an initial synchronisation PTS. Store fragments as long as none has arrived\n    const {\n      initPTS,\n      unparsedVttFrags\n    }=this;\n    const maxAvCC=initPTS.length - 1;\n    if(!initPTS[frag.cc]&&maxAvCC===-1){\n      unparsedVttFrags.push(data);\n      return;\n    }\n    const hls=this.hls;\n    // Parse the WebVTT file contents.\n    const payloadWebVTT=(_frag$initSegment=frag.initSegment)!=null&&_frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)):payload;\n    parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, cues=> {\n      this._appendCues(cues, frag.level);\n      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {\n        success: true,\n        frag: frag\n      });\n    }, error=> {\n      const missingInitPTS=error.message==='Missing initPTS for VTT MPEGTS';\n      if(missingInitPTS){\n        unparsedVttFrags.push(data);\n      }else{\n        this._fallbackToIMSC1(frag, payload);\n      }\n      // Something went wrong while parsing. Trigger event with success false.\n      logger.log(`Failed to parse VTT cue: ${error}`);\n      if(missingInitPTS&&maxAvCC > frag.cc){\n        return;\n      }\n      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {\n        success: false,\n        frag: frag,\n        error\n      });\n    });\n  }\n  _fallbackToIMSC1(frag, payload){\n    // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result\n    const trackPlaylistMedia=this.tracks[frag.level];\n    if(!trackPlaylistMedia.textCodec){\n      parseIMSC1(payload, this.initPTS[frag.cc], ()=> {\n        trackPlaylistMedia.textCodec=IMSC1_CODEC;\n        this._parseIMSC1(frag, payload);\n      }, ()=> {\n        trackPlaylistMedia.textCodec='wvtt';\n      });\n    }\n  }\n  _appendCues(cues, fragLevel){\n    const hls=this.hls;\n    if(this.config.renderTextTracksNatively){\n      const textTrack=this.textTracks[fragLevel];\n      // WebVTTParser.parse is an async method and if the currently selected text track mode is set to \"disabled\"\n      // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null\n      // and trying to access getCueById method of cues will throw an exception\n      // Because we check if the mode is disabled, we can force check `cues` below. They can't be null.\n      if(!textTrack||textTrack.mode==='disabled'){\n        return;\n      }\n      cues.forEach(cue=> addCueToTrack(textTrack, cue));\n    }else{\n      const currentTrack=this.tracks[fragLevel];\n      if(!currentTrack){\n        return;\n      }\n      const track=currentTrack.default ? 'default':'subtitles' + fragLevel;\n      hls.trigger(Events.CUES_PARSED, {\n        type: 'subtitles',\n        cues,\n        track\n      });\n    }\n  }\n  onFragDecrypted(event, data){\n    const {\n      frag\n    }=data;\n    if(frag.type===PlaylistLevelType.SUBTITLE){\n      this.onFragLoaded(Events.FRAG_LOADED, data);\n    }\n  }\n  onSubtitleTracksCleared(){\n    this.tracks=[];\n    this.captionsTracks={};\n  }\n  onFragParsingUserdata(event, data){\n    this.initCea608Parsers();\n    const {\n      cea608Parser1,\n      cea608Parser2\n    }=this;\n    if(!this.enabled||!cea608Parser1||!cea608Parser2){\n      return;\n    }\n    const {\n      frag,\n      samples\n    }=data;\n    if(frag.type===PlaylistLevelType.MAIN&&this.closedCaptionsForLevel(frag)==='NONE'){\n      return;\n    }\n    // If the event contains captions (found in the bytes property), push all bytes into the parser immediately\n    // It will create the proper timestamps based on the PTS value\n    for (let i=0; i < samples.length; i++){\n      const ccBytes=samples[i].bytes;\n      if(ccBytes){\n        const ccdatas=this.extractCea608Data(ccBytes);\n        cea608Parser1.addData(samples[i].pts, ccdatas[0]);\n        cea608Parser2.addData(samples[i].pts, ccdatas[1]);\n      }\n    }\n  }\n  onBufferFlushing(event, {\n    startOffset,\n    endOffset,\n    endOffsetSubtitles,\n    type\n  }){\n    const {\n      media\n    }=this;\n    if(!media||media.currentTime < endOffset){\n      return;\n    }\n    // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed\n    // Forward cues are never removed because we can loose streamed 608 content from recent fragments\n    if(!type||type==='video'){\n      const {\n        captionsTracks\n      }=this;\n      Object.keys(captionsTracks).forEach(trackName=> removeCuesInRange(captionsTracks[trackName], startOffset, endOffset));\n    }\n    if(this.config.renderTextTracksNatively){\n      // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed\n      if(startOffset===0&&endOffsetSubtitles!==undefined){\n        const {\n          textTracks\n        }=this;\n        Object.keys(textTracks).forEach(trackName=> removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles));\n      }\n    }\n  }\n  extractCea608Data(byteArray){\n    const actualCCBytes=[[], []];\n    const count=byteArray[0] & 0x1f;\n    let position=2;\n    for (let j=0; j < count; j++){\n      const tmpByte=byteArray[position++];\n      const ccbyte1=0x7f & byteArray[position++];\n      const ccbyte2=0x7f & byteArray[position++];\n      if(ccbyte1===0&&ccbyte2===0){\n        continue;\n      }\n      const ccValid=(0x04 & tmpByte)!==0; // Support all four channels\n      if(ccValid){\n        const ccType=0x03 & tmpByte;\n        if(0x00 ===ccType||0x01 ===ccType){\n          // Exclude CEA708 CC data.\n          actualCCBytes[ccType].push(ccbyte1);\n          actualCCBytes[ccType].push(ccbyte2);\n        }\n      }\n    }\n    return actualCCBytes;\n  }\n}\nfunction captionsOrSubtitlesFromCharacteristics(track){\n  if(track.characteristics){\n    if(/transcribes-spoken-dialog/gi.test(track.characteristics)&&/describes-music-and-sound/gi.test(track.characteristics)){\n      return 'captions';\n    }\n  }\n  return 'subtitles';\n}\nfunction canReuseVttTextTrack(inUseTrack, manifestTrack){\n  return !!inUseTrack&&inUseTrack.kind===captionsOrSubtitlesFromCharacteristics(manifestTrack)&&subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack);\n}\nfunction intersection(x1, x2, y1, y2){\n  return Math.min(x2, y2) - Math.max(x1, y1);\n}\nfunction newVTTCCs(){\n  return {\n    ccOffset: 0,\n    presentationOffset: 0,\n    0: {\n      start: 0,\n      prevCC: -1,\n      new: true\n    }\n  };\n}\n\nclass CapLevelController {\n  constructor(hls){\n    this.hls=void 0;\n    this.autoLevelCapping=void 0;\n    this.firstLevel=void 0;\n    this.media=void 0;\n    this.restrictedLevels=void 0;\n    this.timer=void 0;\n    this.clientRect=void 0;\n    this.streamController=void 0;\n    this.hls=hls;\n    this.autoLevelCapping=Number.POSITIVE_INFINITY;\n    this.firstLevel=-1;\n    this.media=null;\n    this.restrictedLevels=[];\n    this.timer=undefined;\n    this.clientRect=null;\n    this.registerListeners();\n  }\n  setStreamController(streamController){\n    this.streamController=streamController;\n  }\n  destroy(){\n    if(this.hls){\n      this.unregisterListener();\n    }\n    if(this.timer){\n      this.stopCapping();\n    }\n    this.media=null;\n    this.clientRect=null;\n    // @ts-ignore\n    this.hls=this.streamController=null;\n  }\n  registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);\n    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n  }\n  unregisterListener(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);\n    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n  }\n  onFpsDropLevelCapping(event, data){\n    // Don't add a restricted level more than once\n    const level=this.hls.levels[data.droppedLevel];\n    if(this.isLevelAllowed(level)){\n      this.restrictedLevels.push({\n        bitrate: level.bitrate,\n        height: level.height,\n        width: level.width\n      });\n    }\n  }\n  onMediaAttaching(event, data){\n    this.media=data.media instanceof HTMLVideoElement ? data.media:null;\n    this.clientRect=null;\n    if(this.timer&&this.hls.levels.length){\n      this.detectPlayerSize();\n    }\n  }\n  onManifestParsed(event, data){\n    const hls=this.hls;\n    this.restrictedLevels=[];\n    this.firstLevel=data.firstLevel;\n    if(hls.config.capLevelToPlayerSize&&data.video){\n      // Start capping immediately if the manifest has signaled video codecs\n      this.startCapping();\n    }\n  }\n  onLevelsUpdated(event, data){\n    if(this.timer&&isFiniteNumber(this.autoLevelCapping)){\n      this.detectPlayerSize();\n    }\n  }\n\n  // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted\n  // to the first level\n  onBufferCodecs(event, data){\n    const hls=this.hls;\n    if(hls.config.capLevelToPlayerSize&&data.video){\n      // If the manifest did not signal a video codec capping has been deferred until we're certain video is present\n      this.startCapping();\n    }\n  }\n  onMediaDetaching(){\n    this.stopCapping();\n  }\n  detectPlayerSize(){\n    if(this.media){\n      if(this.mediaHeight <=0||this.mediaWidth <=0){\n        this.clientRect=null;\n        return;\n      }\n      const levels=this.hls.levels;\n      if(levels.length){\n        const hls=this.hls;\n        const maxLevel=this.getMaxLevel(levels.length - 1);\n        if(maxLevel!==this.autoLevelCapping){\n          logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);\n        }\n        hls.autoLevelCapping=maxLevel;\n        if(hls.autoLevelCapping > this.autoLevelCapping&&this.streamController){\n          // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch\n          // usually happen when the user go to the fullscreen mode.\n          this.streamController.nextLevelSwitch();\n        }\n        this.autoLevelCapping=hls.autoLevelCapping;\n      }\n    }\n  }\n\n  \n  getMaxLevel(capLevelIndex){\n    const levels=this.hls.levels;\n    if(!levels.length){\n      return -1;\n    }\n    const validLevels=levels.filter((level, index)=> this.isLevelAllowed(level)&&index <=capLevelIndex);\n    this.clientRect=null;\n    return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);\n  }\n  startCapping(){\n    if(this.timer){\n      // Don't reset capping if started twice; this can happen if the manifest signals a video codec\n      return;\n    }\n    this.autoLevelCapping=Number.POSITIVE_INFINITY;\n    self.clearInterval(this.timer);\n    this.timer=self.setInterval(this.detectPlayerSize.bind(this), 1000);\n    this.detectPlayerSize();\n  }\n  stopCapping(){\n    this.restrictedLevels=[];\n    this.firstLevel=-1;\n    this.autoLevelCapping=Number.POSITIVE_INFINITY;\n    if(this.timer){\n      self.clearInterval(this.timer);\n      this.timer=undefined;\n    }\n  }\n  getDimensions(){\n    if(this.clientRect){\n      return this.clientRect;\n    }\n    const media=this.media;\n    const boundsRect={\n      width: 0,\n      height: 0\n    };\n    if(media){\n      const clientRect=media.getBoundingClientRect();\n      boundsRect.width=clientRect.width;\n      boundsRect.height=clientRect.height;\n      if(!boundsRect.width&&!boundsRect.height){\n        // When the media element has no width or height (equivalent to not being in the DOM),\n        // then use its width and height attributes (media.width, media.height)\n        boundsRect.width=clientRect.right - clientRect.left||media.width||0;\n        boundsRect.height=clientRect.bottom - clientRect.top||media.height||0;\n      }\n    }\n    this.clientRect=boundsRect;\n    return boundsRect;\n  }\n  get mediaWidth(){\n    return this.getDimensions().width * this.contentScaleFactor;\n  }\n  get mediaHeight(){\n    return this.getDimensions().height * this.contentScaleFactor;\n  }\n  get contentScaleFactor(){\n    let pixelRatio=1;\n    if(!this.hls.config.ignoreDevicePixelRatio){\n      try {\n        pixelRatio=self.devicePixelRatio;\n      } catch (e){\n        \n      }\n    }\n    return pixelRatio;\n  }\n  isLevelAllowed(level){\n    const restrictedLevels=this.restrictedLevels;\n    return !restrictedLevels.some(restrictedLevel=> {\n      return level.bitrate===restrictedLevel.bitrate&&level.width===restrictedLevel.width&&level.height===restrictedLevel.height;\n    });\n  }\n  static getMaxLevelByMediaSize(levels, width, height){\n    if(!(levels!=null&&levels.length)){\n      return -1;\n    }\n\n    // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next\n    // to determine whether we've chosen the greatest bandwidth for the media's dimensions\n    const atGreatestBandwidth=(curLevel, nextLevel)=> {\n      if(!nextLevel){\n        return true;\n      }\n      return curLevel.width!==nextLevel.width||curLevel.height!==nextLevel.height;\n    };\n\n    // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to\n    // the max level\n    let maxLevelIndex=levels.length - 1;\n    // Prevent changes in aspect-ratio from causing capping to toggle back and forth\n    const squareSize=Math.max(width, height);\n    for (let i=0; i < levels.length; i +=1){\n      const level=levels[i];\n      if((level.width >=squareSize||level.height >=squareSize)&&atGreatestBandwidth(level, levels[i + 1])){\n        maxLevelIndex=i;\n        break;\n      }\n    }\n    return maxLevelIndex;\n  }\n}\n\nclass FPSController {\n  constructor(hls){\n    this.hls=void 0;\n    this.isVideoPlaybackQualityAvailable=false;\n    this.timer=void 0;\n    this.media=null;\n    this.lastTime=void 0;\n    this.lastDroppedFrames=0;\n    this.lastDecodedFrames=0;\n    // stream controller must be provided as a dependency!\n    this.streamController=void 0;\n    this.hls=hls;\n    this.registerListeners();\n  }\n  setStreamController(streamController){\n    this.streamController=streamController;\n  }\n  registerListeners(){\n    this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n  }\n  unregisterListeners(){\n    this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);\n  }\n  destroy(){\n    if(this.timer){\n      clearInterval(this.timer);\n    }\n    this.unregisterListeners();\n    this.isVideoPlaybackQualityAvailable=false;\n    this.media=null;\n  }\n  onMediaAttaching(event, data){\n    const config=this.hls.config;\n    if(config.capLevelOnFPSDrop){\n      const media=data.media instanceof self.HTMLVideoElement ? data.media:null;\n      this.media=media;\n      if(media&&typeof media.getVideoPlaybackQuality==='function'){\n        this.isVideoPlaybackQualityAvailable=true;\n      }\n      self.clearInterval(this.timer);\n      this.timer=self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);\n    }\n  }\n  checkFPS(video, decodedFrames, droppedFrames){\n    const currentTime=performance.now();\n    if(decodedFrames){\n      if(this.lastTime){\n        const currentPeriod=currentTime - this.lastTime;\n        const currentDropped=droppedFrames - this.lastDroppedFrames;\n        const currentDecoded=decodedFrames - this.lastDecodedFrames;\n        const droppedFPS=1000 * currentDropped / currentPeriod;\n        const hls=this.hls;\n        hls.trigger(Events.FPS_DROP, {\n          currentDropped: currentDropped,\n          currentDecoded: currentDecoded,\n          totalDroppedFrames: droppedFrames\n        });\n        if(droppedFPS > 0){\n          // logger.log('checkFPS:droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));\n          if(currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded){\n            let currentLevel=hls.currentLevel;\n            logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);\n            if(currentLevel > 0&&(hls.autoLevelCapping===-1||hls.autoLevelCapping >=currentLevel)){\n              currentLevel=currentLevel - 1;\n              hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {\n                level: currentLevel,\n                droppedLevel: hls.currentLevel\n              });\n              hls.autoLevelCapping=currentLevel;\n              this.streamController.nextLevelSwitch();\n            }\n          }\n        }\n      }\n      this.lastTime=currentTime;\n      this.lastDroppedFrames=droppedFrames;\n      this.lastDecodedFrames=decodedFrames;\n    }\n  }\n  checkFPSInterval(){\n    const video=this.media;\n    if(video){\n      if(this.isVideoPlaybackQualityAvailable){\n        const videoPlaybackQuality=video.getVideoPlaybackQuality();\n        this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);\n      }else{\n        // HTMLVideoElement doesn't include the webkit types\n        this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount);\n      }\n    }\n  }\n}\n\nconst LOGGER_PREFIX='[eme]';\n\nclass EMEController {\n  constructor(hls){\n    this.hls=void 0;\n    this.config=void 0;\n    this.media=null;\n    this.keyFormatPromise=null;\n    this.keySystemAccessPromises={};\n    this._requestLicenseFailureCount=0;\n    this.mediaKeySessions=[];\n    this.keyIdToKeySessionPromise={};\n    this.setMediaKeysQueue=EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise]:[];\n    this.debug=logger.debug.bind(logger, LOGGER_PREFIX);\n    this.log=logger.log.bind(logger, LOGGER_PREFIX);\n    this.warn=logger.warn.bind(logger, LOGGER_PREFIX);\n    this.error=logger.error.bind(logger, LOGGER_PREFIX);\n    this.onMediaEncrypted=event=> {\n      const {\n        initDataType,\n        initData\n      }=event;\n      const logMessage=`\"${event.type}\" event: init data type: \"${initDataType}\"`;\n      this.debug(logMessage);\n\n      // Ignore event when initData is null\n      if(initData===null){\n        return;\n      }\n      if(!this.keyFormatPromise){\n        let keySystems=Object.keys(this.keySystemAccessPromises);\n        if(!keySystems.length){\n          keySystems=getKeySystemsForConfig(this.config);\n        }\n        const keyFormats=keySystems.map(keySystemDomainToKeySystemFormat).filter(k=> !!k);\n        this.keyFormatPromise=this.getKeyFormatPromise(keyFormats);\n      }\n      this.keyFormatPromise.then(keySystemFormat=> {\n        const keySystem=keySystemFormatToKeySystemDomain(keySystemFormat);\n        let keyId;\n        let keySystemDomain;\n        if(initDataType==='sinf'){\n          if(keySystem!==KeySystems.FAIRPLAY){\n            this.warn(`Ignoring unexpected \"${event.type}\" event with init data type: \"${initDataType}\" for selected key-system ${keySystem}`);\n            return;\n          }\n          // Match sinf keyId to playlist skd://keyId=\n          const json=bin2str(new Uint8Array(initData));\n          try {\n            const sinf=base64Decode(JSON.parse(json).sinf);\n            const tenc=parseSinf(sinf);\n            if(!tenc){\n              throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);\n            }\n            keyId=tenc.subarray(8, 24);\n            keySystemDomain=KeySystems.FAIRPLAY;\n          } catch (error){\n            this.warn(`${logMessage} Failed to parse sinf: ${error}`);\n            return;\n          }\n        }else{\n          if(keySystem!==KeySystems.WIDEVINE&&keySystem!==KeySystems.PLAYREADY){\n            this.warn(`Ignoring unexpected \"${event.type}\" event with init data type: \"${initDataType}\" for selected key-system ${keySystem}`);\n            return;\n          }\n          // Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)\n          const psshResults=parseMultiPssh(initData);\n          const psshInfos=psshResults.filter(pssh=> !!pssh.systemId&&keySystemIdToKeySystemDomain(pssh.systemId)===keySystem);\n          if(psshInfos.length > 1){\n            this.warn(`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`);\n          }\n          const psshInfo=psshInfos[0];\n          if(!psshInfo){\n            if(psshResults.length===0||psshResults.some(pssh=> !pssh.systemId)){\n              this.warn(`${logMessage} contains incomplete or invalid pssh data`);\n            }else{\n              this.log(`ignoring ${logMessage} for ${psshResults.map(pssh=> keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);\n            }\n            return;\n          }\n          keySystemDomain=keySystemIdToKeySystemDomain(psshInfo.systemId);\n          if(psshInfo.version===0&&psshInfo.data){\n            if(keySystemDomain===KeySystems.WIDEVINE){\n              const offset=psshInfo.data.length - 22;\n              keyId=psshInfo.data.subarray(offset, offset + 16);\n            }else if(keySystemDomain===KeySystems.PLAYREADY){\n              keyId=parsePlayReadyWRM(psshInfo.data);\n            }\n          }\n        }\n        if(!keySystemDomain||!keyId){\n          this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);\n          return;\n        }\n        const keyIdHex=Hex.hexDump(keyId);\n        const {\n          keyIdToKeySessionPromise,\n          mediaKeySessions\n        }=this;\n        let keySessionContextPromise=keyIdToKeySessionPromise[keyIdHex];\n        for (let i=0; i < mediaKeySessions.length; i++){\n          // Match playlist key\n          const keyContext=mediaKeySessions[i];\n          const decryptdata=keyContext.decryptdata;\n          if(!decryptdata.keyId){\n            continue;\n          }\n          const oldKeyIdHex=Hex.hexDump(decryptdata.keyId);\n          if(keyIdHex===oldKeyIdHex||decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex)!==-1){\n            keySessionContextPromise=keyIdToKeySessionPromise[oldKeyIdHex];\n            if(decryptdata.pssh){\n              break;\n            }\n            delete keyIdToKeySessionPromise[oldKeyIdHex];\n            decryptdata.pssh=new Uint8Array(initData);\n            decryptdata.keyId=keyId;\n            keySessionContextPromise=keyIdToKeySessionPromise[keyIdHex]=keySessionContextPromise.then(()=> {\n              return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');\n            });\n            keySessionContextPromise.catch(error=> this.handleError(error));\n            break;\n          }\n        }\n        if(!keySessionContextPromise){\n          if(keySystemDomain!==keySystem){\n            this.log(`Ignoring \"${logMessage}\" with ${keySystemDomain} init data for selected key-system ${keySystem}`);\n            return;\n          }\n          // \"Clear-lead\" (misc key not encountered in playlist)\n          keySessionContextPromise=keyIdToKeySessionPromise[keyIdHex]=this.getKeySystemSelectionPromise([keySystemDomain]).then(({\n            keySystem,\n            mediaKeys\n          })=> {\n            var _keySystemToKeySystem;\n            this.throwIfDestroyed();\n            const decryptdata=new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem=keySystemDomainToKeySystemFormat(keySystem))!=null ? _keySystemToKeySystem:'');\n            decryptdata.pssh=new Uint8Array(initData);\n            decryptdata.keyId=keyId;\n            return this.attemptSetMediaKeys(keySystem, mediaKeys).then(()=> {\n              this.throwIfDestroyed();\n              const keySessionContext=this.createMediaKeySessionContext({\n                decryptdata,\n                keySystem,\n                mediaKeys\n              });\n              return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');\n            });\n          });\n          keySessionContextPromise.catch(error=> this.handleError(error));\n        }\n      });\n    };\n    this.onWaitingForKey=event=> {\n      this.log(`\"${event.type}\" event`);\n    };\n    this.hls=hls;\n    this.config=hls.config;\n    this.registerListeners();\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.onMediaDetached();\n    // Remove any references that could be held in config options or callbacks\n    const config=this.config;\n    config.requestMediaKeySystemAccessFunc=null;\n    config.licenseXhrSetup=config.licenseResponseCallback=undefined;\n    config.drmSystems=config.drmSystemOptions={};\n    // @ts-ignore\n    this.hls=this.config=this.keyIdToKeySessionPromise=null;\n    // @ts-ignore\n    this.onMediaEncrypted=this.onWaitingForKey=null;\n  }\n  registerListeners(){\n    this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);\n    this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n  }\n  unregisterListeners(){\n    this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);\n    this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n  }\n  getLicenseServerUrl(keySystem){\n    const {\n      drmSystems,\n      widevineLicenseUrl\n    }=this.config;\n    const keySystemConfiguration=drmSystems[keySystem];\n    if(keySystemConfiguration){\n      return keySystemConfiguration.licenseUrl;\n    }\n\n    // For backward compatibility\n    if(keySystem===KeySystems.WIDEVINE&&widevineLicenseUrl){\n      return widevineLicenseUrl;\n    }\n  }\n  getLicenseServerUrlOrThrow(keySystem){\n    const url=this.getLicenseServerUrl(keySystem);\n    if(url===undefined){\n      throw new Error(`no license server URL configured for key-system \"${keySystem}\"`);\n    }\n    return url;\n  }\n  getServerCertificateUrl(keySystem){\n    const {\n      drmSystems\n    }=this.config;\n    const keySystemConfiguration=drmSystems[keySystem];\n    if(keySystemConfiguration){\n      return keySystemConfiguration.serverCertificateUrl;\n    }else{\n      this.log(`No Server Certificate in config.drmSystems[\"${keySystem}\"]`);\n    }\n  }\n  attemptKeySystemAccess(keySystemsToAttempt){\n    const levels=this.hls.levels;\n    const uniqueCodec=(value, i, a)=> !!value&&a.indexOf(value)===i;\n    const audioCodecs=levels.map(level=> level.audioCodec).filter(uniqueCodec);\n    const videoCodecs=levels.map(level=> level.videoCodec).filter(uniqueCodec);\n    if(audioCodecs.length + videoCodecs.length===0){\n      videoCodecs.push('avc1.42e01e');\n    }\n    return new Promise((resolve, reject)=> {\n      const attempt=keySystems=> {\n        const keySystem=keySystems.shift();\n        this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(mediaKeys=> resolve({\n          keySystem,\n          mediaKeys\n        })).catch(error=> {\n          if(keySystems.length){\n            attempt(keySystems);\n          }else if(error instanceof EMEKeyError){\n            reject(error);\n          }else{\n            reject(new EMEKeyError({\n              type: ErrorTypes.KEY_SYSTEM_ERROR,\n              details: ErrorDetails.KEY_SYSTEM_NO_ACCESS,\n              error,\n              fatal: true\n            }, error.message));\n          }\n        });\n      };\n      attempt(keySystemsToAttempt);\n    });\n  }\n  requestMediaKeySystemAccess(keySystem, supportedConfigurations){\n    const {\n      requestMediaKeySystemAccessFunc\n    }=this.config;\n    if(!(typeof requestMediaKeySystemAccessFunc==='function')){\n      let errMessage=`Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`;\n      if(requestMediaKeySystemAccess===null&&self.location.protocol==='http:'){\n        errMessage=`navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`;\n      }\n      return Promise.reject(new Error(errMessage));\n    }\n    return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations);\n  }\n  getMediaKeysPromise(keySystem, audioCodecs, videoCodecs){\n    // This can throw, but is caught in event handler callpath\n    const mediaKeySystemConfigs=getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions);\n    const keySystemAccessPromises=this.keySystemAccessPromises[keySystem];\n    let keySystemAccess=keySystemAccessPromises==null ? void 0:keySystemAccessPromises.keySystemAccess;\n    if(!keySystemAccess){\n      this.log(`Requesting encrypted media \"${keySystem}\" key-system access with config: ${JSON.stringify(mediaKeySystemConfigs)}`);\n      keySystemAccess=this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs);\n      const _keySystemAccessPromises=this.keySystemAccessPromises[keySystem]={\n        keySystemAccess\n      };\n      keySystemAccess.catch(error=> {\n        this.log(`Failed to obtain access to key-system \"${keySystem}\": ${error}`);\n      });\n      return keySystemAccess.then(mediaKeySystemAccess=> {\n        this.log(`Access for key-system \"${mediaKeySystemAccess.keySystem}\" obtained`);\n        const certificateRequest=this.fetchServerCertificate(keySystem);\n        this.log(`Create media-keys for \"${keySystem}\"`);\n        _keySystemAccessPromises.mediaKeys=mediaKeySystemAccess.createMediaKeys().then(mediaKeys=> {\n          this.log(`Media-keys created for \"${keySystem}\"`);\n          return certificateRequest.then(certificate=> {\n            if(certificate){\n              return this.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate);\n            }\n            return mediaKeys;\n          });\n        });\n        _keySystemAccessPromises.mediaKeys.catch(error=> {\n          this.error(`Failed to create media-keys for \"${keySystem}\"}: ${error}`);\n        });\n        return _keySystemAccessPromises.mediaKeys;\n      });\n    }\n    return keySystemAccess.then(()=> keySystemAccessPromises.mediaKeys);\n  }\n  createMediaKeySessionContext({\n    decryptdata,\n    keySystem,\n    mediaKeys\n  }){\n    this.log(`Creating key-system session \"${keySystem}\" keyId: ${Hex.hexDump(decryptdata.keyId||[])}`);\n    const mediaKeysSession=mediaKeys.createSession();\n    const mediaKeySessionContext={\n      decryptdata,\n      keySystem,\n      mediaKeys,\n      mediaKeysSession,\n      keyStatus: 'status-pending'\n    };\n    this.mediaKeySessions.push(mediaKeySessionContext);\n    return mediaKeySessionContext;\n  }\n  renewKeySession(mediaKeySessionContext){\n    const decryptdata=mediaKeySessionContext.decryptdata;\n    if(decryptdata.pssh){\n      const keySessionContext=this.createMediaKeySessionContext(mediaKeySessionContext);\n      const keyId=this.getKeyIdString(decryptdata);\n      const scheme='cenc';\n      this.keyIdToKeySessionPromise[keyId]=this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'expired');\n    }else{\n      this.warn(`Could not renew expired session. Missing pssh initData.`);\n    }\n    this.removeSession(mediaKeySessionContext);\n  }\n  getKeyIdString(decryptdata){\n    if(!decryptdata){\n      throw new Error('Could not read keyId of undefined decryptdata');\n    }\n    if(decryptdata.keyId===null){\n      throw new Error('keyId is null');\n    }\n    return Hex.hexDump(decryptdata.keyId);\n  }\n  updateKeySession(mediaKeySessionContext, data){\n    var _mediaKeySessionConte;\n    const keySession=mediaKeySessionContext.mediaKeysSession;\n    this.log(`Updating key-session \"${keySession.sessionId}\" for keyID ${Hex.hexDump(((_mediaKeySessionConte=mediaKeySessionContext.decryptdata)==null ? void 0:_mediaKeySessionConte.keyId)||[])}\n      } (data length: ${data ? data.byteLength:data})`);\n    return keySession.update(data);\n  }\n  selectKeySystemFormat(frag){\n    const keyFormats=Object.keys(frag.levelkeys||{});\n    if(!this.keyFormatPromise){\n      this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(', ')}`);\n      this.keyFormatPromise=this.getKeyFormatPromise(keyFormats);\n    }\n    return this.keyFormatPromise;\n  }\n  getKeyFormatPromise(keyFormats){\n    return new Promise((resolve, reject)=> {\n      const keySystemsInConfig=getKeySystemsForConfig(this.config);\n      const keySystemsToAttempt=keyFormats.map(keySystemFormatToKeySystemDomain).filter(value=> !!value&&keySystemsInConfig.indexOf(value)!==-1);\n      return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({\n        keySystem\n      })=> {\n        const keySystemFormat=keySystemDomainToKeySystemFormat(keySystem);\n        if(keySystemFormat){\n          resolve(keySystemFormat);\n        }else{\n          reject(new Error(`Unable to find format for key-system \"${keySystem}\"`));\n        }\n      }).catch(reject);\n    });\n  }\n  loadKey(data){\n    const decryptdata=data.keyInfo.decryptdata;\n    const keyId=this.getKeyIdString(decryptdata);\n    const keyDetails=`(keyId: ${keyId} format: \"${decryptdata.keyFormat}\" method: ${decryptdata.method} uri: ${decryptdata.uri})`;\n    this.log(`Starting session for key ${keyDetails}`);\n    let keySessionContextPromise=this.keyIdToKeySessionPromise[keyId];\n    if(!keySessionContextPromise){\n      keySessionContextPromise=this.keyIdToKeySessionPromise[keyId]=this.getKeySystemForKeyPromise(decryptdata).then(({\n        keySystem,\n        mediaKeys\n      })=> {\n        this.throwIfDestroyed();\n        this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`);\n        return this.attemptSetMediaKeys(keySystem, mediaKeys).then(()=> {\n          this.throwIfDestroyed();\n          const keySessionContext=this.createMediaKeySessionContext({\n            keySystem,\n            mediaKeys,\n            decryptdata\n          });\n          const scheme='cenc';\n          return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'playlist-key');\n        });\n      });\n      keySessionContextPromise.catch(error=> this.handleError(error));\n    }\n    return keySessionContextPromise;\n  }\n  throwIfDestroyed(message='Invalid state'){\n    if(!this.hls){\n      throw new Error('invalid state');\n    }\n  }\n  handleError(error){\n    if(!this.hls){\n      return;\n    }\n    this.error(error.message);\n    if(error instanceof EMEKeyError){\n      this.hls.trigger(Events.ERROR, error.data);\n    }else{\n      this.hls.trigger(Events.ERROR, {\n        type: ErrorTypes.KEY_SYSTEM_ERROR,\n        details: ErrorDetails.KEY_SYSTEM_NO_KEYS,\n        error,\n        fatal: true\n      });\n    }\n  }\n  getKeySystemForKeyPromise(decryptdata){\n    const keyId=this.getKeyIdString(decryptdata);\n    const mediaKeySessionContext=this.keyIdToKeySessionPromise[keyId];\n    if(!mediaKeySessionContext){\n      const keySystem=keySystemFormatToKeySystemDomain(decryptdata.keyFormat);\n      const keySystemsToAttempt=keySystem ? [keySystem]:getKeySystemsForConfig(this.config);\n      return this.attemptKeySystemAccess(keySystemsToAttempt);\n    }\n    return mediaKeySessionContext;\n  }\n  getKeySystemSelectionPromise(keySystemsToAttempt){\n    if(!keySystemsToAttempt.length){\n      keySystemsToAttempt=getKeySystemsForConfig(this.config);\n    }\n    if(keySystemsToAttempt.length===0){\n      throw new EMEKeyError({\n        type: ErrorTypes.KEY_SYSTEM_ERROR,\n        details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE,\n        fatal: true\n      }, `Missing key-system license configuration options ${JSON.stringify({\n        drmSystems: this.config.drmSystems\n      })}`);\n    }\n    return this.attemptKeySystemAccess(keySystemsToAttempt);\n  }\n  attemptSetMediaKeys(keySystem, mediaKeys){\n    const queue=this.setMediaKeysQueue.slice();\n    this.log(`Setting media-keys for \"${keySystem}\"`);\n    // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations\n    // can be queued for execution for multiple key sessions.\n    const setMediaKeysPromise=Promise.all(queue).then(()=> {\n      if(!this.media){\n        throw new Error('Attempted to set mediaKeys without media element attached');\n      }\n      return this.media.setMediaKeys(mediaKeys);\n    });\n    this.setMediaKeysQueue.push(setMediaKeysPromise);\n    return setMediaKeysPromise.then(()=> {\n      this.log(`Media-keys set for \"${keySystem}\"`);\n      queue.push(setMediaKeysPromise);\n      this.setMediaKeysQueue=this.setMediaKeysQueue.filter(p=> queue.indexOf(p)===-1);\n    });\n  }\n  generateRequestWithPreferredKeySession(context, initDataType, initData, reason){\n    var _this$config$drmSyste, _this$config$drmSyste2;\n    const generateRequestFilter=(_this$config$drmSyste=this.config.drmSystems)==null ? void 0:(_this$config$drmSyste2=_this$config$drmSyste[context.keySystem])==null ? void 0:_this$config$drmSyste2.generateRequest;\n    if(generateRequestFilter){\n      try {\n        const mappedInitData=generateRequestFilter.call(this.hls, initDataType, initData, context);\n        if(!mappedInitData){\n          throw new Error('Invalid response from configured generateRequest filter');\n        }\n        initDataType=mappedInitData.initDataType;\n        initData=context.decryptdata.pssh=mappedInitData.initData ? new Uint8Array(mappedInitData.initData):null;\n      } catch (error){\n        var _this$hls;\n        this.warn(error.message);\n        if((_this$hls=this.hls)!=null&&_this$hls.config.debug){\n          throw error;\n        }\n      }\n    }\n    if(initData===null){\n      this.log(`Skipping key-session request for \"${reason}\" (no initData)`);\n      return Promise.resolve(context);\n    }\n    const keyId=this.getKeyIdString(context.decryptdata);\n    this.log(`Generating key-session request for \"${reason}\": ${keyId} (init data type: ${initDataType} length: ${initData ? initData.byteLength:null})`);\n    const licenseStatus=new EventEmitter();\n    const onmessage=context._onmessage=event=> {\n      const keySession=context.mediaKeysSession;\n      if(!keySession){\n        licenseStatus.emit('error', new Error('invalid state'));\n        return;\n      }\n      const {\n        messageType,\n        message\n      }=event;\n      this.log(`\"${messageType}\" message event for session \"${keySession.sessionId}\" message size: ${message.byteLength}`);\n      if(messageType==='license-request'||messageType==='license-renewal'){\n        this.renewLicense(context, message).catch(error=> {\n          this.handleError(error);\n          licenseStatus.emit('error', error);\n        });\n      }else if(messageType==='license-release'){\n        if(context.keySystem===KeySystems.FAIRPLAY){\n          this.updateKeySession(context, strToUtf8array('acknowledged'));\n          this.removeSession(context);\n        }\n      }else{\n        this.warn(`unhandled media key message type \"${messageType}\"`);\n      }\n    };\n    const onkeystatuseschange=context._onkeystatuseschange=event=> {\n      const keySession=context.mediaKeysSession;\n      if(!keySession){\n        licenseStatus.emit('error', new Error('invalid state'));\n        return;\n      }\n      this.onKeyStatusChange(context);\n      const keyStatus=context.keyStatus;\n      licenseStatus.emit('keyStatus', keyStatus);\n      if(keyStatus==='expired'){\n        this.warn(`${context.keySystem} expired for key ${keyId}`);\n        this.renewKeySession(context);\n      }\n    };\n    context.mediaKeysSession.addEventListener('message', onmessage);\n    context.mediaKeysSession.addEventListener('keystatuseschange', onkeystatuseschange);\n    const keyUsablePromise=new Promise((resolve, reject)=> {\n      licenseStatus.on('error', reject);\n      licenseStatus.on('keyStatus', keyStatus=> {\n        if(keyStatus.startsWith('usable')){\n          resolve();\n        }else if(keyStatus==='output-restricted'){\n          reject(new EMEKeyError({\n            type: ErrorTypes.KEY_SYSTEM_ERROR,\n            details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED,\n            fatal: false\n          }, 'HDCP level output restricted'));\n        }else if(keyStatus==='internal-error'){\n          reject(new EMEKeyError({\n            type: ErrorTypes.KEY_SYSTEM_ERROR,\n            details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR,\n            fatal: true\n          }, `key status changed to \"${keyStatus}\"`));\n        }else if(keyStatus==='expired'){\n          reject(new Error('key expired while generating request'));\n        }else{\n          this.warn(`unhandled key status change \"${keyStatus}\"`);\n        }\n      });\n    });\n    return context.mediaKeysSession.generateRequest(initDataType, initData).then(()=> {\n      var _context$mediaKeysSes;\n      this.log(`Request generated for key-session \"${(_context$mediaKeysSes=context.mediaKeysSession)==null ? void 0:_context$mediaKeysSes.sessionId}\" keyId: ${keyId}`);\n    }).catch(error=> {\n      throw new EMEKeyError({\n        type: ErrorTypes.KEY_SYSTEM_ERROR,\n        details: ErrorDetails.KEY_SYSTEM_NO_SESSION,\n        error,\n        fatal: false\n      }, `Error generating key-session request: ${error}`);\n    }).then(()=> keyUsablePromise).catch(error=> {\n      licenseStatus.removeAllListeners();\n      this.removeSession(context);\n      throw error;\n    }).then(()=> {\n      licenseStatus.removeAllListeners();\n      return context;\n    });\n  }\n  onKeyStatusChange(mediaKeySessionContext){\n    mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status, keyId)=> {\n      this.log(`key status change \"${status}\" for keyStatuses keyId: ${Hex.hexDump('buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength):new Uint8Array(keyId))} session keyId: ${Hex.hexDump(new Uint8Array(mediaKeySessionContext.decryptdata.keyId||[]))} uri: ${mediaKeySessionContext.decryptdata.uri}`);\n      mediaKeySessionContext.keyStatus=status;\n    });\n  }\n  fetchServerCertificate(keySystem){\n    const config=this.config;\n    const Loader=config.loader;\n    const certLoader=new Loader(config);\n    const url=this.getServerCertificateUrl(keySystem);\n    if(!url){\n      return Promise.resolve();\n    }\n    this.log(`Fetching server certificate for \"${keySystem}\"`);\n    return new Promise((resolve, reject)=> {\n      const loaderContext={\n        responseType: 'arraybuffer',\n        url\n      };\n      const loadPolicy=config.certLoadPolicy.default;\n      const loaderConfig={\n        loadPolicy,\n        timeout: loadPolicy.maxLoadTimeMs,\n        maxRetry: 0,\n        retryDelay: 0,\n        maxRetryDelay: 0\n      };\n      const loaderCallbacks={\n        onSuccess: (response, stats, context, networkDetails)=> {\n          resolve(response.data);\n        },\n        onError: (response, contex, networkDetails, stats)=> {\n          reject(new EMEKeyError({\n            type: ErrorTypes.KEY_SYSTEM_ERROR,\n            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,\n            fatal: true,\n            networkDetails,\n            response: _objectSpread2({\n              url: loaderContext.url,\n              data: undefined\n            }, response)\n          }, `\"${keySystem}\" certificate request failed (${url}). Status: ${response.code} (${response.text})`));\n        },\n        onTimeout: (stats, context, networkDetails)=> {\n          reject(new EMEKeyError({\n            type: ErrorTypes.KEY_SYSTEM_ERROR,\n            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,\n            fatal: true,\n            networkDetails,\n            response: {\n              url: loaderContext.url,\n              data: undefined\n            }\n          }, `\"${keySystem}\" certificate request timed out (${url})`));\n        },\n        onAbort: (stats, context, networkDetails)=> {\n          reject(new Error('aborted'));\n        }\n      };\n      certLoader.load(loaderContext, loaderConfig, loaderCallbacks);\n    });\n  }\n  setMediaKeysServerCertificate(mediaKeys, keySystem, cert){\n    return new Promise((resolve, reject)=> {\n      mediaKeys.setServerCertificate(cert).then(success=> {\n        this.log(`setServerCertificate ${success ? 'success':'not supported by CDM'} (${cert==null ? void 0:cert.byteLength}) on \"${keySystem}\"`);\n        resolve(mediaKeys);\n      }).catch(error=> {\n        reject(new EMEKeyError({\n          type: ErrorTypes.KEY_SYSTEM_ERROR,\n          details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,\n          error,\n          fatal: true\n        }, error.message));\n      });\n    });\n  }\n  renewLicense(context, keyMessage){\n    return this.requestLicense(context, new Uint8Array(keyMessage)).then(data=> {\n      return this.updateKeySession(context, new Uint8Array(data)).catch(error=> {\n        throw new EMEKeyError({\n          type: ErrorTypes.KEY_SYSTEM_ERROR,\n          details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED,\n          error,\n          fatal: true\n        }, error.message);\n      });\n    });\n  }\n  unpackPlayReadyKeyMessage(xhr, licenseChallenge){\n    // On Edge, the raw license message is UTF-16-encoded XML.  We need\n    // to unpack the Challenge element (base64-encoded string containing the\n    // actual license request) and any HttpHeader elements (sent as request\n    // headers).\n    // For PlayReady CDMs, we need to dig the Challenge out of the XML.\n    const xmlString=String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer));\n    if(!xmlString.includes('PlayReadyKeyMessage')){\n      // This does not appear to be a wrapped message as on Edge.  Some\n      // clients do not need this unwrapping, so we will assume this is one of\n      // them.  Note that \"xml\" at this point probably looks like random\n      // garbage, since we interpreted UTF-8 as UTF-16.\n      xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8');\n      return licenseChallenge;\n    }\n    const keyMessageXml=new DOMParser().parseFromString(xmlString, 'application/xml');\n    // Set request headers.\n    const headers=keyMessageXml.querySelectorAll('HttpHeader');\n    if(headers.length > 0){\n      let header;\n      for (let i=0, len=headers.length; i < len; i++){\n        var _header$querySelector, _header$querySelector2;\n        header=headers[i];\n        const name=(_header$querySelector=header.querySelector('name'))==null ? void 0:_header$querySelector.textContent;\n        const value=(_header$querySelector2=header.querySelector('value'))==null ? void 0:_header$querySelector2.textContent;\n        if(name&&value){\n          xhr.setRequestHeader(name, value);\n        }\n      }\n    }\n    const challengeElement=keyMessageXml.querySelector('Challenge');\n    const challengeText=challengeElement==null ? void 0:challengeElement.textContent;\n    if(!challengeText){\n      throw new Error(`Cannot find <Challenge> in key message`);\n    }\n    return strToUtf8array(atob(challengeText));\n  }\n  setupLicenseXHR(xhr, url, keysListItem, licenseChallenge){\n    const licenseXhrSetup=this.config.licenseXhrSetup;\n    if(!licenseXhrSetup){\n      xhr.open('POST', url, true);\n      return Promise.resolve({\n        xhr,\n        licenseChallenge\n      });\n    }\n    return Promise.resolve().then(()=> {\n      if(!keysListItem.decryptdata){\n        throw new Error('Key removed');\n      }\n      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);\n    }).catch(error=> {\n      if(!keysListItem.decryptdata){\n        // Key session removed. Cancel license request.\n        throw error;\n      }\n      // let's try to open before running setup\n      xhr.open('POST', url, true);\n      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);\n    }).then(licenseXhrSetupResult=> {\n      // if licenseXhrSetup did not yet call open, let's do it now\n      if(!xhr.readyState){\n        xhr.open('POST', url, true);\n      }\n      const finalLicenseChallenge=licenseXhrSetupResult ? licenseXhrSetupResult:licenseChallenge;\n      return {\n        xhr,\n        licenseChallenge: finalLicenseChallenge\n      };\n    });\n  }\n  requestLicense(keySessionContext, licenseChallenge){\n    const keyLoadPolicy=this.config.keyLoadPolicy.default;\n    return new Promise((resolve, reject)=> {\n      const url=this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);\n      this.log(`Sending license request to URL: ${url}`);\n      const xhr=new XMLHttpRequest();\n      xhr.responseType='arraybuffer';\n      xhr.onreadystatechange=()=> {\n        if(!this.hls||!keySessionContext.mediaKeysSession){\n          return reject(new Error('invalid state'));\n        }\n        if(xhr.readyState===4){\n          if(xhr.status===200){\n            this._requestLicenseFailureCount=0;\n            let data=xhr.response;\n            this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength:data}`);\n            const licenseResponseCallback=this.config.licenseResponseCallback;\n            if(licenseResponseCallback){\n              try {\n                data=licenseResponseCallback.call(this.hls, xhr, url, keySessionContext);\n              } catch (error){\n                this.error(error);\n              }\n            }\n            resolve(data);\n          }else{\n            const retryConfig=keyLoadPolicy.errorRetry;\n            const maxNumRetry=retryConfig ? retryConfig.maxNumRetry:0;\n            this._requestLicenseFailureCount++;\n            if(this._requestLicenseFailureCount > maxNumRetry||xhr.status >=400&&xhr.status < 500){\n              reject(new EMEKeyError({\n                type: ErrorTypes.KEY_SYSTEM_ERROR,\n                details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED,\n                fatal: true,\n                networkDetails: xhr,\n                response: {\n                  url,\n                  data: undefined,\n                  code: xhr.status,\n                  text: xhr.statusText\n                }\n              }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`));\n            }else{\n              const attemptsLeft=maxNumRetry - this._requestLicenseFailureCount + 1;\n              this.warn(`Retrying license request, ${attemptsLeft} attempts left`);\n              this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject);\n            }\n          }\n        }\n      };\n      if(keySessionContext.licenseXhr&&keySessionContext.licenseXhr.readyState!==XMLHttpRequest.DONE){\n        keySessionContext.licenseXhr.abort();\n      }\n      keySessionContext.licenseXhr=xhr;\n      this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({\n        xhr,\n        licenseChallenge\n      })=> {\n        if(keySessionContext.keySystem==KeySystems.PLAYREADY){\n          licenseChallenge=this.unpackPlayReadyKeyMessage(xhr, licenseChallenge);\n        }\n        xhr.send(licenseChallenge);\n      });\n    });\n  }\n  onMediaAttached(event, data){\n    if(!this.config.emeEnabled){\n      return;\n    }\n    const media=data.media;\n\n    // keep reference of media\n    this.media=media;\n    media.removeEventListener('encrypted', this.onMediaEncrypted);\n    media.removeEventListener('waitingforkey', this.onWaitingForKey);\n    media.addEventListener('encrypted', this.onMediaEncrypted);\n    media.addEventListener('waitingforkey', this.onWaitingForKey);\n  }\n  onMediaDetached(){\n    const media=this.media;\n    const mediaKeysList=this.mediaKeySessions;\n    if(media){\n      media.removeEventListener('encrypted', this.onMediaEncrypted);\n      media.removeEventListener('waitingforkey', this.onWaitingForKey);\n      this.media=null;\n    }\n    this._requestLicenseFailureCount=0;\n    this.setMediaKeysQueue=[];\n    this.mediaKeySessions=[];\n    this.keyIdToKeySessionPromise={};\n    LevelKey.clearKeyUriToKeyIdMap();\n\n    // Close all sessions and remove media keys from the video element.\n    const keySessionCount=mediaKeysList.length;\n    EMEController.CDMCleanupPromise=Promise.all(mediaKeysList.map(mediaKeySessionContext=> this.removeSession(mediaKeySessionContext)).concat(media==null ? void 0:media.setMediaKeys(null).catch(error=> {\n      this.log(`Could not clear media keys: ${error}`);\n    }))).then(()=> {\n      if(keySessionCount){\n        this.log('finished closing key sessions and clearing media keys');\n        mediaKeysList.length=0;\n      }\n    }).catch(error=> {\n      this.log(`Could not close sessions and clear media keys: ${error}`);\n    });\n  }\n  onManifestLoading(){\n    this.keyFormatPromise=null;\n  }\n  onManifestLoaded(event, {\n    sessionKeys\n  }){\n    if(!sessionKeys||!this.config.emeEnabled){\n      return;\n    }\n    if(!this.keyFormatPromise){\n      const keyFormats=sessionKeys.reduce((formats, sessionKey)=> {\n        if(formats.indexOf(sessionKey.keyFormat)===-1){\n          formats.push(sessionKey.keyFormat);\n        }\n        return formats;\n      }, []);\n      this.log(`Selecting key-system from session-keys ${keyFormats.join(', ')}`);\n      this.keyFormatPromise=this.getKeyFormatPromise(keyFormats);\n    }\n  }\n  removeSession(mediaKeySessionContext){\n    const {\n      mediaKeysSession,\n      licenseXhr\n    }=mediaKeySessionContext;\n    if(mediaKeysSession){\n      this.log(`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`);\n      if(mediaKeySessionContext._onmessage){\n        mediaKeysSession.removeEventListener('message', mediaKeySessionContext._onmessage);\n        mediaKeySessionContext._onmessage=undefined;\n      }\n      if(mediaKeySessionContext._onkeystatuseschange){\n        mediaKeysSession.removeEventListener('keystatuseschange', mediaKeySessionContext._onkeystatuseschange);\n        mediaKeySessionContext._onkeystatuseschange=undefined;\n      }\n      if(licenseXhr&&licenseXhr.readyState!==XMLHttpRequest.DONE){\n        licenseXhr.abort();\n      }\n      mediaKeySessionContext.mediaKeysSession=mediaKeySessionContext.decryptdata=mediaKeySessionContext.licenseXhr=undefined;\n      const index=this.mediaKeySessions.indexOf(mediaKeySessionContext);\n      if(index > -1){\n        this.mediaKeySessions.splice(index, 1);\n      }\n      return mediaKeysSession.remove().catch(error=> {\n        this.log(`Could not remove session: ${error}`);\n      }).then(()=> {\n        return mediaKeysSession.close();\n      }).catch(error=> {\n        this.log(`Could not close session: ${error}`);\n      });\n    }\n  }\n}\nEMEController.CDMCleanupPromise=void 0;\nclass EMEKeyError extends Error {\n  constructor(data, message){\n    super(message);\n    this.data=void 0;\n    data.error||(data.error=new Error(message));\n    this.data=data;\n    data.err=data.error;\n  }\n}\n\n\nvar CmObjectType;\n(function (CmObjectType){\n  \n  CmObjectType[\"MANIFEST\"]=\"m\";\n  \n  CmObjectType[\"AUDIO\"]=\"a\";\n  \n  CmObjectType[\"VIDEO\"]=\"v\";\n  \n  CmObjectType[\"MUXED\"]=\"av\";\n  \n  CmObjectType[\"INIT\"]=\"i\";\n  \n  CmObjectType[\"CAPTION\"]=\"c\";\n  \n  CmObjectType[\"TIMED_TEXT\"]=\"tt\";\n  \n  CmObjectType[\"KEY\"]=\"k\";\n  \n  CmObjectType[\"OTHER\"]=\"o\";\n})(CmObjectType||(CmObjectType={}));\n\n\nvar CmStreamingFormat;\n(function (CmStreamingFormat){\n  \n  CmStreamingFormat[\"DASH\"]=\"d\";\n  \n  CmStreamingFormat[\"HLS\"]=\"h\";\n  \n  CmStreamingFormat[\"SMOOTH\"]=\"s\";\n  \n  CmStreamingFormat[\"OTHER\"]=\"o\";\n})(CmStreamingFormat||(CmStreamingFormat={}));\n\n\nvar CmcdHeaderField;\n(function (CmcdHeaderField){\n  \n  CmcdHeaderField[\"OBJECT\"]=\"CMCD-Object\";\n  \n  CmcdHeaderField[\"REQUEST\"]=\"CMCD-Request\";\n  \n  CmcdHeaderField[\"SESSION\"]=\"CMCD-Session\";\n  \n  CmcdHeaderField[\"STATUS\"]=\"CMCD-Status\";\n})(CmcdHeaderField||(CmcdHeaderField={}));\n\n\nconst CmcdHeaderMap={\n  [CmcdHeaderField.OBJECT]: ['br', 'd', 'ot', 'tb'],\n  [CmcdHeaderField.REQUEST]: ['bl', 'dl', 'mtp', 'nor', 'nrr', 'su'],\n  [CmcdHeaderField.SESSION]: ['cid', 'pr', 'sf', 'sid', 'st', 'v'],\n  [CmcdHeaderField.STATUS]: ['bs', 'rtp']\n};\n\n\nclass SfItem {\n  constructor(value, params){\n    this.value=void 0;\n    this.params=void 0;\n    if(Array.isArray(value)){\n      value=value.map(v=> v instanceof SfItem ? v:new SfItem(v));\n    }\n    this.value=value;\n    this.params=params;\n  }\n}\n\n\nclass SfToken {\n  constructor(description){\n    this.description=void 0;\n    this.description=description;\n  }\n}\n\nconst DICT='Dict';\n\nfunction format(value){\n  if(Array.isArray(value)){\n    return JSON.stringify(value);\n  }\n  if(value instanceof Map){\n    return 'Map{}';\n  }\n  if(value instanceof Set){\n    return 'Set{}';\n  }\n  if(typeof value==='object'){\n    return JSON.stringify(value);\n  }\n  return String(value);\n}\nfunction throwError(action, src, type, cause){\n  return new Error(`failed to ${action} \"${format(src)}\" as ${type}`, {\n    cause\n  });\n}\n\nconst BARE_ITEM='Bare Item';\n\nconst BOOLEAN='Boolean';\n\nconst BYTES='Byte Sequence';\n\nconst DECIMAL='Decimal';\n\nconst INTEGER='Integer';\n\nfunction isInvalidInt(value){\n  return value < -999999999999999||999999999999999 < value;\n}\n\nconst STRING_REGEX=/[\\x00-\\x1f\\x7f]+/; // eslint-disable-line no-control-regex\n\nconst TOKEN='Token';\n\nconst KEY='Key';\n\nfunction serializeError(src, type, cause){\n  return throwError('serialize', src, type, cause);\n}\n\n// 4.1.9.  Serializing a Boolean\n//\n// Given a Boolean as input_boolean, return an ASCII string suitable for\n// use in a HTTP field value.\n//\n// 1.  If input_boolean is not a boolean, fail serialization.\n//\n// 2.  Let output be an empty string.\n//\n// 3.  Append \"?\" to output.\n//\n// 4.  If input_boolean is true, append \"1\" to output.\n//\n// 5.  If input_boolean is false, append \"0\" to output.\n//\n// 6.  Return output.\nfunction serializeBoolean(value){\n  if(typeof value!=='boolean'){\n    throw serializeError(value, BOOLEAN);\n  }\n  return value ? '?1':'?0';\n}\n\n\nfunction base64encode(binary){\n  return btoa(String.fromCharCode(...binary));\n}\n\n// 4.1.8.  Serializing a Byte Sequence\n//\n// Given a Byte Sequence as input_bytes, return an ASCII string suitable\n// for use in a HTTP field value.\n//\n// 1.  If input_bytes is not a sequence of bytes, fail serialization.\n//\n// 2.  Let output be an empty string.\n//\n// 3.  Append \":\" to output.\n//\n// 4.  Append the result of base64-encoding input_bytes as per\n//     [RFC4648], Section 4, taking account of the requirements below.\n//\n// 5.  Append \":\" to output.\n//\n// 6.  Return output.\n//\n// The encoded data is required to be padded with \"=\", as per [RFC4648],\n// Section 3.2.\n//\n// Likewise, encoded data SHOULD have pad bits set to zero, as per\n// [RFC4648], Section 3.5, unless it is not possible to do so due to\n// implementation constraints.\nfunction serializeByteSequence(value){\n  if(ArrayBuffer.isView(value)===false){\n    throw serializeError(value, BYTES);\n  }\n  return `:${base64encode(value)}:`;\n}\n\n// 4.1.4.  Serializing an Integer\n//\n// Given an Integer as input_integer, return an ASCII string suitable\n// for use in a HTTP field value.\n//\n// 1.  If input_integer is not an integer in the range of\n//     -999,999,999,999,999 to 999,999,999,999,999 inclusive, fail\n//     serialization.\n//\n// 2.  Let output be an empty string.\n//\n// 3.  If input_integer is less than (but not equal to) 0, append \"-\" to\n//     output.\n//\n// 4.  Append input_integer's numeric value represented in base 10 using\n//     only decimal digits to output.\n//\n// 5.  Return output.\nfunction serializeInteger(value){\n  if(isInvalidInt(value)){\n    throw serializeError(value, INTEGER);\n  }\n  return value.toString();\n}\n\n// 4.1.10.  Serializing a Date\n//\n// Given a Date as input_integer, return an ASCII string suitable for\n// use in an HTTP field value.\n// 1.  Let output be \"@\".\n// 2.  Append to output the result of running Serializing an Integer\n//     with input_date (Section 4.1.4).\n// 3.  Return output.\nfunction serializeDate(value){\n  return `@${serializeInteger(value.getTime() / 1000)}`;\n}\n\n\nfunction roundToEven(value, precision){\n  if(value < 0){\n    return -roundToEven(-value, precision);\n  }\n  const decimalShift=Math.pow(10, precision);\n  const isEquidistant=Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON;\n  if(isEquidistant){\n    // If the tail of the decimal place is 'equidistant' we round to the nearest even value\n    const flooredValue=Math.floor(value * decimalShift);\n    return (flooredValue % 2===0 ? flooredValue:flooredValue + 1) / decimalShift;\n  }else{\n    // Otherwise, proceed as normal\n    return Math.round(value * decimalShift) / decimalShift;\n  }\n}\n\n// 4.1.5.  Serializing a Decimal\n//\n// Given a decimal number as input_decimal, return an ASCII string\n// suitable for use in a HTTP field value.\n//\n// 1.   If input_decimal is not a decimal number, fail serialization.\n//\n// 2.   If input_decimal has more than three significant digits to the\n//      right of the decimal point, round it to three decimal places,\n//      rounding the final digit to the nearest value, or to the even\n//      value if it is equidistant.\n//\n// 3.   If input_decimal has more than 12 significant digits to the left\n//      of the decimal point after rounding, fail serialization.\n//\n// 4.   Let output be an empty string.\n//\n// 5.   If input_decimal is less than (but not equal to) 0, append \"-\"\n//      to output.\n//\n// 6.   Append input_decimal's integer component represented in base 10\n//      (using only decimal digits) to output; if it is zero, append\n//      \"0\".\n//\n// 7.   Append \".\" to output.\n//\n// 8.   If input_decimal's fractional component is zero, append \"0\" to\n//      output.\n//\n// 9.   Otherwise, append the significant digits of input_decimal's\n//      fractional component represented in base 10 (using only decimal\n//      digits) to output.\n//\n// 10.  Return output.\nfunction serializeDecimal(value){\n  const roundedValue=roundToEven(value, 3); // round to 3 decimal places\n  if(Math.floor(Math.abs(roundedValue)).toString().length > 12){\n    throw serializeError(value, DECIMAL);\n  }\n  const stringValue=roundedValue.toString();\n  return stringValue.includes('.') ? stringValue:`${stringValue}.0`;\n}\n\nconst STRING='String';\n\n// 4.1.6.  Serializing a String\n//\n// Given a String as input_string, return an ASCII string suitable for\n// use in a HTTP field value.\n//\n// 1.  Convert input_string into a sequence of ASCII characters; if\n//     conversion fails, fail serialization.\n//\n// 2.  If input_string contains characters in the range %x00-1f or %x7f\n//     (i.e., not in VCHAR or SP), fail serialization.\n//\n// 3.  Let output be the string DQUOTE.\n//\n// 4.  For each character char in input_string:\n//\n//     1.  If char is \"\\\" or DQUOTE:\n//\n//         1.  Append \"\\\" to output.\n//\n//     2.  Append char to output.\n//\n// 5.  Append DQUOTE to output.\n//\n// 6.  Return output.\nfunction serializeString(value){\n  if(STRING_REGEX.test(value)){\n    throw serializeError(value, STRING);\n  }\n  return `\"${value.replace(/\\\\/g, `\\\\\\\\`).replace(/\"/g, `\\\\\"`)}\"`;\n}\n\nfunction symbolToStr(symbol){\n  return symbol.description||symbol.toString().slice(7, -1);\n}\n\nfunction serializeToken(token){\n  const value=symbolToStr(token);\n  if(/^([a-zA-Z*])([!#$%&'*+\\-.^_`|~\\w:/]*)$/.test(value)===false){\n    throw serializeError(value, TOKEN);\n  }\n  return value;\n}\n\n// 4.1.3.1.  Serializing a Bare Item\n//\n// Given an Item as input_item, return an ASCII string suitable for use\n// in a HTTP field value.\n//\n// 1.  If input_item is an Integer, return the result of running\n//     Serializing an Integer (Section 4.1.4) with input_item.\n//\n// 2.  If input_item is a Decimal, return the result of running\n//     Serializing a Decimal (Section 4.1.5) with input_item.\n//\n// 3.  If input_item is a String, return the result of running\n//     Serializing a String (Section 4.1.6) with input_item.\n//\n// 4.  If input_item is a Token, return the result of running\n//     Serializing a Token (Section 4.1.7) with input_item.\n//\n// 5.  If input_item is a Boolean, return the result of running\n//     Serializing a Boolean (Section 4.1.9) with input_item.\n//\n// 6.  If input_item is a Byte Sequence, return the result of running\n//     Serializing a Byte Sequence (Section 4.1.8) with input_item.\n//\n// 7.  If input_item is a Date, return the result of running Serializing\n//     a Date (Section 4.1.10) with input_item.\n//\n// 8.  Otherwise, fail serialization.\nfunction serializeBareItem(value){\n  switch (typeof value){\n    case 'number':\n      if(!isFiniteNumber(value)){\n        throw serializeError(value, BARE_ITEM);\n      }\n      if(Number.isInteger(value)){\n        return serializeInteger(value);\n      }\n      return serializeDecimal(value);\n    case 'string':\n      return serializeString(value);\n    case 'symbol':\n      return serializeToken(value);\n    case 'boolean':\n      return serializeBoolean(value);\n    case 'object':\n      if(value instanceof Date){\n        return serializeDate(value);\n      }\n      if(value instanceof Uint8Array){\n        return serializeByteSequence(value);\n      }\n      if(value instanceof SfToken){\n        return serializeToken(value);\n      }\n    default:\n      // fail\n      throw serializeError(value, BARE_ITEM);\n  }\n}\n\n// 4.1.1.3.  Serializing a Key\n//\n// Given a key as input_key, return an ASCII string suitable for use in\n// a HTTP field value.\n//\n// 1.  Convert input_key into a sequence of ASCII characters; if\n//     conversion fails, fail serialization.\n//\n// 2.  If input_key contains characters not in lcalpha, DIGIT, \"_\", \"-\",\n//     \".\", or \"*\" fail serialization.\n//\n// 3.  If the first character of input_key is not lcalpha or \"*\", fail\n//     serialization.\n//\n// 4.  Let output be an empty string.\n//\n// 5.  Append input_key to output.\n//\n// 6.  Return output.\nfunction serializeKey(value){\n  if(/^[a-z*][a-z0-9\\-_.*]*$/.test(value)===false){\n    throw serializeError(value, KEY);\n  }\n  return value;\n}\n\n// 4.1.1.2.  Serializing Parameters\n//\n// Given an ordered Dictionary as input_parameters (each member having a\n// param_name and a param_value), return an ASCII string suitable for\n// use in a HTTP field value.\n//\n// 1.  Let output be an empty string.\n//\n// 2.  For each param_name with a value of param_value in\n//     input_parameters:\n//\n//     1.  Append \";\" to output.\n//\n//     2.  Append the result of running Serializing a Key\n//         (Section 4.1.1.3) with param_name to output.\n//\n//     3.  If param_value is not Boolean true:\n//\n//         1.  Append \"=\" to output.\n//\n//         2.  Append the result of running Serializing a bare Item\n//             (Section 4.1.3.1) with param_value to output.\n//\n// 3.  Return output.\nfunction serializeParams(params){\n  if(params==null){\n    return '';\n  }\n  return Object.entries(params).map(([key, value])=> {\n    if(value===true){\n      return `;${serializeKey(key)}`; // omit true\n    }\n    return `;${serializeKey(key)}=${serializeBareItem(value)}`;\n  }).join('');\n}\n\n// 4.1.3.  Serializing an Item\n//\n// Given an Item as bare_item and Parameters as item_parameters, return\n// an ASCII string suitable for use in a HTTP field value.\n//\n// 1.  Let output be an empty string.\n//\n// 2.  Append the result of running Serializing a Bare Item\n//     Section 4.1.3.1 with bare_item to output.\n//\n// 3.  Append the result of running Serializing Parameters\n//     Section 4.1.1.2 with item_parameters to output.\n//\n// 4.  Return output.\nfunction serializeItem(value){\n  if(value instanceof SfItem){\n    return `${serializeBareItem(value.value)}${serializeParams(value.params)}`;\n  }else{\n    return serializeBareItem(value);\n  }\n}\n\n// 4.1.1.1.  Serializing an Inner List\n//\n// Given an array of (member_value, parameters) tuples as inner_list,\n// and parameters as list_parameters, return an ASCII string suitable\n// for use in a HTTP field value.\n//\n// 1.  Let output be the string \"(\".\n//\n// 2.  For each (member_value, parameters) of inner_list:\n//\n//     1.  Append the result of running Serializing an Item\n//         (Section 4.1.3) with (member_value, parameters) to output.\n//\n//     2.  If more values remain in inner_list, append a single SP to\n//         output.\n//\n// 3.  Append \")\" to output.\n//\n// 4.  Append the result of running Serializing Parameters\n//     (Section 4.1.1.2) with list_parameters to output.\n//\n// 5.  Return output.\nfunction serializeInnerList(value){\n  return `(${value.value.map(serializeItem).join(' ')})${serializeParams(value.params)}`;\n}\n\n// 4.1.2.  Serializing a Dictionary\n//\n// Given an ordered Dictionary as input_dictionary (each member having a\n// member_name and a tuple value of (member_value, parameters)), return\n// an ASCII string suitable for use in a HTTP field value.\n//\n// 1.  Let output be an empty string.\n//\n// 2.  For each member_name with a value of (member_value, parameters)\n//     in input_dictionary:\n//\n//     1.  Append the result of running Serializing a Key\n//         (Section 4.1.1.3) with member's member_name to output.\n//\n//     2.  If member_value is Boolean true:\n//\n//         1.  Append the result of running Serializing Parameters\n//             (Section 4.1.1.2) with parameters to output.\n//\n//     3.  Otherwise:\n//\n//         1.  Append \"=\" to output.\n//\n//         2.  If member_value is an array, append the result of running\n//             Serializing an Inner List (Section 4.1.1.1) with\n//             (member_value, parameters) to output.\n//\n//         3.  Otherwise, append the result of running Serializing an\n//             Item (Section 4.1.3) with (member_value, parameters) to\n//             output.\n//\n//     4.  If more members remain in input_dictionary:\n//\n//         1.  Append \",\" to output.\n//\n//         2.  Append a single SP to output.\n//\n// 3.  Return output.\nfunction serializeDict(dict, options={\n  whitespace: true\n}){\n  if(typeof dict!=='object'){\n    throw serializeError(dict, DICT);\n  }\n  const entries=dict instanceof Map ? dict.entries():Object.entries(dict);\n  const optionalWhiteSpace=options!=null&&options.whitespace ? ' ':'';\n  return Array.from(entries).map(([key, item])=> {\n    if(item instanceof SfItem===false){\n      item=new SfItem(item);\n    }\n    let output=serializeKey(key);\n    if(item.value===true){\n      output +=serializeParams(item.params);\n    }else{\n      output +='=';\n      if(Array.isArray(item.value)){\n        output +=serializeInnerList(item);\n      }else{\n        output +=serializeItem(item);\n      }\n    }\n    return output;\n  }).join(`,${optionalWhiteSpace}`);\n}\n\n\nfunction encodeSfDict(value, options){\n  return serializeDict(value, options);\n}\n\n\nconst isTokenField=key=> key==='ot'||key==='sf'||key==='st';\n\nconst isValid=value=> {\n  if(typeof value==='number'){\n    return isFiniteNumber(value);\n  }\n  return value!=null&&value!==''&&value!==false;\n};\n\n\nfunction urlToRelativePath(url, base){\n  const to=new URL(url);\n  const from=new URL(base);\n  if(to.origin!==from.origin){\n    return url;\n  }\n  const toPath=to.pathname.split('/').slice(1);\n  const fromPath=from.pathname.split('/').slice(1, -1);\n  // remove common parents\n  while (toPath[0]===fromPath[0]){\n    toPath.shift();\n    fromPath.shift();\n  }\n  // add back paths\n  while (fromPath.length){\n    fromPath.shift();\n    toPath.unshift('..');\n  }\n  return toPath.join('/');\n}\n\n\nfunction uuid(){\n  try {\n    return crypto.randomUUID();\n  } catch (error){\n    try {\n      const url=URL.createObjectURL(new Blob());\n      const uuid=url.toString();\n      URL.revokeObjectURL(url);\n      return uuid.slice(uuid.lastIndexOf('/') + 1);\n    } catch (error){\n      let dt=new Date().getTime();\n      const uuid='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c=> {\n        const r=(dt + Math.random() * 16) % 16 | 0;\n        dt=Math.floor(dt / 16);\n        return (c=='x' ? r:r & 0x3 | 0x8).toString(16);\n      });\n      return uuid;\n    }\n  }\n}\n\nconst toRounded=value=> Math.round(value);\nconst toUrlSafe=(value, options)=> {\n  if(options!=null&&options.baseUrl){\n    value=urlToRelativePath(value, options.baseUrl);\n  }\n  return encodeURIComponent(value);\n};\nconst toHundred=value=> toRounded(value / 100) * 100;\n\nconst CmcdFormatters={\n  \n  br: toRounded,\n  \n  d: toRounded,\n  \n  bl: toHundred,\n  \n  dl: toHundred,\n  \n  mtp: toHundred,\n  \n  nor: toUrlSafe,\n  \n  rtp: toHundred,\n  \n  tb: toRounded\n};\n\n\nfunction processCmcd(obj, options){\n  const results={};\n  if(obj==null||typeof obj!=='object'){\n    return results;\n  }\n  const keys=Object.keys(obj).sort();\n  const formatters=_extends({}, CmcdFormatters, options==null ? void 0:options.formatters);\n  const filter=options==null ? void 0:options.filter;\n  keys.forEach(key=> {\n    if(filter!=null&&filter(key)){\n      return;\n    }\n    let value=obj[key];\n    const formatter=formatters[key];\n    if(formatter){\n      value=formatter(value, options);\n    }\n    // Version should only be reported if not equal to 1.\n    if(key==='v'&&value===1){\n      return;\n    }\n    // Playback rate should only be sent if not equal to 1.\n    if(key=='pr'&&value===1){\n      return;\n    }\n    // ignore invalid values\n    if(!isValid(value)){\n      return;\n    }\n    if(isTokenField(key)&&typeof value==='string'){\n      value=new SfToken(value);\n    }\n    results[key]=value;\n  });\n  return results;\n}\n\n\nfunction encodeCmcd(cmcd, options={}){\n  if(!cmcd){\n    return '';\n  }\n  return encodeSfDict(processCmcd(cmcd, options), _extends({\n    whitespace: false\n  }, options));\n}\n\n\nfunction toCmcdHeaders(cmcd, options={}){\n  if(!cmcd){\n    return {};\n  }\n  const entries=Object.entries(cmcd);\n  const headerMap=Object.entries(CmcdHeaderMap).concat(Object.entries((options==null ? void 0:options.customHeaderMap)||{}));\n  const shards=entries.reduce((acc, entry)=> {\n    var _headerMap$find, _acc$field;\n    const [key, value]=entry;\n    const field=((_headerMap$find=headerMap.find(entry=> entry[1].includes(key)))==null ? void 0:_headerMap$find[0])||CmcdHeaderField.REQUEST;\n    (_acc$field=acc[field])!=null ? _acc$field:acc[field]={};\n    acc[field][key]=value;\n    return acc;\n  }, {});\n  return Object.entries(shards).reduce((acc, [field, value])=> {\n    acc[field]=encodeCmcd(value, options);\n    return acc;\n  }, {});\n}\n\n\nfunction appendCmcdHeaders(headers, cmcd, options){\n  return _extends(headers, toCmcdHeaders(cmcd, options));\n}\n\n\nconst CMCD_PARAM='CMCD';\n\n\nfunction toCmcdQuery(cmcd, options={}){\n  if(!cmcd){\n    return '';\n  }\n  const params=encodeCmcd(cmcd, options);\n  return `${CMCD_PARAM}=${encodeURIComponent(params)}`;\n}\n\nconst REGEX=/CMCD=[^&#]+/;\n\nfunction appendCmcdQuery(url, cmcd, options){\n  // TODO: Replace with URLSearchParams once we drop Safari < 10.1 & Chrome < 49 support.\n  // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams\n  const query=toCmcdQuery(cmcd, options);\n  if(!query){\n    return url;\n  }\n  if(REGEX.test(url)){\n    return url.replace(REGEX, query);\n  }\n  const separator=url.includes('?') ? '&':'?';\n  return `${url}${separator}${query}`;\n}\n\n\nclass CMCDController {\n  // eslint-disable-line no-restricted-globals\n\n  constructor(hls){\n    this.hls=void 0;\n    this.config=void 0;\n    this.media=void 0;\n    this.sid=void 0;\n    this.cid=void 0;\n    this.useHeaders=false;\n    this.includeKeys=void 0;\n    this.initialized=false;\n    this.starved=false;\n    this.buffering=true;\n    this.audioBuffer=void 0;\n    // eslint-disable-line no-restricted-globals\n    this.videoBuffer=void 0;\n    this.onWaiting=()=> {\n      if(this.initialized){\n        this.starved=true;\n      }\n      this.buffering=true;\n    };\n    this.onPlaying=()=> {\n      if(!this.initialized){\n        this.initialized=true;\n      }\n      this.buffering=false;\n    };\n    \n    this.applyPlaylistData=context=> {\n      try {\n        this.apply(context, {\n          ot: CmObjectType.MANIFEST,\n          su: !this.initialized\n        });\n      } catch (error){\n        logger.warn('Could not generate manifest CMCD data.', error);\n      }\n    };\n    \n    this.applyFragmentData=context=> {\n      try {\n        const fragment=context.frag;\n        const level=this.hls.levels[fragment.level];\n        const ot=this.getObjectType(fragment);\n        const data={\n          d: fragment.duration * 1000,\n          ot\n        };\n        if(ot===CmObjectType.VIDEO||ot===CmObjectType.AUDIO||ot==CmObjectType.MUXED){\n          data.br=level.bitrate / 1000;\n          data.tb=this.getTopBandwidth(ot) / 1000;\n          data.bl=this.getBufferLength(ot);\n        }\n        this.apply(context, data);\n      } catch (error){\n        logger.warn('Could not generate segment CMCD data.', error);\n      }\n    };\n    this.hls=hls;\n    const config=this.config=hls.config;\n    const {\n      cmcd\n    }=config;\n    if(cmcd!=null){\n      config.pLoader=this.createPlaylistLoader();\n      config.fLoader=this.createFragmentLoader();\n      this.sid=cmcd.sessionId||uuid();\n      this.cid=cmcd.contentId;\n      this.useHeaders=cmcd.useHeaders===true;\n      this.includeKeys=cmcd.includeKeys;\n      this.registerListeners();\n    }\n  }\n  registerListeners(){\n    const hls=this.hls;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);\n    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);\n  }\n  unregisterListeners(){\n    const hls=this.hls;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);\n    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.onMediaDetached();\n\n    // @ts-ignore\n    this.hls=this.config=this.audioBuffer=this.videoBuffer=null;\n    // @ts-ignore\n    this.onWaiting=this.onPlaying=null;\n  }\n  onMediaAttached(event, data){\n    this.media=data.media;\n    this.media.addEventListener('waiting', this.onWaiting);\n    this.media.addEventListener('playing', this.onPlaying);\n  }\n  onMediaDetached(){\n    if(!this.media){\n      return;\n    }\n    this.media.removeEventListener('waiting', this.onWaiting);\n    this.media.removeEventListener('playing', this.onPlaying);\n\n    // @ts-ignore\n    this.media=null;\n  }\n  onBufferCreated(event, data){\n    var _data$tracks$audio, _data$tracks$video;\n    this.audioBuffer=(_data$tracks$audio=data.tracks.audio)==null ? void 0:_data$tracks$audio.buffer;\n    this.videoBuffer=(_data$tracks$video=data.tracks.video)==null ? void 0:_data$tracks$video.buffer;\n  }\n  \n  createData(){\n    var _this$media;\n    return {\n      v: 1,\n      sf: CmStreamingFormat.HLS,\n      sid: this.sid,\n      cid: this.cid,\n      pr: (_this$media=this.media)==null ? void 0:_this$media.playbackRate,\n      mtp: this.hls.bandwidthEstimate / 1000\n    };\n  }\n\n  \n  apply(context, data={}){\n    // apply baseline data\n    _extends(data, this.createData());\n    const isVideo=data.ot===CmObjectType.INIT||data.ot===CmObjectType.VIDEO||data.ot===CmObjectType.MUXED;\n    if(this.starved&&isVideo){\n      data.bs=true;\n      data.su=true;\n      this.starved=false;\n    }\n    if(data.su==null){\n      data.su=this.buffering;\n    }\n\n    // TODO: Implement rtp, nrr, nor, dl\n\n    const {\n      includeKeys\n    }=this;\n    if(includeKeys){\n      data=Object.keys(data).reduce((acc, key)=> {\n        includeKeys.includes(key)&&(acc[key]=data[key]);\n        return acc;\n      }, {});\n    }\n    if(this.useHeaders){\n      if(!context.headers){\n        context.headers={};\n      }\n      appendCmcdHeaders(context.headers, data);\n    }else{\n      context.url=appendCmcdQuery(context.url, data);\n    }\n  }\n  \n  getObjectType(fragment){\n    const {\n      type\n    }=fragment;\n    if(type==='subtitle'){\n      return CmObjectType.TIMED_TEXT;\n    }\n    if(fragment.sn==='initSegment'){\n      return CmObjectType.INIT;\n    }\n    if(type==='audio'){\n      return CmObjectType.AUDIO;\n    }\n    if(type==='main'){\n      if(!this.hls.audioTracks.length){\n        return CmObjectType.MUXED;\n      }\n      return CmObjectType.VIDEO;\n    }\n    return undefined;\n  }\n\n  \n  getTopBandwidth(type){\n    let bitrate=0;\n    let levels;\n    const hls=this.hls;\n    if(type===CmObjectType.AUDIO){\n      levels=hls.audioTracks;\n    }else{\n      const max=hls.maxAutoLevel;\n      const len=max > -1 ? max + 1:hls.levels.length;\n      levels=hls.levels.slice(0, len);\n    }\n    for (const level of levels){\n      if(level.bitrate > bitrate){\n        bitrate=level.bitrate;\n      }\n    }\n    return bitrate > 0 ? bitrate:NaN;\n  }\n\n  \n  getBufferLength(type){\n    const media=this.hls.media;\n    const buffer=type===CmObjectType.AUDIO ? this.audioBuffer:this.videoBuffer;\n    if(!buffer||!media){\n      return NaN;\n    }\n    const info=BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole);\n    return info.len * 1000;\n  }\n\n  \n  createPlaylistLoader(){\n    const {\n      pLoader\n    }=this.config;\n    const apply=this.applyPlaylistData;\n    const Ctor=pLoader||this.config.loader;\n    return class CmcdPlaylistLoader {\n      constructor(config){\n        this.loader=void 0;\n        this.loader=new Ctor(config);\n      }\n      get stats(){\n        return this.loader.stats;\n      }\n      get context(){\n        return this.loader.context;\n      }\n      destroy(){\n        this.loader.destroy();\n      }\n      abort(){\n        this.loader.abort();\n      }\n      load(context, config, callbacks){\n        apply(context);\n        this.loader.load(context, config, callbacks);\n      }\n    };\n  }\n\n  \n  createFragmentLoader(){\n    const {\n      fLoader\n    }=this.config;\n    const apply=this.applyFragmentData;\n    const Ctor=fLoader||this.config.loader;\n    return class CmcdFragmentLoader {\n      constructor(config){\n        this.loader=void 0;\n        this.loader=new Ctor(config);\n      }\n      get stats(){\n        return this.loader.stats;\n      }\n      get context(){\n        return this.loader.context;\n      }\n      destroy(){\n        this.loader.destroy();\n      }\n      abort(){\n        this.loader.abort();\n      }\n      load(context, config, callbacks){\n        apply(context);\n        this.loader.load(context, config, callbacks);\n      }\n    };\n  }\n}\n\nconst PATHWAY_PENALTY_DURATION_MS=300000;\nclass ContentSteeringController {\n  constructor(hls){\n    this.hls=void 0;\n    this.log=void 0;\n    this.loader=null;\n    this.uri=null;\n    this.pathwayId='.';\n    this.pathwayPriority=null;\n    this.timeToLoad=300;\n    this.reloadTimer=-1;\n    this.updated=0;\n    this.started=false;\n    this.enabled=true;\n    this.levels=null;\n    this.audioTracks=null;\n    this.subtitleTracks=null;\n    this.penalizedPathways={};\n    this.hls=hls;\n    this.log=logger.log.bind(logger, `[content-steering]:`);\n    this.registerListeners();\n  }\n  registerListeners(){\n    const hls=this.hls;\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.ERROR, this.onError, this);\n  }\n  unregisterListeners(){\n    const hls=this.hls;\n    if(!hls){\n      return;\n    }\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.ERROR, this.onError, this);\n  }\n  startLoad(){\n    this.started=true;\n    this.clearTimeout();\n    if(this.enabled&&this.uri){\n      if(this.updated){\n        const ttl=this.timeToLoad * 1000 - (performance.now() - this.updated);\n        if(ttl > 0){\n          this.scheduleRefresh(this.uri, ttl);\n          return;\n        }\n      }\n      this.loadSteeringManifest(this.uri);\n    }\n  }\n  stopLoad(){\n    this.started=false;\n    if(this.loader){\n      this.loader.destroy();\n      this.loader=null;\n    }\n    this.clearTimeout();\n  }\n  clearTimeout(){\n    if(this.reloadTimer!==-1){\n      self.clearTimeout(this.reloadTimer);\n      this.reloadTimer=-1;\n    }\n  }\n  destroy(){\n    this.unregisterListeners();\n    this.stopLoad();\n    // @ts-ignore\n    this.hls=null;\n    this.levels=this.audioTracks=this.subtitleTracks=null;\n  }\n  removeLevel(levelToRemove){\n    const levels=this.levels;\n    if(levels){\n      this.levels=levels.filter(level=> level!==levelToRemove);\n    }\n  }\n  onManifestLoading(){\n    this.stopLoad();\n    this.enabled=true;\n    this.timeToLoad=300;\n    this.updated=0;\n    this.uri=null;\n    this.pathwayId='.';\n    this.levels=this.audioTracks=this.subtitleTracks=null;\n  }\n  onManifestLoaded(event, data){\n    const {\n      contentSteering\n    }=data;\n    if(contentSteering===null){\n      return;\n    }\n    this.pathwayId=contentSteering.pathwayId;\n    this.uri=contentSteering.uri;\n    if(this.started){\n      this.startLoad();\n    }\n  }\n  onManifestParsed(event, data){\n    this.audioTracks=data.audioTracks;\n    this.subtitleTracks=data.subtitleTracks;\n  }\n  onError(event, data){\n    const {\n      errorAction\n    }=data;\n    if((errorAction==null ? void 0:errorAction.action)===NetworkErrorAction.SendAlternateToPenaltyBox&&errorAction.flags===ErrorActionFlags.MoveAllAlternatesMatchingHost){\n      const levels=this.levels;\n      let pathwayPriority=this.pathwayPriority;\n      let errorPathway=this.pathwayId;\n      if(data.context){\n        const {\n          groupId,\n          pathwayId,\n          type\n        }=data.context;\n        if(groupId&&levels){\n          errorPathway=this.getPathwayForGroupId(groupId, type, errorPathway);\n        }else if(pathwayId){\n          errorPathway=pathwayId;\n        }\n      }\n      if(!(errorPathway in this.penalizedPathways)){\n        this.penalizedPathways[errorPathway]=performance.now();\n      }\n      if(!pathwayPriority&&levels){\n        // If PATHWAY-PRIORITY was not provided, list pathways for error handling\n        pathwayPriority=levels.reduce((pathways, level)=> {\n          if(pathways.indexOf(level.pathwayId)===-1){\n            pathways.push(level.pathwayId);\n          }\n          return pathways;\n        }, []);\n      }\n      if(pathwayPriority&&pathwayPriority.length > 1){\n        this.updatePathwayPriority(pathwayPriority);\n        errorAction.resolved=this.pathwayId!==errorPathway;\n      }\n      if(!errorAction.resolved){\n        logger.warn(`Could not resolve ${data.details} (\"${data.error.message}\") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length:levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);\n      }\n    }\n  }\n  filterParsedLevels(levels){\n    // Filter levels to only include those that are in the initial pathway\n    this.levels=levels;\n    let pathwayLevels=this.getLevelsForPathway(this.pathwayId);\n    if(pathwayLevels.length===0){\n      const pathwayId=levels[0].pathwayId;\n      this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to \"${pathwayId}\"`);\n      pathwayLevels=this.getLevelsForPathway(pathwayId);\n      this.pathwayId=pathwayId;\n    }\n    if(pathwayLevels.length!==levels.length){\n      this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway \"${this.pathwayId}\"`);\n    }\n    return pathwayLevels;\n  }\n  getLevelsForPathway(pathwayId){\n    if(this.levels===null){\n      return [];\n    }\n    return this.levels.filter(level=> pathwayId===level.pathwayId);\n  }\n  updatePathwayPriority(pathwayPriority){\n    this.pathwayPriority=pathwayPriority;\n    let levels;\n\n    // Evaluate if we should remove the pathway from the penalized list\n    const penalizedPathways=this.penalizedPathways;\n    const now=performance.now();\n    Object.keys(penalizedPathways).forEach(pathwayId=> {\n      if(now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS){\n        delete penalizedPathways[pathwayId];\n      }\n    });\n    for (let i=0; i < pathwayPriority.length; i++){\n      const pathwayId=pathwayPriority[i];\n      if(pathwayId in penalizedPathways){\n        continue;\n      }\n      if(pathwayId===this.pathwayId){\n        return;\n      }\n      const selectedIndex=this.hls.nextLoadLevel;\n      const selectedLevel=this.hls.levels[selectedIndex];\n      levels=this.getLevelsForPathway(pathwayId);\n      if(levels.length > 0){\n        this.log(`Setting Pathway to \"${pathwayId}\"`);\n        this.pathwayId=pathwayId;\n        reassignFragmentLevelIndexes(levels);\n        this.hls.trigger(Events.LEVELS_UPDATED, {\n          levels\n        });\n        // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed\n        const levelAfterChange=this.hls.levels[selectedIndex];\n        if(selectedLevel&&levelAfterChange&&this.levels){\n          if(levelAfterChange.attrs['STABLE-VARIANT-ID']!==selectedLevel.attrs['STABLE-VARIANT-ID']&&levelAfterChange.bitrate!==selectedLevel.bitrate){\n            this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`);\n          }\n          this.hls.nextLoadLevel=selectedIndex;\n        }\n        break;\n      }\n    }\n  }\n  getPathwayForGroupId(groupId, type, defaultPathway){\n    const levels=this.getLevelsForPathway(defaultPathway).concat(this.levels||[]);\n    for (let i=0; i < levels.length; i++){\n      if(type===PlaylistContextType.AUDIO_TRACK&&levels[i].hasAudioGroup(groupId)||type===PlaylistContextType.SUBTITLE_TRACK&&levels[i].hasSubtitleGroup(groupId)){\n        return levels[i].pathwayId;\n      }\n    }\n    return defaultPathway;\n  }\n  clonePathways(pathwayClones){\n    const levels=this.levels;\n    if(!levels){\n      return;\n    }\n    const audioGroupCloneMap={};\n    const subtitleGroupCloneMap={};\n    pathwayClones.forEach(pathwayClone=> {\n      const {\n        ID: cloneId,\n        'BASE-ID': baseId,\n        'URI-REPLACEMENT': uriReplacement\n      }=pathwayClone;\n      if(levels.some(level=> level.pathwayId===cloneId)){\n        return;\n      }\n      const clonedVariants=this.getLevelsForPathway(baseId).map(baseLevel=> {\n        const attributes=new AttrList(baseLevel.attrs);\n        attributes['PATHWAY-ID']=cloneId;\n        const clonedAudioGroupId=attributes.AUDIO&&`${attributes.AUDIO}_clone_${cloneId}`;\n        const clonedSubtitleGroupId=attributes.SUBTITLES&&`${attributes.SUBTITLES}_clone_${cloneId}`;\n        if(clonedAudioGroupId){\n          audioGroupCloneMap[attributes.AUDIO]=clonedAudioGroupId;\n          attributes.AUDIO=clonedAudioGroupId;\n        }\n        if(clonedSubtitleGroupId){\n          subtitleGroupCloneMap[attributes.SUBTITLES]=clonedSubtitleGroupId;\n          attributes.SUBTITLES=clonedSubtitleGroupId;\n        }\n        const url=performUriReplacement(baseLevel.uri, attributes['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement);\n        const clonedLevel=new Level({\n          attrs: attributes,\n          audioCodec: baseLevel.audioCodec,\n          bitrate: baseLevel.bitrate,\n          height: baseLevel.height,\n          name: baseLevel.name,\n          url,\n          videoCodec: baseLevel.videoCodec,\n          width: baseLevel.width\n        });\n        if(baseLevel.audioGroups){\n          for (let i=1; i < baseLevel.audioGroups.length; i++){\n            clonedLevel.addGroupId('audio', `${baseLevel.audioGroups[i]}_clone_${cloneId}`);\n          }\n        }\n        if(baseLevel.subtitleGroups){\n          for (let i=1; i < baseLevel.subtitleGroups.length; i++){\n            clonedLevel.addGroupId('text', `${baseLevel.subtitleGroups[i]}_clone_${cloneId}`);\n          }\n        }\n        return clonedLevel;\n      });\n      levels.push(...clonedVariants);\n      cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId);\n      cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId);\n    });\n  }\n  loadSteeringManifest(uri){\n    const config=this.hls.config;\n    const Loader=config.loader;\n    if(this.loader){\n      this.loader.destroy();\n    }\n    this.loader=new Loader(config);\n    let url;\n    try {\n      url=new self.URL(uri);\n    } catch (error){\n      this.enabled=false;\n      this.log(`Failed to parse Steering Manifest URI: ${uri}`);\n      return;\n    }\n    if(url.protocol!=='data:'){\n      const throughput=(this.hls.bandwidthEstimate||config.abrEwmaDefaultEstimate) | 0;\n      url.searchParams.set('_HLS_pathway', this.pathwayId);\n      url.searchParams.set('_HLS_throughput', '' + throughput);\n    }\n    const context={\n      responseType: 'json',\n      url: url.href\n    };\n    const loadPolicy=config.steeringManifestLoadPolicy.default;\n    const legacyRetryCompatibility=loadPolicy.errorRetry||loadPolicy.timeoutRetry||{};\n    const loaderConfig={\n      loadPolicy,\n      timeout: loadPolicy.maxLoadTimeMs,\n      maxRetry: legacyRetryCompatibility.maxNumRetry||0,\n      retryDelay: legacyRetryCompatibility.retryDelayMs||0,\n      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs||0\n    };\n    const callbacks={\n      onSuccess: (response, stats, context, networkDetails)=> {\n        this.log(`Loaded steering manifest: \"${url}\"`);\n        const steeringData=response.data;\n        if(steeringData.VERSION!==1){\n          this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);\n          return;\n        }\n        this.updated=performance.now();\n        this.timeToLoad=steeringData.TTL;\n        const {\n          'RELOAD-URI': reloadUri,\n          'PATHWAY-CLONES': pathwayClones,\n          'PATHWAY-PRIORITY': pathwayPriority\n        }=steeringData;\n        if(reloadUri){\n          try {\n            this.uri=new self.URL(reloadUri, url).href;\n          } catch (error){\n            this.enabled=false;\n            this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`);\n            return;\n          }\n        }\n        this.scheduleRefresh(this.uri||context.url);\n        if(pathwayClones){\n          this.clonePathways(pathwayClones);\n        }\n        const loadedSteeringData={\n          steeringManifest: steeringData,\n          url: url.toString()\n        };\n        this.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData);\n        if(pathwayPriority){\n          this.updatePathwayPriority(pathwayPriority);\n        }\n      },\n      onError: (error, context, networkDetails, stats)=> {\n        this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context.url})`);\n        this.stopLoad();\n        if(error.code===410){\n          this.enabled=false;\n          this.log(`Steering manifest ${context.url} no longer available`);\n          return;\n        }\n        let ttl=this.timeToLoad * 1000;\n        if(error.code===429){\n          const loader=this.loader;\n          if(typeof (loader==null ? void 0:loader.getResponseHeader)==='function'){\n            const retryAfter=loader.getResponseHeader('Retry-After');\n            if(retryAfter){\n              ttl=parseFloat(retryAfter) * 1000;\n            }\n          }\n          this.log(`Steering manifest ${context.url} rate limited`);\n          return;\n        }\n        this.scheduleRefresh(this.uri||context.url, ttl);\n      },\n      onTimeout: (stats, context, networkDetails)=> {\n        this.log(`Timeout loading steering manifest (${context.url})`);\n        this.scheduleRefresh(this.uri||context.url);\n      }\n    };\n    this.log(`Requesting steering manifest: ${url}`);\n    this.loader.load(context, loaderConfig, callbacks);\n  }\n  scheduleRefresh(uri, ttlMs=this.timeToLoad * 1000){\n    this.clearTimeout();\n    this.reloadTimer=self.setTimeout(()=> {\n      var _this$hls;\n      const media=(_this$hls=this.hls)==null ? void 0:_this$hls.media;\n      if(media&&!media.ended){\n        this.loadSteeringManifest(uri);\n        return;\n      }\n      this.scheduleRefresh(uri, this.timeToLoad * 1000);\n    }, ttlMs);\n  }\n}\nfunction cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId){\n  if(!tracks){\n    return;\n  }\n  Object.keys(groupCloneMap).forEach(audioGroupId=> {\n    const clonedTracks=tracks.filter(track=> track.groupId===audioGroupId).map(track=> {\n      const clonedTrack=_extends({}, track);\n      clonedTrack.details=undefined;\n      clonedTrack.attrs=new AttrList(clonedTrack.attrs);\n      clonedTrack.url=clonedTrack.attrs.URI=performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement);\n      clonedTrack.groupId=clonedTrack.attrs['GROUP-ID']=groupCloneMap[audioGroupId];\n      clonedTrack.attrs['PATHWAY-ID']=cloneId;\n      return clonedTrack;\n    });\n    tracks.push(...clonedTracks);\n  });\n}\nfunction performUriReplacement(uri, stableId, perOptionKey, uriReplacement){\n  const {\n    HOST: host,\n    PARAMS: params,\n    [perOptionKey]: perOptionUris\n  }=uriReplacement;\n  let perVariantUri;\n  if(stableId){\n    perVariantUri=perOptionUris==null ? void 0:perOptionUris[stableId];\n    if(perVariantUri){\n      uri=perVariantUri;\n    }\n  }\n  const url=new self.URL(uri);\n  if(host&&!perVariantUri){\n    url.host=host;\n  }\n  if(params){\n    Object.keys(params).sort().forEach(key=> {\n      if(key){\n        url.searchParams.set(key, params[key]);\n      }\n    });\n  }\n  return url.href;\n}\n\nconst AGE_HEADER_LINE_REGEX=/^age:\\s*[\\d.]+\\s*$/im;\nclass XhrLoader {\n  constructor(config){\n    this.xhrSetup=void 0;\n    this.requestTimeout=void 0;\n    this.retryTimeout=void 0;\n    this.retryDelay=void 0;\n    this.config=null;\n    this.callbacks=null;\n    this.context=null;\n    this.loader=null;\n    this.stats=void 0;\n    this.xhrSetup=config ? config.xhrSetup||null:null;\n    this.stats=new LoadStats();\n    this.retryDelay=0;\n  }\n  destroy(){\n    this.callbacks=null;\n    this.abortInternal();\n    this.loader=null;\n    this.config=null;\n    this.context=null;\n    this.xhrSetup=null;\n  }\n  abortInternal(){\n    const loader=this.loader;\n    self.clearTimeout(this.requestTimeout);\n    self.clearTimeout(this.retryTimeout);\n    if(loader){\n      loader.onreadystatechange=null;\n      loader.onprogress=null;\n      if(loader.readyState!==4){\n        this.stats.aborted=true;\n        loader.abort();\n      }\n    }\n  }\n  abort(){\n    var _this$callbacks;\n    this.abortInternal();\n    if((_this$callbacks=this.callbacks)!=null&&_this$callbacks.onAbort){\n      this.callbacks.onAbort(this.stats, this.context, this.loader);\n    }\n  }\n  load(context, config, callbacks){\n    if(this.stats.loading.start){\n      throw new Error('Loader can only be used once.');\n    }\n    this.stats.loading.start=self.performance.now();\n    this.context=context;\n    this.config=config;\n    this.callbacks=callbacks;\n    this.loadInternal();\n  }\n  loadInternal(){\n    const {\n      config,\n      context\n    }=this;\n    if(!config||!context){\n      return;\n    }\n    const xhr=this.loader=new self.XMLHttpRequest();\n    const stats=this.stats;\n    stats.loading.first=0;\n    stats.loaded=0;\n    stats.aborted=false;\n    const xhrSetup=this.xhrSetup;\n    if(xhrSetup){\n      Promise.resolve().then(()=> {\n        if(this.loader!==xhr||this.stats.aborted) return;\n        return xhrSetup(xhr, context.url);\n      }).catch(error=> {\n        if(this.loader!==xhr||this.stats.aborted) return;\n        xhr.open('GET', context.url, true);\n        return xhrSetup(xhr, context.url);\n      }).then(()=> {\n        if(this.loader!==xhr||this.stats.aborted) return;\n        this.openAndSendXhr(xhr, context, config);\n      }).catch(error=> {\n        // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS\n        this.callbacks.onError({\n          code: xhr.status,\n          text: error.message\n        }, context, xhr, stats);\n        return;\n      });\n    }else{\n      this.openAndSendXhr(xhr, context, config);\n    }\n  }\n  openAndSendXhr(xhr, context, config){\n    if(!xhr.readyState){\n      xhr.open('GET', context.url, true);\n    }\n    const headers=context.headers;\n    const {\n      maxTimeToFirstByteMs,\n      maxLoadTimeMs\n    }=config.loadPolicy;\n    if(headers){\n      for (const header in headers){\n        xhr.setRequestHeader(header, headers[header]);\n      }\n    }\n    if(context.rangeEnd){\n      xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1));\n    }\n    xhr.onreadystatechange=this.readystatechange.bind(this);\n    xhr.onprogress=this.loadprogress.bind(this);\n    xhr.responseType=context.responseType;\n    // setup timeout before we perform request\n    self.clearTimeout(this.requestTimeout);\n    config.timeout=maxTimeToFirstByteMs&&isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs:maxLoadTimeMs;\n    this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this), config.timeout);\n    xhr.send();\n  }\n  readystatechange(){\n    const {\n      context,\n      loader: xhr,\n      stats\n    }=this;\n    if(!context||!xhr){\n      return;\n    }\n    const readyState=xhr.readyState;\n    const config=this.config;\n\n    // don't proceed if xhr has been aborted\n    if(stats.aborted){\n      return;\n    }\n\n    // >=HEADERS_RECEIVED\n    if(readyState >=2){\n      if(stats.loading.first===0){\n        stats.loading.first=Math.max(self.performance.now(), stats.loading.start);\n        // readyState >=2 AND readyState!==4 (readyState=HEADERS_RECEIVED||LOADING) rearm timeout as xhr not finished yet\n        if(config.timeout!==config.loadPolicy.maxLoadTimeMs){\n          self.clearTimeout(this.requestTimeout);\n          config.timeout=config.loadPolicy.maxLoadTimeMs;\n          this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start));\n        }\n      }\n      if(readyState===4){\n        self.clearTimeout(this.requestTimeout);\n        xhr.onreadystatechange=null;\n        xhr.onprogress=null;\n        const status=xhr.status;\n        // http status between 200 to 299 are all successful\n        const useResponseText=xhr.responseType==='text' ? xhr.responseText:null;\n        if(status >=200&&status < 300){\n          const data=useResponseText!=null ? useResponseText:xhr.response;\n          if(data!=null){\n            stats.loading.end=Math.max(self.performance.now(), stats.loading.first);\n            const len=xhr.responseType==='arraybuffer' ? data.byteLength:data.length;\n            stats.loaded=stats.total=len;\n            stats.bwEstimate=stats.total * 8000 / (stats.loading.end - stats.loading.first);\n            if(!this.callbacks){\n              return;\n            }\n            const onProgress=this.callbacks.onProgress;\n            if(onProgress){\n              onProgress(stats, context, data, xhr);\n            }\n            if(!this.callbacks){\n              return;\n            }\n            const _response={\n              url: xhr.responseURL,\n              data: data,\n              code: status\n            };\n            this.callbacks.onSuccess(_response, stats, context, xhr);\n            return;\n          }\n        }\n\n        // Handle bad status or nullish response\n        const retryConfig=config.loadPolicy.errorRetry;\n        const retryCount=stats.retry;\n        // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error\n        const response={\n          url: context.url,\n          data: undefined,\n          code: status\n        };\n        if(shouldRetry(retryConfig, retryCount, false, response)){\n          this.retry(retryConfig);\n        }else{\n          logger.error(`${status} while loading ${context.url}`);\n          this.callbacks.onError({\n            code: status,\n            text: xhr.statusText\n          }, context, xhr, stats);\n        }\n      }\n    }\n  }\n  loadtimeout(){\n    if(!this.config) return;\n    const retryConfig=this.config.loadPolicy.timeoutRetry;\n    const retryCount=this.stats.retry;\n    if(shouldRetry(retryConfig, retryCount, true)){\n      this.retry(retryConfig);\n    }else{\n      var _this$context;\n      logger.warn(`timeout while loading ${(_this$context=this.context)==null ? void 0:_this$context.url}`);\n      const callbacks=this.callbacks;\n      if(callbacks){\n        this.abortInternal();\n        callbacks.onTimeout(this.stats, this.context, this.loader);\n      }\n    }\n  }\n  retry(retryConfig){\n    const {\n      context,\n      stats\n    }=this;\n    this.retryDelay=getRetryDelay(retryConfig, stats.retry);\n    stats.retry++;\n    logger.warn(`${status ? 'HTTP Status ' + status:'Timeout'} while loading ${context==null ? void 0:context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`);\n    // abort and reset internal state\n    this.abortInternal();\n    this.loader=null;\n    // schedule retry\n    self.clearTimeout(this.retryTimeout);\n    this.retryTimeout=self.setTimeout(this.loadInternal.bind(this), this.retryDelay);\n  }\n  loadprogress(event){\n    const stats=this.stats;\n    stats.loaded=event.loaded;\n    if(event.lengthComputable){\n      stats.total=event.total;\n    }\n  }\n  getCacheAge(){\n    let result=null;\n    if(this.loader&&AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())){\n      const ageHeader=this.loader.getResponseHeader('age');\n      result=ageHeader ? parseFloat(ageHeader):null;\n    }\n    return result;\n  }\n  getResponseHeader(name){\n    if(this.loader&&new RegExp(`^${name}:\\\\s*[\\\\d.]+\\\\s*$`, 'im').test(this.loader.getAllResponseHeaders())){\n      return this.loader.getResponseHeader(name);\n    }\n    return null;\n  }\n}\n\nfunction fetchSupported(){\n  if(\n  // @ts-ignore\n  self.fetch&&self.AbortController&&self.ReadableStream&&self.Request){\n    try {\n      new self.ReadableStream({});// eslint-disable-line no-new\n      return true;\n    } catch (e){\n      \n    }\n  }\n  return false;\n}\nconst BYTERANGE=/(\\d+)-(\\d+)\\/(\\d+)/;\nclass FetchLoader {\n  constructor(config ){\n    this.fetchSetup=void 0;\n    this.requestTimeout=void 0;\n    this.request=null;\n    this.response=null;\n    this.controller=void 0;\n    this.context=null;\n    this.config=null;\n    this.callbacks=null;\n    this.stats=void 0;\n    this.loader=null;\n    this.fetchSetup=config.fetchSetup||getRequest;\n    this.controller=new self.AbortController();\n    this.stats=new LoadStats();\n  }\n  destroy(){\n    this.loader=this.callbacks=this.context=this.config=this.request=null;\n    this.abortInternal();\n    this.response=null;\n    // @ts-ignore\n    this.fetchSetup=this.controller=this.stats=null;\n  }\n  abortInternal(){\n    if(this.controller&&!this.stats.loading.end){\n      this.stats.aborted=true;\n      this.controller.abort();\n    }\n  }\n  abort(){\n    var _this$callbacks;\n    this.abortInternal();\n    if((_this$callbacks=this.callbacks)!=null&&_this$callbacks.onAbort){\n      this.callbacks.onAbort(this.stats, this.context, this.response);\n    }\n  }\n  load(context, config, callbacks){\n    const stats=this.stats;\n    if(stats.loading.start){\n      throw new Error('Loader can only be used once.');\n    }\n    stats.loading.start=self.performance.now();\n    const initParams=getRequestParameters(context, this.controller.signal);\n    const onProgress=callbacks.onProgress;\n    const isArrayBuffer=context.responseType==='arraybuffer';\n    const LENGTH=isArrayBuffer ? 'byteLength':'length';\n    const {\n      maxTimeToFirstByteMs,\n      maxLoadTimeMs\n    }=config.loadPolicy;\n    this.context=context;\n    this.config=config;\n    this.callbacks=callbacks;\n    this.request=this.fetchSetup(context, initParams);\n    self.clearTimeout(this.requestTimeout);\n    config.timeout=maxTimeToFirstByteMs&&isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs:maxLoadTimeMs;\n    this.requestTimeout=self.setTimeout(()=> {\n      this.abortInternal();\n      callbacks.onTimeout(stats, context, this.response);\n    }, config.timeout);\n    self.fetch(this.request).then(response=> {\n      this.response=this.loader=response;\n      const first=Math.max(self.performance.now(), stats.loading.start);\n      self.clearTimeout(this.requestTimeout);\n      config.timeout=maxLoadTimeMs;\n      this.requestTimeout=self.setTimeout(()=> {\n        this.abortInternal();\n        callbacks.onTimeout(stats, context, this.response);\n      }, maxLoadTimeMs - (first - stats.loading.start));\n      if(!response.ok){\n        const {\n          status,\n          statusText\n        }=response;\n        throw new FetchError(statusText||'fetch, bad network response', status, response);\n      }\n      stats.loading.first=first;\n      stats.total=getContentLength(response.headers)||stats.total;\n      if(onProgress&&isFiniteNumber(config.highWaterMark)){\n        return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress);\n      }\n      if(isArrayBuffer){\n        return response.arrayBuffer();\n      }\n      if(context.responseType==='json'){\n        return response.json();\n      }\n      return response.text();\n    }).then(responseData=> {\n      const response=this.response;\n      if(!response){\n        throw new Error('loader destroyed');\n      }\n      self.clearTimeout(this.requestTimeout);\n      stats.loading.end=Math.max(self.performance.now(), stats.loading.first);\n      const total=responseData[LENGTH];\n      if(total){\n        stats.loaded=stats.total=total;\n      }\n      const loaderResponse={\n        url: response.url,\n        data: responseData,\n        code: response.status\n      };\n      if(onProgress&&!isFiniteNumber(config.highWaterMark)){\n        onProgress(stats, context, responseData, response);\n      }\n      callbacks.onSuccess(loaderResponse, stats, context, response);\n    }).catch(error=> {\n      self.clearTimeout(this.requestTimeout);\n      if(stats.aborted){\n        return;\n      }\n      // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior\n      // when destroying, 'error' itself can be undefined\n      const code = !error ? 0:error.code||0;\n      const text = !error ? null:error.message;\n      callbacks.onError({\n        code,\n        text\n      }, context, error ? error.details:null, stats);\n    });\n  }\n  getCacheAge(){\n    let result=null;\n    if(this.response){\n      const ageHeader=this.response.headers.get('age');\n      result=ageHeader ? parseFloat(ageHeader):null;\n    }\n    return result;\n  }\n  getResponseHeader(name){\n    return this.response ? this.response.headers.get(name):null;\n  }\n  loadProgressively(response, stats, context, highWaterMark=0, onProgress){\n    const chunkCache=new ChunkCache();\n    const reader=response.body.getReader();\n    const pump=()=> {\n      return reader.read().then(data=> {\n        if(data.done){\n          if(chunkCache.dataLength){\n            onProgress(stats, context, chunkCache.flush(), response);\n          }\n          return Promise.resolve(new ArrayBuffer(0));\n        }\n        const chunk=data.value;\n        const len=chunk.length;\n        stats.loaded +=len;\n        if(len < highWaterMark||chunkCache.dataLength){\n          // The current chunk is too small to to be emitted or the cache already has data\n          // Push it to the cache\n          chunkCache.push(chunk);\n          if(chunkCache.dataLength >=highWaterMark){\n            // flush in order to join the typed arrays\n            onProgress(stats, context, chunkCache.flush(), response);\n          }\n        }else{\n          // If there's nothing cached already, and the chache is large enough\n          // just emit the progress event\n          onProgress(stats, context, chunk, response);\n        }\n        return pump();\n      }).catch(()=> {\n        \n        return Promise.reject();\n      });\n    };\n    return pump();\n  }\n}\nfunction getRequestParameters(context, signal){\n  const initParams={\n    method: 'GET',\n    mode: 'cors',\n    credentials: 'same-origin',\n    signal,\n    headers: new self.Headers(_extends({}, context.headers))\n  };\n  if(context.rangeEnd){\n    initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1));\n  }\n  return initParams;\n}\nfunction getByteRangeLength(byteRangeHeader){\n  const result=BYTERANGE.exec(byteRangeHeader);\n  if(result){\n    return parseInt(result[2]) - parseInt(result[1]) + 1;\n  }\n}\nfunction getContentLength(headers){\n  const contentRange=headers.get('Content-Range');\n  if(contentRange){\n    const byteRangeLength=getByteRangeLength(contentRange);\n    if(isFiniteNumber(byteRangeLength)){\n      return byteRangeLength;\n    }\n  }\n  const contentLength=headers.get('Content-Length');\n  if(contentLength){\n    return parseInt(contentLength);\n  }\n}\nfunction getRequest(context, initParams){\n  return new self.Request(context.url, initParams);\n}\nclass FetchError extends Error {\n  constructor(message, code, details){\n    super(message);\n    this.code=void 0;\n    this.details=void 0;\n    this.code=code;\n    this.details=details;\n  }\n}\n\nconst WHITESPACE_CHAR=/\\s/;\nconst Cues={\n  newCue(track, startTime, endTime, captionScreen){\n    const result=[];\n    let row;\n    // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers\n    let cue;\n    let indenting;\n    let indent;\n    let text;\n    const Cue=self.VTTCue||self.TextTrackCue;\n    for (let r=0; r < captionScreen.rows.length; r++){\n      row=captionScreen.rows[r];\n      indenting=true;\n      indent=0;\n      text='';\n      if(!row.isEmpty()){\n        var _track$cues;\n        for (let c=0; c < row.chars.length; c++){\n          if(WHITESPACE_CHAR.test(row.chars[c].uchar)&&indenting){\n            indent++;\n          }else{\n            text +=row.chars[c].uchar;\n            indenting=false;\n          }\n        }\n        // To be used for cleaning-up orphaned roll-up captions\n        row.cueStartTime=startTime;\n\n        // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE\n        if(startTime===endTime){\n          endTime +=0.0001;\n        }\n        if(indent >=16){\n          indent--;\n        }else{\n          indent++;\n        }\n        const cueText=fixLineBreaks(text.trim());\n        const id=generateCueId(startTime, endTime, cueText);\n\n        // If this cue already exists in the track do not push it\n        if(!(track!=null&&(_track$cues=track.cues)!=null&&_track$cues.getCueById(id))){\n          cue=new Cue(startTime, endTime, cueText);\n          cue.id=id;\n          cue.line=r + 1;\n          cue.align='left';\n          // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code)\n          // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608\n          // Firefox throws an exception and captions break with out of bounds 0-100 values\n          cue.position=10 + Math.min(80, Math.floor(indent * 8 / 32) * 10);\n          result.push(cue);\n        }\n      }\n    }\n    if(track&&result.length){\n      // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome\n      result.sort((cueA, cueB)=> {\n        if(cueA.line==='auto'||cueB.line==='auto'){\n          return 0;\n        }\n        if(cueA.line > 8&&cueB.line > 8){\n          return cueB.line - cueA.line;\n        }\n        return cueA.line - cueB.line;\n      });\n      result.forEach(cue=> addCueToTrack(track, cue));\n    }\n    return result;\n  }\n};\n\n\n\n\n\nconst defaultLoadPolicy={\n  maxTimeToFirstByteMs: 8000,\n  maxLoadTimeMs: 20000,\n  timeoutRetry: null,\n  errorRetry: null\n};\n\n\nconst hlsDefaultConfig=_objectSpread2(_objectSpread2({\n  autoStartLoad: true,\n  // used by stream-controller\n  startPosition: -1,\n  // used by stream-controller\n  defaultAudioCodec: undefined,\n  // used by stream-controller\n  debug: false,\n  // used by logger\n  capLevelOnFPSDrop: false,\n  // used by fps-controller\n  capLevelToPlayerSize: false,\n  // used by cap-level-controller\n  ignoreDevicePixelRatio: false,\n  // used by cap-level-controller\n  preferManagedMediaSource: true,\n  initialLiveManifestSize: 1,\n  // used by stream-controller\n  maxBufferLength: 30,\n  // used by stream-controller\n  backBufferLength: Infinity,\n  // used by buffer-controller\n  frontBufferFlushThreshold: Infinity,\n  maxBufferSize: 60 * 1000 * 1000,\n  // used by stream-controller\n  maxBufferHole: 0.1,\n  // used by stream-controller\n  highBufferWatchdogPeriod: 2,\n  // used by stream-controller\n  nudgeOffset: 0.1,\n  // used by stream-controller\n  nudgeMaxRetry: 3,\n  // used by stream-controller\n  maxFragLookUpTolerance: 0.25,\n  // used by stream-controller\n  liveSyncDurationCount: 3,\n  // used by latency-controller\n  liveMaxLatencyDurationCount: Infinity,\n  // used by latency-controller\n  liveSyncDuration: undefined,\n  // used by latency-controller\n  liveMaxLatencyDuration: undefined,\n  // used by latency-controller\n  maxLiveSyncPlaybackRate: 1,\n  // used by latency-controller\n  liveDurationInfinity: false,\n  // used by buffer-controller\n  \n  liveBackBufferLength: null,\n  // used by buffer-controller\n  maxMaxBufferLength: 600,\n  // used by stream-controller\n  enableWorker: true,\n  // used by transmuxer\n  workerPath: null,\n  // used by transmuxer\n  enableSoftwareAES: true,\n  // used by decrypter\n  startLevel: undefined,\n  // used by level-controller\n  startFragPrefetch: false,\n  // used by stream-controller\n  fpsDroppedMonitoringPeriod: 5000,\n  // used by fps-controller\n  fpsDroppedMonitoringThreshold: 0.2,\n  // used by fps-controller\n  appendErrorMaxRetry: 3,\n  // used by buffer-controller\n  loader: XhrLoader,\n  // loader: FetchLoader,\n  fLoader: undefined,\n  // used by fragment-loader\n  pLoader: undefined,\n  // used by playlist-loader\n  xhrSetup: undefined,\n  // used by xhr-loader\n  licenseXhrSetup: undefined,\n  // used by eme-controller\n  licenseResponseCallback: undefined,\n  // used by eme-controller\n  abrController: AbrController,\n  bufferController: BufferController,\n  capLevelController: CapLevelController,\n  errorController: ErrorController,\n  fpsController: FPSController,\n  stretchShortVideoTrack: false,\n  // used by mp4-remuxer\n  maxAudioFramesDrift: 1,\n  // used by mp4-remuxer\n  forceKeyFrameOnDiscontinuity: true,\n  // used by ts-demuxer\n  abrEwmaFastLive: 3,\n  // used by abr-controller\n  abrEwmaSlowLive: 9,\n  // used by abr-controller\n  abrEwmaFastVoD: 3,\n  // used by abr-controller\n  abrEwmaSlowVoD: 9,\n  // used by abr-controller\n  abrEwmaDefaultEstimate: 5e5,\n  // 500 kbps  // used by abr-controller\n  abrEwmaDefaultEstimateMax: 5e6,\n  // 5 mbps\n  abrBandWidthFactor: 0.95,\n  // used by abr-controller\n  abrBandWidthUpFactor: 0.7,\n  // used by abr-controller\n  abrMaxWithRealBitrate: false,\n  // used by abr-controller\n  maxStarvationDelay: 4,\n  // used by abr-controller\n  maxLoadingDelay: 4,\n  // used by abr-controller\n  minAutoBitrate: 0,\n  // used by hls\n  emeEnabled: false,\n  // used by eme-controller\n  widevineLicenseUrl: undefined,\n  // used by eme-controller\n  drmSystems: {},\n  // used by eme-controller\n  drmSystemOptions: {},\n  // used by eme-controller\n  requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess ,\n  // used by eme-controller\n  testBandwidth: true,\n  progressive: false,\n  lowLatencyMode: true,\n  cmcd: undefined,\n  enableDateRangeMetadataCues: true,\n  enableEmsgMetadataCues: true,\n  enableID3MetadataCues: true,\n  useMediaCapabilities: true,\n  certLoadPolicy: {\n    default: defaultLoadPolicy\n  },\n  keyLoadPolicy: {\n    default: {\n      maxTimeToFirstByteMs: 8000,\n      maxLoadTimeMs: 20000,\n      timeoutRetry: {\n        maxNumRetry: 1,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 20000,\n        backoff: 'linear'\n      },\n      errorRetry: {\n        maxNumRetry: 8,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 20000,\n        backoff: 'linear'\n      }\n    }\n  },\n  manifestLoadPolicy: {\n    default: {\n      maxTimeToFirstByteMs: Infinity,\n      maxLoadTimeMs: 20000,\n      timeoutRetry: {\n        maxNumRetry: 2,\n        retryDelayMs: 0,\n        maxRetryDelayMs: 0\n      },\n      errorRetry: {\n        maxNumRetry: 1,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 8000\n      }\n    }\n  },\n  playlistLoadPolicy: {\n    default: {\n      maxTimeToFirstByteMs: 10000,\n      maxLoadTimeMs: 20000,\n      timeoutRetry: {\n        maxNumRetry: 2,\n        retryDelayMs: 0,\n        maxRetryDelayMs: 0\n      },\n      errorRetry: {\n        maxNumRetry: 2,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 8000\n      }\n    }\n  },\n  fragLoadPolicy: {\n    default: {\n      maxTimeToFirstByteMs: 10000,\n      maxLoadTimeMs: 120000,\n      timeoutRetry: {\n        maxNumRetry: 4,\n        retryDelayMs: 0,\n        maxRetryDelayMs: 0\n      },\n      errorRetry: {\n        maxNumRetry: 6,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 8000\n      }\n    }\n  },\n  steeringManifestLoadPolicy: {\n    default: {\n      maxTimeToFirstByteMs: 10000,\n      maxLoadTimeMs: 20000,\n      timeoutRetry: {\n        maxNumRetry: 2,\n        retryDelayMs: 0,\n        maxRetryDelayMs: 0\n      },\n      errorRetry: {\n        maxNumRetry: 1,\n        retryDelayMs: 1000,\n        maxRetryDelayMs: 8000\n      }\n    } \n  },\n  // These default settings are deprecated in favor of the above policies\n  // and are maintained for backwards compatibility\n  manifestLoadingTimeOut: 10000,\n  manifestLoadingMaxRetry: 1,\n  manifestLoadingRetryDelay: 1000,\n  manifestLoadingMaxRetryTimeout: 64000,\n  levelLoadingTimeOut: 10000,\n  levelLoadingMaxRetry: 4,\n  levelLoadingRetryDelay: 1000,\n  levelLoadingMaxRetryTimeout: 64000,\n  fragLoadingTimeOut: 20000,\n  fragLoadingMaxRetry: 6,\n  fragLoadingRetryDelay: 1000,\n  fragLoadingMaxRetryTimeout: 64000\n}, timelineConfig()), {}, {\n  subtitleStreamController: SubtitleStreamController ,\n  subtitleTrackController: SubtitleTrackController ,\n  timelineController: TimelineController ,\n  audioStreamController: AudioStreamController ,\n  audioTrackController: AudioTrackController ,\n  emeController: EMEController ,\n  cmcdController: CMCDController ,\n  contentSteeringController: ContentSteeringController \n});\nfunction timelineConfig(){\n  return {\n    cueHandler: Cues,\n    // used by timeline-controller\n    enableWebVTT: true,\n    // used by timeline-controller\n    enableIMSC1: true,\n    // used by timeline-controller\n    enableCEA708Captions: true,\n    // used by timeline-controller\n    captionsTextTrack1Label: 'English',\n    // used by timeline-controller\n    captionsTextTrack1LanguageCode: 'en',\n    // used by timeline-controller\n    captionsTextTrack2Label: 'Spanish',\n    // used by timeline-controller\n    captionsTextTrack2LanguageCode: 'es',\n    // used by timeline-controller\n    captionsTextTrack3Label: 'Unknown CC',\n    // used by timeline-controller\n    captionsTextTrack3LanguageCode: '',\n    // used by timeline-controller\n    captionsTextTrack4Label: 'Unknown CC',\n    // used by timeline-controller\n    captionsTextTrack4LanguageCode: '',\n    // used by timeline-controller\n    renderTextTracksNatively: true\n  };\n}\n\n\nfunction mergeConfig(defaultConfig, userConfig){\n  if((userConfig.liveSyncDurationCount||userConfig.liveMaxLatencyDurationCount)&&(userConfig.liveSyncDuration||userConfig.liveMaxLatencyDuration)){\n    throw new Error(\"Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration\");\n  }\n  if(userConfig.liveMaxLatencyDurationCount!==undefined&&(userConfig.liveSyncDurationCount===undefined||userConfig.liveMaxLatencyDurationCount <=userConfig.liveSyncDurationCount)){\n    throw new Error('Illegal hls.js config: \"liveMaxLatencyDurationCount\" must be greater than \"liveSyncDurationCount\"');\n  }\n  if(userConfig.liveMaxLatencyDuration!==undefined&&(userConfig.liveSyncDuration===undefined||userConfig.liveMaxLatencyDuration <=userConfig.liveSyncDuration)){\n    throw new Error('Illegal hls.js config: \"liveMaxLatencyDuration\" must be greater than \"liveSyncDuration\"');\n  }\n  const defaultsCopy=deepCpy(defaultConfig);\n\n  // Backwards compatibility with deprecated config values\n  const deprecatedSettingTypes=['manifest', 'level', 'frag'];\n  const deprecatedSettings=['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout'];\n  deprecatedSettingTypes.forEach(type=> {\n    const policyName=`${type==='level' ? 'playlist':type}LoadPolicy`;\n    const policyNotSet=userConfig[policyName]===undefined;\n    const report=[];\n    deprecatedSettings.forEach(setting=> {\n      const deprecatedSetting=`${type}Loading${setting}`;\n      const value=userConfig[deprecatedSetting];\n      if(value!==undefined&&policyNotSet){\n        report.push(deprecatedSetting);\n        const settings=defaultsCopy[policyName].default;\n        userConfig[policyName]={\n          default: settings\n        };\n        switch (setting){\n          case 'TimeOut':\n            settings.maxLoadTimeMs=value;\n            settings.maxTimeToFirstByteMs=value;\n            break;\n          case 'MaxRetry':\n            settings.errorRetry.maxNumRetry=value;\n            settings.timeoutRetry.maxNumRetry=value;\n            break;\n          case 'RetryDelay':\n            settings.errorRetry.retryDelayMs=value;\n            settings.timeoutRetry.retryDelayMs=value;\n            break;\n          case 'MaxRetryTimeout':\n            settings.errorRetry.maxRetryDelayMs=value;\n            settings.timeoutRetry.maxRetryDelayMs=value;\n            break;\n        }\n      }\n    });\n    if(report.length){\n      logger.warn(`hls.js config: \"${report.join('\", \"')}\" setting(s) are deprecated, use \"${policyName}\": ${JSON.stringify(userConfig[policyName])}`);\n    }\n  });\n  return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig);\n}\nfunction deepCpy(obj){\n  if(obj&&typeof obj==='object'){\n    if(Array.isArray(obj)){\n      return obj.map(deepCpy);\n    }\n    return Object.keys(obj).reduce((result, key)=> {\n      result[key]=deepCpy(obj[key]);\n      return result;\n    }, {});\n  }\n  return obj;\n}\n\n\nfunction enableStreamingMode(config){\n  const currentLoader=config.loader;\n  if(currentLoader!==FetchLoader&&currentLoader!==XhrLoader){\n    // If a developer has configured their own loader, respect that choice\n    logger.log('[config]: Custom loader detected, cannot enable progressive streaming');\n    config.progressive=false;\n  }else{\n    const canStreamProgressively=fetchSupported();\n    if(canStreamProgressively){\n      config.loader=FetchLoader;\n      config.progressive=true;\n      config.enableSoftwareAES=true;\n      logger.log('[config]: Progressive streaming enabled, using FetchLoader');\n    }\n  }\n}\n\nlet chromeOrFirefox;\nclass LevelController extends BasePlaylistController {\n  constructor(hls, contentSteeringController){\n    super(hls, '[level-controller]');\n    this._levels=[];\n    this._firstLevel=-1;\n    this._maxAutoLevel=-1;\n    this._startLevel=void 0;\n    this.currentLevel=null;\n    this.currentLevelIndex=-1;\n    this.manualLevelIndex=-1;\n    this.steering=void 0;\n    this.onParsedComplete=void 0;\n    this.steering=contentSteeringController;\n    this._registerListeners();\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.on(Events.ERROR, this.onError, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);\n    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n    hls.off(Events.ERROR, this.onError, this);\n  }\n  destroy(){\n    this._unregisterListeners();\n    this.steering=null;\n    this.resetLevels();\n    super.destroy();\n  }\n  stopLoad(){\n    const levels=this._levels;\n\n    // clean up live level details to force reload them, and reset load errors\n    levels.forEach(level=> {\n      level.loadError=0;\n      level.fragmentError=0;\n    });\n    super.stopLoad();\n  }\n  resetLevels(){\n    this._startLevel=undefined;\n    this.manualLevelIndex=-1;\n    this.currentLevelIndex=-1;\n    this.currentLevel=null;\n    this._levels=[];\n    this._maxAutoLevel=-1;\n  }\n  onManifestLoading(event, data){\n    this.resetLevels();\n  }\n  onManifestLoaded(event, data){\n    const preferManagedMediaSource=this.hls.config.preferManagedMediaSource;\n    const levels=[];\n    const redundantSet={};\n    const generatePathwaySet={};\n    let resolutionFound=false;\n    let videoCodecFound=false;\n    let audioCodecFound=false;\n    data.levels.forEach(levelParsed=> {\n      var _audioCodec, _videoCodec;\n      const attributes=levelParsed.attrs;\n\n      // erase audio codec info if browser does not support mp4a.40.34.\n      // demuxer will autodetect codec and fallback to mpeg/audio\n      let {\n        audioCodec,\n        videoCodec\n      }=levelParsed;\n      if(((_audioCodec=audioCodec)==null ? void 0:_audioCodec.indexOf('mp4a.40.34'))!==-1){\n        chromeOrFirefox||(chromeOrFirefox=/chrome|firefox/i.test(navigator.userAgent));\n        if(chromeOrFirefox){\n          levelParsed.audioCodec=audioCodec=undefined;\n        }\n      }\n      if(audioCodec){\n        levelParsed.audioCodec=audioCodec=getCodecCompatibleName(audioCodec, preferManagedMediaSource);\n      }\n      if(((_videoCodec=videoCodec)==null ? void 0:_videoCodec.indexOf('avc1'))===0){\n        videoCodec=levelParsed.videoCodec=convertAVC1ToAVCOTI(videoCodec);\n      }\n\n      // only keep levels with supported audio/video codecs\n      const {\n        width,\n        height,\n        unknownCodecs\n      }=levelParsed;\n      resolutionFound||(resolutionFound = !!(width&&height));\n      videoCodecFound||(videoCodecFound = !!videoCodec);\n      audioCodecFound||(audioCodecFound = !!audioCodec);\n      if(unknownCodecs!=null&&unknownCodecs.length||audioCodec&&!areCodecsMediaSourceSupported(audioCodec, 'audio', preferManagedMediaSource)||videoCodec&&!areCodecsMediaSourceSupported(videoCodec, 'video', preferManagedMediaSource)){\n        return;\n      }\n      const {\n        CODECS,\n        'FRAME-RATE': FRAMERATE,\n        'HDCP-LEVEL': HDCP,\n        'PATHWAY-ID': PATHWAY,\n        RESOLUTION,\n        'VIDEO-RANGE': VIDEO_RANGE\n      }=attributes;\n      const contentSteeringPrefix=`${PATHWAY||'.'}-`;\n      const levelKey=`${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`;\n      if(!redundantSet[levelKey]){\n        const level=new Level(levelParsed);\n        redundantSet[levelKey]=level;\n        generatePathwaySet[levelKey]=1;\n        levels.push(level);\n      }else if(redundantSet[levelKey].uri!==levelParsed.url&&!levelParsed.attrs['PATHWAY-ID']){\n        // Assign Pathway IDs to Redundant Streams (default Pathways is \".\". Redundant Streams \"..\", \"...\", and so on.)\n        // Content Steering controller to handles Pathway fallback on error\n        const pathwayCount=generatePathwaySet[levelKey] +=1;\n        levelParsed.attrs['PATHWAY-ID']=new Array(pathwayCount + 1).join('.');\n        const level=new Level(levelParsed);\n        redundantSet[levelKey]=level;\n        levels.push(level);\n      }else{\n        redundantSet[levelKey].addGroupId('audio', attributes.AUDIO);\n        redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES);\n      }\n    });\n    this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound);\n  }\n  filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound){\n    let audioTracks=[];\n    let subtitleTracks=[];\n    let levels=filteredLevels;\n\n    // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled\n    if((resolutionFound||videoCodecFound)&&audioCodecFound){\n      levels=levels.filter(({\n        videoCodec,\n        videoRange,\n        width,\n        height\n      })=> (!!videoCodec||!!(width&&height))&&isVideoRange(videoRange));\n    }\n    if(levels.length===0){\n      // Dispatch error after MANIFEST_LOADED is done propagating\n      Promise.resolve().then(()=> {\n        if(this.hls){\n          if(data.levels.length){\n            this.warn(`One or more CODECS in variant not supported: ${JSON.stringify(data.levels[0].attrs)}`);\n          }\n          const error=new Error('no level with compatible codecs found in manifest');\n          this.hls.trigger(Events.ERROR, {\n            type: ErrorTypes.MEDIA_ERROR,\n            details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,\n            fatal: true,\n            url: data.url,\n            error,\n            reason: error.message\n          });\n        }\n      });\n      return;\n    }\n    if(data.audioTracks){\n      const {\n        preferManagedMediaSource\n      }=this.hls.config;\n      audioTracks=data.audioTracks.filter(track=> !track.audioCodec||areCodecsMediaSourceSupported(track.audioCodec, 'audio', preferManagedMediaSource));\n      // Assign ids after filtering as array indices by group-id\n      assignTrackIdsByGroup(audioTracks);\n    }\n    if(data.subtitles){\n      subtitleTracks=data.subtitles;\n      assignTrackIdsByGroup(subtitleTracks);\n    }\n    // start bitrate is the first bitrate of the manifest\n    const unsortedLevels=levels.slice(0);\n    // sort levels from lowest to highest\n    levels.sort((a, b)=> {\n      if(a.attrs['HDCP-LEVEL']!==b.attrs['HDCP-LEVEL']){\n        return (a.attrs['HDCP-LEVEL']||'') > (b.attrs['HDCP-LEVEL']||'') ? 1:-1;\n      }\n      // sort on height before bitrate for cap-level-controller\n      if(resolutionFound&&a.height!==b.height){\n        return a.height - b.height;\n      }\n      if(a.frameRate!==b.frameRate){\n        return a.frameRate - b.frameRate;\n      }\n      if(a.videoRange!==b.videoRange){\n        return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange);\n      }\n      if(a.videoCodec!==b.videoCodec){\n        const valueA=videoCodecPreferenceValue(a.videoCodec);\n        const valueB=videoCodecPreferenceValue(b.videoCodec);\n        if(valueA!==valueB){\n          return valueB - valueA;\n        }\n      }\n      if(a.uri===b.uri&&a.codecSet!==b.codecSet){\n        const valueA=codecsSetSelectionPreferenceValue(a.codecSet);\n        const valueB=codecsSetSelectionPreferenceValue(b.codecSet);\n        if(valueA!==valueB){\n          return valueB - valueA;\n        }\n      }\n      if(a.averageBitrate!==b.averageBitrate){\n        return a.averageBitrate - b.averageBitrate;\n      }\n      return 0;\n    });\n    let firstLevelInPlaylist=unsortedLevels[0];\n    if(this.steering){\n      levels=this.steering.filterParsedLevels(levels);\n      if(levels.length!==unsortedLevels.length){\n        for (let i=0; i < unsortedLevels.length; i++){\n          if(unsortedLevels[i].pathwayId===levels[0].pathwayId){\n            firstLevelInPlaylist=unsortedLevels[i];\n            break;\n          }\n        }\n      }\n    }\n    this._levels=levels;\n\n    // find index of first level in sorted levels\n    for (let i=0; i < levels.length; i++){\n      if(levels[i]===firstLevelInPlaylist){\n        var _this$hls$userConfig;\n        this._firstLevel=i;\n        const firstLevelBitrate=firstLevelInPlaylist.bitrate;\n        const bandwidthEstimate=this.hls.bandwidthEstimate;\n        this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`);\n        // Update default bwe to first variant bitrate as long it has not been configured or set\n        if(((_this$hls$userConfig=this.hls.userConfig)==null ? void 0:_this$hls$userConfig.abrEwmaDefaultEstimate)===undefined){\n          const startingBwEstimate=Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax);\n          if(startingBwEstimate > bandwidthEstimate&&bandwidthEstimate===hlsDefaultConfig.abrEwmaDefaultEstimate){\n            this.hls.bandwidthEstimate=startingBwEstimate;\n          }\n        }\n        break;\n      }\n    }\n\n    // Audio is only alternate if manifest include a URI along with the audio group tag,\n    // and this is not an audio-only stream where levels contain audio-only\n    const audioOnly=audioCodecFound&&!videoCodecFound;\n    const edata={\n      levels,\n      audioTracks,\n      subtitleTracks,\n      sessionData: data.sessionData,\n      sessionKeys: data.sessionKeys,\n      firstLevel: this._firstLevel,\n      stats: data.stats,\n      audio: audioCodecFound,\n      video: videoCodecFound,\n      altAudio: !audioOnly&&audioTracks.some(t=> !!t.url)\n    };\n    this.hls.trigger(Events.MANIFEST_PARSED, edata);\n\n    // Initiate loading after all controllers have received MANIFEST_PARSED\n    if(this.hls.config.autoStartLoad||this.hls.forceStartLoad){\n      this.hls.startLoad(this.hls.config.startPosition);\n    }\n  }\n  get levels(){\n    if(this._levels.length===0){\n      return null;\n    }\n    return this._levels;\n  }\n  get level(){\n    return this.currentLevelIndex;\n  }\n  set level(newLevel){\n    const levels=this._levels;\n    if(levels.length===0){\n      return;\n    }\n    // check if level idx is valid\n    if(newLevel < 0||newLevel >=levels.length){\n      // invalid level id given, trigger error\n      const error=new Error('invalid level idx');\n      const fatal=newLevel < 0;\n      this.hls.trigger(Events.ERROR, {\n        type: ErrorTypes.OTHER_ERROR,\n        details: ErrorDetails.LEVEL_SWITCH_ERROR,\n        level: newLevel,\n        fatal,\n        error,\n        reason: error.message\n      });\n      if(fatal){\n        return;\n      }\n      newLevel=Math.min(newLevel, levels.length - 1);\n    }\n    const lastLevelIndex=this.currentLevelIndex;\n    const lastLevel=this.currentLevel;\n    const lastPathwayId=lastLevel ? lastLevel.attrs['PATHWAY-ID']:undefined;\n    const level=levels[newLevel];\n    const pathwayId=level.attrs['PATHWAY-ID'];\n    this.currentLevelIndex=newLevel;\n    this.currentLevel=level;\n    if(lastLevelIndex===newLevel&&level.details&&lastLevel&&lastPathwayId===pathwayId){\n      return;\n    }\n    this.log(`Switching to level ${newLevel} (${level.height ? level.height + 'p ':''}${level.videoRange ? level.videoRange + ' ':''}${level.codecSet ? level.codecSet + ' ':''}@${level.bitrate})${pathwayId ? ' with Pathway ' + pathwayId:''} from level ${lastLevelIndex}${lastPathwayId ? ' with Pathway ' + lastPathwayId:''}`);\n    const levelSwitchingData={\n      level: newLevel,\n      attrs: level.attrs,\n      details: level.details,\n      bitrate: level.bitrate,\n      averageBitrate: level.averageBitrate,\n      maxBitrate: level.maxBitrate,\n      realBitrate: level.realBitrate,\n      width: level.width,\n      height: level.height,\n      codecSet: level.codecSet,\n      audioCodec: level.audioCodec,\n      videoCodec: level.videoCodec,\n      audioGroups: level.audioGroups,\n      subtitleGroups: level.subtitleGroups,\n      loaded: level.loaded,\n      loadError: level.loadError,\n      fragmentError: level.fragmentError,\n      name: level.name,\n      id: level.id,\n      uri: level.uri,\n      url: level.url,\n      urlId: 0,\n      audioGroupIds: level.audioGroupIds,\n      textGroupIds: level.textGroupIds\n    };\n    this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);\n    // check if we need to load playlist for this level\n    const levelDetails=level.details;\n    if(!levelDetails||levelDetails.live){\n      // level not retrieved yet, or live playlist we need to (re)load it\n      const hlsUrlParameters=this.switchParams(level.uri, lastLevel==null ? void 0:lastLevel.details, levelDetails);\n      this.loadPlaylist(hlsUrlParameters);\n    }\n  }\n  get manualLevel(){\n    return this.manualLevelIndex;\n  }\n  set manualLevel(newLevel){\n    this.manualLevelIndex=newLevel;\n    if(this._startLevel===undefined){\n      this._startLevel=newLevel;\n    }\n    if(newLevel!==-1){\n      this.level=newLevel;\n    }\n  }\n  get firstLevel(){\n    return this._firstLevel;\n  }\n  set firstLevel(newLevel){\n    this._firstLevel=newLevel;\n  }\n  get startLevel(){\n    // Setting hls.startLevel (this._startLevel) overrides config.startLevel\n    if(this._startLevel===undefined){\n      const configStartLevel=this.hls.config.startLevel;\n      if(configStartLevel!==undefined){\n        return configStartLevel;\n      }\n      return this.hls.firstAutoLevel;\n    }\n    return this._startLevel;\n  }\n  set startLevel(newLevel){\n    this._startLevel=newLevel;\n  }\n  onError(event, data){\n    if(data.fatal||!data.context){\n      return;\n    }\n    if(data.context.type===PlaylistContextType.LEVEL&&data.context.level===this.level){\n      this.checkRetry(data);\n    }\n  }\n\n  // reset errors on the successful load of a fragment\n  onFragBuffered(event, {\n    frag\n  }){\n    if(frag!==undefined&&frag.type===PlaylistLevelType.MAIN){\n      const el=frag.elementaryStreams;\n      if(!Object.keys(el).some(type=> !!el[type])){\n        return;\n      }\n      const level=this._levels[frag.level];\n      if(level!=null&&level.loadError){\n        this.log(`Resetting level error count of ${level.loadError} on frag buffered`);\n        level.loadError=0;\n      }\n    }\n  }\n  onLevelLoaded(event, data){\n    var _data$deliveryDirecti2;\n    const {\n      level,\n      details\n    }=data;\n    const curLevel=this._levels[level];\n    if(!curLevel){\n      var _data$deliveryDirecti;\n      this.warn(`Invalid level index ${level}`);\n      if((_data$deliveryDirecti=data.deliveryDirectives)!=null&&_data$deliveryDirecti.skip){\n        details.deltaUpdateFailed=true;\n      }\n      return;\n    }\n\n    // only process level loaded events matching with expected level\n    if(level===this.currentLevelIndex){\n      // reset level load error counter on successful level loaded only if there is no issues with fragments\n      if(curLevel.fragmentError===0){\n        curLevel.loadError=0;\n      }\n      this.playlistLoaded(level, data, curLevel.details);\n    }else if((_data$deliveryDirecti2=data.deliveryDirectives)!=null&&_data$deliveryDirecti2.skip){\n      // received a delta playlist update that cannot be merged\n      details.deltaUpdateFailed=true;\n    }\n  }\n  loadPlaylist(hlsUrlParameters){\n    super.loadPlaylist();\n    const currentLevelIndex=this.currentLevelIndex;\n    const currentLevel=this.currentLevel;\n    if(currentLevel&&this.shouldLoadPlaylist(currentLevel)){\n      let url=currentLevel.uri;\n      if(hlsUrlParameters){\n        try {\n          url=hlsUrlParameters.addDirectives(url);\n        } catch (error){\n          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);\n        }\n      }\n      const pathwayId=currentLevel.attrs['PATHWAY-ID'];\n      this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters==null ? void 0:hlsUrlParameters.msn)!==undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part:''} with${pathwayId ? ' Pathway ' + pathwayId:''} ${url}`);\n\n      // console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId);\n      // console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level);\n      this.clearTimer();\n      this.hls.trigger(Events.LEVEL_LOADING, {\n        url,\n        level: currentLevelIndex,\n        pathwayId: currentLevel.attrs['PATHWAY-ID'],\n        id: 0,\n        // Deprecated Level urlId\n        deliveryDirectives: hlsUrlParameters||null\n      });\n    }\n  }\n  get nextLoadLevel(){\n    if(this.manualLevelIndex!==-1){\n      return this.manualLevelIndex;\n    }else{\n      return this.hls.nextAutoLevel;\n    }\n  }\n  set nextLoadLevel(nextLevel){\n    this.level=nextLevel;\n    if(this.manualLevelIndex===-1){\n      this.hls.nextAutoLevel=nextLevel;\n    }\n  }\n  removeLevel(levelIndex){\n    var _this$currentLevel;\n    const levels=this._levels.filter((level, index)=> {\n      if(index!==levelIndex){\n        return true;\n      }\n      if(this.steering){\n        this.steering.removeLevel(level);\n      }\n      if(level===this.currentLevel){\n        this.currentLevel=null;\n        this.currentLevelIndex=-1;\n        if(level.details){\n          level.details.fragments.forEach(f=> f.level=-1);\n        }\n      }\n      return false;\n    });\n    reassignFragmentLevelIndexes(levels);\n    this._levels=levels;\n    if(this.currentLevelIndex > -1&&(_this$currentLevel=this.currentLevel)!=null&&_this$currentLevel.details){\n      this.currentLevelIndex=this.currentLevel.details.fragments[0].level;\n    }\n    this.hls.trigger(Events.LEVELS_UPDATED, {\n      levels\n    });\n  }\n  onLevelsUpdated(event, {\n    levels\n  }){\n    this._levels=levels;\n  }\n  checkMaxAutoUpdated(){\n    const {\n      autoLevelCapping,\n      maxAutoLevel,\n      maxHdcpLevel\n    }=this.hls;\n    if(this._maxAutoLevel!==maxAutoLevel){\n      this._maxAutoLevel=maxAutoLevel;\n      this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {\n        autoLevelCapping,\n        levels: this.levels,\n        maxAutoLevel,\n        minAutoLevel: this.hls.minAutoLevel,\n        maxHdcpLevel\n      });\n    }\n  }\n}\nfunction assignTrackIdsByGroup(tracks){\n  const groups={};\n  tracks.forEach(track=> {\n    const groupId=track.groupId||'';\n    track.id=groups[groupId]=groups[groupId]||0;\n    groups[groupId]++;\n  });\n}\n\nclass KeyLoader {\n  constructor(config){\n    this.config=void 0;\n    this.keyUriToKeyInfo={};\n    this.emeController=null;\n    this.config=config;\n  }\n  abort(type){\n    for (const uri in this.keyUriToKeyInfo){\n      const loader=this.keyUriToKeyInfo[uri].loader;\n      if(loader){\n        var _loader$context;\n        if(type&&type!==((_loader$context=loader.context)==null ? void 0:_loader$context.frag.type)){\n          return;\n        }\n        loader.abort();\n      }\n    }\n  }\n  detach(){\n    for (const uri in this.keyUriToKeyInfo){\n      const keyInfo=this.keyUriToKeyInfo[uri];\n      // Remove cached EME keys on detach\n      if(keyInfo.mediaKeySessionContext||keyInfo.decryptdata.isCommonEncryption){\n        delete this.keyUriToKeyInfo[uri];\n      }\n    }\n  }\n  destroy(){\n    this.detach();\n    for (const uri in this.keyUriToKeyInfo){\n      const loader=this.keyUriToKeyInfo[uri].loader;\n      if(loader){\n        loader.destroy();\n      }\n    }\n    this.keyUriToKeyInfo={};\n  }\n  createKeyLoadError(frag, details=ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response){\n    return new LoadError({\n      type: ErrorTypes.NETWORK_ERROR,\n      details,\n      fatal: false,\n      frag,\n      response,\n      error,\n      networkDetails\n    });\n  }\n  loadClear(loadingFrag, encryptedFragments){\n    if(this.emeController&&this.config.emeEnabled){\n      // access key-system with nearest key on start (loaidng frag is unencrypted)\n      const {\n        sn,\n        cc\n      }=loadingFrag;\n      for (let i=0; i < encryptedFragments.length; i++){\n        const frag=encryptedFragments[i];\n        if(cc <=frag.cc&&(sn==='initSegment'||frag.sn==='initSegment'||sn < frag.sn)){\n          this.emeController.selectKeySystemFormat(frag).then(keySystemFormat=> {\n            frag.setKeyFormat(keySystemFormat);\n          });\n          break;\n        }\n      }\n    }\n  }\n  load(frag){\n    if(!frag.decryptdata&&frag.encrypted&&this.emeController&&this.config.emeEnabled){\n      // Multiple keys, but none selected, resolve in eme-controller\n      return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat=> {\n        return this.loadInternal(frag, keySystemFormat);\n      });\n    }\n    return this.loadInternal(frag);\n  }\n  loadInternal(frag, keySystemFormat){\n    var _keyInfo, _keyInfo2;\n    if(keySystemFormat){\n      frag.setKeyFormat(keySystemFormat);\n    }\n    const decryptdata=frag.decryptdata;\n    if(!decryptdata){\n      const error=new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}`:'Missing decryption data on fragment in onKeyLoading');\n      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error));\n    }\n    const uri=decryptdata.uri;\n    if(!uri){\n      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: \"${uri}\"`)));\n    }\n    let keyInfo=this.keyUriToKeyInfo[uri];\n    if((_keyInfo=keyInfo)!=null&&_keyInfo.decryptdata.key){\n      decryptdata.key=keyInfo.decryptdata.key;\n      return Promise.resolve({\n        frag,\n        keyInfo\n      });\n    }\n    // Return key load promise as long as it does not have a mediakey session with an unusable key status\n    if((_keyInfo2=keyInfo)!=null&&_keyInfo2.keyLoadPromise){\n      var _keyInfo$mediaKeySess;\n      switch ((_keyInfo$mediaKeySess=keyInfo.mediaKeySessionContext)==null ? void 0:_keyInfo$mediaKeySess.keyStatus){\n        case undefined:\n        case 'status-pending':\n        case 'usable':\n        case 'usable-in-future':\n          return keyInfo.keyLoadPromise.then(keyLoadedData=> {\n            // Return the correct fragment with updated decryptdata key and loaded keyInfo\n            decryptdata.key=keyLoadedData.keyInfo.decryptdata.key;\n            return {\n              frag,\n              keyInfo\n            };\n          });\n      }\n      // If we have a key session and status and it is not pending or usable, continue\n      // This will go back to the eme-controller for expired keys to get a new keyLoadPromise\n    }\n\n    // Load the key or return the loading promise\n    keyInfo=this.keyUriToKeyInfo[uri]={\n      decryptdata,\n      keyLoadPromise: null,\n      loader: null,\n      mediaKeySessionContext: null\n    };\n    switch (decryptdata.method){\n      case 'ISO-23001-7':\n      case 'SAMPLE-AES':\n      case 'SAMPLE-AES-CENC':\n      case 'SAMPLE-AES-CTR':\n        if(decryptdata.keyFormat==='identity'){\n          // loadKeyHTTP handles http(s) and data URLs\n          return this.loadKeyHTTP(keyInfo, frag);\n        }\n        return this.loadKeyEME(keyInfo, frag);\n      case 'AES-128':\n        return this.loadKeyHTTP(keyInfo, frag);\n      default:\n        return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: \"${decryptdata.method}\"`)));\n    }\n  }\n  loadKeyEME(keyInfo, frag){\n    const keyLoadedData={\n      frag,\n      keyInfo\n    };\n    if(this.emeController&&this.config.emeEnabled){\n      const keySessionContextPromise=this.emeController.loadKey(keyLoadedData);\n      if(keySessionContextPromise){\n        return (keyInfo.keyLoadPromise=keySessionContextPromise.then(keySessionContext=> {\n          keyInfo.mediaKeySessionContext=keySessionContext;\n          return keyLoadedData;\n        })).catch(error=> {\n          // Remove promise for license renewal or retry\n          keyInfo.keyLoadPromise=null;\n          throw error;\n        });\n      }\n    }\n    return Promise.resolve(keyLoadedData);\n  }\n  loadKeyHTTP(keyInfo, frag){\n    const config=this.config;\n    const Loader=config.loader;\n    const keyLoader=new Loader(config);\n    frag.keyLoader=keyInfo.loader=keyLoader;\n    return keyInfo.keyLoadPromise=new Promise((resolve, reject)=> {\n      const loaderContext={\n        keyInfo,\n        frag,\n        responseType: 'arraybuffer',\n        url: keyInfo.decryptdata.uri\n      };\n\n      // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,\n      // key-loader will trigger an error and rely on stream-controller to handle retry logic.\n      // this will also align retry logic with fragment-loader\n      const loadPolicy=config.keyLoadPolicy.default;\n      const loaderConfig={\n        loadPolicy,\n        timeout: loadPolicy.maxLoadTimeMs,\n        maxRetry: 0,\n        retryDelay: 0,\n        maxRetryDelay: 0\n      };\n      const loaderCallbacks={\n        onSuccess: (response, stats, context, networkDetails)=> {\n          const {\n            frag,\n            keyInfo,\n            url: uri\n          }=context;\n          if(!frag.decryptdata||keyInfo!==this.keyUriToKeyInfo[uri]){\n            return reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails));\n          }\n          keyInfo.decryptdata.key=frag.decryptdata.key=new Uint8Array(response.data);\n\n          // detach fragment key loader on load success\n          frag.keyLoader=null;\n          keyInfo.loader=null;\n          resolve({\n            frag,\n            keyInfo\n          });\n        },\n        onError: (response, context, networkDetails, stats)=> {\n          this.resetLoader(context);\n          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({\n            url: loaderContext.url,\n            data: undefined\n          }, response)));\n        },\n        onTimeout: (stats, context, networkDetails)=> {\n          this.resetLoader(context);\n          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails));\n        },\n        onAbort: (stats, context, networkDetails)=> {\n          this.resetLoader(context);\n          reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails));\n        }\n      };\n      keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);\n    });\n  }\n  resetLoader(context){\n    const {\n      frag,\n      keyInfo,\n      url: uri\n    }=context;\n    const loader=keyInfo.loader;\n    if(frag.keyLoader===loader){\n      frag.keyLoader=null;\n      keyInfo.loader=null;\n    }\n    delete this.keyUriToKeyInfo[uri];\n    if(loader){\n      loader.destroy();\n    }\n  }\n}\n\nfunction getSourceBuffer(){\n  return self.SourceBuffer||self.WebKitSourceBuffer;\n}\nfunction isMSESupported(){\n  const mediaSource=getMediaSource();\n  if(!mediaSource){\n    return false;\n  }\n\n  // if SourceBuffer is exposed ensure its API is valid\n  // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible\n  const sourceBuffer=getSourceBuffer();\n  return !sourceBuffer||sourceBuffer.prototype&&typeof sourceBuffer.prototype.appendBuffer==='function'&&typeof sourceBuffer.prototype.remove==='function';\n}\nfunction isSupported(){\n  if(!isMSESupported()){\n    return false;\n  }\n  const mediaSource=getMediaSource();\n  return typeof (mediaSource==null ? void 0:mediaSource.isTypeSupported)==='function'&&(['avc1.42E01E,mp4a.40.2', 'av01.0.01M.08', 'vp09.00.50.08'].some(codecsForVideoContainer=> mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, 'video')))||['mp4a.40.2', 'fLaC'].some(codecForAudioContainer=> mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, 'audio'))));\n}\nfunction changeTypeSupported(){\n  var _sourceBuffer$prototy;\n  const sourceBuffer=getSourceBuffer();\n  return typeof (sourceBuffer==null ? void 0:(_sourceBuffer$prototy=sourceBuffer.prototype)==null ? void 0:_sourceBuffer$prototy.changeType)==='function';\n}\n\nconst STALL_MINIMUM_DURATION_MS=250;\nconst MAX_START_GAP_JUMP=2.0;\nconst SKIP_BUFFER_HOLE_STEP_SECONDS=0.1;\nconst SKIP_BUFFER_RANGE_START=0.05;\nclass GapController {\n  constructor(config, media, fragmentTracker, hls){\n    this.config=void 0;\n    this.media=null;\n    this.fragmentTracker=void 0;\n    this.hls=void 0;\n    this.nudgeRetry=0;\n    this.stallReported=false;\n    this.stalled=null;\n    this.moved=false;\n    this.seeking=false;\n    this.config=config;\n    this.media=media;\n    this.fragmentTracker=fragmentTracker;\n    this.hls=hls;\n  }\n  destroy(){\n    this.media=null;\n    // @ts-ignore\n    this.hls=this.fragmentTracker=null;\n  }\n\n  \n  poll(lastCurrentTime, activeFrag){\n    const {\n      config,\n      media,\n      stalled\n    }=this;\n    if(media===null){\n      return;\n    }\n    const {\n      currentTime,\n      seeking\n    }=media;\n    const seeked=this.seeking&&!seeking;\n    const beginSeek = !this.seeking&&seeking;\n    this.seeking=seeking;\n\n    // The playhead is moving, no-op\n    if(currentTime!==lastCurrentTime){\n      this.moved=true;\n      if(!seeking){\n        this.nudgeRetry=0;\n      }\n      if(stalled!==null){\n        // The playhead is now moving, but was previously stalled\n        if(this.stallReported){\n          const _stalledDuration=self.performance.now() - stalled;\n          logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);\n          this.stallReported=false;\n        }\n        this.stalled=null;\n      }\n      return;\n    }\n\n    // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek\n    if(beginSeek||seeked){\n      this.stalled=null;\n      return;\n    }\n\n    // The playhead should not be moving\n    if(media.paused&&!seeking||media.ended||media.playbackRate===0||!BufferHelper.getBuffered(media).length){\n      this.nudgeRetry=0;\n      return;\n    }\n    const bufferInfo=BufferHelper.bufferInfo(media, currentTime, 0);\n    const nextStart=bufferInfo.nextStart||0;\n    if(seeking){\n      // Waiting for seeking in a buffered range to complete\n      const hasEnoughBuffer=bufferInfo.len > MAX_START_GAP_JUMP;\n      // Next buffered range is too far ahead to jump to while still seeking\n      const noBufferGap = !nextStart||activeFrag&&activeFrag.start <=currentTime||nextStart - currentTime > MAX_START_GAP_JUMP&&!this.fragmentTracker.getPartialFragment(currentTime);\n      if(hasEnoughBuffer||noBufferGap){\n        return;\n      }\n      // Reset moved state when seeking to a point in or before a gap\n      this.moved=false;\n    }\n\n    // Skip start gaps if we haven't played, but the last poll detected the start of a stall\n    // The addition poll gives the browser a chance to jump the gap for us\n    if(!this.moved&&this.stalled!==null){\n      var _level$details;\n      // There is no playable buffer (seeked, waiting for buffer)\n      const isBuffered=bufferInfo.len > 0;\n      if(!isBuffered&&!nextStart){\n        return;\n      }\n      // Jump start gaps within jump threshold\n      const startJump=Math.max(nextStart, bufferInfo.start||0) - currentTime;\n\n      // When joining a live stream with audio tracks, account for live playlist window sliding by allowing\n      // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment\n      // that begins over 1 target duration after the video start position.\n      const level=this.hls.levels ? this.hls.levels[this.hls.currentLevel]:null;\n      const isLive=level==null ? void 0:(_level$details=level.details)==null ? void 0:_level$details.live;\n      const maxStartGapJump=isLive ? level.details.targetduration * 2:MAX_START_GAP_JUMP;\n      const partialOrGap=this.fragmentTracker.getPartialFragment(currentTime);\n      if(startJump > 0&&(startJump <=maxStartGapJump||partialOrGap)){\n        if(!media.paused){\n          this._trySkipBufferHole(partialOrGap);\n        }\n        return;\n      }\n    }\n\n    // Start tracking stall time\n    const tnow=self.performance.now();\n    if(stalled===null){\n      this.stalled=tnow;\n      return;\n    }\n    const stalledDuration=tnow - stalled;\n    if(!seeking&&stalledDuration >=STALL_MINIMUM_DURATION_MS){\n      // Report stalling after trying to fix\n      this._reportStall(bufferInfo);\n      if(!this.media){\n        return;\n      }\n    }\n    const bufferedWithHoles=BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);\n    this._tryFixBufferStall(bufferedWithHoles, stalledDuration);\n  }\n\n  \n  _tryFixBufferStall(bufferInfo, stalledDurationMs){\n    const {\n      config,\n      fragmentTracker,\n      media\n    }=this;\n    if(media===null){\n      return;\n    }\n    const currentTime=media.currentTime;\n    const partial=fragmentTracker.getPartialFragment(currentTime);\n    if(partial){\n      // Try to skip over the buffer hole caused by a partial fragment\n      // This method isn't limited by the size of the gap between buffered ranges\n      const targetTime=this._trySkipBufferHole(partial);\n      // we return here in this case, meaning\n      // the branch below only executes when we haven't seeked to a new position\n      if(targetTime||!this.media){\n        return;\n      }\n    }\n\n    // if we haven't had to skip over a buffer hole of a partial fragment\n    // we may just have to \"nudge\" the playlist as the browser decoding/rendering engine\n    // needs to cross some sort of threshold covering all source-buffers content\n    // to start playing properly.\n    if((bufferInfo.len > config.maxBufferHole||bufferInfo.nextStart&&bufferInfo.nextStart - currentTime < config.maxBufferHole)&&stalledDurationMs > config.highBufferWatchdogPeriod * 1000){\n      logger.warn('Trying to nudge playhead over buffer-hole');\n      // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds\n      // We only try to jump the hole if it's under the configured size\n      // Reset stalled so to rearm watchdog timer\n      this.stalled=null;\n      this._tryNudgeBuffer();\n    }\n  }\n\n  \n  _reportStall(bufferInfo){\n    const {\n      hls,\n      media,\n      stallReported\n    }=this;\n    if(!stallReported&&media){\n      // Report stalled error once\n      this.stallReported=true;\n      const error=new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);\n      logger.warn(error.message);\n      hls.trigger(Events.ERROR, {\n        type: ErrorTypes.MEDIA_ERROR,\n        details: ErrorDetails.BUFFER_STALLED_ERROR,\n        fatal: false,\n        error,\n        buffer: bufferInfo.len\n      });\n    }\n  }\n\n  \n  _trySkipBufferHole(partial){\n    const {\n      config,\n      hls,\n      media\n    }=this;\n    if(media===null){\n      return 0;\n    }\n\n    // Check if currentTime is between unbuffered regions of partial fragments\n    const currentTime=media.currentTime;\n    const bufferInfo=BufferHelper.bufferInfo(media, currentTime, 0);\n    const startTime=currentTime < bufferInfo.start ? bufferInfo.start:bufferInfo.nextStart;\n    if(startTime){\n      const bufferStarved=bufferInfo.len <=config.maxBufferHole;\n      const waiting=bufferInfo.len > 0&&bufferInfo.len < 1&&media.readyState < 3;\n      const gapLength=startTime - currentTime;\n      if(gapLength > 0&&(bufferStarved||waiting)){\n        // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial\n        if(gapLength > config.maxBufferHole){\n          const {\n            fragmentTracker\n          }=this;\n          let startGap=false;\n          if(currentTime===0){\n            const startFrag=fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);\n            if(startFrag&&startTime < startFrag.end){\n              startGap=true;\n            }\n          }\n          if(!startGap){\n            const startProvisioned=partial||fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);\n            if(startProvisioned){\n              let moreToLoad=false;\n              let pos=startProvisioned.end;\n              while (pos < startTime){\n                const provisioned=fragmentTracker.getPartialFragment(pos);\n                if(provisioned){\n                  pos +=provisioned.duration;\n                }else{\n                  moreToLoad=true;\n                  break;\n                }\n              }\n              if(moreToLoad){\n                return 0;\n              }\n            }\n          }\n        }\n        const targetTime=Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);\n        logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);\n        this.moved=true;\n        this.stalled=null;\n        media.currentTime=targetTime;\n        if(partial&&!partial.gap){\n          const error=new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);\n          hls.trigger(Events.ERROR, {\n            type: ErrorTypes.MEDIA_ERROR,\n            details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,\n            fatal: false,\n            error,\n            reason: error.message,\n            frag: partial\n          });\n        }\n        return targetTime;\n      }\n    }\n    return 0;\n  }\n\n  \n  _tryNudgeBuffer(){\n    const {\n      config,\n      hls,\n      media,\n      nudgeRetry\n    }=this;\n    if(media===null){\n      return;\n    }\n    const currentTime=media.currentTime;\n    this.nudgeRetry++;\n    if(nudgeRetry < config.nudgeMaxRetry){\n      const targetTime=currentTime + (nudgeRetry + 1) * config.nudgeOffset;\n      // playback stalled in buffered area ... let's nudge currentTime to try to overcome this\n      const error=new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);\n      logger.warn(error.message);\n      media.currentTime=targetTime;\n      hls.trigger(Events.ERROR, {\n        type: ErrorTypes.MEDIA_ERROR,\n        details: ErrorDetails.BUFFER_NUDGE_ON_STALL,\n        error,\n        fatal: false\n      });\n    }else{\n      const error=new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);\n      logger.error(error.message);\n      hls.trigger(Events.ERROR, {\n        type: ErrorTypes.MEDIA_ERROR,\n        details: ErrorDetails.BUFFER_STALLED_ERROR,\n        error,\n        fatal: true\n      });\n    }\n  }\n}\n\nconst TICK_INTERVAL=100; // how often to tick in ms\n\nclass StreamController extends BaseStreamController {\n  constructor(hls, fragmentTracker, keyLoader){\n    super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);\n    this.audioCodecSwap=false;\n    this.gapController=null;\n    this.level=-1;\n    this._forceStartLoad=false;\n    this.altAudio=false;\n    this.audioOnly=false;\n    this.fragPlaying=null;\n    this.onvplaying=null;\n    this.onvseeked=null;\n    this.fragLastKbps=0;\n    this.couldBacktrack=false;\n    this.backtrackFragment=null;\n    this.audioCodecSwitch=false;\n    this.videoBuffer=null;\n    this._registerListeners();\n  }\n  _registerListeners(){\n    const {\n      hls\n    }=this;\n    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);\n    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);\n    hls.on(Events.ERROR, this.onError, this);\n    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);\n    hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);\n    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);\n    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);\n    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n  _unregisterListeners(){\n    const {\n      hls\n    }=this;\n    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);\n    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);\n    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);\n    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);\n    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);\n    hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);\n    hls.off(Events.ERROR, this.onError, this);\n    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);\n    hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);\n    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);\n    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);\n    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);\n    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);\n  }\n  onHandlerDestroying(){\n    this._unregisterListeners();\n    super.onHandlerDestroying();\n  }\n  startLoad(startPosition){\n    if(this.levels){\n      const {\n        lastCurrentTime,\n        hls\n      }=this;\n      this.stopLoad();\n      this.setInterval(TICK_INTERVAL);\n      this.level=-1;\n      if(!this.startFragRequested){\n        // determine load level\n        let startLevel=hls.startLevel;\n        if(startLevel===-1){\n          if(hls.config.testBandwidth&&this.levels.length > 1){\n            // -1:guess start Level by doing a bitrate test by loading first fragment of lowest quality level\n            startLevel=0;\n            this.bitrateTest=true;\n          }else{\n            startLevel=hls.firstAutoLevel;\n          }\n        }\n        // set new level to playlist loader:this will trigger start level load\n        // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded\n        hls.nextLoadLevel=startLevel;\n        this.level=hls.loadLevel;\n        this.loadedmetadata=false;\n      }\n      // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime\n      if(lastCurrentTime > 0&&startPosition===-1){\n        this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);\n        startPosition=lastCurrentTime;\n      }\n      this.state=State.IDLE;\n      this.nextLoadPosition=this.startPosition=this.lastCurrentTime=startPosition;\n      this.tick();\n    }else{\n      this._forceStartLoad=true;\n      this.state=State.STOPPED;\n    }\n  }\n  stopLoad(){\n    this._forceStartLoad=false;\n    super.stopLoad();\n  }\n  doTick(){\n    switch (this.state){\n      case State.WAITING_LEVEL:\n        {\n          const {\n            levels,\n            level\n          }=this;\n          const currentLevel=levels==null ? void 0:levels[level];\n          const details=currentLevel==null ? void 0:currentLevel.details;\n          if(details&&(!details.live||this.levelLastLoaded===currentLevel)){\n            if(this.waitForCdnTuneIn(details)){\n              break;\n            }\n            this.state=State.IDLE;\n            break;\n          }else if(this.hls.nextLoadLevel!==this.level){\n            this.state=State.IDLE;\n            break;\n          }\n          break;\n        }\n      case State.FRAG_LOADING_WAITING_RETRY:\n        {\n          var _this$media;\n          const now=self.performance.now();\n          const retryDate=this.retryDate;\n          // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading\n          if(!retryDate||now >=retryDate||(_this$media=this.media)!=null&&_this$media.seeking){\n            const {\n              levels,\n              level\n            }=this;\n            const currentLevel=levels==null ? void 0:levels[level];\n            this.resetStartWhenNotLoaded(currentLevel||null);\n            this.state=State.IDLE;\n          }\n        }\n        break;\n    }\n    if(this.state===State.IDLE){\n      this.doTickIdle();\n    }\n    this.onTickEnd();\n  }\n  onTickEnd(){\n    super.onTickEnd();\n    this.checkBuffer();\n    this.checkFragmentChanged();\n  }\n  doTickIdle(){\n    const {\n      hls,\n      levelLastLoaded,\n      levels,\n      media\n    }=this;\n\n    // if start level not parsed yet OR\n    // if video not attached AND start fragment already requested OR start frag prefetch not enabled\n    // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment\n    if(levelLastLoaded===null||!media&&(this.startFragRequested||!hls.config.startFragPrefetch)){\n      return;\n    }\n\n    // If the \"main\" level is audio-only but we are loading an alternate track in the same group, do not load anything\n    if(this.altAudio&&this.audioOnly){\n      return;\n    }\n    const level=this.buffering ? hls.nextLoadLevel:hls.loadLevel;\n    if(!(levels!=null&&levels[level])){\n      return;\n    }\n    const levelInfo=levels[level];\n\n    // if buffer length is less than maxBufLen try to load a new fragment\n\n    const bufferInfo=this.getMainFwdBufferInfo();\n    if(bufferInfo===null){\n      return;\n    }\n    const lastDetails=this.getLevelDetails();\n    if(lastDetails&&this._streamEnded(bufferInfo, lastDetails)){\n      const data={};\n      if(this.altAudio){\n        data.type='video';\n      }\n      this.hls.trigger(Events.BUFFER_EOS, data);\n      this.state=State.ENDED;\n      return;\n    }\n    if(!this.buffering){\n      return;\n    }\n\n    // set next load level:this will trigger a playlist load if needed\n    if(hls.loadLevel!==level&&hls.manualLevel===-1){\n      this.log(`Adapting to level ${level} from level ${this.level}`);\n    }\n    this.level=hls.nextLoadLevel=level;\n    const levelDetails=levelInfo.details;\n    // if level info not retrieved yet, switch state and wait for level retrieval\n    // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load\n    // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist)\n    if(!levelDetails||this.state===State.WAITING_LEVEL||levelDetails.live&&this.levelLastLoaded!==levelInfo){\n      this.level=level;\n      this.state=State.WAITING_LEVEL;\n      return;\n    }\n    const bufferLen=bufferInfo.len;\n\n    // compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s\n    const maxBufLen=this.getMaxBufferLength(levelInfo.maxBitrate);\n\n    // Stay idle if we are still with buffer margins\n    if(bufferLen >=maxBufLen){\n      return;\n    }\n    if(this.backtrackFragment&&this.backtrackFragment.start > bufferInfo.end){\n      this.backtrackFragment=null;\n    }\n    const targetBufferTime=this.backtrackFragment ? this.backtrackFragment.start:bufferInfo.end;\n    let frag=this.getNextFragment(targetBufferTime, levelDetails);\n    // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`)\n    if(this.couldBacktrack&&!this.fragPrevious&&frag&&frag.sn!=='initSegment'&&this.fragmentTracker.getState(frag)!==FragmentState.OK){\n      var _this$backtrackFragme;\n      const backtrackSn=((_this$backtrackFragme=this.backtrackFragment)!=null ? _this$backtrackFragme:frag).sn;\n      const fragIdx=backtrackSn - levelDetails.startSN;\n      const backtrackFrag=levelDetails.fragments[fragIdx - 1];\n      if(backtrackFrag&&frag.cc===backtrackFrag.cc){\n        frag=backtrackFrag;\n        this.fragmentTracker.removeFragment(backtrackFrag);\n      }\n    }else if(this.backtrackFragment&&bufferInfo.len){\n      this.backtrackFragment=null;\n    }\n    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags\n    if(frag&&this.isLoopLoading(frag, targetBufferTime)){\n      const gapStart=frag.gap;\n      if(!gapStart){\n        // Cleanup the fragment tracker before trying to find the next unbuffered fragment\n        const type=this.audioOnly&&!this.altAudio ? ElementaryStreamTypes.AUDIO:ElementaryStreamTypes.VIDEO;\n        const mediaBuffer=(type===ElementaryStreamTypes.VIDEO ? this.videoBuffer:this.mediaBuffer)||this.media;\n        if(mediaBuffer){\n          this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);\n        }\n      }\n      frag=this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);\n    }\n    if(!frag){\n      return;\n    }\n    if(frag.initSegment&&!frag.initSegment.data&&!this.bitrateTest){\n      frag=frag.initSegment;\n    }\n    this.loadFragment(frag, levelInfo, targetBufferTime);\n  }\n  loadFragment(frag, level, targetBufferTime){\n    // Check if fragment is not loaded\n    const fragState=this.fragmentTracker.getState(frag);\n    this.fragCurrent=frag;\n    if(fragState===FragmentState.NOT_LOADED||fragState===FragmentState.PARTIAL){\n      if(frag.sn==='initSegment'){\n        this._loadInitSegment(frag, level);\n      }else if(this.bitrateTest){\n        this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);\n        this._loadBitrateTestFrag(frag, level);\n      }else{\n        this.startFragRequested=true;\n        super.loadFragment(frag, level, targetBufferTime);\n      }\n    }else{\n      this.clearTrackerIfNeeded(frag);\n    }\n  }\n  getBufferedFrag(position){\n    return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN);\n  }\n  followingBufferedFrag(frag){\n    if(frag){\n      // try to get range of next fragment (500ms after this range)\n      return this.getBufferedFrag(frag.end + 0.5);\n    }\n    return null;\n  }\n\n  \n  immediateLevelSwitch(){\n    this.abortCurrentFrag();\n    this.flushMainBuffer(0, Number.POSITIVE_INFINITY);\n  }\n\n  \n  nextLevelSwitch(){\n    const {\n      levels,\n      media\n    }=this;\n    // ensure that media is defined and that metadata are available (to retrieve currentTime)\n    if(media!=null&&media.readyState){\n      let fetchdelay;\n      const fragPlayingCurrent=this.getAppendedFrag(media.currentTime);\n      if(fragPlayingCurrent&&fragPlayingCurrent.start > 1){\n        // flush buffer preceding current fragment (flush until current fragment start offset)\n        // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...\n        this.flushMainBuffer(0, fragPlayingCurrent.start - 1);\n      }\n      const levelDetails=this.getLevelDetails();\n      if(levelDetails!=null&&levelDetails.live){\n        const bufferInfo=this.getMainFwdBufferInfo();\n        // Do not flush in live stream with low buffer\n        if(!bufferInfo||bufferInfo.len < levelDetails.targetduration * 2){\n          return;\n        }\n      }\n      if(!media.paused&&levels){\n        // add a safety delay of 1s\n        const nextLevelId=this.hls.nextLoadLevel;\n        const nextLevel=levels[nextLevelId];\n        const fragLastKbps=this.fragLastKbps;\n        if(fragLastKbps&&this.fragCurrent){\n          fetchdelay=this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1;\n        }else{\n          fetchdelay=0;\n        }\n      }else{\n        fetchdelay=0;\n      }\n      // this.log('fetchdelay:'+fetchdelay);\n      // find buffer range that will be reached once new fragment will be fetched\n      const bufferedFrag=this.getBufferedFrag(media.currentTime + fetchdelay);\n      if(bufferedFrag){\n        // we can flush buffer range following this one without stalling playback\n        const nextBufferedFrag=this.followingBufferedFrag(bufferedFrag);\n        if(nextBufferedFrag){\n          // if we are here, we can also cancel any loading/demuxing in progress, as they are useless\n          this.abortCurrentFrag();\n          // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback.\n          const maxStart=nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS:nextBufferedFrag.start;\n          const fragDuration=nextBufferedFrag.duration;\n          const startPts=Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5:0.125)), fragDuration * (this.couldBacktrack ? 0.75:0.25)));\n          this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY);\n        }\n      }\n    }\n  }\n  abortCurrentFrag(){\n    const fragCurrent=this.fragCurrent;\n    this.fragCurrent=null;\n    this.backtrackFragment=null;\n    if(fragCurrent){\n      fragCurrent.abortRequests();\n      this.fragmentTracker.removeFragment(fragCurrent);\n    }\n    switch (this.state){\n      case State.KEY_LOADING:\n      case State.FRAG_LOADING:\n      case State.FRAG_LOADING_WAITING_RETRY:\n      case State.PARSING:\n      case State.PARSED:\n        this.state=State.IDLE;\n        break;\n    }\n    this.nextLoadPosition=this.getLoadPosition();\n  }\n  flushMainBuffer(startOffset, endOffset){\n    super.flushMainBuffer(startOffset, endOffset, this.altAudio ? 'video':null);\n  }\n  onMediaAttached(event, data){\n    super.onMediaAttached(event, data);\n    const media=data.media;\n    this.onvplaying=this.onMediaPlaying.bind(this);\n    this.onvseeked=this.onMediaSeeked.bind(this);\n    media.addEventListener('playing', this.onvplaying);\n    media.addEventListener('seeked', this.onvseeked);\n    this.gapController=new GapController(this.config, media, this.fragmentTracker, this.hls);\n  }\n  onMediaDetaching(){\n    const {\n      media\n    }=this;\n    if(media&&this.onvplaying&&this.onvseeked){\n      media.removeEventListener('playing', this.onvplaying);\n      media.removeEventListener('seeked', this.onvseeked);\n      this.onvplaying=this.onvseeked=null;\n      this.videoBuffer=null;\n    }\n    this.fragPlaying=null;\n    if(this.gapController){\n      this.gapController.destroy();\n      this.gapController=null;\n    }\n    super.onMediaDetaching();\n  }\n  onMediaPlaying(){\n    // tick to speed up FRAG_CHANGED triggering\n    this.tick();\n  }\n  onMediaSeeked(){\n    const media=this.media;\n    const currentTime=media ? media.currentTime:null;\n    if(isFiniteNumber(currentTime)){\n      this.log(`Media seeked to ${currentTime.toFixed(3)}`);\n    }\n\n    // If seeked was issued before buffer was appended do not tick immediately\n    const bufferInfo=this.getMainFwdBufferInfo();\n    if(bufferInfo===null||bufferInfo.len===0){\n      this.warn(`Main forward buffer length on \"seeked\" event ${bufferInfo ? bufferInfo.len:'empty'})`);\n      return;\n    }\n\n    // tick to speed up FRAG_CHANGED triggering\n    this.tick();\n  }\n  onManifestLoading(){\n    // reset buffer on manifest loading\n    this.log('Trigger BUFFER_RESET');\n    this.hls.trigger(Events.BUFFER_RESET, undefined);\n    this.fragmentTracker.removeAllFragments();\n    this.couldBacktrack=false;\n    this.startPosition=this.lastCurrentTime=this.fragLastKbps=0;\n    this.levels=this.fragPlaying=this.backtrackFragment=this.levelLastLoaded=null;\n    this.altAudio=this.audioOnly=this.startFragRequested=false;\n  }\n  onManifestParsed(event, data){\n    // detect if we have different kind of audio codecs used amongst playlists\n    let aac=false;\n    let heaac=false;\n    data.levels.forEach(level=> {\n      const codec=level.audioCodec;\n      if(codec){\n        aac=aac||codec.indexOf('mp4a.40.2')!==-1;\n        heaac=heaac||codec.indexOf('mp4a.40.5')!==-1;\n      }\n    });\n    this.audioCodecSwitch=aac&&heaac&&!changeTypeSupported();\n    if(this.audioCodecSwitch){\n      this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC');\n    }\n    this.levels=data.levels;\n    this.startFragRequested=false;\n  }\n  onLevelLoading(event, data){\n    const {\n      levels\n    }=this;\n    if(!levels||this.state!==State.IDLE){\n      return;\n    }\n    const level=levels[data.level];\n    if(!level.details||level.details.live&&this.levelLastLoaded!==level||this.waitForCdnTuneIn(level.details)){\n      this.state=State.WAITING_LEVEL;\n    }\n  }\n  onLevelLoaded(event, data){\n    var _curLevel$details;\n    const {\n      levels\n    }=this;\n    const newLevelId=data.level;\n    const newDetails=data.details;\n    const duration=newDetails.totalduration;\n    if(!levels){\n      this.warn(`Levels were reset while loading level ${newLevelId}`);\n      return;\n    }\n    this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`:''}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`);\n    const curLevel=levels[newLevelId];\n    const fragCurrent=this.fragCurrent;\n    if(fragCurrent&&(this.state===State.FRAG_LOADING||this.state===State.FRAG_LOADING_WAITING_RETRY)){\n      if(fragCurrent.level!==data.level&&fragCurrent.loader){\n        this.abortCurrentFrag();\n      }\n    }\n    let sliding=0;\n    if(newDetails.live||(_curLevel$details=curLevel.details)!=null&&_curLevel$details.live){\n      var _this$levelLastLoaded;\n      this.checkLiveUpdate(newDetails);\n      if(newDetails.deltaUpdateFailed){\n        return;\n      }\n      sliding=this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded=this.levelLastLoaded)==null ? void 0:_this$levelLastLoaded.details);\n    }\n    // override level info\n    curLevel.details=newDetails;\n    this.levelLastLoaded=curLevel;\n    this.hls.trigger(Events.LEVEL_UPDATED, {\n      details: newDetails,\n      level: newLevelId\n    });\n\n    // only switch back to IDLE state if we were waiting for level to start downloading a new fragment\n    if(this.state===State.WAITING_LEVEL){\n      if(this.waitForCdnTuneIn(newDetails)){\n        // Wait for Low-Latency CDN Tune-in\n        return;\n      }\n      this.state=State.IDLE;\n    }\n    if(!this.startFragRequested){\n      this.setStartPosition(newDetails, sliding);\n    }else if(newDetails.live){\n      this.synchronizeToLiveEdge(newDetails);\n    }\n\n    // trigger handler right now\n    this.tick();\n  }\n  _handleFragmentLoadProgress(data){\n    var _frag$initSegment;\n    const {\n      frag,\n      part,\n      payload\n    }=data;\n    const {\n      levels\n    }=this;\n    if(!levels){\n      this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);\n      return;\n    }\n    const currentLevel=levels[frag.level];\n    const details=currentLevel.details;\n    if(!details){\n      this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`);\n      this.fragmentTracker.removeFragment(frag);\n      return;\n    }\n    const videoCodec=currentLevel.videoCodec;\n\n    // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)\n    const accurateTimeOffset=details.PTSKnown||!details.live;\n    const initSegmentData=(_frag$initSegment=frag.initSegment)==null ? void 0:_frag$initSegment.data;\n    const audioCodec=this._getAudioCodec(currentLevel);\n\n    // transmux the MPEG-TS data to ISO-BMFF segments\n    // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`);\n    const transmuxer=this.transmuxer=this.transmuxer||new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));\n    const partIndex=part ? part.index:-1;\n    const partial=partIndex!==-1;\n    const chunkMeta=new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);\n    const initPTS=this.initPTS[frag.cc];\n    transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);\n  }\n  onAudioTrackSwitching(event, data){\n    // if any URL found on new audio track, it is an alternate audio track\n    const fromAltAudio=this.altAudio;\n    const altAudio = !!data.url;\n    // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered\n    // don't do anything if we switch to alt audio: audio stream controller is handling it.\n    // we will just have to change buffer scheduling on audioTrackSwitched\n    if(!altAudio){\n      if(this.mediaBuffer!==this.media){\n        this.log('Switching on main audio, use media.buffered to schedule main fragment loading');\n        this.mediaBuffer=this.media;\n        const fragCurrent=this.fragCurrent;\n        // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch\n        if(fragCurrent){\n          this.log('Switching to main audio track, cancel main fragment load');\n          fragCurrent.abortRequests();\n          this.fragmentTracker.removeFragment(fragCurrent);\n        }\n        // destroy transmuxer to force init segment generation (following audio switch)\n        this.resetTransmuxer();\n        // switch to IDLE state to load new fragment\n        this.resetLoadingState();\n      }else if(this.audioOnly){\n        // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off\n        this.resetTransmuxer();\n      }\n      const hls=this.hls;\n      // If switching from alt to main audio, flush all audio and trigger track switched\n      if(fromAltAudio){\n        hls.trigger(Events.BUFFER_FLUSHING, {\n          startOffset: 0,\n          endOffset: Number.POSITIVE_INFINITY,\n          type: null\n        });\n        this.fragmentTracker.removeAllFragments();\n      }\n      hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);\n    }\n  }\n  onAudioTrackSwitched(event, data){\n    const trackId=data.id;\n    const altAudio = !!this.hls.audioTracks[trackId].url;\n    if(altAudio){\n      const videoBuffer=this.videoBuffer;\n      // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered\n      if(videoBuffer&&this.mediaBuffer!==videoBuffer){\n        this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading');\n        this.mediaBuffer=videoBuffer;\n      }\n    }\n    this.altAudio=altAudio;\n    this.tick();\n  }\n  onBufferCreated(event, data){\n    const tracks=data.tracks;\n    let mediaTrack;\n    let name;\n    let alternate=false;\n    for (const type in tracks){\n      const track=tracks[type];\n      if(track.id==='main'){\n        name=type;\n        mediaTrack=track;\n        // keep video source buffer reference\n        if(type==='video'){\n          const videoTrack=tracks[type];\n          if(videoTrack){\n            this.videoBuffer=videoTrack.buffer;\n          }\n        }\n      }else{\n        alternate=true;\n      }\n    }\n    if(alternate&&mediaTrack){\n      this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`);\n      this.mediaBuffer=mediaTrack.buffer;\n    }else{\n      this.mediaBuffer=this.media;\n    }\n  }\n  onFragBuffered(event, data){\n    const {\n      frag,\n      part\n    }=data;\n    if(frag&&frag.type!==PlaylistLevelType.MAIN){\n      return;\n    }\n    if(this.fragContextChanged(frag)){\n      // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion\n      // Avoid setting state back to IDLE, since that will interfere with a level switch\n      this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index:''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`);\n      if(this.state===State.PARSED){\n        this.state=State.IDLE;\n      }\n      return;\n    }\n    const stats=part ? part.stats:frag.stats;\n    this.fragLastKbps=Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first));\n    if(frag.sn!=='initSegment'){\n      this.fragPrevious=frag;\n    }\n    this.fragBufferedComplete(frag, part);\n  }\n  onError(event, data){\n    var _data$context;\n    if(data.fatal){\n      this.state=State.ERROR;\n      return;\n    }\n    switch (data.details){\n      case ErrorDetails.FRAG_GAP:\n      case ErrorDetails.FRAG_PARSING_ERROR:\n      case ErrorDetails.FRAG_DECRYPT_ERROR:\n      case ErrorDetails.FRAG_LOAD_ERROR:\n      case ErrorDetails.FRAG_LOAD_TIMEOUT:\n      case ErrorDetails.KEY_LOAD_ERROR:\n      case ErrorDetails.KEY_LOAD_TIMEOUT:\n        this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data);\n        break;\n      case ErrorDetails.LEVEL_LOAD_ERROR:\n      case ErrorDetails.LEVEL_LOAD_TIMEOUT:\n      case ErrorDetails.LEVEL_PARSING_ERROR:\n        // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE\n        if(!data.levelRetry&&this.state===State.WAITING_LEVEL&&((_data$context=data.context)==null ? void 0:_data$context.type)===PlaylistContextType.LEVEL){\n          this.state=State.IDLE;\n        }\n        break;\n      case ErrorDetails.BUFFER_APPEND_ERROR:\n      case ErrorDetails.BUFFER_FULL_ERROR:\n        if(!data.parent||data.parent!=='main'){\n          return;\n        }\n        if(data.details===ErrorDetails.BUFFER_APPEND_ERROR){\n          this.resetLoadingState();\n          return;\n        }\n        if(this.reduceLengthAndFlushBuffer(data)){\n          this.flushMainBuffer(0, Number.POSITIVE_INFINITY);\n        }\n        break;\n      case ErrorDetails.INTERNAL_EXCEPTION:\n        this.recoverWorkerError(data);\n        break;\n    }\n  }\n\n  // Checks the health of the buffer and attempts to resolve playback stalls.\n  checkBuffer(){\n    const {\n      media,\n      gapController\n    }=this;\n    if(!media||!gapController||!media.readyState){\n      // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)\n      return;\n    }\n    if(this.loadedmetadata||!BufferHelper.getBuffered(media).length){\n      // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers\n      const activeFrag=this.state!==State.IDLE ? this.fragCurrent:null;\n      gapController.poll(this.lastCurrentTime, activeFrag);\n    }\n    this.lastCurrentTime=media.currentTime;\n  }\n  onFragLoadEmergencyAborted(){\n    this.state=State.IDLE;\n    // if loadedmetadata is not set, it means that we are emergency switch down on first frag\n    // in that case, reset startFragRequested flag\n    if(!this.loadedmetadata){\n      this.startFragRequested=false;\n      this.nextLoadPosition=this.startPosition;\n    }\n    this.tickImmediate();\n  }\n  onBufferFlushed(event, {\n    type\n  }){\n    if(type!==ElementaryStreamTypes.AUDIO||this.audioOnly&&!this.altAudio){\n      const mediaBuffer=(type===ElementaryStreamTypes.VIDEO ? this.videoBuffer:this.mediaBuffer)||this.media;\n      this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);\n      this.tick();\n    }\n  }\n  onLevelsUpdated(event, data){\n    if(this.level > -1&&this.fragCurrent){\n      this.level=this.fragCurrent.level;\n    }\n    this.levels=data.levels;\n  }\n  swapAudioCodec(){\n    this.audioCodecSwap = !this.audioCodecSwap;\n  }\n\n  \n  seekToStartPos(){\n    const {\n      media\n    }=this;\n    if(!media){\n      return;\n    }\n    const currentTime=media.currentTime;\n    let startPosition=this.startPosition;\n    // only adjust currentTime if different from startPosition or if startPosition not buffered\n    // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered\n    if(startPosition >=0&&currentTime < startPosition){\n      if(media.seeking){\n        this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);\n        return;\n      }\n      const buffered=BufferHelper.getBuffered(media);\n      const bufferStart=buffered.length ? buffered.start(0):0;\n      const delta=bufferStart - startPosition;\n      if(delta > 0&&(delta < this.config.maxBufferHole||delta < this.config.maxFragLookUpTolerance)){\n        this.log(`adjusting start position by ${delta} to match buffer start`);\n        startPosition +=delta;\n        this.startPosition=startPosition;\n      }\n      this.log(`seek to target start position ${startPosition} from current time ${currentTime}`);\n      media.currentTime=startPosition;\n    }\n  }\n  _getAudioCodec(currentLevel){\n    let audioCodec=this.config.defaultAudioCodec||currentLevel.audioCodec;\n    if(this.audioCodecSwap&&audioCodec){\n      this.log('Swapping audio codec');\n      if(audioCodec.indexOf('mp4a.40.5')!==-1){\n        audioCodec='mp4a.40.2';\n      }else{\n        audioCodec='mp4a.40.5';\n      }\n    }\n    return audioCodec;\n  }\n  _loadBitrateTestFrag(frag, level){\n    frag.bitrateTest=true;\n    this._doFragLoad(frag, level).then(data=> {\n      const {\n        hls\n      }=this;\n      if(!data||this.fragContextChanged(frag)){\n        return;\n      }\n      level.fragmentError=0;\n      this.state=State.IDLE;\n      this.startFragRequested=false;\n      this.bitrateTest=false;\n      const stats=frag.stats;\n      // Bitrate tests fragments are neither parsed nor buffered\n      stats.parsing.start=stats.parsing.end=stats.buffering.start=stats.buffering.end=self.performance.now();\n      hls.trigger(Events.FRAG_LOADED, data);\n      frag.bitrateTest=false;\n    });\n  }\n  _handleTransmuxComplete(transmuxResult){\n    var _id3$samples;\n    const id='main';\n    const {\n      hls\n    }=this;\n    const {\n      remuxResult,\n      chunkMeta\n    }=transmuxResult;\n    const context=this.getCurrentContext(chunkMeta);\n    if(!context){\n      this.resetWhenMissingContext(chunkMeta);\n      return;\n    }\n    const {\n      frag,\n      part,\n      level\n    }=context;\n    const {\n      video,\n      text,\n      id3,\n      initSegment\n    }=remuxResult;\n    const {\n      details\n    }=level;\n    // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track\n    const audio=this.altAudio ? undefined:remuxResult.audio;\n\n    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.\n    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.\n    if(this.fragContextChanged(frag)){\n      this.fragmentTracker.removeFragment(frag);\n      return;\n    }\n    this.state=State.PARSING;\n    if(initSegment){\n      if(initSegment!=null&&initSegment.tracks){\n        const mapFragment=frag.initSegment||frag;\n        this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);\n        hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {\n          frag: mapFragment,\n          id,\n          tracks: initSegment.tracks\n        });\n      }\n\n      // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038\n      const initPTS=initSegment.initPTS;\n      const timescale=initSegment.timescale;\n      if(isFiniteNumber(initPTS)){\n        this.initPTS[frag.cc]={\n          baseTime: initPTS,\n          timescale\n        };\n        hls.trigger(Events.INIT_PTS_FOUND, {\n          frag,\n          id,\n          initPTS,\n          timescale\n        });\n      }\n    }\n\n    // Avoid buffering if backtracking this fragment\n    if(video&&details&&frag.sn!=='initSegment'){\n      const prevFrag=details.fragments[frag.sn - 1 - details.startSN];\n      const isFirstFragment=frag.sn===details.startSN;\n      const isFirstInDiscontinuity = !prevFrag||frag.cc > prevFrag.cc;\n      if(remuxResult.independent!==false){\n        const {\n          startPTS,\n          endPTS,\n          startDTS,\n          endDTS\n        }=video;\n        if(part){\n          part.elementaryStreams[video.type]={\n            startPTS,\n            endPTS,\n            startDTS,\n            endDTS\n          };\n        }else{\n          if(video.firstKeyFrame&&video.independent&&chunkMeta.id===1&&!isFirstInDiscontinuity){\n            this.couldBacktrack=true;\n          }\n          if(video.dropped&&video.independent){\n            // Backtrack if dropped frames create a gap after currentTime\n\n            const bufferInfo=this.getMainFwdBufferInfo();\n            const targetBufferTime=(bufferInfo ? bufferInfo.end:this.getLoadPosition()) + this.config.maxBufferHole;\n            const startTime=video.firstKeyFramePTS ? video.firstKeyFramePTS:startPTS;\n            if(!isFirstFragment&&targetBufferTime < startTime - this.config.maxBufferHole&&!isFirstInDiscontinuity){\n              this.backtrack(frag);\n              return;\n            }else if(isFirstInDiscontinuity){\n              // Mark segment with a gap to avoid loop loading\n              frag.gap=true;\n            }\n            // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial\n            frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true);\n          }else if(isFirstFragment&&startPTS > MAX_START_GAP_JUMP){\n            // Mark segment with a gap to skip large start gap\n            frag.gap=true;\n          }\n        }\n        frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS);\n        if(this.backtrackFragment){\n          this.backtrackFragment=frag;\n        }\n        this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment||isFirstInDiscontinuity);\n      }else if(isFirstFragment||isFirstInDiscontinuity){\n        // Mark segment with a gap to avoid loop loading\n        frag.gap=true;\n      }else{\n        this.backtrack(frag);\n        return;\n      }\n    }\n    if(audio){\n      const {\n        startPTS,\n        endPTS,\n        startDTS,\n        endDTS\n      }=audio;\n      if(part){\n        part.elementaryStreams[ElementaryStreamTypes.AUDIO]={\n          startPTS,\n          endPTS,\n          startDTS,\n          endDTS\n        };\n      }\n      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);\n      this.bufferFragmentData(audio, frag, part, chunkMeta);\n    }\n    if(details&&id3!=null&&(_id3$samples=id3.samples)!=null&&_id3$samples.length){\n      const emittedID3={\n        id,\n        frag,\n        details,\n        samples: id3.samples\n      };\n      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);\n    }\n    if(details&&text){\n      const emittedText={\n        id,\n        frag,\n        details,\n        samples: text.samples\n      };\n      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);\n    }\n  }\n  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta){\n    if(this.state!==State.PARSING){\n      return;\n    }\n    this.audioOnly = !!tracks.audio&&!tracks.video;\n\n    // if audio track is expected to come from audio stream controller, discard any coming from main\n    if(this.altAudio&&!this.audioOnly){\n      delete tracks.audio;\n    }\n    // include levelCodec in audio and video tracks\n    const {\n      audio,\n      video,\n      audiovideo\n    }=tracks;\n    if(audio){\n      let audioCodec=currentLevel.audioCodec;\n      const ua=navigator.userAgent.toLowerCase();\n      if(this.audioCodecSwitch){\n        if(audioCodec){\n          if(audioCodec.indexOf('mp4a.40.5')!==-1){\n            audioCodec='mp4a.40.2';\n          }else{\n            audioCodec='mp4a.40.5';\n          }\n        }\n        // In the case that AAC and HE-AAC audio codecs are signalled in manifest,\n        // force HE-AAC, as it seems that most browsers prefers it.\n        // don't force HE-AAC if mono stream, or in Firefox\n        const audioMetadata=audio.metadata;\n        if(audioMetadata&&'channelCount' in audioMetadata&&(audioMetadata.channelCount||1)!==1&&ua.indexOf('firefox')===-1){\n          audioCodec='mp4a.40.5';\n        }\n      }\n      // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise\n      if(audioCodec&&audioCodec.indexOf('mp4a.40.5')!==-1&&ua.indexOf('android')!==-1&&audio.container!=='audio/mpeg'){\n        // Exclude mpeg audio\n        audioCodec='mp4a.40.2';\n        this.log(`Android: force audio codec to ${audioCodec}`);\n      }\n      if(currentLevel.audioCodec&&currentLevel.audioCodec!==audioCodec){\n        this.log(`Swapping manifest audio codec \"${currentLevel.audioCodec}\" for \"${audioCodec}\"`);\n      }\n      audio.levelCodec=audioCodec;\n      audio.id='main';\n      this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec||''}/${currentLevel.audioCodec||''}/${audio.codec}]`);\n    }\n    if(video){\n      video.levelCodec=currentLevel.videoCodec;\n      video.id='main';\n      this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec||''}/${video.codec}]`);\n    }\n    if(audiovideo){\n      this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);\n    }\n    this.hls.trigger(Events.BUFFER_CODECS, tracks);\n    // loop through tracks that are going to be provided to bufferController\n    Object.keys(tracks).forEach(trackName=> {\n      const track=tracks[trackName];\n      const initSegment=track.initSegment;\n      if(initSegment!=null&&initSegment.byteLength){\n        this.hls.trigger(Events.BUFFER_APPENDING, {\n          type: trackName,\n          data: initSegment,\n          frag,\n          part: null,\n          chunkMeta,\n          parent: frag.type\n        });\n      }\n    });\n    // trigger handler right now\n    this.tickImmediate();\n  }\n  getMainFwdBufferInfo(){\n    return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer:this.media, PlaylistLevelType.MAIN);\n  }\n  backtrack(frag){\n    this.couldBacktrack=true;\n    // Causes findFragments to backtrack through fragments to find the keyframe\n    this.backtrackFragment=frag;\n    this.resetTransmuxer();\n    this.flushBufferGap(frag);\n    this.fragmentTracker.removeFragment(frag);\n    this.fragPrevious=null;\n    this.nextLoadPosition=frag.start;\n    this.state=State.IDLE;\n  }\n  checkFragmentChanged(){\n    const video=this.media;\n    let fragPlayingCurrent=null;\n    if(video&&video.readyState > 1&&video.seeking===false){\n      const currentTime=video.currentTime;\n      \n\n      if(BufferHelper.isBuffered(video, currentTime)){\n        fragPlayingCurrent=this.getAppendedFrag(currentTime);\n      }else if(BufferHelper.isBuffered(video, currentTime + 0.1)){\n        \n        fragPlayingCurrent=this.getAppendedFrag(currentTime + 0.1);\n      }\n      if(fragPlayingCurrent){\n        this.backtrackFragment=null;\n        const fragPlaying=this.fragPlaying;\n        const fragCurrentLevel=fragPlayingCurrent.level;\n        if(!fragPlaying||fragPlayingCurrent.sn!==fragPlaying.sn||fragPlaying.level!==fragCurrentLevel){\n          this.fragPlaying=fragPlayingCurrent;\n          this.hls.trigger(Events.FRAG_CHANGED, {\n            frag: fragPlayingCurrent\n          });\n          if(!fragPlaying||fragPlaying.level!==fragCurrentLevel){\n            this.hls.trigger(Events.LEVEL_SWITCHED, {\n              level: fragCurrentLevel\n            });\n          }\n        }\n      }\n    }\n  }\n  get nextLevel(){\n    const frag=this.nextBufferedFrag;\n    if(frag){\n      return frag.level;\n    }\n    return -1;\n  }\n  get currentFrag(){\n    const media=this.media;\n    if(media){\n      return this.fragPlaying||this.getAppendedFrag(media.currentTime);\n    }\n    return null;\n  }\n  get currentProgramDateTime(){\n    const media=this.media;\n    if(media){\n      const currentTime=media.currentTime;\n      const frag=this.currentFrag;\n      if(frag&&isFiniteNumber(currentTime)&&isFiniteNumber(frag.programDateTime)){\n        const epocMs=frag.programDateTime + (currentTime - frag.start) * 1000;\n        return new Date(epocMs);\n      }\n    }\n    return null;\n  }\n  get currentLevel(){\n    const frag=this.currentFrag;\n    if(frag){\n      return frag.level;\n    }\n    return -1;\n  }\n  get nextBufferedFrag(){\n    const frag=this.currentFrag;\n    if(frag){\n      return this.followingBufferedFrag(frag);\n    }\n    return null;\n  }\n  get forceStartLoad(){\n    return this._forceStartLoad;\n  }\n}\n\n\nclass Hls {\n  \n  static get version(){\n    return \"1.5.19\";\n  }\n\n  \n  static isMSESupported(){\n    return isMSESupported();\n  }\n\n  \n  static isSupported(){\n    return isSupported();\n  }\n\n  \n  static getMediaSource(){\n    return getMediaSource();\n  }\n  static get Events(){\n    return Events;\n  }\n  static get ErrorTypes(){\n    return ErrorTypes;\n  }\n  static get ErrorDetails(){\n    return ErrorDetails;\n  }\n\n  \n  static get DefaultConfig(){\n    if(!Hls.defaultConfig){\n      return hlsDefaultConfig;\n    }\n    return Hls.defaultConfig;\n  }\n\n  \n  static set DefaultConfig(defaultConfig){\n    Hls.defaultConfig=defaultConfig;\n  }\n\n  \n  constructor(userConfig={}){\n    \n    this.config=void 0;\n    \n    this.userConfig=void 0;\n    this.coreComponents=void 0;\n    this.networkControllers=void 0;\n    this.started=false;\n    this._emitter=new EventEmitter();\n    this._autoLevelCapping=-1;\n    this._maxHdcpLevel=null;\n    this.abrController=void 0;\n    this.bufferController=void 0;\n    this.capLevelController=void 0;\n    this.latencyController=void 0;\n    this.levelController=void 0;\n    this.streamController=void 0;\n    this.audioTrackController=void 0;\n    this.subtitleTrackController=void 0;\n    this.emeController=void 0;\n    this.cmcdController=void 0;\n    this._media=null;\n    this.url=null;\n    this.triggeringException=void 0;\n    enableLogs(userConfig.debug||false, 'Hls instance');\n    const config=this.config=mergeConfig(Hls.DefaultConfig, userConfig);\n    this.userConfig=userConfig;\n    if(config.progressive){\n      enableStreamingMode(config);\n    }\n\n    // core controllers and network loaders\n    const {\n      abrController: ConfigAbrController,\n      bufferController: ConfigBufferController,\n      capLevelController: ConfigCapLevelController,\n      errorController: ConfigErrorController,\n      fpsController: ConfigFpsController\n    }=config;\n    const errorController=new ConfigErrorController(this);\n    const abrController=this.abrController=new ConfigAbrController(this);\n    const bufferController=this.bufferController=new ConfigBufferController(this);\n    const capLevelController=this.capLevelController=new ConfigCapLevelController(this);\n    const fpsController=new ConfigFpsController(this);\n    const playListLoader=new PlaylistLoader(this);\n    const id3TrackController=new ID3TrackController(this);\n    const ConfigContentSteeringController=config.contentSteeringController;\n    // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first\n    const contentSteering=ConfigContentSteeringController ? new ConfigContentSteeringController(this):null;\n    const levelController=this.levelController=new LevelController(this, contentSteering);\n    // FragmentTracker must be defined before StreamController because the order of event handling is important\n    const fragmentTracker=new FragmentTracker(this);\n    const keyLoader=new KeyLoader(this.config);\n    const streamController=this.streamController=new StreamController(this, fragmentTracker, keyLoader);\n\n    // Cap level controller uses streamController to flush the buffer\n    capLevelController.setStreamController(streamController);\n    // fpsController uses streamController to switch when frames are being dropped\n    fpsController.setStreamController(streamController);\n    const networkControllers=[playListLoader, levelController, streamController];\n    if(contentSteering){\n      networkControllers.splice(1, 0, contentSteering);\n    }\n    this.networkControllers=networkControllers;\n    const coreComponents=[abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker];\n    this.audioTrackController=this.createController(config.audioTrackController, networkControllers);\n    const AudioStreamControllerClass=config.audioStreamController;\n    if(AudioStreamControllerClass){\n      networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));\n    }\n    // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important\n    this.subtitleTrackController=this.createController(config.subtitleTrackController, networkControllers);\n    const SubtitleStreamControllerClass=config.subtitleStreamController;\n    if(SubtitleStreamControllerClass){\n      networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));\n    }\n    this.createController(config.timelineController, coreComponents);\n    keyLoader.emeController=this.emeController=this.createController(config.emeController, coreComponents);\n    this.cmcdController=this.createController(config.cmcdController, coreComponents);\n    this.latencyController=this.createController(LatencyController, coreComponents);\n    this.coreComponents=coreComponents;\n\n    // Error controller handles errors before and after all other controllers\n    // This listener will be invoked after all other controllers error listeners\n    networkControllers.push(errorController);\n    const onErrorOut=errorController.onErrorOut;\n    if(typeof onErrorOut==='function'){\n      this.on(Events.ERROR, onErrorOut, errorController);\n    }\n  }\n  createController(ControllerClass, components){\n    if(ControllerClass){\n      const controllerInstance=new ControllerClass(this);\n      if(components){\n        components.push(controllerInstance);\n      }\n      return controllerInstance;\n    }\n    return null;\n  }\n\n  // Delegate the EventEmitter through the public API of Hls.js\n  on(event, listener, context=this){\n    this._emitter.on(event, listener, context);\n  }\n  once(event, listener, context=this){\n    this._emitter.once(event, listener, context);\n  }\n  removeAllListeners(event){\n    this._emitter.removeAllListeners(event);\n  }\n  off(event, listener, context=this, once){\n    this._emitter.off(event, listener, context, once);\n  }\n  listeners(event){\n    return this._emitter.listeners(event);\n  }\n  emit(event, name, eventObject){\n    return this._emitter.emit(event, name, eventObject);\n  }\n  trigger(event, eventObject){\n    if(this.config.debug){\n      return this.emit(event, event, eventObject);\n    }else{\n      try {\n        return this.emit(event, event, eventObject);\n      } catch (error){\n        logger.error('An internal error happened while handling event ' + event + '. Error message: \"' + error.message + '\". Here is a stacktrace:', error);\n        // Prevent recursion in error event handlers that throw #5497\n        if(!this.triggeringException){\n          this.triggeringException=true;\n          const fatal=event===Events.ERROR;\n          this.trigger(Events.ERROR, {\n            type: ErrorTypes.OTHER_ERROR,\n            details: ErrorDetails.INTERNAL_EXCEPTION,\n            fatal,\n            event,\n            error\n          });\n          this.triggeringException=false;\n        }\n      }\n    }\n    return false;\n  }\n  listenerCount(event){\n    return this._emitter.listenerCount(event);\n  }\n\n  \n  destroy(){\n    logger.log('destroy');\n    this.trigger(Events.DESTROYING, undefined);\n    this.detachMedia();\n    this.removeAllListeners();\n    this._autoLevelCapping=-1;\n    this.url=null;\n    this.networkControllers.forEach(component=> component.destroy());\n    this.networkControllers.length=0;\n    this.coreComponents.forEach(component=> component.destroy());\n    this.coreComponents.length=0;\n    // Remove any references that could be held in config options or callbacks\n    const config=this.config;\n    config.xhrSetup=config.fetchSetup=undefined;\n    // @ts-ignore\n    this.userConfig=null;\n  }\n\n  \n  attachMedia(media){\n    logger.log('attachMedia');\n    this._media=media;\n    this.trigger(Events.MEDIA_ATTACHING, {\n      media: media\n    });\n  }\n\n  \n  detachMedia(){\n    logger.log('detachMedia');\n    this.trigger(Events.MEDIA_DETACHING, undefined);\n    this._media=null;\n  }\n\n  \n  loadSource(url){\n    this.stopLoad();\n    const media=this.media;\n    const loadedSource=this.url;\n    const loadingSource=this.url=urlToolkitExports.buildAbsoluteURL(self.location.href, url, {\n      alwaysNormalize: true\n    });\n    this._autoLevelCapping=-1;\n    this._maxHdcpLevel=null;\n    logger.log(`loadSource:${loadingSource}`);\n    if(media&&loadedSource&&(loadedSource!==loadingSource||this.bufferController.hasSourceTypes())){\n      this.detachMedia();\n      this.attachMedia(media);\n    }\n    // when attaching to a source URL, trigger a playlist load\n    this.trigger(Events.MANIFEST_LOADING, {\n      url: url\n    });\n  }\n\n  \n  startLoad(startPosition=-1){\n    logger.log(`startLoad(${startPosition})`);\n    this.started=true;\n    this.resumeBuffering();\n    for (let i=0; i < this.networkControllers.length; i++){\n      this.networkControllers[i].startLoad(startPosition);\n      if(!this.started||!this.networkControllers){\n        break;\n      }\n    }\n  }\n\n  \n  stopLoad(){\n    logger.log('stopLoad');\n    this.started=false;\n    for (let i=0; i < this.networkControllers.length; i++){\n      this.networkControllers[i].stopLoad();\n      if(this.started||!this.networkControllers){\n        break;\n      }\n    }\n  }\n\n  \n  resumeBuffering(){\n    logger.log(`resume buffering`);\n    this.networkControllers.forEach(controller=> {\n      if(controller.resumeBuffering){\n        controller.resumeBuffering();\n      }\n    });\n  }\n\n  \n  pauseBuffering(){\n    logger.log(`pause buffering`);\n    this.networkControllers.forEach(controller=> {\n      if(controller.pauseBuffering){\n        controller.pauseBuffering();\n      }\n    });\n  }\n\n  \n  swapAudioCodec(){\n    logger.log('swapAudioCodec');\n    this.streamController.swapAudioCodec();\n  }\n\n  \n  recoverMediaError(){\n    logger.log('recoverMediaError');\n    const media=this._media;\n    this.detachMedia();\n    if(media){\n      this.attachMedia(media);\n    }\n  }\n  removeLevel(levelIndex){\n    this.levelController.removeLevel(levelIndex);\n  }\n\n  \n  get levels(){\n    const levels=this.levelController.levels;\n    return levels ? levels:[];\n  }\n\n  \n  get currentLevel(){\n    return this.streamController.currentLevel;\n  }\n\n  \n  set currentLevel(newLevel){\n    logger.log(`set currentLevel:${newLevel}`);\n    this.levelController.manualLevel=newLevel;\n    this.streamController.immediateLevelSwitch();\n  }\n\n  \n  get nextLevel(){\n    return this.streamController.nextLevel;\n  }\n\n  \n  set nextLevel(newLevel){\n    logger.log(`set nextLevel:${newLevel}`);\n    this.levelController.manualLevel=newLevel;\n    this.streamController.nextLevelSwitch();\n  }\n\n  \n  get loadLevel(){\n    return this.levelController.level;\n  }\n\n  \n  set loadLevel(newLevel){\n    logger.log(`set loadLevel:${newLevel}`);\n    this.levelController.manualLevel=newLevel;\n  }\n\n  \n  get nextLoadLevel(){\n    return this.levelController.nextLoadLevel;\n  }\n\n  \n  set nextLoadLevel(level){\n    this.levelController.nextLoadLevel=level;\n  }\n\n  \n  get firstLevel(){\n    return Math.max(this.levelController.firstLevel, this.minAutoLevel);\n  }\n\n  \n  set firstLevel(newLevel){\n    logger.log(`set firstLevel:${newLevel}`);\n    this.levelController.firstLevel=newLevel;\n  }\n\n  \n  get startLevel(){\n    const startLevel=this.levelController.startLevel;\n    if(startLevel===-1&&this.abrController.forcedAutoLevel > -1){\n      return this.abrController.forcedAutoLevel;\n    }\n    return startLevel;\n  }\n\n  \n  set startLevel(newLevel){\n    logger.log(`set startLevel:${newLevel}`);\n    // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel\n    if(newLevel!==-1){\n      newLevel=Math.max(newLevel, this.minAutoLevel);\n    }\n    this.levelController.startLevel=newLevel;\n  }\n\n  \n  get capLevelToPlayerSize(){\n    return this.config.capLevelToPlayerSize;\n  }\n\n  \n  set capLevelToPlayerSize(shouldStartCapping){\n    const newCapLevelToPlayerSize = !!shouldStartCapping;\n    if(newCapLevelToPlayerSize!==this.config.capLevelToPlayerSize){\n      if(newCapLevelToPlayerSize){\n        this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size.\n      }else{\n        this.capLevelController.stopCapping();\n        this.autoLevelCapping=-1;\n        this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap.\n      }\n      this.config.capLevelToPlayerSize=newCapLevelToPlayerSize;\n    }\n  }\n\n  \n  get autoLevelCapping(){\n    return this._autoLevelCapping;\n  }\n\n  \n  get bandwidthEstimate(){\n    const {\n      bwEstimator\n    }=this.abrController;\n    if(!bwEstimator){\n      return NaN;\n    }\n    return bwEstimator.getEstimate();\n  }\n  set bandwidthEstimate(abrEwmaDefaultEstimate){\n    this.abrController.resetEstimator(abrEwmaDefaultEstimate);\n  }\n\n  \n  get ttfbEstimate(){\n    const {\n      bwEstimator\n    }=this.abrController;\n    if(!bwEstimator){\n      return NaN;\n    }\n    return bwEstimator.getEstimateTTFB();\n  }\n\n  \n  set autoLevelCapping(newLevel){\n    if(this._autoLevelCapping!==newLevel){\n      logger.log(`set autoLevelCapping:${newLevel}`);\n      this._autoLevelCapping=newLevel;\n      this.levelController.checkMaxAutoUpdated();\n    }\n  }\n  get maxHdcpLevel(){\n    return this._maxHdcpLevel;\n  }\n  set maxHdcpLevel(value){\n    if(isHdcpLevel(value)&&this._maxHdcpLevel!==value){\n      this._maxHdcpLevel=value;\n      this.levelController.checkMaxAutoUpdated();\n    }\n  }\n\n  \n  get autoLevelEnabled(){\n    return this.levelController.manualLevel===-1;\n  }\n\n  \n  get manualLevel(){\n    return this.levelController.manualLevel;\n  }\n\n  \n  get minAutoLevel(){\n    const {\n      levels,\n      config: {\n        minAutoBitrate\n      }\n    }=this;\n    if(!levels) return 0;\n    const len=levels.length;\n    for (let i=0; i < len; i++){\n      if(levels[i].maxBitrate >=minAutoBitrate){\n        return i;\n      }\n    }\n    return 0;\n  }\n\n  \n  get maxAutoLevel(){\n    const {\n      levels,\n      autoLevelCapping,\n      maxHdcpLevel\n    }=this;\n    let maxAutoLevel;\n    if(autoLevelCapping===-1&&levels!=null&&levels.length){\n      maxAutoLevel=levels.length - 1;\n    }else{\n      maxAutoLevel=autoLevelCapping;\n    }\n    if(maxHdcpLevel){\n      for (let i=maxAutoLevel; i--;){\n        const hdcpLevel=levels[i].attrs['HDCP-LEVEL'];\n        if(hdcpLevel&&hdcpLevel <=maxHdcpLevel){\n          return i;\n        }\n      }\n    }\n    return maxAutoLevel;\n  }\n  get firstAutoLevel(){\n    return this.abrController.firstAutoLevel;\n  }\n\n  \n  get nextAutoLevel(){\n    return this.abrController.nextAutoLevel;\n  }\n\n  \n  set nextAutoLevel(nextLevel){\n    this.abrController.nextAutoLevel=nextLevel;\n  }\n\n  \n  get playingDate(){\n    return this.streamController.currentProgramDateTime;\n  }\n  get mainForwardBufferInfo(){\n    return this.streamController.getMainFwdBufferInfo();\n  }\n\n  \n  setAudioOption(audioOption){\n    var _this$audioTrackContr;\n    return (_this$audioTrackContr=this.audioTrackController)==null ? void 0:_this$audioTrackContr.setAudioOption(audioOption);\n  }\n  \n  setSubtitleOption(subtitleOption){\n    var _this$subtitleTrackCo;\n    (_this$subtitleTrackCo=this.subtitleTrackController)==null ? void 0:_this$subtitleTrackCo.setSubtitleOption(subtitleOption);\n    return null;\n  }\n\n  \n  get allAudioTracks(){\n    const audioTrackController=this.audioTrackController;\n    return audioTrackController ? audioTrackController.allAudioTracks:[];\n  }\n\n  \n  get audioTracks(){\n    const audioTrackController=this.audioTrackController;\n    return audioTrackController ? audioTrackController.audioTracks:[];\n  }\n\n  \n  get audioTrack(){\n    const audioTrackController=this.audioTrackController;\n    return audioTrackController ? audioTrackController.audioTrack:-1;\n  }\n\n  \n  set audioTrack(audioTrackId){\n    const audioTrackController=this.audioTrackController;\n    if(audioTrackController){\n      audioTrackController.audioTrack=audioTrackId;\n    }\n  }\n\n  \n  get allSubtitleTracks(){\n    const subtitleTrackController=this.subtitleTrackController;\n    return subtitleTrackController ? subtitleTrackController.allSubtitleTracks:[];\n  }\n\n  \n  get subtitleTracks(){\n    const subtitleTrackController=this.subtitleTrackController;\n    return subtitleTrackController ? subtitleTrackController.subtitleTracks:[];\n  }\n\n  \n  get subtitleTrack(){\n    const subtitleTrackController=this.subtitleTrackController;\n    return subtitleTrackController ? subtitleTrackController.subtitleTrack:-1;\n  }\n  get media(){\n    return this._media;\n  }\n\n  \n  set subtitleTrack(subtitleTrackId){\n    const subtitleTrackController=this.subtitleTrackController;\n    if(subtitleTrackController){\n      subtitleTrackController.subtitleTrack=subtitleTrackId;\n    }\n  }\n\n  \n  get subtitleDisplay(){\n    const subtitleTrackController=this.subtitleTrackController;\n    return subtitleTrackController ? subtitleTrackController.subtitleDisplay:false;\n  }\n\n  \n  set subtitleDisplay(value){\n    const subtitleTrackController=this.subtitleTrackController;\n    if(subtitleTrackController){\n      subtitleTrackController.subtitleDisplay=value;\n    }\n  }\n\n  \n  get lowLatencyMode(){\n    return this.config.lowLatencyMode;\n  }\n\n  \n  set lowLatencyMode(mode){\n    this.config.lowLatencyMode=mode;\n  }\n\n  \n  get liveSyncPosition(){\n    return this.latencyController.liveSyncPosition;\n  }\n\n  \n  get latency(){\n    return this.latencyController.latency;\n  }\n\n  \n  get maxLatency(){\n    return this.latencyController.maxLatency;\n  }\n\n  \n  get targetLatency(){\n    return this.latencyController.targetLatency;\n  }\n\n  \n  get drift(){\n    return this.latencyController.drift;\n  }\n\n  \n  get forceStartLoad(){\n    return this.streamController.forceStartLoad;\n  }\n}\nHls.defaultConfig=void 0;\n\n\n//# sourceMappingURL=hls.mjs.map\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/hls.js/dist/hls.mjs?")},(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   \"default\": ()=> (__WEBPACK_DEFAULT_EXPORT__)\n });\n var hls_video_element__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 15);\n var jQuery=__webpack_require__( 7);\n\n\n// import Hls, { HlsConfig } from 'hls.js'\n\n\nclass DiadaoWpSdk \n{\n    debug={ \n        'global':false, \n        'loading':false,\n        'prevent_default':false,\n        'init':false,\n        'img_attrs':false, \n        'escape':false,\n        'lazyload':false,\n        'swiper_room_thumb':false,\n        'offcanvas':false,\n        'offcanvas_backdrop':false,\n        'offcanvas_opening':false,\n        'offcanvas_change_hash':false,\n        'offcanvas_tabs':false,\n        'bs_offcanvas_room':false,\n        'bs_pills_room':false, \n        'update_hash':false, \n        'menu_medias':false,   \n        'swiper_snippet':false,\n        'swiper_default':false,\n        'smart_gallery':false,\n        'filter_menu':false, \n        'swiper_room_menu':false, \n        'swiper_room_upgrade':false, \n        'swiper_filter_menu':false, \n        'scroll_top':false, \n        'videos':false,\n        'videos_scrollwatch':false,\n        'videos_mp4':false,\n        'video_modal_autoplay': false,\n        'qs_popup':false,\n    }; \n\n    timeout={\n        'scrolling':500,\n        'temporize':550,\n        'temporize_scrollwatch':600,\n        'scrolling_top':500,\n        'filtering': 200,\n        'offcanvas_show':50,\n        'sdk_offcanvas_show':500, // TOSEE QUENTIN\n        'sdk_offcanvas_hide':400, // TOSEE QUENTIN\n        'sdk_offcanvas_backdrop_show':400, // TOSEE QUENTIN\n        'sdk_offcanvas_backdrop_hide':300, // TOSEE QUENTIN\n        'sdk_offcanvas_backdrop_hide_delay':0,\n        'sdk_offcanvas_backdrop_none_delay':0,\n        'qs_popup':200,\n    };\n\n    triggers={\n        'prev':null,\n        'temporize_enabled':false,\n        'temporize_scrollwatch_enabled':false,\n        'qs_popup_bestprice':false,\n        'qs_popup_availabilities':false,\n    };\n\n    containers={\n        'room_detail':'diadao-room-detail-page-iframe',\n        'qs_popup_bestprice':null,\n        'qs_popup_availabilities':null,\n    };\n\n    gform_instance=null;\n    bootstrap_instance=window.bootstrap; \n\n    sdk_data={\n        'current_hash':'',\n        'current_hash_from_url':false,\n        'current_offcanvas_roomid':null,\n        'current_offcanvas_target':null,\n        'img_attrs_height_auto':true,\n        'bs_offcanvas_opened':[],\n        'bs_offcanvas_backdrop':null,\n        'smart_gallery_init':false,\n        'hash_list_disabled':[ 'diadao-mainmenu', 'diadao-room-detail-page-iframe' ],\n        'qs_popup_enabled':false,\n        'filter_menu_scrolltop_enabled':false,\n    };\n\n    constructor(params) \n    {\n        const that=this;\n\n        that.init();\n    }\n\n    getParam(param)\n    {\n        let res=null;\n\n        if(\n            param!==undefined\n&&param!==null\n&&window[param]!==undefined)\n        {\n            res=window[param];\n\n            if(Number.isInteger(res) )\n            {\n                res=parseInt(res);\n            }\n        }\n\n        return res;\n    }\n\n    init() \n    {\n        const that=this;\n\n        if(that.debug['init']) console.log(\"[DIADAO-SDK] -> init wp sdk js\");\n        \n        that.startSdk();\n    }\n\n    startScrollbar()\n    {\n        // Initialize the plugin\n        // const diaBody=document.querySelector('#dia-body');\n        // const ps=new PerfectScrollbar(diaBody);\n\n        // https://perfectscrollbar.com/how-to-use.html\n        const ps=new PerfectScrollbar('#dia-scroll');\n    }\n\n    startSdk()\n    {\n        const that=this;\n\n        jQuery('body').addClass('diasdk-ready');\n\n        // console.log(\"[DIADAO-SDK] -> start dispatch event\");\n        const DiadaoWpSdkStarted=new Event(\"DiadaoWpSdkStarted\");\n        window.dispatchEvent(DiadaoWpSdkStarted);\n\n        that.initVideos();\n\n        that.initBsOffCanvasSdk();\n\n        that.initScrolling();\n        that.initScrollDownButton();\n        that.initSwiperRoomThumb();\n        that.initSwiperSnippet(null);\n        that.initSwiperDefault();\n        that.initSwiperFilterMenu();\n        that.initSwiperRoomMenu();\n        that.initSwiperRoomUpgrade();\n\n        // that.initHeaderNav();\n        that.initMenuMedias();\n\n        that.initFilterMenu();\n\n        window.addEventListener('DiadaoWpSdkSmartGalleryStarted', event=> {\n            that.initSmartGallery();\n        });\n\n        if(\n            that.bootstrap_instance!==null \n&&that.bootstrap_instance!==undefined\n)\n        {\n            that.initBsPillsRoom();\n        }\n        \n        that.initHashLoad();\n        that.initModalBootstrap();\n\n        window.addEventListener('DiadaoWpHswStarted', event=> {\n            if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs hsw loaded\");\n\n            that.initQsPopup();\n        });\n\n        that.initEscape();\n    }\n\n    getCurrentHash()\n    {\n        const that=this;\n\n        let res=null;\n\n        if(\n            window.location.hash!==undefined\n&&window.location.hash!==null\n&&window.location.hash!=='#'\n&&window.location.hash!=='*'\n&&window.location.hash!==''\n)\n        {\n            res=window.location.hash.replace('#', '');\n        }\n\n        return res;\n    }\n\n    initHashLoad()\n    {\n        const that=this;\n\n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] current hash on load:', that.getCurrentHash());\n\n        if(that.getCurrentHash()!==null)\n        {\n            that.sdk_data['current_hash']=that.getCurrentHash();\n\n            that.openBsOffCanvasSdk(that.getCurrentHash(), null, true);\n        }\n    }\n\n    bsOffCanvasCheckIfMenu(current_id)\n    {\n        const that=this;\n\n        let t_offcanvas_menu_enabled=false;\n        if(\n            current_id!==null \n&&current_id!==undefined \n&&current_id!=='' \n)\n        {\n            let t_offcanvas_current=jQuery('#' + current_id);\n            let t_offcanvas_current_trigger=jQuery('[data-sdkbs-target=\"#' + current_id + '\"]');\n\n            if(\n                current_id \n&&t_offcanvas_current\n&&t_offcanvas_current_trigger\n)\n            {\n                if(\n                    current_id==='diadao-mainmenu'\n||t_offcanvas_current_trigger.hasClass('btn-navbar')\n)\n                {\n                    t_offcanvas_menu_enabled=true;\n                }\n            }\n\n            if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] check if menu:', t_offcanvas_current, t_offcanvas_current_trigger, t_offcanvas_menu_enabled);\n        }\n\n        return t_offcanvas_menu_enabled;\n    }\n\n    eventBsOffCanvasSdk(current_id, update_hash, is_close)\n    {\n        const that=this;\n\n        let t_body=jQuery('body');\n\n        let t_offcanvas_type='default';\n        let t_offcanvas_menu_enabled=false;\n        let t_offcanvas_current=jQuery('#' + current_id);\n        let t_offcanvas_current_trigger=jQuery('[data-sdkbs-target=\"#' + current_id + '\"]');\n        let t_offcanvas_class='';\n        let t_offcanvas_prev_id=that.bsOffCanvasArrayGetLatest();\n\n        if(\n            is_close \n&&t_offcanvas_prev_id!==undefined \n&&t_offcanvas_prev_id!==null\n)\n        {\n            current_id=t_offcanvas_prev_id;\n        }\n\n        if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] total opened:', that.sdk_data['bs_offcanvas_opened'].length, 'CHECK CURRENT PANEL=', t_offcanvas_current, t_offcanvas_current_trigger, current_id);\n\n        t_offcanvas_menu_enabled=that.bsOffCanvasCheckIfMenu(current_id);\n\n        if(t_offcanvas_menu_enabled)\n        {\n            t_offcanvas_type='menu';\n        }\n        else if(\n            current_id \n&&t_offcanvas_current\n&&t_offcanvas_current_trigger\n&&!t_offcanvas_menu_enabled\n&&t_offcanvas_current!==undefined\n&&t_offcanvas_current.length\n&&t_offcanvas_current.data('posttype')!==undefined\n&&t_offcanvas_current.data('posttype')!==null\n)\n        {\n            t_offcanvas_type=t_offcanvas_current.data('posttype');\n        }\n        t_offcanvas_class='paneltype-' + t_offcanvas_type;\n\n\n        if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] menu enabled=', t_offcanvas_menu_enabled, 't_offcanvas_class=', t_offcanvas_class, t_offcanvas_current);\n\n        t_body.removeClass('paneltype-modal').removeClass('paneltype-menu').removeClass('paneltype-offer').removeClass('paneltype-default').removeClass('paneltype-diadaopopup').removeClass('paneltype-bookingcustom');\n\n        if(that.sdk_data['bs_offcanvas_opened'].length===0)\n        {\n            // closed\n            if(that.debug['offcanvas_opening']) console.log('------\x3e closed / not opened', current_id);\n\n            //  closed:remove classes\n            t_body.removeClass('sdkbs-panel-opened');\n            t_body.addClass('sdkbs-panel-not-opened');\n            t_body.attr('data-sdkbs-id', null);\n            t_body.removeAttr('data-sdkbs-id');\n\n            that.sdk_data['current_offcanvas_target']=null;\n\n            if(t_offcanvas_menu_enabled) t_body.removeClass('diadao-menuopened');\n\n            that.initVideoPopupAutoplay(false);\n        }\n        else\n        {\n            // opened:add classes\n            if(that.debug['offcanvas_opening']) console.log('------\x3e opened', current_id);\n\n            t_body.removeClass('sdkbs-panel-not-opened');\n            t_body.addClass('sdkbs-panel-opened');\n            t_body.addClass(t_offcanvas_class);\n            t_body.attr('data-sdkbs-id', current_id);\n\n            if(t_offcanvas_menu_enabled) t_body.addClass('diadao-menuopened');\n\n            // backdrop security\n            // backdropBsOffCanvasSdk\n            if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop security 1 :', that.sdk_data['bs_offcanvas_opened'].length);\n\n            if(\n                that.sdk_data['bs_offcanvas_opened']!==null\n&&that.sdk_data['bs_offcanvas_opened'].length\n)\n            {\n                setTimeout(function(){\n                    that.backdropBsOffCanvasSdk(true, true, true);\n                }, 50);\n            }\n\n            // check for vidéos in offcanvas\n            that.initVideoPopupAutoplay(true);\n\n            // setTimeout(function(){\n            //     if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop security 2 :', that.sdk_data['bs_offcanvas_opened'].length);\n            // }, 500);\n        }\n\n        if(update_hash)\n        {\n            if(\n                that.sdk_data['bs_offcanvas_opened']!==null\n&&that.sdk_data['bs_offcanvas_opened'].length===0 \n)\n            {\n                // all panels are closed -> remove hash\n                that.hashUpdateBsOffCanvasSdk('', false, false);\n            }\n            else\n            {\n                if(\n                    t_offcanvas_prev_id!==undefined \n&&t_offcanvas_prev_id!==null\n)\n                {\n                    that.hashUpdateBsOffCanvasSdk(t_offcanvas_prev_id, true, false);\n                }\n            }\n        }\n\n        const t_sdkbsOffCanvasChanged=new CustomEvent(\n            \"t_sdkbsOffCanvasChanged\", { detail: that.sdk_data['bs_offcanvas_opened'].length }\n);\n        window.dispatchEvent(t_sdkbsOffCanvasChanged);\n    }\n\n    // checkBackdropBsOffCanvasSdk()\n    // {\n    //     const that=this;\n\n    //     console.log(jQuery('.offcanvas-backdrop').length);\n    //     if(\n    //         jQuery('.offcanvas-backdrop')!==undefined\n    //&&jQuery('.offcanvas-backdrop').length > 1\n    //&&jQuery('.offcanvas-backdrop')!==null\n    //)\n    //     {\n    //         if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas backdrop doublon delete');\n\n    //         jQuery('.offcanvas-backdrop:not(:first-child').remove();\n    //     }\n    // }\n\n    backdropBsOffCanvasSdk(open_it, force_it, timeout_disabled)\n    {\n        const that=this;\n\n        let t_sdkbsOffCanvasBackdropContainer=that.sdk_data['bs_offcanvas_backdrop'];\n        if(\n            t_sdkbsOffCanvasBackdropContainer!==null \n&&t_sdkbsOffCanvasBackdropContainer!==undefined\n)\n        {\n            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] check bs backdrop count ' , that.bsOffCanvasArrayGetLatest(), that.sdk_data['bs_offcanvas_opened'].length);\n\n            if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop - open ?', open_it, ' | force ?', force_it);\n\n            let t_sdkbsOffCanvasBackdropTimeoutShow=that.timeout['sdk_offcanvas_backdrop_show'];\n            let t_sdkbsOffCanvasBackdropTimeoutHide=that.timeout['sdk_offcanvas_backdrop_hide'];\n\n            if(timeout_disabled)\n            {\n                t_sdkbsOffCanvasBackdropTimeoutShow=that.timeout['sdk_offcanvas_backdrop_none_delay'];\n                t_sdkbsOffCanvasBackdropTimeoutHide=that.timeout['sdk_offcanvas_backdrop_none_delay'];\n            }\n\n            if(open_it)\n            {\n                // open backdrop\n                if(\n                    (\n                        !t_sdkbsOffCanvasBackdropContainer.classList.contains('show')\n&&\n                        (\n                            (\n                                that.sdk_data['bs_offcanvas_opened']!==null\n&&that.sdk_data['bs_offcanvas_opened'].length===0 \n)\n||\n                            (\n                                t_sdkbsOffCanvasBackdropContainer.classList.contains('is-disabled')\n)\n)\n)\n||force_it\n)\n                {\n                    if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop - try to show', t_sdkbsOffCanvasBackdropTimeoutShow);\n\n                    t_sdkbsOffCanvasBackdropContainer.classList.add('showing');\n                    t_sdkbsOffCanvasBackdropContainer.classList.remove('is-disabled');\n                    setTimeout(function(){\n\n                        t_sdkbsOffCanvasBackdropContainer.classList.remove('showing');\n                        t_sdkbsOffCanvasBackdropContainer.classList.add('show');\n\n                        if(that.debug['offcanvas_backdrop']) console.log('[SDK_OFFCANVAS] offcanvas backdrop - show');\n\n                    }, t_sdkbsOffCanvasBackdropTimeoutShow);\n                }\n            }\n            else\n            {\n                setTimeout(function(){\n                    // close backdrop\n                    if(\n                        t_sdkbsOffCanvasBackdropContainer.classList.contains('show')\n&&\n                        (\n                            (\n                                that.sdk_data['bs_offcanvas_opened']!==null\n&&that.sdk_data['bs_offcanvas_opened'].length <=1\n)\n||\n(\n                                force_it\n)\n)\n)\n                    {\n                        if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop - try to hide', that.sdk_data['bs_offcanvas_opened'].length, that.sdk_data['bs_offcanvas_opened']);\n\n                        t_sdkbsOffCanvasBackdropContainer.classList.add('hiding');\n                        setTimeout(function(){\n\n                            // if(that.sdk_data['bs_offcanvas_opened'].length >=1)\n                            // {\n                            //     t_sdkbsOffCanvasBackdropContainer.classList.remove('hiding');\n                            // }\n                            // else\n                            // {\n                                t_sdkbsOffCanvasBackdropContainer.classList.remove('show');\n                                t_sdkbsOffCanvasBackdropContainer.classList.remove('hiding');\n                                t_sdkbsOffCanvasBackdropContainer.classList.add('is-disabled');\n\n                                if(that.debug['offcanvas_backdrop']) console.log('offcanvas backdrop - hide', that.sdk_data['bs_offcanvas_opened'].length);\n                           // }\n\n                        }, t_sdkbsOffCanvasBackdropTimeoutHide);\n                    }\n                }, that.timeout['sdk_offcanvas_backdrop_hide_delay']);\n            }\n        }\n    }\n\n    searchHashBsOffCanvasSdk(offcanvas_id, elem_trigger)\n    {\n        const that=this;\n\n        let offcanvas_hash=offcanvas_id;\n\n        let t_sdkbsOffCanvasLink=null;\n\n        if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] GO, offcanvas_id=', offcanvas_id, 'elem_trigger=', elem_trigger);\n\n        if(\n            offcanvas_id===that.containers['room_detail'] \n&&elem_trigger!==undefined\n&&elem_trigger!==null\n&&elem_trigger.length\n&&elem_trigger.data('slug')!==null\n&&elem_trigger.data('slug')!==undefined\n)\n        {\n            t_sdkbsOffCanvasLink=elem_trigger;\n        }\n        else\n        {\n            t_sdkbsOffCanvasLink=jQuery('[data-sdkbs-target=\"#' + offcanvas_id + '\"]');\n            \n            if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] CURRENT TRIGGER=', t_sdkbsOffCanvasLink);\n\n            if(\n                elem_trigger!==undefined\n&&elem_trigger!==null\n&&elem_trigger.length\n&&elem_trigger.data('slug')!==null\n&&elem_trigger.data('slug')!==undefined\n&&t_sdkbsOffCanvasLink.data('slug')!==null \n&&t_sdkbsOffCanvasLink.data('slug')!==undefined\n&&t_sdkbsOffCanvasLink.data('slug')!==elem_trigger.data('slug')\n&&elem_trigger.attr('data-sdkbs-target')===t_sdkbsOffCanvasLink.attr('data-sdkbs-target')\n)\n            {\n                t_sdkbsOffCanvasLink=elem_trigger;\n\n                if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] NEW TRIGGER FOR SAME OFFCANVAS CHANGE=', elem_trigger);\n            }\n        }\n\n        if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] TRIGGER=', elem_trigger, ' LINK=', t_sdkbsOffCanvasLink);\n\n        if(\n            t_sdkbsOffCanvasLink!==undefined\n&&t_sdkbsOffCanvasLink.length\n)\n        {\n            if(\n                t_sdkbsOffCanvasLink.data('slug')!==undefined\n&&t_sdkbsOffCanvasLink.data('slug')!==null\n)\n            {\n                offcanvas_hash=t_sdkbsOffCanvasLink.data('slug');\n\n                if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] FROM SLUG=', offcanvas_hash);\n            }\n            else if(\n                t_sdkbsOffCanvasLink.attr('href')!==undefined\n&&t_sdkbsOffCanvasLink.attr('href')!==null\n)\n            {\n                offcanvas_hash=t_sdkbsOffCanvasLink.attr('href');\n                offcanvas_hash=offcanvas_hash.replace(window['diadao_base_url'], '');\n                offcanvas_hash=offcanvas_hash.replace('/', '');\n\n                if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] FROM HREF=', offcanvas_hash);\n            }\n\n            if(offcanvas_hash!==null)\n            {\n                offcanvas_hash=offcanvas_hash.replace('#', '');\n            }\n        }\n\n        offcanvas_hash=that.formatIdBsOffCanvasSdk(offcanvas_hash);\n\n        return offcanvas_hash;\n    }\n\n    roomSlugBsOffCanvasSdk(offcanvas_hash)\n    {\n        const that=this;\n\n        let res=offcanvas_hash;\n\n        if(\n            that.isString(offcanvas_hash) \n&&offcanvas_hash.substr(0, 5)==='room-'\n)\n        {\n            res=offcanvas_hash.substr(5);\n        }\n\n        return res;\n    }\n\n    getBsOffCanvasRoomPill(offcanvas_hash)\n    {\n        const that=this;\n\n        offcanvas_hash=that.roomSlugBsOffCanvasSdk(offcanvas_hash);\n\n        let res=jQuery('#' + that.containers['room_detail'] + ' .room-menu-item[data-bs-toggle=\"pill\"][data-slug=\"' + offcanvas_hash + '\"]');\n\n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] try to search pill room with slug:', offcanvas_hash, ' -> ', res);\n\n        return res;\n    }\n\n    openBsOffCanvasSdk(offcanvas_id, elem_trigger, from_hash)\n    {\n        const that=this;\n\n        if(\n            offcanvas_id!==undefined\n&&offcanvas_id!==null \n&&offcanvas_id!==''\n)\n        {\n            offcanvas_id=that.formatIdBsOffCanvasSdk(offcanvas_id);\n            let offcanvas_hash=that.searchHashBsOffCanvasSdk(offcanvas_id, elem_trigger);\n            let offcanvas_from_hash_trigger=null;\n            \n            let offcanvas_container=jQuery('#' + offcanvas_id);\n            let offcanvas_container_prev_id=null;\n            let offcanvas_container_prev=null;\n            let offcanvas_container_from_slug=false;\n            \n            if(offcanvas_container===undefined||offcanvas_container===null||!offcanvas_container.length)\n            {\n                offcanvas_container=jQuery('.offcanvas[data-offcanvas-slug=\"' + offcanvas_id + '\"]');\n                offcanvas_container_from_slug=true;\n            }\n\n            let offcanvas_type='default';\n            if(offcanvas_container!==undefined&&offcanvas_container!==null&&offcanvas_container.length)\n            {\n                offcanvas_type=offcanvas_container.data('posttype');\n            }\n\n            if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] trigger open id:', offcanvas_id, 'current opened id=', that.sdk_data['current_offcanvas_target'], 'current hash=', offcanvas_hash, 'current type=', offcanvas_type, offcanvas_container);\n\n            if(\n                elem_trigger!==null \n&&elem_trigger!==undefined\n&&elem_trigger.length\n&&!from_hash\n)\n            {\n                jQuery('.offcanvas-active').removeClass('offcanvas-active');\n                elem_trigger.addClass('offcanvas-active');\n\n                if(that.debug['offcanvas_change_hash']) console.log('[SDK_OFFCANVAS_HASH] set trigger active', elem_trigger);\n            }\n\n\n            if(\n                offcanvas_id===that.sdk_data['current_offcanvas_target'] \n&&offcanvas_id===jQuery('body').attr('data-sdkbs-id')\n)\n            {\n                let t_sdkbsOffCanvasTriggerCloseEnabled=false;\n\n                if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] same panel check, offcanvas_id=', offcanvas_id, 'current_offcanvas_target', that.sdk_data['current_offcanvas_target'] , 'body sdkbs id', jQuery('body').attr('data-sdkbs-id') );\n\n                if(offcanvas_id==='diadao-mainmenu')\n                {\n                    let t_sdkbsOffCanvasTriggerClose=jQuery('#diadao-mainmenu-close');\n                    if(\n                        t_sdkbsOffCanvasTriggerClose\n&&t_sdkbsOffCanvasTriggerClose.length \n)\n                    {\n                        t_sdkbsOffCanvasTriggerClose.trigger('click');\n                        t_sdkbsOffCanvasTriggerCloseEnabled=true;\n                    }\n                }\n\n                if(!t_sdkbsOffCanvasTriggerCloseEnabled)\n                {\n                    // same panel - stop\n                    if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] same panel ! just change hash', offcanvas_hash);\n\n                    that.bsOffCanvasHandleTabs(offcanvas_id, offcanvas_hash);\n\n                    if(!that.sdk_data['current_hash_from_url'])\n                    {\n                        that.hashUpdateBsOffCanvasSdk(offcanvas_hash, false, true);\n                    }\n                }\n            }\n            else\n            {\n                let open_allowed=true;\n                let t_sdkbsOffCanvasTrigger=null, t_sdkbsOffCanvasTriggerScrollTo=null, t_sdkbsAnchorLink=null;\n                let t_sdkbsOffCanvasTriggerPill=null;\n\n                let offcanvas_is_room=false;\n                let offcanvas_is_booking=false;\n                let offcanvas_is_booking_mobile=false;\n                let offcanvas_is_menu=that.bsOffCanvasCheckIfMenu(offcanvas_id);\n                let offcanvas_is_bookingcustom=false;\n                if(offcanvas_type!==undefined&&offcanvas_type!==null&&offcanvas_type==='bookingcustom')\n                {\n                    offcanvas_is_bookingcustom=true;\n                }\n\n                if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] trigger is menu:', offcanvas_id, 'offcanvas_is_menu=', offcanvas_is_menu);\n\n                if(!offcanvas_is_bookingcustom)\n                {\n                    if(\n                        elem_trigger!==null\n&&elem_trigger!==undefined\n&&elem_trigger.length\n&&elem_trigger.data('slug')!==undefined\n&&elem_trigger.data('slug')!==null\n&&elem_trigger.data('slug').indexOf('room-')!==-1\n)\n                    {\n                        offcanvas_is_room=true;\n                        offcanvas_hash=that.roomSlugBsOffCanvasSdk(elem_trigger.data('slug') );\n                        t_sdkbsOffCanvasTriggerPill=that.getBsOffCanvasRoomPill(offcanvas_hash);\n\n                        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] is room offcanvas, offcanvas_hash=', offcanvas_hash);\n                    }\n\n                    // booking override\n                    if(\n                        !offcanvas_is_room\n&&\n                        (\n                            offcanvas_id==='booking' \n||offcanvas_id==='booking-hotel'\n)\n)\n                    {\n                        offcanvas_is_booking=true;\n\n                        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] is booking offcanvas');\n\n                        if(offcanvas_id==='booking')\n                        {\n                            offcanvas_id=offcanvas_id.replace('booking', 'diadao-snippet-booking');\n                            offcanvas_hash='booking';\n                        }\n                        else if(offcanvas_id==='booking-hotel')\n                        {\n                            offcanvas_id=offcanvas_id.replace('booking-hotel', 'booking-hotel');\n                            offcanvas_hash='booking-hotel';\n                        }\n\n                        if(that.isMobile())\n                        {\n                            offcanvas_is_booking_mobile=true;\n                        }\n                    }\n                }\n\n                if(\n                    from_hash \n&&!offcanvas_is_booking \n)\n                {\n                    // check if room\n                    if(offcanvas_is_room)\n                    {\n                        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash load - case 1A, offcanvas_hash=', offcanvas_hash);\n\n                        t_sdkbsOffCanvasTrigger=elem_trigger;\n                    }\n                    else\n                    {\n                        offcanvas_hash=offcanvas_id;\n\n                        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash load - case 1B, offcanvas_hash=', offcanvas_hash);\n\n                        t_sdkbsOffCanvasTrigger=that.getBsOffCanvasRoomPill(offcanvas_id); // jQuery('#' + that.containers['room_detail'] + ' .room-menu-item[data-slug=\"' + offcanvas_id + '\"]');\n                    }\n\n                    \n                    if(\n                        t_sdkbsOffCanvasTrigger!==null\n&&t_sdkbsOffCanvasTrigger.length\n&&!offcanvas_is_bookingcustom\n)\n                    {\n                        // is room panel\n                        offcanvas_is_room=true;\n\n                        offcanvas_id=that.containers['room_detail']; // override offcanvas ID to get the real container\n                        t_sdkbsOffCanvasTriggerScrollTo=jQuery('#rooms-container .diadao-room-item[data-postname=\"' + offcanvas_id + '\"]');\n                        \n                        t_sdkbsOffCanvasTriggerPill=that.getBsOffCanvasRoomPill(offcanvas_hash);\n                        \n                        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash load - case 1C', offcanvas_hash, t_sdkbsOffCanvasTriggerPill);\n                    }\n                    else\n                    {\n                        // other panel\n                        t_sdkbsOffCanvasTrigger=jQuery('[data-sdkbs-toggle=\"offcanvas\"][data-slug=\"' + offcanvas_id + '\"]');\n\n                        if(\n                            t_sdkbsOffCanvasTrigger!==null\n&&t_sdkbsOffCanvasTrigger.length\n)\n                        {\n                            t_sdkbsOffCanvasTriggerScrollTo=t_sdkbsOffCanvasTrigger;\n\n                            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash load - case 2');\n                        }   \n                        else\n                        {\n                            t_sdkbsOffCanvasTrigger=jQuery('[data-sdkbs-toggle=\"offcanvas\"][href=\"#' + offcanvas_id + '\"]');\n                            if(\n                                t_sdkbsOffCanvasTrigger!==null\n&&t_sdkbsOffCanvasTrigger.length\n)\n                            {\n                                t_sdkbsOffCanvasTriggerScrollTo=t_sdkbsOffCanvasTrigger;\n\n                                // last chance\n                                if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash load - case 3');\n                            }\n                        }        \n                    }\n\n                    if(\n                        t_sdkbsOffCanvasTrigger!==null\n&&t_sdkbsOffCanvasTrigger.length\n)\n                    {\n                        // is offcanvas hash trigger\n                        that.sdk_data['current_hash_from_url']=true;\n\n                        if(t_sdkbsOffCanvasTrigger.data('sdkbsTarget') )\n                        {\n                            offcanvas_id=t_sdkbsOffCanvasTrigger.data('sdkbsTarget');\n\n                            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] hash new real id:', offcanvas_id);\n                        }\n\n                        if(t_sdkbsOffCanvasTriggerScrollTo)\n                        {\n                            // scroll vers le conteneur parent avant ouverture\n                            jQuery('body, html').animate({\n                                scrollTop: t_sdkbsOffCanvasTrigger.offset().top,\n                            }, that.timeout['scrolling_top']);\n                        }\n                    }\n                    else\n                    {\n                        // is maybe anchor with hash\n                        that.sdk_data['current_hash_from_url']=false;\n\n                        // if anchor dispatch event\n                        t_sdkbsAnchorLink=document.querySelector('#' + offcanvas_id);\n                        if(t_sdkbsAnchorLink!==null)\n                        {\n                            open_allowed=false;\n\n                            const anchorScrollTo=new CustomEvent(\n                                \"anchorScrollTo\", { detail: '#' + offcanvas_id }\n);\n                            window.dispatchEvent(anchorScrollTo);\n                        }\n                    }\n                }\n                else\n                {\n                    that.sdk_data['current_hash_from_url']=false;\n                }\n\n                that.sdk_data['current_hash']=offcanvas_hash;\n\n                let t_sdkbsOffCanvasHashNoChange=false;\n                let t_sdkbsOffCanvasHashUpdate=true;\n\n                if(\n                    t_sdkbsOffCanvasTriggerPill!==null\n&&t_sdkbsOffCanvasTriggerPill!==undefined\n&&t_sdkbsOffCanvasTriggerPill.length\n)\n                {\n                    t_sdkbsOffCanvasHashNoChange=true;\n                    t_sdkbsOffCanvasHashUpdate=false;\n\n                    if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] trigger pill t_sdkbsOffCanvasTriggerPill=', t_sdkbsOffCanvasTriggerPill);\n\n                    that.setBsPillsRoomCurrent(t_sdkbsOffCanvasTriggerPill, true);\n                }\n                else\n                {\n                    offcanvas_id=that.formatIdBsOffCanvasSdk(offcanvas_id);\n                }\n\n                if(open_allowed)\n                {\n                    let t_sdkbsOffCanvasContainer=document.querySelector('.offcanvas#' + offcanvas_id);\n                    if(t_sdkbsOffCanvasContainer!==null)\n                    {\n                        if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] offcanvas debug - showing=', offcanvas_id, offcanvas_hash, 'is menu', offcanvas_is_menu, 'is booking', offcanvas_is_booking, 'offcanvas_is_booking_mobile', offcanvas_is_booking_mobile);\n\n                        if(\n                            offcanvas_is_menu \n||offcanvas_is_booking_mobile\n)\n                        {\n                            // is menu:close all before\n                            if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS] menu super close');\n                            that.superCloseCurrentBsOffCanvasSdk();\n                        }\n                        else\n                        {\n                            that.backdropBsOffCanvasSdk(true, false, false);\n                        }\n\n                        if(offcanvas_is_bookingcustom)\n                        {\n                            let offcanvas_bookingcustom_loader=false;\n                            if(\n                                elem_trigger!==undefined\n&&elem_trigger!==null \n&&elem_trigger.data('sdkbs-loader')!==undefined\n&&elem_trigger.data('sdkbs-loader')!==null\n&&elem_trigger.data('sdkbs-loader')===true\n)\n                            {\n                                offcanvas_bookingcustom_loader=true;\n                                elem_trigger.addClass('offcanvas-loading');\n                                setTimeout(function(){\n                                    elem_trigger.removeClass('offcanvas-loading');\n                                }, 5000);\n                            }\n\n                            // is booking custom:load iframe\n                            let offcanvas_bookingcustom_iframe=offcanvas_container.find('iframe.diadao-hsw-booking-iframe');\n\n                            if(that.debug['offcanvas_opening']) console.log('[SDK_OFFCANVAS is booking custom panel - check iframe load status', offcanvas_bookingcustom_iframe);\n\n                            if(offcanvas_container_from_slug)\n                            {\n                                offcanvas_container_prev_id='diadao-snippet-booking';\n                                offcanvas_container_prev=jQuery('#' + offcanvas_container_prev_id);\n                                if(\n                                    offcanvas_container_prev!==undefined \n&&offcanvas_container_prev.length\n&&!offcanvas_container_prev.hasClass('show')\n)\n                                {\n                                    offcanvas_container_prev.addClass('show');\n\n                                    that.sdk_data['bs_offcanvas_opened'].push(offcanvas_container_prev_id);\n                                    const t_sdkbsOffCanvasChanged=new CustomEvent(\n                                        \"t_sdkbsOffCanvasChanged\", { detail: that.sdk_data['bs_offcanvas_opened'].length }\n);\n                                    window.dispatchEvent(t_sdkbsOffCanvasChanged);\n                                }\n                            }\n\n                            if(\n                                offcanvas_bookingcustom_iframe!==undefined\n&&offcanvas_bookingcustom_iframe.length\n&&!offcanvas_bookingcustom_iframe.hasClass('hsw-is-loaded')\n                                //&&offcanvas_bookingcustom_iframe.data('src')!==undefined\n                                //&&offcanvas_bookingcustom_iframe.data('src')!==null\n)\n                            {\n                                const checkHswModuleReady=setInterval(()=> {\n                                    let offcanvas_bookingcustom_module=offcanvas_bookingcustom_iframe.contents().find('.hswportalbooking');\n                                    if(\n                                        offcanvas_bookingcustom_module!==undefined \n&&offcanvas_bookingcustom_module.length \n&&offcanvas_bookingcustom_module.hasClass('hsw-is-ready')\n)\n                                    {\n                                        clearInterval(checkHswModuleReady); // Arrêter la vérification\n                                        \n                                        offcanvas_bookingcustom_iframe.contents().find('html').trigger('click');\n                                        offcanvas_bookingcustom_iframe.contents().find('body').addClass('hsw-is-loaded');\n                                        offcanvas_bookingcustom_module.addClass('hsw-is-loaded');\n                                        offcanvas_bookingcustom_iframe.addClass('hsw-is-loaded');\n                                    }\n                                }, 50); \n                            }\n                        }\n\n                        ////////////////////////////////////////////////////////////////////////////\n                        // VRAI OUVERTURE D'UN OFFANVAS --\x3e\n                        ////////////////////////////////////////////////////////////////////////////\n\n                        t_sdkbsOffCanvasContainer.classList.add('showing');\n                        jQuery('body').addClass('sdkbs-panel-opening');\n                        setTimeout(function(){\n\n                            t_sdkbsOffCanvasContainer.classList.remove('showing');\n                            t_sdkbsOffCanvasContainer.classList.add('show');\n\n                            const t_sdkbsOffCanvasOpened=new CustomEvent(\n                                \"t_sdkbsOffCanvasOpened\", { \n                                    detail: {\n                                        name: offcanvas_type,\n                                        id: offcanvas_id,\n                                    }\n                                }\n);\n                            window.dispatchEvent(t_sdkbsOffCanvasOpened);\n\n                            that.bsOffCanvasHandleTabs(offcanvas_id, offcanvas_hash);\n\n                            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas - show=', offcanvas_id, 'pill=', t_sdkbsOffCanvasTriggerPill);\n\n                            that.bsOffCanvasArrayAdd(offcanvas_id, t_sdkbsOffCanvasHashUpdate);\n                            that.sdk_data['current_offcanvas_target']=offcanvas_id;\n\n                            if(!that.sdk_data['current_hash_from_url'])\n                            {\n                                that.hashUpdateBsOffCanvasSdk(offcanvas_hash, false, true);\n                            }\n\n                            jQuery('body').removeClass('sdkbs-panel-opening');\n\n                        }, that.timeout['sdk_offcanvas_show']);\n\n                        ////////////////////////////////////////////////////////////////////////////\n                        ////////////////////////////////////////////////////////////////////////////\n                        ////////////////////////////////////////////////////////////////////////////\n                    }\n                }\n            }\n        }\n    }\n\n    bsOffCanvasHandleTabs(offcanvas_id, offcanvas_hash)\n    {\n        const that=this;\n\n        let t_offcanvas_current=jQuery('.offcanvas#' + offcanvas_id);\n\n        if(that.debug['offcanvas_tabs']) console.log('[SDK_OFFCANVAS_TAB] CURRENT=', t_offcanvas_current);\n        \n        if(\n            t_offcanvas_current!==null\n&&t_offcanvas_current!==undefined\n&&t_offcanvas_current.length\n&&t_offcanvas_current.find('.offcanvas-tab')!==undefined\n&&t_offcanvas_current.find('.offcanvas-tab').length\n)\n        {\n            let t_offcanvas_current_tabs=t_offcanvas_current.find('.offcanvas-tab');\n            let t_offcanvas_current_tab=t_offcanvas_current.find('.offcanvas-tab#tab-' + offcanvas_hash + '[data-sdkbs-offcanvas-parent=\"' + offcanvas_id + '\"]');\n\n            if(that.debug['offcanvas_tabs']) console.log('[SDK_OFFCANVAS_TAB] CURRENT TAB=', t_offcanvas_current_tab);\n            if(\n                t_offcanvas_current_tab!==null\n&&t_offcanvas_current_tab!==undefined\n&&t_offcanvas_current_tab.length\n)\n            {\n                t_offcanvas_current_tabs.removeClass('show');\n\n                let t_offcanvas_current_tab_trigger=jQuery('[data-sdkbs-target=\"#' + offcanvas_id + '\"][data-slug=\"' + offcanvas_hash + '\"]');\n                if(\n                    t_offcanvas_current_tab_trigger!==null\n&&t_offcanvas_current_tab_trigger!==undefined\n&&t_offcanvas_current_tab_trigger.length\n)\n                {\n                    jQuery('.offcanvas-active').removeClass('offcanvas-active');\n                    t_offcanvas_current_tab_trigger.addClass('offcanvas-active');\n                }\n\n                t_offcanvas_current_tab.addClass('show');\n            }\n        }\n    }\n\n    formatIdBsOffCanvasSdk(offcanvas_hash)\n    {\n        const that=this;\n\n        let res=offcanvas_hash;\n\n        if(\n            res!==undefined \n&&res!==null \n&&res!==''\n&&res!=='#'\n&&res.indexOf('javascript')===-1\n&&res.indexOf('void(0)')===-1\n)\n        {\n            res=res.replace('#', '');\n        }\n        else if(\n            res===''\n)\n        {\n            res='';\n        } \n\n        if(\n            that.isString(res) \n&&res.substr(0, 5)==='room-'\n)\n        {\n            res=res.substr(5);\n            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] room slug formatted -> new slug=', res);\n        }\n\n        return res;\n    }\n\n    hashUpdateBsOffCanvasSdk(offcanvas_hash_input, offcanvas_search_hash=false, is_opening=false)\n    {\n        const that=this;\n\n        let offcanvas_hash=that.formatIdBsOffCanvasSdk(offcanvas_hash_input);\n        let offcanvas_hash_current=that.formatIdBsOffCanvasSdk(window.location.hash);\n\n        if(\n            offcanvas_hash!==null \n&&offcanvas_hash!==''\n            //&&offcanvas_hash.indexOf('javascript')===-1\n)\n        {\n            if(offcanvas_search_hash)\n            {\n                offcanvas_hash=that.searchHashBsOffCanvasSdk(offcanvas_hash, null);\n            }\n\n            if(that.sdk_data['hash_list_disabled'].indexOf(offcanvas_hash)===-1)\n            {\n                if(offcanvas_hash_current===offcanvas_hash)\n                {\n                    // do nothing (same hash)\n                    if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] update disabled - same hash');\n                }\n                else\n                {\n                    if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] update hash with:', offcanvas_hash, that.sdk_data['bs_offcanvas_opened']);\n\n                    history.replaceState(null, null, document.location.pathname + '#' + offcanvas_hash);\n                }\n            }\n        }\n        else if(\n            offcanvas_hash===''\n&&!is_opening\n)\n        {\n            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] clear hash');\n\n            history.replaceState({}, document.title, '.');  \n            that.backdropBsOffCanvasSdk(false, true, false);\n        }\n\n    }\n\n    superCloseCurrentBsOffCanvasSdk()\n    {\n        const that=this;\n\n        let t_body=jQuery('body');\n\n        if(\n            Fancybox!==undefined\n&&Fancybox!==null\n)\n        {\n            Fancybox.close();\n        }\n\n        jQuery('.offcanvas.show').removeClass('show'); // close all force\n\n        t_body.removeClass('sdkbs-panel-opened');\n        t_body.addClass('sdkbs-panel-not-opened');\n        t_body.removeClass('paneltype-modal').removeClass('paneltype-menu').removeClass('paneltype-offer').removeClass('paneltype-diadaopopup').removeClass('paneltype-default').removeClass('diadao-menuopened').removeClass('paneltype-bookingcustom');\n\n        t_body.attr('data-sdkbs-id', null);\n        t_body.removeAttr('data-sdkbs-id');\n\n        that.sdk_data['bs_offcanvas_opened']=[];\n        that.backdropBsOffCanvasSdk(false, true, false);\n        that.hashUpdateBsOffCanvasSdk('', false, false);\n        jQuery('.offcanvas-active').removeClass('offcanvas-active');\n\n        that.sdk_data['current_offcanvas_target']=null;\n\n        const t_sdkbsOffCanvasChanged=new CustomEvent(\n            \"t_sdkbsOffCanvasChanged\", { detail: that.sdk_data['bs_offcanvas_opened'].length }\n);\n        window.dispatchEvent(t_sdkbsOffCanvasChanged);\n\n        that.initVideoPopupAutoplay(false);\n\n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas super close - close all');\n    }\n\n    closeCurrentBsOffCanvasSdk()\n    {\n        const that=this;\n\n        // close with backdrop or escape\n        if(document.querySelectorAll('.offcanvas.show')!==null)\n        {\n            let t_sdkbsOffCanvasOpenedId=that.bsOffCanvasArrayGetLatest();\n            if(t_sdkbsOffCanvasOpenedId)\n            {\n                that.closeBsOffCanvasSdk(t_sdkbsOffCanvasOpenedId);\n            }\n        }\n    }\n\n    closeBsOffCanvasSdk(offcanvas_id)\n    {\n        const that=this;\n        \n        let t_sdkbsOffCanvasContainer=document.getElementById(offcanvas_id);\n        if(t_sdkbsOffCanvasContainer) \n        {\n            offcanvas_id=that.formatIdBsOffCanvasSdk(offcanvas_id);\n\n            if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas debug - hiding=', offcanvas_id);\n\n            that.backdropBsOffCanvasSdk(false, false, false);\n\n            t_sdkbsOffCanvasContainer.classList.add('hiding');\n\n            const t_sdkbsOffCanvasClosing=new CustomEvent(\n                \"t_sdkbsOffCanvasClosing\", { detail: t_sdkbsOffCanvasContainer }\n);\n            window.dispatchEvent(t_sdkbsOffCanvasClosing);\n\n            setTimeout(function(){\n\n                t_sdkbsOffCanvasContainer.classList.remove('show');\n                t_sdkbsOffCanvasContainer.classList.remove('hiding');\n\n                jQuery('.offcanvas-active').removeClass('offcanvas-active');\n\n                if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas debug - hide=', offcanvas_id);\n\n                that.bsOffCanvasArrayRemove(offcanvas_id);\n\n            }, that.timeout['sdk_offcanvas_hide']);\n        }\n    }\n\n    initBsOffCanvasSdk()\n    {\n        const that=this;\n\n        that.sdk_data['bs_offcanvas_backdrop']=document.querySelector('.offcanvas-backdrop.backdrop-sdk');\n\n        // close with backdrop\n        jQuery(document).on('click', '.offcanvas-backdrop.backdrop-sdk.show:not(.is-disabled)', function(e)\n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n            e.stopPropagation();\n\n            that.closeCurrentBsOffCanvasSdk();\n        });\n\n        // close button trigger\n        jQuery(document).on('click', '.offcanvas-backdrop.backdrop-sdk.show:not(.is-disabled), [data-sdkbs-dismiss=\"offcanvas\"]', function(e)\n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n            e.stopPropagation();\n\n            let t_sdkbsToggle=jQuery(this).closest('.offcanvas');\n            if(\n                t_sdkbsToggle!==undefined\n&&t_sdkbsToggle!==null \n&&t_sdkbsToggle.length\n&&t_sdkbsToggle.attr('id')!==undefined\n&&t_sdkbsToggle.attr('id')!==null\n&&t_sdkbsToggle.hasClass('show')\n)\n            {\n                let t_sdkbsOffCanvasId=t_sdkbsToggle.attr('id');\n\n                that.closeBsOffCanvasSdk(t_sdkbsOffCanvasId);\n            }\n        });\n\n        // open trigger\n        jQuery(document).on('click', '[data-sdkbs-toggle=\"offcanvas\"]', function(e)\n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n            e.stopPropagation();\n\n            let t_sdkbsToggle=jQuery(this);\n            if(\n                t_sdkbsToggle.data('sdkbsTarget')!==undefined\n&&t_sdkbsToggle.data('sdkbsTarget')!==null\n)\n            {\n                let t_sdkbsOffCanvasId=t_sdkbsToggle.data('sdkbsTarget');\n \n                if(\n                    t_sdkbsOffCanvasId!==null\n&&t_sdkbsOffCanvasId!==undefined\n)\n                {\n                    that.openBsOffCanvasSdk(t_sdkbsOffCanvasId, t_sdkbsToggle, false);\n                }\n            }\n        });\n\n        // booking panel\n        let t_bsOffCanvasHash='diadao-snippet-booking';\n        \n        let t_bsOffCanvasSlugMenu='booking';\n        let t_bsOffCanvasBookingMenu=jQuery('#' + t_bsOffCanvasHash);\n        \n        let t_bsOffCanvasSlugHotel='booking-hotel';\n        let t_bsOffCanvasBookingHotel=jQuery('#' + t_bsOffCanvasSlugHotel);\n        \n        let t_bsOffCanvasTriggers='';\n\n        if(\n            t_bsOffCanvasBookingMenu!==undefined\n&&t_bsOffCanvasBookingMenu!==null\n&&t_bsOffCanvasBookingMenu.length\n)\n        {\n            t_bsOffCanvasTriggers='[href=\"#' + t_bsOffCanvasSlugMenu + '\"]:not(.diadao-header-button), [data-snippet=\"' + t_bsOffCanvasSlugMenu + '\"]:not(.diadao-header-button)';\n             if(\n                t_bsOffCanvasBookingHotel===undefined\n||t_bsOffCanvasBookingHotel===null\n||!t_bsOffCanvasBookingHotel.length\n)\n            {\n                t_bsOffCanvasTriggers +=', [href=\"#booking\"], [data-snippet=\"booking\"]';\n            }\n\n            jQuery(document).on('click', t_bsOffCanvasTriggers, function(e){\n                e.preventDefault();\n\n                if(that.debug['prevent_default']) console.log('prevent default');\n                e.stopPropagation();\n                \n                that.superCloseCurrentBsOffCanvasSdk();\n\n                that.openBsOffCanvasSdk(t_bsOffCanvasHash, jQuery(this), false);\n            });\n        }\n\n        if(\n            t_bsOffCanvasBookingHotel!==undefined\n&&t_bsOffCanvasBookingHotel!==null\n&&t_bsOffCanvasBookingHotel.length\n)\n        {\n            let t_bsOffCanvasTriggers='[href=\"' + t_bsOffCanvasSlugHotel + '\"], .btn-booking.btn-booking-room > a:not([href=\"#' + t_bsOffCanvasSlugHotel + '\"])';\n            if(\n                t_bsOffCanvasBookingMenu===undefined\n||t_bsOffCanvasBookingMenu===null\n||!t_bsOffCanvasBookingMenu.length\n)\n            {\n                t_bsOffCanvasTriggers +=', [href=\"#booking\"], [data-snippet=\"booking\"]';\n            }\n\n            jQuery(document).on('click', t_bsOffCanvasTriggers, function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n                e.stopPropagation();\n                \n                that.superCloseCurrentBsOffCanvasSdk();\n\n                that.openBsOffCanvasSdk(t_bsOffCanvasSlugHotel, jQuery(this), false);\n            });\n        }\n    }\n\n    bsOffCanvasArrayAdd(current_id, update_hash)\n    {\n        const that=this;\n\n        let t_index=that.sdk_data['bs_offcanvas_opened'].indexOf(current_id);\n        \n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas - add to array - current index=', current_id);\n\n        if(t_index===-1)\n        {\n            that.sdk_data['bs_offcanvas_opened'].push(current_id);\n\n            that.eventBsOffCanvasSdk(current_id, update_hash, false);\n        }\n\n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas - added to array=', that.sdk_data['bs_offcanvas_opened']);\n    }\n\n    bsOffCanvasArrayRemove(current_id)\n    {\n        const that=this;\n\n        let t_index=that.sdk_data['bs_offcanvas_opened'].indexOf(current_id);\n        \n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas - remove from array - current index=', current_id);\n\n        if(t_index!==-1)\n        {\n            that.sdk_data['bs_offcanvas_opened']=that.sdk_data['bs_offcanvas_opened'].filter(function(id){\n                return id!==current_id;\n            });\n\n            that.eventBsOffCanvasSdk(current_id, true, true);\n        }\n    }\n\n    bsOffCanvasArrayGetLatest()\n    {\n        const that=this;\n\n        let res=null;\n\n        if(that.debug['offcanvas']) console.log('[SDK_OFFCANVAS] offcanvas array=', that.sdk_data['bs_offcanvas_opened'], that.sdk_data['bs_offcanvas_opened'].length);\n\n        if(that.sdk_data['bs_offcanvas_opened'])\n        {\n            res=that.sdk_data['bs_offcanvas_opened'][(that.sdk_data['bs_offcanvas_opened'].length - 1) ];\n        }\n\n        return res;\n    }\n\n    initEscape()\n    {\n        const that=this;\n\n        window.addEventListener('fancyBoxClose', function fancyBoxCloseListen(e) \n        {\n            if(that.debug['escape']) console.log('ESCAPE TRIGGERED FROM FANCYBOX');\n            e.currentTarget.removeEventListener(e.type, fancyBoxCloseListen);\n        });\n\n        jQuery(document).on('keyup', function(e) \n        {\n            if(\n                e.key===\"Escape\"\n&&!that.triggers['temporize_enabled']\n)\n            {\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n                e.stopPropagation();\n\n                if(\n                    !jQuery('html').hasClass('with-fancybox')\n&&!jQuery('body').hasClass('diadao-qs-calendars-opened')\n&&!jQuery('body').hasClass('diadao-qs-calendars-active')\n)\n                {\n                    let t_openedTotal=0; \n\n                    that.setTriggerTemporize();\n\n                    if(that.debug['escape']) console.log('ESCAPE TRIGGERED');\n\n                    if(\n                        jQuery('body').hasClass('diadao-hsw-popup-opened')\n&&jQuery('.diadao-hsw-popup.opened')!==undefined\n&&jQuery('.diadao-hsw-popup.opened').length\n&&!jQuery('body').hasClass('quicksearch-notice-open') \n)\n                    {\n                        t_openedTotal++;\n\n                        if(that.debug['escape']) console.log('ESCAPE CASE 1');\n\n                        jQuery('html').removeClass('diadao-hsw-popup-opened');\n                        jQuery('body').removeClass('diadao-hsw-popup-opened');\n                        jQuery('.diadao-hsw-popup.opened').removeClass('opened');\n                    }\n\n                    if(\n                        jQuery('#diadao-qs-availabilities-snippet-booking.opened')!==undefined\n&&jQuery('#diadao-qs-availabilities-snippet-booking.opened').length \n)\n                    {\n                        t_openedTotal++;\n\n                        if(that.debug['escape']) console.log('ESCAPE CASE 2');\n\n                        jQuery('#diadao-qs-availabilities-snippet-booking.opened').removeClass('opened');\n                        jQuery('html').removeClass('quicksearch-notice-open');\n                        jQuery('body').removeClass('quicksearch-notice-open');\n                    }\n\n                    if(\n                        jQuery('#diadao-qs-snippet-booking.opened')!==undefined\n&&jQuery('#diadao-qs-snippet-booking.opened').length \n)\n                    {\n                        t_openedTotal++;\n\n                        if(that.debug['escape']) console.log('ESCAPE CASE 3');\n\n                        jQuery('#diadao-qs-snippet-booking.opened').removeClass('opened');\n                        jQuery('html').removeClass('quicksearch-notice-open');\n                        jQuery('body').removeClass('quicksearch-notice-open');\n                    }\n\n                    if(that.debug['escape']) console.log('ESCAPE TRIGGERED - TOTAL OPENED:', t_openedTotal);\n\n                    if(t_openedTotal===0)\n                    {\n                        that.closeCurrentBsOffCanvasSdk();\n                    }\n                }\n            }\n        });\n    }\n\n    initScrollDownButton()\n    {\n        const that=this;\n\n        jQuery(document).on('click', 'body.device-desktop #diadao-scroll-down', function(e)\n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n\n            let t_buttonScroll=jQuery(this);\n\n            if(that.debug['scroll_top']) console.log('scroll top 2');\n\n            jQuery('body, html').animate({\n                scrollTop: jQuery(window).height(),\n            }, that.timeout['scrolling_top']);\n        });\n    }\n\n    initBsPillsRoom()\n    {\n        const that=this;\n\n        if(that.sdk_data['bs_pills_room']) console.log('init bs pills rooms');\n\n        let t_elems=document.querySelectorAll('#' + that.containers['room_detail'] + ' .room-menu-item[data-bs-toggle=\"pill\"]');\n\n        if(\n            t_elems \n&&t_elems!==null\n&&t_elems!==undefined\n)\n        {\n            t_elems.forEach(function(elem) \n            {\n                if(elem.getAttribute('id') )\n                {\n                    elem.addEventListener('shown.bs.tab', function(e) \n                    {\n                        let t_bsPillTrigger=jQuery('#' + e.target.getAttribute('id') );\n                        if(\n                            t_bsPillTrigger!==undefined\n&&t_bsPillTrigger.length\n&&t_bsPillTrigger.data('slug')!==undefined\n&&t_bsPillTrigger.data('slug')!==null\n&&t_bsPillTrigger.data('slug')!==''\n)\n                        {\n                            let t_bsPillTriggerHash=t_bsPillTrigger.data('slug');\n\n                            if(that.debug['bs_pills_room']) console.log('bs pill room changed, current -> target id=', e.target.getAttribute('id'), ' | target=', event.target, ' | slug=', t_bsPillTrigger.data('slug') );\n\n                            that.setBsPillsRoomCurrent(t_bsPillTrigger, false);\n\n                            that.hashUpdateBsOffCanvasSdk(t_bsPillTriggerHash, false, true);\n                            // TODO HASH ?\n                            //(t_bsPillTriggerHash, 'pill_room', e.target.getAttribute('id'), false);\n                        }\n                    });\n                }\n            });\n        }\n\n        jQuery(document).on('click', '.diadao-room-item .bs-trigger-target', function(e){\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n\n            if(that.sdk_data['bs_pills_room']) console.log('click surclassement');\n\n            let t_roomTarget=jQuery(this).data('target');\n            if(\n                t_roomTarget!==undefined\n&&t_roomTarget!==null\n)\n            {\n                t_roomTarget=t_roomTarget.replace('#', '');\n                if(t_roomTarget.indexOf('-tab')===-1)\n                {\n                    t_roomTarget=t_roomTarget + '-tab';\n                }\n\n                let t_roomTargetPill=jQuery('#' + t_roomTarget);\n                if(\n                    t_roomTargetPill!==undefined\n&&t_roomTargetPill.length\n)\n                {\n                    t_roomTargetPill.trigger('click');\n                }\n            }\n        });\n    }\n\n    setTriggerTemporize()\n    {\n        const that=this;\n\n        that.triggers['temporize_enabled']=true;\n        setTimeout(function(){\n            that.triggers['temporize_enabled']=false;\n        }, that.timeout['temporize']);\n    }\n\n    setScrollWatchTemporize()\n    {\n        const that=this;\n\n        that.triggers['temporize_scrollwatch_enabled']=true;\n        setTimeout(function(){\n            that.triggers['temporize_scrollwatch_enabled']=false;\n        }, that.timeout['temporize_scrollwatch']);\n    }\n\n    setBsPillsRoomCurrent(bs_pill_current, trigger_click)\n    {\n        const that=this;\n\n        let t_bsOffCanvasPanel=jQuery('#' + that.containers['room_detail'] + ' .offcanvas-body');\n        \n        that.setTriggerTemporize();\n\n        // scroll top\n        if(that.debug['scroll_top']) console.log('scroll top 3');\n\n        t_bsOffCanvasPanel.animate({\n            scrollTop: 0\n        }, 0);\n\n        // slide to\n        let t_currentBsSwiperEnabled=false;\n        let t_currentBsSwiperId=null;\n        let t_currentBsSwiperContainer=null;\n        let t_currentBsIndex=0;\n\n        if(\n            bs_pill_current.parent().hasClass('swiper-slide')\n&&bs_pill_current.parent().data('index')!==undefined\n&&bs_pill_current.parent().data('index')!==null\n)\n        {\n            t_currentBsSwiperEnabled=true;\n            t_currentBsIndex=bs_pill_current.parent().data('index');\n            t_currentBsSwiperId=bs_pill_current.parent().parent().parent().attr('id');\n            t_currentBsSwiperContainer=document.querySelector('#' + t_currentBsSwiperId).swiper;\n            \n            if(\n                t_currentBsSwiperContainer!==undefined\n&&t_currentBsSwiperContainer!==null\n)\n            {\n                if(that.debug['bs_pills_room']) console.log('room bs pill slide to index=', t_currentBsIndex);\n\n                t_currentBsSwiperContainer.slideTo(t_currentBsIndex, that.timeout['swiping']);\n            }\n        }\n\n        if(trigger_click)\n        {\n            bs_pill_current.trigger('click');\n        }\n\n        // après ouverture de la bonne chambre, si swiper gallery pas init, on l'init\n        that.initSwiperSnippet('body .kwpb-diadao-room-detail.active .kwpb-swiper-module.swiper-snippet:not(.is-initialized) .swiper-container.snippetSwiper');\n    }\n\n    // initHeaderNav()\n    // {\n    //     const that=this;\n\n    //     let t_headernavOffcanvas=document.querySelector('#diadao-mainmenu');\n    //     if(t_headernavOffcanvas)\n    //     {\n    //         document.querySelector('body').classList.add('diadao-menu-loaded');\n\n    //         t_headernavOffcanvas.addEventListener('hidden.bs.offcanvas', function(){\n    //             document.querySelector('body').classList.remove('diadao-menuopened');\n    //         });\n\n    //         t_headernavOffcanvas.addEventListener('show.bs.offcanvas', function(){\n    //             document.querySelector('body').classList.add('diadao-menuopened');\n    //         });\n    //     }\n    // }\n\n    initSwiperRoomThumb()\n    {\n        if(window['diadao_sdk_type']!=='sdk_template'){\n            const that=this;\n    \n            let t_swipersArray=jQuery('.swiper-container.swiper-room-thumb');\n            if(\n                t_swipersArray!==undefined \n&&t_swipersArray.length \n)\n            {\n                let t_swiperId=[], t_swiperContainer=[], t_swiperParent=[], t_swiper=[];\n                let t_swiperAutoplayDelay=4500;\n                let t_swiperAutoplayTimer=[\n                    1000,\n                    3000,\n                    5000,\n                    7000,\n                    9000, //\n                    11000,\n                    13000,\n                    15000,\n                    17000,\n                ];\n    \n                t_swipersArray.each(function(index) \n                {\n                    t_swiperContainer[index]=jQuery(this);\n                    t_swiperParent[index]=t_swiperContainer[index].parent();\n                    if(\n                        t_swiperContainer[index].data('diadaoswiperid')!==undefined \n&&t_swiperContainer[index].data('diadaoswiperid')!==null \n)\n                    {\n                        let t_direction=\"horizontal\";\n                        if(\n                            t_swiperContainer[index].data('swiperdirection')!==undefined \n&&t_swiperContainer[index].data('swiperdirection')!==null \n)\n                        {\n                            t_direction=t_swiperContainer[index].data('swiperdirection');\n                        }\n    \n                        t_swiperId[index]=t_swiperContainer[index].data('diadaoswiperid');\n                        \n                        if(that.debug['swiper_room_thumb']) console.log(\"** init swiper room thumb:\" + t_swiperId[index], t_swiperContainer[index]);\n    \n                        t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                            //lazy: true,\n                            //lazyPreloadPrevNext: 2,\n                            init: false,\n                            direction: t_direction,\n                            slidesPerView: 'auto',\n                            effect: \"fade\",\n                            crossFade: true,\n                            speed: 1000,\n                            centeredSlides: false,\n                            loop: true,\n                            rewind: false,\n                            clickable: false,\n                            simulateTouch: false,\n                            preventClicksPropagation: true,\n                            preventClicks: false,\n                            watchSlidesProgress: true,\n                            spaceBetween: 0,\n                            autoplay: {\n                                delay: t_swiperAutoplayDelay,\n                                disableOnInteraction: false,\n                                pauseOnMouseEnter: false,\n                                waitForTransition: true,\n                            },\n                            navigation: {\n                                nextEl: '.swiper-button-next.' + t_swiperId[index],\n                                prevEl: '.swiper-button-prev.' + t_swiperId[index],\n                            },\n                            pagination: {\n                                el: '.swiper-pagination.' + t_swiperId[index],\n                                clickable: true,\n                            }\n                        });\n                        \n                        t_swiper[index].on('init', function(e){\n                            t_swiperParent[index].addClass('is-initialized');\n                            \n                            t_swiper[index].autoplay.stop();\n                            setTimeout(function(){\n                                t_swiper[index].autoplay.start();\n                            }, t_swiperAutoplayTimer[index]);\n                        });\n    \n                        t_swiper[index].init();\n    \n                    }\n                });\n            }\n        }\n    }\n\n    initScrolling()\n    {\n        const that=this;\n        let t_isHsw=false;\n\n        that.handleScrollingTop();\n        that.handleScrollingScrollout();\n\n        setTimeout(function(){\n            that.handleScrolling(t_isHsw);\n        }, that.timeout['scrolling']);\n    }\n\n    checkScroll()\n    {\n        const that=this;\n\n        if(\n            jQuery(window).scrollTop()===0\n&&jQuery('body').hasClass('diadao-villaaugusta') \n)\n        {\n            document.querySelector('body').classList.remove('sdkFiltersStickyEnabled');\n        }\n    }\n\n    handleScrolling(is_hsw)\n    {   \n        const that=this;\n\n        let t_scrollClass='';\n        let t_scrollDirection='';\n\n        that.checkScroll();\n\n        jQuery(window).scroll(function() \n        {\n            if(\n                that.triggers['temporize_enabled'] \n||that.triggers['temporize_scrollwatch_enabled'] \n)\n            {\n                // do nothing\n            }\n            else\n            {\n                that.handleScrollingTop();\n                that.handleScrollingScrollout();\n\n                if(!is_hsw)\n                {\n                    if(that.triggers['prev'] < jQuery(window).scrollTop())\n                    {\n                        // down\n                        t_scrollClass='diadao-scrolling-down';\n                        t_scrollDirection='diadao-scrolled-down';\n                    }\n                    else\n                    {\n                        // top\n                        t_scrollClass='diadao-scrolling-top';\n                        t_scrollDirection='diadao-scrolled-up';\n                    }\n\n                    document.querySelector('body').classList.remove('diadao-scrolling-top');\n                    document.querySelector('body').classList.remove('diadao-scrolling-down');\n                    document.querySelector('body').classList.add(t_scrollClass);\n\n                    if(t_scrollDirection==='diadao-scrolled-up')\n                    {\n                        document.querySelector('body').classList.remove('diadao-scrolled-down');\n                        document.querySelector('body').classList.add(t_scrollDirection); \n                    }\n                    else if(t_scrollDirection==='diadao-scrolled-down')\n                    {\n                        document.querySelector('body').classList.remove('diadao-scrolled-up');\n                        document.querySelector('body').classList.add(t_scrollDirection); \n                    }\n\n                    if(t_scrollClass==='diadao-scrolling-down')\n                    {\n                        document.querySelector('body').classList.add('diadao-is-scrolling');\n                    }\n\n                    clearTimeout(jQuery.data(this, 'scrollTimer') );\n                    jQuery.data(this, 'scrollTimer', setTimeout(function() \n                    {\n                        document.querySelector('body').classList.remove('diadao-is-scrolling');\n                        document.querySelector('body').classList.remove('diadao-scrolling-top');\n                        document.querySelector('body').classList.remove('diadao-scrolling-down');\n\n                        that.checkScroll();\n                    }, 250) );\n\n                    that.triggers['prev']=jQuery(window).scrollTop();\n                }\n            }\n        });\n    }\n\n    handleScrollingTop()\n    {\n        const that=this;\n\n        if(jQuery(window).scrollTop() <=100)\n        {\n            document.querySelector('body').classList.add('diadao-scroll-top');\n        }\n        else\n        {\n            document.querySelector('body').classList.remove('diadao-scroll-top');\n        }\n\n        if(( jQuery(window).scrollTop() + jQuery(window).outerHeight()) >=jQuery(document).outerHeight())\n        {\n            document.querySelector('body').classList.add('diadao-scroll-end');\n        }\n        else\n        {\n            document.querySelector('body').classList.remove('diadao-scroll-end');\n        }\n    }\n\n    handleScrollingScrollout()\n    {\n        const that=this;\n\n        if(jQuery(window).scrollTop() > jQuery(window).outerHeight())\n        {\n            document.querySelector('body').classList.add('diadao-window-scrollout');\n        }\n        else\n        {\n            document.querySelector('body').classList.remove('diadao-window-scrollout');\n        }\n    }\n\n    initMenuMedias()\n    {\n        const that=this;\n\n        let t_menuDynamic=jQuery('body.device-desktop header#diadao-header .diadao-bg-menu-dynamic');\n        if(that.debug['menu_medias']) console.log(\"-> try menu dynamic=\", t_menuDynamic);\n        if(\n            t_menuDynamic!==undefined\n&&t_menuDynamic!==null\n&&t_menuDynamic.length\n)\n        {\n            if(that.debug['menu_medias']) console.log(\"-> init menu dynamic\");\n\n            let t_menuBgDefault=null;\n            if(\n                t_menuDynamic.find('.active')!==undefined\n&&t_menuDynamic.find('.active').length \n)\n            {\n                t_menuBgDefault=t_menuDynamic.find('.active');\n            }\n            else if(\n                t_menuDynamic.find('.slug-default')!==undefined\n&&t_menuDynamic.find('.slug-default').length \n)\n            {\n                t_menuBgDefault=t_menuDynamic.find('.slug-default');\n            }\n            else if(\n                t_menuDynamic.find('.slug-default')!==undefined\n&&t_menuDynamic.find('.slug-default').length \n)\n            {\n                t_menuBgDefault=t_menuDynamic.find('.bg-menu').first();\n            }\n\n            if(that.debug['menu_medias']) console.log(\"-> t_menuBgDefault :\", t_menuBgDefault);\n\n            if(\n                t_menuBgDefault!==undefined\n&&t_menuBgDefault!==null\n&&t_menuBgDefault.length\n)\n            {\n                t_menuBgDefault.addClass('active');\n                // removed for now:body.device-desktop header#diadao-header .diadao-menu ul > li,\n                // ajout du hover pour sous-menus Quentin le 3/07/24:body.device-desktop header#diadao-header .menu-right ul > li\n                jQuery(document).on('mouseover', 'body.device-desktop header#diadao-header .diadao-menu ul > li, body.device-desktop header#diadao-header .menu-left ul > li, body.device-desktop header#diadao-header .menu-right ul > li', function(e) \n                {\n                    t_menuBgDefault.removeClass('active');\n                    if(\n                        jQuery(this).data('id')!==undefined \n&&jQuery(this).data('slug')!==undefined \n&&jQuery(this).data('id')!==null \n&&jQuery(this).data('slug')!==null \n)\n                    {\n                        let t_itemId=jQuery(this).data('id');\n                        let t_itemSlug=jQuery(this).data('slug');\n\n                        if(that.debug['menu_medias']) console.log(\"-> menu dynamic hover :\", t_itemId, t_itemSlug);\n\n                        let t_menuBgHover=t_menuDynamic.find('.menu-item-' + t_itemId);\n                        if(\n                            t_menuBgHover===undefined\n||!t_menuBgHover.length\n)\n                        {\n                            t_menuBgHover=t_menuDynamic.find('.slug-' + t_itemSlug);\n                            if(\n                                t_menuBgHover===undefined \n||!t_menuBgHover.length\n)\n                            {\n                                t_menuBgHover=t_menuBgDefault;\n                            }\n                        }\n\n                        if(!t_menuBgHover.hasClass('active') )\n                        {\n                            t_menuDynamic.find('.active').removeClass('active');\n                            t_menuBgHover.addClass('active');\n                        }\n                    }\n                });\n            }\n        }\n    }\n\n    isMobile()\n    {\n        const that=this;\n\n        let res=false;\n        if(jQuery('body').hasClass('device-mobile') )\n        {\n            res=true;\n        }\n\n        return res;\n    }\n\n    // that.initSwiperSnippet('body .kwpb-diadao-room-detail.active .kwpb-swiper-module.swiper-snippet:not(.is-initialized) .swiper-container.snippetSwiper');\n\n    initSwiperSnippet(selector=null)\n    {\n        const that=this;\n\n        // kwpb-diadao-room-detail tab-pane fade active show\n        // selector='body .kwpb-diadao-room-detail.active .kwpb-swiper-module.swiper-snippet:not(.is-initialized) .swiper-container.snippetSwiper';\n\n        let is_default_selector=false;\n        if(selector===null)\n        {\n            is_default_selector=true;\n            selector='body .kwpb-swiper-module.swiper-snippet:not(.is-initialized) .swiper-container.snippetSwiper';\n        }\n\n        let t_arraySwipers=jQuery(selector);\n        if(that.debug['swiper_snippet']) console.log('[SWIPER INIT 002] INIT SNIPPETS SWIPER', t_arraySwipers.length);\n\n        if(\n            t_arraySwipers!==null\n&&t_arraySwipers!==undefined\n&&t_arraySwipers.length\n)\n        {\n            let t_body=jQuery('body');\n            let t_swiperParentPanel=[], t_swiperId=[], t_swiperContainer=[], t_swiperParent=[], t_swiper=[], t_swiperSlidesTotal=[];\n\n            t_arraySwipers.each(function(index) \n            {\n                t_swiperContainer[index]=jQuery(this);\n                t_swiperParentPanel[index]=jQuery(this).parent().parent();\n\n                if(\n                    is_default_selector\n&&t_swiperParentPanel[index].hasClass('room-panel-gallery')\n)\n                {   \n                    // c'est une chambre, on l'init pas par défaut comme les autres panneaux\n                    // do nothing\n                }\n                else\n                {\n                    if(\n                        t_swiperContainer[index].data('diadaoswiperid')\n&&t_swiperContainer[index].data('diadaoswiperid')!==undefined \n&&t_swiperContainer[index].data('diadaoswiperid')!==null\n)\n                    {\n                        t_swiperId[index]=t_swiperContainer[index].data('diadaoswiperid');\n                        t_swiperParent[index]=t_swiperContainer[index].parent();\n\n                        if(\n                            t_swiperParent[index].find('.swiper-slide')!==null\n&&t_swiperParent[index].find('.swiper-slide')!==undefined\n&&t_swiperParent[index].find('.swiper-slide').length\n)\n                        {\n                            t_swiperSlidesTotal[index]=parseInt(t_swiperParent[index].find('.swiper-slide').length);\n                        }\n\n                        if(\n                            !t_swiperParent[index].hasClass('is-initialized') \n&&t_swiperSlidesTotal[index] > 1\n)\n                        {\n                            // si pas initialisé et > 1 slide\n                            if(that.debug['swiper_snippet']) console.log(\">> init snippet swiper\", t_swiperId[index], \"nb slides:\", t_swiperSlidesTotal[index]);\n\n                            let t_navigation=false;\n                            if(\n                                t_swiperParent[index].find('.swiper-navigation.snippetSwiper.' + t_swiperId[index])!==null\n&&t_swiperParent[index].find('.swiper-navigation.snippetSwiper.' + t_swiperId[index])!==undefined\n&&t_swiperParent[index].find('.swiper-navigation.snippetSwiper.' + t_swiperId[index]).length\n)\n                            {\n                                t_navigation={\n                                                nextEl: '.swiper-button-next.snippetSwiper.' + t_swiperId[index],\n                                                prevEl: '.swiper-button-prev.snippetSwiper.' + t_swiperId[index],\n                                            };\n                            }\n\n                            let t_effect=\"fade\";\n                            let t_fadeEffect={\n                                                    crossFade: true\n                                                };\n                            if(that.isMobile())\n                            {\n                                t_effect=\"slide\";\n                                t_fadeEffect=null;\n                            }\n\n                            t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                                init: false,\n                                speed: 1000,\n                                slidesPerView: 1,\n                                centeredSlides: false,\n                                spaceBetween: 0,\n                                loop: true,\n                                effect: t_effect,\n                                fadeEffect: t_fadeEffect,\n                                clickable: false,\n                                allowTouchMove: true,\n                                simulateTouch: true,\n                                shortSwipes: true,\n                                preventClicks: false,\n                                preventClicksPropagation: true,\n                                navigation: false,\n                                watchSlidesProgress: true,\n                                autoplay: false,\n                                pagination: {\n                                    el: '.swiper-pagination.snippetSwiper.' + t_swiperId[index],\n                                    clickable: true,\n                                },\n                                navigation: t_navigation,\n                                // en cas d'interaction, swipe, on accélère la vitesse, puis on la réinitialise après 1s \n                                on: {\n                                    touchStart: function (){\n                                        this.params.speed=300;\n                                    },\n                                    transitionEnd: function (){\n                                        setTimeout(()=> {\n                                            this.params.speed=1000;\n                                        }, 1000);\n                                    },\n                                },\n                            });\n\n                            t_swiper[index].on('init', function(e){\n                                t_swiperParent[index].addClass('is-initialized');\n\n                                if(that.debug['swiper_snippet']) console.log(\">> swiper is init\", t_swiperId[index]);\n                            });\n\n                            t_swiper[index].on('afterInit', function(e){\n                                if(that.debug['swiper_snippet']) console.log(\">> swiper is afterInit\", t_swiperId[index]);\n                            });\n\n                            t_swiper[index].on('slideChange', function(e){\n                            });\n\n                            t_swiper[index].init();\n                        }\n                    }\n                }\n            });\n        }\n    }\n\n    initSwiperDefault()\n    {\n        const that=this;\n\n        if(that.debug['swiper_default']) console.log(\"[SWIPER INIT 001] try init swiper default\");\n        \n        let t_arraySwipers=jQuery('.kwpb-swiper-module .swiper-container[data-diadaoswiperconfig=\"default\"]:not(.snippetSwiper)');\n        if(that.debug['swiper_default']) console.log(t_arraySwipers);\n\n        if(\n            t_arraySwipers!==undefined\n&&t_arraySwipers.length\n)\n        {\n            let t_body=jQuery('body');\n            let t_swiperContainer=[], t_swiperId=[], t_swiperParent=[], t_swiper=[];\n\n            t_arraySwipers.each(function(index) \n            {\n                t_swiperContainer[index]=jQuery(this);\n                if(\n                    t_swiperContainer[index].data('diadaoswiperid')\n&&t_swiperContainer[index].data('diadaoswiperid')!==undefined \n&&t_swiperContainer[index].data('diadaoswiperid')!==null\n)\n                {\n                    t_swiperId[index]=t_swiperContainer[index].data('diadaoswiperid');\n                    t_swiperParent[index]=t_swiperContainer[index].parent();\n\n                    if(!t_swiperParent[index].hasClass('is-initialized') )\n                    {\n                        if(that.debug['swiper_default']) console.log(\"** init swiper default:\" + t_swiperId[index], t_swiperContainer[index]);\n\n                        t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                            init: false,\n                            slidesPerView: 'auto',\n                            spaceBetween: 0,\n                            speed: 1000,\n                            centeredSlides: false,\n                            loop: false,\n                            rewind: false,\n                            clickable: false,\n                            preventClicksPropagation: true,\n                            preventClicks: false,\n                            watchSlidesProgress: true,\n                            autoplay: false,\n                            navigation: {\n                                nextEl: '.swiper-button-next.' + t_swiperId[index],\n                                prevEl: '.swiper-button-prev.' + t_swiperId[index],\n                            },\n                            pagination: {\n                                el: '.swiper-pagination.' + t_swiperId[index],\n                                clickable: true,\n                            },\n                            breakpoints: {\n                                0: {\n                                    simulateTouch: true,\n                                    effect: 'slide'\n                                },\n                                992: {\n                                    simulateTouch: false,\n                                    effect: 'fade'\n                                }\n                            }\n                        });\n                        \n                        t_swiper[index].on('init', function(e){\n                            t_swiperParent[index].addClass('is-initialized');\n                        });\n\n                        t_swiper[index].on('afterInit', function(e){\n                        });\n\n                        t_swiper[index].on('slideChange', function(e){\n                        });\n\n                        t_swiper[index].init();\n                    }\n                }\n            });\n        }\n    }\n    \n    initSwiperRoomUpgrade()\n    {\n        const that=this;\n\n        if(that.debug['swiper_room_upgrade']) console.log(\"try init swiper upgrades\");\n        \n        let t_arraySwipers=jQuery('.kwpb-swiper-module .swiper-container.roomUpgradeSwiper');\n        if(that.debug['swiper_room_upgrade']) console.log(t_arraySwipers);\n\n        if(\n            t_arraySwipers!==undefined\n&&t_arraySwipers!==null\n&&t_arraySwipers.length\n)\n        {\n            let t_body=jQuery('body');\n            let t_swiperContainer=[], t_swiperId=[], t_swiperParent=[], t_swiper=[];\n\n            t_arraySwipers.each(function(index) \n            {\n                t_swiperContainer[index]=jQuery(this);\n                if(\n                    t_swiperContainer[index].data('diadaoswiperid')!==undefined \n&&t_swiperContainer[index].data('diadaoswiperid')!==null\n)\n                {\n                    t_swiperId[index]=t_swiperContainer[index].data('diadaoswiperid');\n                    t_swiperParent[index]=t_swiperContainer[index].parent();\n\n                    if(!t_swiperParent[index].hasClass('is-initialized') )\n                    {\n                        if(that.debug['swiper_room_upgrade']) console.log(\"** init swiper default:\" + t_swiperId[index], t_swiperContainer[index]);\n\n                        t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                            init: false,\n                            slidesPerView: 1,\n                            spaceBetween: 0,\n                            speed: 1000,\n                            centeredSlides: false,\n                            loop: false,\n                            rewind: false,\n                            clickable: false,\n                            preventClicksPropagation: true,\n                            preventClicks: false,\n                            watchSlidesProgress: true,\n                            autoplay: false,\n                            navigation: false,\n                            pagination: {\n                                el: '.swiper-pagination.' + t_swiperId[index],\n                                clickable: true,\n                            },\n                            breakpoints: {\n                                0: {\n                                    simulateTouch: true,\n                                    effect: 'slide'\n                                },\n                                992: {\n                                    simulateTouch: false,\n                                    effect: 'fade'\n                                }\n                            }\n                        });\n                        \n                        t_swiper[index].on('init', function(e){\n                            t_swiperParent[index].addClass('is-initialized');\n                        });\n\n                        t_swiper[index].on('afterInit', function(e){\n                        });\n\n                        t_swiper[index].on('slideChange', function(e){\n                        });\n\n                        t_swiper[index].init();\n                    }\n                }\n            });\n        }\n    }\n\n    initSwiperRoomMenu()\n    {\n        if(window['diadao_sdk_type']!=='sdk_template'){\n            const that=this;\n    \n            let t_arraySwipers=jQuery('body .roomDetailMenuSwiper');\n            if(that.debug['swiper_room_menu']) console.log(t_arraySwipers);\n    \n            if(\n                t_arraySwipers!==undefined\n&&t_arraySwipers.length\n)\n            {\n                let t_body=jQuery('body');\n                let t_swiperId=[], t_swiperContainer=[], t_swiperParent=[], t_swiper=[];\n    \n                let t_swiperOption_desktop_centered=false;\n                let t_swiperOption_desktop_slidesPerView='auto';\n                let t_swiperOption_desktop_spaceBetween=0;\n    \n                let t_swiperOption_responsive_centered=false; //true;\n                let t_swiperOption_responsive_slidesPerView='auto';\n                let t_swiperOption_responsive_spaceBetween=0;\n    \n                t_arraySwipers.each(function(index) \n                {\n                    t_swiperContainer[index]=jQuery(this);\n                    t_swiperParent[index]=t_swiperContainer[index].parent();\n                    t_swiperId[index]=t_swiperContainer[index].attr('id');\n    \n                    t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                        init: false,\n                        lazy: false,\n                        speed: 800,\n                        slidesPerView: 'auto',\n                        loop: false,\n                        rewind: false,\n                        clickable: false,\n                        allowTouchMove: true,\n                        simulateTouch: true,\n                        shortSwipes: false,\n                        preventClicks: true,\n                        preventClicksPropagation: true,\n                        watchSlidesProgress: true,\n                        autoplay: false,\n                        pagination: false,\n                        navigation: false,\n                        freeMode: {\n                            enabled: true,\n                            sticky: true,\n                        },\n                        breakpoints: {\n                            0: {\n                                centeredSlides: t_swiperOption_responsive_centered,\n                                slidesPerView: t_swiperOption_responsive_slidesPerView,\n                                spaceBetween: t_swiperOption_responsive_spaceBetween\n                            },\n                            992: {\n                                centeredSlides: t_swiperOption_desktop_centered,\n                                slidesPerView: t_swiperOption_desktop_slidesPerView,\n                                spaceBetween: t_swiperOption_desktop_spaceBetween\n                            }\n                        },\n                        on: {\n                            init: function(e)\n                            {\n                                t_swiperParent[index].addClass('is-initialized');\n    \n                                if(\n                                    t_swiperOption_desktop_centered \n&&!that.isMobile()\n)\n                                {\n                                    t_swiperParent[index].addClass('is-centeredMode');\n                                }\n                                else if(\n                                    t_swiperOption_responsive_centered\n&&that.isMobile()\n)\n                                {\n                                    t_swiperParent[index].addClass('is-centeredMode');\n                                }\n                            },\n                            afterInit: function(e) \n                            {\n                                //if(that.debug['swiper_room_menu']) console.log(\"after init swiper menu check current:\", t_swiperParent[index].find('.room-menu-item.active') );\n    \n                                if(\n                                    t_swiperParent[index].find('.room-menu-item.active')!==undefined\n&&t_swiperParent[index].find('.room-menu-item.active').length\n&&t_swiperParent[index].find('.room-menu-item.active').parent().data('index')!==undefined\n)\n                                {\n                                    let t_activeIndex=t_swiperParent[index].find('.room-menu-item.active').parent().data('index');\n    \n                                    if(that.debug['swiper_room_menu']) console.log(\"after init swiper room menu current active index:\", t_activeIndex);\n    \n                                    e.slideTo(t_activeIndex, 0);\n                                }\n                            } \n                        }\n                    });\n                    \n                    t_swiper[index].on('init', function(e) \n                    {\n                        t_swiperParent[index].addClass('is-initialized');\n    \n                        this.autoplay.stop();\n                    });\n    \n                    t_swiper[index].init();\n    \n                });\n            }\n        }\n    }\n\n    initSwiperFilterMenu()\n    {\n        const that=this;\n\n        let t_arraySwipers=jQuery('body .swiperSdkFilters');\n        if(that.debug['swiper_filter_menu']) console.log(t_arraySwipers);\n\n        if(\n            t_arraySwipers!==undefined\n&&t_arraySwipers.length\n)\n        {\n            let t_body=jQuery('body');\n            let t_swiperId=[], t_swiperContainer=[], t_swiperParent=[], t_swiper=[];\n\n            let t_swiperOption_desktop_centered=false;\n            let t_swiperOption_desktop_slidesPerView='auto';\n            let t_swiperOption_desktop_spaceBetween=0;\n\n            let t_swiperOption_responsive_centered=false; //true;\n            let t_swiperOption_responsive_slidesPerView='auto';\n            let t_swiperOption_responsive_spaceBetween=0;\n\n            t_arraySwipers.each(function(index) \n            {\n                t_swiperContainer[index]=jQuery(this);\n                t_swiperParent[index]=t_swiperContainer[index].parent();\n                t_swiperId[index]=t_swiperContainer[index].attr('id');\n\n                t_swiper[index]=new Swiper(t_swiperContainer[index][0], {\n                    init: false,\n                    lazy: false,\n                    speed: 800,\n\n                    openSpeed: 1000,\n                    closeSpeed: 1000,\n                    nextSpeed: 1000,\n                    prevSpeed: 1000,\n                    openEffect: 'elastic',\n                    closeEffect: 'elastic',\n                    nextEffect: 'fade',\n                    prevEffect: 'fade',\n                    openEasing: 'linear',\n                    closeEasing: 'linear',\n                    nextEasing: 'linear',\n                    prevEasing: 'linear',\n\n                    slidesPerView: 'auto',\n                    loop: false,\n                    rewind: false,\n                    clickable: false,\n                    allowTouchMove: true,\n                    simulateTouch: true,\n                    shortSwipes: false,\n                    preventClicks: true,\n                    preventClicksPropagation: true,\n                    watchSlidesProgress: true,\n                    autoplay: false,\n                    pagination: false,\n                    navigation: false,\n                    freeMode: {\n                        enabled: true,\n                        sticky: true,\n                    },\n                    breakpoints: {\n                        0: {\n                            centeredSlides: t_swiperOption_responsive_centered,\n                            slidesPerView: t_swiperOption_responsive_slidesPerView,\n                            spaceBetween: t_swiperOption_responsive_spaceBetween\n                        },\n                        992: {\n                            centeredSlides: t_swiperOption_desktop_centered,\n                            slidesPerView: t_swiperOption_desktop_slidesPerView,\n                            spaceBetween: t_swiperOption_desktop_spaceBetween\n                        }\n                    },\n                    on: {\n                        init: function(e)\n                        {\n                            t_swiperParent[index].addClass('is-initialized');\n\n                            if(\n                                t_swiperOption_desktop_centered \n&&!that.isMobile()\n)\n                            {\n                                t_swiperParent[index].addClass('is-centeredMode');\n                            }\n                            else if(\n                                t_swiperOption_responsive_centered\n&&that.isMobile()\n)\n                            {\n                                t_swiperParent[index].addClass('is-centeredMode');\n                            }\n                        },\n                        afterInit: function(e) \n                        {\n                            if(that.debug['swiper_filter_menu']) console.log(\"after init swiper smart gallery\");\n                        }\n                    }\n                });\n                \n                t_swiper[index].on('init', function(e) \n                {\n                    t_swiperParent[index].addClass('is-initialized');\n\n                    this.autoplay.stop();\n                });\n\n                t_swiper[index].init();\n\n            });\n        }\n    }\n\n    isString(x) \n    {\n        return Object.prototype.toString.call(x)===\"[object String]\"\n    }\n\n    initFilterMenu()\n    {\n        // window['diadao_sdk_blog_categories_multiple_enabled']\n        const that=this;\n        const sdkChangeFilterGallery=new Event(\"sdkChangeFilterGallery\");\n\n        if(that.debug['filter_menu']) console.log('-> filters:init');\n\n        jQuery(document).on('click', '.dia-smartgallery .dia-filters .nav-link.link-filter, .dia-smartgallery__filters .nav-link.link-filter', function(e) \n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n\n            let t_navItem=jQuery(this);\n            if(\n                t_navItem.data('slug')!==undefined\n&&t_navItem.data('slug')!==null\n)\n            {\n                let t_navItemSlug=t_navItem.data('slug'); \n                let t_navItemSlugsActiveArray=[];\n                \n                if(that.debug['filter_menu']) console.log('-> filters:triggered slug=', t_navItemSlug, ' | slugs:');\n                \n                let t_navItemCurrent=null;\n                let t_navItemSwiperEnabled=false;\n                let t_navItemParent=null;\n                let t_navItemParentId=null;\n                let t_navItemParentWrapper=null;\n                let t_navItemContainerScrollTo=null;\n                let t_navItemContainerScrollToId=null;\n                let t_navItemObjectsWrapperId=null;\n                let t_navItemObjectsWrapper=null;\n                let t_navItemObjectsPostType=null;\n                let t_navItemObjectsFilterAllEnabled=false;\n                let t_navItemObjectsFilterAllLink=null;\n                let t_navItemObjectsMultiFilterEnabled=false;\n\n                if(t_navItem.parent().parent().hasClass('swiper-slide') )\n                {\n                    t_navItemSwiperEnabled=true;\n                    t_navItemParent=t_navItem.parent().parent().parent().parent();\n                    t_navItemParentId=t_navItemParent.attr('id');\n                    t_navItemParentWrapper=t_navItemParent.parent().parent().parent();\n                    t_navItemObjectsWrapperId=t_navItemParentId.replace('-nav', '') + '-grid';\n                    t_navItemObjectsWrapper=t_navItemParentWrapper.find('#' + t_navItemObjectsWrapperId);\n                    t_navItemContainerScrollTo=t_navItemObjectsWrapper.parent().parent();\n                    t_navItemContainerScrollToId=t_navItemContainerScrollTo.attr('id');\n                }\n                else\n                {\n                    t_navItemParent=t_navItem.parent();\n                    t_navItemParentId=t_navItemParent.attr('id');\n                    t_navItemParentWrapper=t_navItemParent.parent().parent();\n                    t_navItemObjectsWrapperId=t_navItemParentId.replace('-nav', '') + '-grid';\n                    t_navItemObjectsWrapper=t_navItemParentWrapper.find('#' + t_navItemObjectsWrapperId);\n                    t_navItemContainerScrollTo=t_navItemObjectsWrapper.parent().parent();\n                    t_navItemContainerScrollToId=t_navItemContainerScrollTo.attr('id');\n                }\n\n                if(t_navItemContainerScrollToId===undefined||t_navItemContainerScrollToId===null)\n                {\n                    t_navItemContainerScrollTo=t_navItemObjectsWrapper;\n                    t_navItemContainerScrollToId=t_navItemObjectsWrapperId;\n                }\n\n                if(t_navItemObjectsWrapper)\n                {\n                    if(\n                        jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"all\"]')!==undefined\n||jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"all\"]').length\n)\n                    {\n                        t_navItemObjectsFilterAllEnabled=true;\n                        t_navItemObjectsFilterAllLink=jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"all\"]');\n                    }\n                    else if(\n                        jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"*\"]')!==undefined\n||jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"*\"]').length\n)\n                    {\n                        t_navItemObjectsFilterAllEnabled=true;\n                        t_navItemObjectsFilterAllLink=jQuery('#' + t_navItemParentId + ' .nav-link.link-filter[data-slug=\"*\"]');\n                    }\n\n                    if(\n                        t_navItemObjectsWrapper.data('posttype')!==undefined\n&&t_navItemObjectsWrapper.data('posttype')!==null\n)\n                    {\n                        t_navItemObjectsPostType=t_navItemObjectsWrapper.data('posttype');\n                        if(\n                            window['diadao_sdk_' + t_navItemObjectsPostType + '_categories_multiple_enabled']!==undefined\n&&window['diadao_sdk_' + t_navItemObjectsPostType + '_categories_multiple_enabled']!==null \n&&parseInt(window['diadao_sdk_' + t_navItemObjectsPostType + '_categories_multiple_enabled'])===1\n)\n                        {\n                            t_navItemObjectsMultiFilterEnabled=true;\n                        }\n                    }\n\n                    if(that.debug['filter_menu']) console.log('t_navItemObjectsMultiFilterEnabled', t_navItemObjectsMultiFilterEnabled, t_navItem.hasClass('active'), t_navItem, 't_navItemObjectsMultiFilterEnabled', t_navItemObjectsMultiFilterEnabled);\n\n                    if(\n                        !t_navItemObjectsMultiFilterEnabled \n&&t_navItem.hasClass('active')\n)   \n                    {\n                        // do nothing\n                        if(that.debug['filter_menu']) console.log('-> filters:do nothing ?');\n                    }\n                    else\n                    {\n                        if(that.debug['filter_menu']) console.log('-> filters multiple enabled:', t_navItemObjectsPostType, t_navItemObjectsMultiFilterEnabled, t_navItemSlug);\n                        \n                        if(\n                            t_navItemObjectsMultiFilterEnabled \n&&t_navItemSlug!=='all'\n&&t_navItemSlug!=='*'\n)\n                        {\n                            if(t_navItem.hasClass('active') )\n                            {\n                                t_navItem.removeClass('active');\n\n                                if(that.debug['filter_menu']) console.log('-> multiple remove active', jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active').length, t_navItemObjectsFilterAllLink);\n                                \n                                if(\n                                    t_navItemObjectsFilterAllEnabled \n&&!jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active').length\n)\n                                {\n                                    t_navItemCurrent=t_navItemObjectsFilterAllLink;\n                                    t_navItemObjectsFilterAllLink.addClass('active');\n                                }\n                                else if(\n                                    !t_navItemObjectsFilterAllEnabled\n&&!jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active').length\n)\n                                {\n                                    t_navItemCurrent=t_navItem;\n                                    t_navItem.addClass('active');\n\n                                    if(that.debug['filter_menu']) console.log('-> multiple cant remove active');\n                                }                                \n                            }\n                            else\n                            {\n                                jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active[data-slug=\"all\"]').removeClass('active');\n                                jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active[data-slug=\"*\"]').removeClass('active');\n\n                                t_navItemCurrent=t_navItem;\n                                t_navItem.addClass('active');\n                            }\n\n                            jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active').each(function(){\n                                if(\n                                    jQuery(this).data('slug')!==undefined\n&&jQuery(this).data('slug')!==null\n&&jQuery(this).data('slug')!=='all'\n&&jQuery(this).data('slug')!=='*'\n)\n                                {\n                                    t_navItemSlugsActiveArray.push(jQuery(this).data('slug') );\n                                }\n                            });\n                        }\n                        else\n                        {\n                            t_navItemCurrent=t_navItem;\n                            t_navItemSlugsActiveArray.push(t_navItemSlug);\n\n                            jQuery('#' + t_navItemParentId + ' .nav-link.link-filter.active').removeClass('active');\n                            \n                            t_navItem.addClass('active');\n                        }\n\n                        if(t_navItemSwiperEnabled)\n                        {  \n                            that.setTriggerTemporize();\n\n                            // slide to\n                            if(that.debug['filter_menu']) console.log('-> filters:slide To ?');\n\n                            if(t_navItemCurrent===undefined||!t_navItemCurrent.length)\n                            {\n                                t_navItemCurrent=t_navItem;\n                            }\n                            \n                            that.setSmartGallerySlide(t_navItemCurrent);\n                        }\n\n                        if(that.debug['filter_menu']) console.log('-> current slugs multiple:', t_navItemSlugsActiveArray);\n\n                        if(that.sdk_data['filter_menu_scrolltop_enabled'])\n                        {\n                            let t_scrollTopOffset=t_navItemContainerScrollTo.offset().top;\n                            if(\n                                jQuery('#diadao-header-buttons')!==undefined\n&&jQuery('#diadao-header-buttons').length\n)\n                            {\n                                t_scrollTopOffset=t_scrollTopOffset - parseInt(jQuery('#diadao-header-buttons').outerHeight());\n                            }\n\n                            if(\n                                jQuery('#diasdk-breadcrumb')!==undefined\n&&jQuery('#diasdk-breadcrumb').length\n&&!that.isMobile()\n)\n                            {\n                                t_scrollTopOffset=t_scrollTopOffset - parseInt(jQuery('#diasdk-breadcrumb').outerHeight());\n                            }\n                            \n                            if(that.debug['filter_menu']) console.log('scroll top with jquery', t_scrollTopOffset);\n                            jQuery('body, html').animate({\n                                scrollTop: t_scrollTopOffset\n                            }, that.timeout['scrolling_top']);     \n                        }\n\n                        t_navItemObjectsWrapper.addClass('is-filtering');           \n\n                        // let t_navItemObjectsItems=jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug*=\"' + t_navItemSlug + '\"]'); //.filter();\n                        if(t_navItemObjectsMultiFilterEnabled)\n                        {\n                            if(that.debug['filter_menu']) console.log('-> filters multiple', t_navItemSlugsActiveArray, t_navItemSlugsActiveArray.length);\n\n                            if(!t_navItemSlugsActiveArray.length)\n                            {\n                                // show all\n                                jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug]').show();\n                            }\n                            else\n                            {\n                                // hide all and then show actives\n                                jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug]').hide();\n                                jQuery.each(t_navItemSlugsActiveArray, function(i, val){\n                                    if(val==='all'||val==='*')\n                                    {\n                                        jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug]').show();\n                                    }\n                                    else\n                                    {\n                                        jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug*=\"' + val + '\"]').show();\n                                    }\n                                });\n                            }\n\n                        } \n                        else\n                        {\n                            if(that.debug['filter_menu']) console.log('-> filters simple');\n\n                            if(\n                                t_navItemSlug==='all' \n||t_navItemSlug==='*' \n)\n                            {\n                                jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug]').show();\n                            }\n                            else\n                            {\n                                jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug]').hide();\n                                jQuery('#' + t_navItemObjectsWrapperId + ' div[data-slug*=\"' + t_navItemSlug + '\"]').show();\n                            }\n                        }\n\n                        setTimeout(function(){\n                            t_navItemObjectsWrapper.removeClass('is-filtering');\n                        }, that.timeout['filtering']);\n\n                        // if(that.debug['filter_menu']) console.log('-> filters:objects wrapper id=', t_navItemObjectsWrapperId, ' search=', t_navItemObjectsWrapper, ' | test results=');\n                        // t_navItemObjectsItems\n                    \n                        window.dispatchEvent(sdkChangeFilterGallery);\n                    }\n                }\n\n            }\n        });\n    }\n\n    initSmartGallery()\n    {\n        const that=this;\n\n        that.initSmartGalleryScrollWatch();\n\n        jQuery(document).on('click', '.dia-smartgallery .nav-link.link-smartgallery', function(e) \n        {\n            e.preventDefault();\n            if(that.debug['prevent_default']) console.log('prevent default');\n\n            let t_navItem=jQuery(this);\n            if(\n                t_navItem.data('id')!==undefined \n&&t_navItem.data('id')!==null\n&&!that.triggers['temporize_scrollwatch_enabled']\n)\n            {\n                let t_navItemId=t_navItem.data('id').replace('-cat', '-media');\n\n                let t_navItemMedia=jQuery('.dia-smartgallery__medias a#' + t_navItemId).first();\n                if(\n                    t_navItemMedia===undefined\n||t_navItemMedia===null\n||!t_navItemMedia.length\n)\n                {\n                    t_navItemMedia=jQuery('.dia-smartgallery__medias a img[data-term=\"' + t_navItemId + '\"]').first();\n                    if(\n                        t_navItemMedia!==undefined\n&&t_navItemMedia.length\n)\n                    {\n                        t_navItemMedia=t_navItemMedia.parent();\n                    }\n                }\n\n                if(that.debug['smart_gallery']) console.log('-> smart true trigger id=', t_navItemId, ' t_navItemMedia=', t_navItemMedia);\n\n                if(\n                    t_navItemMedia!==undefined\n&&t_navItemMedia.length\n)\n                {\n                    that.enableSticky();\n\n                    that.setTriggerTemporize();\n                    that.setScrollWatchTemporize();\n\n                    // slide to\n                    that.setSmartGallerySlide(t_navItem);\n\n                    // set active\n                    // jQuery('.dia-smartgallery .nav-link.link-smartgallery.current:not(.active)').removeClass('current').removeClass('active');\n                    jQuery('.dia-smartgallery .nav-link.link-smartgallery.current').removeClass('current');\n                    t_navItem.addClass('current'); \n\n                    if(that.debug['scroll_top']) console.log('scroll top 4');\n\n                    let t_scrollTopValue=t_navItemMedia.offset().top - 20;\n\n                    if(that.isMobile())\n                    {\n                        const stickyFiltersBar=document.querySelector('.dia-filters');\n                        const stickyFiltersBarOffset=stickyFiltersBar.offsetHeight;\n                        const stickyFiltersBarStyle=window.getComputedStyle(stickyFiltersBar);\n                        // on veut la valeur top de la barre des filtres car elle se décale parfois en scrollTop\n                        const stickyFiltersBarTopPosition=parseFloat(stickyFiltersBarStyle.getPropertyValue('top'));\n                        t_scrollTopValue=t_navItemMedia.offset().top - t_navItemMedia.height() + (stickyFiltersBarOffset + stickyFiltersBarTopPosition - 28);\n                    }\n\n                    jQuery('body, html').animate({\n                        scrollTop: t_scrollTopValue,\n                    }, that.timeout['scrolling_top']);\n\n                    if(that.debug['smart_gallery']) console.log('-> smart true trigger media', t_navItemId, t_navItemMedia);\n                }\n            }\n        });\n    }\n\n    setSmartGallerySlide(nav_item)\n    {\n        const that=this;\n\n        if(\n            that.isMobile() \n&&nav_item!==undefined\n&&nav_item.length \n&&nav_item.parent()!==undefined\n&&nav_item.length\n)\n        {\n            let t_navItemParent=nav_item.parent();\n            let t_navItemSwiperEnabled=false;\n            let t_navItemSwiperId=null;\n            let t_navItemSwiperContainer=null;\n            let t_navItemIndex=0;\n            \n            if(that.debug['smart_gallery']) console.log('smart gallery check true ', t_navItemParent.parent().hasClass('swiper-slide') );\n\n            if(\n                t_navItemParent.parent().hasClass('swiper-slide')\n&&t_navItemParent.parent().data('index')!==undefined\n&&t_navItemParent.parent().data('index')!==null\n)\n            {\n                t_navItemSwiperEnabled=true;\n                t_navItemIndex=t_navItemParent.parent().data('index');\n                t_navItemSwiperId=t_navItemParent.parent().parent().parent().attr('id');\n                t_navItemSwiperContainer=document.querySelector('#' + t_navItemSwiperId).swiper;\n                \n                if(\n                    t_navItemSwiperContainer!==undefined\n&&t_navItemSwiperContainer!==null\n)\n                {\n                    t_navItemIndex=t_navItemIndex;\n                    if(that.debug['smart_gallery']) console.log('smart true slide to index=', t_navItemIndex, t_navItemSwiperContainer);\n\n                    t_navItemSwiperContainer.slideTo(t_navItemIndex, that.timeout['swiping']);\n                }\n            }\n        }\n    }\n\n    enableSticky()\n    {\n        const that=this;\n\n        if(\n            that.isMobile() \n&&jQuery('body').hasClass('diadao-villaaugusta') \n)\n        {\n            let t_arrayStickies=jQuery('.dia-filters.has-swiper-filters');\n            if(\n                t_arrayStickies!==undefined\n&&t_arrayStickies.length\n)\n            {\n                document.querySelector('body').classList.add('sdkFiltersStickyEnabled');\n            }\n        }\n\n        that.checkScroll();\n    }\n\n    handleStickyFilters(sticky, grid, trigger)\n    {\n        const that=this;\n\n        console.log(sticky, grid, trigger);\n\n        if(trigger.indexOf('enter')!==-1)\n        {\n            console.log('add class', sticky);\n            sticky.addClass('isSticked');\n        }\n        else if(trigger.indexOf('leave')!==-1)\n        {\n            console.log('remove class', sticky);\n            sticky.removeClass('isSticked');\n        } \n    }\n    \n    initVideoPopupAutoplay(do_play)\n    {\n        const that=this;\n        \n        if(do_play)\n        {\n            let t_elems=document.querySelectorAll('.offcanvas.show .diasdk-video[playsinline]:not([data-sdkvideopause=\"1\"])'); \n\n            if(that.debug['videos_scrollwatch']) console.log('offcanvas opened:video in offcanvas search:', t_elems);\n\n            if(\n                t_elems!==null\n&&t_elems!==undefined\n)\n            {\n                t_elems.forEach(video=> {\n                    console.log('video in offcanvas play');\n                    video.autoplay=true;\n                    video.preload='auto';\n                    video.play();\n                });\n            }\n\n            let t_elemsPlayPause=document.querySelectorAll('.offcanvas.show .diasdk-video[playsinline][data-sdkvideopause=\"1\"]'); \n\n            if(that.debug['videos_scrollwatch']) console.log('offcanvas opened:video playpause in offcanvas search:', t_elemsPlayPause);\n\n            if(\n                t_elemsPlayPause!==null\n&&t_elemsPlayPause!==undefined\n)\n            {\n                t_elemsPlayPause.forEach(video=> {\n                    console.log('video in offcanvas preload');\n                    video.preload='auto';\n                });\n            }\n        }\n        else\n        {\n            let t_elems=document.querySelectorAll('.offcanvas .diasdk-video[playsinline]'); \n\n            if(that.debug['videos_scrollwatch']) console.log('offcanvas closed:video in offcanvas search:', t_elems);\n\n            if(\n                t_elems!==null\n&&t_elems!==undefined\n)\n            {\n                t_elems.forEach(video=> {\n                    console.log('video in offcanvas pause');\n                    video.pause();\n                });\n            }\n        }\n    }\n\n    initVideoModalAutoplay()\n    {\n        const that=this;\n\n        let t_elems=document.querySelectorAll('.modal.diasdk-video-modal'); //  // .button-video[data-bs-toggle=\"modal\"]\n\n        if(that.debug['videos_scrollwatch']) console.log('video modal detected', t_elems);\n        \n        if(\n            t_elems!==null\n&&t_elems!==undefined\n)\n        {\n            let t_modalButtons=[];\n\n            t_elems.forEach(function(elem) \n            {\n                let elemVideos=elem.querySelectorAll('.diasdk-video');\n                if(\n                    elemVideos!==null\n&&elemVideos!==undefined\n)\n                {\n                    elemVideos.forEach(video=> {\n                        if(that.debug['videos_scrollwatch']) console.log('video in modal muted and paused');\n                        video.muted=true;\n                        video.pause();\n                    });\n                }\n\n                elem.addEventListener('hidden.bs.modal', function(e){\n                    if(that.debug['videos_scrollwatch']) console.log('close modal');\n\n                    // restart video site with autoplay\n                    jQuery('#' + e.target.getAttribute('id') + ' .diasdk-video[playsinline]:not([data-sdkvideopause=\"1\"])').removeAttr('autoplay');\n\n                    that.handleVideoPlayPause('#' + e.target.getAttribute('id') + ' .diasdk-video', true, true);\n                    that.handleVideoPlayPause('#main section .diasdk-video', false);\n                });\n\n            });\n\n            jQuery('.button-video[data-bs-toggle=\"modal\"]').on('click', function(e){\n                let t_modalId=jQuery(this).attr('data-bs-target');\n                if(t_modalId)\n                {\n                    t_modalId=t_modalId.replace('#', '');\n\n                    let t_modalDiv=jQuery('#' + t_modalId);\n\n                    if(\n                        t_modalDiv \n&&t_modalDiv.length\n)\n                    {\n                        let t_modalVideo=t_modalDiv.find('.diasdk-video');\n\n                        if(\n                            t_modalVideo \n&&t_modalVideo.length \n)\n                        {\n                            t_modalVideo.attr('autoplay');\n\n                            // that.handleVideoPlayPause('#' + t_modalId + ' video', false, false);\n                            that.handleVideoPlayPause('#main section .diasdk-video[playsinline]:not([data-sdkvideopause=\"1\"])', true, false);\n\n                            setTimeout(function(){\n                                if(that.debug['videos_scrollwatch']) console.log('play video in modal');\n                                t_modalVideo[0].muted=false;\n                                t_modalVideo[0].play();\n\n                                if(that.isMobile())\n                                {\n                                    t_modalVideo[0].requestFullscreen();\n                                }\n                            }, 1000);\n                        }\n                    }\n                }\n            });\n        }\n    }\n\n    initModalBootstrap()\n    {\n        const that=this;\n        \n        jQuery('[data-sdkbs-dismiss=\"super-close-offcanvas\"]').on('click', function(){ \n            if(that.debug['offcanvas']) console.log('super close');\n            that.superCloseCurrentBsOffCanvasSdk();\n        });\n\n    }\n\n\n    handleVideoPlayPause(selector=null, do_pause=false, do_rewind=true)\n    {\n        const that=this;\n\n        // play or pause video in fancy\n        let t_sdkVideos=null;\n        if(\n            typeof selector==='string' \n&&selector!==''\n&&selector!==undefined\n&&selector!==null\n&&selector\n)\n        {\n            t_sdkVideos=jQuery(selector);\n        }\n        else\n        {\n            t_sdkVideos=selector;\n        }\n\n        if(\n            t_sdkVideos!==undefined\n&&t_sdkVideos.length\n)\n        {\n            let t_elem=null, t_videoId=null, t_video=null;\n\n            t_sdkVideos.each(function(index) \n            {\n                t_elem=jQuery(this);\n\n                if(\n                    t_elem.attr('id')!==undefined\n&&t_elem.attr('id')!==null\n)\n                {\n                    t_videoId=t_elem.attr('id');\n                    t_video=document.getElementById(t_videoId);\n\n                    if(\n                        t_video!==undefined\n&&t_video!==null\n)\n                    {\n                        let isPlaying=t_video.currentTime > 0&&!t_video.paused&&!t_video.ended&&t_video.readyState > t_video.HAVE_CURRENT_DATA;\n\n                        if(do_pause)\n                        {\n                            if(isPlaying)\n                            {\n                                t_video.muted=true;\n                                t_video.pause();\n                                if(do_rewind)\n                                {\n                                    t_video.currentTime=0;\n                                }\n                                // t_video.load();\n                            }\n                        }\n                        else\n                        {\n                            if(!isPlaying)\n                            {\n                                t_video.play();\n                            }\n                        }\n                    }\n                }\n            });\n        }\n    }\n\n\n    ////////////////////////////////////////////////////////////////////////\n    //// GENERATION OPENAI /////////////////////////////////////////////////\n    ////////////////////////////////////////////////////////////////////////\n\n    // Méthode pour rechercher les vidéos dans #main, mais les initialiser uniquement dans le ScrollTrigger\n    initVideos(){\n        const that=this;\n\n        // Initialiser le ScrollTrigger pour surveiller les vidéos\n        that.initVideosScrollTrigger();\n\n        setTimeout(function(){\n            // Initialiser le comportement de lecture/pause au survol\n            that.initVideoModalAutoplay();\n\n            if(!that.isMobile())\n            {\n                // init videos play/pause on hover for desktop\n                that.initVideosPlayPauseHover();\n            }\n\n            // make it dynamic ?\n            // jQuery(window).on('resize', function (){\n            //     if(!that.isMobile())\n            //     {\n            //         // init videos play/pause on hover for desktop\n            //         that.initVideosPlayPauseHover();\n            //     }\n            // });\n            \n\n        }, 300);\n    }\n\n    initVideosSection(section, section_index, event_type)\n    {\n        const that=this;\n\n        // console.log(event_type);\n\n        if(section)\n        {\n            let videoElements;\n\n            // pause all videos from all sections except current and disable preload and mute them (security)\n            videoElements=document.querySelectorAll('#main section .diasdk-video');\n            if(\n                videoElements \n&&videoElements.length \n)\n            {\n                let videoElementsArray=Array.from(videoElements);\n                videoElementsArray.filter(function(el, index){ \n                    return index!==section_index;\n                });\n\n                if(that.debug['videos_scrollwatch']) console.log(event_type + ' -> pause all others videos not in current section (' + section_index + ')');\n\n                videoElementsArray.forEach(video=> {\n                    video.pause();\n                });\n            }\n\n            // play video de la section survolée\n            if(!that.isMobile())\n            {\n                videoElements=section.querySelectorAll('.diasdk-video[playsinline]:not([data-sdkvideopause=\"1\"])');\n            }\n            else\n            {\n                // is mobile\n                // videos play/pause (desktop) - for mobile we play it without hover behaviour\n                videoElements=section.querySelectorAll('.diasdk-video[playsinline]');\n            }\n\n            if(\n                videoElements \n&&videoElements.length \n)\n            {\n                let videoWrappers=section.querySelectorAll('.video-wrapper:not(.video-loaded)');\n                \n                if(that.debug['videos_scrollwatch']) console.log(event_type + ' -> play all videos in the current section (' + section_index + ')', videoWrappers);\n\n                if(\n                    videoWrappers \n&&videoWrappers.length \n)\n                {\n                    videoWrappers.forEach(videoWrapper=> {\n                        videoWrapper.classList.add('video-loaded');\n                    });\n                }\n\n                videoElements.forEach(video=> {\n                    video.muted=true;\n                    video.autoplay=true;\n                    video.preload='auto';\n\n                    video.play();\n                });\n            }\n        }\n    }\n\n    handleVideo(video, event_name) \n    {\n        const that=this;\n\n        if(event_name==='play')\n        {    \n            if(\n                video.paused \n&&video.hasAttribute('autoplay')\n) \n            {\n                video.play().catch(( error)=> {\n                });\n            }\n        }\n        else if(event_name==='pause')\n        {\n            if(\n                !video.paused\n&&video.hasAttribute('autoplay')\n) \n            {\n                video.pause();\n            }\n        }\n    }\n\n    // Fonction pour initialiser les vidéos dans le ScrollTrigger\n    initVideosScrollTrigger() \n    {\n        const that=this;\n\n        let main=gsap.utils.toArray('#main section');\n\n        // security video:pause all videos, mute theme, disable preload except first section\n        let videoElementsAll=document.querySelectorAll('body .diasdk-video');\n        if(\n            videoElementsAll \n&&videoElementsAll.length \n)\n        {\n            let videoElementsAllArray=Array.from(videoElementsAll);\n            \n            if(that.debug['videos_scrollwatch']) console.log('init all vidéos from site:pause / mute / preload none, total=', videoElementsAllArray.length);\n\n            videoElementsAllArray.forEach(video=> {\n                video.autoplay=false;\n                video.preload='none';\n                video.muted=true;\n                video.pause();\n            });\n        }\n\n        // except first section\n        let videoElementsFirstSection=document.querySelectorAll('#main > section:first-child .diasdk-video');\n        if(\n            videoElementsFirstSection \n&&videoElementsFirstSection.length \n)\n        {\n            let videoElementsFirstSectionArray=Array.from(videoElementsFirstSection);\n            \n            if(that.debug['videos_scrollwatch']) console.log('init first section vidéos from site:autoplay / preload auto, total=', videoElementsFirstSectionArray.length);\n            \n            videoElementsFirstSectionArray.forEach(video=> {\n                video.autoplay=true;\n                video.preload='auto';\n                video.pause();\n            });\n        }\n\n        let markerStart=\"top 55%\";\n        let markerEnd=\"bottom 45%\";\n        if(that.isMobile())\n        {\n            markerStart=\"top 55%\";\n            markerEnd=\"bottom 45%\";\n            // markerStart=\"top 80%\";\n            // markerEnd=\"top 20%\";\n        }\n\n        main.forEach(( section, section_index)=> {      \n            ScrollTrigger.create({\n                trigger: section,\n                start: markerStart,\n                end: markerEnd,\n                markers: that.debug['videos_scrollwatch'],\n                once: false,\n                onEnter: ()=> that.initVideosSection(section, section_index, 'onEnter'),\n                onEnterBack: ()=> that.initVideosSection(section, section_index, 'onEnterBack'),\n            });\n        });\n\n        // vidéos are ready to rock\n        const DiadaoWpSdkVideosReady=new Event(\"DiadaoWpSdkVideosReady\");\n        window.dispatchEvent(DiadaoWpSdkVideosReady);\n\n    }\n\n    // Méthode pour ajouter un comportement de lecture/pause au survol\n    initVideosPlayPauseHover(){\n        const that=this;\n\n        const videoElements=document.querySelectorAll('.diaPlayPauseVideo');\n\n        videoElements.forEach(videoDiv=> {\n            videoDiv.addEventListener('mouseenter', ()=> {\n                let video=videoDiv.querySelector('video');\n                if(video===null){\n                    video=videoDiv.querySelector('hls-video');\n                }\n                if(video!==null){\n                    if(!video.hasAttribute('autoplay')){\n                        video.play();\n                    }\n                }\n            });\n            videoDiv.addEventListener('mouseleave', ()=> {\n                let video=videoDiv.querySelector('video');\n                if(video===null){\n                    video=videoDiv.querySelector('hls-video');\n                }\n                if(video!==null){\n                    if(!video.hasAttribute('autoplay')){\n                        video.pause();\n                    }\n                }\n            });\n        });\n    }\n\n    initSmartGalleryScrollWatch()\n    {\n        const that=this;\n\n        if(\n            typeof ScrollTrigger!=='undefined' \n&&ScrollTrigger!==undefined\n&&ScrollTrigger!==null\n&&typeof gsap!=='undefined' \n&&gsap!==undefined\n&&gsap!==null\n)\n        {\n            if(that.debug['smart_gallery']) console.log('init smartgallery scrollwatch');\n\n            let t_smartGalleryTerms=gsap.utils.toArray('#main .dia-smartgallery__masonry .diasdk-smartgallery-term-media');\n\n            t_smartGalleryTerms.forEach(( t_term, i)=> {              \n                ScrollTrigger.create({\n                    trigger: t_term,\n                    start: 'top 20%',\n                    markers: that.debug['smart_gallery'],\n                    once: false,\n                    onEnter: ()=> that.handleSmartGalleryScrollWatch(t_term, i, 'enter'),\n                    onEnterBack: ()=> that.handleSmartGalleryScrollWatch(t_term, 'enter'),\n                });\n            });\n        }\n    }\n\n    handleSmartGalleryScrollWatch(term, index, action)\n    {\n        const that=this;\n\n        if(that.debug['smart_gallery']) console.log('smartgallery scrollwatch:term=', term, ' index=', index, ' action=', action);\n\n        if(\n            term!==undefined\n&&term!==null\n&&term.getAttribute('id')!==undefined\n&&term.getAttribute('id')!==null\n&&!that.triggers['temporize_scrollwatch_enabled']\n)\n        {\n            let term_id=term.getAttribute('id');\n            let term_img=jQuery('#' + term_id + ' img');\n\n            if(that.debug['smart_gallery']) console.log('smartgallery scrollwatch:term_img=', term_img);\n\n            if(\n                term_img!==undefined\n&&term_img.length\n&&term_img.attr('id')!==undefined\n&&term_img.attr('id')!==null\n)\n            {\n                let term_id=term_img.attr('id');\n                let t_bsSpyItemId=term_id + '-cat';\n                let t_bsSpyItem=jQuery('.nav-link[data-id=\"' + t_bsSpyItemId + '\"]');\n                if(\n                    t_bsSpyItem!==undefined\n&&t_bsSpyItem.length\n&&t_bsSpyItem.data('id')!==undefined\n&&t_bsSpyItem.data('id')!==null\n)\n                {\n                    if(that.debug['smart_gallery']) console.log('smartgallery scrollwatch:t_bsSpyItem=', t_bsSpyItem);\n\n                    // slide to\n                    that.setSmartGallerySlide(t_bsSpyItem);\n\n                    jQuery('.dia-smartgallery .nav-link.link-smartgallery.current').removeClass('current')\n                    t_bsSpyItem.addClass('current'); \n\n                    that.enableSticky();\n                }\n            }\n        }\n    }\n\n    initQsPopup()\n    {\n        const that=this;\n\n        that.containers['qs_popup_bestprice']=jQuery('#diadao-qs-snippet-booking');\n        that.containers['qs_popup_availabilities']=jQuery('#diadao-qs-availabilities-snippet-booking');\n\n        if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popups init\", that.containers['qs_popup_bestprice'], that.containers['qs_popup_availabilities']);\n\n        if(\n            (\n                that.containers['qs_popup_bestprice']!==undefined\n&&that.containers['qs_popup_bestprice'].length \n)\n||\n            (\n                that.containers['qs_popup_availabilities']!==undefined\n&&that.containers['qs_popup_availabilities'].length \n)\n)\n        {\n            that.sdk_data['qs_popup_enabled']=true;\n\n            jQuery(document).on('click', '.qs-react[data-diadao-widget=\"booking_hsc\"] .diadao-best-rate-snippet, .qs-react[data-diadao-widget=\"booking_hsc\"] i.i-best-rate, .qs-react .diadao-best-rate-snippet, .diadao-best-rate-snippet > span.hsc-best-price-subtitle', function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popup trigger 1\");\n\n                that.qsPopupHandleAvailabilities(true);\n                that.qsPopupHandleBestPrice(false);\n            });\n\n            jQuery(document).on('click', '#diadao-qs-snippet-close', function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popup trigger 2\");\n\n                that.qsPopupHandleAvailabilities(true);\n                that.qsPopupHandleBestPrice(true);\n            });\n            \n            jQuery(document).on('click', '.qs-no-availabilities-button', function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popup trigger 3\");\n\n                that.qsPopupHandleBestPrice(true);\n                that.qsPopupHandleAvailabilities(false);\n            });\n\n            jQuery(document).on('click', '#diadao-qs-availabilities-snippet-close', function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popup trigger 4\");\n\n                that.qsPopupHandleAvailabilities(true);\n            });\n\n            jQuery(document).on('click', '.diadao-button-hdp-mobile', function(e){\n                e.preventDefault();\n                if(that.debug['prevent_default']) console.log('prevent default');\n\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qs popup trigger 5\");\n\n                let t_container=null;\n                let t_parent=null;\n                let t_button=null;\n                let t_class_body='diadao-rooms-sticky-opened';\n\n                if(jQuery('body').hasClass('diadao-snippet-open-room') )\n                {\n                    t_class_body='diadao-room-sticky-opened';\n                }\n\n                if(jQuery(this).hasClass('hsc-button-sticky-rooms-close') )\n                {\n                    t_container=jQuery(this).parent();\n                    t_button=t_container.parent().find('a.diadao-button-hdp-mobile.hsc-main-button');\n                }\n                else\n                {\n                    t_container=jQuery(this).parent().find('.diadao-hdp-mobile-container');\n                    t_button=jQuery(this);\n                }\n\n                if(\n                    t_container!==undefined \n&&t_container.length \n)\n                {\n                    t_parent=t_container.parent().parent();\n\n                    if(t_container.hasClass('opened') )\n                    {\n                        jQuery('html').removeClass('diadao-hsc-sticky-opened');\n                        t_parent.removeClass('is-fullmode');\n                        t_button.removeClass('opened');\n                        t_container.removeClass('opened');\n                        jQuery('body').removeClass(t_class_body);\n                        jQuery('body').removeClass('diadao-sticky-is-fullmode');    \n                    }\n                    else\n                    {\n                        jQuery('html').addClass('diadao-hsc-sticky-opened');\n                        t_parent.addClass('is-fullmode');\n                        t_button.addClass('opened');\n                        t_container.addClass('opened');\n                        jQuery('body').addClass(t_class_body);\n                        jQuery('body').addClass('diadao-sticky-is-fullmode');\n                    }\n                }\n\n            });\n\n        }\n    }\n\n    qsPopupHandleBestPrice(close_only)\n    {\n        const that=this;\n\n        if(!that.triggers['qs_popup_bestprice'])\n        {\n            let triggered=false;\n\n            if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleBestPrice trigger | close=\", close_only, \" | is opened=\", that.containers['qs_popup_bestprice'].hasClass('opened'));\n\n            if(that.containers['qs_popup_bestprice'].hasClass('opened') )\n            {\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleBestPrice:close it\");\n\n                // that.containers['qs_popup_bestprice'].removeClass('opened');\n                function doActionClose(){\n                    that.containers['qs_popup_bestprice'].removeClass('opened');\n                    jQuery('html').removeClass('quicksearch-notice-open');\n                    jQuery('body').removeClass('quicksearch-notice-open notice-bestprice');\n                }\n                setTimeout(function(){\n                    doActionClose();\n                }, 50);\n\n                triggered=true;\n            }\n            else\n            {\n                if(!close_only)\n                {\n                    if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleBestPrice:open it\");\n\n                    function doActionOpen(){\n                        that.containers['qs_popup_bestprice'].addClass('opened');\n                        jQuery('html').addClass('quicksearch-notice-open');\n                        jQuery('body').addClass('quicksearch-notice-open notice-bestprice');\n                    }\n                    setTimeout(function(){\n                        doActionOpen();\n                    }, 50);\n\n                    triggered=true;\n                }\n            }\n\n            if(triggered)\n            {\n                that.triggers['qs_popup_bestprice']=true;\n                setTimeout(function(){\n                    that.triggers['qs_popup_bestprice']=false;\n                }, that.timeout['qs_popup']);\n            }\n        }\n    }\n\n    qsPopupHandleAvailabilities(close_only)\n    {\n        const that=this;\n\n        if(!that.triggers['qs_popup_availabilities'])\n        {\n            let triggered=false;\n\n            if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleAvailabilities trigger | close=\", close_only, \" | is opened=\", that.containers['qs_popup_availabilities'].hasClass('opened'));\n\n            if(that.containers['qs_popup_availabilities'].hasClass('opened') )\n            {\n                if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleAvailabilities:close it\");\n\n                function doActionClose(){\n                    that.containers['qs_popup_availabilities'].removeClass('opened');\n                    jQuery('html').removeClass('quicksearch-notice-open');\n                    jQuery('body').removeClass('quicksearch-notice-open notice-noavailabilities');\n                }\n                setTimeout(function(){\n                    doActionClose();\n                }, 50);\n\n                triggered=true;\n            }\n            else\n            {\n                if(!close_only)\n                {\n                    if(that.debug['qs_popup']) console.log(\"[DIADAO-SDK] -> qsPopupHandleAvailabilities:open it\");\n\n                    function doActionOpen(){\n                        that.containers['qs_popup_availabilities'].addClass('opened');\n                        jQuery('html').addClass('quicksearch-notice-open');\n                        jQuery('body').addClass('quicksearch-notice-open notice-noavailabilities');\n                    }\n                    setTimeout(function(){\n                        doActionOpen();\n                    }, 50);\n\n                    triggered=true;\n                }\n            }\n\n            if(triggered)\n            {\n                that.triggers['qs_popup_availabilities']=true;\n                setTimeout(function(){\n                    that.triggers['qs_popup_availabilities']=false;\n                }, that.timeout['qs_popup']);\n            }\n        }\n    }\n}\n\n const __WEBPACK_DEFAULT_EXPORT__=(DiadaoWpSdk);\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./src/diadaowpsdk.js?")},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   Hls: ()=> ( hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"]),\n   HlsVideoElement: ()=> ( HlsVideoElement),\n   HlsVideoMixin: ()=> ( HlsVideoMixin),\n   "default": ()=> ( hls_video_element_default)\n });\n var custom_media_element__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 16);\n var media_tracks__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 17);\n var hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__( 13);\n\n\n\nconst HlsVideoMixin=(superclass)=> {\n  return class HlsVideo extends superclass {\n    static shadowRootOptions={ ...superclass.shadowRootOptions };\n    static getTemplateHTML=(attrs)=> {\n      const { src, ...rest }=attrs;\n      return superclass.getTemplateHTML(rest);\n    };\n    #airplaySourceEl=null;\n    attributeChangedCallback(attrName, oldValue, newValue){\n      if(attrName!=="src"){\n        super.attributeChangedCallback(attrName, oldValue, newValue);\n      }\n      if(attrName==="src"&&oldValue!=newValue){\n        this.load();\n      }\n    }\n    #destroy(){\n      var _a, _b;\n      (_a=this.#airplaySourceEl)==null ? void 0:_a.remove();\n      (_b=this.nativeEl)==null ? void 0:_b.removeEventListener(\n        "webkitcurrentplaybacktargetiswirelesschanged",\n        this.#toggleHlsLoad\n);\n      if(this.api){\n        this.api.detachMedia();\n        this.api.destroy();\n        this.api=null;\n      }\n    }\n    async load(){\n      var _a;\n      this.#destroy();\n      if(!this.src){\n        return;\n      }\n      if(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].isSupported()){\n        this.api=new hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"]({\n          // Mimic the media element with an Infinity duration for live streams.\n          liveDurationInfinity: true,\n          // Disable auto quality level/fragment loading.\n          autoStartLoad: false\n        });\n        await Promise.resolve();\n        this.api.loadSource(this.src);\n        this.api.attachMedia(this.nativeEl);\n        switch (this.nativeEl.preload){\n          case "none": {\n            const loadSourceOnPlay=()=> this.api.startLoad();\n            this.nativeEl.addEventListener("play", loadSourceOnPlay, {\n              once: true\n            });\n            this.api.on(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.DESTROYING, ()=> {\n              this.nativeEl.removeEventListener("play", loadSourceOnPlay);\n            });\n            break;\n          }\n          case "metadata": {\n            const originalLength=this.api.config.maxBufferLength;\n            const originalSize=this.api.config.maxBufferSize;\n            this.api.config.maxBufferLength=1;\n            this.api.config.maxBufferSize=1;\n            const increaseBufferOnPlay=()=> {\n              this.api.config.maxBufferLength=originalLength;\n              this.api.config.maxBufferSize=originalSize;\n            };\n            this.nativeEl.addEventListener("play", increaseBufferOnPlay, {\n              once: true\n            });\n            this.api.on(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.DESTROYING, ()=> {\n              this.nativeEl.removeEventListener("play", increaseBufferOnPlay);\n            });\n            this.api.startLoad();\n            break;\n          }\n          default:\n            this.api.startLoad();\n        }\n        if(this.nativeEl.webkitCurrentPlaybackTargetIsWireless){\n          this.api.stopLoad();\n        }\n        this.nativeEl.addEventListener(\n          "webkitcurrentplaybacktargetiswirelesschanged",\n          this.#toggleHlsLoad\n);\n        this.#airplaySourceEl=document.createElement("source");\n        this.#airplaySourceEl.setAttribute("type", "application/x-mpegURL");\n        this.#airplaySourceEl.setAttribute("src", this.src);\n        this.nativeEl.disableRemotePlayback=false;\n        this.nativeEl.append(this.#airplaySourceEl);\n        const levelIdMap= new WeakMap();\n        this.api.on(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.MANIFEST_PARSED, (event, data)=> {\n          removeAllMediaTracks();\n          let videoTrack=this.videoTracks.getTrackById("main");\n          if(!videoTrack){\n            videoTrack=this.addVideoTrack("main");\n            videoTrack.id="main";\n            videoTrack.selected=true;\n          }\n          for (const [id, level] of data.levels.entries()){\n            const videoRendition=videoTrack.addRendition(\n              level.url[0],\n              level.width,\n              level.height,\n              level.videoCodec,\n              level.bitrate\n);\n            levelIdMap.set(level, `${id}`);\n            videoRendition.id=`${id}`;\n          }\n          for (let [id, a] of data.audioTracks.entries()){\n            const kind=a.default ? "main":"alternative";\n            const audioTrack=this.addAudioTrack(kind, a.name, a.lang);\n            audioTrack.id=`${id}`;\n            if(a.default){\n              audioTrack.enabled=true;\n            }\n          }\n        });\n        this.audioTracks.addEventListener("change", ()=> {\n          var _a2;\n          const audioTrackId=+((_a2=[...this.audioTracks].find((t)=> t.enabled))==null ? void 0:_a2.id);\n          const availableIds=this.api.audioTracks.map((t)=> t.id);\n          if(audioTrackId!=this.api.audioTrack&&availableIds.includes(audioTrackId)){\n            this.api.audioTrack=audioTrackId;\n          }\n        });\n        this.api.on(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.LEVELS_UPDATED, (event, data)=> {\n          const videoTrack=this.videoTracks[this.videoTracks.selectedIndex ?? 0];\n          if(!videoTrack) return;\n          const levelIds=data.levels.map((l)=> levelIdMap.get(l));\n          for (const rendition of this.videoRenditions){\n            if(rendition.id&&!levelIds.includes(rendition.id)){\n              videoTrack.removeRendition(rendition);\n            }\n          }\n        });\n        const switchRendition=(event)=> {\n          const level=event.target.selectedIndex;\n          if(level!=this.api.nextLevel){\n            smoothSwitch(level);\n          }\n        };\n        const smoothSwitch=(levelIndex)=> {\n          const currentTime=this.currentTime;\n          let flushedFwdBuffer=false;\n          const callback=(event, data)=> {\n            flushedFwdBuffer||= !Number.isFinite(data.endOffset);\n          };\n          this.api.on(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.BUFFER_FLUSHING, callback);\n          this.api.nextLevel=levelIndex;\n          this.api.off(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.BUFFER_FLUSHING, callback);\n          if(!flushedFwdBuffer){\n            this.api.trigger(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.BUFFER_FLUSHING, {\n              startOffset: currentTime + 10,\n              endOffset: Infinity,\n              type: "video"\n            });\n          }\n        };\n        (_a=this.videoRenditions)==null ? void 0:_a.addEventListener("change", switchRendition);\n        const removeAllMediaTracks=()=> {\n          for (const videoTrack of this.videoTracks){\n            this.removeVideoTrack(videoTrack);\n          }\n          for (const audioTrack of this.audioTracks){\n            this.removeAudioTrack(audioTrack);\n          }\n        };\n        this.api.once(hls_js_dist_hls_mjs__WEBPACK_IMPORTED_MODULE_2__["default"].Events.DESTROYING, removeAllMediaTracks);\n        return;\n      }\n      await Promise.resolve();\n      if(this.nativeEl.canPlayType("application/vnd.apple.mpegurl")){\n        this.nativeEl.src=this.src;\n      }\n    }\n    #toggleHlsLoad=()=> {\n      var _a, _b, _c;\n      if((_a=this.nativeEl)==null ? void 0:_a.webkitCurrentPlaybackTargetIsWireless){\n        (_b=this.api)==null ? void 0:_b.stopLoad();\n      }else{\n        (_c=this.api)==null ? void 0:_c.startLoad();\n      }\n    };\n  };\n};\nconst HlsVideoElement=HlsVideoMixin((0,media_tracks__WEBPACK_IMPORTED_MODULE_1__.MediaTracksMixin)(custom_media_element__WEBPACK_IMPORTED_MODULE_0__.CustomVideoElement));\nif(globalThis.customElements&&!globalThis.customElements.get("hls-video")){\n  globalThis.customElements.define("hls-video", HlsVideoElement);\n}\nvar hls_video_element_default=HlsVideoElement;\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/hls-video-element/dist/hls-video-element.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   Attributes: ()=> ( Attributes),\n   CustomAudioElement: ()=> ( CustomAudioElement),\n   CustomMediaMixin: ()=> ( CustomMediaMixin),\n   CustomVideoElement: ()=> ( CustomVideoElement),\n   Events: ()=> ( Events)\n });\nconst Events=[\n  "abort",\n  "canplay",\n  "canplaythrough",\n  "durationchange",\n  "emptied",\n  "encrypted",\n  "ended",\n  "error",\n  "loadeddata",\n  "loadedmetadata",\n  "loadstart",\n  "pause",\n  "play",\n  "playing",\n  "progress",\n  "ratechange",\n  "seeked",\n  "seeking",\n  "stalled",\n  "suspend",\n  "timeupdate",\n  "volumechange",\n  "waiting",\n  "waitingforkey",\n  "resize",\n  "enterpictureinpicture",\n  "leavepictureinpicture",\n  "webkitbeginfullscreen",\n  "webkitendfullscreen",\n  "webkitpresentationmodechanged"\n];\nconst Attributes=[\n  "autopictureinpicture",\n  "disablepictureinpicture",\n  "disableremoteplayback",\n  "autoplay",\n  "controls",\n  "controlslist",\n  "crossorigin",\n  "loop",\n  "muted",\n  "playsinline",\n  "poster",\n  "preload",\n  "src"\n];\nfunction getAudioTemplateHTML(attrs){\n  return (\n    \n    `\n    <style>\n      :host {\n        display: inline-flex;\n        line-height: 0;\n        flex-direction: column;\n        justify-content: end;\n      }\n\n      audio {\n        width: 100%;\n      }\n    </style>\n    <slot name="media">\n      <audio${serializeAttributes(attrs)}></audio>\n    </slot>\n    <slot></slot>\n  `\n);\n}\nfunction getVideoTemplateHTML(attrs){\n  return (\n    \n    `\n    <style>\n      :host {\n        display: inline-block;\n        line-height: 0;\n      }\n\n      video {\n        max-width: 100%;\n        max-height: 100%;\n        min-width: 100%;\n        min-height: 100%;\n        object-fit: var(--media-object-fit, contain);\n        object-position: var(--media-object-position, 50% 50%);\n      }\n\n      video::-webkit-media-text-track-container {\n        transform: var(--media-webkit-text-track-transform);\n        transition: var(--media-webkit-text-track-transition);\n      }\n    </style>\n    <slot name="media">\n      <video${serializeAttributes(attrs)}></video>\n    </slot>\n    <slot></slot>\n  `\n);\n}\nfunction CustomMediaMixin(superclass, { tag, is }){\n  const nativeElTest=globalThis.document?.createElement?.(tag, { is });\n  const nativeElProps=nativeElTest ? getNativeElProps(nativeElTest):[];\n  return class CustomMedia extends superclass {\n    static getTemplateHTML=tag.endsWith("audio") ? getAudioTemplateHTML:getVideoTemplateHTML;\n    static shadowRootOptions={ mode: "open" };\n    static Events=Events;\n    static #isDefined=false;\n    static get observedAttributes(){\n      CustomMedia.#define();\n      const natAttrs=nativeElTest?.constructor?.observedAttributes ?? [];\n      return [\n        ...natAttrs,\n        ...Attributes\n      ];\n    }\n    static #define(){\n      if(this.#isDefined) return;\n      this.#isDefined=true;\n      const propsToAttrs=new Set(this.observedAttributes);\n      propsToAttrs.delete("muted");\n      for (const prop of nativeElProps){\n        if(prop in this.prototype) continue;\n        if(typeof nativeElTest[prop]==="function"){\n          this.prototype[prop]=function(...args){\n            this.#init();\n            const fn=()=> {\n              if(this.call) return this.call(prop, ...args);\n              const nativeFn=this.nativeEl?.[prop];\n              return nativeFn?.apply(this.nativeEl, args);\n            };\n            return fn();\n          };\n        }else{\n          const config={\n            get(){\n              this.#init();\n              const attr=prop.toLowerCase();\n              if(propsToAttrs.has(attr)){\n                const val=this.getAttribute(attr);\n                return val===null ? false:val==="" ? true:val;\n              }\n              return this.get?.(prop) ?? this.nativeEl?.[prop];\n            }\n          };\n          if(prop!==prop.toUpperCase()){\n            config.set=function(val){\n              this.#init();\n              const attr=prop.toLowerCase();\n              if(propsToAttrs.has(attr)){\n                if(val===true||val===false||val==null){\n                  this.toggleAttribute(attr, Boolean(val));\n                }else{\n                  this.setAttribute(attr, val);\n                }\n                return;\n              }\n              if(this.set){\n                this.set(prop, val);\n                return;\n              }\n              if(this.nativeEl){\n                this.nativeEl[prop]=val;\n              }\n            };\n          }\n          Object.defineProperty(this.prototype, prop, config);\n        }\n      }\n    }\n    // Private fields\n    #isInit=false;\n    #nativeEl=null;\n    #childMap= new Map();\n    #childObserver;\n    get;\n    set;\n    call;\n    // If the custom element is defined before the custom element\'s HTML is parsed\n    // no attributes will be available in the constructor (construction process).\n    // Wait until initializing in the attributeChangedCallback or\n    // connectedCallback or accessing any properties.\n    get nativeEl(){\n      this.#init();\n      return this.#nativeEl ?? this.querySelector(":scope > [slot=media]") ?? this.querySelector(tag) ?? this.shadowRoot?.querySelector(tag) ?? null;\n    }\n    set nativeEl(val){\n      this.#nativeEl=val;\n    }\n    get defaultMuted(){\n      return this.hasAttribute("muted");\n    }\n    set defaultMuted(val){\n      this.toggleAttribute("muted", val);\n    }\n    get src(){\n      return this.getAttribute("src");\n    }\n    set src(val){\n      this.setAttribute("src", `${val}`);\n    }\n    get preload(){\n      return this.getAttribute("preload") ?? this.nativeEl?.preload;\n    }\n    set preload(val){\n      this.setAttribute("preload", `${val}`);\n    }\n    #init(){\n      if(this.#isInit) return;\n      this.#isInit=true;\n      this.init();\n    }\n    init(){\n      if(!this.shadowRoot){\n        this.attachShadow({ mode: "open" });\n        const attrs=namedNodeMapToObject(this.attributes);\n        if(is) attrs.is=is;\n        if(tag) attrs.part=tag;\n        this.shadowRoot.innerHTML=this.constructor.getTemplateHTML(attrs);\n      }\n      this.nativeEl.muted=this.hasAttribute("muted");\n      for (const prop of nativeElProps){\n        this.#upgradeProperty(prop);\n      }\n      this.#childObserver=new MutationObserver(this.#syncMediaChildAttribute.bind(this));\n      this.shadowRoot.addEventListener("slotchange", this);\n      this.#syncMediaChildren();\n      for (const type of this.constructor.Events){\n        this.shadowRoot?.addEventListener(type, this, true);\n      }\n    }\n    handleEvent(event){\n      if(event.type==="slotchange"){\n        this.#syncMediaChildren();\n        return;\n      }\n      if(event.target===this.nativeEl){\n        this.dispatchEvent(new CustomEvent(event.type, { detail: event.detail }));\n      }\n    }\n    #syncMediaChildren(){\n      const removeNativeChildren=new Map(this.#childMap);\n      const defaultSlot=this.shadowRoot?.querySelector("slot:not([name])");\n      const mediaChildren=defaultSlot?.assignedElements({ flatten: true }).filter((el)=> ["track", "source"].includes(el.localName));\n      mediaChildren.forEach((el)=> {\n        removeNativeChildren.delete(el);\n        let clone=this.#childMap.get(el);\n        if(!clone){\n          clone=el.cloneNode();\n          this.#childMap.set(el, clone);\n          this.#childObserver?.observe(el, { attributes: true });\n        }\n        this.nativeEl?.append(clone);\n        this.#enableDefaultTrack(clone);\n      });\n      removeNativeChildren.forEach((clone, el)=> {\n        clone.remove();\n        this.#childMap.delete(el);\n      });\n    }\n    #syncMediaChildAttribute(mutations){\n      for (const mutation of mutations){\n        if(mutation.type==="attributes"){\n          const { target, attributeName }=mutation;\n          const clone=this.#childMap.get(target);\n          if(clone&&attributeName){\n            clone.setAttribute(attributeName, target.getAttribute(attributeName) ?? "");\n            this.#enableDefaultTrack(clone);\n          }\n        }\n      }\n    }\n    #enableDefaultTrack(trackEl){\n      if(trackEl&&trackEl.localName==="track"&&trackEl.default&&(trackEl.kind==="chapters"||trackEl.kind==="metadata")&&trackEl.track.mode==="disabled"){\n        trackEl.track.mode="hidden";\n      }\n    }\n    #upgradeProperty(prop){\n      if(Object.prototype.hasOwnProperty.call(this, prop)){\n        const value=this[prop];\n        delete this[prop];\n        this[prop]=value;\n      }\n    }\n    attributeChangedCallback(attrName, oldValue, newValue){\n      this.#init();\n      this.#forwardAttribute(attrName, oldValue, newValue);\n    }\n    #forwardAttribute(attrName, _oldValue, newValue){\n      if(["id", "class"].includes(attrName)) return;\n      if(!CustomMedia.observedAttributes.includes(attrName)&&this.constructor.observedAttributes.includes(attrName)){\n        return;\n      }\n      if(newValue===null){\n        this.nativeEl?.removeAttribute(attrName);\n      }else if(this.nativeEl?.getAttribute(attrName)!==newValue){\n        this.nativeEl?.setAttribute(attrName, newValue);\n      }\n    }\n    connectedCallback(){\n      this.#init();\n    }\n  };\n}\nfunction getNativeElProps(nativeElTest){\n  const nativeElProps=[];\n  for (let proto=Object.getPrototypeOf(nativeElTest); proto&&proto!==HTMLElement.prototype; proto=Object.getPrototypeOf(proto)){\n    const props=Object.getOwnPropertyNames(proto);\n    nativeElProps.push(...props);\n  }\n  return nativeElProps;\n}\nfunction serializeAttributes(attrs){\n  let html="";\n  for (const key in attrs){\n    if(!Attributes.includes(key)) continue;\n    const value=attrs[key];\n    if(value==="") html +=` ${key}`;\n    else html +=` ${key}="${value}"`;\n  }\n  return html;\n}\nfunction namedNodeMapToObject(namedNodeMap){\n  const obj={};\n  for (const attr of namedNodeMap){\n    obj[attr.name]=attr.value;\n  }\n  return obj;\n}\nconst CustomVideoElement=CustomMediaMixin(globalThis.HTMLElement ?? class {\n}, {\n  tag: "video"\n});\nconst CustomAudioElement=CustomMediaMixin(globalThis.HTMLElement ?? class {\n}, {\n  tag: "audio"\n});\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/custom-media-element/dist/custom-media-element.js?')},(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n __webpack_require__.d(__webpack_exports__, {\n   AudioRendition: ()=> ( _audio_rendition_js__WEBPACK_IMPORTED_MODULE_7__.AudioRendition),\n   AudioRenditionList: ()=> ( _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_8__.AudioRenditionList),\n   AudioTrack: ()=> ( _audio_track_js__WEBPACK_IMPORTED_MODULE_5__.AudioTrack),\n   AudioTrackList: ()=> ( _audio_track_list_js__WEBPACK_IMPORTED_MODULE_6__.AudioTrackList),\n   MediaTracksMixin: ()=> ( _mixin_js__WEBPACK_IMPORTED_MODULE_0__.MediaTracksMixin),\n   RenditionEvent: ()=> ( _rendition_event_js__WEBPACK_IMPORTED_MODULE_10__.RenditionEvent),\n   TrackEvent: ()=> ( _track_event_js__WEBPACK_IMPORTED_MODULE_9__.TrackEvent),\n   VideoRendition: ()=> ( _video_rendition_js__WEBPACK_IMPORTED_MODULE_3__.VideoRendition),\n   VideoRenditionList: ()=> ( _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_4__.VideoRenditionList),\n   VideoTrack: ()=> ( _video_track_js__WEBPACK_IMPORTED_MODULE_1__.VideoTrack),\n   VideoTrackList: ()=> ( _video_track_list_js__WEBPACK_IMPORTED_MODULE_2__.VideoTrackList)\n });\n var _mixin_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__( 12);\n var _video_track_js__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__( 8);\n var _video_track_list_js__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__( 3);\n var _video_rendition_js__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__( 9);\n var _video_rendition_list_js__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__( 1);\n var _audio_track_js__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__( 10);\n var _audio_track_list_js__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__( 6);\n var _audio_rendition_js__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__( 11);\n var _audio_rendition_list_js__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__( 2);\n var _track_event_js__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__( 4);\n var _rendition_event_js__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__( 5);\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n//# sourceURL=webpack://DiadaoWpSdk/./node_modules/media-tracks/dist/index.js?")}],__webpack_module_cache__={};function __webpack_require__(t){var e=__webpack_module_cache__[t];if(void 0!==e)return e.exports;var n=__webpack_module_cache__[t]={exports:{}};return __webpack_modules__[t].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.d=(t,e)=>{for(var n in e)__webpack_require__.o(e,n)&&!__webpack_require__.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},__webpack_require__.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),__webpack_require__.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(14);return __webpack_exports__})()));